Exporting array data
With the constructor function complete we know that the Microarray objects can be made
with the required set of attributes, for the data, rows and columns etc. The next task is to
create other functions within the class definition that provide objects of that class with any
special functionality that we need. After having discussed constructing and loading data
into the Microarray objects we next turn to getting data out. As with the import functions
we will consider both text files and images, the latter of which will be handy to indicate
the changes that occur when we process and analyse the data.
We define the writeData function inside the above class definition, hence all of the code
for the function is indented. Internally the function works by opening a file object
(fileObj) to write out and loops through the rows and columns of the array, converting the
row and column identifiers to strings with str(), just in case they are stored as numbers.
def writeData(self, fileName, separator=' '):
fileObj = open(fileName, 'w')
for i in range(self.nRows):
rowName = str(self.rowData[i])
for j in range(self.nCols):
colName = str(self.colData[j])
For each row and column combination we use the indices (i,j) to get the data from the
array for all array channels:
values = self.data[:,i,j]
The line of text that will be written to the file is constructed using a list of data that has
the name of the row and column at the start and then string representations of the numeric
data in values. We convert the floating point numbers to strings with three decimal places
with the format ‘%.3f’, though we could increase the number of places if needed (see
Appendix for detailed discussion of string formatting codes).
lineData = [rowName, colName]
lineData += ['%.3f' % (v,) for v in values]
The actual line to write is created by using the separator string (by default a space) and
the .join() method to combine the separate lineData strings into one. The line is finally
written to the file object with a trailing newline character, before the loops move on to the
next item.
line = separator.join(lineData)
fileObj.write(line + '\n')
When we call this function we will do so from an instance of a Microarray object (here
called rgArray). Using example data that accompanies this book as a test
3
we can load the
array data from an image and export it as a text file:
imgFile = 'examples/RedGreenArray.png'
rgArray = loadArrayImage(imgFile, 'TwoChannel', 18, 17)
rgArray.writeData('RedGreenArrayData.txt')
For the next export example we will define an internal class function (a method) that
creates a picture representing the microarray data. This will be very useful to users and
programmers to get a visual representation of the experimental values in the array. The
second argument after self is a number that determines how large a square to use to
represent each element of the microarray, i.e. we are aiming to make a picture of the array
using coloured squares. The channels argument can be used to specify which layers of the
array data will be used to create the red, green and blue components of the image, bearing
in mind that the Microarray could have many layers of data. It will be specified as a list
(or tuple) of integer indices to select the layers and may contain None to specify that a
colour channel should be blank (zeros).
def makeImage(self, squareSize=20, channels=None):
Because we will be making an image file that uses eight data bits to store each colour
component the numeric values are adjusted so they fit the integer range 0 to 255 (2
8
−1).
Accordingly the extreme values present in the array data are found using the handy
functions built into NumPy arrays and the overall range is calculated.
minVal = self.data.min()
maxVal = self.data.max()
dataRange = maxVal - minVal
The adjusted array adjData contains pixel colour intensities and is a copy of the
self.data value array that has its lower limit subtracted (so that the pixmap has a minimum
value of zero, corresponding to black here) and which is then scaled so that the upper limit
is set to have the value 255 (the brightest colour). The array is then converted into an
unsigned 8-bit (uint8) version of itself; this way of storing numbers is the way that they
are represented in our image data.
adjData = (self.data - minVal) * 255 / dataRange
adjData = array(adjData, uint8)
Next, if the array channels (layers) to take for image construction were not passed in
then we decide on some sensible defaults. If there is only one channel in the data, the red,
green and blue components of the image (which will end up grey) will all come from the
only data layer (index 0). Otherwise we will simply take the first layers of the array up to a
maximum of three (we will fill missing RBG channels with zeros later).
if not channels:
if self.nChannels == 1:
channels = (0,0,0) # Greyscale
else:
channels = list(range(self.nChannels))[:3]
In the next step we will allow for blank colour channels. For example, if we want to
specify that an image should use red only we could set channels as (0, None, None), so
that the first array Microarray.data layer makes the red colour but there is no green or blue.
Hence if a None is found in the channels we append an array of zeros of the required size
to the pixmap list. Otherwise we add the required layer from the adjusted data. Using
channels could also result in a different colour order to the original, e.g. by specifying
channels as (2, 1, 0) the layers that usually represent red and blue would be swapped.
pixmap = []
for i in channels:
if i is None:
pixmap.append(zeros((self.nRows, self.nCols), uint8))
else:
pixmap.append(adjData[i])
We will also allow for the channels to be shorter than three, in which case we simply
add missing channels as zero arrays to pixmap.
while len(pixmap) < 3:
pixmap.append(zeros((self.nRows, self.nCols), uint8))
The three-dimensional image pixmap array is created by stacking the three colour
layers along the depth axis (hence dstack()) and this is used with the PIL module to make
an Image object called img. Given that we usually don’t want the array elements only
represented by single pixels, which would be too small to distinguish, the whole image is
resized so we have squareSize pixels in each row and column, and hence much larger
colour blocks. The final image object is then passed back at the end of the function.
pixmap = dstack(pixmap)
img = Image.fromarray(pixmap, 'RGB')
width = self.nCols * squareSize
height = self.nRows * squareSize
img = img.resize((width, height))
return img
To test the function we will again load the red and green example image as a
Microarray.
imgFile = 'examples/RedGreenArray.png'
rgArray = loadArrayImage(imgFile, 'TwoChannel', 18, 17)
The image generation function can be used to make a picture with 20×20 pixel squares,
so we can see whether the data is faithfully reproduced, albeit not in its original spotty
form. Note that we display the Image object generated immediately using its inbuilt
.show() method (see
Figure 16.2a
).
rgArray.makeImage(20).show()
Do'stlaringiz bilan baham: |