celldetective 1.3.8.post1__py3-none-any.whl → 1.3.9__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.
- celldetective/_version.py +1 -1
- celldetective/extra_properties.py +113 -17
- celldetective/filters.py +12 -12
- celldetective/gui/btrack_options.py +1 -1
- celldetective/gui/control_panel.py +1 -1
- celldetective/gui/gui_utils.py +4 -4
- celldetective/gui/measurement_options.py +1 -1
- celldetective/gui/plot_signals_ui.py +23 -6
- celldetective/gui/process_block.py +1 -1
- celldetective/gui/processes/measure_cells.py +4 -4
- celldetective/gui/processes/segment_cells.py +3 -3
- celldetective/gui/processes/track_cells.py +4 -4
- celldetective/gui/signal_annotator.py +26 -6
- celldetective/gui/signal_annotator2.py +1 -1
- celldetective/gui/signal_annotator_options.py +1 -1
- celldetective/gui/survival_ui.py +4 -1
- celldetective/gui/thresholds_gui.py +6 -5
- celldetective/io.py +1 -44
- celldetective/measure.py +22 -16
- celldetective/regionprops/__init__.py +1 -0
- celldetective/regionprops/_regionprops.py +310 -0
- celldetective/regionprops/props.json +63 -0
- celldetective/scripts/measure_relative.py +2 -20
- celldetective/segmentation.py +14 -4
- celldetective/utils.py +182 -171
- {celldetective-1.3.8.post1.dist-info → celldetective-1.3.9.dist-info}/METADATA +1 -1
- {celldetective-1.3.8.post1.dist-info → celldetective-1.3.9.dist-info}/RECORD +31 -28
- {celldetective-1.3.8.post1.dist-info → celldetective-1.3.9.dist-info}/LICENSE +0 -0
- {celldetective-1.3.8.post1.dist-info → celldetective-1.3.9.dist-info}/WHEEL +0 -0
- {celldetective-1.3.8.post1.dist-info → celldetective-1.3.9.dist-info}/entry_points.txt +0 -0
- {celldetective-1.3.8.post1.dist-info → celldetective-1.3.9.dist-info}/top_level.txt +0 -0
celldetective/utils.py
CHANGED
|
@@ -31,6 +31,55 @@ from scipy.stats import ks_2samp
|
|
|
31
31
|
from cliffs_delta import cliffs_delta
|
|
32
32
|
from stardist.models import StarDist2D
|
|
33
33
|
from cellpose.models import CellposeModel
|
|
34
|
+
from pathlib import PosixPath, PurePosixPath
|
|
35
|
+
|
|
36
|
+
def get_config(experiment):
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
Retrieves the path to the configuration file for a given experiment.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
experiment : str
|
|
44
|
+
The file system path to the directory of the experiment project.
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
str
|
|
49
|
+
The full path to the configuration file (`config.ini`) within the experiment directory.
|
|
50
|
+
|
|
51
|
+
Raises
|
|
52
|
+
------
|
|
53
|
+
AssertionError
|
|
54
|
+
If the `config.ini` file does not exist in the specified experiment directory.
|
|
55
|
+
|
|
56
|
+
Notes
|
|
57
|
+
-----
|
|
58
|
+
- The function ensures that the provided experiment path ends with the appropriate file separator (`os.sep`)
|
|
59
|
+
before appending `config.ini` to locate the configuration file.
|
|
60
|
+
- The configuration file is expected to be named `config.ini` and located at the root of the experiment directory.
|
|
61
|
+
|
|
62
|
+
Example
|
|
63
|
+
-------
|
|
64
|
+
>>> experiment = "/path/to/experiment"
|
|
65
|
+
>>> config_path = get_config(experiment)
|
|
66
|
+
>>> print(config_path)
|
|
67
|
+
'/path/to/experiment/config.ini'
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
if isinstance(experiment, (PosixPath, PurePosixPath)):
|
|
72
|
+
experiment = str(experiment)
|
|
73
|
+
|
|
74
|
+
if not experiment.endswith(os.sep):
|
|
75
|
+
experiment += os.sep
|
|
76
|
+
|
|
77
|
+
config = experiment + 'config.ini'
|
|
78
|
+
config = rf"{config}"
|
|
79
|
+
|
|
80
|
+
assert os.path.exists(config), 'The experiment configuration could not be located...'
|
|
81
|
+
return config
|
|
82
|
+
|
|
34
83
|
|
|
35
84
|
def _remove_invalid_cols(df):
|
|
36
85
|
|
|
@@ -38,8 +87,7 @@ def _remove_invalid_cols(df):
|
|
|
38
87
|
Removes invalid columns from a DataFrame.
|
|
39
88
|
|
|
40
89
|
This function identifies and removes columns in the DataFrame whose names
|
|
41
|
-
start with "Unnamed",
|
|
42
|
-
formatted columns (e.g., leftover columns from improperly read CSV files).
|
|
90
|
+
start with "Unnamed", or that contain only NaN values.
|
|
43
91
|
|
|
44
92
|
Parameters
|
|
45
93
|
----------
|
|
@@ -51,32 +99,22 @@ def _remove_invalid_cols(df):
|
|
|
51
99
|
pandas.DataFrame
|
|
52
100
|
A new DataFrame with the invalid columns removed. If no invalid
|
|
53
101
|
columns are found, the original DataFrame is returned unchanged.
|
|
54
|
-
|
|
55
|
-
Notes
|
|
56
|
-
-----
|
|
57
|
-
- This function does not modify the original DataFrame in place; instead,
|
|
58
|
-
it returns a new DataFrame.
|
|
59
|
-
- Columns starting with "Unnamed" are commonly introduced when saving
|
|
60
|
-
or loading data files with misaligned headers.
|
|
61
102
|
"""
|
|
62
103
|
|
|
63
104
|
invalid_cols = [c for c in list(df.columns) if c.startswith('Unnamed')]
|
|
64
105
|
if len(invalid_cols)>0:
|
|
65
106
|
df = df.drop(invalid_cols, axis=1)
|
|
107
|
+
df = df.dropna(axis=1, how='all')
|
|
66
108
|
return df
|
|
67
109
|
|
|
68
|
-
def _extract_coordinates_from_features(
|
|
110
|
+
def _extract_coordinates_from_features(df, timepoint):
|
|
69
111
|
|
|
70
112
|
"""
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
This function processes a DataFrame of features to extract and rename centroid
|
|
74
|
-
coordinates, assign a unique identifier to each feature, and add a frame (timepoint)
|
|
75
|
-
column. The resulting DataFrame is structured for use in trajectory analysis.
|
|
113
|
+
Re-format coordinates from a regionprops table to tracking/measurement table format.
|
|
76
114
|
|
|
77
115
|
Parameters
|
|
78
116
|
----------
|
|
79
|
-
|
|
117
|
+
df : pandas.DataFrame
|
|
80
118
|
A DataFrame containing feature data, including columns for centroids
|
|
81
119
|
(`'centroid-1'` and `'centroid-0'`) and feature classes (`'class_id'`).
|
|
82
120
|
timepoint : int
|
|
@@ -103,7 +141,7 @@ def _extract_coordinates_from_features(features, timepoint):
|
|
|
103
141
|
to `'POSITION_Y'`.
|
|
104
142
|
"""
|
|
105
143
|
|
|
106
|
-
coords =
|
|
144
|
+
coords = df[['centroid-1', 'centroid-0', 'class_id']].copy()
|
|
107
145
|
coords['ID'] = np.arange(len(coords))
|
|
108
146
|
coords.rename(columns={'centroid-1': 'POSITION_X', 'centroid-0': 'POSITION_Y'}, inplace=True)
|
|
109
147
|
coords['FRAME'] = int(timepoint)
|
|
@@ -143,10 +181,13 @@ def _mask_intensity_measurements(df, mask_channels):
|
|
|
143
181
|
does not modify the input DataFrame.
|
|
144
182
|
"""
|
|
145
183
|
|
|
184
|
+
if isinstance(mask_channels, str):
|
|
185
|
+
mask_channels = [mask_channels]
|
|
186
|
+
|
|
146
187
|
if mask_channels is not None:
|
|
147
188
|
|
|
148
189
|
cols_to_drop = []
|
|
149
|
-
columns = df.columns
|
|
190
|
+
columns = list(df.columns)
|
|
150
191
|
|
|
151
192
|
for mc in mask_channels:
|
|
152
193
|
cols_to_remove = [c for c in columns if mc in c]
|
|
@@ -156,7 +197,7 @@ def _mask_intensity_measurements(df, mask_channels):
|
|
|
156
197
|
df = df.drop(cols_to_drop, axis=1)
|
|
157
198
|
return df
|
|
158
199
|
|
|
159
|
-
def _rearrange_multichannel_frame(frame):
|
|
200
|
+
def _rearrange_multichannel_frame(frame, n_channels=None):
|
|
160
201
|
|
|
161
202
|
"""
|
|
162
203
|
Rearranges the axes of a multi-channel frame to ensure the channel axis is at the end.
|
|
@@ -206,7 +247,10 @@ def _rearrange_multichannel_frame(frame):
|
|
|
206
247
|
|
|
207
248
|
if frame.ndim == 3:
|
|
208
249
|
# Systematically move channel axis to the end
|
|
209
|
-
|
|
250
|
+
if n_channels is not None and n_channels in list(frame.shape):
|
|
251
|
+
channel_axis = list(frame.shape).index(n_channels)
|
|
252
|
+
else:
|
|
253
|
+
channel_axis = np.argmin(frame.shape)
|
|
210
254
|
frame = np.moveaxis(frame, channel_axis, -1)
|
|
211
255
|
|
|
212
256
|
if frame.ndim==2:
|
|
@@ -1083,9 +1127,11 @@ def mask_edges(binary_mask, border_size):
|
|
|
1083
1127
|
return binary_mask
|
|
1084
1128
|
|
|
1085
1129
|
def demangle_column_name(name):
|
|
1086
|
-
if name.startswith("BACKTICK_QUOTED_STRING_"):
|
|
1130
|
+
if name.startswith("BACKTICK_QUOTED_STRING_") and not '_MINUS_' in name:
|
|
1087
1131
|
# Unquote backtick-quoted string.
|
|
1088
1132
|
return name[len("BACKTICK_QUOTED_STRING_"):].replace("_DOT_", ".").replace("_SLASH_", "/")
|
|
1133
|
+
elif name.startswith("BACKTICK_QUOTED_STRING_") and '_MINUS_' in name:
|
|
1134
|
+
return name[len("BACKTICK_QUOTED_STRING_"):].replace("_DOT_", ".").replace("_SLASH_", "/").replace('_MINUS_','-')
|
|
1089
1135
|
return name
|
|
1090
1136
|
|
|
1091
1137
|
def extract_cols_from_query(query: str):
|
|
@@ -1792,6 +1838,21 @@ def ConfigSectionMap(path,section):
|
|
|
1792
1838
|
|
|
1793
1839
|
dict1: dictionary
|
|
1794
1840
|
|
|
1841
|
+
Examples
|
|
1842
|
+
--------
|
|
1843
|
+
>>> config = "path/to/config_file.ini"
|
|
1844
|
+
>>> section = "Channels"
|
|
1845
|
+
>>> channel_dictionary = ConfigSectionMap(config,section)
|
|
1846
|
+
>>> print(channel_dictionary)
|
|
1847
|
+
# {'brightfield_channel': '0',
|
|
1848
|
+
# 'live_nuclei_channel': 'nan',
|
|
1849
|
+
# 'dead_nuclei_channel': 'nan',
|
|
1850
|
+
# 'effector_fluo_channel': 'nan',
|
|
1851
|
+
# 'adhesion_channel': '1',
|
|
1852
|
+
# 'fluo_channel_1': 'nan',
|
|
1853
|
+
# 'fluo_channel_2': 'nan',
|
|
1854
|
+
# 'fitc_channel': '2',
|
|
1855
|
+
# 'cy5_channel': '3'}
|
|
1795
1856
|
"""
|
|
1796
1857
|
|
|
1797
1858
|
Config = configparser.ConfigParser(interpolation=None)
|
|
@@ -1846,14 +1907,16 @@ def _extract_channel_indices_from_config(config, channels_to_extract):
|
|
|
1846
1907
|
|
|
1847
1908
|
Examples
|
|
1848
1909
|
--------
|
|
1849
|
-
>>> config =
|
|
1850
|
-
>>>
|
|
1851
|
-
>>> channels_to_extract = ['GFP', 'RFP']
|
|
1910
|
+
>>> config = "path/to/config_file.ini"
|
|
1911
|
+
>>> channels_to_extract = ['adhesion_channel', 'brightfield_channel']
|
|
1852
1912
|
>>> channel_indices = _extract_channel_indices_from_config(config, channels_to_extract)
|
|
1853
1913
|
>>> print(channel_indices)
|
|
1854
|
-
# [1,
|
|
1914
|
+
# [1, 0] or None if an error occurs or the channels are not found.
|
|
1855
1915
|
"""
|
|
1856
1916
|
|
|
1917
|
+
if isinstance(channels_to_extract, str):
|
|
1918
|
+
channels_to_extract = [channels_to_extract]
|
|
1919
|
+
|
|
1857
1920
|
channels = []
|
|
1858
1921
|
for c in channels_to_extract:
|
|
1859
1922
|
try:
|
|
@@ -1870,44 +1933,13 @@ def _extract_channel_indices_from_config(config, channels_to_extract):
|
|
|
1870
1933
|
def _extract_nbr_channels_from_config(config, return_names=False):
|
|
1871
1934
|
|
|
1872
1935
|
"""
|
|
1873
|
-
Extracts the indices of specified channels from a configuration object.
|
|
1874
|
-
|
|
1875
|
-
This function attempts to map required channel names to their respective indices as specified in a
|
|
1876
|
-
configuration file. It supports two versions of configuration parsing: a primary method (V2) and a
|
|
1877
|
-
fallback legacy method. If the required channels are not found using the primary method, the function
|
|
1878
|
-
attempts to find them using the legacy configuration settings.
|
|
1879
|
-
|
|
1880
|
-
Parameters
|
|
1881
|
-
----------
|
|
1882
|
-
config : ConfigParser object
|
|
1883
|
-
The configuration object parsed from a .ini or similar configuration file that includes channel settings.
|
|
1884
|
-
channels_to_extract : list of str
|
|
1885
|
-
A list of channel names for which indices are to be extracted from the configuration settings.
|
|
1886
|
-
|
|
1887
|
-
Returns
|
|
1888
|
-
-------
|
|
1889
|
-
list of int or None
|
|
1890
|
-
A list containing the indices of the specified channels as found in the configuration settings.
|
|
1891
|
-
If a channel cannot be found, None is appended in its place. If an error occurs during the extraction
|
|
1892
|
-
process, the function returns None.
|
|
1893
|
-
|
|
1894
|
-
Notes
|
|
1895
|
-
-----
|
|
1896
|
-
- This function is designed to be flexible, accommodating changes in configuration file structure by
|
|
1897
|
-
checking multiple sections for the required information.
|
|
1898
|
-
- The configuration file is expected to contain either "Channels" or "MovieSettings" sections with mappings
|
|
1899
|
-
from channel names to indices.
|
|
1900
|
-
- An error message is printed if a required channel cannot be found, advising the user to check the
|
|
1901
|
-
configuration file.
|
|
1902
1936
|
|
|
1903
1937
|
Examples
|
|
1904
1938
|
--------
|
|
1905
|
-
>>> config =
|
|
1906
|
-
>>> config
|
|
1907
|
-
>>>
|
|
1908
|
-
|
|
1909
|
-
>>> print(channel_indices)
|
|
1910
|
-
# [1, 2] or None if an error occurs or the channels are not found.
|
|
1939
|
+
>>> config = "path/to/config_file.ini"
|
|
1940
|
+
>>> nbr_channels = _extract_channel_indices_from_config(config)
|
|
1941
|
+
>>> print(nbr_channels)
|
|
1942
|
+
# 4
|
|
1911
1943
|
"""
|
|
1912
1944
|
|
|
1913
1945
|
# V2
|
|
@@ -2019,16 +2051,26 @@ def _get_img_num_per_channel(channels_indices, len_movie, nbr_channels):
|
|
|
2019
2051
|
|
|
2020
2052
|
Examples
|
|
2021
2053
|
--------
|
|
2022
|
-
>>> channels_indices = [0
|
|
2054
|
+
>>> channels_indices = [0] # Indices for channels 1, 3, and a non-existing channel
|
|
2055
|
+
>>> len_movie = 10 # Total frames for each channel
|
|
2056
|
+
>>> nbr_channels = 3 # Total channels in the movie
|
|
2057
|
+
>>> img_num_per_channel = _get_img_num_per_channel(channels_indices, len_movie, nbr_channels)
|
|
2058
|
+
>>> print(img_num_per_channel)
|
|
2059
|
+
# array([[ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27]])
|
|
2060
|
+
|
|
2061
|
+
>>> channels_indices = [1,2] # Indices for channels 1, 3, and a non-existing channel
|
|
2023
2062
|
>>> len_movie = 10 # Total frames for each channel
|
|
2024
2063
|
>>> nbr_channels = 3 # Total channels in the movie
|
|
2025
2064
|
>>> img_num_per_channel = _get_img_num_per_channel(channels_indices, len_movie, nbr_channels)
|
|
2026
2065
|
>>> print(img_num_per_channel)
|
|
2027
|
-
# [[
|
|
2028
|
-
|
|
2029
|
-
|
|
2066
|
+
# array([[ 1, 4, 7, 10, 13, 16, 19, 22, 25, 28],
|
|
2067
|
+
# [ 2, 5, 8, 11, 14, 17, 20, 23, 26, 29]])
|
|
2068
|
+
|
|
2030
2069
|
"""
|
|
2031
2070
|
|
|
2071
|
+
if isinstance(channels_indices, (int, np.int_)):
|
|
2072
|
+
channels_indices = [channels_indices]
|
|
2073
|
+
|
|
2032
2074
|
len_movie = int(len_movie)
|
|
2033
2075
|
nbr_channels = int(nbr_channels)
|
|
2034
2076
|
|
|
@@ -2039,7 +2081,8 @@ def _get_img_num_per_channel(channels_indices, len_movie, nbr_channels):
|
|
|
2039
2081
|
else:
|
|
2040
2082
|
indices = [-1]*len_movie
|
|
2041
2083
|
img_num_all_channels.append(indices)
|
|
2042
|
-
img_num_all_channels = np.array(img_num_all_channels, dtype=int)
|
|
2084
|
+
img_num_all_channels = np.array(img_num_all_channels, dtype=int)
|
|
2085
|
+
|
|
2043
2086
|
return img_num_all_channels
|
|
2044
2087
|
|
|
2045
2088
|
def _extract_labels_from_config(config,number_of_wells):
|
|
@@ -2063,6 +2106,9 @@ def _extract_labels_from_config(config,number_of_wells):
|
|
|
2063
2106
|
labels: string of the biological condition for each well
|
|
2064
2107
|
|
|
2065
2108
|
"""
|
|
2109
|
+
|
|
2110
|
+
# Deprecated, need to read metadata to extract concentration units and discard non essential fields
|
|
2111
|
+
|
|
2066
2112
|
|
|
2067
2113
|
try:
|
|
2068
2114
|
concentrations = ConfigSectionMap(config,"Labels")["concentrations"].split(",")
|
|
@@ -2082,20 +2128,15 @@ def _extract_labels_from_config(config,number_of_wells):
|
|
|
2082
2128
|
|
|
2083
2129
|
return(labels)
|
|
2084
2130
|
|
|
2085
|
-
|
|
2131
|
+
|
|
2132
|
+
def _extract_channels_from_config(config):
|
|
2086
2133
|
|
|
2087
2134
|
"""
|
|
2088
2135
|
Extracts channel names and their indices from an experiment configuration.
|
|
2089
2136
|
|
|
2090
|
-
This function attempts to parse channel information from a given configuration object, supporting
|
|
2091
|
-
both a newer (V2) and a legacy format. It first tries to extract channel names and indices according
|
|
2092
|
-
to the V2 format from the "Channels" section. If no channels are found or if the section does not
|
|
2093
|
-
exist, it falls back to extracting specific channel information from the "MovieSettings" section
|
|
2094
|
-
based on predefined channel names.
|
|
2095
|
-
|
|
2096
2137
|
Parameters
|
|
2097
2138
|
----------
|
|
2098
|
-
config :
|
|
2139
|
+
config : path to config file (.ini)
|
|
2099
2140
|
The configuration object parsed from an experiment's .ini or similar configuration file.
|
|
2100
2141
|
|
|
2101
2142
|
Returns
|
|
@@ -2105,23 +2146,17 @@ def extract_experiment_channels(config):
|
|
|
2105
2146
|
the names of the channels as specified in the configuration, and `channel_indices` includes their
|
|
2106
2147
|
corresponding indices. Both arrays are ordered according to the channel indices.
|
|
2107
2148
|
|
|
2108
|
-
Notes
|
|
2109
|
-
-----
|
|
2110
|
-
- The function supports extracting a variety of channel types, including brightfield, live and dead nuclei
|
|
2111
|
-
channels, effector fluorescence channels, adhesion channels, and generic fluorescence channels.
|
|
2112
|
-
- If channel information cannot be parsed or if required fields are missing, the function returns empty arrays.
|
|
2113
|
-
- This utility is particularly useful for preprocessing steps where specific channels of multi-channel
|
|
2114
|
-
experimental data are needed for further analysis or model input.
|
|
2115
|
-
|
|
2116
2149
|
Examples
|
|
2117
2150
|
--------
|
|
2118
|
-
>>> config =
|
|
2119
|
-
>>> config
|
|
2120
|
-
>>>
|
|
2121
|
-
#
|
|
2151
|
+
>>> config = "path/to/config_file.ini"
|
|
2152
|
+
>>> channels, indices = _extract_channels_from_config(config)
|
|
2153
|
+
>>> print(channels)
|
|
2154
|
+
# array(['brightfield_channel', 'adhesion_channel', 'fitc_channel',
|
|
2155
|
+
# 'cy5_channel'], dtype='<U19')
|
|
2156
|
+
>>> print(indices)
|
|
2157
|
+
# array([0, 1, 2, 3])
|
|
2122
2158
|
"""
|
|
2123
2159
|
|
|
2124
|
-
# V2
|
|
2125
2160
|
channel_names = []
|
|
2126
2161
|
channel_indices = []
|
|
2127
2162
|
try:
|
|
@@ -2135,64 +2170,7 @@ def extract_experiment_channels(config):
|
|
|
2135
2170
|
pass
|
|
2136
2171
|
except:
|
|
2137
2172
|
pass
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
if not channel_names:
|
|
2141
|
-
# LEGACY
|
|
2142
|
-
# Remap intensities to channel:
|
|
2143
|
-
channel_names = []
|
|
2144
|
-
channel_indices = []
|
|
2145
|
-
|
|
2146
|
-
try:
|
|
2147
|
-
brightfield_channel = int(ConfigSectionMap(config,"MovieSettings")["brightfield_channel"])
|
|
2148
|
-
channel_names.append("brightfield_channel")
|
|
2149
|
-
channel_indices.append(brightfield_channel)
|
|
2150
|
-
#exp_channels.update({"brightfield_channel": brightfield_channel})
|
|
2151
|
-
except:
|
|
2152
|
-
pass
|
|
2153
|
-
try:
|
|
2154
|
-
live_nuclei_channel = int(ConfigSectionMap(config,"MovieSettings")["live_nuclei_channel"])
|
|
2155
|
-
channel_names.append("live_nuclei_channel")
|
|
2156
|
-
channel_indices.append(live_nuclei_channel)
|
|
2157
|
-
#exp_channels.update({"live_nuclei_channel": live_nuclei_channel})
|
|
2158
|
-
except:
|
|
2159
|
-
pass
|
|
2160
|
-
try:
|
|
2161
|
-
dead_nuclei_channel = int(ConfigSectionMap(config,"MovieSettings")["dead_nuclei_channel"])
|
|
2162
|
-
channel_names.append("dead_nuclei_channel")
|
|
2163
|
-
channel_indices.append(dead_nuclei_channel)
|
|
2164
|
-
#exp_channels.update({"dead_nuclei_channel": dead_nuclei_channel})
|
|
2165
|
-
except:
|
|
2166
|
-
pass
|
|
2167
|
-
try:
|
|
2168
|
-
effector_fluo_channel = int(ConfigSectionMap(config,"MovieSettings")["effector_fluo_channel"])
|
|
2169
|
-
channel_names.append("effector_fluo_channel")
|
|
2170
|
-
channel_indices.append(effector_fluo_channel)
|
|
2171
|
-
#exp_channels.update({"effector_fluo_channel": effector_fluo_channel})
|
|
2172
|
-
except:
|
|
2173
|
-
pass
|
|
2174
|
-
try:
|
|
2175
|
-
adhesion_channel = int(ConfigSectionMap(config,"MovieSettings")["adhesion_channel"])
|
|
2176
|
-
channel_names.append("adhesion_channel")
|
|
2177
|
-
channel_indices.append(adhesion_channel)
|
|
2178
|
-
#exp_channels.update({"adhesion_channel": adhesion_channel})
|
|
2179
|
-
except:
|
|
2180
|
-
pass
|
|
2181
|
-
try:
|
|
2182
|
-
fluo_channel_1 = int(ConfigSectionMap(config,"MovieSettings")["fluo_channel_1"])
|
|
2183
|
-
channel_names.append("fluo_channel_1")
|
|
2184
|
-
channel_indices.append(fluo_channel_1)
|
|
2185
|
-
#exp_channels.update({"fluo_channel_1": fluo_channel_1})
|
|
2186
|
-
except:
|
|
2187
|
-
pass
|
|
2188
|
-
try:
|
|
2189
|
-
fluo_channel_2 = int(ConfigSectionMap(config,"MovieSettings")["fluo_channel_2"])
|
|
2190
|
-
channel_names.append("fluo_channel_2")
|
|
2191
|
-
channel_indices.append(fluo_channel_2)
|
|
2192
|
-
#exp_channels.update({"fluo_channel_2": fluo_channel_2})
|
|
2193
|
-
except:
|
|
2194
|
-
pass
|
|
2195
|
-
|
|
2173
|
+
|
|
2196
2174
|
channel_indices = np.array(channel_indices)
|
|
2197
2175
|
channel_names = np.array(channel_names)
|
|
2198
2176
|
reorder = np.argsort(channel_indices)
|
|
@@ -2201,61 +2179,94 @@ def extract_experiment_channels(config):
|
|
|
2201
2179
|
|
|
2202
2180
|
return channel_names, channel_indices
|
|
2203
2181
|
|
|
2182
|
+
|
|
2183
|
+
def extract_experiment_channels(experiment):
|
|
2184
|
+
|
|
2185
|
+
"""
|
|
2186
|
+
Extracts channel names and their indices from an experiment project.
|
|
2187
|
+
|
|
2188
|
+
Parameters
|
|
2189
|
+
----------
|
|
2190
|
+
experiment : str
|
|
2191
|
+
The file system path to the directory of the experiment project.
|
|
2192
|
+
|
|
2193
|
+
Returns
|
|
2194
|
+
-------
|
|
2195
|
+
tuple
|
|
2196
|
+
A tuple containing two numpy arrays: `channel_names` and `channel_indices`. `channel_names` includes
|
|
2197
|
+
the names of the channels as specified in the configuration, and `channel_indices` includes their
|
|
2198
|
+
corresponding indices. Both arrays are ordered according to the channel indices.
|
|
2199
|
+
|
|
2200
|
+
Examples
|
|
2201
|
+
--------
|
|
2202
|
+
>>> experiment = "path/to/my_experiment"
|
|
2203
|
+
>>> channels, indices = extract_experiment_channels(experiment)
|
|
2204
|
+
>>> print(channels)
|
|
2205
|
+
# array(['brightfield_channel', 'adhesion_channel', 'fitc_channel',
|
|
2206
|
+
# 'cy5_channel'], dtype='<U19')
|
|
2207
|
+
>>> print(indices)
|
|
2208
|
+
# array([0, 1, 2, 3])
|
|
2209
|
+
"""
|
|
2210
|
+
|
|
2211
|
+
config = get_config(experiment)
|
|
2212
|
+
return _extract_channels_from_config(config)
|
|
2213
|
+
|
|
2214
|
+
|
|
2204
2215
|
def get_software_location():
|
|
2205
|
-
return rf"{os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]}"
|
|
2206
2216
|
|
|
2207
|
-
|
|
2217
|
+
"""
|
|
2218
|
+
Get the installation folder of celldetective.
|
|
2208
2219
|
|
|
2220
|
+
Returns
|
|
2221
|
+
-------
|
|
2222
|
+
str
|
|
2223
|
+
Path to the celldetective installation folder.
|
|
2209
2224
|
"""
|
|
2210
|
-
Filters a DataFrame of trajectory measurements to retain only essential tracking and classification columns.
|
|
2211
2225
|
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2226
|
+
return rf"{os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]}"
|
|
2227
|
+
|
|
2228
|
+
def remove_trajectory_measurements(trajectories, column_labels={'track': "TRACK_ID", 'time': 'FRAME', 'x': 'POSITION_X', 'y': 'POSITION_Y'}):
|
|
2229
|
+
|
|
2230
|
+
"""
|
|
2231
|
+
Clear a measurement table, while keeping the tracking information.
|
|
2216
2232
|
|
|
2217
2233
|
Parameters
|
|
2218
2234
|
----------
|
|
2219
2235
|
trajectories : pandas.DataFrame
|
|
2220
|
-
The
|
|
2221
|
-
column_labels : dict
|
|
2222
|
-
|
|
2223
|
-
|
|
2236
|
+
The measurement table where each line is a cell at a timepoint and each column a tracking feature or measurement.
|
|
2237
|
+
column_labels : dict, optional
|
|
2238
|
+
The column labels to use in the output DataFrame. Default is {'track': "TRACK_ID", 'time': 'FRAME', 'x': 'POSITION_X', 'y': 'POSITION_Y'}.
|
|
2239
|
+
|
|
2224
2240
|
|
|
2225
2241
|
Returns
|
|
2226
2242
|
-------
|
|
2227
2243
|
pandas.DataFrame
|
|
2228
|
-
A filtered DataFrame containing only the
|
|
2229
|
-
in `trajectories`.
|
|
2230
|
-
|
|
2231
|
-
Notes
|
|
2232
|
-
-----
|
|
2233
|
-
- The function dynamically adjusts the list of columns to retain based on their presence in the input DataFrame,
|
|
2234
|
-
ensuring compatibility with DataFrames containing varying sets of measurements.
|
|
2235
|
-
- Essential columns include tracking identifiers, time points, spatial coordinates (both pixel and physical units),
|
|
2236
|
-
classification labels, state information, lineage metadata, and visualization attributes.
|
|
2244
|
+
A filtered DataFrame containing only the tracking columns.
|
|
2237
2245
|
|
|
2238
2246
|
Examples
|
|
2239
2247
|
--------
|
|
2240
|
-
>>> column_labels = {
|
|
2241
|
-
... 'track': 'TRACK_ID', 'time': 'FRAME', 'x': 'POSITION_X', 'y': 'POSITION_Y'
|
|
2242
|
-
... }
|
|
2243
2248
|
>>> trajectories_df = pd.DataFrame({
|
|
2244
2249
|
... 'TRACK_ID': [1, 1, 2],
|
|
2245
2250
|
... 'FRAME': [0, 1, 0],
|
|
2246
2251
|
... 'POSITION_X': [100, 105, 200],
|
|
2247
2252
|
... 'POSITION_Y': [150, 155, 250],
|
|
2248
|
-
... '
|
|
2253
|
+
... 'area': [10,100,100], # Additional column to be removed
|
|
2249
2254
|
... })
|
|
2250
|
-
>>> filtered_df = remove_trajectory_measurements(trajectories_df
|
|
2251
|
-
|
|
2255
|
+
>>> filtered_df = remove_trajectory_measurements(trajectories_df)
|
|
2256
|
+
>>> print(filtered_df)
|
|
2257
|
+
# pd.DataFrame({
|
|
2258
|
+
# 'TRACK_ID': [1, 1, 2],
|
|
2259
|
+
# 'FRAME': [0, 1, 0],
|
|
2260
|
+
# 'POSITION_X': [100, 105, 200],
|
|
2261
|
+
# 'POSITION_Y': [150, 155, 250],
|
|
2262
|
+
# })
|
|
2252
2263
|
"""
|
|
2253
2264
|
|
|
2254
2265
|
tracks = trajectories.copy()
|
|
2255
2266
|
|
|
2256
2267
|
columns_to_keep = [column_labels['track'], column_labels['time'], column_labels['x'], column_labels['y'],column_labels['x']+'_um', column_labels['y']+'_um', 'class_id',
|
|
2257
2268
|
't', 'state', 'generation', 'root', 'parent', 'ID', 't0', 'class', 'status', 'class_color', 'status_color', 'class_firstdetection', 't_firstdetection', 'velocity']
|
|
2258
|
-
cols = tracks.columns
|
|
2269
|
+
cols = list(tracks.columns)
|
|
2259
2270
|
for c in columns_to_keep:
|
|
2260
2271
|
if c not in cols:
|
|
2261
2272
|
columns_to_keep.remove(c)
|
|
@@ -1,47 +1,47 @@
|
|
|
1
1
|
celldetective/__init__.py,sha256=bi3SGTMo6s2qQBsJAaKy-a4xaGcTQVW8zsqaiX5XKeY,139
|
|
2
2
|
celldetective/__main__.py,sha256=bxTlSvbKhqn3LW_azd2baDCnDsgb37PAP9DfuAJ1_5M,1844
|
|
3
|
-
celldetective/_version.py,sha256=
|
|
3
|
+
celldetective/_version.py,sha256=SaWgUI6v92kVfF_Qdoxbfc38bwA34RuDGZmXMqa5g3c,22
|
|
4
4
|
celldetective/events.py,sha256=UkjY_-THo6WviWWCLnDbma7jWOd_O9a60C4IOX2htG8,8254
|
|
5
|
-
celldetective/extra_properties.py,sha256=
|
|
6
|
-
celldetective/filters.py,sha256=
|
|
7
|
-
celldetective/io.py,sha256=
|
|
8
|
-
celldetective/measure.py,sha256=
|
|
5
|
+
celldetective/extra_properties.py,sha256=PUs-lltSj_jSko_Gd-w483OxZW6oR3BAHQrw51DUxMg,8081
|
|
6
|
+
celldetective/filters.py,sha256=6pl2IGPK2FH8KPWbjzQEbT4C-ruqE1fQ8NQNN7Euvy8,4433
|
|
7
|
+
celldetective/io.py,sha256=HEy9KbidrPVbzzbC1kJcXR0TsvIxNgRiDk31ecdjxpE,121954
|
|
8
|
+
celldetective/measure.py,sha256=h1F08Vf7sy-20syMFZTUl1lHzE4h6cQvswJpwD9fcfE,58475
|
|
9
9
|
celldetective/neighborhood.py,sha256=s-zVsfGnPlqs6HlDJCXRh21lLiPKbA_S1JC6uZvfG_0,56712
|
|
10
10
|
celldetective/preprocessing.py,sha256=Wlt_PJua97CpMuWe_M65znUmz_yjYPqWIcs2ZK_RLgk,44109
|
|
11
11
|
celldetective/relative_measurements.py,sha256=-GWig0lC5UWAcJSPlo9Sp45khGj80fxuQfFk-bdBca0,30117
|
|
12
|
-
celldetective/segmentation.py,sha256=
|
|
12
|
+
celldetective/segmentation.py,sha256=WApzoU1s3Ntvp0eIw_pRZXNwCA8LDKC9YoyR52tioz4,30943
|
|
13
13
|
celldetective/signals.py,sha256=nMyyGUpla8D2sUYKY1zjbWsAueVPI_gUalY0KXfWteI,111595
|
|
14
14
|
celldetective/tracking.py,sha256=VBJLd-1EeJarOVPBNEomhVBh9UYAMdSnH0tmUiUoTrY,40242
|
|
15
|
-
celldetective/utils.py,sha256=
|
|
15
|
+
celldetective/utils.py,sha256=RoNkCGCs9V3EiIn63N20qNKBQ_-H9bogF5LgByQ2UZo,106316
|
|
16
16
|
celldetective/datasets/segmentation_annotations/blank,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
celldetective/datasets/signal_annotations/blank,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
18
|
celldetective/gui/InitWindow.py,sha256=TPKWYczhPHvPKWg654sXnE9xCEx6U-oWX0_OJgLTWbU,15044
|
|
19
19
|
celldetective/gui/__init__.py,sha256=2_r2xfOj4_2xj0yBkCTIfzlF94AHKm-j6Pvpd7DddQc,989
|
|
20
20
|
celldetective/gui/about.py,sha256=FJZrj6C-p6uqp_3UaprKosuW-Sw9_HPNQAvFbis9Gdk,1749
|
|
21
21
|
celldetective/gui/analyze_block.py,sha256=h0sp7Tk3hZFM8w0yTidwIjZX4WRI-lQF3JCFObrLCPo,2761
|
|
22
|
-
celldetective/gui/btrack_options.py,sha256=
|
|
22
|
+
celldetective/gui/btrack_options.py,sha256=F2wrhuaNSaBr6vNApI22XSjdI5Cl3q138qCYBk-FDu4,44684
|
|
23
23
|
celldetective/gui/classifier_widget.py,sha256=nHWmaWXse2CxxRFmR4mA0JRADr5i0MsWldaqJQIQ-i8,19992
|
|
24
24
|
celldetective/gui/configure_new_exp.py,sha256=N4SdL-4Xo0XbTAkCtt1ywr74HmHc5eaPlHGv1XgxAX0,20772
|
|
25
|
-
celldetective/gui/control_panel.py,sha256=
|
|
25
|
+
celldetective/gui/control_panel.py,sha256=jFpedm03x6_jKabeadsNiigKnlh7sXCkulwe4gMCJaM,22042
|
|
26
26
|
celldetective/gui/generic_signal_plot.py,sha256=Gv4KhA5vhbgVSj5jteE42T0aNCoQZtmIAUkEsMi6JNA,36309
|
|
27
|
-
celldetective/gui/gui_utils.py,sha256=
|
|
27
|
+
celldetective/gui/gui_utils.py,sha256=lF3YqMecN_xEyFN_MS0j2KWYZoCnOvMmZeMj4lTRFu4,39335
|
|
28
28
|
celldetective/gui/json_readers.py,sha256=SkI_bR1MlAKd8NLBmQUVjJfZ7mKiU_FEjgC4dUwBACk,3698
|
|
29
29
|
celldetective/gui/layouts.py,sha256=XsqiHR58DXsG5SSD5S8KOtUv4yw-y-s2_wZx_XsHeJs,52013
|
|
30
|
-
celldetective/gui/measurement_options.py,sha256=
|
|
30
|
+
celldetective/gui/measurement_options.py,sha256=sxtQPKSixVbrFcBC1XLLC4-h7ZHlgTLcPfVAW2RVV7w,40357
|
|
31
31
|
celldetective/gui/neighborhood_options.py,sha256=FBNDvlzMPKp8s0Grxds90pCPHG1s27XrpMN0HV2gf0I,19839
|
|
32
32
|
celldetective/gui/plot_measurements.py,sha256=n0pDUcYcsKlSMaUaBSVplGziuWp_7jKaeXdREs-MqyI,50848
|
|
33
|
-
celldetective/gui/plot_signals_ui.py,sha256=
|
|
34
|
-
celldetective/gui/process_block.py,sha256=
|
|
33
|
+
celldetective/gui/plot_signals_ui.py,sha256=7jCu-wLk_NDL4ThLZwhltODngre8iqPMUcEhJNNllo8,18189
|
|
34
|
+
celldetective/gui/process_block.py,sha256=dH0zHIEouL8ApAD4uN7IR9dSl8X8QqE-hXL6U7HZHaM,71212
|
|
35
35
|
celldetective/gui/retrain_segmentation_model_options.py,sha256=7iawDN4kwq56Z-dX9kQe9tLW8B3YMrIW_D85LMAAYwk,23906
|
|
36
36
|
celldetective/gui/retrain_signal_model_options.py,sha256=GCa0WKKsgmH2CFDHAKxPGbHtCE19p1_bbcWNasyZw5o,22482
|
|
37
37
|
celldetective/gui/seg_model_loader.py,sha256=b1BiHuAf_ZqroE4jSEVCo7ASQv-xyWMPWU799alpbNM,19727
|
|
38
|
-
celldetective/gui/signal_annotator.py,sha256=
|
|
39
|
-
celldetective/gui/signal_annotator2.py,sha256=
|
|
40
|
-
celldetective/gui/signal_annotator_options.py,sha256=
|
|
38
|
+
celldetective/gui/signal_annotator.py,sha256=lf15PWZoWx2S1qZtrMsbBeZKtwVEKTjwwyIIG-XHdQA,89541
|
|
39
|
+
celldetective/gui/signal_annotator2.py,sha256=RhcMX32FH_zg8GgAofGIyda9H8euSFADau301qi1NU0,106342
|
|
40
|
+
celldetective/gui/signal_annotator_options.py,sha256=Tq20tybpelHFUqFCSemqH9es2FqM8S3I6Fab-u0FK0A,11015
|
|
41
41
|
celldetective/gui/styles.py,sha256=SZy_ACkA6QB_4ANyY1V0m1QF66C0SVGssOrwW1Qt1Aw,5076
|
|
42
|
-
celldetective/gui/survival_ui.py,sha256=
|
|
42
|
+
celldetective/gui/survival_ui.py,sha256=Q-530cAK-SsKPr8sWFTbhGekB2CFLWd0SNjtV1Wr0oo,14202
|
|
43
43
|
celldetective/gui/tableUI.py,sha256=Yz_pHk1ERXRb0QsBPrvLEwAGpvVlawgn1b6uzz5wL_0,58022
|
|
44
|
-
celldetective/gui/thresholds_gui.py,sha256=
|
|
44
|
+
celldetective/gui/thresholds_gui.py,sha256=O7NkeaPV1nnQdTbduXd3lMttWPBzKcJ-9jI5OMr8H-0,48808
|
|
45
45
|
celldetective/gui/viewers.py,sha256=HDLB6j1FJwgKR6dQwzeHmcDvDMbDIYwD2svd-VZhJFE,47806
|
|
46
46
|
celldetective/gui/workers.py,sha256=P4qUMXuCtGcggGmJr3VitAPSfRG30wkJ1B0pfcdGbKg,4225
|
|
47
47
|
celldetective/gui/help/DL-segmentation-strategy.json,sha256=PZD9xXjrwbX3TiudHJPuvcyZD28o4k-fVgeTd7dBKzI,1583
|
|
@@ -56,9 +56,9 @@ celldetective/gui/help/propagate-classification.json,sha256=F7Ir1mtgRVTXWLN7n3ny
|
|
|
56
56
|
celldetective/gui/help/track-postprocessing.json,sha256=VaGd8EEkA33OL-EI3NXWZ8yHeWWyUeImDF5yAjsVYGA,3990
|
|
57
57
|
celldetective/gui/help/tracking.json,sha256=yIAoOToqCSQ_XF4gwEZCcyXcvQ3mROju263ZPDvlUyY,776
|
|
58
58
|
celldetective/gui/processes/downloader.py,sha256=SuMTuM82QOZBqLfj36I14fhZ2k3NmLp0PBcGUHxnpXI,3287
|
|
59
|
-
celldetective/gui/processes/measure_cells.py,sha256=
|
|
60
|
-
celldetective/gui/processes/segment_cells.py,sha256=
|
|
61
|
-
celldetective/gui/processes/track_cells.py,sha256=
|
|
59
|
+
celldetective/gui/processes/measure_cells.py,sha256=MLkVLLuUtVVgwQB9a9N8eZ5TzF-Xtuhzb5wtcFRa1lU,12846
|
|
60
|
+
celldetective/gui/processes/segment_cells.py,sha256=49Bh3nejkrxlLNhlP3Y_6-flkBOhkqFMQmrc2qDj7Dk,11320
|
|
61
|
+
celldetective/gui/processes/track_cells.py,sha256=y8ohHAMcGtAjrJOP1WqvDfy_K85LbmdXoN7CA7BYpaY,9942
|
|
62
62
|
celldetective/gui/processes/train_segmentation_model.py,sha256=bvcPG19hBjhNY9hd6Ch5_wk2FOJYQg97Azoz4RKeP-0,10776
|
|
63
63
|
celldetective/gui/processes/train_signal_model.py,sha256=qqqkq9gdvNyvycYkmzWgRXWvsbEozyzNWP_POGvnlIs,3816
|
|
64
64
|
celldetective/icons/logo-large.png,sha256=FXSwV3u6zEKcfpuSn4unnqB0oUnN9cHqQ9BCKWytrpg,36631
|
|
@@ -98,9 +98,12 @@ celldetective/models/tracking_configs/mcf7.json,sha256=iDjb8i6yxs0GleW39dvY3Ld5b
|
|
|
98
98
|
celldetective/models/tracking_configs/no_z_motion.json,sha256=b4RWOJ0w6Y2e0vJYwKMyOexedeL2eA8fEDbSzbNmB4A,2702
|
|
99
99
|
celldetective/models/tracking_configs/ricm.json,sha256=L-vmwCR1f89U-qnH2Ms0cBfPFR_dxIWoe2ccH8V-QBA,2727
|
|
100
100
|
celldetective/models/tracking_configs/ricm2.json,sha256=DDjJ6ScYcDWvlsy7ujPID8v8H28vcNcMuZmNR8XmGxo,2718
|
|
101
|
+
celldetective/regionprops/__init__.py,sha256=ohe9vC7j4lnpKnGXidVo1PVfoQfC8TqCuHTNo8rXmdg,44
|
|
102
|
+
celldetective/regionprops/_regionprops.py,sha256=K1yHZDdLe81N9w73XMmRVLVsaMXJ3IE5VLP8ui6QsL4,9855
|
|
103
|
+
celldetective/regionprops/props.json,sha256=sCwACmbh0n-JAw9eve9yV85REukoMBJLsRjxCwTRMm8,2424
|
|
101
104
|
celldetective/scripts/analyze_signals.py,sha256=YE05wZujl2hQFWkvqATBcCx-cAd_V3RxnvKoh0SB7To,2194
|
|
102
105
|
celldetective/scripts/measure_cells.py,sha256=GvuHrSb3S-JnUjBaACx84BhUd_vUrwEHiSgDV9BoXXE,11766
|
|
103
|
-
celldetective/scripts/measure_relative.py,sha256=
|
|
106
|
+
celldetective/scripts/measure_relative.py,sha256=wI7ibgWCsh5NVfcYum9LK0khL7cwKyCxwHFaXG4oWjM,3698
|
|
104
107
|
celldetective/scripts/segment_cells.py,sha256=AZfHqig0WBMkAh9ho7JHmjM9vMO-EiPVviTHJcDj5Mw,6954
|
|
105
108
|
celldetective/scripts/segment_cells_thresholds.py,sha256=lsW5hsHenNPiyIwZuZHipeCN2Wer69VcLko5rS96GAU,5118
|
|
106
109
|
celldetective/scripts/track_cells.py,sha256=_OPfn9BsbvdpAU4dg36SXmOTx_BkDkZL4N05X0_Tig4,8795
|
|
@@ -118,9 +121,9 @@ tests/test_segmentation.py,sha256=k1b_zIZdlytEdJcHjAUQEO3gTBAHtv5WvrwQN2xD4kc,34
|
|
|
118
121
|
tests/test_signals.py,sha256=No4cah6KxplhDcKXnU8RrA7eDla4hWw6ccf7xGnBokU,3599
|
|
119
122
|
tests/test_tracking.py,sha256=8hebWSqEIuttD1ABn-6dKCT7EXKRR7-4RwyFWi1WPFo,8800
|
|
120
123
|
tests/test_utils.py,sha256=NKRCAC1d89aBK5cWjTb7-pInYow901RrT-uBlIdz4KI,3692
|
|
121
|
-
celldetective-1.3.
|
|
122
|
-
celldetective-1.3.
|
|
123
|
-
celldetective-1.3.
|
|
124
|
-
celldetective-1.3.
|
|
125
|
-
celldetective-1.3.
|
|
126
|
-
celldetective-1.3.
|
|
124
|
+
celldetective-1.3.9.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
125
|
+
celldetective-1.3.9.dist-info/METADATA,sha256=POcWapSbuacLCmplgT1JURcBQIQ25jNSigcN5ltnx5s,10747
|
|
126
|
+
celldetective-1.3.9.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
127
|
+
celldetective-1.3.9.dist-info/entry_points.txt,sha256=2NU6_EOByvPxqBbCvjwxlVlvnQreqZ3BKRCVIKEv3dg,62
|
|
128
|
+
celldetective-1.3.9.dist-info/top_level.txt,sha256=6rsIKKfGMKgud7HPuATcpq6EhdXwcg_yknBVWn9x4C4,20
|
|
129
|
+
celldetective-1.3.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|