Beispiel #1
0
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()]
Beispiel #3
0
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()]
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')