celldetective 1.4.0__py3-none-any.whl → 1.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. celldetective/_version.py +1 -1
  2. celldetective/exceptions.py +11 -0
  3. celldetective/filters.py +7 -1
  4. celldetective/gui/InitWindow.py +4 -1
  5. celldetective/gui/__init__.py +2 -9
  6. celldetective/gui/about.py +2 -2
  7. celldetective/gui/base_annotator.py +786 -0
  8. celldetective/gui/classifier_widget.py +18 -13
  9. celldetective/gui/configure_new_exp.py +51 -30
  10. celldetective/gui/control_panel.py +10 -7
  11. celldetective/gui/{signal_annotator.py → event_annotator.py} +473 -1437
  12. celldetective/gui/generic_signal_plot.py +2 -1
  13. celldetective/gui/gui_utils.py +5 -2
  14. celldetective/gui/help/neighborhood.json +2 -2
  15. celldetective/gui/layouts.py +21 -11
  16. celldetective/gui/{signal_annotator2.py → pair_event_annotator.py} +3 -1
  17. celldetective/gui/process_block.py +129 -91
  18. celldetective/gui/processes/downloader.py +37 -34
  19. celldetective/gui/processes/measure_cells.py +14 -8
  20. celldetective/gui/processes/segment_cells.py +21 -6
  21. celldetective/gui/processes/track_cells.py +12 -13
  22. celldetective/gui/settings/__init__.py +7 -0
  23. celldetective/gui/settings/_settings_base.py +70 -0
  24. celldetective/gui/{retrain_signal_model_options.py → settings/_settings_event_model_training.py} +35 -91
  25. celldetective/gui/{measurement_options.py → settings/_settings_measurements.py} +28 -81
  26. celldetective/gui/{neighborhood_options.py → settings/_settings_neighborhood.py} +1 -1
  27. celldetective/gui/settings/_settings_segmentation.py +49 -0
  28. celldetective/gui/{retrain_segmentation_model_options.py → settings/_settings_segmentation_model_training.py} +33 -79
  29. celldetective/gui/{signal_annotator_options.py → settings/_settings_signal_annotator.py} +73 -95
  30. celldetective/gui/{btrack_options.py → settings/_settings_tracking.py} +64 -87
  31. celldetective/gui/styles.py +2 -1
  32. celldetective/gui/survival_ui.py +1 -1
  33. celldetective/gui/tableUI.py +25 -0
  34. celldetective/gui/table_ops/__init__.py +0 -0
  35. celldetective/gui/table_ops/merge_groups.py +118 -0
  36. celldetective/gui/viewers.py +3 -5
  37. celldetective/gui/workers.py +0 -2
  38. celldetective/io.py +98 -55
  39. celldetective/links/zenodo.json +145 -144
  40. celldetective/measure.py +31 -26
  41. celldetective/preprocessing.py +34 -21
  42. celldetective/regionprops/_regionprops.py +16 -5
  43. celldetective/scripts/measure_cells.py +5 -5
  44. celldetective/scripts/measure_relative.py +16 -11
  45. celldetective/scripts/segment_cells.py +4 -4
  46. celldetective/scripts/segment_cells_thresholds.py +3 -3
  47. celldetective/scripts/track_cells.py +7 -7
  48. celldetective/scripts/train_segmentation_model.py +10 -1
  49. celldetective/tracking.py +10 -4
  50. celldetective/utils.py +59 -58
  51. {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/METADATA +1 -1
  52. celldetective-1.4.1.dist-info/RECORD +123 -0
  53. tests/gui/__init__.py +0 -0
  54. tests/gui/test_new_project.py +228 -0
  55. tests/{test_qt.py → gui/test_project.py} +22 -26
  56. tests/test_preprocessing.py +2 -2
  57. celldetective/models/segmentation_effectors/ricm_bf_all_last/config_input.json +0 -79
  58. celldetective/models/segmentation_effectors/ricm_bf_all_last/ricm_bf_all_last +0 -0
  59. celldetective/models/segmentation_effectors/ricm_bf_all_last/training_instructions.json +0 -37
  60. celldetective/models/segmentation_effectors/test-transfer/config_input.json +0 -39
  61. celldetective/models/segmentation_effectors/test-transfer/test-transfer +0 -0
  62. celldetective/models/signal_detection/NucCond/classification_loss.png +0 -0
  63. celldetective/models/signal_detection/NucCond/classifier.h5 +0 -0
  64. celldetective/models/signal_detection/NucCond/config_input.json +0 -1
  65. celldetective/models/signal_detection/NucCond/log_classifier.csv +0 -126
  66. celldetective/models/signal_detection/NucCond/log_regressor.csv +0 -282
  67. celldetective/models/signal_detection/NucCond/regression_loss.png +0 -0
  68. celldetective/models/signal_detection/NucCond/regressor.h5 +0 -0
  69. celldetective/models/signal_detection/NucCond/scores.npy +0 -0
  70. celldetective/models/signal_detection/NucCond/test_confusion_matrix.png +0 -0
  71. celldetective/models/signal_detection/NucCond/test_regression.png +0 -0
  72. celldetective/models/signal_detection/NucCond/validation_confusion_matrix.png +0 -0
  73. celldetective/models/signal_detection/NucCond/validation_regression.png +0 -0
  74. celldetective-1.4.0.dist-info/RECORD +0 -131
  75. {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/WHEEL +0 -0
  76. {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/entry_points.txt +0 -0
  77. {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/licenses/LICENSE +0 -0
  78. {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/top_level.txt +0 -0
celldetective/measure.py CHANGED
@@ -17,6 +17,7 @@ from math import ceil
17
17
  from skimage.draw import disk as dsk
18
18
  from skimage.feature import blob_dog, blob_log
19
19
 
20
+ from celldetective.exceptions import EmptyQueryError, MissingColumnsError, QueryError
20
21
  from celldetective.utils import rename_intensity_column, create_patch_mask, remove_redundant_features, \
21
22
  remove_trajectory_measurements, contour_of_instance_segmentation, extract_cols_from_query, step_function, interpolate_nan, _remove_invalid_cols
22
23
  from celldetective.preprocessing import field_correction
@@ -1432,35 +1433,39 @@ def classify_cells_from_query(df, status_attr, query):
1432
1433
  If the query is invalid or if there are issues with the DataFrame or query syntax, an error message is printed, and `None` is returned.
1433
1434
 
1434
1435
  """
1435
-
1436
-
1437
- # Initialize all states to 0
1438
- if not status_attr.startswith('status_'):
1439
- status_attr = 'status_'+status_attr
1440
-
1436
+
1437
+ if not status_attr.startswith("status_"):
1438
+ status_attr = "status_" + status_attr
1439
+
1441
1440
  df = df.copy()
1442
- df.loc[:,status_attr] = 0
1441
+ df = df.replace([np.inf, -np.inf, None], np.nan)
1442
+ #df = df.convert_dtypes()
1443
+
1444
+ df.loc[:, status_attr] = 0
1443
1445
  df[status_attr] = df[status_attr].astype(float)
1444
-
1446
+
1445
1447
  cols = extract_cols_from_query(query)
1446
- print(f"{cols=}")
1447
-
1448
- cols_in_df = np.all([c in list(df.columns) for c in cols], axis=0)
1449
- if query=='':
1450
- print('The provided query is empty...')
1451
- else:
1452
- try:
1453
- if cols_in_df:
1454
- selection = df.dropna(subset=cols).query(query).index
1455
- null_selection = df[df.loc[:,cols].isna().any(axis=1)].index
1456
- # Set NaN to invalid cells, 1 otherwise
1457
- df.loc[null_selection, status_attr] = np.nan
1458
- df.loc[selection, status_attr] = 1
1459
- else:
1460
- df.loc[:, status_attr] = np.nan
1461
- except Exception as e:
1462
- print(f"The query could not be understood. No filtering was applied. {e}...")
1463
- return None
1448
+ print(f"The following DataFrame measurements were identified in the query: {cols=}...")
1449
+
1450
+ if query.strip() == "":
1451
+ raise EmptyQueryError("The provided query is empty.")
1452
+
1453
+ missing_cols = [c for c in cols if c not in df.columns]
1454
+ if missing_cols:
1455
+ raise MissingColumnsError(missing_cols)
1456
+
1457
+ try:
1458
+ sub_df = df.dropna(subset=cols)
1459
+ if len(sub_df) > 0:
1460
+ selection = sub_df.query(query).index
1461
+ null_selection = df[df.loc[:, cols].isna().any(axis=1)].index
1462
+ df.loc[null_selection, status_attr] = np.nan
1463
+ df.loc[selection, status_attr] = 1
1464
+ else:
1465
+ df.loc[:, status_attr] = np.nan
1466
+ except Exception as e:
1467
+ raise QueryError(f"The query could not be understood: {e}")
1468
+
1464
1469
  return df.copy()
1465
1470
 
1466
1471
  def classify_tracks_from_query(df, event_name, query, irreversible_event=True, unique_state=False, r2_threshold=0.5, percentile_recovery=50):
@@ -1,12 +1,13 @@
1
1
  """
2
2
  Copright © 2024 Laboratoire Adhesion et Inflammation, Authored by Remy Torro & Ksenija Dervanova.
3
3
  """
4
+ from typing import List
4
5
 
5
6
  from tqdm import tqdm
6
7
  import numpy as np
7
8
  import os
8
9
  from celldetective.io import get_config, get_experiment_wells, interpret_wells_and_positions, extract_well_name_and_number, get_positions_in_well, extract_position_name, get_position_movie_path, load_frames, auto_load_number_of_frames
9
- from celldetective.utils import interpolate_nan, estimate_unreliable_edge, unpad, ConfigSectionMap, _extract_channel_indices_from_config, _extract_nbr_channels_from_config, _get_img_num_per_channel
10
+ from celldetective.utils import interpolate_nan, estimate_unreliable_edge, unpad, config_section_to_dict, _extract_channel_indices_from_config, _extract_nbr_channels_from_config, _get_img_num_per_channel
10
11
  from celldetective.segmentation import filter_image, threshold_image
11
12
  from csbdeep.io import save_tiff_imagej_compatible
12
13
  from gc import collect
@@ -14,7 +15,7 @@ from lmfit import Parameters, Model
14
15
  import tifffile.tifffile as tiff
15
16
  from scipy.ndimage import shift
16
17
 
17
- def estimate_background_per_condition(experiment, threshold_on_std=1, well_option='*', target_channel="channel_name", frame_range=[0,5], mode="timeseries", activation_protocol=[['gauss',2],['std',4]], show_progress_per_pos=False, show_progress_per_well=True, offset=None):
18
+ def estimate_background_per_condition(experiment, threshold_on_std=1, well_option='*', target_channel="channel_name", frame_range=[0,5], mode="timeseries", activation_protocol=[['gauss',2],['std',4]], show_progress_per_pos=False, show_progress_per_well=True, offset=None, fix_nan: bool = False):
18
19
 
19
20
  """
20
21
  Estimate the background for each condition in an experiment.
@@ -71,8 +72,8 @@ def estimate_background_per_condition(experiment, threshold_on_std=1, well_optio
71
72
 
72
73
  config = get_config(experiment)
73
74
  wells = get_experiment_wells(experiment)
74
- len_movie = float(ConfigSectionMap(config,"MovieSettings")["len_movie"])
75
- movie_prefix = ConfigSectionMap(config,"MovieSettings")["movie_prefix"]
75
+ len_movie = float(config_section_to_dict(config, "MovieSettings")["len_movie"])
76
+ movie_prefix = config_section_to_dict(config, "MovieSettings")["movie_prefix"]
76
77
 
77
78
  well_indices, position_indices = interpret_wells_and_positions(experiment, well_option, "*")
78
79
 
@@ -152,6 +153,8 @@ def estimate_background_per_condition(experiment, threshold_on_std=1, well_optio
152
153
  if offset is not None:
153
154
  #print("The offset is applied to background...")
154
155
  background -= offset
156
+ if fix_nan:
157
+ background = interpolate_nan(background.copy().astype(float))
155
158
  backgrounds.append({"bg": background, "well": well_path})
156
159
  print(f"Background successfully computed for well {well_name}...")
157
160
  except Exception as e:
@@ -179,6 +182,7 @@ def correct_background_model_free(
179
182
  export = False,
180
183
  return_stacks = False,
181
184
  movie_prefix=None,
185
+ fix_nan=False,
182
186
  activation_protocol=[['gauss',2],['std',4]],
183
187
  export_prefix='Corrected',
184
188
  **kwargs,
@@ -247,9 +251,9 @@ def correct_background_model_free(
247
251
 
248
252
  config = get_config(experiment)
249
253
  wells = get_experiment_wells(experiment)
250
- len_movie = float(ConfigSectionMap(config,"MovieSettings")["len_movie"])
254
+ len_movie = float(config_section_to_dict(config, "MovieSettings")["len_movie"])
251
255
  if movie_prefix is None:
252
- movie_prefix = ConfigSectionMap(config,"MovieSettings")["movie_prefix"]
256
+ movie_prefix = config_section_to_dict(config, "MovieSettings")["movie_prefix"]
253
257
 
254
258
  well_indices, position_indices = interpret_wells_and_positions(experiment, well_option, position_option)
255
259
  channel_indices = _extract_channel_indices_from_config(config, [target_channel])
@@ -263,7 +267,7 @@ def correct_background_model_free(
263
267
  well_name, _ = extract_well_name_and_number(well_path)
264
268
 
265
269
  try:
266
- background = estimate_background_per_condition(experiment, threshold_on_std=threshold_on_std, well_option=int(well_indices[k]), target_channel=target_channel, frame_range=frame_range, mode=mode, show_progress_per_pos=True, show_progress_per_well=False, activation_protocol=activation_protocol, offset=offset)
270
+ background = estimate_background_per_condition(experiment, threshold_on_std=threshold_on_std, well_option=int(well_indices[k]), target_channel=target_channel, frame_range=frame_range, mode=mode, show_progress_per_pos=True, show_progress_per_well=False, activation_protocol=activation_protocol, offset=offset, fix_nan=fix_nan)
267
271
  background = background[0]
268
272
  background = background['bg']
269
273
  except Exception as e:
@@ -298,6 +302,7 @@ def correct_background_model_free(
298
302
  clip = clip,
299
303
  offset = offset,
300
304
  export = export,
305
+ fix_nan=fix_nan,
301
306
  activation_protocol = activation_protocol,
302
307
  prefix = export_prefix,
303
308
  )
@@ -315,7 +320,7 @@ def correct_background_model_free(
315
320
 
316
321
 
317
322
 
318
- def apply_background_to_stack(stack_path, background, target_channel_index=0, nbr_channels=1, stack_length=45, offset = None, activation_protocol=[['gauss',2],['std',4]], threshold_on_std=1, optimize_option=True, opt_coef_range=(0.95,1.05), opt_coef_nbr=100, operation='divide', clip=False, export=False, prefix="Corrected"):
323
+ def apply_background_to_stack(stack_path, background, target_channel_index=0, nbr_channels=1, stack_length=45, offset = None, activation_protocol=[['gauss',2],['std',4]], threshold_on_std=1, optimize_option=True, opt_coef_range=(0.95,1.05), opt_coef_nbr=100, operation='divide', clip=False, export=False, prefix="Corrected", fix_nan=False):
319
324
 
320
325
  """
321
326
  Apply background correction to an image stack.
@@ -420,7 +425,7 @@ def apply_background_to_stack(stack_path, background, target_channel_index=0, nb
420
425
  loss.append(s)
421
426
 
422
427
  c = coefficients[np.argmin(loss)]
423
- print(f"Frame: {i}; optimal coefficient: {c}...")
428
+ print(f"IFD {i}; optimal coefficient: {c}...")
424
429
  # if c==min(coefficients) or c==max(coefficients):
425
430
  # print('Warning... The optimal coefficient is beyond the range provided... Please adjust your coefficient range...')
426
431
  else:
@@ -430,16 +435,20 @@ def apply_background_to_stack(stack_path, background, target_channel_index=0, nb
430
435
  correction = np.divide(target_img, background*c, where=background==background)
431
436
  correction[background!=background] = np.nan
432
437
  correction[target_img!=target_img] = np.nan
433
- fill_val = 1.0
434
438
 
435
439
  elif operation=="subtract":
436
440
  correction = np.subtract(target_img, background*c, where=background==background)
437
441
  correction[background!=background] = np.nan
438
442
  correction[target_img!=target_img] = np.nan
439
- fill_val = 0.0
440
443
  if clip:
441
444
  correction[correction<=0.] = 0.
445
+ else:
446
+ print("Operation not supported... Abort.")
447
+ return
442
448
 
449
+ correction[~np.isfinite(correction)] = np.nan
450
+ if fix_nan:
451
+ correction = interpolate_nan(correction.copy())
443
452
  frames[:,:,target_channel_index] = correction
444
453
  corrected_stack.append(frames)
445
454
 
@@ -771,9 +780,9 @@ def correct_background_model(
771
780
 
772
781
  config = get_config(experiment)
773
782
  wells = get_experiment_wells(experiment)
774
- len_movie = float(ConfigSectionMap(config,"MovieSettings")["len_movie"])
783
+ len_movie = float(config_section_to_dict(config, "MovieSettings")["len_movie"])
775
784
  if movie_prefix is None:
776
- movie_prefix = ConfigSectionMap(config,"MovieSettings")["movie_prefix"]
785
+ movie_prefix = config_section_to_dict(config, "MovieSettings")["movie_prefix"]
777
786
 
778
787
  well_indices, position_indices = interpret_wells_and_positions(experiment, well_option, position_option)
779
788
  channel_indices = _extract_channel_indices_from_config(config, [target_channel])
@@ -793,6 +802,10 @@ def correct_background_model(
793
802
  for pidx,pos_path in enumerate(tqdm(selection, disable=not show_progress_per_pos)):
794
803
 
795
804
  stack_path = get_position_movie_path(pos_path, prefix=movie_prefix)
805
+ if stack_path is None:
806
+ print(f"No stack could be found in {pos_path}... Skip...")
807
+ continue
808
+
796
809
  print(f'Applying the correction to position {extract_position_name(pos_path)}...')
797
810
  len_movie_auto = auto_load_number_of_frames(stack_path)
798
811
  if len_movie_auto is not None:
@@ -909,7 +922,7 @@ def fit_and_apply_model_background_to_stack(stack_path,
909
922
  frames = load_frames(list(np.arange(i,(i+nbr_channels))), stack_path, normalize_input=False).astype(float)
910
923
  target_img = frames[:,:,target_channel_index].copy()
911
924
 
912
- correction = field_correction(target_img, threshold_on_std=threshold_on_std, operation=operation, model=model, clip=clip, activation_protocol=activation_protocol)
925
+ correction = field_correction(target_img, threshold=threshold_on_std, operation=operation, model=model, clip=clip, activation_protocol=activation_protocol)
913
926
  frames[:,:,target_channel_index] = correction.copy()
914
927
 
915
928
  if return_stacks:
@@ -930,7 +943,7 @@ def fit_and_apply_model_background_to_stack(stack_path,
930
943
  frames = load_frames(list(np.arange(i,(i+nbr_channels))), stack_path, normalize_input=False).astype(float)
931
944
  target_img = frames[:,:,target_channel_index].copy()
932
945
 
933
- correction = field_correction(target_img, threshold_on_std=threshold_on_std, operation=operation, model=model, clip=clip, activation_protocol=activation_protocol)
946
+ correction = field_correction(target_img, threshold=threshold_on_std, operation=operation, model=model, clip=clip, activation_protocol=activation_protocol)
934
947
  frames[:,:,target_channel_index] = correction.copy()
935
948
 
936
949
  corrected_stack.append(frames)
@@ -945,7 +958,7 @@ def fit_and_apply_model_background_to_stack(stack_path,
945
958
  else:
946
959
  return None
947
960
 
948
- def field_correction(img, threshold_on_std=1, operation='divide', model='paraboloid', clip=False, return_bg=False, activation_protocol=[['gauss',2],['std',4]]):
961
+ def field_correction(img: np.ndarray, threshold: float = 1, operation: str = 'divide', model: str = 'paraboloid', clip: bool = False, return_bg: bool = False, activation_protocol: List[List] = [['gauss',2],['std',4]]):
949
962
 
950
963
  """
951
964
  Apply field correction to an image.
@@ -958,8 +971,8 @@ def field_correction(img, threshold_on_std=1, operation='divide', model='parabol
958
971
  ----------
959
972
  img : numpy.ndarray
960
973
  The input image to be corrected.
961
- threshold_on_std : float, optional
962
- The threshold value on the standard deviation for masking (default is 1).
974
+ threshold : float, optional
975
+ The threshold value on the image, post activation protocol for masking out cells (default is 1).
963
976
  operation : str, optional
964
977
  The operation to apply for background correction, either 'divide' or 'subtract' (default is 'divide').
965
978
  model : str, optional
@@ -997,7 +1010,7 @@ def field_correction(img, threshold_on_std=1, operation='divide', model='parabol
997
1010
 
998
1011
  std_frame = filter_image(target_copy,filters=activation_protocol)
999
1012
  edge = estimate_unreliable_edge(activation_protocol)
1000
- mask = threshold_image(std_frame, threshold_on_std, np.inf, foreground_value=1, edge_exclusion=edge).astype(int)
1013
+ mask = threshold_image(std_frame, threshold, np.inf, foreground_value=1, edge_exclusion=edge).astype(int)
1001
1014
  background = fit_background_model(img, cell_masks=mask, model=model, edge_exclusion=edge)
1002
1015
 
1003
1016
  if operation=="divide":
@@ -1086,9 +1099,9 @@ def correct_channel_offset(
1086
1099
 
1087
1100
  config = get_config(experiment)
1088
1101
  wells = get_experiment_wells(experiment)
1089
- len_movie = float(ConfigSectionMap(config,"MovieSettings")["len_movie"])
1102
+ len_movie = float(config_section_to_dict(config, "MovieSettings")["len_movie"])
1090
1103
  if movie_prefix is None:
1091
- movie_prefix = ConfigSectionMap(config,"MovieSettings")["movie_prefix"]
1104
+ movie_prefix = config_section_to_dict(config, "MovieSettings")["movie_prefix"]
1092
1105
 
1093
1106
  well_indices, position_indices = interpret_wells_and_positions(experiment, well_option, position_option)
1094
1107
  channel_indices = _extract_channel_indices_from_config(config, [target_channel])
@@ -59,6 +59,22 @@ COL_DTYPES = {
59
59
  OBJECT_COLUMNS = [col for col, dtype in COL_DTYPES.items() if dtype == object]
60
60
  PROP_VALS = set(PROPS.values())
61
61
 
62
+ _require_intensity_image = (
63
+ 'image_intensity',
64
+ 'intensity_max',
65
+ 'intensity_mean',
66
+ 'intensity_median',
67
+ 'intensity_min',
68
+ 'intensity_std',
69
+ 'moments_weighted',
70
+ 'moments_weighted_central',
71
+ 'centroid_weighted',
72
+ 'centroid_weighted_local',
73
+ 'moments_weighted_hu',
74
+ 'moments_weighted_normalized',
75
+ )
76
+
77
+
62
78
  class CustomRegionProps(RegionProperties):
63
79
 
64
80
  """
@@ -79,11 +95,6 @@ class CustomRegionProps(RegionProperties):
79
95
  assert len(self.channel_names)==self._intensity_image.shape[-1],'Mismatch between provided channel names and the number of channels in the image...'
80
96
 
81
97
  if attr == "__setstate__":
82
- # When deserializing this object with pickle, `__setstate__`
83
- # is accessed before any other attributes like `self._intensity_image`
84
- # are available which leads to a RecursionError when trying to
85
- # access them later on in this function. So guard against this by
86
- # provoking the default AttributeError (gh-6465).
87
98
  return self.__getattribute__(attr)
88
99
 
89
100
  if self._intensity_image is None and attr in _require_intensity_image:
@@ -6,7 +6,7 @@ import argparse
6
6
  import os
7
7
  import json
8
8
  from celldetective.io import auto_load_number_of_frames, load_frames, fix_missing_labels, locate_labels, extract_position_name
9
- from celldetective.utils import extract_experiment_channels, ConfigSectionMap, _get_img_num_per_channel, extract_experiment_channels
9
+ from celldetective.utils import extract_experiment_channels, config_section_to_dict, _get_img_num_per_channel, extract_experiment_channels
10
10
  from celldetective.utils import _remove_invalid_cols, remove_redundant_features, remove_trajectory_measurements, _extract_coordinates_from_features
11
11
  from celldetective.measure import drop_tonal_features, measure_features, measure_isotropic_intensity, center_of_mass_to_abs_coordinates, measure_radial_distance_to_center
12
12
  from pathlib import Path, PurePath
@@ -55,10 +55,10 @@ print("Configuration file: ",config)
55
55
  print(f"Population: {mode}...")
56
56
 
57
57
  # from exp config fetch spatial calib, channel names
58
- movie_prefix = ConfigSectionMap(config,"MovieSettings")["movie_prefix"]
59
- spatial_calibration = float(ConfigSectionMap(config,"MovieSettings")["pxtoum"])
60
- time_calibration = float(ConfigSectionMap(config,"MovieSettings")["frametomin"])
61
- len_movie = float(ConfigSectionMap(config,"MovieSettings")["len_movie"])
58
+ movie_prefix = config_section_to_dict(config, "MovieSettings")["movie_prefix"]
59
+ spatial_calibration = float(config_section_to_dict(config, "MovieSettings")["pxtoum"])
60
+ time_calibration = float(config_section_to_dict(config, "MovieSettings")["frametomin"])
61
+ len_movie = float(config_section_to_dict(config, "MovieSettings")["len_movie"])
62
62
  channel_names, channel_indices = extract_experiment_channels(expfolder)
63
63
  nbr_channels = len(channel_names)
64
64
 
@@ -1,7 +1,7 @@
1
1
  import argparse
2
2
  import os
3
3
  from celldetective.relative_measurements import measure_pair_signals_at_position, extract_neighborhoods_from_pickles
4
- from celldetective.utils import ConfigSectionMap, extract_experiment_channels
4
+ from celldetective.utils import config_section_to_dict, extract_experiment_channels
5
5
  from celldetective.io import get_experiment_populations
6
6
 
7
7
  from pathlib import Path, PurePath
@@ -31,10 +31,10 @@ assert os.path.exists(config), 'The configuration file for the experiment could
31
31
  print("Configuration file: ", config)
32
32
 
33
33
  # from exp config fetch spatial calib, channel names
34
- movie_prefix = ConfigSectionMap(config, "MovieSettings")["movie_prefix"]
35
- spatial_calibration = float(ConfigSectionMap(config, "MovieSettings")["pxtoum"])
36
- time_calibration = float(ConfigSectionMap(config, "MovieSettings")["frametomin"])
37
- len_movie = float(ConfigSectionMap(config, "MovieSettings")["len_movie"])
34
+ movie_prefix = config_section_to_dict(config, "MovieSettings")["movie_prefix"]
35
+ spatial_calibration = float(config_section_to_dict(config, "MovieSettings")["pxtoum"])
36
+ time_calibration = float(config_section_to_dict(config, "MovieSettings")["frametomin"])
37
+ len_movie = float(config_section_to_dict(config, "MovieSettings")["len_movie"])
38
38
  channel_names, channel_indices = extract_experiment_channels(expfolder)
39
39
  nbr_channels = len(channel_names)
40
40
 
@@ -79,9 +79,14 @@ if len(all_df_pairs)>1:
79
79
  df_pairs = pd.merge(df_pairs.round(decimals=6), all_df_pairs[i].round(decimals=6), how="outer", on=cols)
80
80
  elif len(all_df_pairs)==1:
81
81
  df_pairs = all_df_pairs[0]
82
-
83
- print('Writing table...')
84
- df_pairs = df_pairs.sort_values(by=['reference_population', 'neighbor_population', 'REFERENCE_ID', 'NEIGHBOR_ID', 'FRAME'])
85
- df_pairs.to_csv(previous_pair_table_path, index=False)
86
- print('Done.')
87
-
82
+ else:
83
+ df_pairs = None
84
+ print('No dataframe could be computed for the pairs...')
85
+
86
+ if df_pairs is not None:
87
+ print('Writing table...')
88
+ if "reference_population" in list(df_pairs.columns) and "neighbor_population" in list(df_pairs.columns):
89
+ df_pairs = df_pairs.sort_values(by=['reference_population', 'neighbor_population', 'REFERENCE_ID', 'NEIGHBOR_ID', 'FRAME'])
90
+ df_pairs.to_csv(previous_pair_table_path, index=False)
91
+ print('Done.')
92
+
@@ -7,7 +7,7 @@ import datetime
7
7
  import os
8
8
  import json
9
9
  from celldetective.io import locate_segmentation_model, auto_load_number_of_frames, extract_position_name, _load_frames_to_segment, _check_label_dims
10
- from celldetective.utils import _prep_stardist_model, _prep_cellpose_model, _rescale_labels, _segment_image_with_stardist_model,_segment_image_with_cellpose_model,_get_normalize_kwargs_from_config, _estimate_scale_factor, _extract_channel_indices_from_config, ConfigSectionMap, _extract_nbr_channels_from_config, _get_img_num_per_channel
10
+ from celldetective.utils import _prep_stardist_model, _prep_cellpose_model, _rescale_labels, _segment_image_with_stardist_model,_segment_image_with_cellpose_model,_get_normalize_kwargs_from_config, _estimate_scale_factor, _extract_channel_indices_from_config, config_section_to_dict, _extract_nbr_channels_from_config, _get_img_num_per_channel
11
11
  from pathlib import Path, PurePath
12
12
  from glob import glob
13
13
  from shutil import rmtree
@@ -91,9 +91,9 @@ normalize_kwargs = _get_normalize_kwargs_from_config(input_config)
91
91
 
92
92
  model_type = input_config['model_type']
93
93
 
94
- movie_prefix = ConfigSectionMap(config,"MovieSettings")["movie_prefix"]
95
- spatial_calibration = float(ConfigSectionMap(config,"MovieSettings")["pxtoum"])
96
- len_movie = float(ConfigSectionMap(config,"MovieSettings")["len_movie"])
94
+ movie_prefix = config_section_to_dict(config, "MovieSettings")["movie_prefix"]
95
+ spatial_calibration = float(config_section_to_dict(config, "MovieSettings")["pxtoum"])
96
+ len_movie = float(config_section_to_dict(config, "MovieSettings")["len_movie"])
97
97
 
98
98
  # Try to find the file
99
99
  try:
@@ -7,7 +7,7 @@ import os
7
7
  import json
8
8
  from celldetective.io import auto_load_number_of_frames, load_frames, extract_position_name
9
9
  from celldetective.segmentation import segment_frame_from_thresholds
10
- from celldetective.utils import _extract_channel_indices_from_config, ConfigSectionMap, _extract_nbr_channels_from_config, _get_img_num_per_channel, extract_experiment_channels
10
+ from celldetective.utils import _extract_channel_indices_from_config, config_section_to_dict, _extract_nbr_channels_from_config, _get_img_num_per_channel, extract_experiment_channels
11
11
  from pathlib import Path, PurePath
12
12
  from glob import glob
13
13
  from shutil import rmtree
@@ -70,8 +70,8 @@ print(f'Required channels: {required_channels} located at channel indices {chann
70
70
 
71
71
  threshold_instructions.update({'target_channel': channel_indices[0]})
72
72
 
73
- movie_prefix = ConfigSectionMap(config,"MovieSettings")["movie_prefix"]
74
- len_movie = float(ConfigSectionMap(config,"MovieSettings")["len_movie"])
73
+ movie_prefix = config_section_to_dict(config, "MovieSettings")["movie_prefix"]
74
+ len_movie = float(config_section_to_dict(config, "MovieSettings")["len_movie"])
75
75
  channel_names, channel_indices = extract_experiment_channels(expfolder)
76
76
  threshold_instructions.update({'channel_names': channel_names})
77
77
 
@@ -9,7 +9,7 @@ import json
9
9
  from celldetective.io import _load_frames_to_measure, auto_load_number_of_frames, interpret_tracking_configuration, \
10
10
  extract_position_name, \
11
11
  locate_labels
12
- from celldetective.utils import _mask_intensity_measurements, extract_experiment_channels, ConfigSectionMap, _get_img_num_per_channel, extract_experiment_channels
12
+ from celldetective.utils import _mask_intensity_measurements, extract_experiment_channels, config_section_to_dict, _get_img_num_per_channel, extract_experiment_channels
13
13
  from celldetective.measure import drop_tonal_features, measure_features
14
14
  from celldetective.tracking import track
15
15
  from pathlib import Path, PurePath
@@ -67,12 +67,12 @@ print("Configuration file: ",config)
67
67
  print(f"Population: {mode}...")
68
68
 
69
69
  # from exp config fetch spatial calib, channel names
70
- movie_prefix = ConfigSectionMap(config,"MovieSettings")["movie_prefix"]
71
- spatial_calibration = float(ConfigSectionMap(config,"MovieSettings")["pxtoum"])
72
- time_calibration = float(ConfigSectionMap(config,"MovieSettings")["frametomin"])
73
- len_movie = float(ConfigSectionMap(config,"MovieSettings")["len_movie"])
74
- shape_x = int(ConfigSectionMap(config,"MovieSettings")["shape_x"])
75
- shape_y = int(ConfigSectionMap(config,"MovieSettings")["shape_y"])
70
+ movie_prefix = config_section_to_dict(config, "MovieSettings")["movie_prefix"]
71
+ spatial_calibration = float(config_section_to_dict(config, "MovieSettings")["pxtoum"])
72
+ time_calibration = float(config_section_to_dict(config, "MovieSettings")["frametomin"])
73
+ len_movie = float(config_section_to_dict(config, "MovieSettings")["len_movie"])
74
+ shape_x = int(config_section_to_dict(config, "MovieSettings")["shape_x"])
75
+ shape_y = int(config_section_to_dict(config, "MovieSettings")["shape_y"])
76
76
 
77
77
  channel_names, channel_indices = extract_experiment_channels(expfolder)
78
78
  nbr_channels = len(channel_names)
@@ -232,7 +232,16 @@ elif model_type=='stardist':
232
232
  'normalization_clip': normalization_clip, 'normalization_values': normalization_values,
233
233
  'model_type': 'stardist', 'spatial_calibration': spatial_calibration,'cell_size_um': median_size * spatial_calibration, 'dataset': {'train': files_train, 'validation': files_val}}
234
234
 
235
- json_input_config = json.dumps(config_inputs, indent=4)
235
+ def make_json_safe(obj):
236
+ if isinstance(obj, np.ndarray):
237
+ return obj.tolist() # convert to list
238
+ if isinstance(obj, (np.int64, np.int32)):
239
+ return int(obj)
240
+ if isinstance(obj, (np.float32, np.float64)):
241
+ return float(obj)
242
+ return str(obj) # fallback
243
+
244
+ json_input_config = json.dumps(config_inputs, indent=4, default=make_json_safe)
236
245
  with open(os.sep.join([target_directory, model_name, "config_input.json"]), "w") as outfile:
237
246
  outfile.write(json_input_config)
238
247
 
celldetective/tracking.py CHANGED
@@ -189,7 +189,8 @@ def track(labels, configuration=None, stack=None, spatial_calibration=1, feature
189
189
 
190
190
  if clean_trajectories_kwargs is not None:
191
191
  df = clean_trajectories(df.copy(),**clean_trajectories_kwargs)
192
-
192
+
193
+ df.loc[df["status_firstdetection"].isna(), "status_firstdetection"] = 0
193
194
  df['ID'] = np.arange(len(df)).astype(int)
194
195
 
195
196
  invalid_cols = [c for c in list(df.columns) if c.startswith('Unnamed')]
@@ -1002,7 +1003,10 @@ def write_first_detection_class(df, img_shape=None, edge_threshold=20, column_la
1002
1003
  positions_x = track_group[column_labels['x']].values
1003
1004
  positions_y = track_group[column_labels['y']].values
1004
1005
  dt = 1
1005
-
1006
+
1007
+ timeline = track_group['FRAME'].to_numpy()
1008
+ status = np.ones_like(timeline)
1009
+
1006
1010
  # Initialize
1007
1011
  cclass = 2; t_first = np.nan;
1008
1012
 
@@ -1023,16 +1027,18 @@ def write_first_detection_class(df, img_shape=None, edge_threshold=20, column_la
1023
1027
  t_first = float(t_first) - float(dt)
1024
1028
  if t_first==0:
1025
1029
  t_first += 0.01
1026
-
1030
+
1027
1031
  if edge_test:
1028
1032
  cclass = 2
1029
1033
  # switch to class 2 but keep time/status information
1030
1034
  else:
1031
1035
  t_first = -1
1032
1036
  cclass = 2
1033
-
1037
+
1038
+ status[timeline < t_first] = 0.
1034
1039
  df.loc[indices, 'class_firstdetection'] = cclass
1035
1040
  df.loc[indices, 't_firstdetection'] = t_first
1041
+ df.loc[indices, 'status_firstdetection'] = status
1036
1042
 
1037
1043
  return df
1038
1044