class Plugin(SlideRunnerPlugin.SlideRunnerPlugin): version = 0.1 shortName = 'Secondary database visualization' inQueue = Queue() outQueue = Queue() initialOpacity = 1.0 updateTimer = 0.1 outputType = SlideRunnerPlugin.PluginOutputType.RGB_IMAGE description = 'Visualize secondary SlideRunner database' pluginType = SlideRunnerPlugin.PluginTypes.WHOLESLIDE_PLUGIN configurationList = list( (SlideRunnerPlugin.FilePickerConfigurationEntry(uid='file', name='Database file', mask='*.sqlite'), )) COLORS = [[0, 128, 0, 255], [128, 0, 0, 255], [0, 0, 128, 255], [128, 128, 0, 255], [0, 128, 128, 255], [128, 128, 128, 255]] def __init__(self, statusQueue: Queue): self.statusQueue = statusQueue self.annotationLabels = {} self.secondaryDB = Database() self.p = Thread(target=self.queueWorker, daemon=True) self.p.start() pass def getAnnotationUpdatePolicy(): # This is important to tell SlideRunner that he needs to update for every change in position. return SlideRunnerPlugin.AnnotationUpdatePolicy.UPDATE_ON_SLIDE_CHANGE def queueWorker(self): debugModule = False quitSignal = False oldFilename = '' oldArchive = '' oldSlide = '' oldThres = -1 while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) print(job) if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal = True continue if (job.configuration['file'] == oldArchive) and (job.slideFilename == oldSlide): continue if not (os.path.exists(job.configuration['file'])): continue self.sendAnnotationLabelUpdate() oldArchive = job.configuration['file'] oldSlide = job.slideFilename self.secondaryDB.open(oldArchive) self.annos = list() self.annotationLabels = dict() for key, (label, annoId, col) in enumerate(self.secondaryDB.getAllClasses()): self.annotationLabels[ annoId] = SlideRunnerPlugin.PluginAnnotationLabel( 0, '%s' % label, [*hex_to_rgb(col), 0]) pname, fname = os.path.split(job.slideFilename) self.slideUID = self.secondaryDB.findSlideWithFilename( fname, pname) self.secondaryDB.loadIntoMemory(self.slideUID) self.annos = list() for annoId in self.secondaryDB.annotations.keys(): anno = self.secondaryDB.annotations[annoId] anno.pluginAnnotationLabel = self.annotationLabels[ anno.agreedClass] self.annos.append(anno) self.sendAnnotationLabelUpdate() self.updateAnnotations() self.setProgressBar(-1) self.setMessage('found %d annotations.' % len(self.annos)) def getAnnotations(self): return self.annos def getAnnotationLabels(self): # sending default annotation labels return [self.annotationLabels[k] for k in self.annotationLabels.keys()]
class Plugin(SlideRunnerPlugin.SlideRunnerPlugin): version = 0.1 shortName = 'Mitosis Heatmap' inQueue = Queue() outQueue = Queue() initialOpacity = 0.6 updateTimer = 0.1 outputType = SlideRunnerPlugin.PluginOutputType.BINARY_MASK description = 'Show heatmap of mitotic figures in WSI' pluginType = SlideRunnerPlugin.PluginTypes.WHOLESLIDE_PLUGIN configurationList = list( (SlideRunnerPlugin.FilePickerConfigurationEntry( uid='file', name='Result file', mask='*.p;;*.txt;;*.p.bz2'), SlideRunnerPlugin.FilePickerConfigurationEntry(uid='dbfile', name='Database file', mask='*.sqlite'), SlideRunnerPlugin.PluginConfigurationEntry(uid='threshold', name='Detection threshold', initValue=0.75, minValue=0.0, maxValue=1.0), SlideRunnerPlugin.ComboboxPluginConfigurationEntry( uid='source', name='Heatmap shows', options=['Primary Database', 'Results', 'SecondaryDatabase']))) COLORS = [[0, 128, 0, 255], [128, 0, 0, 255], [0, 0, 128, 255], [128, 128, 0, 255], [0, 128, 128, 255], [128, 128, 128, 255]] def __init__(self, statusQueue: Queue): self.statusQueue = statusQueue self.annotationLabels = { 'Detection': SlideRunnerPlugin.PluginAnnotationLabel(0, 'Detection', [0, 180, 0, 255]), } self.p = Thread(target=self.queueWorker, daemon=True) self.p.start() self.annos = [] self.downsampledMap = np.zeros((10, 10)) pass def getAnnotationUpdatePolicy(): # This is important to tell SlideRunner that he needs to update for every change in position. return SlideRunnerPlugin.AnnotationUpdatePolicy.UPDATE_ON_SLIDE_CHANGE def queueWorker(self): debugModule = False quitSignal = False oldFilename = '' oldArchive = '' oldSlide = '' oldDBfile = '' oldCoordinates = [-1, -1, -1, -1] oldThres = -1 oldSource = -1 self.ds = 32 while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) print(job) print(job.configuration) if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal = True continue sourceChanged = job.configuration['source'] != oldSource if (job.configuration['source'] == 0): if not hasattr(job.openedDatabase, 'dbfilename'): # DB not open yet continue job.configuration['dbfile'] = job.openedDatabase.dbfilename dbfilechanged = job.configuration['dbfile'] != oldDBfile if not (sourceChanged) and ( job.configuration['file'] == oldArchive ) and (job.configuration['threshold'] == oldThres) and (job.slideFilename == oldSlide) and np.all( job.coordinates == oldCoordinates) and not ( dbfilechanged): continue if not (os.path.exists(job.configuration['file'])) and ( job.configuration['source'] == 1): continue self.sendAnnotationLabelUpdate() fileChanged = job.configuration['file'] != oldArchive oldDBfile = job.configuration['dbfile'] slideChanged = job.slideFilename != oldSlide thresChanged = job.configuration['threshold'] != oldThres oldArchive = job.configuration['file'] oldThres = job.configuration['threshold'] oldSlide = job.slideFilename oldSource = job.configuration['source'] oldCoordinates = job.coordinates [foo, self.ext] = os.path.splitext(oldArchive) self.ext = self.ext.upper() self.slideObj = openslide.open_slide(job.slideFilename) if (fileChanged): if (self.ext == '.P') or ( self.ext == '.BZ2'): # Pickled format - results for many slides if (self.ext == '.BZ2'): self.resultsArchive = pickle.load( bz2.BZ2File(oldArchive, 'rb')) print('Opened bz2-compressed results container.') else: self.resultsArchive = pickle.load( open(oldArchive, 'rb')) print('Sourcechanged:', sourceChanged, 'dbfilechanged:', dbfilechanged, (len(job.configuration['dbfile']) > 0)) if (sourceChanged or dbfilechanged or slideChanged) and ( (job.configuration['source'] == 2) or (job.configuration['source'] == 0)) and (len(job.configuration['dbfile']) > 0): self.slideObj = openslide.open_slide(job.slideFilename) self.downsampledMap = np.zeros( (int(self.slideObj.dimensions[1] / self.ds), int(self.slideObj.dimensions[0] / self.ds))) self.newDB = Database() self.newDB.open(job.configuration['dbfile']) allClasses = self.newDB.getAllClasses() mitosisClass = -1 for clsname, clsuid, col in allClasses: if (mitosisClass == -1) and ('MITO' in clsname.upper()) and ( 'LOOK' not in clsname.upper()): mitosisClass = clsuid pname, fname = os.path.split(job.slideFilename) uid = self.newDB.findSlideWithFilename(fname, pname) self.newDB.loadIntoMemory(uid) for anno in self.newDB.annotations: if (self.newDB.annotations[anno].agreedClass == mitosisClass): annodet = self.newDB.annotations[anno] self.downsampledMap[int(annodet.y1 / self.ds), int(annodet.x1 / self.ds)] += 1 else: self.setMessage('No DB open.') if ((sourceChanged and job.configuration['source'] == 1) or (slideChanged) or (thresChanged)) and len(job.configuration['file']) > 0: pname, fname = os.path.split(job.slideFilename) print('Stage 6') if (oldFilename is not fname) or (slideChanged): # process slide self.annos = list() if (fname not in self.resultsArchive): self.setMessage('Slide ' + str(fname) + ' not found in results file.') print('List of files is: ', self.resultsArchive.keys()) continue oldFilename = fname uniqueLabels = np.unique( np.array(self.resultsArchive[fname])[:, 4]) self.annotationLabels = dict() for key, label in enumerate(uniqueLabels): self.annotationLabels[ label] = SlideRunnerPlugin.PluginAnnotationLabel( 0, 'Class %d' % label, self.COLORS[key % len(self.COLORS)]) if (job.configuration['source'] == 1): self.downsampledMap = np.zeros( (int(self.slideObj.dimensions[1] / self.ds), int(self.slideObj.dimensions[0] / self.ds))) print('Downsampled image: ', self.downsampledMap.shape) for idx in range(len(self.resultsArchive[fname])): row = self.resultsArchive[fname][idx] if (row[5] > job.configuration['threshold']): myanno = annotations.rectangularAnnotation( uid=idx, x1=row[0], x2=row[2], y1=row[1], y2=row[3], text='%.2f' % row[5], pluginAnnotationLabel=self.annotationLabels[ row[4]]) if (job.configuration['source'] == 1): self.downsampledMap[int( (row[1] + row[3]) / 2 / self.ds), int((row[0] + row[2]) / 2 / self.ds)] += 1 self.annos.append(myanno) self.sendAnnotationLabelUpdate() elif (self.ext == '.TXT'): # Assume MS Coco format self.annos = list() self.resultsArchive = np.loadtxt( oldArchive, dtype={ 'names': ('label', 'confidence', 'x', 'y', 'w', 'h'), 'formats': ('U30', 'f4', 'i4', 'i4', 'i4', 'i4') }, skiprows=0, delimiter=' ') uniqueLabels = np.unique(self.resultsArchive['label']) self.annotationLabels = dict() for key, label in enumerate(uniqueLabels): self.annotationLabels[ label] = SlideRunnerPlugin.PluginAnnotationLabel( 0, label, self.COLORS[key % len(self.COLORS)]) self.sendAnnotationLabelUpdate() self.slideObj = openslide.open_slide(job.slideFilename) self.ds = 32 if (job.configuration['source'] == 1): self.downsampledMap = np.zeros( (int(self.slideObj.dimensions[1] / self.ds), int(self.slideObj.dimensions[0] / self.ds))) print('Downsampled image: ', self.downsampledMap.shape) for idx in range(len(self.resultsArchive)): row = self.resultsArchive[idx] if (row[5] > job.configuration['threshold']): if (job.configuration['source'] == 1): self.downsampledMap[int( (row['y'] - row['h'] / 2) / self.ds), int((row['x'] - row['w'] / 2) / self.ds)] += 1 myanno = annotations.rectangularAnnotation( uid=idx, x1=row['x'], x2=row['y'], y1=row['x'] + row['w'], y2=row['y'] + row['h'], text='%.2f' % row['confidence'], pluginAnnotationLabel=self.annotationLabels[ row['label']]) self.annos.append(myanno) print('returning overlay...') A = 2.37 # mm^2 W_hpf_microns = np.sqrt(A * 4 / 3) * 1000 # in microns H_hpf_microns = np.sqrt(A * 3 / 4) * 1000 # in microns micronsPerPixel = self.slideObj.properties[ openslide.PROPERTY_NAME_MPP_X] W_hpf = int(W_hpf_microns / float(micronsPerPixel)) H_hpf = int(H_hpf_microns / float(micronsPerPixel)) W_x = int(W_hpf / self.ds) W_y = int(H_hpf / self.ds) kernel = np.ones((W_y, W_x), np.float32) mitoticCount = cv2.filter2D(self.downsampledMap, -1, kernel) coords_ds = np.int16(np.array(job.coordinates) / self.ds) centerImg = cv2.getRectSubPix( np.float32(mitoticCount[:, :, None]), patchSize=(coords_ds[2], coords_ds[3]), center=(coords_ds[0] + coords_ds[2] * 0.5, coords_ds[1] + coords_ds[3] * 0.5)) resized = cv2.resize(centerImg, dsize=(job.currentImage.shape[1], job.currentImage.shape[0])) self.returnImage(resized) self.updateAnnotations() self.setProgressBar(-1) self.setMessage('found %d annotations.' % len(self.annos)) def getAnnotations(self): return self.annos def getAnnotationLabels(self): # sending default annotation labels return [self.annotationLabels[k] for k in self.annotationLabels.keys()]