spacr 0.0.20__py3-none-any.whl → 0.0.35__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.
spacr/io.py CHANGED
@@ -1,4 +1,4 @@
1
- import os, re, sqlite3, gc, torch, time, random, shutil, cv2, tarfile, cellpose
1
+ import os, re, sqlite3, gc, torch, time, random, shutil, cv2, tarfile, cellpose, glob
2
2
  import numpy as np
3
3
  import pandas as pd
4
4
  import tifffile
@@ -45,19 +45,19 @@ def _load_images_and_labels(image_files, label_files, circular=False, invert=Fal
45
45
 
46
46
  if not image_files is None and not label_files is None:
47
47
  for img_file, lbl_file in zip(image_files, label_files):
48
- image = cellpose.imread(img_file)
48
+ image = cellpose.io.imread(img_file)
49
49
  if invert:
50
50
  image = invert_image(image)
51
51
  if circular:
52
52
  image = apply_mask(image, output_value=0)
53
- label = cellpose.imread(lbl_file)
53
+ label = cellpose.io.imread(lbl_file)
54
54
  if image.max() > 1:
55
55
  image = image / image.max()
56
56
  images.append(image)
57
57
  labels.append(label)
58
58
  elif not image_files is None:
59
59
  for img_file in image_files:
60
- image = cellpose.imread(img_file)
60
+ image = cellpose.io.imread(img_file)
61
61
  if invert:
62
62
  image = invert_image(image)
63
63
  if circular:
@@ -67,7 +67,7 @@ def _load_images_and_labels(image_files, label_files, circular=False, invert=Fal
67
67
  images.append(image)
68
68
  elif not image_files is None:
69
69
  for lbl_file in label_files:
70
- label = cellpose.imread(lbl_file)
70
+ label = cellpose.io.imread(lbl_file)
71
71
  if circular:
72
72
  label = apply_mask(label, output_value=0)
73
73
  labels.append(label)
@@ -109,15 +109,17 @@ def _load_normalized_images_and_labels(image_files, label_files, signal_threshol
109
109
 
110
110
  if label_files is not None:
111
111
  label_names = [os.path.basename(f) for f in label_files]
112
+ label_dir = os.path.dirname(label_files[0])
112
113
 
113
114
  # Load images and check percentiles
114
115
  for i,img_file in enumerate(image_files):
115
- image = cellpose.imread(img_file)
116
+ #print(img_file)
117
+ image = cellpose.io.imread(img_file)
116
118
  if invert:
117
119
  image = invert_image(image)
118
120
  if circular:
119
121
  image = apply_mask(image, output_value=0)
120
-
122
+ #print(image.shape)
121
123
  # If specific channels are specified, select them
122
124
  if channels is not None and image.ndim == 3:
123
125
  image = image[..., channels]
@@ -169,7 +171,7 @@ def _load_normalized_images_and_labels(image_files, label_files, signal_threshol
169
171
 
170
172
  if label_files is not None:
171
173
  for lbl_file in label_files:
172
- labels.append(cellpose.imread(lbl_file))
174
+ labels.append(cellpose.io.imread(lbl_file))
173
175
  else:
174
176
  label_names = []
175
177
  label_dir = None
@@ -178,85 +180,6 @@ def _load_normalized_images_and_labels(image_files, label_files, signal_threshol
178
180
 
179
181
  return normalized_images, labels, image_names, label_names
180
182
 
181
- class MyDataset(Dataset):
182
- """
183
- Custom dataset class for loading and processing image data.
184
-
185
- Args:
186
- data_dir (str): The directory path where the data is stored.
187
- loader_classes (list): List of class names.
188
- transform (callable, optional): A function/transform that takes in an PIL image and returns a transformed version. Default is None.
189
- shuffle (bool, optional): Whether to shuffle the dataset. Default is True.
190
- load_to_memory (bool, optional): Whether to load images into memory. Default is False.
191
-
192
- Attributes:
193
- data_dir (str): The directory path where the data is stored.
194
- classes (list): List of class names.
195
- transform (callable): A function/transform that takes in an PIL image and returns a transformed version.
196
- shuffle (bool): Whether to shuffle the dataset.
197
- load_to_memory (bool): Whether to load images into memory.
198
- filenames (list): List of file paths.
199
- labels (list): List of labels corresponding to each file.
200
- images (list): List of loaded images.
201
- image_cache (Cache): Cache object for storing loaded images.
202
-
203
- Methods:
204
- load_image: Load an image from file.
205
- __len__: Get the length of the dataset.
206
- shuffle_dataset: Shuffle the dataset.
207
- __getitem__: Get an item from the dataset.
208
-
209
- """
210
-
211
- def _init__(self, data_dir, loader_classes, transform=None, shuffle=True, load_to_memory=False):
212
- from .utils import Cache
213
- self.data_dir = data_dir
214
- self.classes = loader_classes
215
- self.transform = transform
216
- self.shuffle = shuffle
217
- self.load_to_memory = load_to_memory
218
- self.filenames = []
219
- self.labels = []
220
- self.images = []
221
- self.image_cache = Cache(50)
222
- for class_name in self.classes:
223
- class_path = os.path.join(data_dir, class_name)
224
- class_files = [os.path.join(class_path, f) for f in os.listdir(class_path) if os.path.isfile(os.path.join(class_path, f))]
225
- self.filenames.extend(class_files)
226
- self.labels.extend([self.classes.index(class_name)] * len(class_files))
227
- if self.shuffle:
228
- self.shuffle_dataset()
229
- if self.load_to_memory:
230
- self.images = [self.load_image(f) for f in self.filenames]
231
-
232
- def load_image(self, img_path):
233
- img = self.image_cache.get(img_path)
234
- if img is None:
235
- img = Image.open(img_path).convert('RGB')
236
- self.image_cache.put(img_path, img)
237
- return img
238
-
239
- def _len__(self):
240
- return len(self.filenames)
241
-
242
- def shuffle_dataset(self):
243
- combined = list(zip(self.filenames, self.labels))
244
- random.shuffle(combined)
245
- self.filenames, self.labels = zip(*combined)
246
-
247
- def _getitem__(self, index):
248
- label = self.labels[index]
249
- filename = self.filenames[index]
250
- if self.load_to_memory:
251
- img = self.images[index]
252
- else:
253
- img = self.load_image(filename)
254
- if self.transform is not None:
255
- img = self.transform(img)
256
- else:
257
- img = ToTensor()(img)
258
- return img, label, filename
259
-
260
183
  class CombineLoaders:
261
184
  """
262
185
  A class that combines multiple data loaders into a single iterator.
@@ -383,6 +306,85 @@ class NoClassDataset(Dataset):
383
306
  img = ToTensor()(img)
384
307
  # Return both the image and its filename
385
308
  return img, self.filenames[index]
309
+
310
+ class MyDataset_v1(Dataset):
311
+ """
312
+ Custom dataset class for loading and processing image data.
313
+
314
+ Args:
315
+ data_dir (str): The directory path where the data is stored.
316
+ loader_classes (list): List of class names.
317
+ transform (callable, optional): A function/transform that takes in an PIL image and returns a transformed version. Default is None.
318
+ shuffle (bool, optional): Whether to shuffle the dataset. Default is True.
319
+ load_to_memory (bool, optional): Whether to load images into memory. Default is False.
320
+
321
+ Attributes:
322
+ data_dir (str): The directory path where the data is stored.
323
+ classes (list): List of class names.
324
+ transform (callable): A function/transform that takes in an PIL image and returns a transformed version.
325
+ shuffle (bool): Whether to shuffle the dataset.
326
+ load_to_memory (bool): Whether to load images into memory.
327
+ filenames (list): List of file paths.
328
+ labels (list): List of labels corresponding to each file.
329
+ images (list): List of loaded images.
330
+ image_cache (Cache): Cache object for storing loaded images.
331
+
332
+ Methods:
333
+ load_image: Load an image from file.
334
+ __len__: Get the length of the dataset.
335
+ shuffle_dataset: Shuffle the dataset.
336
+ __getitem__: Get an item from the dataset.
337
+
338
+ """
339
+
340
+ def __init__(self, data_dir, loader_classes, transform=None, shuffle=True, load_to_memory=False):
341
+ from .utils import Cache
342
+ self.data_dir = data_dir
343
+ self.classes = loader_classes
344
+ self.transform = transform
345
+ self.shuffle = shuffle
346
+ self.load_to_memory = load_to_memory
347
+ self.filenames = []
348
+ self.labels = []
349
+ self.images = []
350
+ self.image_cache = Cache(50)
351
+ for class_name in self.classes:
352
+ class_path = os.path.join(data_dir, class_name)
353
+ class_files = [os.path.join(class_path, f) for f in os.listdir(class_path) if os.path.isfile(os.path.join(class_path, f))]
354
+ self.filenames.extend(class_files)
355
+ self.labels.extend([self.classes.index(class_name)] * len(class_files))
356
+ if self.shuffle:
357
+ self.shuffle_dataset()
358
+ if self.load_to_memory:
359
+ self.images = [self.load_image(f) for f in self.filenames]
360
+
361
+ def load_image(self, img_path):
362
+ img = self.image_cache.get(img_path)
363
+ if img is None:
364
+ img = Image.open(img_path).convert('RGB')
365
+ self.image_cache.put(img_path, img)
366
+ return img
367
+
368
+ def _len__(self):
369
+ return len(self.filenames)
370
+
371
+ def shuffle_dataset(self):
372
+ combined = list(zip(self.filenames, self.labels))
373
+ random.shuffle(combined)
374
+ self.filenames, self.labels = zip(*combined)
375
+
376
+ def _getitem__(self, index):
377
+ label = self.labels[index]
378
+ filename = self.filenames[index]
379
+ if self.load_to_memory:
380
+ img = self.images[index]
381
+ else:
382
+ img = self.load_image(filename)
383
+ if self.transform is not None:
384
+ img = self.transform(img)
385
+ else:
386
+ img = ToTensor()(img)
387
+ return img, label, filename
386
388
 
387
389
  class MyDataset(Dataset):
388
390
  """
@@ -398,7 +400,7 @@ class MyDataset(Dataset):
398
400
  specific_labels (list, optional): A list of specific labels corresponding to the specific files. Default is None.
399
401
  """
400
402
 
401
- def _init__(self, data_dir, loader_classes, transform=None, shuffle=True, pin_memory=False, specific_files=None, specific_labels=None):
403
+ def __init__(self, data_dir, loader_classes, transform=None, shuffle=True, pin_memory=False, specific_files=None, specific_labels=None):
402
404
  self.data_dir = data_dir
403
405
  self.classes = loader_classes
404
406
  self.transform = transform
@@ -427,7 +429,7 @@ class MyDataset(Dataset):
427
429
  img = Image.open(img_path).convert('RGB')
428
430
  return img
429
431
 
430
- def _len__(self):
432
+ def __len__(self):
431
433
  return len(self.filenames)
432
434
 
433
435
  def shuffle_dataset(self):
@@ -439,7 +441,7 @@ class MyDataset(Dataset):
439
441
  filename = os.path.basename(filepath) # Get just the filename from the full path
440
442
  return filename.split('_')[0]
441
443
 
442
- def _getitem__(self, index):
444
+ def __getitem__(self, index):
443
445
  label = self.labels[index]
444
446
  filename = self.filenames[index]
445
447
  img = self.load_image(filename)
@@ -600,7 +602,7 @@ def _rename_and_organize_image_files(src, regex, batch_size=100, pick_slice=Fals
600
602
  shutil.move(os.path.join(src, filename), move)
601
603
  return
602
604
 
603
- def _merge_file(chan_dirs, stack_dir, file):
605
+ def _merge_file_v1(chan_dirs, stack_dir, file):
604
606
  """
605
607
  Merge multiple channels into a single stack and save it as a numpy array.
606
608
 
@@ -625,15 +627,80 @@ def _merge_file(chan_dirs, stack_dir, file):
625
627
  stack = np.concatenate(channels, axis=2)
626
628
  np.save(new_file, stack)
627
629
 
628
- def _is_dir_empty(dir_path):
630
+ def _merge_file_v1(chan_dirs, stack_dir, file):
629
631
  """
630
- Check if a directory is empty.
632
+ Merge multiple channels into a single stack and save it as a numpy array.
633
+ Args:
634
+ chan_dirs (list): List of directories containing channel images.
635
+ stack_dir (str): Directory to save the merged stack.
636
+ file (str): File name of the channel image.
631
637
 
638
+ Returns:
639
+ None
640
+ """
641
+ new_file = stack_dir / (file.stem + '.npy')
642
+ if not new_file.exists():
643
+ stack_dir.mkdir(exist_ok=True)
644
+ channels = []
645
+ for i, chan_dir in enumerate(chan_dirs):
646
+ img_path = str(chan_dir / file.name)
647
+ img = cv2.imread(img_path, -1)
648
+ if img is None:
649
+ print(f"Warning: Failed to read image {img_path}")
650
+ continue
651
+ chan = np.expand_dims(img, axis=2)
652
+ channels.append(chan)
653
+ del img # Explicitly delete the reference to the image to free up memory
654
+ if i % 10 == 0: # Periodically suggest garbage collection
655
+ gc.collect()
656
+
657
+ if channels:
658
+ stack = np.concatenate(channels, axis=2)
659
+ np.save(new_file, stack)
660
+ else:
661
+ print(f"No valid channels to merge for file {file.name}")
662
+
663
+ def _merge_file(chan_dirs, stack_dir, file_name):
664
+ """
665
+ Merge multiple channels into a single stack and save it as a numpy array, using os module for path handling.
666
+
632
667
  Args:
633
- dir_path (str): The path to the directory.
668
+ chan_dirs (list): List of directories containing channel images.
669
+ stack_dir (str): Directory to save the merged stack.
670
+ file_name (str): File name of the channel image.
634
671
 
635
672
  Returns:
636
- bool: True if the directory is empty, False otherwise.
673
+ None
674
+ """
675
+ # Construct new file path
676
+ file_root, file_ext = os.path.splitext(file_name)
677
+ new_file = os.path.join(stack_dir, file_root + '.npy')
678
+
679
+ # Check if the new file exists and create the stack directory if it doesn't
680
+ if not os.path.exists(new_file):
681
+ os.makedirs(stack_dir, exist_ok=True)
682
+ channels = []
683
+ for i, chan_dir in enumerate(chan_dirs):
684
+ img_path = os.path.join(chan_dir, file_name)
685
+ img = cv2.imread(img_path, -1)
686
+ if img is None:
687
+ print(f"Warning: Failed to read image {img_path}")
688
+ continue
689
+ chan = np.expand_dims(img, axis=2)
690
+ channels.append(chan)
691
+ del img # Explicitly delete the reference to the image to free up memory
692
+ if i % 10 == 0: # Periodically suggest garbage collection
693
+ gc.collect()
694
+
695
+ if channels:
696
+ stack = np.concatenate(channels, axis=2)
697
+ np.save(new_file, stack)
698
+ else:
699
+ print(f"No valid channels to merge for file {file_name}")
700
+
701
+ def _is_dir_empty(dir_path):
702
+ """
703
+ Check if a directory is empty using os module.
637
704
  """
638
705
  return len(os.listdir(dir_path)) == 0
639
706
 
@@ -733,7 +800,7 @@ def _move_to_chan_folder(src, regex, timelapse=False, metadata_type=''):
733
800
  shutil.move(os.path.join(src, filename), move)
734
801
  return
735
802
 
736
- def _merge_channels(src, plot=False):
803
+ def _merge_channels_v2(src, plot=False):
737
804
  from .plot import plot_arrays
738
805
  """
739
806
  Merge the channels in the given source directory and save the merged files in a 'stack' directory.
@@ -761,7 +828,8 @@ def _merge_channels(src, plot=False):
761
828
  print(f'generated folder with merged arrays: {stack_dir}')
762
829
 
763
830
  if _is_dir_empty(stack_dir):
764
- with Pool(cpu_count()) as pool:
831
+ with Pool(max(cpu_count() // 2, 1)) as pool:
832
+ #with Pool(cpu_count()) as pool:
765
833
  merge_func = partial(_merge_file, chan_dirs, stack_dir)
766
834
  pool.map(merge_func, dir_files)
767
835
 
@@ -773,6 +841,47 @@ def _merge_channels(src, plot=False):
773
841
 
774
842
  return
775
843
 
844
+ def _merge_channels(src, plot=False):
845
+ """
846
+ Merge the channels in the given source directory and save the merged files in a 'stack' directory without using multiprocessing.
847
+ """
848
+
849
+ from .plot import plot_arrays
850
+
851
+ stack_dir = os.path.join(src, 'stack')
852
+ allowed_names = ['01', '02', '03', '04', '00', '1', '2', '3', '4', '0']
853
+
854
+ # List directories that match the allowed names
855
+ chan_dirs = [d for d in os.listdir(src) if os.path.isdir(os.path.join(src, d)) and d in allowed_names]
856
+ chan_dirs.sort()
857
+
858
+ print(f'List of folders in src: {chan_dirs}. Single channel folders.')
859
+ start_time = time.time()
860
+
861
+ # Assuming chan_dirs[0] is not empty and exists, adjust according to your logic
862
+ first_dir_path = os.path.join(src, chan_dirs[0])
863
+ dir_files = os.listdir(first_dir_path)
864
+
865
+ # Create the 'stack' directory if it doesn't exist
866
+ if not os.path.exists(stack_dir):
867
+ os.makedirs(stack_dir, exist_ok=True)
868
+ print(f'Generated folder with merged arrays: {stack_dir}')
869
+
870
+ if _is_dir_empty(stack_dir):
871
+ for file_name in dir_files:
872
+ full_file_path = os.path.join(first_dir_path, file_name)
873
+ if os.path.isfile(full_file_path):
874
+ _merge_file([os.path.join(src, d) for d in chan_dirs], stack_dir, file_name)
875
+
876
+ elapsed_time = time.time() - start_time
877
+ avg_time = elapsed_time / len(dir_files) if dir_files else 0
878
+ print(f'Average Time: {avg_time:.3f} sec, Total Elapsed Time: {elapsed_time:.3f} sec')
879
+
880
+ if plot:
881
+ plot_arrays(os.path.join(src, 'stack'))
882
+
883
+ return
884
+
776
885
  def _mip_all(src, include_first_chan=True):
777
886
 
778
887
  """
@@ -1191,6 +1300,7 @@ def preprocess_img_data(settings):
1191
1300
  extension_counts = Counter(extensions)
1192
1301
  most_common_extension = extension_counts.most_common(1)[0][0]
1193
1302
  img_format = None
1303
+
1194
1304
 
1195
1305
  delete_empty_subdirectories(src)
1196
1306
 
@@ -1206,7 +1316,7 @@ def preprocess_img_data(settings):
1206
1316
  print('Found existing channel_stack folder.')
1207
1317
  if os.path.exists(src+'/norm_channel_stack'):
1208
1318
  print('Found existing norm_channel_stack folder. Skipping preprocessing')
1209
- return
1319
+ return settings, src
1210
1320
 
1211
1321
  cmap = 'inferno'
1212
1322
  figuresize = 20
@@ -1214,8 +1324,10 @@ def preprocess_img_data(settings):
1214
1324
  save_dtype = 'uint16'
1215
1325
  correct_illumination = False
1216
1326
 
1217
- mask_channels = [settings['nucleus_channel'], settings['pathogen_channel'], settings['cell_channel']]
1218
- backgrounds = [settings['nucleus_background'], settings['pathogen_background'], settings['cell_background']]
1327
+ #mask_channels = [settings['nucleus_channel'], settings['pathogen_channel'], settings['cell_channel']]
1328
+ #backgrounds = [settings['nucleus_background'], settings['pathogen_background'], settings['cell_background']]
1329
+ mask_channels = [settings['nucleus_channel'], settings['cell_channel'], settings['pathogen_channel']]
1330
+ backgrounds = [settings['nucleus_background'], settings['cell_background'], settings['pathogen_background']]
1219
1331
 
1220
1332
  metadata_type = settings['metadata_type']
1221
1333
  custom_regex = settings['custom_regex']
@@ -1230,7 +1342,6 @@ def preprocess_img_data(settings):
1230
1342
  pick_slice = settings['pick_slice']
1231
1343
  skip_mode = settings['skip_mode']
1232
1344
 
1233
-
1234
1345
  if not img_format == None:
1235
1346
  if metadata_type == 'cellvoyager':
1236
1347
  regex = f'(?P<plateID>.*)_(?P<wellID>.*)_T(?P<timeID>.*)F(?P<fieldID>.*)L(?P<laserID>..)A(?P<AID>..)Z(?P<sliceID>.*)C(?P<chanID>.*){img_format}'
@@ -1248,6 +1359,8 @@ def preprocess_img_data(settings):
1248
1359
  print(f'regex mode:{metadata_type} regex:{regex}')
1249
1360
 
1250
1361
  if settings.get('test_mode', False):
1362
+ print(f'Running spacr in test mode')
1363
+ settings['plot'] = True
1251
1364
  try:
1252
1365
  os.rmdir(os.path.join(src, 'test'))
1253
1366
  print(f"Deleted test directory: {os.path.join(src, 'test')}")
@@ -1256,6 +1369,10 @@ def preprocess_img_data(settings):
1256
1369
 
1257
1370
  src = _run_test_mode(settings['src'], regex, timelapse=timelapse)
1258
1371
  settings['src'] = src
1372
+
1373
+ if img_format == None:
1374
+ if not os.path.exists(src+'/stack'):
1375
+ _merge_channels(src, plot=False)
1259
1376
 
1260
1377
  if not os.path.exists(src+'/stack'):
1261
1378
  try:
@@ -2273,6 +2390,8 @@ def convert_numpy_to_tiff(folder_path, limit=None):
2273
2390
  for i, filename in enumerate(files):
2274
2391
  if limit is not None and i >= limit:
2275
2392
  break
2393
+ if not filename.endswith('.npy'):
2394
+ continue
2276
2395
 
2277
2396
  # Construct the full file path
2278
2397
  file_path = os.path.join(folder_path, filename)
@@ -2289,7 +2408,47 @@ def convert_numpy_to_tiff(folder_path, limit=None):
2289
2408
  print(f"Converted {filename} to {tiff_filename} and saved in 'tiff' subdirectory.")
2290
2409
  return
2291
2410
 
2292
-
2411
+ def generate_cellpose_train_test(src, test_split=0.1):
2412
+
2413
+ mask_src = os.path.join(src, 'masks')
2414
+ img_paths = glob.glob(os.path.join(src, '*.tif'))
2415
+ img_filenames = [os.path.basename(file) for file in img_paths + img_paths]
2416
+ img_filenames = [file for file in img_filenames if os.path.exists(os.path.join(mask_src, file))]
2417
+ print(f'Found {len(img_filenames)} images with masks')
2418
+
2419
+ random.shuffle(img_filenames)
2420
+ split_index = int(len(img_filenames) * test_split)
2421
+ train_files = img_filenames[split_index:]
2422
+ test_files = img_filenames[:split_index]
2423
+ list_of_lists = [test_files, train_files]
2424
+ print(f'Split dataset into Train {len(train_files)} and Test {len(test_files)} files')
2425
+
2426
+ train_dir = os.path.join(os.path.dirname(src), 'train')
2427
+ train_dir_masks = os.path.join(train_dir, 'mask')
2428
+ test_dir = os.path.join(os.path.dirname(src), 'test')
2429
+ test_dir_masks = os.path.join(test_dir, 'mask')
2430
+
2431
+ os.makedirs(train_dir_masks, exist_ok=True)
2432
+ os.makedirs(test_dir_masks, exist_ok=True)
2433
+ for i, ls in enumerate(list_of_lists):
2434
+
2435
+ if i == 0:
2436
+ dst = test_dir
2437
+ dst_mask = test_dir_masks
2438
+ _type = 'Test'
2439
+ if i == 1:
2440
+ dst = train_dir
2441
+ dst_mask = train_dir_masks
2442
+ _type = 'Train'
2443
+
2444
+ for idx, filename in enumerate(ls):
2445
+ img_path = os.path.join(src, filename)
2446
+ mask_path = os.path.join(mask_src, filename)
2447
+ new_img_path = os.path.join(dst, filename)
2448
+ new_mask_path = os.path.join(dst_mask, filename)
2449
+ shutil.copy(img_path, new_img_path)
2450
+ shutil.copy(mask_path, new_mask_path)
2451
+ print(f'Copied {idx+1}/{len(ls)} images to {_type} set', end='\r', flush=True)
2293
2452
 
2294
2453
 
2295
2454
 
spacr/mask_app.py CHANGED
@@ -13,7 +13,7 @@ from ttkthemes import ThemedTk
13
13
 
14
14
  from .logger import log_function_call
15
15
 
16
- from .gui_utils import ScrollableFrame, set_dark_style, set_default_font, create_dark_mode
16
+ from .gui_utils import ScrollableFrame, set_dark_style, set_default_font, create_dark_mode, style_text_boxes, create_menu_bar
17
17
 
18
18
  class modify_masks:
19
19
 
@@ -759,9 +759,12 @@ def initiate_mask_app_root(width, height):
759
759
  root = ThemedTk(theme=theme)
760
760
  style = ttk.Style(root)
761
761
  set_dark_style(style)
762
- set_default_font(root, font_name="Arial", size=10)
762
+
763
+ style_text_boxes(style)
764
+ set_default_font(root, font_name="Arial", size=8)
763
765
  root.geometry(f"{width}x{height}")
764
766
  root.title("Mask App")
767
+ create_menu_bar(root)
765
768
 
766
769
  container = tk.PanedWindow(root, orient=tk.HORIZONTAL)
767
770
  container.pack(fill=tk.BOTH, expand=True)
@@ -806,7 +809,7 @@ def initiate_mask_app_root(width, height):
806
809
  create_dark_mode(root, style, console_output=None)
807
810
 
808
811
  run_button = ttk.Button(scrollable_frame.scrollable_frame, text="Run", command=run_app)
809
- run_button.grid(row=row, column=0, columnspan=2, pady=10)
812
+ run_button.grid(row=row, column=0, columnspan=2, pady=10, padx=10)
810
813
 
811
814
  return root
812
815