spacr 0.4.12__py3-none-any.whl → 0.4.60__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/utils.py CHANGED
@@ -78,7 +78,7 @@ def filepaths_to_database(img_paths, settings, source_folder, crop_mode):
78
78
 
79
79
  parts = png_df['file_name'].apply(lambda x: pd.Series(_map_wells_png(x, timelapse=settings['timelapse'])))
80
80
 
81
- columns = ['plate', 'row_name', 'column_name', 'field']
81
+ columns = ['plateID', 'rowID', 'columnID', 'fieldID']
82
82
 
83
83
  if settings['timelapse']:
84
84
  columns = columns + ['time_id']
@@ -113,7 +113,7 @@ def activation_maps_to_database(img_paths, source_folder, settings):
113
113
  png_df = pd.DataFrame(img_paths, columns=['png_path'])
114
114
  png_df['file_name'] = png_df['png_path'].apply(lambda x: os.path.basename(x))
115
115
  parts = png_df['file_name'].apply(lambda x: pd.Series(_map_wells_png(x, timelapse=False)))
116
- columns = ['plate', 'row_name', 'column_name', 'field', 'prcfo', 'object']
116
+ columns = ['plateID', 'rowID', 'columnID', 'fieldID', 'prcfo', 'object']
117
117
  png_df[columns] = parts
118
118
 
119
119
  dataset_name = os.path.splitext(os.path.basename(settings['dataset']))[0]
@@ -136,7 +136,7 @@ def activation_correlations_to_database(df, img_paths, source_folder, settings):
136
136
  png_df = pd.DataFrame(img_paths, columns=['png_path'])
137
137
  png_df['file_name'] = png_df['png_path'].apply(lambda x: os.path.basename(x))
138
138
  parts = png_df['file_name'].apply(lambda x: pd.Series(_map_wells_png(x, timelapse=False)))
139
- columns = ['plate', 'row_name', 'column_name', 'field', 'prcfo', 'object']
139
+ columns = ['plateID', 'rowID', 'columnID', 'fieldID', 'prcfo', 'object']
140
140
  png_df[columns] = parts
141
141
 
142
142
  # Align both DataFrames by file_name
@@ -319,23 +319,30 @@ def load_settings(csv_file_path, show=False, setting_key='setting_key', setting_
319
319
 
320
320
  return result_dict
321
321
 
322
-
323
322
  def save_settings(settings, name='settings', show=False):
324
323
 
325
- settings_df = pd.DataFrame(list(settings.items()), columns=['Key', 'Value'])
326
- if show:
327
- display(settings_df)
324
+ settings_2 = settings.copy()
328
325
 
329
- if isinstance(settings['src'], list):
330
- src = settings['src'][0]
331
- #if os.path.exists(src):
332
-
326
+ if isinstance(settings_2['src'], list):
327
+ src = settings_2['src'][0]
333
328
  name = f"{name}_list"
334
329
  else:
335
- src = settings['src']
330
+ src = settings_2['src']
331
+
332
+ if 'test_mode' in settings_2.keys():
333
+ settings_2['test_mode'] = False
334
+
335
+ if 'plot' in settings_2.keys():
336
+ settings_2['plot'] = False
337
+
338
+ settings_df = pd.DataFrame(list(settings_2.items()), columns=['Key', 'Value'])
339
+
340
+ if show:
341
+ display(settings_df)
336
342
 
337
343
  settings_csv = os.path.join(src,'settings',f'{name}.csv')
338
344
  os.makedirs(os.path.join(src,'settings'), exist_ok=True)
345
+ print(f"Saving settings to {settings_csv}")
339
346
  settings_df.to_csv(settings_csv, index=False)
340
347
 
341
348
  def print_progress(files_processed, files_to_process, n_jobs, time_ls=None, batch_size=None, operation_type=""):
@@ -550,7 +557,7 @@ def _get_cellpose_batch_size():
550
557
  def _extract_filename_metadata(filenames, src, regular_expression, metadata_type='cellvoyager', pick_slice=False, skip_mode='01'):
551
558
 
552
559
  images_by_key = defaultdict(list)
553
-
560
+
554
561
  for filename in filenames:
555
562
  match = regular_expression.match(filename)
556
563
  if match:
@@ -593,7 +600,7 @@ def _extract_filename_metadata(filenames, src, regular_expression, metadata_type
593
600
  except IndexError:
594
601
  print(f"Could not extract information from filename {filename} using provided regex")
595
602
  else:
596
- print(f"Filename {filename} did not match provided regex")
603
+ print(f"Filename {filename} did not match provided regex: {regular_expression}")
597
604
  continue
598
605
 
599
606
  return images_by_key
@@ -635,11 +642,11 @@ def _update_database_with_merged_info(db_path, df, table='png_list', columns=['p
635
642
  if 'prcfo' not in df.columns:
636
643
  print(f'generating prcfo columns')
637
644
  try:
638
- df['prcfo'] = df['plate'].astype(str) + '_' + df['row_name'].astype(str) + '_' + df['column_name'].astype(str) + '_' + df['field'].astype(str) + '_o' + df['object_label'].astype(int).astype(str)
645
+ df['prcfo'] = df['plateID'].astype(str) + '_' + df['rowID'].astype(str) + '_' + df['columnID'].astype(str) + '_' + df['fieldID'].astype(str) + '_o' + df['object_label'].astype(int).astype(str)
639
646
  except Exception as e:
640
647
  print('Merging on cell failed, trying with cell_id')
641
648
  try:
642
- df['prcfo'] = df['plate'].astype(str) + '_' + df['row_name'].astype(str) + '_' + df['column_name'].astype(str) + '_' + df['field'].astype(str) + '_o' + df['cell_id'].astype(int).astype(str)
649
+ df['prcfo'] = df['plateID'].astype(str) + '_' + df['rowID'].astype(str) + '_' + df['columnID'].astype(str) + '_' + df['fieldID'].astype(str) + '_o' + df['cell_id'].astype(int).astype(str)
643
650
  except Exception as e:
644
651
  print(e)
645
652
 
@@ -731,7 +738,7 @@ def _map_values(row, values, locs):
731
738
  if locs:
732
739
  value_dict = {loc: value for value, loc_list in zip(values, locs) for loc in loc_list}
733
740
  # Determine if we're dealing with row or column based on first location identifier
734
- type_ = 'row_name' if locs[0][0][0] == 'r' else 'column_name'
741
+ type_ = 'rowID' if locs[0][0][0] == 'r' else 'columnID'
735
742
  return value_dict.get(row[type_], None)
736
743
  return values[0] if values else None
737
744
 
@@ -916,21 +923,21 @@ def _merge_and_save_to_database(morph_df, intensity_df, table_type, source_folde
916
923
  merged_df['file_name'] = file_name
917
924
  merged_df['path_name'] = os.path.join(source_folder, file_name + '.npy')
918
925
  if timelapse:
919
- merged_df[['plate', 'row_name', 'column_name', 'field', 'timeid', 'prcf']] = merged_df['file_name'].apply(lambda x: pd.Series(_map_wells(x, timelapse)))
926
+ merged_df[['plateID', 'rowID', 'columnID', 'fieldID', 'timeid', 'prcf']] = merged_df['file_name'].apply(lambda x: pd.Series(_map_wells(x, timelapse)))
920
927
  else:
921
- merged_df[['plate', 'row_name', 'column_name', 'field', 'prcf']] = merged_df['file_name'].apply(lambda x: pd.Series(_map_wells(x, timelapse)))
928
+ merged_df[['plateID', 'rowID', 'columnID', 'fieldID', 'prcf']] = merged_df['file_name'].apply(lambda x: pd.Series(_map_wells(x, timelapse)))
922
929
  cols = merged_df.columns.tolist() # get the list of all columns
923
930
  if table_type == 'cell' or table_type == 'cytoplasm':
924
- column_list = ['object_label', 'plate', 'row_name', 'column_name', 'field', 'prcf', 'file_name', 'path_name']
931
+ column_list = ['object_label', 'plateID', 'rowID', 'columnID', 'fieldID', 'prcf', 'file_name', 'path_name']
925
932
  elif table_type == 'nucleus' or table_type == 'pathogen':
926
- column_list = ['object_label', 'cell_id', 'plate', 'row_name', 'column_name', 'field', 'prcf', 'file_name', 'path_name']
933
+ column_list = ['object_label', 'cell_id', 'plateID', 'rowID', 'columnID', 'fieldID', 'prcf', 'file_name', 'path_name']
927
934
  else:
928
935
  raise ValueError(f"Invalid table_type: {table_type}")
929
936
  # Check if all columns in column_list are in cols
930
937
  missing_columns = [col for col in column_list if col not in cols]
931
938
  if len(missing_columns) == 1 and missing_columns[0] == 'cell_id':
932
939
  missing_columns = False
933
- column_list = ['object_label', 'plate', 'row_name', 'column_name', 'field', 'prcf', 'file_name', 'path_name']
940
+ column_list = ['object_label', 'plateID', 'rowID', 'columnID', 'fieldID', 'prcf', 'file_name', 'path_name']
934
941
  if missing_columns:
935
942
  raise ValueError(f"Columns missing in DataFrame: {missing_columns}")
936
943
  for i, col in enumerate(column_list):
@@ -1144,42 +1151,22 @@ def _masks_to_masks_stack(masks):
1144
1151
  for idx, mask in enumerate(masks):
1145
1152
  mask_stack.append(mask)
1146
1153
  return mask_stack
1147
-
1154
+
1148
1155
  def _get_diam(mag, obj):
1149
1156
 
1150
- if mag == 20:
1151
- if obj == 'cell':
1152
- diamiter = 120
1153
- elif obj == 'nucleus':
1154
- diamiter = 60
1155
- elif obj == 'pathogen':
1156
- diamiter = 20
1157
- else:
1158
- raise ValueError("Invalid magnification: Use 20, 40 or 60")
1159
-
1160
- elif mag == 40:
1161
- if obj == 'cell':
1162
- diamiter = 160
1163
- elif obj == 'nucleus':
1164
- diamiter = 80
1165
- elif obj == 'pathogen':
1166
- diamiter = 40
1167
- else:
1168
- raise ValueError("Invalid magnification: Use 20, 40 or 60")
1169
-
1170
- elif mag == 60:
1171
- if obj == 'cell':
1172
- diamiter = 200
1173
- if obj == 'nucleus':
1174
- diamiter = 90
1175
- if obj == 'pathogen':
1176
- diamiter = 60
1177
- else:
1178
- raise ValueError("Invalid magnification: Use 20, 40 or 60")
1179
- else:
1180
- raise ValueError("Invalid magnification: Use 20, 40 or 60")
1181
-
1182
- return diamiter
1157
+ if obj == 'cell':
1158
+ diamiter = 2 * mag + 80
1159
+
1160
+ if obj == 'cell_large':
1161
+ diamiter = 2 * mag + 120
1162
+
1163
+ if obj == 'nucleus':
1164
+ diamiter = 0.75 * mag + 45
1165
+
1166
+ if obj == 'pathogen':
1167
+ diamiter = mag
1168
+
1169
+ return int(diamiter)
1183
1170
 
1184
1171
  def _get_object_settings(object_type, settings):
1185
1172
  object_settings = {}
@@ -1323,30 +1310,35 @@ def annotate_conditions(df, cells=None, cell_loc=None, pathogens=None, pathogen_
1323
1310
  """
1324
1311
 
1325
1312
  def _get_type(val):
1326
- """Determine if a value maps to 'row_name' or 'column_name'."""
1313
+ """Determine if a value maps to 'rowID' or 'columnID'."""
1327
1314
  if isinstance(val, str) and val.startswith('c'):
1328
- return 'column_name'
1315
+ return 'columnID'
1329
1316
  elif isinstance(val, str) and val.startswith('r'):
1330
- return 'row_name'
1317
+ return 'rowID'
1331
1318
  return None
1332
1319
 
1333
1320
  def _map_or_default(column_name, values, loc, df):
1334
1321
  """
1335
1322
  Consolidates the logic for mapping values or assigning defaults when loc is None.
1336
-
1323
+
1337
1324
  Args:
1338
1325
  column_name (str): The column in the DataFrame to annotate.
1339
1326
  values (list/str): The list of values or a single string to annotate.
1340
1327
  loc (list of lists): Location mapping for the values, or None if not used.
1341
1328
  df (pandas.DataFrame): The DataFrame to modify.
1342
1329
  """
1343
- if isinstance(values, str) or (isinstance(values, list) and loc is None):
1344
- # Assign all rows the first value in the list or the single string
1345
- df[column_name] = values if isinstance(values, str) else values[0]
1330
+ if isinstance(values, str) and loc is None:
1331
+ # If a single string is provided and loc is None, assign the value to all rows
1332
+ df[column_name] = values
1333
+
1334
+ elif isinstance(values, list) and loc is None:
1335
+ # If a list of values is provided but no loc, assign the first value to all rows
1336
+ df[column_name] = values[0]
1337
+
1346
1338
  elif values is not None and loc is not None:
1347
- # Perform the location-based mapping
1339
+ # Perform location-based mapping
1348
1340
  value_dict = {val: key for key, loc_list in zip(values, loc) for val in loc_list}
1349
- df[column_name] = np.nan
1341
+ df[column_name] = np.nan # Start with NaN
1350
1342
  for val, key in value_dict.items():
1351
1343
  loc_type = _get_type(val)
1352
1344
  if loc_type:
@@ -1390,7 +1382,7 @@ def _split_data(df, group_by, object_type):
1390
1382
  # Ensure 'prcf' column exists by concatenating specific columns
1391
1383
  if 'prcf' not in df.columns:
1392
1384
  try:
1393
- df['prcf'] = df['plate'].astype(str) + '_' + df['row_name'].astype(str) + '_' + df['column_name'].astype(str) + '_' + df['field'].astype(str)
1385
+ df['prcf'] = df['plateID'].astype(str) + '_' + df['rowID'].astype(str) + '_' + df['columnID'].astype(str) + '_' + df['fieldID'].astype(str)
1394
1386
  except Exception as e:
1395
1387
  print(e)
1396
1388
 
@@ -1487,7 +1479,7 @@ def _group_by_well(df):
1487
1479
  non_numeric_cols = df.select_dtypes(include=['object']).columns
1488
1480
 
1489
1481
  # Apply mean function to numeric columns and first to non-numeric
1490
- df_grouped = df.groupby(['plate', 'row_name', 'column_name']).agg({**{col: np.mean for col in numeric_cols}, **{col: 'first' for col in non_numeric_cols}})
1482
+ df_grouped = df.groupby(['plateID', 'rowID', 'columnID']).agg({**{col: np.mean for col in numeric_cols}, **{col: 'first' for col in non_numeric_cols}})
1491
1483
  return df_grouped
1492
1484
 
1493
1485
  ###################################################
@@ -2166,11 +2158,11 @@ def augment_classes(dst, nc, pc, generate=True,move=True):
2166
2158
  def annotate_predictions(csv_loc):
2167
2159
  df = pd.read_csv(csv_loc)
2168
2160
  df['filename'] = df['path'].apply(lambda x: x.split('/')[-1])
2169
- df[['plate', 'well', 'field', 'object']] = df['filename'].str.split('_', expand=True)
2161
+ df[['plateID', 'well', 'fieldID', 'object']] = df['filename'].str.split('_', expand=True)
2170
2162
  df['object'] = df['object'].str.replace('.png', '')
2171
2163
 
2172
2164
  def assign_condition(row):
2173
- plate = int(row['plate'])
2165
+ plate = int(row['plateID'])
2174
2166
  col = int(row['well'][1:])
2175
2167
 
2176
2168
  if col > 3:
@@ -2321,7 +2313,7 @@ def check_multicollinearity(x):
2321
2313
 
2322
2314
  def lasso_reg(merged_df, alpha_value=0.01, reg_type='lasso'):
2323
2315
  # Separate predictors and response
2324
- X = merged_df[['gene', 'grna', 'plate', 'row_name', 'column']]
2316
+ X = merged_df[['gene', 'grna', 'plateID', 'rowID', 'columnID']]
2325
2317
  y = merged_df['pred']
2326
2318
 
2327
2319
  # One-hot encode the categorical predictors
@@ -3099,12 +3091,8 @@ def _get_regex(metadata_type, img_format, custom_regex=None):
3099
3091
  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}'
3100
3092
  elif metadata_type == 'cq1':
3101
3093
  regex = f'W(?P<wellID>.*)F(?P<fieldID>.*)T(?P<timeID>.*)Z(?P<sliceID>.*)C(?P<chanID>.*){img_format}'
3102
- elif metadata_type == 'nikon':
3103
- 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}'
3104
- elif metadata_type == 'zeis':
3105
- 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}'
3106
- elif metadata_type == 'leica':
3107
- 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}'
3094
+ elif metadata_type == 'auto':
3095
+ regex = f'(?P<plateID>.*)_(?P<wellID>.*)_T(?P<timeID>.*)F(?P<fieldID>.*)L(?P<laserID>.*)C(?P<chanID>.*).tif'
3108
3096
  elif metadata_type == 'custom':
3109
3097
  regex = f'({custom_regex}){img_format}'
3110
3098
 
@@ -3122,7 +3110,7 @@ def _run_test_mode(src, regex, timelapse=False, test_images=10, random_test=True
3122
3110
 
3123
3111
  if os.path.exists(os.path.join(src, 'orig')):
3124
3112
  src = os.path.join(src, 'orig')
3125
-
3113
+
3126
3114
  all_filenames = [filename for filename in os.listdir(src) if regular_expression.match(filename)]
3127
3115
  print(f'Found {len(all_filenames)} files')
3128
3116
  images_by_set = defaultdict(list)
@@ -3164,7 +3152,6 @@ def _choose_model(model_name, device, object_type='cell', restore_type=None, obj
3164
3152
  model_path = os.path.join(current_dir, 'models', 'cp', 'toxo_pv_lumen.CP_model')
3165
3153
  print(model_path)
3166
3154
  model = cp_models.CellposeModel(gpu=torch.cuda.is_available(), model_type=None, pretrained_model=model_path, diam_mean=diameter, device=device)
3167
- #model = cp_models.Cellpose(gpu=torch.cuda.is_available(), model_type='cyto', device=device)
3168
3155
  print(f'Using Toxoplasma PV lumen model to generate pathogen masks')
3169
3156
  return model
3170
3157
 
@@ -4693,12 +4680,12 @@ def process_vision_results(df, threshold=0.5):
4693
4680
  # Split the 'path' column using _map_wells function
4694
4681
  mapped_values = df['path'].apply(lambda x: _map_wells(x))
4695
4682
 
4696
- df['plate'] = mapped_values.apply(lambda x: x[0])
4697
- df['row_name'] = mapped_values.apply(lambda x: x[1])
4698
- df['column'] = mapped_values.apply(lambda x: x[2])
4699
- df['field'] = mapped_values.apply(lambda x: x[3])
4683
+ df['plateID'] = mapped_values.apply(lambda x: x[0])
4684
+ df['rowID'] = mapped_values.apply(lambda x: x[1])
4685
+ df['columnID'] = mapped_values.apply(lambda x: x[2])
4686
+ df['fieldID'] = mapped_values.apply(lambda x: x[3])
4700
4687
  df['object'] = df['path'].str.split('_').str[3].str.split('.').str[0]
4701
- df['prc'] = df['plate'].astype(str) + '_' + df['row_name'].astype(str) + '_' + df['column'].astype(str)
4688
+ df['prc'] = df['plateID'].astype(str) + '_' + df['rowID'].astype(str) + '_' + df['columnID'].astype(str)
4702
4689
  df['cv_predictions'] = (df['pred'] >= threshold).astype(int)
4703
4690
 
4704
4691
  return df
@@ -4945,7 +4932,7 @@ def download_models(repo_id="einarolafsson/models", retries=5, delay=5):
4945
4932
  if not os.path.exists(local_dir):
4946
4933
  os.makedirs(local_dir)
4947
4934
  elif len(os.listdir(local_dir)) > 0:
4948
- print(f"Models already downloaded to: {local_dir}")
4935
+ #print(f"Models already downloaded to: {local_dir}")
4949
4936
  return local_dir
4950
4937
 
4951
4938
  attempt = 0
@@ -5113,24 +5100,24 @@ def fill_holes_in_mask(mask):
5113
5100
 
5114
5101
  def correct_metadata_column_names(df):
5115
5102
  if 'plate_name' in df.columns:
5116
- df = df.rename(columns={'plate_name': 'plate'})
5103
+ df = df.rename(columns={'plate_name': 'plateID'})
5117
5104
  if 'column_name' in df.columns:
5118
- df = df.rename(columns={'column_name': 'column'})
5105
+ df = df.rename(columns={'column_name': 'columnID'})
5119
5106
  if 'col' in df.columns:
5120
- df = df.rename(columns={'col': 'column'})
5107
+ df = df.rename(columns={'col': 'columnID'})
5121
5108
  if 'row_name' in df.columns:
5122
- df = df.rename(columns={'row_name': 'row_name'})
5109
+ df = df.rename(columns={'row_name': 'rowID'})
5123
5110
  if 'grna_name' in df.columns:
5124
5111
  df = df.rename(columns={'grna_name': 'grna'})
5125
5112
  if 'plate_row' in df.columns:
5126
- df[['plate', 'row_name']] = df['plate_row'].str.split('_', expand=True)
5113
+ df[['plateID', 'rowID']] = df['plate_row'].str.split('_', expand=True)
5127
5114
  return df
5128
5115
 
5129
- def control_filelist(folder, mode='column', values=['01','02']):
5116
+ def control_filelist(folder, mode='columnID', values=['01','02']):
5130
5117
  files = os.listdir(folder)
5131
- if mode is 'column':
5118
+ if mode is 'columnID':
5132
5119
  filtered_files = [file for file in files if file.split('_')[1][1:] in values]
5133
- if mode is 'row_name':
5120
+ if mode is 'rowID':
5134
5121
  filtered_files = [file for file in files if file.split('_')[1][:1] in values]
5135
5122
  return filtered_files
5136
5123
 
@@ -5148,12 +5135,12 @@ def rename_columns_in_db(db_path):
5148
5135
  columns_info = cursor.fetchall()
5149
5136
  column_names = [col[1] for col in columns_info]
5150
5137
 
5151
- # Check if columns 'row' or 'col' exist
5138
+ # Check if columns 'rowID' or 'columnID' exist
5152
5139
  columns_to_rename = {}
5153
5140
  if 'row' in column_names:
5154
- columns_to_rename['row'] = 'row_name'
5141
+ columns_to_rename['row'] = 'rowID'
5155
5142
  if 'col' in column_names:
5156
- columns_to_rename['col'] = 'column_name'
5143
+ columns_to_rename['col'] = 'columnID'
5157
5144
 
5158
5145
  # Rename columns if necessary
5159
5146
  if columns_to_rename:
@@ -5337,3 +5324,170 @@ def calculate_shortest_distance(df, object1, object2):
5337
5324
  df[f'{object1}_{object2}_shortest_distance'] = np.maximum(shortest_distance, 0)
5338
5325
 
5339
5326
  return df
5327
+
5328
+ def format_path_for_system(path):
5329
+ """
5330
+ Takes a file path and reformats it to be compatible with the current operating system.
5331
+
5332
+ Args:
5333
+ path (str): The file path to be formatted.
5334
+
5335
+ Returns:
5336
+ str: The formatted path for the current operating system.
5337
+ """
5338
+ system = platform.system()
5339
+
5340
+ # Convert Windows-style paths to Unix-style (Linux/macOS)
5341
+ if system in ["Linux", "Darwin"]: # Darwin is macOS
5342
+ formatted_path = path.replace("\\", "/")
5343
+
5344
+ # Convert Unix-style paths to Windows-style
5345
+ elif system == "Windows":
5346
+ formatted_path = path.replace("/", "\\")
5347
+
5348
+ else:
5349
+ raise ValueError(f"Unsupported OS: {system}")
5350
+
5351
+ # Normalize path to ensure consistency
5352
+ new_path = os.path.normpath(formatted_path)
5353
+ if os.path.exists(new_path):
5354
+ print(f"Found path: {new_path}")
5355
+ else:
5356
+ print(f"Path not found: {new_path}")
5357
+
5358
+ return new_path
5359
+
5360
+
5361
+ def normalize_src_path(src):
5362
+ """
5363
+ Ensures that the 'src' value is properly formatted as either a list of strings or a single string.
5364
+
5365
+ Args:
5366
+ src (str or list): The input source path(s).
5367
+
5368
+ Returns:
5369
+ list or str: A correctly formatted list if the input was a list (or string representation of a list),
5370
+ otherwise a single string.
5371
+ """
5372
+ if isinstance(src, list):
5373
+ return src # Already a list, return as-is
5374
+
5375
+ if isinstance(src, str):
5376
+ try:
5377
+ # Check if it is a string representation of a list
5378
+ evaluated_src = ast.literal_eval(src)
5379
+ if isinstance(evaluated_src, list) and all(isinstance(item, str) for item in evaluated_src):
5380
+ return evaluated_src # Convert to real list
5381
+ except (SyntaxError, ValueError):
5382
+ pass # Not a valid list, treat as a string
5383
+
5384
+ return src # Return as a string if not a list
5385
+
5386
+ raise ValueError(f"Invalid type for 'src': {type(src).__name__}, expected str or list")
5387
+
5388
+ def generate_image_path_map(root_folder, valid_extensions=("tif", "tiff", "png", "jpg", "jpeg", "bmp", "czi", "nd2", "lif")):
5389
+ """
5390
+ Recursively scans a folder and its subfolders for images, then creates a mapping of:
5391
+ {original_image_path: new_image_path}, where the new path includes all subfolder names.
5392
+
5393
+ Args:
5394
+ root_folder (str): The root directory to scan for images.
5395
+ valid_extensions (tuple): Tuple of valid image file extensions.
5396
+
5397
+ Returns:
5398
+ dict: A dictionary mapping original image paths to their new paths.
5399
+ """
5400
+ image_path_map = {}
5401
+
5402
+ for dirpath, _, filenames in os.walk(root_folder):
5403
+ for file in filenames:
5404
+ ext = file.lower().split('.')[-1]
5405
+ if ext in valid_extensions:
5406
+ # Get relative path of the image from root_folder
5407
+ relative_path = os.path.relpath(dirpath, root_folder)
5408
+
5409
+ # Construct new filename: Embed folder hierarchy into the name
5410
+ folder_parts = relative_path.split(os.sep) # Get all folder names
5411
+ folder_info = "_".join(folder_parts) if folder_parts else "" # Join with underscores
5412
+
5413
+ # Generate new filename
5414
+ new_filename = f"{folder_info}_{file}" if folder_info else file
5415
+
5416
+ # Store in dictionary (original path -> new path)
5417
+ original_path = os.path.join(dirpath, file)
5418
+ new_path = os.path.join(root_folder, new_filename)
5419
+ image_path_map[original_path] = new_path
5420
+
5421
+ return image_path_map
5422
+
5423
+ def copy_images_to_consolidated(image_path_map, root_folder):
5424
+ """
5425
+ Copies images from their original locations to a 'consolidated' folder,
5426
+ renaming them according to the generated dictionary.
5427
+
5428
+ Args:
5429
+ image_path_map (dict): Dictionary mapping {original_path: new_path}.
5430
+ root_folder (str): The root directory where the 'consolidated' folder will be created.
5431
+ """
5432
+
5433
+ consolidated_folder = os.path.join(root_folder, "consolidated")
5434
+ os.makedirs(consolidated_folder, exist_ok=True) # Ensure 'consolidated' folder exists
5435
+ files_processed = 0
5436
+ files_to_process = len(image_path_map)
5437
+ time_ls= []
5438
+
5439
+ for original_path, new_path in image_path_map.items():
5440
+
5441
+ start = time.time()
5442
+ new_filename = os.path.basename(new_path) # Extract only the new filename
5443
+ new_file_path = os.path.join(consolidated_folder, new_filename) # Place in 'consolidated' folder
5444
+
5445
+ shutil.copy2(original_path, new_file_path) # Copy file with metadata preserved
5446
+
5447
+ files_processed += 1
5448
+ stop = time.time()
5449
+ duration = (stop - start)
5450
+ time_ls.append(duration)
5451
+
5452
+ print_progress(files_processed, files_to_process, n_jobs=1, time_ls=time_ls, batch_size=None, operation_type=f'Consolidating images')
5453
+ #print(f"Copied: {original_path} -> {new_file_path}")
5454
+
5455
+ def correct_metadata(df):
5456
+
5457
+ #if 'object' in df.columns:
5458
+ # df['objectID'] = df['object']
5459
+
5460
+ if 'object_name' in df.columns:
5461
+ df['objectID'] = df['object_name']
5462
+
5463
+ if 'field_name' in df.columns:
5464
+ df['fieldID'] = df['field_name']
5465
+
5466
+ if 'plate' in df.columns:
5467
+ df['plateID'] = df['plate']
5468
+
5469
+ if 'plate_name' in df.columns:
5470
+ df['plateID'] = df['plate_name']
5471
+
5472
+ if 'row' in df.columns:
5473
+ df = df.rename(columns={'row': 'rowID'})
5474
+
5475
+ if 'row_name' in df.columns:
5476
+ df = df.rename(columns={'row_name': 'rowID'})
5477
+
5478
+ if 'col' in df.columns:
5479
+ df = df.rename(columns={'col': 'columnID'})
5480
+
5481
+ if 'column' in df.columns:
5482
+ df = df.rename(columns={'column': 'columnID'})
5483
+
5484
+ if 'column_name' in df.columns:
5485
+ df = df.rename(columns={'column_name': 'columnID'})
5486
+
5487
+ if 'field' in df.columns:
5488
+ df = df.rename(columns={'field': 'fieldID'})
5489
+
5490
+ if 'field_name' in df.columns:
5491
+ df = df.rename(columns={'field_name': 'fieldID'})
5492
+
5493
+ return df
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: spacr
3
- Version: 0.4.12
3
+ Version: 0.4.60
4
4
  Summary: Spatial phenotype analysis of crisp screens (SpaCr)
5
5
  Home-page: https://github.com/EinarOlafsson/spacr
6
6
  Author: Einar Birnir Olafsson
@@ -41,6 +41,7 @@ Requires-Dist: pillow<11.0,>=10.2.0
41
41
  Requires-Dist: tifffile>=2023.4.12
42
42
  Requires-Dist: nd2reader<4.0,>=3.3.0
43
43
  Requires-Dist: czifile
44
+ Requires-Dist: readlif
44
45
  Requires-Dist: imageio<3.0,>=2.34.0
45
46
  Requires-Dist: pingouin<1.0,>=0.5.5
46
47
  Requires-Dist: umap-learn<1.0,>=0.5.6
@@ -9,28 +9,28 @@ spacr/app_sequencing.py,sha256=DjG26jy4cpddnV8WOOAIiExtOe9MleVMY4MFa5uTo5w,157
9
9
  spacr/app_umap.py,sha256=ZWAmf_OsIKbYvolYuWPMYhdlVe-n2CADoJulAizMiEo,153
10
10
  spacr/cellpose.py,sha256=RBHMs2vwXcfkj0xqAULpALyzJYXddSRycgZSzmwI7v0,14755
11
11
  spacr/chat_bot.py,sha256=n3Fhqg3qofVXHmh3H9sUcmfYy9MmgRnr48663MVdY9E,1244
12
- spacr/core.py,sha256=lKeqmsVrGQ8cPU_WkoNGNBWrk-gtR1RkRkwDdnJ0u64,48829
13
- spacr/deep_spacr.py,sha256=WN64EaQqF87JZg3Uan46t5Y28xsAGD2KMjr2ht6CyDs,54563
12
+ spacr/core.py,sha256=ghesCiRKxdHqnujztb9XiuhhZ-3gBs7aQIIzvqaf-iI,51409
13
+ spacr/deep_spacr.py,sha256=055tIo3WP3elGFiIuSZaLURgu2XyUDxAdbw5ezASEqM,54526
14
14
  spacr/gui.py,sha256=ARyn9Q_g8HoP-cXh1nzMLVFCKqthY4v2u9yORyaQqQE,8230
15
- spacr/gui_core.py,sha256=U0A7waKgWq_Es9fMwcZbXUZYGzCqt2bgfY3HbxiFXnw,47466
16
- spacr/gui_elements.py,sha256=HmITDncklKwtdFhxLhtYXOwndsRfgwWIPVi83VlXHB4,146419
17
- spacr/gui_utils.py,sha256=0rDF23BUGcmjSJvfCiLoxhlGJdHkio1jTxyCzrMXr-g,41211
18
- spacr/io.py,sha256=oqJwDJWksVdWE0bRAwytTOsjlL0o-J9lr_pQaw2cQ4Y,138288
15
+ spacr/gui_core.py,sha256=AYXZpdKMRezJfS7Xcfztdc2yco6lV21ovwkyKQGbIZg,56206
16
+ spacr/gui_elements.py,sha256=5a3BOpctBPklsT1NungqS72h1Bg1FArUndE0OfvWD8Y,152646
17
+ spacr/gui_utils.py,sha256=dWVPFwDj793Z3ERG4mMC0hI0MKkOrvXJpUYlcjpCBsU,41357
18
+ spacr/io.py,sha256=eZj5RCqje3xb_4BwPC0ASBqI-c7_6EeQYv8zrQf0tCc,159371
19
19
  spacr/logger.py,sha256=lJhTqt-_wfAunCPl93xE65Wr9Y1oIHJWaZMjunHUeIw,1538
20
- spacr/measure.py,sha256=jmOnLBudq3TuY723Cfo1EJBn67P6rlEvL6I-2FSkUgI,55315
20
+ spacr/measure.py,sha256=Z3u4BU5RzcY82IZuboQ0OsxuXaPVwOlH65Rw6FrL5z4,55045
21
21
  spacr/mediar.py,sha256=FwLvbLQW5LQzPgvJZG8Lw7GniA2vbZx6Jv6vIKu7I5c,14743
22
- spacr/ml.py,sha256=MrIAtUUxMOibWVL1SjCUnYlizawCp3l3SeY4Y9yEsPw,97251
22
+ spacr/ml.py,sha256=XCRZeX7UkbMctQICIoskeWVx8CCmmCoHNauUOAkfFq0,91692
23
23
  spacr/openai.py,sha256=5vBZ3Jl2llYcW3oaTEXgdyCB2aJujMUIO5K038z7w_A,1246
24
- spacr/plot.py,sha256=Q5TbsR2NUWhA7z4HyF_2_FAEBFSNMU-G3UNDbRzW6mM,169485
25
- spacr/sequencing.py,sha256=ClUfwPPK6rNUbUuiEkzcwakzVyDKKUMv9ricrxT8qQY,25227
26
- spacr/settings.py,sha256=fEk-9LSSvV1wGsn6xTaJWY7wF7_u8Fc-S1DaDHqZU3I,83997
24
+ spacr/plot.py,sha256=lmpIJozfxMyOyGAMno4j-C7NPjDYRMr29SDAnrV-bb4,170815
25
+ spacr/sequencing.py,sha256=EY12RdW5QRKpHDRQCw1QoAlxCq8FK2v6WoVa5uuDBXQ,26745
26
+ spacr/settings.py,sha256=j2H5OieJKwu-TITlfP5tAnUGUhBjng8ECy3jXm3JMb8,86599
27
27
  spacr/sim.py,sha256=1xKhXimNU3ukzIw-3l9cF3Znc_brW8h20yv8fSTzvss,71173
28
28
  spacr/sp_stats.py,sha256=mbhwsyIqt5upsSD346qGjdCw7CFBa0tIS7zHU9e0jNI,9536
29
29
  spacr/stats.py,sha256=mbhwsyIqt5upsSD346qGjdCw7CFBa0tIS7zHU9e0jNI,9536
30
- spacr/submodules.py,sha256=mb2g0igUTws7y6xW1zIJw1E7eQyxsjEj5mk2Z-Qd8uw,67629
31
- spacr/timelapse.py,sha256=KGfG4L4-QnFfgbF7L6C5wL_3gd_rqr05Foje6RsoTBg,39603
32
- spacr/toxo.py,sha256=TmuhejSIPLBvsgeblsUgSvBFCR1gOkApyTKidooJ5Us,26044
33
- spacr/utils.py,sha256=of2t5Tq_RKdJ1QRDo4nJ3oEVev_6s2Oko3-lBxl4ScU,226293
30
+ spacr/submodules.py,sha256=dNiUqwDYwJ2J1s3DkwGLOTB-TzyGq_GXoojpCrLMm7A,82812
31
+ spacr/timelapse.py,sha256=lh3Aev5S7Ou1YWPBYBYeSGU0I-NPb0-4znYfm2NYf_I,39629
32
+ spacr/toxo.py,sha256=GoNfgyH-NJx3WOzNQPgzODir7Jp65fs7UM46XpzcrUo,26056
33
+ spacr/utils.py,sha256=20FRbevoFmPA61vS50tR48g6DpGkIvInGPv9nE810gQ,231668
34
34
  spacr/version.py,sha256=axH5tnGwtgSnJHb5IDhiu4Zjk5GhLyAEDRe-rnaoFOA,409
35
35
  spacr/resources/MEDIAR/.gitignore,sha256=Ff1q9Nme14JUd-4Q3jZ65aeQ5X4uttptssVDgBVHYo8,152
36
36
  spacr/resources/MEDIAR/LICENSE,sha256=yEj_TRDLUfDpHDNM0StALXIt6mLqSgaV2hcCwa6_TcY,1065
@@ -153,9 +153,9 @@ spacr/resources/icons/umap.png,sha256=dOLF3DeLYy9k0nkUybiZMe1wzHQwLJFRmgccppw-8b
153
153
  spacr/resources/images/plate1_E01_T0001F001L01A01Z01C02.tif,sha256=Tl0ZUfZ_AYAbu0up_nO0tPRtF1BxXhWQ3T3pURBCCRo,7958528
154
154
  spacr/resources/images/plate1_E01_T0001F001L01A02Z01C01.tif,sha256=m8N-V71rA1TT4dFlENNg8s0Q0YEXXs8slIn7yObmZJQ,7958528
155
155
  spacr/resources/images/plate1_E01_T0001F001L01A03Z01C03.tif,sha256=Pbhk7xn-KUP6RSIhJsxQcrHFImBm3GEpLkzx7WOc-5M,7958528
156
- spacr-0.4.12.dist-info/LICENSE,sha256=SR-2MeGc6SCM1UORJYyarSWY_A-JaOMFDj7ReSs9tRM,1083
157
- spacr-0.4.12.dist-info/METADATA,sha256=mSEI7oQQ8wHvVWQwzELtk2bpV_Bil3OhhteOJ3SoY68,6073
158
- spacr-0.4.12.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
159
- spacr-0.4.12.dist-info/entry_points.txt,sha256=BMC0ql9aNNpv8lUZ8sgDLQMsqaVnX5L535gEhKUP5ho,296
160
- spacr-0.4.12.dist-info/top_level.txt,sha256=GJPU8FgwRXGzKeut6JopsSRY2R8T3i9lDgya42tLInY,6
161
- spacr-0.4.12.dist-info/RECORD,,
156
+ spacr-0.4.60.dist-info/LICENSE,sha256=SR-2MeGc6SCM1UORJYyarSWY_A-JaOMFDj7ReSs9tRM,1083
157
+ spacr-0.4.60.dist-info/METADATA,sha256=Na4GGSJEuobmjgAo8HSbejQ0h8E1etVHb249v5oBB6Y,6096
158
+ spacr-0.4.60.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
159
+ spacr-0.4.60.dist-info/entry_points.txt,sha256=BMC0ql9aNNpv8lUZ8sgDLQMsqaVnX5L535gEhKUP5ho,296
160
+ spacr-0.4.60.dist-info/top_level.txt,sha256=GJPU8FgwRXGzKeut6JopsSRY2R8T3i9lDgya42tLInY,6
161
+ spacr-0.4.60.dist-info/RECORD,,
File without changes