Source code for OCT_GUI.GUI_Classes.FrameManualCorrection

""" 
FrameManualCorrection: Module to create manual correction frame 
-------------------------------------------------------------------
PRLEC Framework for OCT Processing and Visualization 
"""
# This framework evolved from a collaboration of:
# - Research Laboratory of Electronics, Massachusetts Institute of Technology, Cambdrige, MA, US
# - Pattern Recognition Lab, Friedrich-Alexander-Universitaet Erlangen-Nuernberg, Germany
# - Department of Biomedical Engineering, Peking University, Beijing, China
# - New England Eye Center, Tufts Medical Center, Boston, MA, US
# v1.0: Updated on Mar 20, 2019
# @author: Daniel Stromer - EMAIL:daniel.stromer@fau.de
# Copyright (C) 2018-2019 - Daniel Stromer
# PRLE is developed as an Open Source project under the GNU General Public License (GPL) v3.0.
import numpy as np
from skimage import io
from tkinter import *
import PIL.Image, PIL.ImageTk
from skimage.transform import rescale as rsc
from Algorithms.Flattening.VolumeFlattening import unFlatten
from Algorithms.ManualCorrection.GraphCutBM import propagateBM
from Algorithms.ManualCorrection.GraphCutRPE import propagateRPE
from Algorithms.ManualCorrection.GraphCutILM import propagateILM
from Algorithms.ManualCorrection.Evaluation import calc_MSE_STDDEV
from matplotlib.colors import LinearSegmentedColormap
from FileHandler import ImportHandler
import threading
from Tooltips.TipHandler import CreateToolTip
import os
#threshold for mode
MODE_THRESHOLD = 16

[docs]def createManualCorrectionFrame(self): """ Creation of Frame for manual refinement. This is the basic module for correction integration. If a new layer is added, add the abbreviation to the option menu at the bottom (search: IntegrateOption). Furthermore, Algorithms -> ManualCorrection add a module GraphCutxxx for the specific layer and add a call to propagate this where 'IntegratePropagation' is commented. You will also have to add a Layer segmentation value at line 33 and add segmentations where the comment 'IntegrateLayerVariable' are located. Parameters: ---------- self: object Framework """ #diverging colormap c = ["blue","royalblue","white","indianred","red"] #fixed variables self.point_list = {} self.startpoint_x = None self.endpoint_x = None self.startpoint_y = None self.endpoint_y = None self.rectid = None self.segmentation_points = None self.savedslices = {} self._lines = [] self._job_xz_corr = None self._job_volume_xy_corr = None self._job_HM = None self.parent = None self.rectx0 = 0 self.recty0 = 0 self.rectx1 = 0 self.recty1 = 0 self.move = False self.GT_EXISTS = True self.enface_lines = [] #default segmentation layer self.layerSelection = "BM" # try to load ground truth automatically try: self.groundtruth = io.imread(self.initialdir+"//groundtruth.tif").astype('uint8') self.groundtruth = unFlatten(self.groundtruth, -self.shiftedValues).astype('uint8') except: self.GT_EXISTS = False def background(func): """ Background worker - doesn't stop the GUI Parameters ---------- func: function function to put in extra thread """ th = threading.Thread(target=func) th.start() def layerChange(selection): """ Change and inpaint correction layer. Parameters ---------- selection: event, StringVar selected layer from dropdown menu """ self.statusText.set("Change to:"+selection) self.layerSelection = selection resetVars() updateVolumeXZCorrSlice(None) if self.GT_EXISTS is True: createHeatmap() self.statusText.set("Change successful!") def saveSegmentation(): """ Save segmentation to directory. Saves as tif if nothing specified. """ self.statusText.set("Saving Segmentation...") f =filedialog.asksaveasfilename(initialdir = self.initialdir,title = "Save as ...",filetypes = (("all files","*.*"),("tiff","*.tiff"),("tif","*.tif"))) if f is None: return if np.logical_not(f[-4:] == '.tif' or f[-5:] =='.tiff'): f += '.tif' saveVolume = self.segmentation.copy() try: saveVolume = unFlatten(saveVolume, self.shiftedValues).astype('uint8') self.statusText.set("Segmentation saved!") except Exception as e: self.statusText.set("Attention! Segmentation not saved!") print('Error:',e) io.imsave(f, saveVolume) def leftclickXYCorr(event): """ Left click handler - Inpaint corrected line and store values Parameters ---------- event: event click event """ canvas_event = event.widget c_x = int(canvas_event.canvasx(event.x)) c_y = int(canvas_event.canvasy(event.y)) #dictionary entry self.savedslices[self.vol_sliderXZCorrection.get()] = np.zeros((1)) if self.SMALLWINDOW is True: mul_fac = 2 if(c_x % 2 == 1): c_x = (c_x - 1)//mul_fac else: c_x = c_x//mul_fac else: mul_fac = 3 if(c_x % 3 == 1): c_x = (c_x - 1)//mul_fac elif(c_x % 3 == 2): c_x = (c_x - 2)//mul_fac else: c_x = c_x//mul_fac #inpaint new point and delete old - windows is strecthed by 3 self.canvasXZCorrection.delete(str(c_x)+'a') self.canvasXZCorrection.delete(str(c_x)+'b') if mul_fac == 3: self.canvasXZCorrection.delete(str(c_x)+'c') self.segmentation_points[1][c_x] = c_y//mul_fac self.point_list[c_x] = self.canvasXZCorrection.create_oval(c_x*mul_fac, c_y, c_x*mul_fac, c_y, width = 0, fill=self.crosshair_color, tags=str(c_x)+'a') self.point_list[c_x+1] = self.canvasXZCorrection.create_oval(c_x*mul_fac+1, c_y, c_x*mul_fac+1, c_y, width = 0, fill=self.crosshair_color, tags=str(c_x)+'b') if mul_fac == 3: self.point_list[c_x+2] = self.canvasXZCorrection.create_oval(c_x*mul_fac+2, c_y, c_x*mul_fac+2, c_y, width = 0, fill=self.crosshair_color, tags=str(c_x)+'c') #write into segmentation self.segmentation[self.vol_sliderXZCorrection.get(),:,c_x] = np.zeros((self.segmentation.shape[1])) # get mode, if wrong mode, BM ''' IntegrateLayerVariable: Insert line when integrating new layer ''' if self.layerSelection is 'BM': self.segmentation[self.vol_sliderXZCorrection.get(),c_y//mul_fac,c_x] = self.BM_VALUE elif self.layerSelection is 'RPE': self.segmentation[self.vol_sliderXZCorrection.get(),c_y//mul_fac,c_x] = self.RPE_VALUE elif self.layerSelection is 'ILM': self.segmentation[self.vol_sliderXZCorrection.get(),c_y//mul_fac,c_x] = self.ILM_VALUE else: self.segmentation[self.vol_sliderXZCorrection.get(),c_y//mul_fac,c_x] = self.BM_VALUE if self.startpoint_x is None or self.endpoint_x is None: self.startpoint_x = self.endpoint_x = c_x else: if c_x < self.startpoint_x: self.startpoint_x = c_x elif c_x > self.endpoint_x: self.endpoint_x = c_x if self.SMALLWINDOW is True: self.canvasXYCorrection.create_line(self.startpoint_x//2,self.vol_sliderXZCorrection.get()//2,self.endpoint_x//2,self.vol_sliderXZCorrection.get()//2,fill=self.crosshair_color,tags='enfaceline'+str(self.vol_sliderXZCorrection.get())) else: self.canvasXYCorrection.create_line(self.startpoint_x,self.vol_sliderXZCorrection.get(),self.endpoint_x,self.vol_sliderXZCorrection.get(),fill=self.crosshair_color,tags='enfaceline'+str(self.vol_sliderXZCorrection.get())) self.enface_lines.append('enfaceline'+str(self.vol_sliderXZCorrection.get())) def leftclickXYCorrMovement(event): """ Left click and move handler - Inpaint corrected line and store values Compare function leftclickXYCorr(event) Parameters ---------- event: event click event """ canvas_event = event.widget c_x = int(canvas_event.canvasx(event.x)) c_y = int(canvas_event.canvasy(event.y)) if self.SMALLWINDOW is True: mul_fac = 2 if(c_x % 2 == 1): c_x = (c_x - 1)//mul_fac else: c_x = c_x//mul_fac else: mul_fac = 3 if(c_x % 3 == 1): c_x = (c_x - 1)//mul_fac elif(c_x % 3 == 2): c_x = (c_x - 2)//mul_fac else: c_x = c_x//mul_fac self.canvasXZCorrection.delete(str(c_x)+'a') self.canvasXZCorrection.delete(str(c_x)+'b') if mul_fac == 3: self.canvasXZCorrection.delete(str(c_x)+'c') self.savedslices[self.vol_sliderXZCorrection.get()] = np.zeros((1)) self.segmentation_points[1][c_x] = c_y//mul_fac self.point_list[c_x] = self.canvasXZCorrection.create_oval(c_x*mul_fac, c_y, c_x*mul_fac, c_y, width = 0, fill=self.crosshair_color, tags=str(c_x)+'a') self.point_list[c_x+1] = self.canvasXZCorrection.create_oval(c_x*mul_fac+1, c_y, c_x*mul_fac+1, c_y, width = 0, fill=self.crosshair_color, tags=str(c_x)+'b') if mul_fac == 3: self.point_list[c_x+2] = self.canvasXZCorrection.create_oval(c_x*mul_fac+2, c_y, c_x*mul_fac+2, c_y, width = 0, fill=self.crosshair_color, tags=str(c_x)+'c') self.segmentation[self.vol_sliderXZCorrection.get(),:,c_x] = np.zeros((self.segmentation.shape[1])) ''' IntegrateLayerVariable: Insert line when integrating new layer ''' if self.layerSelection is 'BM': self.segmentation[self.vol_sliderXZCorrection.get(),c_y//mul_fac,c_x] = self.BM_VALUE elif self.layerSelection is 'RPE': self.segmentation[self.vol_sliderXZCorrection.get(),c_y//mul_fac,c_x] = self.RPE_VALUE elif self.layerSelection is 'ILM': self.segmentation[self.vol_sliderXZCorrection.get(),c_y//mul_fac,c_x] = self.ILM_VALUE else: self.segmentation[self.vol_sliderXZCorrection.get(),c_y//mul_fac,c_x] = self.BM_VALUE if self.startpoint_x is None or self.endpoint_x is None: self.startpoint_x = self.endpoint_x = c_x else: if c_x < self.startpoint_x: self.startpoint_x = c_x elif c_x > self.endpoint_x: self.endpoint_x = c_x if self.SMALLWINDOW is True: self.canvasXYCorrection.create_line(self.startpoint_x//2,self.vol_sliderXZCorrection.get()//2,self.endpoint_x//2,self.vol_sliderXZCorrection.get()//2,fill=self.crosshair_color,tags='enfaceline'+str(self.vol_sliderXZCorrection.get())) else: self.canvasXYCorrection.create_line(self.startpoint_x,self.vol_sliderXZCorrection.get(),self.endpoint_x,self.vol_sliderXZCorrection.get(),fill=self.crosshair_color,tags='enfaceline'+str(self.vol_sliderXZCorrection.get())) self.enface_lines.append('enfaceline'+str(self.vol_sliderXZCorrection.get())) def resetSlice(): """ Reset correction of current slice """ self.statusText.set("Resetting slice...") try: self.segmentation[self.vol_sliderXZCorrection.get(),:,:] = self.segmentation_original[self.vol_sliderXZCorrection.get(),:,:] del self.savedslices[self.vol_sliderXZCorrection.get()] updateVolumeXZCorrSlice(None) try: self.canvasXYCorrection.delete('enfaceline'+str(self.vol_sliderXZCorrection.get())) self.enface_lines.remove('enfaceline'+str(self.vol_sliderXZCorrection.get())) except: pass except: pass self.statusText.set("Slice reset done.") def inpaintCorrSegmentation(): """ Inpaint corrected BM segmentation to volume """ #select layer ''' IntegrateLayerVariable: Insert line when integrating new layer ''' if self.layerSelection is 'BM': coordinates = np.asarray(np.where(self.segmentation[self.vol_sliderXZCorrection.get()] == self.BM_VALUE)) elif self.layerSelection is 'RPE': coordinates = np.asarray(np.where(self.segmentation[self.vol_sliderXZCorrection.get()] == self.RPE_VALUE)) elif self.layerSelection is 'ILM': coordinates = np.asarray(np.where(self.segmentation[self.vol_sliderXZCorrection.get()] == self.ILM_VALUE)) else: coordinates = np.asarray(np.where(self.segmentation[self.vol_sliderXZCorrection.get()] == self.BM_VALUE)) if self.SMALLWINDOW is True: mul_fac = 2 else: mul_fac = 3 dict = {} for x in range(coordinates.shape[1]): dict[coordinates[1][x]] = coordinates[0][x] for key,value in dict.items(): self.point_list[str(key)+'a'] = self.canvasXZCorrection.create_oval(key*mul_fac, value*mul_fac, key*mul_fac, value*mul_fac, width = 0, fill=self.crosshair_color, tags=str(key)+'a') self.point_list[str(key)+'b'] = self.canvasXZCorrection.create_oval(key*mul_fac+1, value*mul_fac, key*mul_fac+1, value*mul_fac, width = 0, fill=self.crosshair_color, tags=str(key)+'b') if mul_fac == 3: self.point_list[str(key)+'c'] = self.canvasXZCorrection.create_oval(key*mul_fac+2, value*mul_fac, key*mul_fac+2, value*mul_fac, width = 0, fill=self.crosshair_color, tags=str(key)+'c') def _createVariables(parent_var): """ Create basic variables """ self.parent = parent_var self.rectx0 = 0 self.recty0 = 0 self.rectx1 = 0 self.recty1 = 0 self.rectid = None self.move = False def _createCanvasBinding(): """ Create basic bindings """ self.canvasXYCorrection.bind( "<Button-1>", startRect ) self.canvasXYCorrection.bind( "<ButtonRelease-1>", stopRect ) self.canvasXYCorrection.bind( "<Motion>", movingRect ) def startRect(event): """ start rectangular selection Parameters ---------- event: event click event """ self.move = True self.canvasXYCorrection.delete('enFaceRect') self.rectx0 = self.canvasXYCorrection.canvasx(event.x) self.recty0 = self.canvasXYCorrection.canvasy(event.y) self.rect = self.canvasXYCorrection.create_rectangle(self.rectx0, self.recty0, self.rectx0, self.recty0, outline=self.crosshair_color,width=3, tags='enFaceRect') self.rectid = self.canvasXYCorrection.find_closest(self.rectx0, self.recty0, halo=2) def movingRect(event): """ Move endpoint of rectangle Parameters ---------- event: event click event """ if self.move == True: self.rectx1 = self.canvasXYCorrection.canvasx(event.x) self.recty1 = self.canvasXYCorrection.canvasy(event.y) self.canvasXYCorrection.coords(self.rectid, self.rectx0, self.recty0, self.rectx1, self.recty1) def stopRect(event): """ Stop rectangular selection and check if valid Parameters ---------- event: event click event """ try: self.move = False self.rectx1 = self.canvasXYCorrection.canvasx(event.x) self.recty1 = self.canvasXYCorrection.canvasy(event.y) if self.SMALLWINDOW is True: self.canvasXYCorrection.coords(self.rectid, self.startpoint_x//2, self.recty0, self.endpoint_x//2, self.recty1) self.box = (int(self.startpoint_x), int(self.recty0), int(self.endpoint_x)-int(self.startpoint_x), int(self.recty1)-int(self.recty0)) else: self.canvasXYCorrection.coords(self.rectid, self.startpoint_x, self.recty0, self.endpoint_x, self.recty1) self.box = (int(self.startpoint_x), int(self.recty0), int(self.endpoint_x)-int(self.startpoint_x), int(self.recty1)-int(self.recty0)) if(checkRectValid() == False): self.canvasXYCorrection.delete('enFaceRect') self.startpoint_y = self.endpoint_y = self.vol_sliderXZCorrection.get() else: if self.SMALLWINDOW is True: self.startpoint_y = int(self.recty0*2) self.endpoint_y = int(self.recty1*2) else: self.startpoint_y = int(self.recty0) self.endpoint_y = int(self.recty1) except: pass def checkRectValid(): """ Check if rectangle is valid. It has to cover at least one point on each line. """ if self.SMALLWINDOW is True: if (self.recty1*2 >= self.vol_sliderXZCorrection.get() and self.recty0*2 <= self.vol_sliderXZCorrection.get()) or (self.recty1*2 <= self.vol_sliderXZCorrection.get() and self.recty0*2 >= self.vol_sliderXZCorrection.get()): return True else: return False else: if (self.recty1 >= self.vol_sliderXZCorrection.get() and self.recty0 <= self.vol_sliderXZCorrection.get()) or (self.recty1 <= self.vol_sliderXZCorrection.get() and self.recty0 >= self.vol_sliderXZCorrection.get()): return True else: return False def propagateCorrection(): """ Propagate manual corrections. The segmentation is cropped and based on the corrections, l and r are calculated. From this, the mode is determined and the linear approximation (l<16) or graph-cut (l>=16) executed. """ try: #get corrected slices for key in self.savedslices: self.savedslices[key] = self.segmentation[key] #determine mode mode = 'high' if (self.endpoint_y-self.startpoint_y)//len(self.savedslices) < MODE_THRESHOLD: mode = 'low' self.statusText.set("Running: (r="+str(self.endpoint_x-self.startpoint_x)+'x'+str(self.endpoint_y-self.startpoint_y)+ ", l="+str((self.endpoint_y-self.startpoint_y)//len(self.savedslices))+")") #propagation algorithm ''' IntegratePropagation: Add lines for new layers ''' rect_correction = (self.startpoint_x,self.endpoint_x,self.startpoint_y,self.endpoint_y) if self.layerSelection is 'BM': cropped_segmentation = propagateBM(self.volume_original, self.segmentation, self.segmentation_original, self.savedslices, rect_correction, mode) elif self.layerSelection is 'RPE': cropped_segmentation = propagateRPE(self.volume_original, self.segmentation, self.segmentation_original, self.savedslices, rect_correction, mode) elif self.layerSelection is 'ILM': cropped_segmentation = propagateILM(self.volume_original, self.segmentation, self.segmentation_original, self.savedslices, rect_correction, mode) else: cropped_segmentation = propagateBM(self.volume_original, self.segmentation, self.segmentation_original, self.savedslices, rect_correction, mode) self.segmentation[self.startpoint_y-1:self.endpoint_y+1,:,self.startpoint_x-1:self.endpoint_x+2] = cropped_segmentation #update slices updateVolumeXZCorrSlice(None) updateCorrectionHeatmap() resetVars() except Exception as e: print("Error:",e) updateVolumeXZCorrSlice(None) updateCorrectionHeatmap() resetVars() self.statusText.set("Propagation finished!") def createHeatmap(): """ Create canvas when ground truth loaded/exists The colormap is diverging, red/blue are poistive/negative distances, whereas white is a perfect match. This tool can be used to evaluate an automatic segmentation algorithm. """ if hasattr(self, "FrameEvaluation"): self.FrameEvaluation.destroy() del self.FrameEvaluation self._job_HM = None if self.SMALLWINDOW is True: self.FrameEvaluation = Frame(self.correctionFrame,width = self.widthXY//2+30, height = self.heightXY//2,bg=self.canvasbackground) self.canvasEvaluation = Canvas(self.FrameEvaluation, width = self.widthXY//2, height = self.heightXY//2,highlightthickness=2,highlightbackground=self.hlghtbg,bg=self.canvasbackground) self.canvasColorbarCorr = Canvas(self.FrameEvaluation, width = 1, height = self.heightXY//2,bg=self.canvasbackground) self.canvas_Corrcbar_HM = Canvas(self.canvasColorbarCorr, width=20, height = self.heightXY//2 - 40,bg=self.canvasbackground) self.btnAutoEval = Button(self.FrameEvaluation, width = 18 , height = 2, text="Automatic Evaluation",font=self.statusFont, command=lambda:background(propagateCorrectionEvaluation), activebackground=self.btn_common_bg, fg=self.btnforeground, bg=self.btnbackground, activeforeground=self.btnforeground) self.btnAutoEval.grid(row=0,column=0,padx=(10,10),pady=(215,0), sticky="NW") else: self.FrameEvaluation = Frame(self.correctionFrame,width = self.widthXY+30, height = self.heightXY,bg=self.canvasbackground) self.canvasEvaluation = Canvas(self.FrameEvaluation, width = self.widthXY, height = self.heightXY,highlightthickness=2,highlightbackground=self.hlghtbg,bg=self.canvasbackground) self.canvasColorbarCorr = Canvas(self.FrameEvaluation, width = 1, height = self.heightXY,bg=self.canvasbackground) self.canvas_Corrcbar_HM = Canvas(self.canvasColorbarCorr, width=20, height = self.heightXY - 40,bg=self.canvasbackground) self.btnAutoEval = Button(self.FrameEvaluation, width = 15 , height = 3, text="Automatic Evaluation",font=self.statusFont, command=lambda:background(propagateCorrectionEvaluation), activebackground=self.btn_common_bg, fg=self.btnforeground, bg=self.btnbackground, activeforeground=self.btnforeground) self.btnAutoEval.grid(row=0,column=0,padx=(10,10),pady=(435,0), sticky="NW") self.FrameEvaluation.grid(row=0,column=2, sticky="NE") self.canvasEvaluation.grid(row=0,column=1, sticky="W") CreateToolTip(self.btnAutoEval, self.ttip_dict['manAutoEval']) #create difference map self.differencemap = np.zeros((self.groundtruth.shape[0],self.groundtruth.shape[2])).astype('int32') ''' IntegrateLayerVariable: Insert line when integrating new layer ''' for z in range(self.groundtruth.shape[0]): for x in range (self.groundtruth.shape[2]): if self.layerSelection is 'BM': values_algorithm = np.where(self.segmentation[z,:,x] == self.BM_VALUE)[0] values_gt = np.where(self.groundtruth[z,:,x] == self.BM_VALUE)[0] elif self.layerSelection is 'RPE': values_algorithm = np.where(self.segmentation[z,:,x] == self.RPE_VALUE)[0] values_gt = np.where(self.groundtruth[z,:,x] == self.RPE_VALUE)[0] elif self.layerSelection is 'ILM': values_algorithm = np.where(self.segmentation[z,:,x] == self.ILM_VALUE)[0] values_gt = np.where(self.groundtruth[z,:,x] == self.ILM_VALUE)[0] else: values_algorithm = np.where(self.segmentation[z,:,x] == self.BM_VALUE)[0] values_gt = np.where(self.groundtruth[z,:,x] == self.BM_VALUE)[0] try: self.differencemap[z,x] = values_gt - values_algorithm except: self.differencemap[z,x] = 0 if(np.max(self.differencemap) <= 0): range_of_values = np.abs(np.max(self.differencemap))+np.abs(np.min(self.differencemap)) else: range_of_values = np.abs(np.max(self.differencemap))+np.abs(np.min(self.differencemap)) + 1 offset = np.abs(np.min(self.differencemap)) if (range_of_values == 0): range_of_values = 8 offset = 4 self.differencemap += offset self.differencemap = self.differencemap.astype('uint8') divisor = float(offset/range_of_values) #colorbar v = [0,divisor/2,divisor, divisor + (1-divisor)/2,1.] l = list(zip(v,c)) if self.SMALLWINDOW is True: self.differencemap = self.differencemap[::2,::2] self.cmap_corr = LinearSegmentedColormap.from_list('rg',l, N=range_of_values) self.heatmap_diff = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(self.cmap_corr(self.differencemap, bytes=True))) self.image_on_canvas_diff = self.canvasEvaluation.create_image(0, 0, image=self.heatmap_diff, anchor=NW) self.canvasColorbarCorr.grid(row=0,column=2,sticky="nw") self.label_Corrtop_HM = Label(self.canvasColorbarCorr, width = 3,height= 1, text=''+str(int(range_of_values-offset)),font=self.clrbarFont,anchor="nw",bg=self.canvasbackground, fg =self.canvasforeground) self.label_Corrtop_HM.grid(row=0,column=0) self.canvas_Corrcbar_HM.grid(row=1,column=0,padx=(3, 0),pady = (0,0),sticky="nw") self.label_Corrbottom_HM = Label(self.canvasColorbarCorr, width = 3, height= 1, text=""+str(int(-offset)),font=self.clrbarFont,anchor="nw",bg=self.canvasbackground, fg =self.canvasforeground) self.label_Corrbottom_HM.grid(row=2,column=0) cbar_arr = np.zeros((self.heightXY, 20)).astype('uint8') incrementer = range_of_values/self.heightXY for i in range (self.heightXY): cbar_arr[i,:] = incrementer*i #update canvas if self.SMALLWINDOW is True: cbar_arr = cbar_arr[::2,:] self.photo_cbar_corr = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(self.cmap_corr(cbar_arr[::-1], bytes=True))) self.image_on_canvasCorr_cbar = self.canvas_Corrcbar_HM.create_image(0, 0, image=self.photo_cbar_corr, anchor=NW) def updateVolumeXZCorrSlice(event=None): """ Update XZ correction slice (time delay) Optional --------- event: event event (unused) """ if self._job_xz_corr: self.master.after_cancel(self._job_xz_corr) self._job_xz_corr = self.master.after(50, updateVolumeXZCorrSlice_Helper) def updateVolumeXZCorrSlice_Helper(): """ Update XZ correction slice helper Load new slice, resize by factor 3 and paint to canvas. """ self.canvasXZCorrection.delete("all") self._job_xz_corr = None self.segmentation_slice = self.segmentation[self.vol_sliderXZCorrection.get()].astype('uint8') ''' IntegrateLayerVariable: Insert line when integrating new layer ''' if self.layerSelection is 'BM': self.segmentation_points = np.asarray(np.where(self.segmentation_slice.transpose() == self.BM_VALUE)) elif self.layerSelection is 'RPE': self.segmentation_points = np.asarray(np.where(self.segmentation_slice.transpose() == self.RPE_VALUE)) elif self.layerSelection is 'ILM': self.segmentation_points = np.asarray(np.where(self.segmentation_slice.transpose() == self.ILM_VALUE)) else: self.segmentation_points = np.asarray(np.where(self.segmentation_slice.transpose() == self.BM_VALUE)) self.sliceXZCorrection = self.volumeXZ[self.vol_sliderXZCorrection.get()].astype('uint8') if self.SMALLWINDOW is True: self.sliceXZCorrection = rsc(self.sliceXZCorrection , 2, preserve_range = True).astype('uint8') else: self.sliceXZCorrection = rsc(self.sliceXZCorrection , 3, preserve_range = True).astype('uint8') self.slice_pilXZCorrection = PIL.Image.fromarray(self.sliceXZCorrection) self.photoXZCorrection = PIL.ImageTk.PhotoImage(image = self.slice_pilXZCorrection) self.image_on_canvas_XZCorrection = self.canvasXZCorrection.create_image(0, 0, image=self.photoXZCorrection, anchor=NW) self.canvasXZCorrection.configure(scrollregion = self.canvasXZCorrection.bbox("all")) inpaintCorrSegmentation() def updateVolumeSliceCorr(event): """ Update en face correction slice (time delay) Optional --------- event: event event (unused) """ if self._job_volume_xy_corr : self.master.after_cancel(self._job_volume_xy_corr) self._job_volume_xy_corr = self.master.after(50, updateVolumeSliceCorr_Helper) def updateVolumeSliceCorr_Helper(): """ Update en face correction slice helper Load new slice """ self._job_volume_xy_corr = None self.sliceEnface = self.volume[self.vol_sliderXY_corr.get()].astype('uint8') if self.SMALLWINDOW is True: self.sliceEnface = self.sliceEnface[::2,::2] self.slice_pil_Enface = PIL.Image.fromarray(self.sliceEnface) self.photoEnface = PIL.ImageTk.PhotoImage(image = self.slice_pil_Enface) self.canvasXYCorrection.itemconfig(self.image_on_canvas_Enface, image = self.photoEnface) self.canvasXYCorrection.configure(scrollregion = self.canvasXYCorrection.bbox("all")) if self.mover_init is True: if self.SMALLWINDOW is True: self.canvasXZCorrection.yview_moveto((self.heightXZ*2//2-175)/(self.heightXZ*2)) else: self.canvasXZCorrection.yview_moveto((self.heightXZ*3//2-175)/(self.heightXZ*3)) self.mover_init = False def updateCorrectionHeatmap(): """ Update ground truth heatmap slice (time delay) """ if self._job_HM: self.master.after_cancel(self._job_HM) self._job_HM = self.master.after(50, updateCorrectionHeatmap_Helper) def updateCorrectionHeatmap_Helper(): """ Update ground truth heatmap slice helper Load new slice """ self._job_HM = None self.differencemap = np.zeros((self.groundtruth.shape[0],self.groundtruth.shape[2])).astype('int32') ''' IntegrateLayerVariable: Insert line when integrating new layer ''' for z in range(self.groundtruth.shape[0]): for x in range (self.groundtruth.shape[2]): if self.layerSelection is 'BM': values_algorithm = np.where(self.segmentation[z,:,x] == self.BM_VALUE)[0] values_gt = np.where(self.groundtruth[z,:,x] == self.BM_VALUE)[0] elif self.layerSelection is 'RPE': values_algorithm = np.where(self.segmentation[z,:,x] == self.RPE_VALUE)[0] values_gt = np.where(self.groundtruth[z,:,x] == self.RPE_VALUE)[0] elif self.layerSelection is 'ILM': values_algorithm = np.where(self.segmentation[z,:,x] == self.ILM_VALUE)[0] values_gt = np.where(self.groundtruth[z,:,x] == self.ILM_VALUE)[0] else: values_algorithm = np.where(self.segmentation[z,:,x] == self.BM_VALUE)[0] values_gt = np.where(self.groundtruth[z,:,x] == self.BM_VALUE)[0] try: self.differencemap[z,x] = values_gt - values_algorithm except: self.differencemap[z,x] = 0 offset = np.abs(np.min(self.differencemap)) self.differencemap += offset self.differencemap = self.differencemap.astype('uint8') if self.SMALLWINDOW is True: self.differencemap = self.differencemap[::2,::2] self.heatmap_diff = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(self.cmap_corr(self.differencemap, bytes=True))) self.canvasEvaluation.itemconfig(self.image_on_canvas_diff, image = self.heatmap_diff) def loadGroundtruth(): """ Load ground truth volume """ self.statusText.set("Loading GT...") try: self.groundtruth = ImportHandler.loadGroundtruthVolume(self.initialdir,self.shiftedValues) self.GT_EXISTS = True if(hasattr(self, "FrameEvaluation")): del self.FrameEvaluation createHeatmap() self.statusText.set("GT loaded!") except Exception as e : self.statusText.set("Error when loading GT!") print("Error:",e) self.GT_EXISTS = False def propagateCorrectionEvaluation(): """ Run an automatic algorithm evaluation of the current volume. Define r,l, and spacing in auto_evaluation.txt. The evaluation then evaluates MSE and STDDEV for the algorithm of the current volume. The algorithm uses the ground truth as input for the corrected lines and sets an equal spacing. Example: 1) Load Volume 2) Run automatic segmentation 3) Switch to Manual Refinement 4) Load ground truth Parameters in txt: r=32,128 l=2,16 spacing=32 5) An evaluation of the manual correction algorithm is run for the following settings: Two different region sizes (r): 32x32 and 128x128 In those regions, two different line settings are evaluated (l): i) every second line ii) every 16th line is corrected by inserting the underlying ground truth lines 6) Spacing describes the stride from of the batches. The lower the number, the higher the computation time. For initial experiments, half of the region r is sufficient!!! """ self.statusText.set("Running evaluation...") s = ttk.Style() s.theme_use('clam') s.configure("PRLEC.Horizontal.TProgressbar", troughcolor='#A0A0A0', background="#32E3E3", thickness=1, troughrelief="flat", relief="flat", borderwidth=0) try: if(np.count_nonzero(self.groundtruth) == 0): return f = open(os.path.dirname(os.path.abspath(sys.argv[0]))+"\\auto_evaluation.txt", "r") dimensions = f.readline().replace("r=","").replace("\n","").split(',') correction_lines = f.readline().replace("l=","").replace("\n","").split(',') spacing = f.readline().replace("spacing=","").replace("\n","") f.close() except Exception as e: print("Error when running automatic evaluation:\n",e) return dim_val=IntVar() dim_val.set(1) self.labelprogressBarDimension = Label(self.FrameEvaluation, width = 12,height= 1, text='Region progress',font=self.textFont,anchor="nw",bg=self.canvasbackground, fg =self.canvasforeground) self.progressBarDimension = ttk.Progressbar(self.FrameEvaluation, style="PRLEC.Horizontal.TProgressbar",orient="horizontal",length=140, maximum=len(dimensions), mode='determinate', variable = dim_val) if self.SMALLWINDOW is True: self.labelprogressBarDimension.grid(row=0,column=0,padx=(10,10),pady=(80,0), sticky="NW") self.progressBarDimension.grid(row=0,column=0,padx=(10,10),pady=(110,0), sticky="NW") else: self.labelprogressBarDimension.grid(row=0,column=0,padx=(10,10),pady=(280,0), sticky="NW") self.progressBarDimension.grid(row=0,column=0,padx=(10,10),pady=(320,0), sticky="NW") f = open(os.path.dirname(os.path.abspath(sys.argv[0]))+"\\Evaluation.txt", "a") ''' IntegrateLayerVariable: Insert line when integrating new layer ''' if self.layerSelection is 'BM': seg_val = self.BM_VALUE f.write("Bruchs membrane\n") elif self.layerSelection is 'RPE': seg_val = self.RPE_VALUE f.write("Retinal pigment epithelium\n") elif self.layerSelection is 'ILM': seg_val = self.ILM_VALUE f.write("Inner limiting membrane\n") else: seg_val = self.BM_VALUE f.write("Bruchs membrane\n") mean_val,stddev_val = calc_MSE_STDDEV(self.segmentation, self.groundtruth, seg_val) f.write(self.initialdir+"\n") f.write("FA MSE: "+str(mean_val)+" Stddev: "+str(stddev_val)+"\n") spacing = int(spacing) self.labelprogressBarLines = Label(self.FrameEvaluation, width = 12,height= 1, text='Line progress',font=self.textFont,anchor="nw",bg=self.canvasbackground, fg =self.canvasforeground) region_val=IntVar() region_val.set(1) self.progressBarLines = ttk.Progressbar(self.FrameEvaluation, style="PRLEC.Horizontal.TProgressbar", orient="horizontal", length=140, maximum=100, mode='determinate', variable=region_val) if self.SMALLWINDOW is True: self.labelprogressBarLines.grid(row=0,column=0,padx=(10,10),pady=(150,0), sticky="NW") self.progressBarLines.grid(row=0,column=0,padx=(10,10),pady=(180,0), sticky="NW") else: self.labelprogressBarLines.grid(row=0,column=0,padx=(10,10),pady=(360,0), sticky="NW") self.progressBarLines.grid(row=0,column=0,padx=(10,10),pady=(400,0), sticky="NW") for dim_ctr,dim in enumerate(dimensions): dim_val.set(dim_ctr+1) dim = int(dim) gt_mean_list = [] gt_stddev_list = [] test_mean_list = [] test_stddev_list = [] for no_of_l in range(len(correction_lines)): test_mean_list.append([]) test_stddev_list.append([]) f.write("r="+str(dim)+"\n") self.progressBarLines.configure(maximum = int((self.volume.shape[2] - dim)/spacing * (self.volume.shape[1] - dim - 1)/ spacing)+1) ctr_lines = 1 for x in range(1, self.volume.shape[2] - dim ,spacing): self.startpoint_x = x self.endpoint_x = self.startpoint_x + dim - 1 for y in range(1, self.volume.shape[1] - dim - 1, spacing): region_val.set(ctr_lines) self.startpoint_y = y self.endpoint_y = self.startpoint_y + dim cropped_segmentation = self.segmentation[self.startpoint_y-1:self.endpoint_y+1,:,self.startpoint_x-1:self.endpoint_x+2].astype('uint8') cropped_segmentation[0,:,:] = self.groundtruth[self.startpoint_y-1,:,self.startpoint_x-1:self.endpoint_x+2] cropped_segmentation[-1,:,:] = self.groundtruth[self.endpoint_y+1,:,self.startpoint_x-1:self.endpoint_x+2] ground_truth_crop = self.groundtruth[self.startpoint_y:self.endpoint_y,:,self.startpoint_x:self.endpoint_x+1] mean_val,stddev_val = calc_MSE_STDDEV(cropped_segmentation[1:-1,:,1:-1], ground_truth_crop,seg_val) gt_mean_list.append(mean_val) gt_stddev_list.append(stddev_val) for ctr,i in enumerate(correction_lines): i = int(i) mode ='low' if(i > 16): mode = 'high' ''' IntegrateLayerVariable: Insert line when integrating new layer ''' for z in range(self.startpoint_y+i,self.endpoint_y+1, i): if self.layerSelection is 'BM': self.savedslices[z] = np.where(self.groundtruth[z] == self.BM_VALUE, self.BM_VALUE,0) elif self.layerSelection is 'RPE': self.savedslices[z] = np.where(self.groundtruth[z] == self.RPE_VALUE, self.RPE_VALUE,0) elif self.layerSelection is 'ILM': self.savedslices[z] = np.where(self.groundtruth[z] == self.ILM_VALUE, self.ILM_VALUE,0) else: self.savedslices[z] = np.where(self.groundtruth[z] == self.BM_VALUE, self.BM_VALUE,0) rect_correction = (self.startpoint_x,self.endpoint_x,self.startpoint_y,self.endpoint_y) ''' IntegratePropagation: Add lines for new layers ''' if self.layerSelection is 'BM': cropped_segmentation = propagateBM(self.volume_original, self.segmentation, self.segmentation_original, self.savedslices, rect_correction, mode) elif self.layerSelection is 'RPE': cropped_segmentation = propagateRPE(self.volume_original, self.segmentation, self.segmentation_original, self.savedslices, rect_correction, mode) elif self.layerSelection is 'ILM': cropped_segmentation = propagateILM(self.volume_original, self.segmentation, self.segmentation_original, self.savedslices, rect_correction, mode) else: cropped_segmentation = propagateBM(self.volume_original, self.segmentation, self.segmentation_original, self.savedslices, rect_correction, mode) mean,stddev = calc_MSE_STDDEV(cropped_segmentation[1:-1,:,1:-1], ground_truth_crop,seg_val) test_mean_list[ctr].append(mean) test_stddev_list[ctr].append(stddev) self.savedslices.clear() ctr_lines += 1 f.write("AveragePatch MSE: "+str(np.mean(np.array(gt_mean_list)))+" SDEV: " + str(np.mean(np.array(gt_stddev_list)))+'\n') for no_of_l in range(len(correction_lines)): f.write("L="+correction_lines[no_of_l]+" MSE: "+str(np.mean(np.array(test_mean_list[no_of_l]))) + " SDEV: "+str(np.mean(np.array(test_stddev_list[no_of_l])))+'\n') self.progressBarLines.destroy() self.progressBarDimension.destroy() self.labelprogressBarLines.destroy() self.labelprogressBarDimension.destroy() f.close() self.statusText.set("") def resetVars(): """ Reset all variables necessary to correct """ self.point_list = {} self.startpoint_x = None self.endpoint_x = None self.startpoint_y = None self.endpoint_y = None self.rectid = None self.segmentation_points = None self.savedslices = {} #delete correction lines for line in self.enface_lines: self.canvasXYCorrection.delete(line) self.enface_lines = [] self.canvasXYCorrection.delete('enFaceRect') ''' Basic frame setup ''' if self.SMALLWINDOW is True: self.correctionFrame = Frame(self.master, width = self.widthXZ, height = self.heightXY//2 + self.heightXZ + 25, relief=SUNKEN,bg=self.canvasbackground) self.canvasXYCorrection = Canvas(self.correctionFrame, width = self.widthXY//2+5, height = self.heightXY//2,highlightthickness=2,highlightbackground=self.hlghtbg,bg=self.canvasbackground) self.sliceEnface = self.volume[self.volume.shape[0]//2][::2,::2].astype('uint8') self.vol_sliderXY_corr = Scale(self.correctionFrame, from_=0, to=self.volume.shape[0]-1, length =self.widthXZ//2-4, orient=VERTICAL, command=updateVolumeSliceCorr,bg=self.canvasbackground, fg =self.canvasforeground) pad_x_sliderXY = (475,0) self.canvasXZCorrection = Canvas(self.correctionFrame, width = self.widthXZ*2, height = 350,bg=self.canvasbackground,highlightthickness=2,highlightbackground=self.hlghtbg) self.manCorrBtnHeight = 1 self.corr_y_pad_increment = 40 else: self.correctionFrame = Frame(self.master, width = self.widthXZ*3, height = self.heightXY + self.heightXZ*3 + 50, relief=SUNKEN,bg=self.canvasbackground) self.canvasXYCorrection = Canvas(self.correctionFrame, width = self.widthXY+5, height = self.heightXY,highlightthickness=2,highlightbackground=self.hlghtbg,bg=self.canvasbackground) self.sliceEnface = self.volume[self.volume.shape[0]//2].astype('uint8') self.vol_sliderXY_corr = Scale(self.correctionFrame, from_=0, to=self.volume.shape[0]-1, length =self.widthXZ-4, orient=VERTICAL, command=updateVolumeSliceCorr,bg=self.canvasbackground, fg =self.canvasforeground) pad_x_sliderXY = (710,0) self.canvasXZCorrection = Canvas(self.correctionFrame, width = self.widthXZ*3, height = 350,bg=self.canvasbackground,highlightthickness=2,highlightbackground=self.hlghtbg) self.manCorrBtnHeight = 2 self.corr_y_pad_increment = 80 self.correctionFrame.grid(row = 0, column = 2, sticky ='nw', pady=(10,0)) #setting bruchs correction as default self.varManCorrLayer = StringVar(self.correctionFrame) self.varManCorrLayer.set("BM") self.canvasXYCorrection.grid(row=0,column=0, padx=(200,0), sticky="NW") self.slice_pil_Enface = PIL.Image.fromarray(self.sliceEnface) self.photoEnface = PIL.ImageTk.PhotoImage(image = self.slice_pil_Enface) self.image_on_canvas_Enface = self.canvasXYCorrection.create_image(0, 0, image=self.photoEnface, anchor=NW) self.vol_sliderXY_corr.grid(row=0,column=0, padx=pad_x_sliderXY,pady=(1,0),sticky="NW") self.vol_sliderXY_corr.set(self.volume.shape[0]//2) if self.rectid == None: _createVariables(self.master) _createCanvasBinding() #check if GT exists if self.GT_EXISTS: createHeatmap() self.canvasXZCorrection.grid(row=1,column=0, columnspan = 3, padx=(200,0), pady=(20,0)) #mouse bindings for correction self.canvasXZCorrection.bind("<Button-1>", leftclickXYCorr) self.canvasXZCorrection.bind('<B1-Motion>', leftclickXYCorrMovement) self.sbarV_XZCorr = Scrollbar(self.correctionFrame, orient=VERTICAL,command=self.canvasXZCorrection.yview) self.canvasXZCorrection.config(yscrollcommand=self.sbarV_XZCorr.set) self.sbarV_XZCorr.grid(row=1, column = 3, sticky=N+S,pady=(20,0)) self.canvasXZCorrection.configure(scrollregion = self.canvasXZCorrection.bbox("all")) self.sliceXZCorrection = self.volumeXZ[self.volumeXZ.shape[0]//2].astype('uint8') self.sliceXZCorrection = rsc(self.sliceXZCorrection , 3, mode='reflect', preserve_range = True).astype('uint8') self.slice_pilXZCorrection = PIL.Image.fromarray(self.sliceXZCorrection) self.photoXZCorrection = PIL.ImageTk.PhotoImage(image = self.slice_pilXZCorrection) self.image_on_canvas_XZCorrection = self.canvasXZCorrection.create_image(0,0, image=self.photoXZCorrection, anchor=NW) self.vol_sliderXZCorrection = Scale(self.correctionFrame, from_=0, length= 350, to=self.volumeXZ.shape[0]-1, orient=VERTICAL, command=updateVolumeXZCorrSlice,bg=self.canvasbackground, fg =self.canvasforeground) self.vol_sliderXZCorrection.grid(row=1,column=4,sticky=W,pady=(20,0)) self.vol_sliderXZCorrection.set(self.volumeXZ.shape[0]//2) ctr_row = 0 self.statusText = StringVar() self.statusText.set("") self.LabelPGBarCorr = Label(self.correctionFrame, width = 18,height= 2, textvariable=self.statusText, font=self.clrbarFont,anchor="nw", bg=self.canvasbackground, fg =self.hlghtbg) self.LabelPGBarCorr.grid(row=0,column=0,pady=(10,0),padx=(5,0),sticky='NW') #button menu - most callbacks is put in threading to not stop the GUI self.btnExplore = Button(self.correctionFrame,width = 15 , height = self.manCorrBtnHeight, text="Explore Mode",font=self.statusFont, command=self.exploremode, activebackground=self.btn_common_bg, fg=self.btnforeground, bg=self.btnbackground, activeforeground=self.btnforeground) self.btnExplore.grid(row=ctr_row,column=0,padx=(40,0),pady=(self.corr_y_pad_increment,0), sticky="NW") CreateToolTip(self.btnExplore, self.ttip_dict['manExplore']) self.btnLoadGT = Button(self.correctionFrame,width = 15 , height = self.manCorrBtnHeight, text="Load Ground Truth",font=self.statusFont, command=lambda:background(loadGroundtruth), activebackground=self.btn_common_bg, fg=self.btnforeground, bg=self.btnbackground, activeforeground=self.btnforeground) self.btnLoadGT.grid(row=ctr_row,column=0,padx=(40,0),pady=(self.corr_y_pad_increment*2,0), sticky="NW") CreateToolTip(self.btnLoadGT, self.ttip_dict['manLoadGT']) self.btnSaveRes = Button(self.correctionFrame,width = 15 , height = self.manCorrBtnHeight, text="Save Result",font=self.statusFont, command=lambda:background(saveSegmentation), activebackground=self.btn_common_bg, fg=self.btnforeground, bg=self.btnbackground, activeforeground=self.btnforeground) self.btnSaveRes.grid(row=ctr_row,column=0,padx=(40,0),pady=(self.corr_y_pad_increment*3,0), sticky="NW") CreateToolTip(self.btnSaveRes, self.ttip_dict['manSaveRes']) ''' Drop Down Menu to select correction layer IntegrateOption: If new layers are added, integrate it here by adding the specific layer. ''' Label(self.correctionFrame, width = 11,height= 1, text="Layer",font=self.headerFont,anchor="nw", bg=self.canvasbackground, fg =self.canvasforeground).grid(row=ctr_row,column=0,padx=(40,0),pady=(self.corr_y_pad_increment*4,0),sticky="NW") self.optionManCorrLayer = OptionMenu(self.correctionFrame, self.varManCorrLayer, "ILM", "RPE", "BM", command=layerChange) self.optionManCorrLayer.config(font=self.textFont) menu = self.optionManCorrLayer.nametowidget(self.optionManCorrLayer.menuname) menu.configure(font=self.textFont) self.optionManCorrLayer.grid(row=ctr_row,column=0,padx=(40,0),pady=(int(self.corr_y_pad_increment*4.5),0),sticky="NW") CreateToolTip(self.optionManCorrLayer, self.ttip_dict['manCorrLayer']) ctr_row =+1 self.btnRunManRefine = Button(self.correctionFrame,width = 15 , height = self.manCorrBtnHeight + 1, text="Run Refinement",font=self.statusFont, command=lambda:background(propagateCorrection), activebackground=self.btn_common_bg, fg=self.btnforeground, bg=self.btnbackground, activeforeground=self.btnforeground) self.btnRunManRefine.grid(row=ctr_row,column=0,padx=(40,0),pady=(22,0), sticky="NW") CreateToolTip(self.btnRunManRefine, self.ttip_dict['manRunRefine']) self.btnResetSlice = Button(self.correctionFrame,width = 15 , height = self.manCorrBtnHeight + 1, text="Reset slice",font=self.statusFont, command=lambda:background(resetSlice), activebackground=self.btn_common_bg, fg=self.btnforeground, bg=self.btnbackground, activeforeground=self.btnforeground) self.btnResetSlice.grid(row=ctr_row,column=0,padx=(40,0),pady=(305,0), sticky="NW") CreateToolTip(self.btnResetSlice, self.ttip_dict['manResetSlice']) self.canvasXZCorrection.yview_moveto(0.5)