spacr 0.2.2__py3-none-any.whl → 0.2.21__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. spacr/gui.py +2 -1
  2. spacr/gui_elements.py +1 -5
  3. spacr/resources/icons/abort.png +0 -0
  4. spacr/resources/icons/classify.png +0 -0
  5. spacr/resources/icons/make_masks.png +0 -0
  6. spacr/resources/icons/mask.png +0 -0
  7. spacr/resources/icons/measure.png +0 -0
  8. spacr/resources/icons/recruitment.png +0 -0
  9. spacr/resources/icons/regression.png +0 -0
  10. spacr/resources/icons/run.png +0 -0
  11. spacr/resources/icons/umap.png +0 -0
  12. {spacr-0.2.2.dist-info → spacr-0.2.21.dist-info}/METADATA +1 -1
  13. spacr-0.2.21.dist-info/RECORD +56 -0
  14. spacr/alpha.py +0 -807
  15. spacr/annotate_app.py +0 -670
  16. spacr/annotate_app_v2.py +0 -670
  17. spacr/app_make_masks_v2.py +0 -686
  18. spacr/classify_app.py +0 -201
  19. spacr/cli.py +0 -41
  20. spacr/foldseek.py +0 -779
  21. spacr/get_alfafold_structures.py +0 -72
  22. spacr/gui_2.py +0 -157
  23. spacr/gui_annotate.py +0 -145
  24. spacr/gui_classify_app.py +0 -201
  25. spacr/gui_make_masks_app.py +0 -927
  26. spacr/gui_make_masks_app_v2.py +0 -688
  27. spacr/gui_mask_app.py +0 -249
  28. spacr/gui_measure_app.py +0 -246
  29. spacr/gui_run.py +0 -58
  30. spacr/gui_sim_app.py +0 -0
  31. spacr/gui_wrappers.py +0 -149
  32. spacr/icons/abort.png +0 -0
  33. spacr/icons/abort.svg +0 -1
  34. spacr/icons/download.png +0 -0
  35. spacr/icons/download.svg +0 -1
  36. spacr/icons/download_for_offline_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.png +0 -0
  37. spacr/icons/download_for_offline_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.svg +0 -1
  38. spacr/icons/logo_spacr.png +0 -0
  39. spacr/icons/make_masks.png +0 -0
  40. spacr/icons/make_masks.svg +0 -1
  41. spacr/icons/map_barcodes.png +0 -0
  42. spacr/icons/map_barcodes.svg +0 -1
  43. spacr/icons/mask.png +0 -0
  44. spacr/icons/mask.svg +0 -1
  45. spacr/icons/measure.png +0 -0
  46. spacr/icons/measure.svg +0 -1
  47. spacr/icons/play_circle_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.png +0 -0
  48. spacr/icons/play_circle_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.svg +0 -1
  49. spacr/icons/run.png +0 -0
  50. spacr/icons/run.svg +0 -1
  51. spacr/icons/sequencing.png +0 -0
  52. spacr/icons/sequencing.svg +0 -1
  53. spacr/icons/settings.png +0 -0
  54. spacr/icons/settings.svg +0 -1
  55. spacr/icons/settings_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.png +0 -0
  56. spacr/icons/settings_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.svg +0 -1
  57. spacr/icons/stop_circle_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.png +0 -0
  58. spacr/icons/stop_circle_100dp_E8EAED_FILL0_wght100_GRAD-25_opsz48.svg +0 -1
  59. spacr/icons/theater_comedy_100dp_E8EAED_FILL0_wght100_GRAD200_opsz48.png +0 -0
  60. spacr/icons/theater_comedy_100dp_E8EAED_FILL0_wght100_GRAD200_opsz48.svg +0 -1
  61. spacr/make_masks_app.py +0 -929
  62. spacr/make_masks_app_v2.py +0 -688
  63. spacr/mask_app.py +0 -249
  64. spacr/measure_app.py +0 -246
  65. spacr/models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model +0 -0
  66. spacr/models/cp/toxo_plaque_cyto_e25000_X1120_Y1120.CP_model_settings.csv +0 -23
  67. spacr/models/cp/toxo_pv_lumen.CP_model +0 -0
  68. spacr/old_code.py +0 -358
  69. spacr/resources/icons/abort.svg +0 -1
  70. spacr/resources/icons/annotate.svg +0 -1
  71. spacr/resources/icons/classify.svg +0 -1
  72. spacr/resources/icons/download.svg +0 -1
  73. spacr/resources/icons/icon.psd +0 -0
  74. spacr/resources/icons/make_masks.svg +0 -1
  75. spacr/resources/icons/map_barcodes.svg +0 -1
  76. spacr/resources/icons/mask.svg +0 -1
  77. spacr/resources/icons/measure.svg +0 -1
  78. spacr/resources/icons/run.svg +0 -1
  79. spacr/resources/icons/run_2.png +0 -0
  80. spacr/resources/icons/run_2.svg +0 -1
  81. spacr/resources/icons/sequencing.svg +0 -1
  82. spacr/resources/icons/settings.svg +0 -1
  83. spacr/resources/icons/train_cellpose.svg +0 -1
  84. spacr/test_gui.py +0 -0
  85. spacr-0.2.2.dist-info/RECORD +0 -126
  86. /spacr/resources/icons/{cellpose.png → cellpose_all.png} +0 -0
  87. {spacr-0.2.2.dist-info → spacr-0.2.21.dist-info}/LICENSE +0 -0
  88. {spacr-0.2.2.dist-info → spacr-0.2.21.dist-info}/WHEEL +0 -0
  89. {spacr-0.2.2.dist-info → spacr-0.2.21.dist-info}/entry_points.txt +0 -0
  90. {spacr-0.2.2.dist-info → spacr-0.2.21.dist-info}/top_level.txt +0 -0
spacr/make_masks_app.py DELETED
@@ -1,929 +0,0 @@
1
- import os
2
- import numpy as np
3
- import tkinter as tk
4
- import imageio.v2 as imageio
5
- from collections import deque
6
- from PIL import Image, ImageTk
7
- from skimage.draw import polygon, line
8
- from skimage.transform import resize
9
- from scipy.ndimage import binary_fill_holes, label
10
- import tkinter as tk
11
- from tkinter import ttk
12
- from ttkthemes import ThemedTk
13
-
14
- from .logger import log_function_call
15
-
16
- from .gui_utils import ScrollableFrame, CustomButton, set_dark_style, set_default_font, create_dark_mode, style_text_boxes, create_menu_bar
17
-
18
- class modify_masks:
19
-
20
- def __init__(self, root, folder_path, scale_factor):
21
- self.root = root
22
- self.folder_path = folder_path
23
- self.scale_factor = scale_factor
24
- self.image_filenames = sorted([f for f in os.listdir(folder_path) if f.endswith(('.png', '.jpg', '.jpeg', '.tif', '.tiff'))])
25
- self.masks_folder = os.path.join(folder_path, 'masks')
26
- self.current_image_index = 0
27
- self.initialize_flags()
28
- self.canvas_width = self.root.winfo_screenheight() -100
29
- self.canvas_height = self.root.winfo_screenheight() -100
30
- self.root.configure(bg='black')
31
- self.setup_navigation_toolbar()
32
- self.setup_mode_toolbar()
33
- self.setup_function_toolbar()
34
- self.setup_zoom_toolbar()
35
- self.setup_canvas()
36
- self.load_first_image()
37
-
38
- ####################################################################################################
39
- # Helper functions#
40
- ####################################################################################################
41
-
42
- def update_display(self):
43
- if self.zoom_active:
44
- self.display_zoomed_image()
45
- else:
46
- self.display_image()
47
-
48
- def update_original_mask_from_zoom(self):
49
- y0, y1, x0, x1 = self.zoom_y0, self.zoom_y1, self.zoom_x0, self.zoom_x1
50
- zoomed_mask_resized = resize(self.zoom_mask, (y1 - y0, x1 - x0), order=0, preserve_range=True).astype(np.uint8)
51
- self.mask[y0:y1, x0:x1] = zoomed_mask_resized
52
-
53
- def update_original_mask(self, zoomed_mask, x0, x1, y0, y1):
54
- actual_mask_region = self.mask[y0:y1, x0:x1]
55
- target_shape = actual_mask_region.shape
56
- resized_mask = resize(zoomed_mask, target_shape, order=0, preserve_range=True).astype(np.uint8)
57
- if resized_mask.shape != actual_mask_region.shape:
58
- raise ValueError(f"Shape mismatch: resized_mask {resized_mask.shape}, actual_mask_region {actual_mask_region.shape}")
59
- self.mask[y0:y1, x0:x1] = np.maximum(actual_mask_region, resized_mask)
60
- self.mask = self.mask.copy()
61
- self.mask[y0:y1, x0:x1] = np.maximum(self.mask[y0:y1, x0:x1], resized_mask)
62
- self.mask = self.mask.copy()
63
-
64
- def get_scaling_factors(self, img_width, img_height, canvas_width, canvas_height):
65
- x_scale = img_width / canvas_width
66
- y_scale = img_height / canvas_height
67
- return x_scale, y_scale
68
-
69
- def canvas_to_image(self, x_canvas, y_canvas):
70
- x_scale, y_scale = self.get_scaling_factors(
71
- self.image.shape[1], self.image.shape[0],
72
- self.canvas_width, self.canvas_height
73
- )
74
- x_image = int(x_canvas * x_scale)
75
- y_image = int(y_canvas * y_scale)
76
- return x_image, y_image
77
-
78
- def apply_zoom_on_enter(self, event):
79
- if self.zoom_active and self.zoom_rectangle_start is not None:
80
- self.set_zoom_rectangle_end(event)
81
-
82
- def normalize_image(self, image, lower_quantile, upper_quantile):
83
- lower_bound = np.percentile(image, lower_quantile)
84
- upper_bound = np.percentile(image, upper_quantile)
85
- normalized = np.clip(image, lower_bound, upper_bound)
86
- normalized = (normalized - lower_bound) / (upper_bound - lower_bound)
87
- max_value = np.iinfo(image.dtype).max
88
- normalized = (normalized * max_value).astype(image.dtype)
89
- return normalized
90
-
91
- def resize_arrays(self, img, mask):
92
- original_dtype = img.dtype
93
- scaled_height = int(img.shape[0] * self.scale_factor)
94
- scaled_width = int(img.shape[1] * self.scale_factor)
95
- scaled_img = resize(img, (scaled_height, scaled_width), anti_aliasing=True, preserve_range=True)
96
- scaled_mask = resize(mask, (scaled_height, scaled_width), order=0, anti_aliasing=False, preserve_range=True)
97
- stretched_img = resize(scaled_img, (self.canvas_height, self.canvas_width), anti_aliasing=True, preserve_range=True)
98
- stretched_mask = resize(scaled_mask, (self.canvas_height, self.canvas_width), order=0, anti_aliasing=False, preserve_range=True)
99
- return stretched_img.astype(original_dtype), stretched_mask.astype(original_dtype)
100
-
101
- ####################################################################################################
102
- #Initiate canvas elements#
103
- ####################################################################################################
104
-
105
- def load_first_image(self):
106
- self.image, self.mask = self.load_image_and_mask(self.current_image_index)
107
- self.original_size = self.image.shape
108
- self.image, self.mask = self.resize_arrays(self.image, self.mask)
109
- self.display_image()
110
-
111
- def setup_canvas(self):
112
- self.canvas = tk.Canvas(self.root, width=self.canvas_width, height=self.canvas_height, bg='black')
113
- self.canvas.pack()
114
- self.canvas.bind("<Motion>", self.update_mouse_info)
115
-
116
- def initialize_flags(self):
117
- self.zoom_rectangle_start = None
118
- self.zoom_rectangle_end = None
119
- self.zoom_rectangle_id = None
120
- self.zoom_x0 = None
121
- self.zoom_y0 = None
122
- self.zoom_x1 = None
123
- self.zoom_y1 = None
124
- self.zoom_mask = None
125
- self.zoom_image = None
126
- self.zoom_image_orig = None
127
- self.zoom_scale = 1
128
- self.drawing = False
129
- self.zoom_active = False
130
- self.magic_wand_active = False
131
- self.brush_active = False
132
- self.dividing_line_active = False
133
- self.dividing_line_coords = []
134
- self.current_dividing_line = None
135
- self.lower_quantile = tk.StringVar(value="1.0")
136
- self.upper_quantile = tk.StringVar(value="99.9")
137
- self.magic_wand_tolerance = tk.StringVar(value="1000")
138
-
139
- def update_mouse_info(self, event):
140
- x, y = event.x, event.y
141
- intensity = "N/A"
142
- mask_value = "N/A"
143
- pixel_count = "N/A"
144
- if self.zoom_active:
145
- if 0 <= x < self.canvas_width and 0 <= y < self.canvas_height:
146
- intensity = self.zoom_image_orig[y, x] if self.zoom_image_orig is not None else "N/A"
147
- mask_value = self.zoom_mask[y, x] if self.zoom_mask is not None else "N/A"
148
- else:
149
- if 0 <= x < self.image.shape[1] and 0 <= y < self.image.shape[0]:
150
- intensity = self.image[y, x]
151
- mask_value = self.mask[y, x]
152
- if mask_value != "N/A" and mask_value != 0:
153
- pixel_count = np.sum(self.mask == mask_value)
154
- self.intensity_label.config(text=f"Intensity: {intensity}")
155
- self.mask_value_label.config(text=f"Mask: {mask_value}, Area: {pixel_count}")
156
- self.mask_value_label.config(text=f"Mask: {mask_value}")
157
- if mask_value != "N/A" and mask_value != 0:
158
- self.pixel_count_label.config(text=f"Area: {pixel_count}")
159
- else:
160
- self.pixel_count_label.config(text="Area: N/A")
161
-
162
- def setup_navigation_toolbar(self):
163
- navigation_toolbar = tk.Frame(self.root, bg='black')
164
- navigation_toolbar.pack(side='top', fill='x')
165
- prev_btn = tk.Button(navigation_toolbar, text="Previous", command=self.previous_image, bg='black', fg='white')
166
- prev_btn.pack(side='left')
167
- next_btn = tk.Button(navigation_toolbar, text="Next", command=self.next_image, bg='black', fg='white')
168
- next_btn.pack(side='left')
169
- save_btn = tk.Button(navigation_toolbar, text="Save", command=self.save_mask, bg='black', fg='white')
170
- save_btn.pack(side='left')
171
- self.intensity_label = tk.Label(navigation_toolbar, text="Image: N/A", bg='black', fg='white')
172
- self.intensity_label.pack(side='right')
173
- self.mask_value_label = tk.Label(navigation_toolbar, text="Mask: N/A", bg='black', fg='white')
174
- self.mask_value_label.pack(side='right')
175
- self.pixel_count_label = tk.Label(navigation_toolbar, text="Area: N/A", bg='black', fg='white')
176
- self.pixel_count_label.pack(side='right')
177
-
178
- def setup_mode_toolbar(self):
179
- self.mode_toolbar = tk.Frame(self.root, bg='black')
180
- self.mode_toolbar.pack(side='top', fill='x')
181
- self.draw_btn = tk.Button(self.mode_toolbar, text="Draw", command=self.toggle_draw_mode, bg='black', fg='white')
182
- self.draw_btn.pack(side='left')
183
- self.magic_wand_btn = tk.Button(self.mode_toolbar, text="Magic Wand", command=self.toggle_magic_wand_mode, bg='black', fg='white')
184
- self.magic_wand_btn.pack(side='left')
185
- tk.Label(self.mode_toolbar, text="Tolerance:", bg='black', fg='white').pack(side='left')
186
- self.tolerance_entry = tk.Entry(self.mode_toolbar, textvariable=self.magic_wand_tolerance, bg='black', fg='white')
187
- self.tolerance_entry.pack(side='left')
188
- tk.Label(self.mode_toolbar, text="Max Pixels:", bg='black', fg='white').pack(side='left')
189
- self.max_pixels_entry = tk.Entry(self.mode_toolbar, bg='black', fg='white')
190
- self.max_pixels_entry.insert(0, "1000")
191
- self.max_pixels_entry.pack(side='left')
192
- self.erase_btn = tk.Button(self.mode_toolbar, text="Erase", command=self.toggle_erase_mode, bg='black', fg='white')
193
- self.erase_btn.pack(side='left')
194
- self.brush_btn = tk.Button(self.mode_toolbar, text="Brush", command=self.toggle_brush_mode, bg='black', fg='white')
195
- self.brush_btn.pack(side='left')
196
- self.brush_size_entry = tk.Entry(self.mode_toolbar, bg='black', fg='white')
197
- self.brush_size_entry.insert(0, "10")
198
- self.brush_size_entry.pack(side='left')
199
- tk.Label(self.mode_toolbar, text="Brush Size:", bg='black', fg='white').pack(side='left')
200
- self.dividing_line_btn = tk.Button(self.mode_toolbar, text="Dividing Line", command=self.toggle_dividing_line_mode, bg='black', fg='white')
201
- self.dividing_line_btn.pack(side='left')
202
-
203
- def setup_function_toolbar(self):
204
- self.function_toolbar = tk.Frame(self.root, bg='black')
205
- self.function_toolbar.pack(side='top', fill='x')
206
- self.fill_btn = tk.Button(self.function_toolbar, text="Fill", command=self.fill_objects, bg='black', fg='white')
207
- self.fill_btn.pack(side='left')
208
- self.relabel_btn = tk.Button(self.function_toolbar, text="Relabel", command=self.relabel_objects, bg='black', fg='white')
209
- self.relabel_btn.pack(side='left')
210
- self.clear_btn = tk.Button(self.function_toolbar, text="Clear", command=self.clear_objects, bg='black', fg='white')
211
- self.clear_btn.pack(side='left')
212
- self.invert_btn = tk.Button(self.function_toolbar, text="Invert", command=self.invert_mask, bg='black', fg='white')
213
- self.invert_btn.pack(side='left')
214
- remove_small_btn = tk.Button(self.function_toolbar, text="Remove Small", command=self.remove_small_objects, bg='black', fg='white')
215
- remove_small_btn.pack(side='left')
216
- tk.Label(self.function_toolbar, text="Min Area:", bg='black', fg='white').pack(side='left')
217
- self.min_area_entry = tk.Entry(self.function_toolbar, bg='black', fg='white')
218
- self.min_area_entry.insert(0, "100") # Default minimum area
219
- self.min_area_entry.pack(side='left')
220
-
221
- def setup_zoom_toolbar(self):
222
- self.zoom_toolbar = tk.Frame(self.root, bg='black')
223
- self.zoom_toolbar.pack(side='top', fill='x')
224
- self.zoom_btn = tk.Button(self.zoom_toolbar, text="Zoom", command=self.toggle_zoom_mode, bg='black', fg='white')
225
- self.zoom_btn.pack(side='left')
226
- self.normalize_btn = tk.Button(self.zoom_toolbar, text="Apply Normalization", command=self.apply_normalization, bg='black', fg='white')
227
- self.normalize_btn.pack(side='left')
228
- tk.Label(self.zoom_toolbar, text="Lower Percentile:", bg='black', fg='white').pack(side='left')
229
- self.lower_entry = tk.Entry(self.zoom_toolbar, textvariable=self.lower_quantile, bg='black', fg='white')
230
- self.lower_entry.pack(side='left')
231
-
232
- tk.Label(self.zoom_toolbar, text="Upper Percentile:", bg='black', fg='white').pack(side='left')
233
- self.upper_entry = tk.Entry(self.zoom_toolbar, textvariable=self.upper_quantile, bg='black', fg='white')
234
- self.upper_entry.pack(side='left')
235
-
236
-
237
- def load_image_and_mask(self, index):
238
- image_path = os.path.join(self.folder_path, self.image_filenames[index])
239
- image = imageio.imread(image_path)
240
- mask_path = os.path.join(self.masks_folder, self.image_filenames[index])
241
- if os.path.exists(mask_path):
242
- print(f'loading mask:{mask_path} for image: {image_path}')
243
- mask = imageio.imread(mask_path)
244
- if mask.dtype != np.uint8:
245
- mask = (mask / np.max(mask) * 255).astype(np.uint8)
246
- else:
247
- mask = np.zeros(image.shape[:2], dtype=np.uint8)
248
- print(f'loaded new mask for image: {image_path}')
249
- return image, mask
250
-
251
- ####################################################################################################
252
- # Image Display functions#
253
- ####################################################################################################
254
- def display_image(self):
255
- if self.zoom_rectangle_id is not None:
256
- self.canvas.delete(self.zoom_rectangle_id)
257
- self.zoom_rectangle_id = None
258
- lower_quantile = float(self.lower_quantile.get()) if self.lower_quantile.get() else 1.0
259
- upper_quantile = float(self.upper_quantile.get()) if self.upper_quantile.get() else 99.9
260
- normalized = self.normalize_image(self.image, lower_quantile, upper_quantile)
261
- combined = self.overlay_mask_on_image(normalized, self.mask)
262
- self.tk_image = ImageTk.PhotoImage(image=Image.fromarray(combined))
263
- self.canvas.create_image(0, 0, anchor='nw', image=self.tk_image)
264
-
265
- def display_zoomed_image(self):
266
- if self.zoom_rectangle_start and self.zoom_rectangle_end:
267
- # Convert canvas coordinates to image coordinates
268
- x0, y0 = self.canvas_to_image(*self.zoom_rectangle_start)
269
- x1, y1 = self.canvas_to_image(*self.zoom_rectangle_end)
270
- x0, x1 = min(x0, x1), max(x0, x1)
271
- y0, y1 = min(y0, y1), max(y0, y1)
272
- self.zoom_x0 = x0
273
- self.zoom_y0 = y0
274
- self.zoom_x1 = x1
275
- self.zoom_y1 = y1
276
- # Normalize the entire image
277
- lower_quantile = float(self.lower_quantile.get()) if self.lower_quantile.get() else 1.0
278
- upper_quantile = float(self.upper_quantile.get()) if self.upper_quantile.get() else 99.9
279
- normalized_image = self.normalize_image(self.image, lower_quantile, upper_quantile)
280
- # Extract the zoomed portion of the normalized image and mask
281
- self.zoom_image = normalized_image[y0:y1, x0:x1]
282
- self.zoom_image_orig = self.image[y0:y1, x0:x1]
283
- self.zoom_mask = self.mask[y0:y1, x0:x1]
284
- original_mask_area = self.mask.shape[0] * self.mask.shape[1]
285
- zoom_mask_area = self.zoom_mask.shape[0] * self.zoom_mask.shape[1]
286
- if original_mask_area > 0:
287
- self.zoom_scale = original_mask_area/zoom_mask_area
288
- # Resize the zoomed image and mask to fit the canvas
289
- canvas_height = self.canvas.winfo_height()
290
- canvas_width = self.canvas.winfo_width()
291
-
292
- if self.zoom_image.size > 0 and canvas_height > 0 and canvas_width > 0:
293
- self.zoom_image = resize(self.zoom_image, (canvas_height, canvas_width), preserve_range=True).astype(self.zoom_image.dtype)
294
- self.zoom_image_orig = resize(self.zoom_image_orig, (canvas_height, canvas_width), preserve_range=True).astype(self.zoom_image_orig.dtype)
295
- #self.zoom_mask = resize(self.zoom_mask, (canvas_height, canvas_width), preserve_range=True).astype(np.uint8)
296
- self.zoom_mask = resize(self.zoom_mask, (canvas_height, canvas_width), order=0, preserve_range=True).astype(np.uint8)
297
- combined = self.overlay_mask_on_image(self.zoom_image, self.zoom_mask)
298
- self.tk_image = ImageTk.PhotoImage(image=Image.fromarray(combined))
299
- self.canvas.create_image(0, 0, anchor='nw', image=self.tk_image)
300
-
301
- def overlay_mask_on_image(self, image, mask, alpha=0.5):
302
- if len(image.shape) == 2:
303
- image = np.stack((image,) * 3, axis=-1)
304
- mask = mask.astype(np.int32)
305
- max_label = np.max(mask)
306
- np.random.seed(0)
307
- colors = np.random.randint(0, 255, size=(max_label + 1, 3), dtype=np.uint8)
308
- colors[0] = [0, 0, 0] # background color
309
- colored_mask = colors[mask]
310
- image_8bit = (image / 256).astype(np.uint8)
311
- # Blend the mask and the image with transparency
312
- combined_image = np.where(mask[..., None] > 0,
313
- np.clip(image_8bit * (1 - alpha) + colored_mask * alpha, 0, 255),
314
- image_8bit)
315
- # Convert the final image back to uint8
316
- combined_image = combined_image.astype(np.uint8)
317
- return combined_image
318
-
319
- ####################################################################################################
320
- # Navigation functions#
321
- ####################################################################################################
322
-
323
- def previous_image(self):
324
- if self.current_image_index > 0:
325
- self.current_image_index -= 1
326
- self.initialize_flags()
327
- self.image, self.mask = self.load_image_and_mask(self.current_image_index)
328
- self.original_size = self.image.shape
329
- self.image, self.mask = self.resize_arrays(self.image, self.mask)
330
- self.display_image()
331
-
332
- def next_image(self):
333
- if self.current_image_index < len(self.image_filenames) - 1:
334
- self.current_image_index += 1
335
- self.initialize_flags()
336
- self.image, self.mask = self.load_image_and_mask(self.current_image_index)
337
- self.original_size = self.image.shape
338
- self.image, self.mask = self.resize_arrays(self.image, self.mask)
339
- self.display_image()
340
-
341
- def save_mask(self):
342
- if self.current_image_index < len(self.image_filenames):
343
- original_size = self.original_size
344
- if self.mask.shape != original_size:
345
- resized_mask = resize(self.mask, original_size, order=0, preserve_range=True).astype(np.uint16)
346
- else:
347
- resized_mask = self.mask
348
- resized_mask, _ = label(resized_mask > 0)
349
- save_folder = os.path.join(self.folder_path, 'masks')
350
- if not os.path.exists(save_folder):
351
- os.makedirs(save_folder)
352
- image_filename = os.path.splitext(self.image_filenames[self.current_image_index])[0] + '.tif'
353
- save_path = os.path.join(save_folder, image_filename)
354
-
355
- print(f"Saving mask to: {save_path}") # Debug print
356
- imageio.imwrite(save_path, resized_mask)
357
-
358
- ####################################################################################################
359
- # Zoom Functions #
360
- ####################################################################################################
361
- def set_zoom_rectangle_start(self, event):
362
- if self.zoom_active:
363
- self.zoom_rectangle_start = (event.x, event.y)
364
-
365
- def set_zoom_rectangle_end(self, event):
366
- if self.zoom_active:
367
- self.zoom_rectangle_end = (event.x, event.y)
368
- if self.zoom_rectangle_id is not None:
369
- self.canvas.delete(self.zoom_rectangle_id)
370
- self.zoom_rectangle_id = None
371
- self.display_zoomed_image()
372
- self.canvas.unbind("<Motion>")
373
- self.canvas.unbind("<Button-1>")
374
- self.canvas.unbind("<Button-3>")
375
- self.canvas.bind("<Motion>", self.update_mouse_info)
376
-
377
- def update_zoom_box(self, event):
378
- if self.zoom_active and self.zoom_rectangle_start is not None:
379
- if self.zoom_rectangle_id is not None:
380
- self.canvas.delete(self.zoom_rectangle_id)
381
- # Assuming event.x and event.y are already in image coordinates
382
- self.zoom_rectangle_end = (event.x, event.y)
383
- x0, y0 = self.zoom_rectangle_start
384
- x1, y1 = self.zoom_rectangle_end
385
- self.zoom_rectangle_id = self.canvas.create_rectangle(x0, y0, x1, y1, outline="red", width=2)
386
-
387
- ####################################################################################################
388
- # Mode activation#
389
- ####################################################################################################
390
-
391
- def toggle_zoom_mode(self):
392
- if not self.zoom_active:
393
- self.brush_btn.config(text="Brush")
394
- self.canvas.unbind("<B1-Motion>")
395
- self.canvas.unbind("<B3-Motion>")
396
- self.canvas.unbind("<ButtonRelease-1>")
397
- self.canvas.unbind("<ButtonRelease-3>")
398
- self.zoom_active = True
399
- self.drawing = False
400
- self.magic_wand_active = False
401
- self.erase_active = False
402
- self.brush_active = False
403
- self.dividing_line_active = False
404
- self.draw_btn.config(text="Draw")
405
- self.erase_btn.config(text="Erase")
406
- self.magic_wand_btn.config(text="Magic Wand")
407
- self.zoom_btn.config(text="Zoom ON")
408
- self.dividing_line_btn.config(text="Dividing Line")
409
- self.canvas.unbind("<Button-1>")
410
- self.canvas.unbind("<Button-3>")
411
- self.canvas.unbind("<Motion>")
412
- self.canvas.bind("<Button-1>", self.set_zoom_rectangle_start)
413
- self.canvas.bind("<Button-3>", self.set_zoom_rectangle_end)
414
- self.canvas.bind("<Motion>", self.update_zoom_box)
415
- else:
416
- self.zoom_active = False
417
- self.zoom_btn.config(text="Zoom")
418
- self.canvas.unbind("<Button-1>")
419
- self.canvas.unbind("<Button-3>")
420
- self.canvas.unbind("<Motion>")
421
- self.zoom_rectangle_start = self.zoom_rectangle_end = None
422
- self.zoom_rectangle_id = None
423
- self.display_image()
424
- self.canvas.bind("<Motion>", self.update_mouse_info)
425
- self.zoom_rectangle_start = None
426
- self.zoom_rectangle_end = None
427
- self.zoom_rectangle_id = None
428
- self.zoom_x0 = None
429
- self.zoom_y0 = None
430
- self.zoom_x1 = None
431
- self.zoom_y1 = None
432
- self.zoom_mask = None
433
- self.zoom_image = None
434
- self.zoom_image_orig = None
435
-
436
- def toggle_brush_mode(self):
437
- self.brush_active = not self.brush_active
438
- if self.brush_active:
439
- self.drawing = False
440
- self.magic_wand_active = False
441
- self.erase_active = False
442
- self.brush_btn.config(text="Brush ON")
443
- self.draw_btn.config(text="Draw")
444
- self.erase_btn.config(text="Erase")
445
- self.magic_wand_btn.config(text="Magic Wand")
446
- self.canvas.unbind("<Button-1>")
447
- self.canvas.unbind("<Button-3>")
448
- self.canvas.unbind("<Motion>")
449
- self.canvas.bind("<B1-Motion>", self.apply_brush) # Left click and drag to apply brush
450
- self.canvas.bind("<B3-Motion>", self.erase_brush) # Right click and drag to erase with brush
451
- self.canvas.bind("<ButtonRelease-1>", self.apply_brush_release) # Left button release
452
- self.canvas.bind("<ButtonRelease-3>", self.erase_brush_release) # Right button release
453
- else:
454
- self.brush_active = False
455
- self.brush_btn.config(text="Brush")
456
- self.canvas.unbind("<B1-Motion>")
457
- self.canvas.unbind("<B3-Motion>")
458
- self.canvas.unbind("<ButtonRelease-1>")
459
- self.canvas.unbind("<ButtonRelease-3>")
460
-
461
- def image_to_canvas(self, x_image, y_image):
462
- x_scale, y_scale = self.get_scaling_factors(
463
- self.image.shape[1], self.image.shape[0],
464
- self.canvas_width, self.canvas_height
465
- )
466
- x_canvas = int(x_image / x_scale)
467
- y_canvas = int(y_image / y_scale)
468
- return x_canvas, y_canvas
469
-
470
- def toggle_dividing_line_mode(self):
471
- self.dividing_line_active = not self.dividing_line_active
472
- if self.dividing_line_active:
473
- self.drawing = False
474
- self.magic_wand_active = False
475
- self.erase_active = False
476
- self.brush_active = False
477
- self.draw_btn.config(text="Draw")
478
- self.erase_btn.config(text="Erase")
479
- self.magic_wand_btn.config(text="Magic Wand")
480
- self.brush_btn.config(text="Brush")
481
- self.dividing_line_btn.config(text="Dividing Line ON")
482
- self.canvas.unbind("<Button-1>")
483
- self.canvas.unbind("<ButtonRelease-1>")
484
- self.canvas.unbind("<Motion>")
485
- self.canvas.bind("<Button-1>", self.start_dividing_line)
486
- self.canvas.bind("<ButtonRelease-1>", self.finish_dividing_line)
487
- self.canvas.bind("<Motion>", self.update_dividing_line_preview)
488
- else:
489
- print("Dividing Line Mode: OFF")
490
- self.dividing_line_active = False
491
- self.dividing_line_btn.config(text="Dividing Line")
492
- self.canvas.unbind("<Button-1>")
493
- self.canvas.unbind("<ButtonRelease-1>")
494
- self.canvas.unbind("<Motion>")
495
- self.display_image()
496
-
497
- def start_dividing_line(self, event):
498
- if self.dividing_line_active:
499
- self.dividing_line_coords = [(event.x, event.y)]
500
- self.current_dividing_line = self.canvas.create_line(event.x, event.y, event.x, event.y, fill="red", width=2)
501
-
502
- def finish_dividing_line(self, event):
503
- if self.dividing_line_active:
504
- self.dividing_line_coords.append((event.x, event.y))
505
- if self.zoom_active:
506
- self.dividing_line_coords = [self.canvas_to_image(x, y) for x, y in self.dividing_line_coords]
507
- self.apply_dividing_line()
508
- self.canvas.delete(self.current_dividing_line)
509
- self.current_dividing_line = None
510
-
511
- def update_dividing_line_preview(self, event):
512
- if self.dividing_line_active and self.dividing_line_coords:
513
- x, y = event.x, event.y
514
- if self.zoom_active:
515
- x, y = self.canvas_to_image(x, y)
516
- self.dividing_line_coords.append((x, y))
517
- canvas_coords = [(self.image_to_canvas(*pt) if self.zoom_active else pt) for pt in self.dividing_line_coords]
518
- flat_canvas_coords = [coord for pt in canvas_coords for coord in pt]
519
- self.canvas.coords(self.current_dividing_line, *flat_canvas_coords)
520
-
521
- def apply_dividing_line(self):
522
- if self.dividing_line_coords:
523
- coords = self.dividing_line_coords
524
- if self.zoom_active:
525
- coords = [self.canvas_to_image(x, y) for x, y in coords]
526
-
527
- rr, cc = [], []
528
- for (x0, y0), (x1, y1) in zip(coords[:-1], coords[1:]):
529
- line_rr, line_cc = line(y0, x0, y1, x1)
530
- rr.extend(line_rr)
531
- cc.extend(line_cc)
532
- rr, cc = np.array(rr), np.array(cc)
533
-
534
- mask_copy = self.mask.copy()
535
-
536
- if self.zoom_active:
537
- # Update the zoomed mask
538
- self.zoom_mask[rr, cc] = 0
539
- # Reflect changes to the original mask
540
- y0, y1, x0, x1 = self.zoom_y0, self.zoom_y1, self.zoom_x0, self.zoom_x1
541
- zoomed_mask_resized_back = resize(self.zoom_mask, (y1 - y0, x1 - x0), order=0, preserve_range=True).astype(np.uint8)
542
- self.mask[y0:y1, x0:x1] = zoomed_mask_resized_back
543
- else:
544
- # Directly update the original mask
545
- mask_copy[rr, cc] = 0
546
- self.mask = mask_copy
547
-
548
- labeled_mask, num_labels = label(self.mask > 0)
549
- self.mask = labeled_mask
550
- self.update_display()
551
-
552
- self.dividing_line_coords = []
553
- self.canvas.unbind("<Button-1>")
554
- self.canvas.unbind("<ButtonRelease-1>")
555
- self.canvas.unbind("<Motion>")
556
- self.dividing_line_active = False
557
- self.dividing_line_btn.config(text="Dividing Line")
558
-
559
- def toggle_draw_mode(self):
560
- self.drawing = not self.drawing
561
- if self.drawing:
562
- self.brush_btn.config(text="Brush")
563
- self.canvas.unbind("<B1-Motion>")
564
- self.canvas.unbind("<B3-Motion>")
565
- self.canvas.unbind("<ButtonRelease-1>")
566
- self.canvas.unbind("<ButtonRelease-3>")
567
- self.magic_wand_active = False
568
- self.erase_active = False
569
- self.brush_active = False
570
- self.draw_btn.config(text="Draw ON")
571
- self.magic_wand_btn.config(text="Magic Wand")
572
- self.erase_btn.config(text="Erase")
573
- self.draw_coordinates = []
574
- self.canvas.unbind("<Button-1>")
575
- self.canvas.unbind("<Motion>")
576
- self.canvas.bind("<B1-Motion>", self.draw)
577
- self.canvas.bind("<ButtonRelease-1>", self.finish_drawing)
578
- else:
579
- self.drawing = False
580
- self.draw_btn.config(text="Draw")
581
- self.canvas.unbind("<B1-Motion>")
582
- self.canvas.unbind("<ButtonRelease-1>")
583
-
584
- def toggle_magic_wand_mode(self):
585
- self.magic_wand_active = not self.magic_wand_active
586
- if self.magic_wand_active:
587
- self.brush_btn.config(text="Brush")
588
- self.canvas.unbind("<B1-Motion>")
589
- self.canvas.unbind("<B3-Motion>")
590
- self.canvas.unbind("<ButtonRelease-1>")
591
- self.canvas.unbind("<ButtonRelease-3>")
592
- self.drawing = False
593
- self.erase_active = False
594
- self.brush_active = False
595
- self.draw_btn.config(text="Draw")
596
- self.erase_btn.config(text="Erase")
597
- self.magic_wand_btn.config(text="Magic Wand ON")
598
- self.canvas.bind("<Button-1>", self.use_magic_wand)
599
- self.canvas.bind("<Button-3>", self.use_magic_wand)
600
- else:
601
- self.magic_wand_btn.config(text="Magic Wand")
602
- self.canvas.unbind("<Button-1>")
603
- self.canvas.unbind("<Button-3>")
604
-
605
- def toggle_erase_mode(self):
606
- self.erase_active = not self.erase_active
607
- if self.erase_active:
608
- self.brush_btn.config(text="Brush")
609
- self.canvas.unbind("<B1-Motion>")
610
- self.canvas.unbind("<B3-Motion>")
611
- self.canvas.unbind("<ButtonRelease-1>")
612
- self.canvas.unbind("<ButtonRelease-3>")
613
- self.erase_btn.config(text="Erase ON")
614
- self.canvas.bind("<Button-1>", self.erase_object)
615
- self.drawing = False
616
- self.magic_wand_active = False
617
- self.brush_active = False
618
- self.draw_btn.config(text="Draw")
619
- self.magic_wand_btn.config(text="Magic Wand")
620
- else:
621
- self.erase_active = False
622
- self.erase_btn.config(text="Erase")
623
- self.canvas.unbind("<Button-1>")
624
-
625
- ####################################################################################################
626
- # Mode functions#
627
- ####################################################################################################
628
-
629
- def apply_brush_release(self, event):
630
- if hasattr(self, 'brush_path'):
631
- for x, y, brush_size in self.brush_path:
632
- img_x, img_y = (x, y) if self.zoom_active else self.canvas_to_image(x, y)
633
- x0 = max(img_x - brush_size // 2, 0)
634
- y0 = max(img_y - brush_size // 2, 0)
635
- x1 = min(img_x + brush_size // 2, self.zoom_mask.shape[1] if self.zoom_active else self.mask.shape[1])
636
- y1 = min(img_y + brush_size // 2, self.zoom_mask.shape[0] if self.zoom_active else self.mask.shape[0])
637
- if self.zoom_active:
638
- self.zoom_mask[y0:y1, x0:x1] = 255
639
- self.update_original_mask_from_zoom()
640
- else:
641
- self.mask[y0:y1, x0:x1] = 255
642
- del self.brush_path
643
- self.canvas.delete("temp_line")
644
- self.update_display()
645
-
646
- def erase_brush_release(self, event):
647
- if hasattr(self, 'erase_path'):
648
- for x, y, brush_size in self.erase_path:
649
- img_x, img_y = (x, y) if self.zoom_active else self.canvas_to_image(x, y)
650
- x0 = max(img_x - brush_size // 2, 0)
651
- y0 = max(img_y - brush_size // 2, 0)
652
- x1 = min(img_x + brush_size // 2, self.zoom_mask.shape[1] if self.zoom_active else self.mask.shape[1])
653
- y1 = min(img_y + brush_size // 2, self.zoom_mask.shape[0] if self.zoom_active else self.mask.shape[0])
654
- if self.zoom_active:
655
- self.zoom_mask[y0:y1, x0:x1] = 0
656
- self.update_original_mask_from_zoom()
657
- else:
658
- self.mask[y0:y1, x0:x1] = 0
659
- del self.erase_path
660
- self.canvas.delete("temp_line")
661
- self.update_display()
662
-
663
- def apply_brush(self, event):
664
- brush_size = int(self.brush_size_entry.get())
665
- x, y = event.x, event.y
666
- if not hasattr(self, 'brush_path'):
667
- self.brush_path = []
668
- self.last_brush_coord = (x, y)
669
- if self.last_brush_coord:
670
- last_x, last_y = self.last_brush_coord
671
- rr, cc = line(last_y, last_x, y, x)
672
- for ry, rx in zip(rr, cc):
673
- self.brush_path.append((rx, ry, brush_size))
674
-
675
- self.canvas.create_line(self.last_brush_coord[0], self.last_brush_coord[1], x, y, width=brush_size, fill="blue", tag="temp_line")
676
- self.last_brush_coord = (x, y)
677
-
678
- def erase_brush(self, event):
679
- brush_size = int(self.brush_size_entry.get())
680
- x, y = event.x, event.y
681
- if not hasattr(self, 'erase_path'):
682
- self.erase_path = []
683
- self.last_erase_coord = (x, y)
684
- if self.last_erase_coord:
685
- last_x, last_y = self.last_erase_coord
686
- rr, cc = line(last_y, last_x, y, x)
687
- for ry, rx in zip(rr, cc):
688
- self.erase_path.append((rx, ry, brush_size))
689
-
690
- self.canvas.create_line(self.last_erase_coord[0], self.last_erase_coord[1], x, y, width=brush_size, fill="white", tag="temp_line")
691
- self.last_erase_coord = (x, y)
692
-
693
- def erase_object(self, event):
694
- x, y = event.x, event.y
695
- if self.zoom_active:
696
- canvas_x, canvas_y = x, y
697
- zoomed_x = int(canvas_x * (self.zoom_image.shape[1] / self.canvas_width))
698
- zoomed_y = int(canvas_y * (self.zoom_image.shape[0] / self.canvas_height))
699
- orig_x = int(zoomed_x * ((self.zoom_x1 - self.zoom_x0) / self.canvas_width) + self.zoom_x0)
700
- orig_y = int(zoomed_y * ((self.zoom_y1 - self.zoom_y0) / self.canvas_height) + self.zoom_y0)
701
- if orig_x < 0 or orig_y < 0 or orig_x >= self.image.shape[1] or orig_y >= self.image.shape[0]:
702
- print("Point is out of bounds in the original image.")
703
- return
704
- else:
705
- orig_x, orig_y = x, y
706
- label_to_remove = self.mask[orig_y, orig_x]
707
- if label_to_remove > 0:
708
- self.mask[self.mask == label_to_remove] = 0
709
- self.update_display()
710
-
711
- def use_magic_wand(self, event):
712
- x, y = event.x, event.y
713
- tolerance = int(self.magic_wand_tolerance.get())
714
- maximum = int(self.max_pixels_entry.get())
715
- action = 'add' if event.num == 1 else 'erase'
716
- if self.zoom_active:
717
- self.magic_wand_zoomed((x, y), tolerance, action)
718
- else:
719
- self.magic_wand_normal((x, y), tolerance, action)
720
-
721
- def apply_magic_wand(self, image, mask, seed_point, tolerance, maximum, action='add'):
722
- x, y = seed_point
723
- initial_value = image[y, x].astype(np.float32)
724
- visited = np.zeros_like(image, dtype=bool)
725
- queue = deque([(x, y)])
726
- added_pixels = 0
727
-
728
- while queue and added_pixels < maximum:
729
- cx, cy = queue.popleft()
730
- if visited[cy, cx]:
731
- continue
732
- visited[cy, cx] = True
733
- current_value = image[cy, cx].astype(np.float32)
734
-
735
- if np.linalg.norm(abs(current_value - initial_value)) <= tolerance:
736
- if mask[cy, cx] == 0:
737
- added_pixels += 1
738
- mask[cy, cx] = 255 if action == 'add' else 0
739
-
740
- if added_pixels >= maximum:
741
- break
742
-
743
- for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
744
- nx, ny = cx + dx, cy + dy
745
- if 0 <= nx < image.shape[1] and 0 <= ny < image.shape[0] and not visited[ny, nx]:
746
- queue.append((nx, ny))
747
- return mask
748
-
749
- def magic_wand_normal(self, seed_point, tolerance, action):
750
- try:
751
- maximum = int(self.max_pixels_entry.get())
752
- except ValueError:
753
- print("Invalid maximum value; using default of 1000")
754
- maximum = 1000
755
- self.mask = self.apply_magic_wand(self.image, self.mask, seed_point, tolerance, maximum, action)
756
- self.display_image()
757
-
758
- def magic_wand_zoomed(self, seed_point, tolerance, action):
759
- if self.zoom_image_orig is None or self.zoom_mask is None:
760
- print("Zoomed image or mask not initialized")
761
- return
762
- try:
763
- maximum = int(self.max_pixels_entry.get())
764
- maximum = maximum * self.zoom_scale
765
- except ValueError:
766
- print("Invalid maximum value; using default of 1000")
767
- maximum = 1000
768
-
769
- canvas_x, canvas_y = seed_point
770
- if canvas_x < 0 or canvas_y < 0 or canvas_x >= self.zoom_image_orig.shape[1] or canvas_y >= self.zoom_image_orig.shape[0]:
771
- print("Selected point is out of bounds in the zoomed image.")
772
- return
773
-
774
- self.zoom_mask = self.apply_magic_wand(self.zoom_image_orig, self.zoom_mask, (canvas_x, canvas_y), tolerance, maximum, action)
775
- y0, y1, x0, x1 = self.zoom_y0, self.zoom_y1, self.zoom_x0, self.zoom_x1
776
- zoomed_mask_resized_back = resize(self.zoom_mask, (y1 - y0, x1 - x0), order=0, preserve_range=True).astype(np.uint8)
777
- if action == 'erase':
778
- self.mask[y0:y1, x0:x1] = np.where(zoomed_mask_resized_back == 0, 0, self.mask[y0:y1, x0:x1])
779
- else:
780
- self.mask[y0:y1, x0:x1] = np.where(zoomed_mask_resized_back > 0, zoomed_mask_resized_back, self.mask[y0:y1, x0:x1])
781
- self.update_display()
782
-
783
- def draw(self, event):
784
- if self.drawing:
785
- x, y = event.x, event.y
786
- if self.draw_coordinates:
787
- last_x, last_y = self.draw_coordinates[-1]
788
- self.current_line = self.canvas.create_line(last_x, last_y, x, y, fill="yellow", width=3)
789
- self.draw_coordinates.append((x, y))
790
-
791
- def draw_on_zoomed_mask(self, draw_coordinates):
792
- canvas_height = self.canvas.winfo_height()
793
- canvas_width = self.canvas.winfo_width()
794
- zoomed_mask = np.zeros((canvas_height, canvas_width), dtype=np.uint8)
795
- rr, cc = polygon(np.array(draw_coordinates)[:, 1], np.array(draw_coordinates)[:, 0], shape=zoomed_mask.shape)
796
- zoomed_mask[rr, cc] = 255
797
- return zoomed_mask
798
-
799
- def finish_drawing(self, event):
800
- if len(self.draw_coordinates) > 2:
801
- self.draw_coordinates.append(self.draw_coordinates[0])
802
- if self.zoom_active:
803
- x0, x1, y0, y1 = self.zoom_x0, self.zoom_x1, self.zoom_y0, self.zoom_y1
804
- zoomed_mask = self.draw_on_zoomed_mask(self.draw_coordinates)
805
- self.update_original_mask(zoomed_mask, x0, x1, y0, y1)
806
- else:
807
- rr, cc = polygon(np.array(self.draw_coordinates)[:, 1], np.array(self.draw_coordinates)[:, 0], shape=self.mask.shape)
808
- self.mask[rr, cc] = np.maximum(self.mask[rr, cc], 255)
809
- self.mask = self.mask.copy()
810
- self.canvas.delete(self.current_line)
811
- self.draw_coordinates.clear()
812
- self.update_display()
813
-
814
- def finish_drawing_if_active(self, event):
815
- if self.drawing and len(self.draw_coordinates) > 2:
816
- self.finish_drawing(event)
817
-
818
- ####################################################################################################
819
- # Single function butons#
820
- ####################################################################################################
821
-
822
- def apply_normalization(self):
823
- self.lower_quantile.set(self.lower_entry.get())
824
- self.upper_quantile.set(self.upper_entry.get())
825
- self.update_display()
826
-
827
- def fill_objects(self):
828
- binary_mask = self.mask > 0
829
- filled_mask = binary_fill_holes(binary_mask)
830
- self.mask = filled_mask.astype(np.uint8) * 255
831
- labeled_mask, _ = label(filled_mask)
832
- self.mask = labeled_mask
833
- self.update_display()
834
-
835
- def relabel_objects(self):
836
- mask = self.mask
837
- labeled_mask, num_labels = label(mask > 0)
838
- self.mask = labeled_mask
839
- self.update_display()
840
-
841
- def clear_objects(self):
842
- self.mask = np.zeros_like(self.mask)
843
- self.update_display()
844
-
845
- def invert_mask(self):
846
- self.mask = np.where(self.mask > 0, 0, 1)
847
- self.relabel_objects()
848
- self.update_display()
849
-
850
- def remove_small_objects(self):
851
- try:
852
- min_area = int(self.min_area_entry.get())
853
- except ValueError:
854
- print("Invalid minimum area value; using default of 100")
855
- min_area = 100
856
-
857
- labeled_mask, num_labels = label(self.mask > 0)
858
- for i in range(1, num_labels + 1): # Skip background
859
- if np.sum(labeled_mask == i) < min_area:
860
- self.mask[labeled_mask == i] = 0 # Remove small objects
861
- self.update_display()
862
-
863
- ##@log_function_call
864
- def initiate_mask_app_root(parent_frame):
865
- style = ttk.Style(parent_frame)
866
- set_dark_style(style)
867
- style_text_boxes(style)
868
- set_default_font(parent_frame, font_name="Arial", size=8)
869
-
870
- container = tk.PanedWindow(parent_frame, orient=tk.HORIZONTAL)
871
- container.pack(fill=tk.BOTH, expand=True)
872
-
873
- scrollable_frame = ScrollableFrame(container, bg='#333333')
874
- container.add(scrollable_frame, stretch="always")
875
-
876
- # Setup input fields
877
- vars_dict = {
878
- 'folder_path': ttk.Entry(scrollable_frame.scrollable_frame),
879
- 'scale_factor': ttk.Entry(scrollable_frame.scrollable_frame)
880
- }
881
-
882
- # Arrange input fields and labels
883
- row = 0
884
- for name, entry in vars_dict.items():
885
- ttk.Label(scrollable_frame.scrollable_frame, text=f"{name.replace('_', ' ').capitalize()}:").grid(row=row, column=0)
886
- entry.grid(row=row, column=1)
887
- row += 1
888
-
889
- # Function to be called when "Run" button is clicked
890
- def run_app():
891
- folder_path = vars_dict['folder_path'].get()
892
- scale_factor = float(vars_dict['scale_factor'].get())
893
-
894
- # Clear previous content instead of destroying the root
895
- for widget in parent_frame.winfo_children():
896
- widget.destroy()
897
-
898
- # Start the modify_masks application in the same root window
899
- app_instance = modify_masks(parent_frame, folder_path, scale_factor)
900
-
901
- run_button = CustomButton(scrollable_frame.scrollable_frame, text="Run", command=run_app)
902
- run_button.grid(row=row, column=0, columnspan=2, pady=10, padx=10)
903
-
904
- return parent_frame
905
-
906
- def gui_make_masks():
907
- root = tk.Tk()
908
- width = root.winfo_screenwidth()
909
- height = root.winfo_screenheight()
910
- root.geometry(f"{width}x{height}")
911
- root.title("Mask Application")
912
-
913
- # Clear previous content if any
914
- if hasattr(root, 'content_frame'):
915
- for widget in root.content_frame.winfo_children():
916
- widget.destroy()
917
- root.content_frame.grid_forget()
918
- else:
919
- root.content_frame = tk.Frame(root)
920
- root.content_frame.grid(row=1, column=0, sticky="nsew")
921
- root.grid_rowconfigure(1, weight=1)
922
- root.grid_columnconfigure(0, weight=1)
923
-
924
- initiate_mask_app_root(root.content_frame)
925
- create_menu_bar(root)
926
- root.mainloop()
927
-
928
- if __name__ == "__main__":
929
- gui_make_masks()