celldetective 1.1.1__py3-none-any.whl → 1.1.1.post1__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/extra_properties.py +1 -1
- celldetective/filters.py +39 -11
- celldetective/gui/classifier_widget.py +56 -7
- celldetective/gui/layouts.py +2 -2
- celldetective/gui/tableUI.py +15 -1
- celldetective/gui/thresholds_gui.py +44 -5
- celldetective/io.py +29 -14
- celldetective/scripts/segment_cells.py +12 -4
- celldetective/segmentation.py +16 -12
- celldetective/utils.py +3 -0
- {celldetective-1.1.1.dist-info → celldetective-1.1.1.post1.dist-info}/METADATA +1 -1
- {celldetective-1.1.1.dist-info → celldetective-1.1.1.post1.dist-info}/RECORD +16 -16
- {celldetective-1.1.1.dist-info → celldetective-1.1.1.post1.dist-info}/WHEEL +1 -1
- {celldetective-1.1.1.dist-info → celldetective-1.1.1.post1.dist-info}/LICENSE +0 -0
- {celldetective-1.1.1.dist-info → celldetective-1.1.1.post1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.1.1.dist-info → celldetective-1.1.1.post1.dist-info}/top_level.txt +0 -0
|
@@ -7,7 +7,7 @@ If intensity is in function name, it will be replaced by the name of the channel
|
|
|
7
7
|
import warnings
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
|
-
from scipy.ndimage import distance_transform_edt
|
|
10
|
+
from scipy.ndimage import distance_transform_edt, center_of_mass
|
|
11
11
|
from scipy.spatial.distance import euclidean
|
|
12
12
|
|
|
13
13
|
|
celldetective/filters.py
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
1
|
from skimage.filters import difference_of_gaussians, threshold_otsu, threshold_local, threshold_niblack, threshold_sauvola
|
|
2
|
+
from celldetective.utils import interpolate_nan
|
|
2
3
|
import scipy.ndimage as snd
|
|
3
4
|
import numpy as np
|
|
4
5
|
|
|
5
|
-
def gauss_filter(img, sigma, *kwargs):
|
|
6
|
+
def gauss_filter(img, sigma, interpolate=True, *kwargs):
|
|
7
|
+
if interpolate:
|
|
8
|
+
img = interpolate_nan(img.astype(float))
|
|
6
9
|
return snd.gaussian_filter(img.astype(float), sigma, *kwargs)
|
|
7
10
|
|
|
8
|
-
def median_filter(img, size, *kwargs):
|
|
11
|
+
def median_filter(img, size, interpolate=True, *kwargs):
|
|
12
|
+
if interpolate:
|
|
13
|
+
img = interpolate_nan(img.astype(float))
|
|
14
|
+
|
|
9
15
|
size = int(size)
|
|
10
16
|
return snd.median_filter(img, size, *kwargs)
|
|
11
17
|
|
|
12
|
-
def maximum_filter(img, size, *kwargs):
|
|
18
|
+
def maximum_filter(img, size, interpolate=True, *kwargs):
|
|
19
|
+
if interpolate:
|
|
20
|
+
img = interpolate_nan(img.astype(float))
|
|
21
|
+
|
|
13
22
|
return snd.maximum_filter(img.astype(float), size, *kwargs)
|
|
14
23
|
|
|
15
|
-
def minimum_filter(img, size, *kwargs):
|
|
24
|
+
def minimum_filter(img, size, interpolate=True, *kwargs):
|
|
25
|
+
if interpolate:
|
|
26
|
+
img = interpolate_nan(img.astype(float))
|
|
27
|
+
|
|
16
28
|
return snd.minimum_filter(img.astype(float), size, *kwargs)
|
|
17
29
|
|
|
18
|
-
def percentile_filter(img, percentile, size, *kwargs):
|
|
30
|
+
def percentile_filter(img, percentile, size, interpolate=True, *kwargs):
|
|
31
|
+
if interpolate:
|
|
32
|
+
img = interpolate_nan(img.astype(float))
|
|
33
|
+
|
|
19
34
|
return snd.percentile_filter(img.astype(float), percentile, size, *kwargs)
|
|
20
35
|
|
|
21
36
|
def subtract_filter(img, value, *kwargs):
|
|
@@ -24,14 +39,19 @@ def subtract_filter(img, value, *kwargs):
|
|
|
24
39
|
def abs_filter(img, *kwargs):
|
|
25
40
|
return np.abs(img)
|
|
26
41
|
|
|
27
|
-
def ln_filter(img, *kwargs):
|
|
42
|
+
def ln_filter(img, interpolate=True, *kwargs):
|
|
43
|
+
if interpolate:
|
|
44
|
+
img = interpolate_nan(img.astype(float))
|
|
28
45
|
|
|
29
46
|
img[np.where(img>0.)] = np.log(img[np.where(img>0.)])
|
|
30
47
|
img[np.where(img<=0.)] = 0.
|
|
31
48
|
|
|
32
49
|
return img
|
|
33
50
|
|
|
34
|
-
def variance_filter(img, size):
|
|
51
|
+
def variance_filter(img, size, interpolate=True):
|
|
52
|
+
|
|
53
|
+
if interpolate:
|
|
54
|
+
img = interpolate_nan(img.astype(float))
|
|
35
55
|
|
|
36
56
|
size = int(size)
|
|
37
57
|
img = img.astype(float)
|
|
@@ -41,8 +61,10 @@ def variance_filter(img, size):
|
|
|
41
61
|
|
|
42
62
|
return img
|
|
43
63
|
|
|
44
|
-
def std_filter(img, size):
|
|
64
|
+
def std_filter(img, size, interpolate=True):
|
|
45
65
|
|
|
66
|
+
if interpolate:
|
|
67
|
+
img = interpolate_nan(img.astype(float))
|
|
46
68
|
size = int(size)
|
|
47
69
|
img = img.astype(float)
|
|
48
70
|
win_mean = snd.uniform_filter(img, (size,size), mode='wrap')
|
|
@@ -53,10 +75,14 @@ def std_filter(img, size):
|
|
|
53
75
|
|
|
54
76
|
return img
|
|
55
77
|
|
|
56
|
-
def laplace_filter(img, output=float, *kwargs):
|
|
78
|
+
def laplace_filter(img, output=float, interpolate=True, *kwargs):
|
|
79
|
+
if interpolate:
|
|
80
|
+
img = interpolate_nan(img.astype(float))
|
|
57
81
|
return snd.laplace(img.astype(float), *kwargs)
|
|
58
82
|
|
|
59
|
-
def dog_filter(img, sigma_low, sigma_high, *kwargs):
|
|
83
|
+
def dog_filter(img, sigma_low, sigma_high, interpolate=True, *kwargs):
|
|
84
|
+
if interpolate:
|
|
85
|
+
img = interpolate_nan(img.astype(float))
|
|
60
86
|
return difference_of_gaussians(img.astype(float), sigma_low, sigma_high, *kwargs)
|
|
61
87
|
|
|
62
88
|
def otsu_filter(img, *kwargs):
|
|
@@ -82,7 +108,9 @@ def sauvola_filter(img, *kwargs):
|
|
|
82
108
|
def log_filter(img, sigma, *kwargs):
|
|
83
109
|
return snd.gaussian_laplace(img.astype(float), sigma, *kwargs)
|
|
84
110
|
|
|
85
|
-
def tophat_filter(img, size, connectivity=4, *kwargs):
|
|
111
|
+
def tophat_filter(img, size, connectivity=4, interpolate=True, *kwargs):
|
|
112
|
+
if interpolate:
|
|
113
|
+
img = interpolate_nan(img.astype(float))
|
|
86
114
|
structure = snd.generate_binary_structure(rank=2, connectivity=connectivity)
|
|
87
115
|
img = snd.white_tophat(img.astype(float), structure=structure, size=size, *kwargs)
|
|
88
116
|
return img
|
|
@@ -136,9 +136,10 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
136
136
|
|
|
137
137
|
self.irreversible_event_btn = QRadioButton('irreversible event')
|
|
138
138
|
self.unique_state_btn = QRadioButton('unique state')
|
|
139
|
-
self.time_corr_options = [self.irreversible_event_btn, self.unique_state_btn]
|
|
140
139
|
time_corr_btn_group = QButtonGroup()
|
|
141
140
|
self.unique_state_btn.click()
|
|
141
|
+
self.time_corr_options = [self.irreversible_event_btn, self.unique_state_btn]
|
|
142
|
+
|
|
142
143
|
for btn in self.time_corr_options:
|
|
143
144
|
time_corr_btn_group.addButton(btn)
|
|
144
145
|
btn.setEnabled(False)
|
|
@@ -148,8 +149,28 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
148
149
|
time_corr_layout.addWidget(self.irreversible_event_btn, 50,alignment=Qt.AlignCenter)
|
|
149
150
|
layout.addLayout(time_corr_layout)
|
|
150
151
|
|
|
152
|
+
self.r2_slider = QLabeledDoubleSlider()
|
|
153
|
+
self.r2_slider.setValue(0.75)
|
|
154
|
+
self.r2_slider.setRange(0,1)
|
|
155
|
+
self.r2_slider.setSingleStep(0.01)
|
|
156
|
+
self.r2_slider.setOrientation(1)
|
|
157
|
+
self.r2_label = QLabel('R2 tolerance:')
|
|
158
|
+
self.r2_label.setToolTip('Minimum R2 between the fit sigmoid and the binary response to the filters to accept the event.')
|
|
159
|
+
r2_threshold_layout = QHBoxLayout()
|
|
160
|
+
r2_threshold_layout.addWidget(QLabel(''), 50)
|
|
161
|
+
r2_threshold_layout.addWidget(self.r2_label, 15)
|
|
162
|
+
r2_threshold_layout.addWidget(self.r2_slider, 35)
|
|
163
|
+
layout.addLayout(r2_threshold_layout)
|
|
164
|
+
|
|
165
|
+
self.irreversible_event_btn.clicked.connect(self.activate_r2)
|
|
166
|
+
self.unique_state_btn.clicked.connect(self.activate_r2)
|
|
167
|
+
|
|
168
|
+
for wg in [self.r2_slider, self.r2_label]:
|
|
169
|
+
wg.setEnabled(False)
|
|
170
|
+
|
|
151
171
|
layout.addWidget(QLabel())
|
|
152
172
|
|
|
173
|
+
|
|
153
174
|
self.submit_btn = QPushButton('apply')
|
|
154
175
|
self.submit_btn.setStyleSheet(self.button_style_sheet)
|
|
155
176
|
self.submit_btn.clicked.connect(self.submit_classification)
|
|
@@ -158,14 +179,30 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
158
179
|
self.frame_slider.valueChanged.connect(self.set_frame)
|
|
159
180
|
self.alpha_slider.valueChanged.connect(self.set_transparency)
|
|
160
181
|
|
|
182
|
+
def activate_r2(self):
|
|
183
|
+
if self.irreversible_event_btn.isChecked() and self.time_corr.isChecked():
|
|
184
|
+
for wg in [self.r2_slider, self.r2_label]:
|
|
185
|
+
wg.setEnabled(True)
|
|
186
|
+
else:
|
|
187
|
+
for wg in [self.r2_slider, self.r2_label]:
|
|
188
|
+
wg.setEnabled(False)
|
|
189
|
+
|
|
161
190
|
def activate_time_corr_options(self):
|
|
162
191
|
|
|
163
192
|
if self.time_corr.isChecked():
|
|
164
193
|
for btn in self.time_corr_options:
|
|
165
194
|
btn.setEnabled(True)
|
|
195
|
+
if self.irreversible_event_btn.isChecked():
|
|
196
|
+
for wg in [self.r2_slider, self.r2_label]:
|
|
197
|
+
wg.setEnabled(True)
|
|
198
|
+
else:
|
|
199
|
+
for wg in [self.r2_slider, self.r2_label]:
|
|
200
|
+
wg.setEnabled(False)
|
|
166
201
|
else:
|
|
167
202
|
for btn in self.time_corr_options:
|
|
168
203
|
btn.setEnabled(False)
|
|
204
|
+
for wg in [self.r2_slider, self.r2_label]:
|
|
205
|
+
wg.setEnabled(False)
|
|
169
206
|
|
|
170
207
|
def init_class(self):
|
|
171
208
|
|
|
@@ -207,8 +244,20 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
207
244
|
self.scat_props.set_alpha(self.currentAlpha)
|
|
208
245
|
self.ax_props.set_xlabel(self.features_cb[1].currentText())
|
|
209
246
|
self.ax_props.set_ylabel(self.features_cb[0].currentText())
|
|
210
|
-
|
|
211
|
-
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
feat_x = self.features_cb[1].currentText()
|
|
250
|
+
feat_y = self.features_cb[0].currentText()
|
|
251
|
+
min_x = self.df.dropna(subset=feat_x)[feat_x].min()
|
|
252
|
+
max_x = self.df.dropna(subset=feat_x)[feat_x].max()
|
|
253
|
+
min_y = self.df.dropna(subset=feat_y)[feat_y].min()
|
|
254
|
+
max_y = self.df.dropna(subset=feat_y)[feat_y].max()
|
|
255
|
+
|
|
256
|
+
if min_x==min_x and max_x==max_x:
|
|
257
|
+
self.ax_props.set_xlim(min_x, max_x)
|
|
258
|
+
if min_y==min_y and max_y==max_y:
|
|
259
|
+
self.ax_props.set_ylim(min_y, max_y)
|
|
260
|
+
|
|
212
261
|
if feature_changed:
|
|
213
262
|
self.propscanvas.canvas.toolbar.update()
|
|
214
263
|
self.propscanvas.canvas.draw_idle()
|
|
@@ -400,15 +449,16 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
400
449
|
timeline = group['FRAME'].values
|
|
401
450
|
|
|
402
451
|
try:
|
|
403
|
-
popt, pcov = curve_fit(step_function, timeline, status_signal,p0=[self.df['FRAME'].max()//2, 0.
|
|
404
|
-
|
|
452
|
+
popt, pcov = curve_fit(step_function, timeline.astype(int), status_signal, p0=[self.df['FRAME'].max()//2, 0.8],maxfev=30000)
|
|
453
|
+
values = [step_function(t, *popt) for t in timeline]
|
|
454
|
+
r2 = r2_score(status_signal,values)
|
|
405
455
|
except Exception as e:
|
|
406
456
|
print(e)
|
|
407
457
|
self.df.loc[indices, self.class_name_user] = 2.0
|
|
408
458
|
self.df.loc[indices, self.class_name_user.replace('class','t')] = -1
|
|
409
459
|
continue
|
|
410
460
|
|
|
411
|
-
if r2 >
|
|
461
|
+
if r2 > float(self.r2_slider.value()):
|
|
412
462
|
t0 = popt[0]
|
|
413
463
|
self.df.loc[indices, self.class_name_user.replace('class','t')] = t0
|
|
414
464
|
self.df.loc[indices, self.class_name_user] = 0.0
|
|
@@ -426,4 +476,3 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
426
476
|
|
|
427
477
|
|
|
428
478
|
|
|
429
|
-
|
celldetective/gui/layouts.py
CHANGED
|
@@ -702,12 +702,12 @@ class BackgroundModelFreeCorrectionLayout(QGridLayout, Styles):
|
|
|
702
702
|
show_progress_per_pos = False,
|
|
703
703
|
)
|
|
704
704
|
|
|
705
|
-
|
|
706
705
|
self.viewer = StackVisualizer(
|
|
707
706
|
stack=corrected_stacks[0],
|
|
708
707
|
window_title='Corrected channel',
|
|
709
708
|
frame_slider = True,
|
|
710
|
-
contrast_slider = True
|
|
709
|
+
contrast_slider = True,
|
|
710
|
+
target_channel=self.channels_cb.currentIndex(),
|
|
711
711
|
)
|
|
712
712
|
self.viewer.show()
|
|
713
713
|
|
celldetective/gui/tableUI.py
CHANGED
|
@@ -232,6 +232,11 @@ class TableUI(QMainWindow, Styles):
|
|
|
232
232
|
self.plot_action.setShortcut("Ctrl+p")
|
|
233
233
|
self.fileMenu.addAction(self.plot_action)
|
|
234
234
|
|
|
235
|
+
self.plot_inst_action = QAction("&Plot instantaneous...", self)
|
|
236
|
+
self.plot_inst_action.triggered.connect(self.plot_instantaneous)
|
|
237
|
+
self.plot_inst_action.setShortcut("Ctrl+i")
|
|
238
|
+
self.fileMenu.addAction(self.plot_inst_action)
|
|
239
|
+
|
|
235
240
|
self.groupby_action = QAction("&Group by tracks...", self)
|
|
236
241
|
self.groupby_action.triggered.connect(self.set_projection_mode_tracks)
|
|
237
242
|
self.groupby_action.setShortcut("Ctrl+g")
|
|
@@ -408,7 +413,7 @@ class TableUI(QMainWindow, Styles):
|
|
|
408
413
|
self.status_operation.setEnabled(False)
|
|
409
414
|
self.status_operation.addItems(['mean','median','min','max', 'prod', 'sum'])
|
|
410
415
|
|
|
411
|
-
status_cols = np.array([c.startswith('status_') for c in cols])
|
|
416
|
+
status_cols = np.array([c.startswith('status_') or c.startswith('group_') for c in cols])
|
|
412
417
|
status_cols = list(cols[status_cols])
|
|
413
418
|
if 'status' in list(self.data.columns):
|
|
414
419
|
status_cols.append('status')
|
|
@@ -739,6 +744,15 @@ class TableUI(QMainWindow, Styles):
|
|
|
739
744
|
else:
|
|
740
745
|
return array
|
|
741
746
|
|
|
747
|
+
def plot_instantaneous(self):
|
|
748
|
+
|
|
749
|
+
if self.plot_mode=='plot_track_signals':
|
|
750
|
+
self.plot_mode = 'static'
|
|
751
|
+
self.plot()
|
|
752
|
+
self.plot_mode = 'plot_track_signals'
|
|
753
|
+
elif self.plot_mode=="static":
|
|
754
|
+
self.plot()
|
|
755
|
+
|
|
742
756
|
def plot(self):
|
|
743
757
|
if self.plot_mode == "static":
|
|
744
758
|
|
|
@@ -2,7 +2,7 @@ import math
|
|
|
2
2
|
|
|
3
3
|
import skimage
|
|
4
4
|
from PyQt5.QtWidgets import QAction, QMenu, QMainWindow, QMessageBox, QLabel, QWidget, QFileDialog, QHBoxLayout, \
|
|
5
|
-
QGridLayout, QLineEdit, QScrollArea, QVBoxLayout, QComboBox, QPushButton, QApplication, QPushButton
|
|
5
|
+
QGridLayout, QLineEdit, QScrollArea, QVBoxLayout, QComboBox, QPushButton, QApplication, QPushButton, QRadioButton, QButtonGroup
|
|
6
6
|
from PyQt5.QtGui import QDoubleValidator, QIntValidator
|
|
7
7
|
from matplotlib.backends.backend_qt import NavigationToolbar2QT
|
|
8
8
|
from matplotlib.patches import Circle
|
|
@@ -17,6 +17,7 @@ from celldetective.io import auto_load_number_of_frames, load_frames
|
|
|
17
17
|
from celldetective.segmentation import threshold_image, identify_markers_from_binary, apply_watershed, \
|
|
18
18
|
segment_frame_from_thresholds
|
|
19
19
|
from scipy.ndimage import binary_fill_holes
|
|
20
|
+
import scipy.ndimage as ndi
|
|
20
21
|
from PyQt5.QtCore import Qt, QSize
|
|
21
22
|
from glob import glob
|
|
22
23
|
from superqt.fonticon import icon
|
|
@@ -263,10 +264,20 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
263
264
|
marker_box = QVBoxLayout()
|
|
264
265
|
marker_box.setContentsMargins(30, 30, 30, 30)
|
|
265
266
|
|
|
266
|
-
marker_lbl = QLabel('
|
|
267
|
+
marker_lbl = QLabel('Objects')
|
|
267
268
|
marker_lbl.setStyleSheet("font-weight: bold;")
|
|
268
269
|
marker_box.addWidget(marker_lbl, alignment=Qt.AlignCenter)
|
|
269
270
|
|
|
271
|
+
object_option_hbox = QHBoxLayout()
|
|
272
|
+
self.marker_option = QRadioButton('markers')
|
|
273
|
+
self.all_objects_option = QRadioButton('all non-contiguous objects')
|
|
274
|
+
self.marker_option_group = QButtonGroup()
|
|
275
|
+
self.marker_option_group.addButton(self.marker_option)
|
|
276
|
+
self.marker_option_group.addButton(self.all_objects_option)
|
|
277
|
+
object_option_hbox.addWidget(self.marker_option, 50, alignment=Qt.AlignCenter)
|
|
278
|
+
object_option_hbox.addWidget(self.all_objects_option, 50, alignment=Qt.AlignCenter)
|
|
279
|
+
marker_box.addLayout(object_option_hbox)
|
|
280
|
+
|
|
270
281
|
hbox_footprint = QHBoxLayout()
|
|
271
282
|
hbox_footprint.addWidget(QLabel('Footprint: '), 20)
|
|
272
283
|
self.footprint_slider = QLabeledSlider()
|
|
@@ -275,7 +286,8 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
275
286
|
self.footprint_slider.setRange(1, self.binary.shape[0] // 4)
|
|
276
287
|
self.footprint_slider.setValue(self.footprint)
|
|
277
288
|
self.footprint_slider.valueChanged.connect(self.set_footprint)
|
|
278
|
-
hbox_footprint.addWidget(self.footprint_slider,
|
|
289
|
+
hbox_footprint.addWidget(self.footprint_slider, 30)
|
|
290
|
+
hbox_footprint.addWidget(QLabel(''), 50)
|
|
279
291
|
marker_box.addLayout(hbox_footprint)
|
|
280
292
|
|
|
281
293
|
hbox_distance = QHBoxLayout()
|
|
@@ -286,7 +298,8 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
286
298
|
self.min_dist_slider.setRange(0, self.binary.shape[0] // 4)
|
|
287
299
|
self.min_dist_slider.setValue(self.min_dist)
|
|
288
300
|
self.min_dist_slider.valueChanged.connect(self.set_min_dist)
|
|
289
|
-
hbox_distance.addWidget(self.min_dist_slider,
|
|
301
|
+
hbox_distance.addWidget(self.min_dist_slider, 30)
|
|
302
|
+
hbox_distance.addWidget(QLabel(''), 50)
|
|
290
303
|
marker_box.addLayout(hbox_distance)
|
|
291
304
|
|
|
292
305
|
hbox_marker_btns = QHBoxLayout()
|
|
@@ -305,8 +318,23 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
305
318
|
hbox_marker_btns.addWidget(self.watershed_btn)
|
|
306
319
|
marker_box.addLayout(hbox_marker_btns)
|
|
307
320
|
|
|
321
|
+
self.marker_option.clicked.connect(self.enable_marker_options)
|
|
322
|
+
self.all_objects_option.clicked.connect(self.enable_marker_options)
|
|
323
|
+
self.marker_option.click()
|
|
324
|
+
|
|
308
325
|
self.left_panel.addLayout(marker_box)
|
|
309
326
|
|
|
327
|
+
def enable_marker_options(self):
|
|
328
|
+
if self.marker_option.isChecked():
|
|
329
|
+
self.footprint_slider.setEnabled(True)
|
|
330
|
+
self.min_dist_slider.setEnabled(True)
|
|
331
|
+
self.markers_btn.setEnabled(True)
|
|
332
|
+
else:
|
|
333
|
+
self.footprint_slider.setEnabled(False)
|
|
334
|
+
self.min_dist_slider.setEnabled(False)
|
|
335
|
+
self.markers_btn.setEnabled(False)
|
|
336
|
+
self.watershed_btn.setEnabled(True)
|
|
337
|
+
|
|
310
338
|
def generate_props_contents(self):
|
|
311
339
|
|
|
312
340
|
properties_box = QVBoxLayout()
|
|
@@ -675,7 +703,10 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
675
703
|
|
|
676
704
|
def apply_watershed_to_selection(self):
|
|
677
705
|
|
|
678
|
-
|
|
706
|
+
if self.marker_option.isChecked():
|
|
707
|
+
self.labels = apply_watershed(self.binary, self.coords, self.edt_map)
|
|
708
|
+
else:
|
|
709
|
+
self.labels,_ = ndi.label(self.binary.astype(int))
|
|
679
710
|
|
|
680
711
|
self.current_channel = self.channels_cb.currentIndex()
|
|
681
712
|
t = int(self.frame_slider.value())
|
|
@@ -806,6 +837,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
806
837
|
"marker_footprint_size": self.footprint,
|
|
807
838
|
"feature_queries": [self.property_query_le.text()],
|
|
808
839
|
"equalize_reference": [self.equalize_option, self.frame_slider.value()],
|
|
840
|
+
"do_watershed": self.marker_option.isChecked(),
|
|
809
841
|
}
|
|
810
842
|
|
|
811
843
|
print('The following instructions will be written: ', instructions)
|
|
@@ -873,6 +905,13 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
873
905
|
self.property_query_le.setText(feature_queries[0])
|
|
874
906
|
self.submit_query_btn.click()
|
|
875
907
|
|
|
908
|
+
if 'do_watershed' in threshold_instructions:
|
|
909
|
+
do_watershed = threshold_instructions['do_watershed']
|
|
910
|
+
if do_watershed:
|
|
911
|
+
self.marker_option.click()
|
|
912
|
+
else:
|
|
913
|
+
self.all_objects_option.click()
|
|
914
|
+
|
|
876
915
|
|
|
877
916
|
class ThresholdNormalisation(ThresholdConfigWizard):
|
|
878
917
|
def __init__(self, min_threshold, current_channel, parent_window=None):
|
celldetective/io.py
CHANGED
|
@@ -1238,12 +1238,23 @@ def load_napari_data(position, prefix="Aligned", population="target", return_sta
|
|
|
1238
1238
|
"""
|
|
1239
1239
|
position = position.replace('\\','/')
|
|
1240
1240
|
if population.lower()=="target" or population.lower()=="targets":
|
|
1241
|
-
|
|
1241
|
+
if os.path.exists(position+os.sep.join(['output','tables','napari_target_trajectories.npy'])):
|
|
1242
|
+
napari_data = np.load(position+os.sep.join(['output','tables','napari_target_trajectories.npy']), allow_pickle=True)
|
|
1243
|
+
else:
|
|
1244
|
+
napari_data = None
|
|
1242
1245
|
elif population.lower()=="effector" or population.lower()=="effectors":
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1246
|
+
if os.path.exists(position+os.sep.join(['output', 'tables', 'napari_effector_trajectories.npy'])):
|
|
1247
|
+
napari_data = np.load(position+os.sep.join(['output', 'tables', 'napari_effector_trajectories.npy']), allow_pickle=True)
|
|
1248
|
+
else:
|
|
1249
|
+
napari_data = None
|
|
1250
|
+
if napari_data is not None:
|
|
1251
|
+
data = napari_data.item()['data']
|
|
1252
|
+
properties = napari_data.item()['properties']
|
|
1253
|
+
graph = napari_data.item()['graph']
|
|
1254
|
+
else:
|
|
1255
|
+
data = None
|
|
1256
|
+
properties = None
|
|
1257
|
+
graph = None
|
|
1247
1258
|
if return_stack:
|
|
1248
1259
|
stack,labels = locate_stack_and_labels(position, prefix=prefix, population=population)
|
|
1249
1260
|
else:
|
|
@@ -1968,15 +1979,19 @@ def normalize_multichannel(multichannel_frame, percentiles=None,
|
|
|
1968
1979
|
v = values[c]
|
|
1969
1980
|
else:
|
|
1970
1981
|
v = None
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1982
|
+
|
|
1983
|
+
if np.all(mf[:,:,c]==0.):
|
|
1984
|
+
mf_new.append(mf[:,:,c].copy())
|
|
1985
|
+
else:
|
|
1986
|
+
norm = normalize(mf[:,:,c].copy(),
|
|
1987
|
+
percentiles=percentiles[c],
|
|
1988
|
+
values=v,
|
|
1989
|
+
ignore_gray_value=ignore_gray_value,
|
|
1990
|
+
clip=clip,
|
|
1991
|
+
amplification=amplification,
|
|
1992
|
+
dtype=dtype,
|
|
1993
|
+
)
|
|
1994
|
+
mf_new.append(norm)
|
|
1980
1995
|
|
|
1981
1996
|
return np.moveaxis(mf_new,0,-1)
|
|
1982
1997
|
|
|
@@ -9,7 +9,7 @@ import json
|
|
|
9
9
|
from stardist.models import StarDist2D
|
|
10
10
|
from cellpose.models import CellposeModel
|
|
11
11
|
from celldetective.io import locate_segmentation_model, auto_load_number_of_frames, load_frames
|
|
12
|
-
from celldetective.utils import _estimate_scale_factor, _extract_channel_indices_from_config, _extract_channel_indices, ConfigSectionMap, _extract_nbr_channels_from_config, _get_img_num_per_channel, normalize_per_channel
|
|
12
|
+
from celldetective.utils import interpolate_nan, _estimate_scale_factor, _extract_channel_indices_from_config, _extract_channel_indices, ConfigSectionMap, _extract_nbr_channels_from_config, _get_img_num_per_channel, normalize_per_channel
|
|
13
13
|
from pathlib import Path, PurePath
|
|
14
14
|
from glob import glob
|
|
15
15
|
from shutil import rmtree
|
|
@@ -121,6 +121,7 @@ if model_type=='cellpose':
|
|
|
121
121
|
flow_threshold = input_config['flow_threshold']
|
|
122
122
|
|
|
123
123
|
scale = _estimate_scale_factor(spatial_calibration, required_spatial_calibration)
|
|
124
|
+
print(f"Scale = {scale}...")
|
|
124
125
|
|
|
125
126
|
nbr_channels = _extract_nbr_channels_from_config(config)
|
|
126
127
|
print(f'Number of channels in the input movie: {nbr_channels}')
|
|
@@ -140,6 +141,7 @@ with open(pos+f'log_{mode}.json', 'a') as f:
|
|
|
140
141
|
|
|
141
142
|
# Loop over all frames and segment
|
|
142
143
|
def segment_index(indices):
|
|
144
|
+
global scale
|
|
143
145
|
|
|
144
146
|
if model_type=='stardist':
|
|
145
147
|
model = StarDist2D(None, name=modelname, basedir=Path(model_complete_path).parent)
|
|
@@ -156,7 +158,11 @@ def segment_index(indices):
|
|
|
156
158
|
device = torch.device("cuda")
|
|
157
159
|
|
|
158
160
|
model = CellposeModel(gpu=use_gpu, device=device, pretrained_model=model_complete_path+modelname, model_type=None, nchan=len(required_channels)) #diam_mean=30.0,
|
|
159
|
-
|
|
161
|
+
if scale is None:
|
|
162
|
+
scale_model = model.diam_mean / model.diam_labels
|
|
163
|
+
else:
|
|
164
|
+
scale_model = scale * model.diam_mean / model.diam_labels
|
|
165
|
+
print(f"Diam mean: {model.diam_mean}; Diam labels: {model.diam_labels}; Final rescaling: {scale_model}...")
|
|
160
166
|
print(f'Cellpose model {modelname} successfully loaded.')
|
|
161
167
|
|
|
162
168
|
for t in tqdm(indices,desc="frame"):
|
|
@@ -172,10 +178,12 @@ def segment_index(indices):
|
|
|
172
178
|
percentiles.append(None)
|
|
173
179
|
values.append(normalization_values[k])
|
|
174
180
|
|
|
175
|
-
f = load_frames(img_num_channels[:,t], file, scale=
|
|
181
|
+
f = load_frames(img_num_channels[:,t], file, scale=scale_model, normalize_input=True, normalize_kwargs={"percentiles": percentiles, 'values': values, 'clip': normalization_clip})
|
|
182
|
+
f = np.moveaxis([interpolate_nan(f[:,:,c].copy()) for c in range(f.shape[-1])],0,-1)
|
|
176
183
|
|
|
177
184
|
if np.any(img_num_channels[:,t]==-1):
|
|
178
185
|
f[:,:,np.where(img_num_channels[:,t]==-1)[0]] = 0.
|
|
186
|
+
|
|
179
187
|
|
|
180
188
|
if model_type=="stardist":
|
|
181
189
|
Y_pred, details = model.predict_instances(f, n_tiles=model._guess_n_tiles(f), show_tile_progress=False, verbose=False)
|
|
@@ -188,7 +196,7 @@ def segment_index(indices):
|
|
|
188
196
|
Y_pred = Y_pred.astype(np.uint16)
|
|
189
197
|
|
|
190
198
|
if scale is not None:
|
|
191
|
-
Y_pred = zoom(Y_pred, [1./
|
|
199
|
+
Y_pred = zoom(Y_pred, [1./scale_model,1./scale_model],order=0)
|
|
192
200
|
|
|
193
201
|
template = load_frames(0,file,scale=1,normalize_input=False)
|
|
194
202
|
if Y_pred.shape != template.shape[:2]:
|
celldetective/segmentation.py
CHANGED
|
@@ -116,6 +116,10 @@ def segment(stack, model_name, channels=None, spatial_calibration=None, view_on_
|
|
|
116
116
|
|
|
117
117
|
elif model_type=='cellpose':
|
|
118
118
|
model = CellposeModel(gpu=use_gpu, pretrained_model=model_path+model_path.split('/')[-2], diam_mean=30.0)
|
|
119
|
+
if scale is None:
|
|
120
|
+
scale_model = model.diam_mean / model.diam_labels
|
|
121
|
+
else:
|
|
122
|
+
scale_model = scale * model.diam_mean / model.diam_labels
|
|
119
123
|
|
|
120
124
|
labels = []
|
|
121
125
|
if (time_flat_normalization)*normalize:
|
|
@@ -133,7 +137,7 @@ def segment(stack, model_name, channels=None, spatial_calibration=None, view_on_
|
|
|
133
137
|
frame = normalize_multichannel(frame, values=normalization_values)
|
|
134
138
|
|
|
135
139
|
if scale is not None:
|
|
136
|
-
frame = [ndi.zoom(frame[:,:,c].copy(), [
|
|
140
|
+
frame = [ndi.zoom(frame[:,:,c].copy(), [scale_model,scale_model], order=3, prefilter=False) for c in range(frame.shape[-1])]
|
|
137
141
|
|
|
138
142
|
if model_type=="stardist":
|
|
139
143
|
|
|
@@ -142,16 +146,11 @@ def segment(stack, model_name, channels=None, spatial_calibration=None, view_on_
|
|
|
142
146
|
|
|
143
147
|
elif model_type=="cellpose":
|
|
144
148
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
else:
|
|
148
|
-
channels_cp = [[0,1]]
|
|
149
|
-
|
|
150
|
-
Y_pred, _, _ = model.eval([frame], diameter = diameter, flow_threshold=flow_threshold, channels=channels_cp, normalize=normalize)
|
|
151
|
-
Y_pred = Y_pred[0].astype(np.uint16)
|
|
149
|
+
Y_pred, _, _ = model.eval(frame, diameter = diameter, cellprob_threshold=cellprob_threshold, flow_threshold=flow_threshold, channels=None, normalize=False)
|
|
150
|
+
Y_pred = Y_pred.astype(np.uint16)
|
|
152
151
|
|
|
153
152
|
if scale is not None:
|
|
154
|
-
Y_pred = ndi.zoom(Y_pred, [1./
|
|
153
|
+
Y_pred = ndi.zoom(Y_pred, [1./scale_model,1./scale_model],order=0)
|
|
155
154
|
|
|
156
155
|
|
|
157
156
|
if Y_pred.shape != stack[0].shape[:2]:
|
|
@@ -229,7 +228,7 @@ def segment_from_thresholds(stack, target_channel=0, thresholds=None, view_on_na
|
|
|
229
228
|
return masks
|
|
230
229
|
|
|
231
230
|
def segment_frame_from_thresholds(frame, target_channel=0, thresholds=None, equalize_reference=None,
|
|
232
|
-
filters=None, marker_min_distance=30, marker_footprint_size=20, marker_footprint=None, feature_queries=None, channel_names=None):
|
|
231
|
+
filters=None, marker_min_distance=30, marker_footprint_size=20, marker_footprint=None, feature_queries=None, channel_names=None, do_watershed=True):
|
|
233
232
|
|
|
234
233
|
"""
|
|
235
234
|
Segments objects within a single frame based on intensity thresholds and optional image processing steps.
|
|
@@ -276,8 +275,13 @@ def segment_frame_from_thresholds(frame, target_channel=0, thresholds=None, equa
|
|
|
276
275
|
img = filter_image(img, filters=filters)
|
|
277
276
|
edge = estimate_unreliable_edge(filters)
|
|
278
277
|
binary_image = threshold_image(img, thresholds[0], thresholds[1], fill_holes=True, edge_exclusion=edge)
|
|
279
|
-
|
|
280
|
-
|
|
278
|
+
|
|
279
|
+
if do_watershed:
|
|
280
|
+
coords,distance = identify_markers_from_binary(binary_image, marker_min_distance, footprint_size=marker_footprint_size, footprint=marker_footprint, return_edt=True)
|
|
281
|
+
instance_seg = apply_watershed(binary_image, coords, distance)
|
|
282
|
+
else:
|
|
283
|
+
instance_seg, _ = ndi.label(binary_image.astype(int).copy())
|
|
284
|
+
|
|
281
285
|
instance_seg = filter_on_property(instance_seg, intensity_image=img_mc, queries=feature_queries, channel_names=channel_names)
|
|
282
286
|
|
|
283
287
|
return instance_seg
|
celldetective/utils.py
CHANGED
|
@@ -2220,6 +2220,9 @@ def interpolate_nan(img, method='nearest'):
|
|
|
2220
2220
|
Interpolate NaN on single channel array 2D
|
|
2221
2221
|
"""
|
|
2222
2222
|
|
|
2223
|
+
if np.all(img==0):
|
|
2224
|
+
return img
|
|
2225
|
+
|
|
2223
2226
|
if np.any(img.flatten()!=img.flatten()):
|
|
2224
2227
|
# then need to interpolate
|
|
2225
2228
|
x_grid, y_grid = np.meshgrid(np.arange(img.shape[1]),np.arange(img.shape[0]))
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
celldetective/__init__.py,sha256=FEZpJKcskBH2IginYzeqPWoR1lVGuyYCXhv7Hnlkoo8,49
|
|
2
2
|
celldetective/__main__.py,sha256=XFAkq_2cBEkWAVXDGSNFagoQBglyl0Y-GOO3KFc8UqM,13888
|
|
3
3
|
celldetective/events.py,sha256=s2pWnR3Z1fcB15sET5WsF2Pi6v6qv16hks_m3WiklNs,3658
|
|
4
|
-
celldetective/extra_properties.py,sha256=
|
|
5
|
-
celldetective/filters.py,sha256=
|
|
6
|
-
celldetective/io.py,sha256=
|
|
4
|
+
celldetective/extra_properties.py,sha256=ZdWV046RR8jrQPYxLSVVb1cXkWYxfrrLj2tXfc358cU,4179
|
|
5
|
+
celldetective/filters.py,sha256=b0qKwHor1fvNA_dHovP17nQz8EsW5YlyhT2TJnayn08,3615
|
|
6
|
+
celldetective/io.py,sha256=ptaX4GadWuf0zOQ3ZWSLCzihEID-YAalPfxW1nqvgIU,80889
|
|
7
7
|
celldetective/measure.py,sha256=HDQZfSRx3daOCV5Snu1paYU5JYkwu8engO2qZqhTAUo,48089
|
|
8
8
|
celldetective/neighborhood.py,sha256=QCuhesMHGyr3c3ys9wWcNR1HM6CHdHe51R8upoolgPw,49514
|
|
9
9
|
celldetective/preprocessing.py,sha256=psCs4CAI7gG3YlKvxkXKnpClFL4SjWm5TToXq2ZwL-s,37137
|
|
10
|
-
celldetective/segmentation.py,sha256=
|
|
10
|
+
celldetective/segmentation.py,sha256=Hu4-lOJ4UhPw2o0Hn_NPOCHsa6Iwsw5A3PR6X3Tzbb8,29196
|
|
11
11
|
celldetective/signals.py,sha256=P7eiDZGGIAYCFBKjGCBi8gMBvJYywxlxZNzyGgw-26Y,102783
|
|
12
12
|
celldetective/tracking.py,sha256=A0mhdF4uq4m8OX1-rhtuhG69rlh_6Pb34Aebu7hIeKM,37601
|
|
13
|
-
celldetective/utils.py,sha256=
|
|
13
|
+
celldetective/utils.py,sha256=00jBr_Ur6wX3xeFQzwgpFv9XDhnt_VCh-e2usFdrfo4,77543
|
|
14
14
|
celldetective/datasets/segmentation_annotations/blank,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
celldetective/datasets/signal_annotations/blank,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
celldetective/gui/__init__.py,sha256=y2dvrUdJi17QMgPjl8WN3XFHYzJpu2ul4_8y7MQV1Bk,941
|
|
17
17
|
celldetective/gui/about.py,sha256=i-y54Opb10pKTVNUEcJC-D6Cbiqud2EJ3ZLayXqhdqc,1715
|
|
18
18
|
celldetective/gui/analyze_block.py,sha256=WD3JQQylx_dVozFCvNqrOyR6LcNHV7R1_gGh7XqOVeI,25423
|
|
19
19
|
celldetective/gui/btrack_options.py,sha256=eQEf63yTUsPCN-d1LqgAMmUQpfv2FH85FqnOSaR-9Q8,35575
|
|
20
|
-
celldetective/gui/classifier_widget.py,sha256=
|
|
20
|
+
celldetective/gui/classifier_widget.py,sha256=52a3kOfU3uMEzUYMIOHGqiy-Q5itMEvcG-3JYrO34I8,16918
|
|
21
21
|
celldetective/gui/configure_new_exp.py,sha256=ANJ-Zn4sjBphtj_aoJu6m1PFEKyv9gxeh9XqS6xOGjk,18969
|
|
22
22
|
celldetective/gui/control_panel.py,sha256=wcDqe4XaDJRMmPmWKJpxd0D9V4_DrdRGnEH6D7B_IK0,17557
|
|
23
23
|
celldetective/gui/gui_utils.py,sha256=PidFfdc8XASeIzZO5pfKgwqe4vROG7-KpYMcBZ42jdw,22673
|
|
24
24
|
celldetective/gui/json_readers.py,sha256=fTrNrlxv9NCae8ZJexBEHxI3yCLRqt6F0Yo1OeDycfA,3686
|
|
25
|
-
celldetective/gui/layouts.py,sha256=
|
|
25
|
+
celldetective/gui/layouts.py,sha256=gC24lyA48hzOo2oabYF1vXTFh5r7WcQFVkqjAXXo5g0,26997
|
|
26
26
|
celldetective/gui/measurement_options.py,sha256=i0CdAfupHJAqhOT7RsufEK919sAzQnFBkQO4IAMYZL0,47704
|
|
27
27
|
celldetective/gui/neighborhood_options.py,sha256=sdKxVRliZtuKSpcPfnFxqkW4V8rN2tzjhDxOPVmElyE,20191
|
|
28
28
|
celldetective/gui/plot_measurements.py,sha256=xUoGxV6uXcen-t4yWtAmcGTUayICI-FxTVKPrWMNlfg,51799
|
|
@@ -35,8 +35,8 @@ celldetective/gui/signal_annotator.py,sha256=4ymMpo_GjSBsJSRkyNKrWRLy0EFXHINbFtp
|
|
|
35
35
|
celldetective/gui/signal_annotator_options.py,sha256=-Q7f8eCwniqbgLJqMCa91Wc-V3VHAZidwt7LPd4Z5yU,10879
|
|
36
36
|
celldetective/gui/styles.py,sha256=Vw4wr6MQ4iBwhOY-ZWAxFDZZ3CNohmEnuPPazwhJaho,4129
|
|
37
37
|
celldetective/gui/survival_ui.py,sha256=2JGLC5m6D_gVLwnBAM7uEvuCKw1Cli8nM9i5s7TIpGg,33612
|
|
38
|
-
celldetective/gui/tableUI.py,sha256=
|
|
39
|
-
celldetective/gui/thresholds_gui.py,sha256=
|
|
38
|
+
celldetective/gui/tableUI.py,sha256=QRYlXc7751Ka5qbUHSGbE_nXTiiezAVfEWXiRKRmuRM,32188
|
|
39
|
+
celldetective/gui/thresholds_gui.py,sha256=b8SkG4DlfxBRjacKTe1NSNkq7rJm8lnSLifH-mg846k,49529
|
|
40
40
|
celldetective/gui/viewers.py,sha256=G8uNb0U_4tJiZkcAWX9BbCSBIUbF4tjedZD-5o4WKxY,27734
|
|
41
41
|
celldetective/icons/logo-large.png,sha256=FXSwV3u6zEKcfpuSn4unnqB0oUnN9cHqQ9BCKWytrpg,36631
|
|
42
42
|
celldetective/icons/logo.png,sha256=wV2OS8_dU5Td5cgdPbCOU3JpMpTwNuYLnfVcnQX0tJA,2437
|
|
@@ -60,7 +60,7 @@ celldetective/models/tracking_configs/ricm.json,sha256=L-vmwCR1f89U-qnH2Ms0cBfPF
|
|
|
60
60
|
celldetective/models/tracking_configs/ricm2.json,sha256=DDjJ6ScYcDWvlsy7ujPID8v8H28vcNcMuZmNR8XmGxo,2718
|
|
61
61
|
celldetective/scripts/analyze_signals.py,sha256=23TXGNw-j5xT3ss4mXlnKdBgFLnQ50JUEQOC6_H7Q_0,2203
|
|
62
62
|
celldetective/scripts/measure_cells.py,sha256=4uRG6Dg0WsO-N8ZaBJ4loWOvX6FdHaCblIFXq6Dtirc,11000
|
|
63
|
-
celldetective/scripts/segment_cells.py,sha256=
|
|
63
|
+
celldetective/scripts/segment_cells.py,sha256=OSP52sPPHyhfXEjiXXdezkDY4MiFXLBWTb2_umRBD-M,8269
|
|
64
64
|
celldetective/scripts/segment_cells_thresholds.py,sha256=GbWXa6xoO8s4PinJPZIxAuosw4vpzyJ7FiFYpSURojk,4998
|
|
65
65
|
celldetective/scripts/track_cells.py,sha256=AaNiYEW4osYKKR2kbdVLOUnQEBbcZIA-D0mkhcxPWTY,7985
|
|
66
66
|
celldetective/scripts/train_segmentation_model.py,sha256=UY493QK7_FhS9uHYl2eeEYx7t0kw1jhvc0YMY12YZpI,8614
|
|
@@ -76,9 +76,9 @@ tests/test_segmentation.py,sha256=-3b7o_fUVMYxfVwX5VHFqRF0dDXObSTtylf5XQGcq1A,34
|
|
|
76
76
|
tests/test_signals.py,sha256=No4cah6KxplhDcKXnU8RrA7eDla4hWw6ccf7xGnBokU,3599
|
|
77
77
|
tests/test_tracking.py,sha256=8hebWSqEIuttD1ABn-6dKCT7EXKRR7-4RwyFWi1WPFo,8800
|
|
78
78
|
tests/test_utils.py,sha256=NKRCAC1d89aBK5cWjTb7-pInYow901RrT-uBlIdz4KI,3692
|
|
79
|
-
celldetective-1.1.1.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
80
|
-
celldetective-1.1.1.dist-info/METADATA,sha256=
|
|
81
|
-
celldetective-1.1.1.dist-info/WHEEL,sha256=
|
|
82
|
-
celldetective-1.1.1.dist-info/entry_points.txt,sha256=2NU6_EOByvPxqBbCvjwxlVlvnQreqZ3BKRCVIKEv3dg,62
|
|
83
|
-
celldetective-1.1.1.dist-info/top_level.txt,sha256=6rsIKKfGMKgud7HPuATcpq6EhdXwcg_yknBVWn9x4C4,20
|
|
84
|
-
celldetective-1.1.1.dist-info/RECORD,,
|
|
79
|
+
celldetective-1.1.1.post1.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
80
|
+
celldetective-1.1.1.post1.dist-info/METADATA,sha256=MItn6uMJhqWHbN0urj8068yO8xcUoc9c3kUsYTVre10,12418
|
|
81
|
+
celldetective-1.1.1.post1.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
|
|
82
|
+
celldetective-1.1.1.post1.dist-info/entry_points.txt,sha256=2NU6_EOByvPxqBbCvjwxlVlvnQreqZ3BKRCVIKEv3dg,62
|
|
83
|
+
celldetective-1.1.1.post1.dist-info/top_level.txt,sha256=6rsIKKfGMKgud7HPuATcpq6EhdXwcg_yknBVWn9x4C4,20
|
|
84
|
+
celldetective-1.1.1.post1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|