def OnOpenImage(self, evt=None): # 1) Get the image key # Start with the table_id if there is one tblNum = None if p.table_id: dlg = wx.TextEntryDialog(self, p.table_id + ':', 'Enter ' + p.table_id) dlg.SetValue('0') if dlg.ShowModal() == wx.ID_OK: try: tblNum = int(dlg.GetValue()) except ValueError: errdlg = wx.MessageDialog( self, 'Invalid value for %s!' % (p.table_id), "Invalid value", wx.OK | wx.ICON_EXCLAMATION) errdlg.ShowModal() return dlg.Destroy() else: dlg.Destroy() return # Then get the image_id dlg = wx.TextEntryDialog(self, p.image_id + ':', 'Enter ' + p.image_id) dlg.SetValue('') if dlg.ShowModal() == wx.ID_OK: try: imgNum = int(dlg.GetValue()) except ValueError: errdlg = wx.MessageDialog( self, 'Invalid value for %s!' % (p.image_id), "Invalid value", wx.OK | wx.ICON_EXCLAMATION) errdlg.ShowModal() return dlg.Destroy() else: dlg.Destroy() return # Build the imkey if p.table_id: imkey = (tblNum, imgNum) else: imkey = (imgNum, ) dm = DataModel.getInstance() if imkey not in dm.GetAllImageKeys(): errdlg = wx.MessageDialog(self, 'There is no image with that key.', "Couldn't find image", wx.OK | wx.ICON_EXCLAMATION) errdlg.ShowModal() self.Destroy() else: # load the image self.img_key = imkey self.SetImage(imagetools.FetchImage(imkey), p.image_channel_colors) self.DoLayout()
def OnPaint(self, evt=None): dc = wx.PaintDC(self) dc.Clear() dc.BeginDrawing() w_win, h_win = (float(self.Size[0]), float(self.Size[1])) cols_data, rows_data = (self.data.shape[1], self.data.shape[0]) # calculate the well radius r = min(w_win / (cols_data + 1.) / 2., h_win / (rows_data + 1.) / 2.) - 0.5 # calculate start position to draw at so image is centered. w_data, h_data = ((cols_data + 1) * 2. * (r + 0.5), (rows_data + 1) * 2. * (r + 0.5)) self.xo, self.yo = (0., 0.) if w_win / h_win < w_data / h_data: self.yo = (h_win - h_data) / 2 else: self.xo = (w_win - w_data) / 2 # Set font size to fit font = dc.GetFont() if r > 14: font.SetPixelSize((12, 24)) elif r > 6: font.SetPixelSize((r - 2, (r - 2) * 2)) else: font.SetPixelSize((3, 6)) wtext, htext = font.GetPixelSize()[0] * 2, font.GetPixelSize()[1] dc.SetFont(font) db = dbconnect.DBConnect.getInstance() bmp = {} imgs = {} if self.well_disp == IMAGE: if p.plate_id: wells_and_images = db.execute( 'SELECT %s, %s FROM %s WHERE %s="%s" GROUP BY %s' % (p.well_id, p.image_id, p.image_table, p.plate_id, self.plate, p.well_id)) else: wells_and_images = db.execute( 'SELECT %s, %s FROM %s GROUP BY %s' % (p.well_id, p.image_id, p.image_table, p.well_id)) for well, im in wells_and_images: imgs[well] = (im, ) elif self.well_disp == THUMBNAIL: assert p.image_thumbnail_cols, 'No thumbnail columns are defined in the database. Platemap cannot be drawn.' if p.plate_id: wells_and_images = db.execute( 'SELECT %s, %s FROM %s WHERE %s="%s" GROUP BY %s' % (p.well_id, ','.join(p.image_thumbnail_cols), p.image_table, p.plate_id, self.plate, p.well_id)) else: wells_and_images = db.execute( 'SELECT %s, %s FROM %s GROUP BY %s' % (p.well_id, ','.join( p.image_thumbnail_cols), p.image_table, p.well_id)) for wims in wells_and_images: try: ims = [Image.open(StringIO(im), 'r') for im in wims[1:]] except IOError as e: ims = [ Image.open(StringIO(b64decode(im)), 'r') for im in wims[1:] ] ims = [ np.fromstring(im.tobytes(), dtype='uint8').reshape( im.size[1], im.size[0]).astype('float32') / 255 for im in ims ] imgs[wims[0]] = ims py = self.yo for y in range(rows_data + 1): texty = py + (2. * r - htext) / 2. px = self.xo for x in range(cols_data + 1): textx = px + (2. * r - wtext) / 2. # Draw column headers if y == 0 and x != 0: dc.DrawText(self.col_labels[x - 1], textx, texty) # Draw row headers elif y != 0 and x == 0: dc.DrawText(self.row_labels[y - 1], textx, texty) px += 2 * r + 1 py += 2 * r + 1 py = self.yo font.SetPixelSize((4, 8)) dc.SetFont(font) for y in range(rows_data + 1): texty = py + (2. * r - htext) / 2. px = self.xo for x in range(cols_data + 1): textx = px + (2. * r - wtext) / 2. # Draw wells if y > 0 and x > 0: if (y - 1, x - 1) in self.selection: # thick black outline for selected wells dc.SetPen(wx.Pen("BLACK", 5)) elif tuple(self.well_keys[y - 1][x - 1]) in self.outlined: # thick gray outline for selected wells dc.SetPen(wx.Pen("BLACK", 2, style=self.outline_style)) else: # normal outline dc.SetPen(wx.Pen("GRAY", 0.5)) color = np.array( self.colormap( self.data_scaled[y - 1][x - 1])[:3]) * 255 # NaNs get no color if np.isnan(self.data[y - 1][x - 1]): dc.SetBrush(wx.Brush(color, style=wx.TRANSPARENT)) else: dc.SetBrush(wx.Brush(color)) # Draw Well Display if self.well_disp == ROUNDED: dc.DrawRoundedRectangle(px + 1, py + 1, r * 2, r * 2, r * 0.75) elif self.well_disp == CIRCLE: dc.DrawCircle(px + r + 1, py + r + 1, r) elif self.well_disp == SQUARE: dc.DrawRectangle(px + 1, py + 1, r * 2, r * 2) elif self.well_disp == THUMBNAIL: wellkey = self.GetWellKeyAtCoord(px + r, py + r) well = wellkey[-1] if imgs.has_key(well): size = imgs[well][0].shape scale = r * 2. / max(size) bmp[well] = imagetools.MergeToBitmap( imgs[well], p.image_channel_colors, scale=scale) dc.DrawBitmap(bmp[well], px + 1, py + 1) elif self.well_disp == IMAGE: p.image_buffer_size = p.plate_shape[0] * p.plate_shape[ 1] wellkey = self.GetWellKeyAtCoord(px + r, py + r) well = wellkey[-1] if imgs.has_key(well): ims = imagetools.FetchImage(imgs[well]) size = ims[0].shape scale = r * 2. / max(size) bmp[well] = imagetools.MergeToBitmap( ims, p.image_channel_colors, scale=scale) dc.DrawBitmap(bmp[well], px + 1, py + 1) # Draw text data if self.text_data is not None: if type(self.text_data[y - 1][x - 1]) == str: dc.DrawText(str(self.text_data[y - 1][x - 1]), px + 3, py + r) else: dc.SetPen(wx.Pen("GRAY", 1)) dc.DrawLine(px + 3, py + 3, px + r * 2 - 2, py + r * 2 - 2) dc.DrawLine(px + 3, py + r * 2 - 2, px + r * 2 - 2, py + 3) # Draw X elif np.isnan(self.data[y - 1][x - 1]): dc.SetPen(wx.Pen("GRAY", 1)) dc.DrawLine(px + 3, py + 3, px + r * 2 - 2, py + r * 2 - 2) dc.DrawLine(px + 3, py + r * 2 - 2, px + r * 2 - 2, py + 3) px += 2 * r + 1 py += 2 * r + 1 dc.EndDrawing() return dc
class PlateMapPanel(wx.Panel): ''' A Panel that displays a plate layout with wells that are colored by their data (in the range [0,1]). The panel provides mechanisms for selection, color mapping, setting row & column labels, and reshaping the layout. ''' def __init__(self, parent, data, well_keys, shape=None, colormap='jet', well_disp=ROUNDED, data_range=None, toggle_selection_mode=False, **kwargs): ''' ARGUMENTS: parent -- wx parent window data -- a numpy array of numeric values KEYWORD ARGUMENTS: shape -- a 2-tuple to reshape the data to (must fit the data) well_keys -- list of keys for each well eg: (plate, well) colormap -- a colormap name from matplotlib.cm well_disp -- ROUNDED, CIRCLE, SQUARE, THUMBNAIL or IMAGE data_range -- 2-tuple containing the min and max values that the data should be normalized to. Otherwise the min and max will be taken from the data (ignoring NaNs). ''' wx.Panel.__init__(self, parent, **kwargs) self.chMap = p.image_channel_colors self.plate = None self.tip = wx.ToolTip('') self.tip.Enable(False) self.SetToolTip(self.tip) self.hideLabels = False self.selection = set([]) self.outlined = [] self.repaint = False self.outline_style = wx.SHORT_DASH self.well_selection_handlers = [] # funcs to call when a well is selected self.SetColorMap(colormap) self.well_disp = well_disp self.toggle_selection_mode = toggle_selection_mode self.SetData(data, shape, data_range=data_range) self.well_keys = np.ones(np.prod(self.data.shape), dtype=np.object) for i in xrange(len(self.well_keys)): self.well_keys[i] = ('Unknown Plate','Unknown Well') self.well_keys = self.well_keys.reshape(self.data.shape) for key in well_keys: self.well_keys[DataModel.getInstance().get_well_position_from_name(key[-1])] = key ## for i, key in enumerate(well_keys): ## if i % self.data.shape[1] == 0: ## self.well_keys += [[]] ## self.well_keys[-1] += [key] if self.data.shape[0] <= len(abc): self.row_labels = ['%2s'%c for c in abc[:self.data.shape[0]]] else: self.row_labels = ['%02d'%(i+1) for i in range(self.data.shape[0])] self.col_labels = ['%02d'%i for i in range(1,self.data.shape[1]+1)] # minimum 5 sq. pixels per cell self.SetMinSize((self.data.shape[1] * 10.0, self.data.shape[0] * 10.0)) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_IDLE, self.OnIdle) self.Bind(wx.EVT_LEFT_DOWN, self.OnLClick) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_LEFT_DCLICK, self.OnDClick) self.Bind(wx.EVT_RIGHT_UP, self.OnRClick) def SetData(self, data, shape=None, data_range=None, clip_interval=None, clip_mode='rescale'): ''' data -- An iterable containing numeric values. It's shape will be used to layout the plate unless overridden by the shape parameter shape -- If passed, this will be used to reshape the data. (rows,cols) ''' self.text_data = None self.data = np.array(data).astype('float32') if shape is not None: self.data = self.data.reshape(shape) self.SetClipInterval(clip_interval, data_range, clip_mode) def SetTextData(self, data): '''data -- An iterable containing values to be printed on top of each well. The length of this must match the size of the platemap. ''' self.text_data = np.array(data) assert self.data.size == self.text_data.size self.text_data = self.text_data.reshape(self.data.shape) def SetClipInterval(self, clip_interval, data_range=None, clip_mode='rescale'): ''' Rescales/clips the colormap to fit a new range. clip_interval -- iterable pair of values to clip/rescale colors to data_range -- 2-tuple containing the extents that the data should be normalized to. Otherwise the extents will be taken from the data. clip_mode -- whether to rescale the colormap to fit the interval, or to simply clip the values. ''' if data_range is None: data_range = (np.nanmin(self.data), np.nanmax(self.data)) self.data_range = data_range if clip_interval is None: clip_interval = data_range if clip_interval[0] == clip_interval[1] or data_range[0] == data_range[1]: self.data_scaled = self.data - data_range[0] + 0.5 else: # TODO: Set max, min values to inf, -inf and draw these differently on paint if clip_mode == 'rescale': self.data_scaled = (self.data - clip_interval[0]) / (clip_interval[1] - clip_interval[0]) elif clip_mode == 'clip': self.data_scaled = (self.data - data_range[0]) / (data_range[1] - data_range[0]) scaled_interval = (clip_interval - data_range[0]) / (data_range[1] - data_range[0]) self.data_scaled[self.data_scaled < scaled_interval[0]] = 0. self.data_scaled[self.data_scaled > scaled_interval[1]] = 1. self.Refresh() def SetColLabels(self, labels): assert len(labels) >= self.data.shape[1] self.col_labels = ['%2s'%c for c in labels] self.Refresh() def SetRowLabels(self, labels): assert len(labels) >= self.data.shape[0] self.row_labels = ['%2s'%c for c in labels] self.Refresh() def SetWellDisplay(self, well_disp): ''' well_disp in PlatMapPanel.ROUNDED, PlatMapPanel.CIRCLE, PlatMapPanel.SQUARE ''' self.well_disp = well_disp self.Refresh() def SetColorMap(self, map): ''' map: the name of a matplotlib.colors.LinearSegmentedColormap instance ''' self.colormap = matplotlib.cm.get_cmap(map) self.Refresh() def SetWellKeys(self, keys): ''' keys - a 2D array of keys uniquely identifying each well in the plate.''' self.well_keys = keys def SetOutlinedWells(self, well_keys): '''well_keys: list of the well keys to flag''' self.outlined = list(set(well_keys)) self.repaint = True def OutlineWells(self, well_keys): self.outlined = list(set(self.outlined + well_keys)) self.repaint = True def UnOutlineWells(self, well_keys): self.outlined = list(set(self.outlined) - set(well_keys)) self.repaint = True def SelectWell(self, well): ''' well: 2-tuple of integers indexing a well position (row,col)''' self.selection = set([well]) self.Refresh() def ToggleSelected(self, well): ''' well: 2-tuple of integers indexing a well position (row,col)''' if well in self.selection: self.selection.remove(well) else: self.selection.add(well) self.Refresh() def GetWellAtCoord(self, x, y): ''' returns a 2 tuple of integers indexing a well position or None if there is no well at the given position. ''' if not hasattr(self, 'xo'): return None r = min(self.Size[0]/(self.data.shape[1]+1.)/2., self.Size[1]/(self.data.shape[0]+1.)/2.) - 0.5 i = int((x-2-self.xo)/(r*2+1)) j = int((y-2-self.yo)/(r*2+1)) if 0<i<=self.data.shape[1] and 0<j<=self.data.shape[0]: return (j-1,i-1) else: return None def GetWellLabelAtCoord(self, x, y): ''' returns the well label at the given x,y position ''' loc = self.GetWellAtCoord(x,y) if self.well_keys is not None and loc is not None: row, col = loc return str(self.well_keys[row][col][-1]) else: return None def GetWellKeyAtCoord(self, x, y): ''' returns the well key at the given x,y position ''' loc = self.GetWellAtCoord(x,y) if self.well_keys is not None and loc is not None: row, col = loc return self.well_keys[row][col] else: return None def GetWellKeys(self): return [tuple(wk) for wk in self.well_keys] def get_selected_well_keys(self): return [tuple(self.well_keys[row][col]) for row, col in self.selection] def OnPaint(self, evt=None): dc = wx.PaintDC(self) dc.Clear() dc.BeginDrawing() w_win, h_win = (float(self.Size[0]), float(self.Size[1])) cols_data, rows_data = (self.data.shape[1], self.data.shape[0]) # calculate the well radius r = min(w_win/(cols_data+1.)/2., h_win/(rows_data+1.)/2.) - 0.5 # calculate start position to draw at so image is centered. w_data, h_data = ((cols_data+1)*2.*(r+0.5), (rows_data+1)*2.*(r+0.5)) self.xo, self.yo = (0., 0.) if w_win/h_win < w_data/h_data: self.yo = (h_win-h_data)/2 else: self.xo = (w_win-w_data)/2 # Set font size to fit font = dc.GetFont() if r>14: font.SetPixelSize((12,24)) elif r>6: font.SetPixelSize((r-2,(r-2)*2)) else: font.SetPixelSize((3,6)) wtext, htext = font.GetPixelSize()[0]*2, font.GetPixelSize()[1] dc.SetFont(font) db = dbconnect.DBConnect.getInstance() bmp = {} imgs = {} if self.well_disp == IMAGE: if p.plate_id: wells_and_images = db.execute('SELECT %s, %s FROM %s WHERE %s="%s" GROUP BY %s'%( p.well_id, p.image_id, p.image_table, p.plate_id, self.plate, p.well_id)) else: wells_and_images = db.execute('SELECT %s, %s FROM %s GROUP BY %s'%( p.well_id, p.image_id, p.image_table, p.well_id)) for well, im in wells_and_images: imgs[well] = (im, ) elif self.well_disp == THUMBNAIL: assert p.image_thumbnail_cols, 'No thumbnail columns are defined in the database. Platemap cannot be drawn.' if p.plate_id: wells_and_images = db.execute('SELECT %s, %s FROM %s WHERE %s="%s" GROUP BY %s'%( p.well_id, ','.join(p.image_thumbnail_cols), p.image_table, p.plate_id, self.plate, p.well_id)) else: wells_and_images = db.execute('SELECT %s, %s FROM %s GROUP BY %s'%( p.well_id, ','.join(p.image_thumbnail_cols), p.image_table, p.well_id)) for wims in wells_and_images: try: ims = [Image.open(StringIO(im), 'r') for im in wims[1:]] except IOError, e: ims = [Image.open(StringIO(b64decode(im)), 'r') for im in wims[1:]] ims = [np.fromstring(im.tostring(), dtype='uint8').reshape(im.size[1], im.size[0]).astype('float32') / 255 for im in ims] imgs[wims[0]] = ims py = self.yo for y in range(rows_data+1): texty = py+(2.*r - htext)/2. px = self.xo for x in range(cols_data+1): textx = px+(2.*r - wtext)/2. # Draw column headers if y==0 and x!=0: dc.DrawText(self.col_labels[x-1], textx, texty) # Draw row headers elif y!=0 and x==0: dc.DrawText(self.row_labels[y-1], textx, texty) px += 2*r+1 py += 2*r+1 py = self.yo font.SetPixelSize((4,8)) dc.SetFont(font) for y in range(rows_data+1): texty = py+(2.*r - htext)/2. px = self.xo for x in range(cols_data+1): textx = px+(2.*r - wtext)/2. # Draw wells if y>0 and x>0: if (y-1, x-1) in self.selection: # thick black outline for selected wells dc.SetPen(wx.Pen("BLACK",5)) elif tuple(self.well_keys[y-1][x-1]) in self.outlined: # thick gray outline for selected wells dc.SetPen(wx.Pen("BLACK",2, style=self.outline_style)) else: # normal outline dc.SetPen(wx.Pen("GRAY",0.5)) color = np.array(self.colormap(self.data_scaled[y-1][x-1])[:3]) * 255 # NaNs get no color if np.isnan(self.data[y-1][x-1]): dc.SetBrush(wx.Brush(color, style=wx.TRANSPARENT)) else: dc.SetBrush(wx.Brush(color)) # Draw Well Display if self.well_disp == ROUNDED: dc.DrawRoundedRectangle(px+1, py+1, r*2, r*2, r*0.75) elif self.well_disp == CIRCLE: dc.DrawCircle(px+r+1, py+r+1, r) elif self.well_disp == SQUARE: dc.DrawRectangle(px+1, py+1, r*2, r*2) elif self.well_disp == THUMBNAIL: wellkey = self.GetWellKeyAtCoord(px+r, py+r) well = wellkey[-1] if imgs.has_key(well): size = imgs[well][0].shape scale = r*2./max(size) bmp[well] = imagetools.MergeToBitmap(imgs[well], p.image_channel_colors, scale=scale) dc.DrawBitmap(bmp[well], px+1, py+1) elif self.well_disp == IMAGE: p.image_buffer_size = p.plate_shape[0] * p.plate_shape[1] wellkey = self.GetWellKeyAtCoord(px+r, py+r) well = wellkey[-1] if imgs.has_key(well): ims = imagetools.FetchImage(imgs[well]) size = ims[0].shape scale = r*2./max(size) bmp[well] = imagetools.MergeToBitmap(ims, p.image_channel_colors, scale=scale) dc.DrawBitmap(bmp[well], px+1, py+1) # Draw text data if self.text_data is not None: if type(self.text_data[y-1][x-1]) == str: dc.DrawText(str(self.text_data[y-1][x-1]), px+3, py+r) else: dc.SetPen(wx.Pen("GRAY",1)) dc.DrawLine(px+3, py+3, px+r*2-2, py+r*2-2) dc.DrawLine(px+3, py+r*2-2, px+r*2-2, py+3) # Draw X elif np.isnan(self.data[y-1][x-1]): dc.SetPen(wx.Pen("GRAY",1)) dc.DrawLine(px+3, py+3, px+r*2-2, py+r*2-2) dc.DrawLine(px+3, py+r*2-2, px+r*2-2, py+3) px += 2*r+1 py += 2*r+1 dc.EndDrawing() return dc