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/__init__.py +4 -1
- spacr/__main__.py +0 -7
- spacr/annotate_app.py +75 -61
- spacr/core.py +39 -246
- spacr/foldseek.py +6 -6
- spacr/get_alfafold_structures.py +3 -3
- spacr/io.py +53 -116
- spacr/measure.py +46 -59
- spacr/plot.py +117 -81
- spacr/sequencing.py +508 -491
- spacr/sim.py +24 -29
- spacr/utils.py +487 -260
- {spacr-0.0.70.dist-info → spacr-0.0.80.dist-info}/METADATA +10 -8
- spacr-0.0.80.dist-info/RECORD +36 -0
- spacr/graph_learning_lap.py +0 -84
- spacr/train.py +0 -667
- spacr/umap.py +0 -0
- spacr-0.0.70.dist-info/RECORD +0 -39
- {spacr-0.0.70.dist-info → spacr-0.0.80.dist-info}/LICENSE +0 -0
- {spacr-0.0.70.dist-info → spacr-0.0.80.dist-info}/WHEEL +0 -0
- {spacr-0.0.70.dist-info → spacr-0.0.80.dist-info}/entry_points.txt +0 -0
- {spacr-0.0.70.dist-info → spacr-0.0.80.dist-info}/top_level.txt +0 -0
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
|
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
|
262
|
-
transform (callable, optional): A function/transform
|
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
|
268
|
-
transform (callable): A function/transform
|
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):
|
272
|
-
images (list):
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
411
|
+
|
413
412
|
def load_image(self, img_path):
|
414
413
|
img = Image.open(img_path).convert('RGB')
|
415
414
|
return img
|
416
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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,
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
916
|
-
settings
|
917
|
-
|
918
|
-
settings['
|
919
|
-
|
920
|
-
|
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['
|
975
|
-
|
976
|
-
|
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")
|