"""
Framework: Core class to load initial GUI
---------------------------------------------------
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 tkinter import *
import PIL.Image, PIL.ImageTk, PIL.ImageEnhance
from matplotlib import cm
from Visualization import Heatmap
from Visualization import Projections
from Visualization import DelineateGA
from FileHandler import ExportHandler, ImportHandler
from Algorithms.AutomaticSegmentation import ThreeLayerSegmentation
from Algorithms.RefinementRPE import RPERefinementAlgorithm
from Algorithms.Flattening.VolumeFlattening import unFlatten
from GUI_Classes.OptionsPane import createOptionMenu
from GUI_Classes.CanvasXZ import createCanvasXZ
from GUI_Classes.CanvasXY import createCanvasXY
from GUI_Classes.CanvasYZ import createCanvasYZ
from GUI_Classes.CanvasHeatmap import createCanvasHeatmap
from GUI_Classes.FrameManualCorrection import createManualCorrectionFrame
from Tooltips.TipHandler import createToolTipDictionary
from FileHandler.ParameterReader import readGlobalParameters
[docs]class GUI:
"""
Basic GUI Class to load PRLEC Framework
"""
#GUI colors
btnbackground = '#303030' #Button background
btnforeground = '#FFFFFF' #Button font
btn_common_bg = '#307130' #Button active
canvasbackground = '#070707' #Canvas background
canvasforeground = '#FAFAFA' #Canvas Font
hlghtbg = "#32E3E3" #Canvas highlighting
crosshair_color = "#32E3E3" #Crosshair color
def __init__(self):
"""
Initializing the GUI
"""
#empty basic volumes for initial loader
self.volume = np.zeros((500,500,500)).astype('uint8')
self.volume_original = np.zeros((500,500,500)).astype('uint8')
self.segmentation = np.zeros((500,500,500)).astype('uint8')
self.heatmap_rpe_bruchs = np.zeros((500,500)).astype('uint8')
self.heatmap_original = np.zeros((500,500,3)).astype('uint8')
self.metric_map = np.zeros((500,500)).astype('uint8')
self.ttip_dict = createToolTipDictionary()
#heatmap minimum
self.heatmap_min = 0
'''
Insert new i in parameters.txt
'''
readGlobalParameters(self)
#GUI fonts
if self.SMALLWINDOW is True:
self.headerFont = ("Times 10 bold") #headlines
self.textFont = ("Times 9") #text
self.btnFont = ("Times 9") #Buttons
self.statusFont = ("Times 9") #statustext
self.clrbarFont = ("Times 9") #colorbar
else:
self.headerFont = ("Times 14 bold") #headlines
self.textFont = ("Times 13") #text
self.btnFont = ("Times 13") #Buttons
self.statusFont = ("Times 12") #statustext
self.clrbarFont = ("Times 11") #colorbar
#Initial Heatmap
self.heatmapType = 'Drusen'
#Canvas Zoom variables
self.scale_delta = 0.1
self.scale_img = 1.0
#Canvas Contrast variables
self.contrast_value = 1.0
self.brightness_value = 1.0
#Flattening polynomial
self.flattening_polynomial = 4
#Rect for delineating GA
self.rect = (0,0,0,0)
#slow scan flattening
self.shiftedValuesSlowScan = None
#Variables to store buttons initial values
self.blender = False
self.contrast = False
self.threshold_to_one = False
self.threshold_activate = True
self.flatten = True
self.flattenBM = True
self.projMode = 'mean'
self.mover_init = True
self.corr_active= False
self.fromCorrMenu = False
#blender job
self._job_Blender = None
#initial colormap
self.COLORMAP = 'viridis'
#Contours for GA
self.contourlist = []
#initial colors for segmentation inpainting
self.colorRPE = StringVar()
self.colorRPE.set("#FFBB00")
self.colorBM = StringVar()
self.colorBM.set("#BB0000")
self.colorILM = StringVar()
self.colorILM.set("#23CFCF")
#resolutions
self.tv_res = DoubleVar()
self.tv_res.set(12)
self.axial_res = DoubleVar()
self.axial_res.set(4.5)
#Visualization initially RPEDC
self.varVis = IntVar()
self.varVis.set(1)
[docs] def start_explore_mode(self,master):
"""
Start Explore Volume Mode
Parameters
----------
master: TK object
TK object for setting up frame
"""
#delete segmentation when coming from manual refinement
if hasattr(self,'segmentation_original'):
del self.segmentation_original
#initial call
if(not hasattr(self,'master')):
self.master = master
#geometry size
if(self.SMALLWINDOW == True):
self.master.geometry('800x650')
else:
self.master.geometry('1200x850')
#titlebar
self.master.title("PRLE - OCT Processing Tool")
self.master.state('zoomed')
#binding arrow keys to zoom into planes
self.master.bind("<Up>", self.keyPressedUp)
self.master.bind("<Down>", self.keyPressedDown)
self.master.configure(background=self.canvasbackground)
#delete correction plane
if(hasattr(self,'FrameSegCorrection')):
self.FrameSegCorrection.destroy()
#Create menu's and planes for investigations
createOptionMenu(self)
self.fromCorrMenu = False
createCanvasXY(self)
createCanvasHeatmap(self)
createCanvasXZ(self)
createCanvasYZ(self)
[docs] def exploremode(self):
"""
Starting Volume Explorer
"""
#delete correction frame and set flattening
if hasattr(self,"correctionFrame"):
self.correctionFrame.destroy()
if(hasattr(self,"shiftedValues") and np.count_nonzero(self.shiftedValues) != 0):
self.flattenBM = False
self.corr_active= False
self.start_explore_mode(self.master)
[docs] def correctionmode(self):
"""
Starting Manual Refinement Mode
Note: Volume will be flattened to BM for Correction
"""
self.statusText.set("Switching to manual refinement...")
#Check if volume is already flat
if(self.flattenBM is False):
self.start_correction_mode()
self.corr_active= True
else:
#Volume not flattened - flatten to BM
if(np.count_nonzero(self.segmentation) != 0):
im_center_y = int(self.segmentation.shape[1]//2)
self.shiftedValues = self.shiftedValues.astype('int32')
vol = self.volume_original.copy()
for z in range(vol.shape[0]):
coordinates_bruchs = np.where(self.segmentation[z].transpose() == self.BM_VALUE)[1]
self.shiftedValues[z,:]= -coordinates_bruchs[:] + im_center_y
for x in range (vol.shape[2]):
vol[z,:,x] = np.roll(vol[z,:,x],self.shiftedValues[z][x], axis= 0)
self.volume = ImportHandler.getOriginalRGBVolume(np.asarray(vol))
#note the minus: -shiftedvalues flattens the image
self.segmentation = unFlatten(self.segmentation,-self.shiftedValues)
#update volumes
self.updateVolumeSliceXY(None)
self.updateVolumeXZ()
self.updateVolumeYZ()
self.updateSegmentation()
self.flattenBM = False
#start correction mode
self.start_correction_mode()
self.corr_active= True
#Volume not flattened and no base-segmentation exists
else:
self.segmentation = np.zeros((self.volume.shape[1],self.volume.shape[0],self.volume.shape[2])).astype('uint8')
im_center_y = int(self.segmentation.shape[1]//2)
for z in range(self.segmentation.shape[0]):
self.segmentation[z,im_center_y,:] = self.BM_VALUE
self.shiftedValues = self.shiftedValues.astype('int32')
vol = self.volume_original.copy()
for z in range(vol.shape[0]):
coordinates_bruchs = np.where(self.segmentation[z].transpose() == self.BM_VALUE)[1]
self.shiftedValues[z,:]= -coordinates_bruchs[:] + im_center_y
for x in range (vol.shape[2]):
vol[z,:,x] = np.roll(vol[z,:,x],self.shiftedValues[z][x], axis= 0)
self.volume = ImportHandler.getOriginalRGBVolume(np.asarray(vol))
#note the minus: -shiftedvalues flattens the image
self.segmentation = unFlatten(self.segmentation,-self.shiftedValues)
#update volumes
self.updateVolumeSliceXY(None)
self.updateVolumeXZ()
self.updateVolumeYZ()
self.updateSegmentation()
self.flattenBM = False
#start correction mode
self.start_correction_mode()
self.corr_active= True
self.fromCorrMenu = True
[docs] def saveInpainted(self):
"""
Saving inpainted volume as tif
A window will be opened to select the path
"""
self.statusText.set("Saving inpainted volume...")
self.disableDataButtons()
try:
self.inpaintOriginalSegmentation()
if self.shiftedValuesSlowScan is None:
ExportHandler.saveInpainted(self.initialdir, self.volumeXZ, self.shiftedValues)
else:
ExportHandler.saveInpainted(self.initialdir, self.volumeXZ, self.shiftedValues+self.shiftedValuesSlowScan)
self.statusText.set("Inpainted segmentation saved!")
except Exception as e:
self.statusText.set("Attention! Segmentation not saved!")
print("Error:",e)
self.enableDataButtons()
[docs] def saveLineSegmentation(self):
"""
Saving line segmentation as tif
A window will be opened to select the path
"""
self.statusText.set("Saving line segmentation...")
self.disableDataButtons()
try:
if self.shiftedValuesSlowScan is None:
ExportHandler.saveLineSegmentation(self.initialdir, self.segmentation, self.shiftedValues)
else:
ExportHandler.saveLineSegmentation(self.initialdir, self.segmentation, self.shiftedValues+self.shiftedValuesSlowScan)
self.statusText.set("Line segmentation saved!")
except Exception as e:
self.statusText.set("Attention! Error while saving.")
print('Error:',e)
self.enableDataButtons()
[docs] def saveHeatmap(self):
"""
Saving heatmap
A window will be opened to select the path
"""
self.statusText.set("Saving heatmap...")
self.disableDataButtons()
if self.heatmapType is 'minILMRPEProjection' or self.heatmapType is 'MarkGaByRect' or self.heatmapType is 'subRPESlab' or self.heatmapType is 'Vesselness':
self.COLORMAP = 'gray'
try:
ExportHandler.saveHeatmap(self.initialdir, self.heatmapForSaving,self.COLORMAP)
self.statusText.set("Heatmap saved!")
except Exception as e:
self.statusText.set("Attention: Heatmap not saved!")
print('Error:',e)
self.enableDataButtons()
[docs] def loadVolumeOCTA(self):
"""
Load OCTA volume calling loadVolume
A window will be opened to select the path
"""
try:
self.loadVolume(path=None, OCTA = True)
self.statusText.set("OCTA volume loaded!")
except Exception as e:
self.statusText.set("Error while loading volume!")
print("Error:",e)
[docs] def loadVolumeUnmerged(self):
"""
Load Raw OCT volume calling loadVolume with parameter merged = False.
A window will be opened to select the path. The slow-scan direction
will be registered to generate a smoother volume. For more information
on the registration, please look at:
Algorithms -> SlowScanRegistration -> runSlowScanRegistration
"""
try:
self.loadVolume(path=None, merged = False, OCTA = False)
self.statusText.set("Volume loaded!")
except Exception as e:
self.statusText.set("Error while loading volume!")
print("Error:",e)
[docs] def loadVolume(self, path=None, OCTA = False, merged = True):
"""
Load volume calling ImportHandler.loadVolume and setting up planes
A window will be opened to select the path
"""
if merged is True:
self.statusText.set("Loading merged OCT volume...")
elif OCTA is True:
self.statusText.set("Loading OCTA volume...")
elif merged is False and OCTA is False:
self.statusText.set("Loading raw volume...")
self.disableDataButtons()
try:
if merged is False:
self.initialdir,self.shiftedValuesSlowScan,self.volume_original,self.volume = ImportHandler.loadVolume(self.initialdir, path, OCTA, merged, self.flattening_polynomial)
self.shiftedValues = np.zeros((self.shiftedValuesSlowScan.shape[0],self.shiftedValuesSlowScan.shape[1]))
else:
self.initialdir,self.shiftedValues,self.volume_original,self.volume = ImportHandler.loadVolume(self.initialdir, path, OCTA, merged, self.flattening_polynomial)
self.shiftedValuesSlowScan = None
self.resetSegmentation()
#resetting the scale
self.scale_img = 1.0
#intitally volume is not flat
self.flatten = True
except Exception as e:
self.enableDataButtons()
self.statusText.set("Error while loading volume!")
return
self.enableDataButtons()
#updating scales for browsing through volume
self.vol_sliderXY.destroy()
if self.SMALLWINDOW is True:
self.vol_sliderXY = Scale(self.FrameVolumeXY, from_=0, to=self.volume.shape[0]-1, length = self.widthXY//2, orient=HORIZONTAL, command=self.updateVolumeSliceXY, bg=self.canvasbackground, fg =self.canvasforeground)
else:
self.vol_sliderXY = Scale(self.FrameVolumeXY, from_=0, to=self.volume.shape[0]-1, length = self.widthXY, orient=HORIZONTAL, command=self.updateVolumeSliceXY, bg=self.canvasbackground, fg =self.canvasforeground)
self.vol_sliderXY.grid(row=3,column=0)
self.vol_sliderXY.set(self.volume.shape[0]//2)
self.vol_sliderXZ.destroy()
if self.SMALLWINDOW is True:
self.vol_sliderXZ = Scale(self.FrameVolumeXZ, from_=0, to=self.volumeXZ.shape[0]-1, length = self.widthXZ//2,orient=HORIZONTAL, command=self.updateVolumeXZSlice, bg=self.canvasbackground, fg =self.canvasforeground)
else:
self.vol_sliderXZ = Scale(self.FrameVolumeXZ, from_=0, to=self.volumeXZ.shape[0]-1, length = self.widthXZ,orient=HORIZONTAL, command=self.updateVolumeXZSlice, bg=self.canvasbackground, fg =self.canvasforeground)
self.vol_sliderXZ.grid(row=2,column=0)
self.vol_sliderXZ.set(self.volumeXZ.shape[0]//2)
self.vol_sliderYZ.destroy()
if self.SMALLWINDOW is True:
self.vol_sliderYZ = Scale(self.FrameVolumeYZ, from_=0, to=self.volumeYZ.shape[0]-1, length = self.widthYZ//2, orient=HORIZONTAL, command=self.updateVolumeYZSlice, bg=self.canvasbackground, fg =self.canvasforeground)
else:
self.vol_sliderYZ = Scale(self.FrameVolumeYZ, from_=0, to=self.volumeYZ.shape[0]-1, length = self.widthYZ, orient=HORIZONTAL, command=self.updateVolumeYZSlice, bg=self.canvasbackground, fg =self.canvasforeground)
self.vol_sliderYZ.grid(row=2,column=0)
self.vol_sliderYZ.set(self.volumeYZ.shape[0]//2)
#updating volumes
self.updateSegmentation()
self.updateVolumeSliceXY(None)
self.updateVolumeXZ()
self.updateVolumeXZSlice(None)
self.updateVolumeYZ()
self.updateVolumeYZSlice(None)
#loading segmentation - filename has to be complete_segmentation.tif
try:
self.loadCompleteSegmentation(self.initialdir+"//complete_segmentation.tif")
except:
print('Please load segmentation manually. No file found at:',self.initialdir+"//complete_segmentation.tif")
[docs] def resetSegmentation(self):
"""
Reset segmentation
"""
self.statusText.set("Resetting segmentation...")
self.disableDataButtons()
self.segmentation = np.zeros((self.volume.shape[1],self.volume.shape[0],self.volume.shape[2])).astype('uint8')
self.heatmapType = 'Drusen'
self.heatmap_rpe_bruchs = np.zeros((self.volume.shape[1],self.volume.shape[2])).astype('int32')
self.updateSegmentation()
self.enableDataButtons()
self.statusText.set("Segementation reset done.")
[docs] def loadCompleteSegmentation(self, path=None):
"""
Load segmentation volume calling ImportHandler.
"""
self.statusText.set("Loading segmentation...")
self.disableDataButtons()
try:
if self.shiftedValuesSlowScan is None:
self.segmentation, self.heatmap_rpe_bruchs = ImportHandler.loadCompleteSegmentation(self.initialdir, self.shiftedValues, path)
else:
self.segmentation, self.heatmap_rpe_bruchs = ImportHandler.loadCompleteSegmentation(self.initialdir, self.shiftedValues+self.shiftedValuesSlowScan, path)
self.updateSegmentation()
self.statusText.set("Segmentation loaded!")
except Exception as e:
self.statusText.set("Error while loading segmentation!")
print("Error:", e)
self.enableDataButtons()
[docs] def updateSegmentation(self):
"""
Updated Segmentation
Different things happen for different heatmaps
"""
if(self.heatmapType is 'Vesselness'):
self.heat_slider_min.configure(from_=0, to=255)
self.heat_slider_min.set(0)
self.heatmap = self.vessel_original
self.heatmap_max = 255
self.updateHeatSlice(None)
return
elif(self.heatmapType is 'Drusen'):
self.heatmap = self.heatmap_rpe_bruchs
self.heatmapForSaving = self.heatmap
elif(self.heatmapType is 'DrusenMetric'):
self.heatmap = self.metric_map
self.heatmapForSaving = self.heatmap
elif(self.heatmapType is 'subRPESlab'):
self.heatmapForSaving = self.heatmap.copy()
self.heatmap_max = np.maximum(10,np.max(self.heatmap))
self.heatmap_min = np.min(self.heatmap)
self.label_top_HM.configure(text=' '+str(int(self.heatmap_max)))
self.heat_slider_min.configure( to=self.heatmap_max, resolution=1)
self.heat_slider_min.set(0)
self.updateHeatSlice(None)
[docs] def keyPressedUp(self, event):
"""
Zooming into planes when key up pressed
"""
self.scale_img = np.round(self.scale_img + self.scale_delta,1)
self.updateVolumeSliceXY_Helper()
self.updateVolumeYZSlice_Helper()
self.updateVolumeXZSlice_Helper()
self.updateHeatSlice_Helper()
[docs] def keyPressedDown(self, event):
"""
Zooming out of planes when key down pressed
"""
self.scale_img = np.round(self.scale_img - self.scale_delta,1)
if(self.scale_img < 1.0):
self.scale_img = 1.0
self.updateVolumeSliceXY_Helper()
self.updateVolumeYZSlice_Helper()
self.updateVolumeXZSlice_Helper()
self.updateHeatSlice_Helper()
[docs] def getTopYMotion(self, *args):
"""
Scrollbars vertical method
"""
self.canvasXY.yview(*args)
self.canvas_heat.yview(*args)
[docs] def getTopXMotion(self, *args):
"""
Scrollbars horizontal method
"""
self.canvasXY.xview(*args)
self.canvas_heat.xview(*args)
[docs] def getBottomYMotion(self,*args):
"""
Scrollbars vertical method
"""
self.canvasXZ.yview(*args)
self.canvasYZ.yview(*args)
[docs] def getBottomXMotion(self,*args):
"""
Scrollbars horizontal method
"""
self.canvasXZ.xview(*args)
self.canvasYZ.xview(*args)
[docs] def updateVolumeSliceXY(self, event):
"""
Updating XY plane image
Parameters
----------
event: event
not used but necessary
"""
if self._job_volume_xy:
self.master.after_cancel(self._job_volume_xy)
self._job_volume_xy = self.master.after(50, self.updateVolumeSliceXY_Helper)
[docs] def updateVolumeSliceXY_Helper(self):
"""
Updating XY plane image helper
"""
self._job_volume_xy = None
#setting up slice
try:
self.slicexy = self.volume[self.vol_sliderXY.get()].astype('uint8')
except:
self.slicexy = self.volume[self.volume.shape[0]//2].astype('uint8')
#setting up downscaled (self.SMALLWINDOW) or original size
if(self.SMALLWINDOW == True):
self.slice_pil_xy = PIL.Image.fromarray(self.slicexy[::2,::2])
else:
self.slice_pil_xy = PIL.Image.fromarray(self.slicexy)
#zoom scale
if self.scale_img is not 1.0:
h,w = self.slice_pil_xy.size
self.slice_pil_xy = self.slice_pil_xy.resize((int(h*self.scale_img), int(w*self.scale_img)), PIL.Image.ANTIALIAS)
#contrast settings
if self.contrast is True:
self.slice_pil_xy = PIL.ImageEnhance.Contrast(self.slice_pil_xy).enhance(self.contrast_value)
self.slice_pil_xy = PIL.ImageEnhance.Brightness(self.slice_pil_xy).enhance(self.brightness_value)
#load image on canvas
try:
self.photoxy = PIL.ImageTk.PhotoImage(image = self.slice_pil_xy)
self.canvasXY.itemconfig(self.image_on_canvas_xy, image = self.photoxy)
self.canvasXY.configure(scrollregion = self.canvasXY.bbox("all"))
except:
return
#when blending active, blend
self.blendSlices()
[docs] def updateHeatSlice(self, event):
"""
Updating Heatmap image
Parameters
----------
event: event
not used but necessary
"""
if self._job_HM:
self.master.after_cancel(self._job_HM)
self._job_HM = self.master.after(50, self.updateHeatSlice_Helper)
[docs] def updateHeatSlice_Helper(self):
"""
Updating Heatmap image helper
"""
self._job_HM = None
#load colormap
self.cmap_own= cm.get_cmap(self.COLORMAP, self.heatmap_max)
#Processing pipeline vesselness
if(self.heatmapType is 'Vesselness'):
#thresholding active
if self.heat_slider_min.get() == 0.0:
self.heatmapThreshed = self.vessel_original
else:
self.heatmapThreshed = np.where(self.heatmap < self.heat_slider_min.get(), 0 , self.heatmap)
self.heatmapThreshed = self.heatmapThreshed.astype('uint8')
#configure colorbar
cmap_std= cm.get_cmap('gray', 256)
self.label_top_HM.configure(text=str(int(np.max(self.volume_original))))
if(self.SMALLWINDOW == True):
self.heatmapThreshedPil = PIL.Image.fromarray(self.heatmapThreshed[::2,::2])
self.cbar_arr = np.array([range(0,self.heightHM//2)[::-1] for i in range(20)]).T.astype('uint8')
else:
self.heatmapThreshedPil = PIL.Image.fromarray(self.heatmapThreshed)
self.cbar_arr = np.zeros((self.heightHM, 20)).astype('uint8')
for i in range (256):
self.cbar_arr[i*2:i*2+2,:] = i
self.cbar_arr = self.cbar_arr[::-1]
#Processing projections
elif(self.heatmapType is 'minILMRPEProjection' or self.heatmapType is 'subRPESlab'):
#thresholding active
if self.heat_slider_min.get() == 0.0:
self.heatmapThreshed = self.heatmap.copy()
else:
self.heatmapThreshed = np.where(self.heatmap < self.heat_slider_min.get(), 0 , self.heatmap)
self.heatmapThreshed = self.heatmapThreshed.astype('uint8')
#configure colorbar
cmap_std= cm.get_cmap('gray', 256)
self.label_top_HM.configure(text=str(int(np.max(self.volume_original))))
if(self.SMALLWINDOW == True):
self.heatmapThreshedPil = PIL.Image.fromarray(self.heatmapThreshed[::2,::2])
self.cbar_arr = np.array([range(0,self.heightHM//2)[::-1] for i in range(20)]).T.astype('uint8')
else:
self.heatmapThreshedPil = PIL.Image.fromarray(self.heatmapThreshed)
self.cbar_arr = np.zeros((self.heightHM, 20)).astype('uint8')
for i in range (256):
self.cbar_arr[i*2:i*2+2,:] = i
self.cbar_arr = self.cbar_arr[::-1]
#Processing delineating GA
elif(self.heatmapType is 'MarkGaByRect'):
cmap_std= cm.get_cmap('gray', 256)
self.label_top_HM.configure(text=str(int(np.max(self.volume_original))))
if(self.SMALLWINDOW == True):
self.heatmapThreshedPil = PIL.Image.fromarray(self.heatmapThreshed[::2,::2])
self.cbar_arr = np.array([range(0,self.heightHM//2)[::-1] for i in range(20)]).T.astype('uint8')
else:
self.heatmapThreshedPil = PIL.Image.fromarray(self.heatmapThreshed)
self.cbar_arr = np.zeros((self.heightHM, 20)).astype('uint8')
for i in range (256):
self.cbar_arr[i*2:i*2+2,:] = i
self.cbar_arr = self.cbar_arr[::-1]
#Other mode
else:
#thresholding active
try:
self.heatmapThreshed = np.where(self.heatmap < self.heat_slider_min.get(), 0 ,self.heatmap).astype('uint8')
except:
self.heatmapThreshed = self.heatmap.astype('uint8')
#configure colorbar
cmap_std= cm.get_cmap(self.COLORMAP, 256)
try:
self.label_top_HM.configure(text=' '+str(np.max(self.heatmap)))
except:
return
if(self.SMALLWINDOW == True):
self.heatmapThreshedPil = PIL.Image.fromarray(self.cmap_own(self.heatmapThreshed[::2,::2], bytes=True))
self.cbar_arr = np.array([range(0,self.heightHM//2)[::-1] for i in range(20)]).T.astype('uint8')
else:
self.heatmapThreshedPil = PIL.Image.fromarray(self.cmap_own(self.heatmapThreshed, bytes=True))
self.cbar_arr = np.zeros((self.heightHM, 20)).astype('uint8')
for i in range (256):
self.cbar_arr[i*2:i*2+2,:] = i
self.cbar_arr = self.cbar_arr[::-1]
#save original heatmap
self.heatmap_original = self.heatmapThreshedPil.copy()
#scale heatmap
if self.scale_img is not 1.0:
h,w = self.heatmapThreshedPil.size
self.heatmapThreshedPil = self.heatmapThreshedPil.resize((int(h*self.scale_img), int(w*self.scale_img)), PIL.Image.ANTIALIAS)
#update heatmap
try:
self.photo_heat = PIL.ImageTk.PhotoImage(image = self.heatmapThreshedPil)
self.photo_cbar = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(cmap_std(self.cbar_arr, bytes=True)))
self.canvas_heat.itemconfig(self.image_on_canvas_heat, image = self.photo_heat)
self.canvas_cbar_HM.itemconfig(self.image_on_canvas_cbar, image = self.photo_cbar)
except:
return
self.canvas_heat.configure(scrollregion = self.canvas_heat.bbox("all"))
#update blender
self.blendSlices()
[docs] def blendSlices(self, event = None):
"""
Updating Blender
Parameters
----------
event: event
not used but necessary
"""
self._job_Blender = None
#only valid blendings
if(self.blender is False or self.heatmapType is 'Vesselness' or self.heatmapType is 'subRPESlab' or self.heatmapType is 'MarkGaByRect' or self.heatmapType is 'minILMRPEProjection'):
return
#update blender
self.updateBlender(None)
[docs] def updateBlender(self, event):
"""
Updating Blender
Parameters
----------
event: event
not used but necessary
"""
if self._job_Blender:
self.master.after_cancel(self._job_Blender)
self._job_XZ = self.master.after(50, self.updateBlenderHelper)
[docs] def updateBlenderHelper(self):
"""
Updating Blender Helper
Parameters
----------
event: event
not used but necessary
"""
self._job_Blender = None
#setting up image
if(self.SMALLWINDOW == True):
self.img_vol = self.slicexy[::2, ::2].astype('uint8')
else:
self.img_vol = self.slicexy.astype('uint8')
#contrast active
if self.contrast is True:
self.img_vol = PIL.ImageEnhance.Contrast(self.img_vol).enhance(self.contrast_value)
self.img_vol = PIL.ImageEnhance.Brightness(self.img_vol).enhance(self.brightness_value)
self.img_heatmap = np.asarray(self.heatmap_original)[:,:,0:3].astype('uint8')
#add weighted images
if(self.img_vol.size != self.img_heatmap.size):
self.blended_img = np.zeros((self.img_heatmap.shape)).astype('uint8')
else:
self.blended_img = (self.img_heatmap[:,:,:]*self.blend_slider.get() + self.img_vol*(1 - self.blend_slider.get())).astype('uint8') + 30
img = PIL.Image.fromarray(self.blended_img)
#scaling
if self.scale_img is not 1.0:
h,w = img.size
img = img.resize((int(h*self.scale_img), int(w*self.scale_img)), PIL.Image.ANTIALIAS)
#configure image
self.photo_blended = PIL.ImageTk.PhotoImage(image = img)
self.canvasXY.itemconfig(self.image_on_canvas_xy, image = self.photo_blended)
self.canvasXY.configure(scrollregion = self.canvasXY.bbox("all"))
[docs] def updateVolumeXZ(self):
"""
Updating XZ Slice by swapping volume
"""
self.volumeXZ = np.swapaxes(self.volume, axis1 = 1, axis2 = 0)
self.updateVolumeXZSlice(None)
[docs] def updateVolumeXZSlice(self, event):
"""
Updating XZ Slice
"""
if self._job_XZ:
self.master.after_cancel(self._job_XZ)
self._job_XZ = self.master.after(50, self.updateVolumeXZSlice_Helper)
[docs] def updateVolumeXZSlice_Helper(self):
"""
Updating XZ Slice helper
"""
self._job_XZ = None
#setting up slices
try:
self.sliceXZ = self.volumeXZ[self.vol_sliderXZ.get()].astype('uint8')
except:
self.sliceXZ = self.volumeXZ[self.volumeXZ.shape[0]//2].astype('uint8')
if(self.SMALLWINDOW == True):
self.slice_pilXZ = PIL.Image.fromarray(self.sliceXZ[::2,::2])
else:
self.slice_pilXZ = PIL.Image.fromarray(self.sliceXZ)
#scaling active
if self.scale_img is not 1.0:
h,w = self.slice_pilXZ.size
self.slice_pilXZ = self.slice_pilXZ.resize((int(h*self.scale_img), int(w*self.scale_img)), PIL.Image.ANTIALIAS)
#contrast active
if self.contrast is True:
self.slice_pilXZ = PIL.ImageEnhance.Contrast(self.slice_pilXZ).enhance(self.contrast_value)
self.slice_pilXZ = PIL.ImageEnhance.Brightness(self.slice_pilXZ).enhance(self.brightness_value)
#set up canvas
try:
self.photoXZ = PIL.ImageTk.PhotoImage(image = self.slice_pilXZ)
self.canvasXZ.itemconfig(self.image_on_canvas_XZ, image = self.photoXZ)
self.canvasXZ.configure(scrollregion = self.canvasXZ.bbox("all"))
except:
return
[docs] def updateVolumeYZ(self):
"""
Updating YZ Slice by swapping volume
"""
self.volumeYZ = np.swapaxes(self.volume, axis1 = 0, axis2 = 1)
self.volumeYZ = np.swapaxes(self.volumeYZ, axis1 = 2, axis2 = 0)
self.updateVolumeYZSlice(None)
[docs] def updateVolumeYZSlice(self, event):
"""
Updating YZ Slice
Parameters
----------
event: event
not used but necessary
"""
if self._job_YZ:
self.master.after_cancel(self._job_YZ)
self._job_YZ = self.master.after(50, self.updateVolumeYZSlice_Helper)
[docs] def updateVolumeYZSlice_Helper(self):
"""
Updating YZ Slice by swapping volume
"""
self._job_YZ = None
#setting up slice
try:
self.sliceYZ = self.volumeYZ[self.vol_sliderYZ.get()].astype('uint8')
except:
self.sliceYZ = self.volumeYZ[self.volumeYZ.shape[0]//2].astype('uint8')
if(self.SMALLWINDOW == True):
self.slice_pilYZ = PIL.Image.fromarray(self.sliceYZ[::2,::2])
else:
self.slice_pilYZ = PIL.Image.fromarray(self.sliceYZ)
#scaling
if self.scale_img is not 1.0:
h,w = self.slice_pilYZ.size
self.slice_pilYZ = self.slice_pilYZ.resize((int(h*self.scale_img), int(w*self.scale_img)), PIL.Image.ANTIALIAS)
#contrast
if self.contrast is True:
self.slice_pilYZ = PIL.ImageEnhance.Contrast(self.slice_pilYZ).enhance(self.contrast_value)
self.slice_pilYZ = PIL.ImageEnhance.Brightness(self.slice_pilYZ).enhance(self.brightness_value)
#load image to canvas
try:
self.photoYZ = PIL.ImageTk.PhotoImage(image = self.slice_pilYZ)
self.canvasYZ.itemconfig(self.image_on_canvas_YZ, image = self.photoYZ)
self.canvasYZ.configure(scrollregion = self.canvasYZ.bbox("all"))
except:
return
[docs] def runRPERefinement(self):
"""
Run RPE refinement
Extended graph-cut neighborhood (7 neighbors) with limited search space (above BM-only)
This ensures that no CC is segmented falsely as RPE and improves accuracy.
"""
if np.count_nonzero(self.segmentation == self.BM_VALUE) == 0:
print("No BM segmentation present.")
return
self.statusText.set("Running RPE Refinement...")
if self.shiftedValuesSlowScan is None:
rfm = RPERefinementAlgorithm.RPERefinementClass(self.volume_original, self.segmentation, self.shiftedValues)
else:
rfm = RPERefinementAlgorithm.RPERefinementClass(self.volume_original, self.segmentation, (self.shiftedValues+self.shiftedValuesSlowScan).astype('int32'))
self.segmentation = rfm.runPipeline()
self.heatmap_rpe_bruchs = Heatmap.calculateHeatmap(self.segmentation)
self.updateSegmentation()
del rfm
self.statusText.set("RPE refinement finished")
[docs] def runAutomaticSegmentation(self):
"""
Run Automatic three-layer segmentation
Algorithms can be inserted here - Replace "tls" by own Algorithm and store result in self.segmentation
"""
self.statusText.set("Running 3-Layer segmentation...")
#INSERT CODE HERE - call segmentation and store
tls = ThreeLayerSegmentation.LayerSegmentation(self.volume_original, flattening_factor=self.flattening_polynomial,statusText = self.statusText)
self.segmentation = tls.runPipeline()
# YOU SHOULD NOT TOUCH THE REST HERE
#resetting flattening
self.flatten = True
self.shiftedValues = np.zeros((self.shiftedValues.shape[0],self.shiftedValues.shape[1]))
#calculate heatmaps from segmentation
self.heatmap_rpe_bruchs = Heatmap.calculateHeatmap(self.segmentation)
self.updateSegmentation()
del tls
self.statusText.set("3-Layer Segmentation finished.")
[docs] def markGA(self):
"""
Delineate GA selected - Setting up RPE slab
"""
self.statusText.set("Running 3-Layer segmentation...")
self.thicknessMarkGA = 2
self.subBMSlab(self.projMode, int(self.thickness_var.get()))
self.heatmapType = 'MarkGaByRect'
self.heatmapThreshed = self.heatmap.copy()
self.heatmapForSaving = self.heatmap.copy()
DelineateGA.pack(self)
[docs] def HMDrusen(self):
"""
Drusen Map (RPEDC) selected
"""
self.statusText.set("Setting up RPEDC Map...")
DelineateGA.clearMarkingData(self)
self.heatmapType = 'Drusen'
self.updateSegmentation()
self.statusText.set("RPEDC Map selected")
[docs] def minProjectionILMRPE(self):
"""
Projection ILM to RPE selected
"""
self.statusText.set("Setting up ILM to RPE Projection...")
DelineateGA.clearMarkingData(self)
self.heatmapType = 'minILMRPEProjection'
self.blendSlices()
self.heatmap = Projections.getProjectionILMRPE(np.asarray(self.volume_original), self.segmentation, self.projMode)
self.heatmapForSaving = self.heatmap.copy()
self.heatmap = (255*self.heatmap/65535.0).astype('uint8')
self.updateSegmentation()
self.statusText.set("ILM to RPE Projection loaded.")
[docs] def showVessels(self):
"""
Vesselness selected
"""
self.statusText.set("Setting up Vessel image...")
DelineateGA.clearMarkingData(self)
self.heatmapType = 'Vesselness'
self.blendSlices()
self.vessel_original = Projections.getVesselnessImage(np.asarray(self.volume_original), self.segmentation)
self.heatmap = self.vessel_original
self.heatmapForSaving = self.vessel_original.copy()
self.updateSegmentation()
self.statusText.set("Vessel image loaded.")
[docs] def subRPESlab(self, ga_proj_mode=None, ga_thickness = None):
"""
Sub-RPE Slab selected
Optional:
---------
ga_proj_mode: string
projection type(max,min,mean,median)
ga_thickness: scalar
thickness of slab
"""
self.statusText.set("Setting up sub-RPE slab...")
DelineateGA.clearMarkingData(self)
self.heatmapType = 'subRPESlab'
self.blendSlices()
# normal and ga delineating mode
if ga_proj_mode is None and ga_thickness is None:
try:
thickness = int(self.thickness_var.get())
except:
thickness = 6
self.thickness_var.set(6)
self.heatmap = Projections.getsubRPESlab(np.asarray(self.volume_original), self.segmentation, thickness,self.projMode)
else:
self.heatmap = Projections.getsubRPESlab(np.asarray(self.volume_original), self.segmentation, ga_thickness, ga_proj_mode)
#store heatmap
self.heatmapForSaving = self.heatmap.copy().astype('uint16')
self.heatmap = (255*self.heatmap/65535.0).astype('uint8')
self.updateSegmentation()
self.statusText.set("Sub-RPE slab loaded.")
[docs] def subBMSlab(self, ga_proj_mode=None, ga_thickness = None):
"""
Sub-BM Slab selected
Optional:
---------
ga_proj_mode: string
projection type(max,min,mean,median)
ga_thickness: scalar
thickness of slab
"""
self.statusText.set("Setting up sub-BM slab...")
self.heatmapType = 'subRPESlab'
self.blendSlices()
if ga_proj_mode is None and ga_thickness is None:
try:
thickness = int(self.entryThickness.get())
except:
thickness = 6
self.thickness_var.set(6)
self.heatmap = Projections.getsubBMSlab(np.asarray(self.volume_original), self.segmentation, thickness,self.projMode)
else:
self.heatmap = Projections.getsubBMSlab(np.asarray(self.volume_original), self.segmentation, ga_thickness, ga_proj_mode)
#store heatmap
self.heatmapForSaving = self.heatmap.copy()
self.heatmap = (255*self.heatmap/65535.0).astype('uint8')
self.updateSegmentation()
self.statusText.set("Sub-BM slab loaded.")
[docs] def inpaintOriginalSegmentation(self):
"""
Inpaint segmentation - Thickness is 3
"""
self.statusText.set("Inpainting segmentation lines...")
#flatten original volume (note the minus at shifted volume!!!)
inpaintedVolume = self.volume_original.copy()
inpaintedVolume = unFlatten(inpaintedVolume, -self.shiftedValues)
#volume to rgb
inpaintedVolume = ImportHandler.getOriginalRGBVolume(inpaintedVolume)
#segment the three layers
coordinates_rpe = np.where(self.segmentation == self.RPE_VALUE)
coordinates_vit = np.where(self.segmentation == self.ILM_VALUE)
coordinates_bruchs = np.where(self.segmentation == self.BM_VALUE)
#inpaint with thickness 3
inpaintedVolume[coordinates_rpe[1],coordinates_rpe[0],coordinates_rpe[2],:] = tuple(int(self.colorRPE.get().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4))
inpaintedVolume[coordinates_rpe[1]+1,coordinates_rpe[0],coordinates_rpe[2],:] = tuple(int(self.colorRPE.get().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4))
inpaintedVolume[coordinates_rpe[1]-1,coordinates_rpe[0],coordinates_rpe[2],:] = tuple(int(self.colorRPE.get().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4))
inpaintedVolume[coordinates_bruchs[1],coordinates_bruchs[0],coordinates_bruchs[2],:] = tuple(int(self.colorBM.get().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4))
inpaintedVolume[coordinates_bruchs[1]+1,coordinates_bruchs[0],coordinates_bruchs[2],:] = tuple(int(self.colorBM.get().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4))
inpaintedVolume[coordinates_bruchs[1]-1,coordinates_bruchs[0],coordinates_bruchs[2],:] = tuple(int(self.colorBM.get().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4))
inpaintedVolume[coordinates_vit[1],coordinates_vit[0],coordinates_vit[2],:] = tuple(int(self.colorILM.get().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4))
inpaintedVolume[coordinates_vit[1]+1,coordinates_vit[0],coordinates_vit[2],:] = tuple(int(self.colorILM.get().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4))
inpaintedVolume[coordinates_vit[1]-1,coordinates_vit[0],coordinates_vit[2],:] = tuple(int(self.colorILM.get().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4))
self.volume = inpaintedVolume
#update planes
self.updateVolumeSliceXY(None)
self.updateVolumeYZ()
self.updateVolumeXZ()
self.statusText.set("Inpainting finished.")
[docs] def inpaintSegmentationFiltered(self):
"""
Inpaint filtered segmentation - Thickness is 3
When thresholding the heatmap, the results can be backpropagated.
This reduces the sensitivity of the map.
"""
self.statusText.set("Inpainting filtered segmentation...")
#load original volume
inpaintedVolume = self.volume_original.copy()
#flatten original volume (note the minus at shifted volume!!!)
inpaintedVolume = unFlatten(inpaintedVolume, -self.shiftedValues)
#volume to rgb
inpaintedVolume = ImportHandler.getOriginalRGBVolume(inpaintedVolume)
#inpaint
for z in range(self.segmentation.shape[0]):
coordinates_bruchs = np.where(self.segmentation[z] == self.BM_VALUE)
inpaintedVolume[coordinates_bruchs[0],z,coordinates_bruchs[1],:] = tuple(int(self.colorBM.get().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4))
inpaintedVolume[coordinates_bruchs[0]+1,z,coordinates_bruchs[1],:] = tuple(int(self.colorBM.get().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4))
inpaintedVolume[coordinates_bruchs[0]-1,z,coordinates_bruchs[1],:] = tuple(int(self.colorBM.get().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4))
for i in range(coordinates_bruchs[0].size):
coordinates_rpe_fil = coordinates_bruchs[0][i] - self.heatmapThreshed[z,coordinates_bruchs[1][i]] - 1
inpaintedVolume[coordinates_rpe_fil, z , coordinates_bruchs[1][i],:] = tuple(int(self.colorRPE.get().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4))
inpaintedVolume[coordinates_rpe_fil+1, z , coordinates_bruchs[1][i],:] = tuple(int(self.colorRPE.get().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4))
inpaintedVolume[coordinates_rpe_fil-1, z , coordinates_bruchs[1][i],:] = tuple(int(self.colorRPE.get().lstrip('#')[i:i+2], 16) for i in (0, 2 ,4))
#update planes
self.volume = inpaintedVolume
self.updateVolumeSliceXY(None)
self.updateVolumeYZ()
self.updateVolumeXZ()
self.statusText.set("Inpainting finished.")
[docs] def undoSegmentation(self):
"""
Reset inpainted layers - back to volume only
"""
self.statusText.set("Reset inpainting...")
inpaintedVolume = self.volume_original.copy()
#flatten original volume (note the minus at shifted volume!!!)
inpaintedVolume = unFlatten(inpaintedVolume, -self.shiftedValues)
#vol to rgb
inpaintedVolume = ImportHandler.getOriginalRGBVolume(inpaintedVolume)
#update planes
self.volume = inpaintedVolume
self.updateVolumeSliceXY(None)
self.updateVolumeXZ()
self.updateVolumeYZ()
self.statusText.set("Segmentation reset finished.")
[docs] def metricCalculation(self):
"""
Calculate Drusen metrics and show in new window
"""
self.metric_map = Heatmap.calculateMetrics(self.heatmap, self.heat_slider_min.get(), self.tv_res.get(), self.axial_res.get())
self.heatmapType = 'DrusenMetric'
self.updateSegmentation()
[docs] def start_correction_mode(self):
"""
Start correction method
"""
#destroy explore windows
self.FrameVolumeXY.destroy()
self.FrameVolumeXZ.destroy()
self.FrameVolumeYZ.destroy()
self.FrameHeatmap.destroy()
self.FrameButton.destroy()
#reset segmentation
self.segmentation_original = self.segmentation.copy()
#call manual correction
try:
createManualCorrectionFrame(self)
except Exception as e:
print(e)