spacr 0.0.70__py3-none-any.whl → 0.0.80__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
@@ -255,31 +255,24 @@ class CombinedDataset(Dataset):
255
255
 
256
256
  class NoClassDataset(Dataset):
257
257
  """
258
- A custom dataset class for handling images without class labels.
259
-
258
+ A custom dataset class for handling image data without class labels.
259
+
260
260
  Args:
261
- data_dir (str): The directory path where the images are stored.
262
- transform (callable, optional): A function/transform that takes in an PIL image and returns a transformed version. Default is None.
261
+ data_dir (str): The directory path where the image files are located.
262
+ transform (callable, optional): A function/transform to apply to the image data. Default is None.
263
263
  shuffle (bool, optional): Whether to shuffle the dataset. Default is True.
264
264
  load_to_memory (bool, optional): Whether to load all images into memory. Default is False.
265
-
265
+
266
266
  Attributes:
267
- data_dir (str): The directory path where the images are stored.
268
- transform (callable): A function/transform that takes in an PIL image and returns a transformed version.
267
+ data_dir (str): The directory path where the image files are located.
268
+ transform (callable): A function/transform to apply to the image data.
269
269
  shuffle (bool): Whether to shuffle the dataset.
270
270
  load_to_memory (bool): Whether to load all images into memory.
271
- filenames (list): List of file paths for the images.
272
- images (list): List of loaded images (if load_to_memory is True).
273
-
274
- Methods:
275
- load_image: Loads an image from the given file path.
276
- __len__: Returns the number of images in the dataset.
277
- shuffle_dataset: Shuffles the dataset.
278
- __getitem__: Retrieves an image and its corresponding file path from the dataset.
279
-
271
+ filenames (list): A list of file paths for the image files.
272
+ images (list): A list of loaded images (if load_to_memory is True).
280
273
  """
281
-
282
- def _init__(self, data_dir, transform=None, shuffle=True, load_to_memory=False):
274
+
275
+ def __init__(self, data_dir, transform=None, shuffle=True, load_to_memory=False):
283
276
  self.data_dir = data_dir
284
277
  self.transform = transform
285
278
  self.shuffle = shuffle
@@ -289,16 +282,47 @@ class NoClassDataset(Dataset):
289
282
  self.shuffle_dataset()
290
283
  if self.load_to_memory:
291
284
  self.images = [self.load_image(f) for f in self.filenames]
285
+
292
286
  #@lru_cache(maxsize=None)
293
287
  def load_image(self, img_path):
288
+ """
289
+ Load an image from the given file path.
290
+
291
+ Args:
292
+ img_path (str): The file path of the image.
293
+
294
+ Returns:
295
+ PIL.Image: The loaded image.
296
+ """
294
297
  img = Image.open(img_path).convert('RGB')
295
298
  return img
296
- def _len__(self):
299
+
300
+ def __len__(self):
301
+ """
302
+ Get the total number of images in the dataset.
303
+
304
+ Returns:
305
+ int: The number of images in the dataset.
306
+ """
297
307
  return len(self.filenames)
308
+
298
309
  def shuffle_dataset(self):
310
+ """
311
+ Shuffle the dataset.
312
+ """
299
313
  if self.shuffle:
300
314
  random.shuffle(self.filenames)
301
- def _getitem__(self, index):
315
+
316
+ def __getitem__(self, index):
317
+ """
318
+ Get the image and its corresponding filename at the given index.
319
+
320
+ Args:
321
+ index (int): The index of the image in the dataset.
322
+
323
+ Returns:
324
+ tuple: A tuple containing the image and its filename.
325
+ """
302
326
  if self.load_to_memory:
303
327
  img = self.images[index]
304
328
  else:
@@ -374,32 +398,7 @@ class MyDataset(Dataset):
374
398
  return img, label, filename
375
399
 
376
400
  class NoClassDataset(Dataset):
377
- """
378
- A custom dataset class for handling images without class labels.
379
-
380
- Args:
381
- data_dir (str): The directory path where the images are stored.
382
- transform (callable, optional): A function/transform that takes in an PIL image and returns a transformed version. Default is None.
383
- shuffle (bool, optional): Whether to shuffle the dataset. Default is True.
384
- load_to_memory (bool, optional): Whether to load all images into memory. Default is False.
385
-
386
- Attributes:
387
- data_dir (str): The directory path where the images are stored.
388
- transform (callable): A function/transform that takes in an PIL image and returns a transformed version.
389
- shuffle (bool): Whether to shuffle the dataset.
390
- load_to_memory (bool): Whether to load all images into memory.
391
- filenames (list): List of file paths of the images.
392
- images (list): List of loaded images (if load_to_memory is True).
393
-
394
- Methods:
395
- load_image: Load an image from the given file path.
396
- __len__: Get the length of the dataset.
397
- shuffle_dataset: Shuffle the dataset.
398
- __getitem__: Get an item (image and its filename) from the dataset.
399
-
400
- """
401
-
402
- def _init__(self, data_dir, transform=None, shuffle=True, load_to_memory=False):
401
+ def __init__(self, data_dir, transform=None, shuffle=True, load_to_memory=False):
403
402
  self.data_dir = data_dir
404
403
  self.transform = transform
405
404
  self.shuffle = shuffle
@@ -409,16 +408,20 @@ class NoClassDataset(Dataset):
409
408
  self.shuffle_dataset()
410
409
  if self.load_to_memory:
411
410
  self.images = [self.load_image(f) for f in self.filenames]
412
- #@lru_cache(maxsize=None)
411
+
413
412
  def load_image(self, img_path):
414
413
  img = Image.open(img_path).convert('RGB')
415
414
  return img
416
- def _len__(self):
415
+
416
+ def __len__(self):
417
+
417
418
  return len(self.filenames)
419
+
418
420
  def shuffle_dataset(self):
419
421
  if self.shuffle:
420
422
  random.shuffle(self.filenames)
421
- def _getitem__(self, index):
423
+
424
+ def __getitem__(self, index):
422
425
  if self.load_to_memory:
423
426
  img = self.images[index]
424
427
  else:
@@ -427,8 +430,8 @@ class NoClassDataset(Dataset):
427
430
  img = self.transform(img)
428
431
  else:
429
432
  img = ToTensor()(img)
430
- # Return both the image and its filename
431
433
  return img, self.filenames[index]
434
+
432
435
 
433
436
  class TarImageDataset(Dataset):
434
437
  def _init__(self, tar_path, transform=None):
@@ -1038,72 +1041,6 @@ def _normalize_img_batch(stack, backgrounds, remove_backgrounds, lower_percentil
1038
1041
 
1039
1042
  return normalized_stack.astype(save_dtype)
1040
1043
 
1041
- def _normalize_img_batch_v1(stack, backgrounds, remove_backgrounds, lower_percentile, save_dtype, signal_to_noise, signal_thresholds):
1042
- """
1043
- Normalize the stack of images.
1044
-
1045
- Args:
1046
- stack (numpy.ndarray): The stack of images to normalize.
1047
- backgrounds (list): Background values for each channel.
1048
- remove_backgrounds (list): Whether to remove background values for each channel.
1049
- lower_percentile (int): Lower percentile value for normalization.
1050
- save_dtype (numpy.dtype): Data type for saving the normalized stack.
1051
- signal_to_noise (list): Signal-to-noise ratio thresholds for each channel.
1052
- signal_thresholds (list): Signal thresholds for each channel.
1053
-
1054
- Returns:
1055
- numpy.ndarray: The normalized stack.
1056
- """
1057
- normalized_stack = np.zeros_like(stack, dtype=np.float32)
1058
- time_ls = []
1059
-
1060
- for chan_index, channel in enumerate(range(stack.shape[-1])):
1061
- single_channel = stack[:, :, :, channel]
1062
- background = backgrounds[chan_index]
1063
- signal_threshold = signal_thresholds[chan_index]
1064
- remove_background = remove_backgrounds[chan_index]
1065
- signal_2_noise = signal_to_noise[chan_index]
1066
- print(f'chan_index:{chan_index} background:{background} signal_threshold:{signal_threshold} remove_background:{remove_background} signal_2_noise:{signal_2_noise}')
1067
-
1068
- if remove_background:
1069
- single_channel[single_channel < background] = 0
1070
-
1071
- non_zero_single_channel = single_channel[single_channel != 0]
1072
- global_lower = np.percentile(non_zero_single_channel, lower_percentile)
1073
- for upper_p in np.linspace(98, 99.5, num=20).tolist():
1074
- global_upper = np.percentile(non_zero_single_channel, upper_p)
1075
- if global_upper >= signal_threshold:
1076
- break
1077
-
1078
- arr_2d_normalized = np.zeros_like(single_channel, dtype=single_channel.dtype)
1079
- signal_to_noise_ratio_ls = []
1080
- for array_index in range(single_channel.shape[0]):
1081
- start = time.time()
1082
- arr_2d = single_channel[array_index, :, :]
1083
- non_zero_arr_2d = arr_2d[arr_2d != 0]
1084
- if non_zero_arr_2d.size > 0:
1085
- lower, upper = np.percentile(non_zero_arr_2d, (lower_percentile, upper_p))
1086
- signal_to_noise_ratio = upper / lower
1087
- else:
1088
- signal_to_noise_ratio = 0
1089
- signal_to_noise_ratio_ls.append(signal_to_noise_ratio)
1090
- average_stnr = np.mean(signal_to_noise_ratio_ls) if len(signal_to_noise_ratio_ls) > 0 else 0
1091
-
1092
- if signal_to_noise_ratio > signal_2_noise:
1093
- arr_2d_rescaled = exposure.rescale_intensity(arr_2d, in_range=(lower, upper), out_range=(0, 1))
1094
- arr_2d_normalized[array_index, :, :] = arr_2d_rescaled
1095
- else:
1096
- arr_2d_normalized[array_index, :, :] = arr_2d
1097
- stop = time.time()
1098
- duration = (stop - start) * single_channel.shape[0]
1099
- time_ls.append(duration)
1100
- average_time = np.mean(time_ls) if len(time_ls) > 0 else 0
1101
- print(f'Progress: channels:{chan_index}/{stack.shape[-1] - 1}, arrays:{array_index + 1}/{single_channel.shape[0]}, Signal:{upper:.1f}, noise:{lower:.1f}, Signal-to-noise:{average_stnr:.1f}, Time/channel:{average_time:.2f}sec')
1102
-
1103
- normalized_stack[:, :, :, channel] = arr_2d_normalized
1104
-
1105
- return normalized_stack.astype(save_dtype)
1106
-
1107
1044
  def _get_lists_for_normalization(settings):
1108
1045
  """
1109
1046
  Get lists for normalization based on the provided settings.
spacr/measure.py CHANGED
@@ -626,7 +626,13 @@ def _measure_crop_core(index, time_ls, file, settings):
626
626
  _create_database(source_folder+'/measurements/measurements.db')
627
627
 
628
628
  if settings['plot_filtration']:
629
- _plot_cropped_arrays(data)
629
+
630
+ if len(data.shape) == 3:
631
+ figuresize = data.shape[2]*10
632
+ else:
633
+ figuresize = 10
634
+ print('')
635
+ _plot_cropped_arrays(data, file, figuresize)
630
636
 
631
637
  channel_arrays = data[:, :, settings['channels']].astype(data_type)
632
638
  if settings['cell_mask_dim'] is not None:
@@ -652,7 +658,6 @@ def _measure_crop_core(index, time_ls, file, settings):
652
658
  data[:, :, settings['nucleus_mask_dim']] = nucleus_mask
653
659
  save_folder = settings['input_folder']
654
660
  np.save(os.path.join(save_folder, file), data)
655
-
656
661
  else:
657
662
  nucleus_mask = np.zeros_like(data[:, :, 0])
658
663
 
@@ -703,7 +708,8 @@ def _measure_crop_core(index, time_ls, file, settings):
703
708
  data = np.concatenate((data, cytoplasm_mask[:, :, np.newaxis]), axis=2)
704
709
 
705
710
  if settings['plot_filtration']:
706
- _plot_cropped_arrays(data)
711
+ _plot_cropped_arrays(data, file, figuresize)
712
+ #_plot_cropped_arrays(data)
707
713
 
708
714
  if settings['save_measurements']:
709
715
 
@@ -792,23 +798,25 @@ def _measure_crop_core(index, time_ls, file, settings):
792
798
  if settings['save_png']:
793
799
  fldr_type = f"{crop_mode}_png/"
794
800
  png_folder = os.path.join(fldr,fldr_type)
795
-
796
801
  img_path = os.path.join(png_folder, img_name)
797
-
802
+ img_paths.append(img_path)
803
+
798
804
  png_channels = data[:, :, settings['png_dims']].astype(data_type)
799
805
 
800
806
  if settings['normalize_by'] == 'fov':
801
- percentiles_list = _get_percentiles(png_channels, settings['normalize'][0],q2=settings['normalize'][1])
807
+ if not settings['normalize'] is False:
808
+ percentile_list = _get_percentiles(png_channels, settings['normalize'][0], settings['normalize'][1])
802
809
 
803
810
  png_channels = _crop_center(png_channels, region, new_width=width, new_height=height)
804
-
805
811
  if isinstance(settings['normalize'], list):
806
812
  if settings['normalize_by'] == 'png':
807
- png_channels = normalize_to_dtype(png_channels, q1=settings['normalize'][0],q2=settings['normalize'][1])
813
+ png_channels = normalize_to_dtype(png_channels, settings['normalize'][0], settings['normalize'][1])
808
814
 
809
815
  if settings['normalize_by'] == 'fov':
810
- png_channels = normalize_to_dtype(png_channels, q1=settings['normalize'][0],q2=settings['normalize'][1], percentiles=percentiles_list)
811
-
816
+ png_channels = normalize_to_dtype(png_channels, settings['normalize'][0], settings['normalize'][1], percentile_list=percentile_list)
817
+ else:
818
+ png_channels = normalize_to_dtype(png_channels, 0, 100)
819
+
812
820
  os.makedirs(png_folder, exist_ok=True)
813
821
 
814
822
  if png_channels.shape[2] == 2:
@@ -818,8 +826,6 @@ def _measure_crop_core(index, time_ls, file, settings):
818
826
  else:
819
827
  cv2.imwrite(img_path, png_channels)
820
828
 
821
- img_paths.append(img_path)
822
-
823
829
  if len(img_paths) == len(objects_in_image):
824
830
 
825
831
  png_df = pd.DataFrame(img_paths, columns=['png_path'])
@@ -858,7 +864,11 @@ def _measure_crop_core(index, time_ls, file, settings):
858
864
  traceback.print_exc()
859
865
 
860
866
  if settings['plot']:
861
- _plot_cropped_arrays(png_channels)
867
+ if len(png_channels.shape) == 3:
868
+ figuresize = png_channels.shape[2]*10
869
+ else:
870
+ figuresize = 10
871
+ _plot_cropped_arrays(png_channels, img_name, figuresize, threshold=1)
862
872
 
863
873
  if settings['save_arrays']:
864
874
  row_idx, col_idx = np.where(region)
@@ -867,12 +877,20 @@ def _measure_crop_core(index, time_ls, file, settings):
867
877
  os.makedirs(array_folder, exist_ok=True)
868
878
  np.save(os.path.join(array_folder, img_name), region_array)
869
879
  if settings['plot']:
870
- _plot_cropped_arrays(region_array)
880
+ if len(png_channels.shape) == 3:
881
+ figuresize = png_channels.shape[2]*10
882
+ else:
883
+ figuresize = 10
884
+ _plot_cropped_arrays(png_channels, img_name, figuresize, threshold=1)
871
885
 
872
886
  if not settings['save_arrays'] and not settings['save_png'] and settings['plot']:
873
887
  row_idx, col_idx = np.where(region)
874
888
  region_array = data[row_idx.min():row_idx.max()+1, col_idx.min():col_idx.max()+1, :]
875
- _plot_cropped_arrays(region_array)
889
+ if len(png_channels.shape) == 3:
890
+ figuresize = png_channels.shape[2]*10
891
+ else:
892
+ figuresize = 10
893
+ _plot_cropped_arrays(png_channels, file, figuresize, threshold=1)
876
894
 
877
895
  cells = np.unique(cell_mask)
878
896
  except Exception as e:
@@ -899,47 +917,17 @@ def measure_crop(settings):
899
917
  None
900
918
  """
901
919
 
902
- if settings.get('test_mode', False):
903
- if not os.basename(settings['src']) == 'test':
904
- src = os.path.join(src, 'test')
905
- settings['src'] = src
906
- print(f'Changed source folder to {src} for test mode')
907
- else:
908
- print(f'Test mode enabled, using source folder {settings["src"]}')
909
-
910
920
  from .io import _save_settings_to_db
911
921
  from .timelapse import _timelapse_masks_to_gif, _scmovie
912
922
  from .plot import _save_scimg_plot
913
- from .utils import _list_endpoint_subdirectories, _generate_representative_images
914
-
915
- #general settings
916
- settings['merge_edge_pathogen_cells'] = True
917
- settings['radial_dist'] = True
918
- settings['calculate_correlation'] = True
919
- settings['manders_thresholds'] = [15,85,95]
920
- settings['homogeneity'] = True
921
- settings['homogeneity_distances'] = [8,16,32]
922
- settings['save_arrays'] = False
923
-
924
- settings['dialate_pngs'] = False
925
- settings['dialate_png_ratios'] = [0.2]
926
- settings['timelapse'] = False
927
- settings['representative_images'] = False
928
- settings['timelapse_objects'] = 'cell'
929
- settings['max_workers'] = os.cpu_count()-2
930
- settings['experiment'] = 'test'
931
- settings['cells'] = 'HeLa'
932
- settings['cell_loc'] = None
933
- settings['pathogens'] = ['ME49Dku80WT', 'ME49Dku80dgra8:GRA8', 'ME49Dku80dgra8', 'ME49Dku80TKO']
934
- settings['pathogen_loc'] = [['c1', 'c2', 'c3', 'c4', 'c5', 'c6'], ['c7', 'c8', 'c9', 'c10', 'c11', 'c12'], ['c13', 'c14', 'c15', 'c16', 'c17', 'c18'], ['c19', 'c20', 'c21', 'c22', 'c23', 'c24']]
935
- settings['treatments'] = ['BR1', 'BR2', 'BR3']
936
- settings['treatment_loc'] = [['c1', 'c2', 'c7', 'c8', 'c13', 'c14', 'c19', 'c20'], ['c3', 'c4', 'c9', 'c10', 'c15', 'c16', 'c21', 'c22'], ['c5', 'c6', 'c11', 'c12', 'c17', 'c18', 'c23', 'c24']]
937
- settings['channel_of_interest'] = 2
938
- settings['compartments'] = ['pathogen', 'cytoplasm']
939
- settings['measurement'] = 'mean_intensity'
940
- settings['nr_imgs'] = 32
941
- settings['um_per_pixel'] = 0.1
942
- settings['center_crop'] = True
923
+ from .utils import _list_endpoint_subdirectories, _generate_representative_images, get_measure_crop_settings, measure_test_mode
924
+
925
+ settings = get_measure_crop_settings(settings)
926
+ settings = measure_test_mode(settings)
927
+
928
+ if not os.path.exists(settings['input_folder']):
929
+ print(f"Error: {settings['input_folder']} does not exist")
930
+ return
943
931
 
944
932
  if settings['cell_mask_dim'] is None:
945
933
  settings['include_uninfected'] = True
@@ -951,8 +939,6 @@ def measure_crop(settings):
951
939
  settings['cytoplasm'] = True
952
940
  else:
953
941
  settings['cytoplasm'] = False
954
-
955
- #settings = {**settings, **annotation_settings, **advanced_settings}
956
942
 
957
943
  dirname = os.path.dirname(settings['input_folder'])
958
944
  settings_df = pd.DataFrame(list(settings.items()), columns=['Key', 'Value'])
@@ -970,10 +956,11 @@ def measure_crop(settings):
970
956
  if isinstance(settings['normalize'], bool) and settings['normalize']:
971
957
  print(f'WARNING: to notmalize single object pngs set normalize to a list of 2 integers, e.g. [1,99] (lower and upper percentiles)')
972
958
  return
973
-
974
- if settings['normalize_by'] not in ['png', 'fov']:
975
- print("Warning: normalize_by should be either 'png' to notmalize each png to its own percentiles or 'fov' to normalize each png to the fov percentiles ")
976
- return
959
+
960
+ if isinstance(settings['normalize'], list) or isinstance(settings['normalize'], bool) and settings['normalize']:
961
+ if settings['normalize_by'] not in ['png', 'fov']:
962
+ print("Warning: normalize_by should be either 'png' to notmalize each png to its own percentiles or 'fov' to normalize each png to the fov percentiles ")
963
+ return
977
964
 
978
965
  if not all(isinstance(settings[key], int) or settings[key] is None for key in int_setting_keys):
979
966
  print(f"WARNING: {int_setting_keys} must all be integers")