Newer
Older
from typing import Callable, Sequence, Tuple, Union
import skimage.segmentation as sisg
from PIL import Image as plim
tk_image_t = pltk.PhotoImage
STATIC_ROW_MIN_HEIGHT = 30
class soma_validation_window_t:
"gfp_pil_image_initial",
"gfp_pil_image",
"gfp_tk_image",
"lmap_pil_image",
"lmap_tk_image",
"color_version",
"with_cm",
"main_window",
gfp_pil_image_initial: image_t
gfp_pil_image: image_t
gfp_tk_image: tk_image_t
lmap_mip: array_t
lmap_mip_4_display: array_t
lmap_pil_image: image_t
color_version: bool
with_cm: str
main_window: tknt.Tk
mip_axis_wgt: tknt.Menubutton
gfp_wgt: tknt.Canvas
lmap_wgt: tknt.Canvas
lmap: array_t,
mip_axis: int = -1,
color_version: bool = True,
with_cm: str = None,
):
"""
with_cm: "plasma" and "viridis" seem to be good options
"""
main_window = tknt.Tk()
(
gfp_mip,
lmap_mip,
lmap_mip_4_display,
gfp_pil_image,
gfp_tk_image,
lmap_pil_image,
lmap_tk_image,
) = _MIPImages(
gfp,
lmap,
mip_axis,
color_version,
with_cm,
main_window,
gfp_pil_image_initial = gfp_pil_image
# ---- Creation of widgets
if mip_axis < 0:
mip_axis = gfp.ndim + mip_axis
mip_axis_wgt = _MIPAxisChoiceWidget(
mip_axis, self._ChangeMIPAxis, gfp.shape, main_window
gfp_wgt = tknt.Label(main_window, image=gfp_tk_image)
lmap_wgt = tknt.Label(main_window, image=lmap_tk_image)
cursor_nfo = tknt.Label(main_window, text="")
done_button = tknt.Button(main_window, text="Done", command=main_window.quit)
gfp_wgt.bind("<Configure>", self._OnResize)
lmap_wgt.bind("<Motion>", self._DisplaySomaLabel)
lmap_wgt.bind("<Button-1>", self._DeleteSoma)
# --- Widget placement in grid
next_available_row = 0
mip_axis_wgt.grid(row=next_available_row, column=0)
next_available_row += 1
gfp_wgt.grid(row=next_available_row, column=0)
lmap_wgt.grid(row=next_available_row, column=1)
next_available_row += 1
cursor_nfo.grid(row=next_available_row, column=0)
done_button.grid(row=next_available_row, column=1)
next_available_row += 1
main_window.rowconfigure(0, weight=1, minsize=STATIC_ROW_MIN_HEIGHT)
main_window.rowconfigure(1, weight=10)
main_window.rowconfigure(2, weight=1, minsize=STATIC_ROW_MIN_HEIGHT)
main_window.columnconfigure(0, weight=1)
main_window.columnconfigure(1, weight=1)
# --- Saving required variables as object attributes
for attribute in self.__class__.__slots__:
setattr(self, attribute, eval(attribute))
def LaunchValidation(self) -> int:
self.main_window.mainloop()
self.main_window.destroy()
relabeled, _, _ = sisg.relabel_sequential(self.lmap)
self.lmap[...] = relabeled
return nmpy.amax(self.lmap)
def _OnResize(self, event: tknt.EventType.Configure) -> None:
""""""
self.gfp_pil_image = self.gfp_pil_image_initial.resize((event.width, event.height))
self.gfp_tk_image = pltk.PhotoImage(master=self.main_window, image=self.gfp_pil_image)
self.gfp_wgt.configure(image=self.gfp_tk_image)
def _ChangeMIPAxis(self, mip_axis: tknt.IntVar, *_, **__):
""""""
new_mip_axis = mip_axis.get()
(
gfp_mip,
lmap_mip,
lmap_mip_4_display,
gfp_pil_image,
gfp_tk_image,
lmap_pil_image,
lmap_tk_image,
) = _MIPImages(
self.gfp,
self.lmap,
new_mip_axis,
self.color_version,
self.with_cm,
self.main_window,
self.gfp_pil_image_initial = self.gfp_pil_image
self.gfp_wgt.configure(image=gfp_tk_image)
self.lmap_wgt.configure(image=lmap_tk_image)
self.gfp_mip = gfp_mip
self.gfp_pil_image = gfp_pil_image
self.gfp_tk_image = gfp_tk_image
self.lmap_mip = lmap_mip
self.lmap_mip_4_display = lmap_mip_4_display
self.lmap_pil_image = lmap_pil_image
self.lmap_tk_image = lmap_tk_image
def _DisplaySomaLabel(self, event: tknt.EventType.Motion) -> None:
except IndexError:
# This problem appeared when pack was replaced with grid. Setting ipad? and pad? to zero when adding the
# lmap_wgt to the grid did not solve it. Is this a bug in TkInter grid?
return
self.cursor_nfo.configure(text=f"Label:{label}@{row}x{col}")
def _DeleteSoma(self, event: tknt.EventType.ButtonPress) -> None:
""""""
row = event.y
col = event.x
except IndexError:
# This problem appeared when pack was replaced with grid. Setting ipad? and pad? to zero when adding the
# lmap_wgt to the grid did not solve it. Is this a bug in TkInter grid?
if label > 0:
self.lmap[self.lmap == label] = 0
soma_bmap = self.lmap_mip == label
self.lmap_mip[soma_bmap] = 0
if self.lmap_mip_4_display.ndim == 1:
self.lmap_mip_4_display[soma_bmap] = 0
for channel in range(self.lmap_mip_4_display.shape[2]):
self.lmap_mip_4_display[..., channel][soma_bmap] = 0
self.lmap_tk_image, self.lmap_pil_image = _TkImageFromNumpyArray(
self.lmap_mip_4_display, self.main_window
)
self.lmap_wgt.configure(image=self.lmap_tk_image)
def _MIPImages(
gfp: array_t,
lmap: array_t,
mip_axis: int,
color_version: bool,
with_cm: str,
parent: Union[tknt.Widget, tknt.Tk],
) -> Tuple[array_t, array_t, array_t, image_t, tk_image_t, image_t, tk_image_t]:
""""""
gfp_mip = nmpy.amax(gfp, axis=mip_axis)
gfp_mip *= 255.0 / nmpy.amax(gfp_mip)
lmap_mip = nmpy.amax(lmap, axis=mip_axis)
if color_version:
if with_cm is None:
lmap_mip_4_display = _ColoredVersion(lmap_mip)
else:
lmap_mip_4_display = _ColoredVersionFromColormap(lmap_mip, with_cm)
else:
lmap_mip_4_display = _ScaledVersion(lmap_mip)
gfp_tk_image, gfp_pil_image = _TkImageFromNumpyArray(gfp_mip, parent)
lmap_tk_image, lmap_pil_image = _TkImageFromNumpyArray(lmap_mip_4_display, parent)
return (
gfp_mip,
lmap_mip,
lmap_mip_4_display,
gfp_pil_image,
gfp_tk_image,
lmap_pil_image,
lmap_tk_image,
)
def _ScaledVersion(image: array_t, offset: int = 50) -> array_t:
"""
offset: Value of darkest non-background intensity
"""
scaling = (255.0 - offset) / nmpy.max(image)
output = scaling * image + offset
output[image == 0] = 0
return output.astype(nmpy.uint8)
def _ColoredVersion(image: array_t) -> array_t:
""""""
max_label = nmpy.amax(image)
half_length = int(round(0.5 * max_label))
shuffled_labels = labels[half_length:] + labels[:half_length]
shuffled_image = nmpy.zeros_like(image)
for label, shuffled_label in enumerate(shuffled_labels):
shuffled_image[image == label] = shuffled_label
output = nmpy.dstack((image, shuffled_image, max_label - image))
output = (255.0 / max_label) * output
output[image == 0] = 0
return output.astype(nmpy.uint8)
def _ColoredVersionFromColormap(image: array_t, colormap_name: str) -> array_t:
""""""
output = nmpy.zeros(image.shape + (3,), dtype=nmpy.uint8)
LinearValueToRGB = mpcm.get_cmap(colormap_name)
max_label = nmpy.amax(image)
for label in range(1, max_label + 1):
color_01 = LinearValueToRGB((label - 1.0) / (max_label - 1.0))
color_255 = nmpy.round(255.0 * nmpy.array(color_01[:3]))
output[image == label, :] = color_255
return output
def _TkImageFromNumpyArray(
array: array_t, parent: Union[tknt.Widget, tknt.Tk]
) -> Tuple[tk_image_t, image_t]:
pil_image = plim.fromarray(array)
tk_image = pltk.PhotoImage(master=parent, image=pil_image)
return tk_image, pil_image
def _MIPAxisChoiceWidget(
current_axis: int,
Action: Callable,
shape: Sequence[int],
parent: Union[tknt.Widget, tknt.Tk],
) -> tknt.Menubutton:
""""""
title = f"MIP Axis [{','.join(str(_lgt) for _lgt in shape)}]"
output = tknt.Menubutton(parent, text=title, relief="raised")
menu = tknt.Menu(output, tearoff=False)
entries = ("First dim", "Second dim", "Third dim")
selected_mip_axis = tknt.IntVar()
for idx, entry in enumerate(entries):
menu.add_radiobutton(label=entry, value=idx, variable=selected_mip_axis)
menu.invoke(current_axis)
# Set action only after calling invoke to avoid redundant call at window creation
TkAction = lambda *args, **kwargs: Action(selected_mip_axis, *args, **kwargs)
selected_mip_axis.trace_add("write", TkAction)
output["menu"] = menu