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] anno.clickable = job.configuration['mode'] == 0 self.annos.append(anno) self.sendAnnotationLabelUpdate() self.updateAnnotations() self.setProgressBar(-1) self.setMessage('found %d annotations.' % len(self.annos))
def queueWorker(self): quitSignal = False while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) self.total_number = int(job.configuration[0]) if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal = True continue database = job.openedDatabase annotations_count = len(database.annotations) if annotations_count != self.last_count: self.last_count = annotations_count if self.total_number - annotations_count == 0: self.setMessage('Done. Thanks for your help :)') self.showMessageBox('Done. Thanks for your help :)') elif self.total_number - annotations_count < 0: self.setMessage( 'You are ambitious, thats gread, thank you') else: self.setMessage( '{0} anotations to go'.format(self.total_number - annotations_count))
def queueWorker(self): quitSignal = False while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) image = job.currentImage if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal = True continue print('OTSU plugin: received 1 image from queue') self.setProgressBar(0) rgb = np.copy(image[:, :, 0:3]) gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY) # OTSU thresholding ret, thresh = cv2.threshold( gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) self.returnImage(np.float32(thresh / 255.0), job.procId) self.setMessage('OTSU calculation done.') print('OTSU plugin: done') self.setProgressBar(-1)
def processWholeSlide(self, job: SlideRunnerPlugin.pluginJob): filename = job.slideFilename self.slide = openslide.open_slide(filename) # 1 HPF = 0.237 mm^2 A = 0.237 # 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.slide.properties[openslide.PROPERTY_NAME_MPP_X] W_hpf = int(W_hpf_microns / float(micronsPerPixel)) H_hpf = int(H_hpf_microns / float(micronsPerPixel)) center = (int((job.coordinates[0] + 0.5 * job.coordinates[2])), int((job.coordinates[1] + 0.5 * job.coordinates[3]))) self.annos = list() myanno = SlideRunnerPlugin.rectangularAnnotation( center[0] - W_hpf / 2, center[1] - H_hpf / 2, center[0] + W_hpf / 2, center[1] + H_hpf / 2, 'High-Power Field') self.annos.append(myanno) self.updateAnnotations()
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() pass
def queueWorker(self): quitSignal = False while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) image = job.currentImage if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal = True continue print('Macenko norm plugin: received 1 image from queue') self.setProgressBar(0) print(job) if (job.annotations is not None) and len(job.annotations) > 0: if (job.annotations[0].annotationType == annotations.AnnotationType.AREA): print('Found an area annotation - great!') minC = job.annotations[0].minCoordinates() maxC = job.annotations[0].maxCoordinates() scaleX = (job.coordinates[2]) / job.currentImage.shape[1] scaleY = (job.coordinates[3]) / job.currentImage.shape[0] minC = np.array( (max(0, int((minC.x - job.coordinates[0]) / scaleX)), max(0, int((minC.y - job.coordinates[1]) / scaleY)))) maxC = np.array( (min(job.currentImage.shape[1], int((maxC.x - job.coordinates[0]) / scaleX)), min(job.currentImage.shape[0], int((maxC.y - job.coordinates[1]) / scaleY)))) rgb = np.copy(image[:, :, 0:3]) rgb = normalize(rgb, np.reshape( rgb[minC[1]:maxC[1], minC[0]:maxC[0], :], (-1, 3)), job=job) else: rgb = np.copy(image[:, :, 0:3]) rgb = normalize(rgb, job=job) print('Stats: ', np.max(np.float32(rgb)), np.min(np.float32(rgb)), np.mean(np.float32(rgb)), np.float32(rgb).shape) self.returnImage(np.float32(rgb), job.procId) self.setMessage('Macenko normalization: done.') self.setProgressBar(-1)
class Plugin(SlideRunnerPlugin.SlideRunnerPlugin): version = 0.1 shortName = 'Countdown' inQueue = Queue() outQueue = Queue() updateTimer = 0.5 description = 'Count database objects down to zero' outputType = SlideRunnerPlugin.PluginOutputType.NO_OVERLAY pluginType = SlideRunnerPlugin.PluginTypes.WHOLESLIDE_PLUGIN configurationList = list( (SlideRunnerPlugin.PluginConfigurationEntry(uid=0, name='Count down from', initValue=300., minValue=0.0, maxValue=1200.0), )) def __init__(self, statusQueue: Queue): self.statusQueue = statusQueue self.p = Thread(target=self.queueWorker, daemon=True) self.p.start() self.total_number = 300 self.last_count = None pass def queueWorker(self): quitSignal = False while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) self.total_number = int(job.configuration[0]) if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal = True continue database = job.openedDatabase annotations_count = len(database.annotations) if annotations_count != self.last_count: self.last_count = annotations_count if self.total_number - annotations_count == 0: self.setMessage('Done. Thanks for your help :)') self.showMessageBox('Done. Thanks for your help :)') elif self.total_number - annotations_count < 0: self.setMessage( 'You are ambitious, thats gread, thank you') else: self.setMessage( '{0} anotations to go'.format(self.total_number - annotations_count))
def queueWorker(self): quitSignal = False while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) filename = job if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal = True continue self.processWholeSlide(job)
def queueWorker(self): self.targetModel = -1 self.lastCoordinates = ((None, None, None, None)) modelInitialized = False oldThres = 0.0 print('Queue worker running.') quitSignal = False while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) if not (modelInitialized): self.initializeModel() modelInitialized = True if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal = True continue print('Received request for: ', job.slideFilename, job.coordinates, job.currentImage.shape) updateAnnos = False model = Models_Extensions[job.configuration[1]][1] if not (model == self.targetModel): self.targetModel = model self.loadModel(self.targetModel, int(job.configuration[2])) self.resultsStoreFilename = job.slideFilename + '_results_%s_%d.npz' % ( Models_Extensions[job.configuration[1]][0], job.configuration[2]) if (self.slideFilename is None): print('Processing complete slide ..') self.processWholeSlide(job) self.slideFilename = job.slideFilename updateAnnos = True if not (oldThres == job.configuration[0]): updateAnnos = True oldThres = job.configuration[0]
def queueWorker(self): quitSignal = False oldSlide = '' while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) filename = job if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal = True continue if (job.slideFilename != oldSlide) or (job.trigger is not None): self.processWholeSlide(job) oldSlide = job.slideFilename else: print('Trigger:', job.trigger)
def queueWorker(self): self.targetModel = '' self.targetEpoch = -1 self.lastCoordinates = ((None, None, None, None)) modelInitialized = False print('Queue worker running.') quitSignal = False while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) if not (modelInitialized): self.initializeModel() modelInitialized = True if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal = True continue print('Received request for: ', job.slideFilename, job.coordinates, job.currentImage.shape) model = Models_Extensions[job.configuration[2]][1] if not (model == self.targetModel) and (self.targetEpoch is not int( job.configuration[3])): if (self.loadModel(model, int(job.configuration[3]))): self.targetModel = model self.targetEpoch = int(job.configuration[3]) else: self.targetModel = '' self.targetEpoch = -1 self.EXTENSION = Models_Extensions[job.configuration[2]][0] if (job.configuration[3] < 150): self.EXTENSION += "_epoch%d" % int(job.configuration[3]) if (self.slideFilename is None): print('Processing complete slide ..') self.processWholeSlide(job) self.slideFilename = job.slideFilename
def queueWorker(self): quitSignal = False while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) image = job.currentImage if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal=True continue print('Macenko norm plugin: received 1 image from queue') self.setProgressBar(0) rgb = np.copy(image[:,:,0:3]) rgb = normalize(rgb) self.returnImage(np.float32(rgb), job.procId) self.setMessage('Macenko normalization: done.') self.setProgressBar(-1)
class Plugin(SlideRunnerPlugin.SlideRunnerPlugin): version = 0.1 shortName = 'Re-Stained WSI Registration (Jiang et al.)' inQueue = Queue() outQueue = Queue() updateTimer=0.5 outputType = SlideRunnerPlugin.PluginOutputType.RGB_OVERLAY description = 'Apply Jiang''s method for WSI co-registration' pluginType = SlideRunnerPlugin.PluginTypes.IMAGE_PLUGIN configurationList = list(( SlideRunnerPlugin.FilePickerConfigurationEntry(uid='file', name='Second WSI', mask='*.svs;;*.tif'), SlideRunnerPlugin.PluginConfigurationEntry(uid='xoffset', name='X Offset', initValue=0, minValue=-2000, maxValue=2000.0), SlideRunnerPlugin.PluginConfigurationEntry(uid='yoffset', name='Y Offset', initValue=0, minValue=-2000, maxValue=2000.0), SlideRunnerPlugin.PushbuttonPluginConfigurationEntry(uid='match',name='Match') )) def __init__(self, statusQueue:Queue): self.statusQueue = statusQueue self.p = Thread(target=self.queueWorker, daemon=True) self.p.start() pass def queueWorker(self): oldwsi=None quitSignal=False sl=None detected_offset = None sl_main = None while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) image = job.currentImage mainWSI = job.slideFilename if 'file' not in job.configuration: continue if (job.configuration['file'] != oldwsi) and (job.configuration['file']!='') and job.configuration['file']: sl = openslide.open_slide(job.configuration['file']) if (mainWSI): sl_main = openslide.open_slide(mainWSI) if (job.trigger is not None) and job.trigger.uid=='match': print('Trigger: ',job.trigger) self.setProgressBar(0) self.setMessage('Calculating optimum offset') tissue_detector = TissueDetector("LAB_Threshold", threshold=80) # option 1 matcher_parameters = MatcherParameters() # use the default parameters matcher = WSI_Matcher(tissue_detector, matcher_parameters) detected_offset = matcher.match(mainWSI, job.configuration['file']) self.setProgressBar(-1) updateConfig = list() updateConfig.append(SlideRunnerPlugin.PluginConfigUpdateEntry(SlideRunnerPlugin.PluginConfigurationType.SLIDER_WITH_FLOAT_VALUE, uid='xoffset', value=detected_offset[0])) updateConfig.append(SlideRunnerPlugin.PluginConfigUpdateEntry(SlideRunnerPlugin.PluginConfigurationType.SLIDER_WITH_FLOAT_VALUE, uid='yoffset', value=detected_offset[1])) self.updateConfiguration(SlideRunnerPlugin.PluginConfigUpdate(updateConfig)) if (sl) and (sl_main): self.setProgressBar(0) print('Reading from: ',job) zoomValue=job.coordinates[3]/job.currentImage.shape[0] print('Zoom value: ',zoomValue) act_level = np.argmin(np.abs(np.asarray(sl.level_downsamples)-zoomValue)) closest_ds = sl_main.level_downsamples[np.argmin(np.abs(np.asarray(sl_main.level_downsamples)-zoomValue))] offset = (job.configuration['xoffset'],job.configuration['yoffset']) if (detected_offset is not None): offset=detected_offset offset_scaled = [int(x/closest_ds) for x in offset] print('Scaled offset is: ',offset_scaled) imgarea_w=job.coordinates[2:4] size_im = (int(imgarea_w[0]/closest_ds), int(imgarea_w[1]/closest_ds)) print('Image size: ',size_im) location = [int(x+y) for x,y in zip(job.coordinates[0:2],offset)] print('Location (original):',job.coordinates[0:2]) print('Location (offset): ',location) img = sl.read_region(location=location, level=act_level, size=size_im) img = np.array(img.resize((job.currentImage.shape[1],job.currentImage.shape[0] ))) self.returnImage(img, job.procId) self.setMessage('Align done.') self.setProgressBar(-1) if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal=True continue print('OTSU plugin: received 1 image from queue')
class Plugin(SlideRunnerPlugin.SlideRunnerPlugin): version = 0.0 shortName = 'Mitosis Detection CMT' inQueue = Queue() outQueue = Queue() description = 'Direct Regression of Mitotic Activity' pluginType = SlideRunnerPlugin.PluginTypes.WHOLESLIDE_PLUGIN outputType = SlideRunnerPlugin.PluginOutputType.BINARY_MASK modelInitialized = False updateTimer = 0.1 slideFilename = None models = list() for [dirname, model] in Models_Extensions: models.append(model) configurationList = list( (SlideRunnerPlugin.PluginConfigurationEntry(uid=0, name='Detection Threshold', initValue=0.3, minValue=0.3, maxValue=1.0), SlideRunnerPlugin.ComboboxPluginConfigurationEntry(uid=1, name='Model', options=models), SlideRunnerPlugin.PluginConfigurationEntry(uid=2, name='Epoch', initValue=150.00, minValue=1.0, maxValue=150.0))) def __init__(self, statusQueue: Queue): self.statusQueue = statusQueue self.preprocessQueue = Queue() self.preprocessorOutQueue = Queue() self.slide = None self.p = Thread(target=self.queueWorker, daemon=True) self.p.start() self.currentModelCheckpoint = -1 self.pp = dict() for k in range(10): self.pp[k] = Thread(target=self.preprocessWorker, daemon=True) self.pp[k].start() def worldCoordinatesToGridcoordinates(self, x, y) -> ((int, int), (int, int)): tile_indices = (int(np.floor(x / TILESIZE_X)), int(np.floor(y / TILESIZE_Y))) onTile_coordinates = (int(np.mod(x, TILESIZE_X)), int(np.mod(y, TILESIZE_Y))) return (tile_indices, onTile_coordinates) def gridCoordinatesToWorldCoordinates( self, tile_indices: (int, int), onTile_coordinates: (int, int) ) -> (int, int): return (int(tile_indices[0] * TILESIZE_X + onTile_coordinates[0]), int(tile_indices[1] * TILESIZE_Y + onTile_coordinates[1])) def conv_conv_pool(self, input_, n_filters, training, name, pool=True, activation=tf.nn.relu): """{Conv -> BN -> RELU}x2 -> {Pool, optional} Args: input_ (4-D Tensor): (batch_size, H, W, C) n_filters (list): number of filters [int, int] training (1-D Tensor): Boolean Tensor name (str): name postfix pool (bool): If True, MaxPool2D activation: Activaion functions Returns: net: output of the Convolution operations pool (optional): output of the max pooling operations """ net = input_ with tf.variable_scope("layer{}".format(name)): for i, F in enumerate(n_filters): net = tf.layers.conv2d(net, F, (3, 3), activation=None, padding='same', name="conv_{}".format(i + 1)) net = tf.layers.batch_normalization(net, training=training, name="bn_{}".format(i + 1)) net = activation(net, name="relu{}_{}".format(name, i + 1)) if pool is False: return net pool = tf.layers.max_pooling2d(net, (2, 2), strides=(2, 2), name="pool_{}".format(name)) return net, pool def upsample_concat(self, inputA, input_B, name): """Upsample `inputA` and concat with `input_B` Args: input_A (4-D Tensor): (N, H, W, C) input_B (4-D Tensor): (N, 2*H, 2*H, C2) name (str): name of the concat operation Returns: output (4-D Tensor): (N, 2*H, 2*W, C + C2) """ upsample = self.upsampling_2D(inputA, size=(2, 2), name=name) return tf.concat([upsample, input_B], axis=-1, name="concat_{}".format(name)) def upsampling_2D(self, tensor, name, size=(2, 2)): """Upsample/Rescale `tensor` by size Args: tensor (4-D Tensor): (N, H, W, C) name (str): name of upsampling operations size (tuple, optional): (height_multiplier, width_multiplier) (default: (2, 2)) Returns: output (4-D Tensor): (N, h_multiplier * H, w_multiplier * W, C) """ H, W, _ = tensor.get_shape().as_list()[1:] H_multi, W_multi = size target_H = H * H_multi target_W = W * W_multi return tf.image.resize_nearest_neighbor( tensor, (target_H, target_W), name="upsample_{}".format(name)) def loadModel(self, modelpath, epoch): pluginDir = os.path.dirname(os.path.realpath(__file__)) modelpath = pluginDir + os.sep + modelpath modelFound = False for dirpath, dirnames, files in os.walk(modelpath): for fname in files: realfname, ext = os.path.splitext(fname) if ('_%d.ckpt' % epoch) in realfname: if (modelpath + os.sep + realfname is not self.currentModelCheckpoint): try: self.saver.restore(self.sess, modelpath + os.sep + realfname) print('Restored ', realfname) modelFound = True self.currentModelCheckpoint = modelpath + os.sep + realfname except: print('Unable to load', realfname) exit() if not modelFound: print('Model not found in ', modelpath) exit() def initializeModel(self): self.model = dict() self.setMessage('DirReg init.') self.setProgressBar(0) tf.reset_default_graph() self.netEpoch = tf.placeholder(tf.float32, name="epoch") self.model['X'] = tf.placeholder( tf.float32, shape=[None, IMAGESIZE_Y, IMAGESIZE_X, 3], name="X0") self.model['y'] = tf.placeholder( tf.float32, shape=[None, IMAGESIZE_Y, IMAGESIZE_X, 1], name="y") self.model['mode'] = tf.placeholder(tf.bool, name="mode") self.trainStep = tf.placeholder(tf.float32, name="trainStep") self.model['pred'] = make_net(self.model['X'], self.model['mode']) update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) summary_op = tf.summary.merge_all() self.sess = tf.Session() print('Initializing variables') init = tf.global_variables_initializer() self.sess.run(init) print('Done.') self.saver = tf.train.Saver() def queueWorker(self): self.targetModel = -1 self.lastCoordinates = ((None, None, None, None)) modelInitialized = False oldThres = 0.0 print('Queue worker running.') quitSignal = False while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) if not (modelInitialized): self.initializeModel() modelInitialized = True if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal = True continue print('Received request for: ', job.slideFilename, job.coordinates, job.currentImage.shape) updateAnnos = False model = Models_Extensions[job.configuration[1]][1] if not (model == self.targetModel): self.targetModel = model self.loadModel(self.targetModel, int(job.configuration[2])) self.resultsStoreFilename = job.slideFilename + '_results_%s_%d.npz' % ( Models_Extensions[job.configuration[1]][0], job.configuration[2]) if (self.slideFilename is None): print('Processing complete slide ..') self.processWholeSlide(job) self.slideFilename = job.slideFilename updateAnnos = True if not (oldThres == job.configuration[0]): updateAnnos = True oldThres = job.configuration[0] def preprocessWorker(self): while (True): if self.preprocessorOutQueue.qsize() < 500: (tile_x, tile_y, filename, coordinates, tile_current) = self.preprocessQueue.get() sl = openslide.open_slide(filename) tn = sl.read_region( location=(int(coordinates[0] - margin), int(coordinates[1] - margin)), level=0, size=(int(coordinates[2] - coordinates[0] + 2 * margin), int(coordinates[3] - coordinates[1] + 2 * margin))) X_test = np.float32( cv2.cvtColor(np.array(tn), cv2.COLOR_BGRA2RGB))[:, :, ::-1] X_test = cv2.cvtColor(X_test, cv2.COLOR_BGR2RGB) #X_test = np.reshape(X_test, newshape=[1,512,512,3]) self.preprocessorOutQueue.put( (X_test, tile_x, tile_y, coordinates, tile_current)) else: time.sleep(0.1) def processWholeSlide(self, job): self.setMessage('DirReg preparing ..') self.setProgressBar(0) filename = job.slideFilename sl = openslide.open_slide(filename) if os.path.isfile(self.resultsStoreFilename): resultsStore = np.load(self.resultsStoreFilename) self.tilesProcessed = resultsStore['tilesProcessed'].tolist() self.scores = resultsStore['scores'].tolist() else: self.tilesProcessed = list() self.scores = list() self.slide = sl tiles_total_x = int(np.floor(sl.dimensions[0] / TILESIZE_X)) tiles_total_y = int(np.floor(sl.dimensions[1] / TILESIZE_Y)) tiles_total = tiles_total_x * tiles_total_y tiles2process_total = tile_current = 0 for tile_y in range(tiles_total_y): for tile_x in range(tiles_total_x): tile_current += 1 if ([tile_x, tile_y] in self.tilesProcessed): continue coords_p1 = self.gridCoordinatesToWorldCoordinates( (tile_x, tile_y), (0, 0)) coords_p2 = self.gridCoordinatesToWorldCoordinates( (tile_x, tile_y), (TILESIZE_X, TILESIZE_Y)) # out_fullscale = self.processTile(sl, coordinates=(coords_p1+coords_p2)) coordinates = coords_p1 + coords_p2 self.preprocessQueue.put( (tile_x, tile_y, filename, coordinates, tile_current)) tiles2process_total += 1 out_tile_current = 0 batchsize = 20 batchcounter = 0 batch = np.zeros((batchsize, IMAGESIZE_Y, IMAGESIZE_X, 3)) batchTiles = list() print('Images to be processed count: ', tiles2process_total) import time gt = 0 gtf = 0 out_tile_count = 0 while (out_tile_count < tiles2process_total): t = time.time() (X_test, tile_x, tile_y, coordinates, out_tile_current) = self.preprocessorOutQueue.get() gt += time.time() - t batch[batchcounter] = X_test batchTiles.append([tile_x, tile_y]) batchcounter += 1 out_tile_count += 1 if (batchcounter == batchsize) or (out_tile_count == tiles2process_total): gtf -= time.time() out = self.evaluateImage(batch[0:batchcounter]) gtf += time.time() # print('TensorFlow: %.2f s, Preproc: %.2f s, QSize: %d ' % (gtf, gt, self.preprocessorOutQueue.qsize())) for k in range(out.shape[0]): self.scores.append(out[k]) self.tilesProcessed += batchTiles print('Length of out:', len(self.scores), len(self.tilesProcessed)) if (len(self.scores) != len(self.tilesProcessed)): print('Wrong length!') sys.exit() batchTiles = list() batchcounter = 0 self.setProgressBar(100 * out_tile_count / tiles_total) # split into chunks if (self.inQueue.qsize() > 0): # new request pending self.saveState() return print('Length of out:', len(self.scores), len(self.tilesProcessed), out_tile_count) self.saveState() self.setProgressBar(-1) self.setMessage('DirReg calculation done.') def saveState(self): np.savez_compressed(self.resultsStoreFilename, scores=self.scores, tilesProcessed=self.tilesProcessed) def exceptionHandlerOnExit(self): if (self.slide): self.saveState() def getAnnotations(self): return self.annos def evaluateImage(self, image): # if (np.all(np.mean(image, axis=(1,2,3))<200)): [y_act] = self.sess.run([self.model['pred']], feed_dict={ self.model['X']: image, self.model['mode']: False }) return y_act
class Plugin(SlideRunnerPlugin.SlideRunnerPlugin): version = 0.0 shortName = 'Mitosis UNET offline (model select, downsampled)' inQueue = Queue() outQueue = Queue() description = 'Find mitotic figures using UNET' pluginType = SlideRunnerPlugin.PluginTypes.WHOLESLIDE_PLUGIN outputType = SlideRunnerPlugin.PluginOutputType.BINARY_MASK modelInitialized = False updateTimer = 1.0 slideFilename = None models = list() for [dirname, model] in Models_Extensions: models.append(model) configurationList = list(( SlideRunnerPlugin.PluginConfigurationEntry(uid=0, name='Density Radius', initValue=0.00, minValue=0.0, maxValue=255.0), SlideRunnerPlugin.PluginConfigurationEntry(uid=1, name='Amplification', initValue=4.00, minValue=1.0, maxValue=10.0), SlideRunnerPlugin.ComboboxPluginConfigurationEntry(uid=2, name='Model', options=models), SlideRunnerPlugin.PluginConfigurationEntry(uid=3, name='Epoch', initValue=134.00, minValue=130.0, maxValue=150.0), )) def __init__(self, statusQueue: Queue): self.statusQueue = statusQueue self.preprocessQueue = Queue() self.preprocessorOutQueue = Queue() self.p = Thread(target=self.queueWorker, daemon=True) self.p.start() self.EXTENSION = '' self.pp = dict() self.lastPlugin = -1 for k in range(5): self.pp[k] = Thread(target=self.preprocessWorker, daemon=True) self.pp[k].start() def conv_conv_pool(self, input_, n_filters, training, name, pool=True, activation=tf.nn.relu): """{Conv -> BN -> RELU}x2 -> {Pool, optional} Args: input_ (4-D Tensor): (batch_size, H, W, C) n_filters (list): number of filters [int, int] training (1-D Tensor): Boolean Tensor name (str): name postfix pool (bool): If True, MaxPool2D activation: Activaion functions Returns: net: output of the Convolution operations pool (optional): output of the max pooling operations """ net = input_ with tf.variable_scope("layer{}".format(name)): for i, F in enumerate(n_filters): net = tf.layers.conv2d(net, F, (3, 3), activation=None, padding='same', name="conv_{}".format(i + 1)) net = tf.layers.batch_normalization(net, training=training, name="bn_{}".format(i + 1)) net = activation(net, name="relu{}_{}".format(name, i + 1)) if pool is False: return net pool = tf.layers.max_pooling2d(net, (2, 2), strides=(2, 2), name="pool_{}".format(name)) return net, pool def upsample_concat(self, inputA, input_B, name): """Upsample `inputA` and concat with `input_B` Args: input_A (4-D Tensor): (N, H, W, C) input_B (4-D Tensor): (N, 2*H, 2*H, C2) name (str): name of the concat operation Returns: output (4-D Tensor): (N, 2*H, 2*W, C + C2) """ upsample = self.upsampling_2D(inputA, size=(2, 2), name=name) return tf.concat([upsample, input_B], axis=-1, name="concat_{}".format(name)) def upsampling_2D(self, tensor, name, size=(2, 2)): """Upsample/Rescale `tensor` by size Args: tensor (4-D Tensor): (N, H, W, C) name (str): name of upsampling operations size (tuple, optional): (height_multiplier, width_multiplier) (default: (2, 2)) Returns: output (4-D Tensor): (N, h_multiplier * H, w_multiplier * W, C) """ H, W, _ = tensor.get_shape().as_list()[1:] H_multi, W_multi = size target_H = H * H_multi target_W = W * W_multi return tf.image.resize_nearest_neighbor( tensor, (target_H, target_W), name="upsample_{}".format(name)) def make_unet(self, Xhinted, training): """Build a U-Net architecture Args: X (4-D Tensor): (N, H, W, C) training (1-D Tensor): Boolean Tensor is required for batchnormalization layers Returns: output (4-D Tensor): (N, H, W, C) Same shape as the `input` tensor Notes: U-Net: Convolutional Networks for Biomedical Image Segmentation https://arxiv.org/abs/1505.04597 """ net = Xhinted / 127.5 - 1 net = tf.layers.conv2d(net, 4, (1, 1), name="color_space_adjust") conv1, pool1 = self.conv_conv_pool(net, [8, 8], training, name=1) conv2, pool2 = self.conv_conv_pool(pool1, [16, 16], training, name=2) conv3, pool3 = self.conv_conv_pool(pool2, [32, 32], training, name=3) conv4, pool4 = self.conv_conv_pool(pool3, [64, 64], training, name=4) conv5 = self.conv_conv_pool(pool4, [128, 128], training, name=5, pool=False) up6 = self.upsample_concat(conv5, conv4, name=6) conv6 = self.conv_conv_pool(up6, [64, 64], training, name=6, pool=False) up7 = self.upsample_concat(conv6, conv3, name=7) conv7 = self.conv_conv_pool(up7, [32, 32], training, name=7, pool=False) up8 = self.upsample_concat(conv7, conv2, name=8) conv8 = self.conv_conv_pool(up8, [16, 16], training, name=8, pool=False) up9 = self.upsample_concat(conv8, conv1, name=9) conv9 = self.conv_conv_pool(up9, [8, 8], training, name=9, pool=False) return tf.layers.conv2d(conv9, 1, (1, 1), name='final', activation=tf.nn.sigmoid, padding='same') def initializeModel(self): self.model = dict() tf.reset_default_graph() self.netEpoch = tf.placeholder(tf.float32, name="epoch") self.model['X'] = tf.placeholder( tf.float32, shape=[None, IMAGESIZE_Y, IMAGESIZE_X, 3], name="X0") self.model['y'] = tf.placeholder( tf.float32, shape=[None, IMAGESIZE_Y, IMAGESIZE_X, 1], name="y") self.model['mode'] = tf.placeholder(tf.bool, name="mode") self.trainStep = tf.placeholder(tf.float32, name="trainStep") self.model['pred'] = self.make_unet(self.model['X'], self.model['mode']) pluginDir = os.path.dirname(os.path.realpath(__file__)) update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) summary_op = tf.summary.merge_all() self.sess = tf.Session() print('Initializing variables') init = tf.global_variables_initializer() self.sess.run(init) print('Done.') self.currentModelCheckpoint = '' self.saver = tf.train.Saver() def loadModel(self, modelpath, epoch): pluginDir = os.path.dirname(os.path.realpath(__file__)) modelpath = pluginDir + os.sep + modelpath modelFound = False for dirpath, dirnames, files in os.walk(modelpath): for fname in files: realfname, ext = os.path.splitext(fname) if ('_%d.ckpt' % epoch) in realfname: if (modelpath + os.sep + realfname is not self.currentModelCheckpoint): try: self.saver.restore(self.sess, modelpath + os.sep + realfname) print('Restored ', realfname) modelFound = True self.currentModelCheckpoint = modelpath + os.sep + realfname except: print('Unable to load', realfname, 'in', modelpath) exit() if not modelFound: print('Model not found in ', modelpath) self.setMessage('Model not found in' + str(modelpath)) return False return True def queueWorker(self): self.targetModel = '' self.targetEpoch = -1 self.lastCoordinates = ((None, None, None, None)) modelInitialized = False print('Queue worker running.') quitSignal = False while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) if not (modelInitialized): self.initializeModel() modelInitialized = True if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal = True continue print('Received request for: ', job.slideFilename, job.coordinates, job.currentImage.shape) model = Models_Extensions[job.configuration[2]][1] if not (model == self.targetModel) and (self.targetEpoch is not int( job.configuration[3])): if (self.loadModel(model, int(job.configuration[3]))): self.targetModel = model self.targetEpoch = int(job.configuration[3]) else: self.targetModel = '' self.targetEpoch = -1 self.EXTENSION = Models_Extensions[job.configuration[2]][0] if (job.configuration[3] < 150): self.EXTENSION += "_epoch%d" % int(job.configuration[3]) if (self.slideFilename is None): print('Processing complete slide ..') self.processWholeSlide(job) self.slideFilename = job.slideFilename def preprocessWorker(self): while (True): if self.preprocessorOutQueue.qsize() < 100: (tile_x, tile_y, sl, coordinates, tile_current) = self.preprocessQueue.get() try: tn = sl.read_region( location=(int(coordinates[0] - margin), int(coordinates[1] - margin)), level=0, size=(int(coordinates[2] - coordinates[0] + 2 * margin), int(coordinates[3] - coordinates[1] + 2 * margin))) X_test = np.float32( cv2.cvtColor(np.array(tn), cv2.COLOR_BGRA2RGB))[:, :, ::-1] self.preprocessorOutQueue.put( (X_test, tile_x, tile_y, coordinates, tile_current)) except Exception as e: self.preprocessQueue.put( (tile_x, tile_y, sl, coordinates, tile_current)) print('Did not work, message is: ', e) print('Tiles: ', tile_x, tile_y) print('Coordinates:') print('Slide dimensions:', sl.dimensions) print('Coordinates are:', (int(coordinates[0] - margin), int(coordinates[1])), (int(coordinates[2] - coordinates[0] + 2 * margin), int(coordinates[3] - coordinates[1] + 1 * margin))) else: time.sleep(0.1) def worldCoordinatesToGridcoordinates(self, x, y) -> ((int, int), (int, int)): tile_indices = (int(np.floor(x / TILESIZE_X)), int(np.floor(y / TILESIZE_Y))) onTile_coordinates = (int(np.mod(x, TILESIZE_X)), int(np.mod(y, TILESIZE_Y))) return (tile_indices, onTile_coordinates) def gridCoordinatesToWorldCoordinates( self, tile_indices: (int, int), onTile_coordinates: (int, int) ) -> (int, int): return (int(tile_indices[0] * TILESIZE_X + onTile_coordinates[0]), int(tile_indices[1] * TILESIZE_Y + onTile_coordinates[1])) def processWholeSlide(self, job): filename = job.slideFilename sl = openslide.open_slide(filename) ds = 32 self.imageMap = np.zeros( (int(sl.dimensions[1] / ds), int(sl.dimensions[0] / ds))) self.slide = sl tiles_total_x = int(np.ceil(sl.dimensions[0] / TILESIZE_X)) tiles_total_y = int(np.ceil(sl.dimensions[1] / TILESIZE_Y)) tiles_total = tiles_total_x * tiles_total_y basefilename = job.slideFilename + self.EXTENSION + '_UNET.npz' if not os.path.exists(basefilename) and (self.targetModel is not ''): tiles2process_total = tile_current = 0 for tile_y in range(tiles_total_y): for tile_x in range(tiles_total_x): tile_current += 1 coords_p1 = self.gridCoordinatesToWorldCoordinates( (tile_x, tile_y), (0, 0)) coords_p2 = self.gridCoordinatesToWorldCoordinates( (tile_x, tile_y), (TILESIZE_X, TILESIZE_Y)) # out_fullscale = self.processTile(sl, coordinates=(coords_p1+coords_p2)) coordinates = coords_p1 + coords_p2 self.preprocessQueue.put( (tile_x, tile_y, sl, coordinates, tile_current)) tiles2process_total += 1 out_tile_current = 0 out_tile_num = 0 ds = 32 print('Image size:', sl.dimensions) print('Images to be processed count: ', tiles2process_total) while (out_tile_num < tiles2process_total): out_tile_num += 1 (X_test, tile_x, tile_y, coordinates, out_tile_current) = self.preprocessorOutQueue.get() out_fullscale = self.evaluateImage(X_test) target_size = (int(TILESIZE_Y / ds), int(TILESIZE_X / ds)) out_ds = cv2.resize(out_fullscale, dsize=(target_size)) target_x = int(coordinates[0] / ds) target_y = int(coordinates[1] / ds) #print('from coord: ',coordinates, target_x, target_y) if (coordinates[0] + IMAGESIZE_X < sl.dimensions[0]) and ( coordinates[1] + IMAGESIZE_Y < sl.dimensions[1]): self.imageMap[target_y:target_y + target_size[0], target_x:target_x + target_size[1]] = out_ds else: remainsize = self.imageMap[target_y:target_y + target_size[0], target_x:target_x + target_size[1]].shape self.imageMap[target_y:target_y + target_size[0], target_x:target_x + target_size[1]] = out_ds[0:remainsize[0], 0:remainsize[1]] self.setProgressBar(100 * out_tile_num / tiles_total) np.savez(basefilename, imageMap=self.imageMap) elif os.path.exists(basefilename): f = np.load(basefilename) self.imageMap = f['imageMap'] else: self.setMessage('No cache and no model found.') x, y, w, h = job.coordinates retImg = cv2.getRectSubPix(np.float32(np.copy(self.imageMap)), (int(w / ds), int(h / ds)), center=((x - 0.5 * w) / ds, (y - 0.5 * h) / ds)) retImg = cv2.resize(retImg, dsize=(job.currentImage.shape[1], job.currentImage.shape[0])) self.returnImage(retImg, job.procId) self.setProgressBar(-1) def evaluateImage(self, image): import cv2 overlap = 128 margin = 64 gridCoords_relative = list() # split into chunks X_test = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) X_test = np.reshape(X_test, newshape=[1, 512, 512, 3]) y_est = self.sess.run([self.model['pred']], feed_dict={ self.model['X']: X_test, self.model['mode']: False }) y_est = np.float32(np.asarray(y_est).squeeze()) mapOut = y_est[margin:IMAGESIZE_Y - margin, margin:IMAGESIZE_X - margin] return mapOut
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))
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()]
def queueWorker(self): debugModule = False quitSignal = False oldFilename = '' oldArchive = '' oldSlide = '' oldThres = -1 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 if (job.configuration['file'] == oldArchive) and (job.configuration['threshold'] == oldThres) and (job.slideFilename == oldSlide): continue if not (os.path.exists(job.configuration['file'])): continue print('Performing label update') self.sendAnnotationLabelUpdate() oldArchive = job.configuration['file'] oldThres = job.configuration['threshold'] oldSlide = job.slideFilename [foo, self.ext] = os.path.splitext(oldArchive) self.ext = self.ext.upper() self.annos = list() if (self.ext == '.P'): # Pickled format - results for many slides self.resultsArchive = pickle.load(open(oldArchive, 'rb')) pname, fname = os.path.split(job.slideFilename) fnamewithfolder = pname.split(os.sep)[-1] + os.sep + fname if (oldFilename is not fname): # process slide if (fname not in self.resultsArchive) and ( fnamewithfolder not in self.resultsArchive): self.setMessage('Slide ' + str(fname) + ' not found in results file.') print('List is:', self.resultsArchive.keys()) continue if (fnamewithfolder in self.resultsArchive): fname = fnamewithfolder 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( key, 'Class %d' % label, self.COLORS[key % len(self.COLORS)]) 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]]) self.annos.append(myanno) self.sendAnnotationLabelUpdate() elif (self.ext == '.TXT'): # Assume MS Coco format 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( key, label, self.COLORS[key % len(self.COLORS)]) self.sendAnnotationLabelUpdate() for idx in range(len(self.resultsArchive)): row = self.resultsArchive[idx] if (row[5] > job.configuration['threshold']): myanno = annotations.rectangularAnnotation( uid=idx, x1=row['x'], y1=row['y'], x2=row['x'] + row['w'], y2=row['y'] + row['h'], text='%.2f' % row['confidence'], pluginAnnotationLabel=self.annotationLabels[ row['label']]) self.annos.append(myanno) self.updateAnnotations() self.setProgressBar(-1) self.setMessage('found %d annotations.' % len(self.annos))
class Plugin(SlideRunnerPlugin.SlideRunnerPlugin): version = 0.1 shortName = 'Object Detection Results' inQueue = Queue() outQueue = Queue() initialOpacity = 1.0 updateTimer = 0.1 outputType = SlideRunnerPlugin.PluginOutputType.NO_OVERLAY description = 'Show unpickled object detection results' pluginType = SlideRunnerPlugin.PluginTypes.WHOLESLIDE_PLUGIN configurationList = list(( SlideRunnerPlugin.FilePickerConfigurationEntry(uid='file', name='Result file', mask='*.p;;*.txt'), SlideRunnerPlugin.PluginConfigurationEntry(uid='threshold', name='Detection threshold', initValue=0.75, minValue=0.0, maxValue=1.0), )) 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() 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) print(job.configuration) if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal = True continue if (job.configuration['file'] == oldArchive) and (job.configuration['threshold'] == oldThres) and (job.slideFilename == oldSlide): continue if not (os.path.exists(job.configuration['file'])): continue print('Performing label update') self.sendAnnotationLabelUpdate() oldArchive = job.configuration['file'] oldThres = job.configuration['threshold'] oldSlide = job.slideFilename [foo, self.ext] = os.path.splitext(oldArchive) self.ext = self.ext.upper() self.annos = list() if (self.ext == '.P'): # Pickled format - results for many slides self.resultsArchive = pickle.load(open(oldArchive, 'rb')) pname, fname = os.path.split(job.slideFilename) fnamewithfolder = pname.split(os.sep)[-1] + os.sep + fname if (oldFilename is not fname): # process slide if (fname not in self.resultsArchive) and ( fnamewithfolder not in self.resultsArchive): self.setMessage('Slide ' + str(fname) + ' not found in results file.') print('List is:', self.resultsArchive.keys()) continue if (fnamewithfolder in self.resultsArchive): fname = fnamewithfolder 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( key, 'Class %d' % label, self.COLORS[key % len(self.COLORS)]) 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]]) self.annos.append(myanno) self.sendAnnotationLabelUpdate() elif (self.ext == '.TXT'): # Assume MS Coco format 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( key, label, self.COLORS[key % len(self.COLORS)]) self.sendAnnotationLabelUpdate() for idx in range(len(self.resultsArchive)): row = self.resultsArchive[idx] if (row[5] > job.configuration['threshold']): myanno = annotations.rectangularAnnotation( uid=idx, x1=row['x'], y1=row['y'], x2=row['x'] + row['w'], y2=row['y'] + row['h'], text='%.2f' % row['confidence'], pluginAnnotationLabel=self.annotationLabels[ row['label']]) self.annos.append(myanno) 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()]
def queueWorker(self): oldwsi=None quitSignal=False sl=None detected_offset = None sl_main = None while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) image = job.currentImage mainWSI = job.slideFilename if 'file' not in job.configuration: continue if (job.configuration['file'] != oldwsi) and (job.configuration['file']!='') and job.configuration['file']: sl = openslide.open_slide(job.configuration['file']) if (mainWSI): sl_main = openslide.open_slide(mainWSI) if (job.trigger is not None) and job.trigger.uid=='match': print('Trigger: ',job.trigger) self.setProgressBar(0) self.setMessage('Calculating optimum offset') tissue_detector = TissueDetector("LAB_Threshold", threshold=80) # option 1 matcher_parameters = MatcherParameters() # use the default parameters matcher = WSI_Matcher(tissue_detector, matcher_parameters) detected_offset = matcher.match(mainWSI, job.configuration['file']) self.setProgressBar(-1) updateConfig = list() updateConfig.append(SlideRunnerPlugin.PluginConfigUpdateEntry(SlideRunnerPlugin.PluginConfigurationType.SLIDER_WITH_FLOAT_VALUE, uid='xoffset', value=detected_offset[0])) updateConfig.append(SlideRunnerPlugin.PluginConfigUpdateEntry(SlideRunnerPlugin.PluginConfigurationType.SLIDER_WITH_FLOAT_VALUE, uid='yoffset', value=detected_offset[1])) self.updateConfiguration(SlideRunnerPlugin.PluginConfigUpdate(updateConfig)) if (sl) and (sl_main): self.setProgressBar(0) print('Reading from: ',job) zoomValue=job.coordinates[3]/job.currentImage.shape[0] print('Zoom value: ',zoomValue) act_level = np.argmin(np.abs(np.asarray(sl.level_downsamples)-zoomValue)) closest_ds = sl_main.level_downsamples[np.argmin(np.abs(np.asarray(sl_main.level_downsamples)-zoomValue))] offset = (job.configuration['xoffset'],job.configuration['yoffset']) if (detected_offset is not None): offset=detected_offset offset_scaled = [int(x/closest_ds) for x in offset] print('Scaled offset is: ',offset_scaled) imgarea_w=job.coordinates[2:4] size_im = (int(imgarea_w[0]/closest_ds), int(imgarea_w[1]/closest_ds)) print('Image size: ',size_im) location = [int(x+y) for x,y in zip(job.coordinates[0:2],offset)] print('Location (original):',job.coordinates[0:2]) print('Location (offset): ',location) img = sl.read_region(location=location, level=act_level, size=size_im) img = np.array(img.resize((job.currentImage.shape[1],job.currentImage.shape[0] ))) self.returnImage(img, job.procId) self.setMessage('Align done.') self.setProgressBar(-1) if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal=True continue print('OTSU plugin: received 1 image from queue')
class Plugin(SlideRunnerPlugin.SlideRunnerPlugin): version = 0.0 shortName = 'High Power Field Visualization' inQueue = Queue() outQueue = Queue() description = 'Display size of 1 HPF' pluginType = SlideRunnerPlugin.PluginTypes.WHOLESLIDE_PLUGIN outputType = SlideRunnerPlugin.PluginOutputType.NO_OVERLAY modelInitialized = False updateTimer = 0.1 slideFilename = None annos = list() configurationList = list(( SlideRunnerPlugin.PluginConfigurationEntry( uid=0, name='Re-center HPF', ctype=SlideRunnerPlugin.PluginConfigurationType.PUSHBUTTON), SlideRunnerPlugin.PluginConfigurationEntry(uid=1, name='Number of HPFs', initValue=1.00, minValue=1.0, maxValue=10.0), SlideRunnerPlugin.PluginConfigurationEntry(uid=2, name='Size of HPF (mm2)', initValue=0.237, minValue=0.20, maxValue=0.3), )) #0.237 def __init__(self, statusQueue: Queue): self.statusQueue = statusQueue 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): quitSignal = False oldSlide = '' while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) filename = job if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal = True continue if (job.slideFilename != oldSlide) or (job.trigger is not None): self.processWholeSlide(job) oldSlide = job.slideFilename else: print('Trigger:', job.trigger) def getAnnotations(self): return self.annos def processWholeSlide(self, job: SlideRunnerPlugin.pluginJob): filename = job.slideFilename self.slide = openslide.open_slide(filename) # 1 HPF = 0.237 mm^2 A = job.configuration[2] # 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.slide.properties[openslide.PROPERTY_NAME_MPP_X] W_hpf = int(W_hpf_microns / float(micronsPerPixel)) * np.sqrt( float(int(job.configuration[1]))) H_hpf = int(H_hpf_microns / float(micronsPerPixel)) * np.sqrt( float(int(job.configuration[1]))) center = (int((job.coordinates[0] + 0.5 * job.coordinates[2])), int((job.coordinates[1] + 0.5 * job.coordinates[3]))) self.annos = list() if (int(job.configuration[1]) == 1): myanno = annotations.rectangularAnnotation(0, center[0] - W_hpf / 2, center[1] - H_hpf / 2, center[0] + W_hpf / 2, center[1] + H_hpf / 2, 'High-Power Field') else: myanno = annotations.rectangularAnnotation( 0, center[0] - W_hpf / 2, center[1] - H_hpf / 2, center[0] + W_hpf / 2, center[1] + H_hpf / 2, '%d High-Power Fields' % int(job.configuration[1])) self.annos.append(myanno) self.updateAnnotations()
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 = 'Normalize (Macenko)' inQueue = Queue() outQueue = Queue() initialOpacity = 1.0 updateTimer = 0.5 outputType = SlideRunnerPlugin.PluginOutputType.RGB_IMAGE description = 'H&E Image normalization (Method by Macenko)' pluginType = SlideRunnerPlugin.PluginTypes.IMAGE_PLUGIN configurationList = list( (SlideRunnerPlugin.ComboboxPluginConfigurationEntry( uid='mode', name='Mode', options=['show H&E', 'only E', 'only H'], selected_value=0), )) def __init__(self, statusQueue: Queue): self.statusQueue = statusQueue self.p = Thread(target=self.queueWorker, daemon=True) self.p.start() pass def queueWorker(self): quitSignal = False while not quitSignal: job = SlideRunnerPlugin.pluginJob(self.inQueue.get()) image = job.currentImage if (job.jobDescription == SlideRunnerPlugin.JobDescription.QUIT_PLUGIN_THREAD): # signal to exit this thread quitSignal = True continue print('Macenko norm plugin: received 1 image from queue') self.setProgressBar(0) print(job) if (job.annotations is not None) and len(job.annotations) > 0: if (job.annotations[0].annotationType == annotations.AnnotationType.AREA): print('Found an area annotation - great!') minC = job.annotations[0].minCoordinates() maxC = job.annotations[0].maxCoordinates() scaleX = (job.coordinates[2]) / job.currentImage.shape[1] scaleY = (job.coordinates[3]) / job.currentImage.shape[0] minC = np.array( (max(0, int((minC.x - job.coordinates[0]) / scaleX)), max(0, int((minC.y - job.coordinates[1]) / scaleY)))) maxC = np.array( (min(job.currentImage.shape[1], int((maxC.x - job.coordinates[0]) / scaleX)), min(job.currentImage.shape[0], int((maxC.y - job.coordinates[1]) / scaleY)))) rgb = np.copy(image[:, :, 0:3]) rgb = normalize(rgb, np.reshape( rgb[minC[1]:maxC[1], minC[0]:maxC[0], :], (-1, 3)), job=job) else: rgb = np.copy(image[:, :, 0:3]) rgb = normalize(rgb, job=job) print('Stats: ', np.max(np.float32(rgb)), np.min(np.float32(rgb)), np.mean(np.float32(rgb)), np.float32(rgb).shape) self.returnImage(np.float32(rgb), job.procId) self.setMessage('Macenko normalization: done.') self.setProgressBar(-1)