cellfinder 1.1.0__py3-none-any.whl → 1.1.2__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.

Potentially problematic release.


This version of cellfinder might be problematic. Click here for more details.

@@ -197,9 +197,7 @@ class CubeGeneratorFromFile(Sequence):
197
197
  """
198
198
  return len(self.batches)
199
199
 
200
- def __getitem__(
201
- self, index: int
202
- ) -> Union[
200
+ def __getitem__(self, index: int) -> Union[
203
201
  np.ndarray,
204
202
  Tuple[np.ndarray, List[Dict[str, float]]],
205
203
  Tuple[np.ndarray, Dict],
@@ -389,9 +387,7 @@ class CubeGeneratorFromDisk(Sequence):
389
387
  """
390
388
  return int(np.ceil(len(self.signal_list) / self.batch_size))
391
389
 
392
- def __getitem__(
393
- self, index: int
394
- ) -> Union[
390
+ def __getitem__(self, index: int) -> Union[
395
391
  np.ndarray,
396
392
  Tuple[np.ndarray, List[Dict[str, float]]],
397
393
  Tuple[np.ndarray, Dict],
@@ -51,6 +51,16 @@ def calculate_parameters_in_pixels(
51
51
  ball_xy_size = int(round(ball_xy_size_um / mean_in_plane_pixel_size))
52
52
  ball_z_size = int(round(ball_z_size_um / float(voxel_sizes[0])))
53
53
 
54
+ if ball_z_size == 0:
55
+ raise ValueError(
56
+ "Ball z size has been calculated to be 0 voxels."
57
+ " This may be due to large axial spacing of your data or the "
58
+ "ball_z_size_um parameter being too small. "
59
+ "Please check input parameters are correct. "
60
+ "Note that cellfinder requires high resolution data in all "
61
+ "dimensions, so that cells can be detected in multiple "
62
+ "image planes."
63
+ )
54
64
  return soma_diameter, max_cluster_size, ball_xy_size, ball_z_size
55
65
 
56
66
 
@@ -76,11 +86,69 @@ def main(
76
86
  callback: Optional[Callable[[int], None]] = None,
77
87
  ) -> List[Cell]:
78
88
  """
89
+ Perform cell candidate detection on a 3D signal array.
90
+
79
91
  Parameters
80
92
  ----------
93
+ signal_array : numpy.ndarray
94
+ 3D array representing the signal data.
95
+
96
+ start_plane : int
97
+ Index of the starting plane for detection.
98
+
99
+ end_plane : int
100
+ Index of the ending plane for detection.
101
+
102
+ voxel_sizes : Tuple[float, float, float]
103
+ Tuple of voxel sizes in each dimension (x, y, z).
104
+
105
+ soma_diameter : float
106
+ Diameter of the soma in physical units.
107
+
108
+ max_cluster_size : float
109
+ Maximum size of a cluster in physical units.
110
+
111
+ ball_xy_size : float
112
+ Size of the XY ball used for filtering in physical units.
113
+
114
+ ball_z_size : float
115
+ Size of the Z ball used for filtering in physical units.
116
+
117
+ ball_overlap_fraction : float
118
+ Fraction of overlap allowed between balls.
119
+
120
+ soma_spread_factor : float
121
+ Spread factor for soma size.
122
+
123
+ n_free_cpus : int
124
+ Number of free CPU cores available for parallel processing.
125
+
126
+ log_sigma_size : float
127
+ Size of the sigma for the log filter.
128
+
129
+ n_sds_above_mean_thresh : float
130
+ Number of standard deviations above the mean threshold.
131
+
132
+ outlier_keep : bool, optional
133
+ Whether to keep outliers during detection. Defaults to False.
134
+
135
+ artifact_keep : bool, optional
136
+ Whether to keep artifacts during detection. Defaults to False.
137
+
138
+ save_planes : bool, optional
139
+ Whether to save the planes during detection. Defaults to False.
140
+
141
+ plane_directory : str, optional
142
+ Directory path to save the planes. Defaults to None.
143
+
81
144
  callback : Callable[int], optional
82
145
  A callback function that is called every time a plane has finished
83
146
  being processed. Called with the plane number that has finished.
147
+
148
+ Returns
149
+ -------
150
+ List[Cell]
151
+ List of detected cells.
84
152
  """
85
153
  if not np.issubdtype(signal_array.dtype, np.integer):
86
154
  raise ValueError(
@@ -107,6 +175,7 @@ def main(
107
175
  if end_plane == -1:
108
176
  end_plane = len(signal_array)
109
177
  signal_array = signal_array[start_plane:end_plane]
178
+ signal_array = signal_array.astype(np.uint32)
110
179
 
111
180
  callback = callback or (lambda *args, **kwargs: None)
112
181
 
@@ -169,11 +238,11 @@ def main(
169
238
  # processes.
170
239
  cells = mp_3d_filter.process(async_results, locks, callback=callback)
171
240
 
172
- print(
173
- "Detection complete - all planes done in : {}".format(
174
- datetime.now() - start_time
175
- )
241
+ time_elapsed = datetime.now() - start_time
242
+ logger.debug(
243
+ f"All Planes done. Found {len(cells)} cells in {format(time_elapsed)}"
176
244
  )
245
+ print("Detection complete - all planes done in : {}".format(time_elapsed))
177
246
  return cells
178
247
 
179
248
 
@@ -6,6 +6,31 @@ from scipy.signal import medfilt2d
6
6
  def enhance_peaks(
7
7
  img: np.ndarray, clipping_value: float, gaussian_sigma: float = 2.5
8
8
  ) -> np.ndarray:
9
+ """
10
+ Enhances the peaks (bright pixels) in an input image.
11
+
12
+ Parameters:
13
+ ----------
14
+ img : np.ndarray
15
+ Input image.
16
+ clipping_value : float
17
+ Maximum value for the enhanced image.
18
+ gaussian_sigma : float, optional
19
+ Standard deviation for the Gaussian filter. Default is 2.5.
20
+
21
+ Returns:
22
+ -------
23
+ np.ndarray
24
+ Enhanced image with peaks.
25
+
26
+ Notes:
27
+ ------
28
+ The enhancement process includes the following steps:
29
+ 1. Applying a 2D median filter.
30
+ 2. Applying a Laplacian of Gaussian filter (LoG).
31
+ 3. Multiplying by -1 (bright spots respond negative in a LoG).
32
+ 4. Rescaling image values to range from 0 to clipping value.
33
+ """
9
34
  type_in = img.dtype
10
35
  filtered_img = medfilt2d(img.astype(np.float64))
11
36
  filtered_img = gaussian_filter(filtered_img, gaussian_sigma)
@@ -104,7 +104,7 @@ class BallFilter:
104
104
 
105
105
  # Stores the current planes that are being filtered
106
106
  self.volume = np.empty(
107
- (plane_width, plane_height, ball_z_size), dtype=np.uint16
107
+ (plane_width, plane_height, ball_z_size), dtype=np.uint32
108
108
  )
109
109
  # Index of the middle plane in the volume
110
110
  self.middle_z_idx = int(np.floor(ball_z_size / 2))
@@ -165,7 +165,7 @@ class BallFilter:
165
165
  Get the plane in the middle of self.volume.
166
166
  """
167
167
  z = self.middle_z_idx
168
- return np.array(self.volume[:, :, z], dtype=np.uint16)
168
+ return np.array(self.volume[:, :, z], dtype=np.uint32)
169
169
 
170
170
  def walk(self) -> None: # Highly optimised because most time critical
171
171
  ball_radius = self.ball_xy_size // 2
@@ -327,6 +327,6 @@ def _walk(
327
327
  THRESHOLD_VALUE,
328
328
  kernel,
329
329
  ):
330
- volume[
331
- ball_centre_x, ball_centre_y, middle_z
332
- ] = SOMA_CENTRE_VALUE
330
+ volume[ball_centre_x, ball_centre_y, middle_z] = (
331
+ SOMA_CENTRE_VALUE
332
+ )
@@ -28,7 +28,7 @@ def coords_to_volume(
28
28
  expanded_shape = [
29
29
  dim_size + ball_diameter for dim_size in get_shape(xs, ys, zs)
30
30
  ]
31
- volume = np.zeros(expanded_shape, dtype=np.uint16)
31
+ volume = np.zeros(expanded_shape, dtype=np.uint32)
32
32
 
33
33
  x_min, y_min, z_min = xs.min(), ys.min(), zs.min()
34
34
 
@@ -38,7 +38,7 @@ def coords_to_volume(
38
38
 
39
39
  # OPTIMISE: vectorize
40
40
  for rel_x, rel_y, rel_z in zip(relative_xs, relative_ys, relative_zs):
41
- volume[rel_x, rel_y, rel_z] = 65534
41
+ volume[rel_x, rel_y, rel_z] = np.iinfo(volume.dtype).max - 1
42
42
  return volume
43
43
 
44
44
 
@@ -49,6 +49,26 @@ def ball_filter_imgs(
49
49
  ball_xy_size: int = 3,
50
50
  ball_z_size: int = 3,
51
51
  ) -> Tuple[np.ndarray, np.ndarray]:
52
+ """
53
+ Apply ball filtering to a 3D volume and detect cell centres.
54
+
55
+ Uses the `BallFilter` class to perform ball filtering on the volume
56
+ and the `CellDetector` class to detect cell centres.
57
+
58
+ Args:
59
+ volume (np.ndarray): The 3D volume to be filtered.
60
+ threshold_value (int): The threshold value for ball filtering.
61
+ soma_centre_value (int): The value representing the soma centre.
62
+ ball_xy_size (int, optional):
63
+ The size of the ball filter in the XY plane. Defaults to 3.
64
+ ball_z_size (int, optional):
65
+ The size of the ball filter in the Z plane. Defaults to 3.
66
+
67
+ Returns:
68
+ Tuple[np.ndarray, np.ndarray]:
69
+ A tuple containing the filtered volume and the cell centres.
70
+
71
+ """
52
72
  # OPTIMISE: reuse ball filter instance
53
73
 
54
74
  good_tiles_mask = np.ones((1, 1, volume.shape[2]), dtype=bool)
@@ -71,10 +91,10 @@ def ball_filter_imgs(
71
91
  )
72
92
 
73
93
  # FIXME: hard coded type
74
- ball_filtered_volume = np.zeros(volume.shape, dtype=np.uint16)
94
+ ball_filtered_volume = np.zeros(volume.shape, dtype=np.uint32)
75
95
  previous_plane = None
76
96
  for z in range(volume.shape[2]):
77
- bf.append(volume[:, :, z].astype(np.uint16), good_tiles_mask[:, :, z])
97
+ bf.append(volume[:, :, z].astype(np.uint32), good_tiles_mask[:, :, z])
78
98
  if bf.ready:
79
99
  bf.walk()
80
100
  middle_plane = bf.get_middle_plane()
@@ -89,11 +109,24 @@ def ball_filter_imgs(
89
109
  def iterative_ball_filter(
90
110
  volume: np.ndarray, n_iter: int = 10
91
111
  ) -> Tuple[List[int], List[np.ndarray]]:
112
+ """
113
+ Apply iterative ball filtering to the given volume.
114
+ The volume is eroded at each iteration, by subtracting 1 from the volume.
115
+
116
+ Parameters:
117
+ volume (np.ndarray): The input volume.
118
+ n_iter (int): The number of iterations to perform. Default is 10.
119
+
120
+ Returns:
121
+ Tuple[List[int], List[np.ndarray]]: A tuple containing two lists:
122
+ The structures found in each iteration.
123
+ The cell centres found in each iteration.
124
+ """
92
125
  ns = []
93
126
  centres = []
94
127
 
95
- threshold_value = 65534
96
- soma_centre_value = 65535
128
+ threshold_value = np.iinfo(volume.dtype).max - 1
129
+ soma_centre_value = np.iinfo(volume.dtype).max
97
130
 
98
131
  vol = volume.copy() # TODO: check if required
99
132
 
@@ -131,6 +164,21 @@ def check_centre_in_cuboid(centre: np.ndarray, max_coords: np.ndarray) -> bool:
131
164
  def split_cells(
132
165
  cell_points: np.ndarray, outlier_keep: bool = False
133
166
  ) -> np.ndarray:
167
+ """
168
+ Split the given cell points into individual cell centres.
169
+
170
+ Args:
171
+ cell_points (np.ndarray): Array of cell points with shape (N, 3),
172
+ where N is the number of cell points and each point is represented
173
+ by its x, y, and z coordinates.
174
+ outlier_keep (bool, optional): Flag indicating whether to keep outliers
175
+ during the splitting process. Defaults to False.
176
+
177
+ Returns:
178
+ np.ndarray: Array of absolute cell centres with shape (M, 3),
179
+ where M is the number of individual cells and each centre is
180
+ represented by its x, y, and z coordinates.
181
+ """
134
182
  orig_centre = get_structure_centre(cell_points)
135
183
 
136
184
  xs = cell_points[:, 0]
@@ -142,6 +142,10 @@ class VolumeFilter(object):
142
142
  )
143
143
 
144
144
  cells = []
145
+
146
+ logger.debug(
147
+ f"Processing {len(self.cell_detector.coords_maps.items())} cells"
148
+ )
145
149
  for cell_id, cell_points in self.cell_detector.coords_maps.items():
146
150
  cell_volume = len(cell_points)
147
151
 
@@ -191,6 +195,7 @@ class VolumeFilter(object):
191
195
  )
192
196
  )
193
197
 
198
+ logger.debug("Finished splitting cell clusters.")
194
199
  return cells
195
200
 
196
201
 
@@ -3,7 +3,7 @@ from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
3
3
  from pathlib import Path
4
4
 
5
5
  from cellfinder.core.download import models
6
- from cellfinder.core.download.download import amend_cfg
6
+ from cellfinder.core.download.download import amend_user_configuration
7
7
 
8
8
  home = Path.home()
9
9
  DEFAULT_DOWNLOAD_DIRECTORY = home / ".cellfinder"
@@ -65,7 +65,7 @@ def main():
65
65
  model_path = models.main(args.model, args.install_path)
66
66
 
67
67
  if not args.no_amend_config:
68
- amend_cfg(new_model_path=model_path)
68
+ amend_user_configuration(new_model_path=model_path)
69
69
 
70
70
 
71
71
  if __name__ == "__main__":
@@ -7,8 +7,8 @@ from brainglobe_utils.general.config import get_config_obj
7
7
  from brainglobe_utils.general.system import disk_free_gb
8
8
 
9
9
  from cellfinder.core.tools.source_files import (
10
- source_config_cellfinder,
11
- source_custom_config_cellfinder,
10
+ default_configuration_path,
11
+ user_specific_configuration_path,
12
12
  )
13
13
 
14
14
 
@@ -75,16 +75,45 @@ def download(
75
75
  os.remove(download_path)
76
76
 
77
77
 
78
- def amend_cfg(new_model_path=None):
79
- print("Ensuring custom config file is correct")
80
-
81
- original_config = source_config_cellfinder()
82
- new_config = source_custom_config_cellfinder()
83
- if new_model_path is not None:
84
- write_model_to_cfg(new_model_path, original_config, new_config)
78
+ def amend_user_configuration(new_model_path=None) -> None:
79
+ """
80
+ Amends the user configuration to contain the configuration
81
+ in new_model_path, if specified.
85
82
 
83
+ Parameters
84
+ ----------
85
+ new_model_path : str, optional
86
+ The path to the new model configuration.
87
+ """
88
+ print("(Over-)writing custom user configuration")
86
89
 
87
- def write_model_to_cfg(new_model_path, orig_config, custom_config):
90
+ original_config = default_configuration_path()
91
+ new_config = user_specific_configuration_path()
92
+ if new_model_path is not None:
93
+ write_model_to_config(new_model_path, original_config, new_config)
94
+
95
+
96
+ def write_model_to_config(new_model_path, orig_config, custom_config):
97
+ """
98
+ Update the model path in the custom configuration file, by
99
+ reading the lines in the original configuration file, replacing
100
+ the line starting with "model_path =" and writing these
101
+ lines to the custom file.
102
+
103
+ Parameters
104
+ ----------
105
+ new_model_path : str
106
+ The new path to the model.
107
+ orig_config : str
108
+ The path to the original configuration file.
109
+ custom_config : str
110
+ The path to the custom configuration file to be created.
111
+
112
+ Returns
113
+ -------
114
+ None
115
+
116
+ """
88
117
  config_obj = get_config_obj(orig_config)
89
118
  model_conf = config_obj["model"]
90
119
  orig_path = model_conf["model_path"]
cellfinder/core/main.py CHANGED
@@ -2,6 +2,7 @@
2
2
  N.B imports are within functions to prevent tensorflow being imported before
3
3
  it's warnings are silenced
4
4
  """
5
+
5
6
  import os
6
7
  from typing import Callable, List, Optional, Tuple
7
8
 
@@ -3,6 +3,7 @@ prep
3
3
  ==================
4
4
  Functions to prepare files and directories needed for other functions
5
5
  """
6
+
6
7
  import os
7
8
  from pathlib import Path
8
9
  from typing import Optional
@@ -13,8 +14,8 @@ from brainglobe_utils.general.system import get_num_processes
13
14
  import cellfinder.core.tools.tf as tf_tools
14
15
  from cellfinder.core import logger
15
16
  from cellfinder.core.download import models as model_download
16
- from cellfinder.core.download.download import amend_cfg
17
- from cellfinder.core.tools.source_files import source_custom_config_cellfinder
17
+ from cellfinder.core.download.download import amend_user_configuration
18
+ from cellfinder.core.tools.source_files import user_specific_configuration_path
18
19
 
19
20
  home = Path.home()
20
21
  DEFAULT_INSTALL_PATH = home / ".cellfinder"
@@ -48,18 +49,18 @@ def prep_models(
48
49
  if model_weights_path is None:
49
50
  logger.debug("No model supplied, so using the default")
50
51
 
51
- config_file = source_custom_config_cellfinder()
52
+ config_file = user_specific_configuration_path()
52
53
 
53
54
  if not Path(config_file).exists():
54
55
  logger.debug("Custom config does not exist, downloading models")
55
56
  model_path = model_download.main(model_name, install_path)
56
- amend_cfg(new_model_path=model_path)
57
+ amend_user_configuration(new_model_path=model_path)
57
58
 
58
59
  model_weights = get_model_weights(config_file)
59
60
  if not model_weights.exists():
60
61
  logger.debug("Model weights do not exist, downloading")
61
62
  model_path = model_download.main(model_name, install_path)
62
- amend_cfg(new_model_path=model_path)
63
+ amend_user_configuration(new_model_path=model_path)
63
64
  model_weights = get_model_weights(config_file)
64
65
  else:
65
66
  model_weights = Path(model_weights_path)
@@ -1,9 +1,27 @@
1
1
  from pathlib import Path
2
2
 
3
3
 
4
- def source_config_cellfinder():
4
+ def default_configuration_path():
5
+ """
6
+ Returns the default configuration path for cellfinder.
7
+
8
+ Returns:
9
+ Path: The default configuration path.
10
+ """
5
11
  return Path(__file__).parent.parent / "config" / "cellfinder.conf"
6
12
 
7
13
 
8
- def source_custom_config_cellfinder():
9
- return Path(__file__).parent.parent / "config" / "cellfinder.conf.custom"
14
+ def user_specific_configuration_path():
15
+ """
16
+ Returns the path to the user-specific configuration file for cellfinder.
17
+
18
+ This function returns the path to the user-specific configuration file
19
+ for cellfinder. The user-specific configuration file is located in the
20
+ user's home directory under the ".cellfinder" folder and is named
21
+ "cellfinder.conf.custom".
22
+
23
+ Returns:
24
+ Path: The path to the custom configuration file.
25
+
26
+ """
27
+ return Path.home() / ".cellfinder" / "cellfinder.conf.custom"
@@ -338,7 +338,10 @@ def run(
338
338
 
339
339
  ensure_directory_exists(output_dir)
340
340
  model_weights = prep_model_weights(
341
- install_path, model_weights, model, n_free_cpus
341
+ model_weights=model_weights,
342
+ install_path=install_path,
343
+ model_name=model,
344
+ n_free_cpus=n_free_cpus,
342
345
  )
343
346
 
344
347
  yaml_contents = parse_yaml(yaml_file)
@@ -1,3 +0,0 @@
1
- __version__ = "0.0.20"
2
- __author__ = "Adam Tyson"
3
- __license__ = "GPL-3.0"
@@ -6,7 +6,10 @@ import numpy as np
6
6
  import tifffile
7
7
  from brainglobe_napari_io.cellfinder.utils import convert_layer_to_cells
8
8
  from brainglobe_utils.cells.cells import Cell
9
+ from brainglobe_utils.general.system import delete_directory_contents
9
10
  from brainglobe_utils.IO.yaml import save_yaml
11
+ from brainglobe_utils.qtpy.dialog import display_warning
12
+ from brainglobe_utils.qtpy.interaction import add_button, add_combobox
10
13
  from magicgui.widgets import ProgressBar
11
14
  from napari.qt.threading import thread_worker
12
15
  from napari.utils.notifications import show_info
@@ -20,8 +23,6 @@ from qtpy.QtWidgets import (
20
23
  QWidget,
21
24
  )
22
25
 
23
- from .utils import add_button, add_combobox, display_question
24
-
25
26
  # Constants used throughout
26
27
  WINDOW_HEIGHT = 750
27
28
  WINDOW_WIDTH = 1500
@@ -69,6 +70,7 @@ class CurationWidget(QWidget):
69
70
  self.output_directory: Optional[Path] = None
70
71
 
71
72
  self.setup_main_layout()
73
+ self.setup_keybindings()
72
74
 
73
75
  @self.viewer.layers.events.connect
74
76
  def update_layer_list(v: napari.viewer.Viewer):
@@ -173,33 +175,35 @@ class CurationWidget(QWidget):
173
175
  self.load_data_layout,
174
176
  "Training_data (non_cells)",
175
177
  self.point_layer_names,
176
- 4,
178
+ row=4,
177
179
  callback=self.set_training_data_non_cell,
178
180
  )
179
181
  self.mark_as_cell_button = add_button(
180
182
  "Mark as cell(s)",
181
183
  self.load_data_layout,
182
184
  self.mark_as_cell,
183
- 5,
185
+ row=5,
186
+ tooltip="Mark all selected points as non cell. Shortcut: 'c'",
184
187
  )
185
188
  self.mark_as_non_cell_button = add_button(
186
189
  "Mark as non cell(s)",
187
190
  self.load_data_layout,
188
191
  self.mark_as_non_cell,
189
- 5,
192
+ row=5,
190
193
  column=1,
194
+ tooltip="Mark all selected points as non cell. Shortcut: 'x'",
191
195
  )
192
196
  self.add_training_data_button = add_button(
193
197
  "Add training data layers",
194
198
  self.load_data_layout,
195
199
  self.add_training_data,
196
- 6,
200
+ row=6,
197
201
  )
198
202
  self.save_training_data_button = add_button(
199
203
  "Save training data",
200
204
  self.load_data_layout,
201
205
  self.save_training_data,
202
- 6,
206
+ row=6,
203
207
  column=1,
204
208
  )
205
209
  self.load_data_layout.setColumnMinimumWidth(0, COLUMN_WIDTH)
@@ -207,6 +211,10 @@ class CurationWidget(QWidget):
207
211
  self.load_data_panel.setVisible(True)
208
212
  self.layout.addWidget(self.load_data_panel, row, column, 1, 1)
209
213
 
214
+ def setup_keybindings(self):
215
+ self.viewer.bind_key("c", self.mark_as_cell)
216
+ self.viewer.bind_key("x", self.mark_as_non_cell)
217
+
210
218
  def set_signal_image(self):
211
219
  """
212
220
  Set signal layer from current signal text box selection.
@@ -245,9 +253,9 @@ class CurationWidget(QWidget):
245
253
  self.training_data_non_cell_layer = self.viewer.layers[
246
254
  self.training_data_non_cell_choice.currentText()
247
255
  ]
248
- self.training_data_non_cell_layer.metadata[
249
- "point_type"
250
- ] = Cell.UNKNOWN
256
+ self.training_data_non_cell_layer.metadata["point_type"] = (
257
+ Cell.UNKNOWN
258
+ )
251
259
  self.training_data_non_cell_layer.metadata["training_data"] = True
252
260
 
253
261
  def add_training_data(self):
@@ -256,7 +264,7 @@ class CurationWidget(QWidget):
256
264
 
257
265
  overwrite = False
258
266
  if self.training_data_cell_layer or self.training_data_non_cell_layer:
259
- overwrite = display_question(
267
+ overwrite = display_warning(
260
268
  self,
261
269
  "Training data layers exist",
262
270
  "Training data layers already exist, "
@@ -303,10 +311,10 @@ class CurationWidget(QWidget):
303
311
  )
304
312
  self.training_data_non_cell_choice.setCurrentText(non_cell_name)
305
313
 
306
- def mark_as_cell(self):
314
+ def mark_as_cell(self, viewer=None):
307
315
  self.mark_point_as_type("cell")
308
316
 
309
- def mark_as_non_cell(self):
317
+ def mark_as_non_cell(self, viewer=None):
310
318
  self.mark_point_as_type("non-cell")
311
319
 
312
320
  def mark_point_as_type(self, point_type: str):
@@ -363,7 +371,10 @@ class CurationWidget(QWidget):
363
371
  )
364
372
 
365
373
  def save_training_data(
366
- self, *, block: bool = False, prompt_for_directory: bool = True
374
+ self,
375
+ *,
376
+ block: bool = False,
377
+ prompt_for_directory: bool = True,
367
378
  ) -> None:
368
379
  """
369
380
  Parameters
@@ -373,16 +384,45 @@ class CurationWidget(QWidget):
373
384
  prompt_for_directory :
374
385
  If `True` show a file dialog for the user to select a directory.
375
386
  """
387
+
376
388
  if self.is_data_extractable():
377
389
  if prompt_for_directory:
378
390
  self.get_output_directory()
391
+ # if the directory is not empty
392
+ if any(self.output_directory.iterdir()):
393
+ choice = display_warning(
394
+ self,
395
+ "About to save training data",
396
+ "Existing files will be will be deleted. Proceed?",
397
+ )
398
+ if not choice:
399
+ return
379
400
  if self.output_directory is not None:
401
+ self.__prep_directories_for_save()
380
402
  self.__extract_cubes(block=block)
381
403
  self.__save_yaml_file()
382
404
  show_info("Done")
383
405
 
384
406
  self.update_status_label("Ready")
385
407
 
408
+ def __prep_directories_for_save(self):
409
+ self.yaml_filename = self.output_directory / "training.yml"
410
+ self.cell_cube_dir = self.output_directory / "cells"
411
+ self.no_cell_cube_dir = self.output_directory / "non_cells"
412
+
413
+ self.__delete_existing_saved_training_data()
414
+
415
+ def __delete_existing_saved_training_data(self):
416
+ self.yaml_filename.unlink(missing_ok=True)
417
+ for directory in (
418
+ self.cell_cube_dir,
419
+ self.no_cell_cube_dir,
420
+ ):
421
+ if directory.exists():
422
+ delete_directory_contents(directory)
423
+ else:
424
+ directory.mkdir(exist_ok=True, parents=True)
425
+
386
426
  def __extract_cubes(self, *, block=False):
387
427
  """
388
428
  Parameters
@@ -489,18 +529,16 @@ class CurationWidget(QWidget):
489
529
  self.non_cells_to_extract = list(set(self.non_cells_to_extract))
490
530
 
491
531
  def __save_yaml_file(self):
492
- # TODO: implement this in a portable way
493
- yaml_filename = self.output_directory / "training.yml"
494
532
  yaml_section = [
495
533
  {
496
- "cube_dir": str(self.output_directory / "cells"),
534
+ "cube_dir": str(self.cell_cube_dir),
497
535
  "cell_def": "",
498
536
  "type": "cell",
499
537
  "signal_channel": 0,
500
538
  "bg_channel": 1,
501
539
  },
502
540
  {
503
- "cube_dir": str(self.output_directory / "non_cells"),
541
+ "cube_dir": str(self.no_cell_cube_dir),
504
542
  "cell_def": "",
505
543
  "type": "no_cell",
506
544
  "signal_channel": 0,
@@ -509,7 +547,7 @@ class CurationWidget(QWidget):
509
547
  ]
510
548
 
511
549
  yaml_contents = {"data": yaml_section}
512
- save_yaml(yaml_contents, yaml_filename)
550
+ save_yaml(yaml_contents, self.yaml_filename)
513
551
 
514
552
  def update_progress(self, attributes: dict):
515
553
  """
@@ -538,9 +576,15 @@ class CurationWidget(QWidget):
538
576
  "non_cells": self.non_cells_to_extract,
539
577
  }
540
578
 
541
- for cell_type, cell_list in to_extract.items():
542
- cell_type_output_directory = self.output_directory / cell_type
543
- cell_type_output_directory.mkdir(exist_ok=True, parents=True)
579
+ directories = {
580
+ "cells": self.cell_cube_dir,
581
+ "non_cells": self.no_cell_cube_dir,
582
+ }
583
+
584
+ for cell_type in ["cells", "non_cells"]:
585
+ cell_type_output_directory = directories[cell_type]
586
+ cell_list = to_extract[cell_type]
587
+
544
588
  self.update_status_label(f"Saving {cell_type}...")
545
589
 
546
590
  cube_generator = CubeGeneratorFromFile(
@@ -5,6 +5,7 @@ from magicgui import magicgui
5
5
  from magicgui.widgets import FunctionGui, PushButton
6
6
  from napari.qt.threading import thread_worker
7
7
  from napari.utils.notifications import show_info
8
+ from qtpy.QtWidgets import QScrollArea
8
9
 
9
10
  from cellfinder.core.train.train_yml import run as train_yml
10
11
  from cellfinder.napari.utils import (
@@ -48,6 +49,7 @@ def training_widget() -> FunctionGui:
48
49
  **MiscTrainingInputs.widget_representation(),
49
50
  call_button=True,
50
51
  reset_button=dict(widget_type="PushButton", text="Reset defaults"),
52
+ scrollable=True,
51
53
  )
52
54
  def widget(
53
55
  header: dict,
@@ -175,4 +177,8 @@ def training_widget() -> FunctionGui:
175
177
  if value is not None:
176
178
  getattr(widget, name).value = value
177
179
 
180
+ scroll = QScrollArea()
181
+ scroll.setWidget(widget._widget._qwidget)
182
+ widget._widget._qwidget = scroll
183
+
178
184
  return widget
@@ -1,18 +1,10 @@
1
- from typing import Callable, List, Optional, Tuple
1
+ from typing import List, Tuple
2
2
 
3
3
  import napari
4
4
  import numpy as np
5
5
  import pandas as pd
6
6
  from brainglobe_utils.cells.cells import Cell
7
7
  from pkg_resources import resource_filename
8
- from qtpy.QtWidgets import (
9
- QComboBox,
10
- QLabel,
11
- QLayout,
12
- QMessageBox,
13
- QPushButton,
14
- QWidget,
15
- )
16
8
 
17
9
  brainglobe_logo = resource_filename(
18
10
  "cellfinder", "napari/images/brainglobe.png"
@@ -98,83 +90,3 @@ def cells_to_array(cells: List[Cell]) -> Tuple[np.ndarray, np.ndarray]:
98
90
  points = cells_df_as_np(df[df["type"] == Cell.CELL])
99
91
  rejected = cells_df_as_np(df[df["type"] == Cell.UNKNOWN])
100
92
  return points, rejected
101
-
102
-
103
- def add_combobox(
104
- layout: QLayout,
105
- label: str,
106
- items: List[str],
107
- row: int,
108
- column: int = 0,
109
- label_stack: bool = False,
110
- callback=None,
111
- width: int = 150,
112
- ) -> Tuple[QComboBox, Optional[QLabel]]:
113
- """
114
- Add a selection box to *layout*.
115
- """
116
- if label_stack:
117
- combobox_row = row + 1
118
- combobox_column = column
119
- else:
120
- combobox_row = row
121
- combobox_column = column + 1
122
- combobox = QComboBox()
123
- combobox.addItems(items)
124
- if callback:
125
- combobox.currentIndexChanged.connect(callback)
126
- combobox.setMaximumWidth = width
127
-
128
- if label is not None:
129
- combobox_label = QLabel(label)
130
- combobox_label.setMaximumWidth = width
131
- layout.addWidget(combobox_label, row, column)
132
- else:
133
- combobox_label = None
134
-
135
- layout.addWidget(combobox, combobox_row, combobox_column)
136
- return combobox, combobox_label
137
-
138
-
139
- def add_button(
140
- label: str,
141
- layout: QLayout,
142
- connected_function: Callable,
143
- row: int,
144
- column: int = 0,
145
- visibility: bool = True,
146
- minimum_width: int = 0,
147
- alignment: str = "center",
148
- ) -> QPushButton:
149
- """
150
- Add a button to *layout*.
151
- """
152
- button = QPushButton(label)
153
- if alignment == "center":
154
- pass
155
- elif alignment == "left":
156
- button.setStyleSheet("QPushButton { text-align: left; }")
157
- elif alignment == "right":
158
- button.setStyleSheet("QPushButton { text-align: right; }")
159
-
160
- button.setVisible(visibility)
161
- button.setMinimumWidth(minimum_width)
162
- layout.addWidget(button, row, column)
163
- button.clicked.connect(connected_function)
164
- return button
165
-
166
-
167
- def display_question(widget: QWidget, title: str, message: str) -> bool:
168
- """
169
- Display a warning in a pop up that informs about overwriting files.
170
- """
171
- message_reply = QMessageBox.question(
172
- widget,
173
- title,
174
- message,
175
- QMessageBox.Yes | QMessageBox.Cancel,
176
- )
177
- if message_reply == QMessageBox.Yes:
178
- return True
179
- else:
180
- return False
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cellfinder
3
- Version: 1.1.0
3
+ Version: 1.1.2
4
4
  Summary: Automated 3D cell detection in large microscopy images
5
5
  Author-email: "Adam Tyson, Christian Niedworok, Charly Rousseau" <code@adamltyson.com>
6
6
  License: BSD-3-Clause
7
7
  Project-URL: Homepage, https://brainglobe.info/documentation/cellfinder/index.html
8
- Project-URL: Source Code, https://github.com/brainglobe/cellfinder-core
9
- Project-URL: Bug Tracker, https://github.com/brainglobe/cellfinder-core/issues
8
+ Project-URL: Source Code, https://github.com/brainglobe/cellfinder
9
+ Project-URL: Bug Tracker, https://github.com/brainglobe/cellfinder/issues
10
10
  Project-URL: Documentation, https://brainglobe.info/documentation/cellfinder/index.html
11
11
  Project-URL: User Support, https://forum.image.sc/tag/brainglobe
12
12
  Classifier: Development Status :: 4 - Beta
@@ -22,7 +22,8 @@ Classifier: Topic :: Scientific/Engineering :: Image Recognition
22
22
  Requires-Python: >=3.9
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
- Requires-Dist: brainglobe-utils
25
+ Requires-Dist: brainglobe-utils >=0.4.2
26
+ Requires-Dist: brainglobe-napari-io >=0.3.4
26
27
  Requires-Dist: dask[array]
27
28
  Requires-Dist: fancylog >=0.0.7
28
29
  Requires-Dist: natsort
@@ -39,7 +40,6 @@ Requires-Dist: black ; extra == 'dev'
39
40
  Requires-Dist: pre-commit ; extra == 'dev'
40
41
  Requires-Dist: pyinstrument ; extra == 'dev'
41
42
  Requires-Dist: pytest-cov ; extra == 'dev'
42
- Requires-Dist: pytest-lazy-fixture ; extra == 'dev'
43
43
  Requires-Dist: pytest-mock ; extra == 'dev'
44
44
  Requires-Dist: pytest-qt ; extra == 'dev'
45
45
  Requires-Dist: pytest-timeout ; extra == 'dev'
@@ -59,7 +59,7 @@ Requires-Dist: qtpy ; extra == 'napari'
59
59
  [![Downloads](https://pepy.tech/badge/cellfinder-core)](https://pepy.tech/project/cellfinder)
60
60
  [![Wheel](https://img.shields.io/pypi/wheel/cellfinder-core.svg)](https://pypi.org/project/cellfinder)
61
61
  [![Development Status](https://img.shields.io/pypi/status/cellfinder-core.svg)](https://github.com/brainglobe/cellfinder)
62
- [![Tests](https://img.shields.io/github/workflow/status/brainglobe/cellfinder-core/tests)](https://github.com/brainglobe/cellfinder/actions)
62
+ [![Tests](https://img.shields.io/github/actions/workflow/status/brainglobe/cellfinder/test_and_deploy.yml?branch=main)](https://github.com/brainglobe/cellfinder/actions)
63
63
  [![codecov](https://codecov.io/gh/brainglobe/cellfinder-core/branch/main/graph/badge.svg?token=nx1lhNI7ox)](https://codecov.io/gh/brainglobe/cellfinder)
64
64
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black)
65
65
  [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
@@ -1,63 +1,63 @@
1
1
  cellfinder/__init__.py,sha256=XwXxMZJgw62dOHhrAKMQk0CE3378WRQWoC-kpf5LLpE,935
2
2
  cellfinder/cli_migration_warning.py,sha256=gPtNrtnXvWpl5q0k_EGAQZg0DwcpCmuBTgpg56n5NfA,1578
3
3
  cellfinder/core/__init__.py,sha256=pRFuQsl78HEK0S6gvhJw70QLbjjSBzP-GFO0AtVaGtk,62
4
- cellfinder/core/main.py,sha256=LJTtx-cdbRwZZStMcd2tNxRIC_kWvbjvicBsJu4UNMs,3899
4
+ cellfinder/core/main.py,sha256=3Wg4QvBUlaHq04Yltjg9FSiePAhtGeFL1C-6Z1SI0Gk,3900
5
5
  cellfinder/core/types.py,sha256=lTqWE4v0SMM0qLAZJdyAzqV1nLgDtobEpglNJcXt160,106
6
6
  cellfinder/core/classify/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  cellfinder/core/classify/augment.py,sha256=8dMbM7KhimM6NMgdMC53oHoCfYj1CIB-h3Yk8CZAxPw,6321
8
8
  cellfinder/core/classify/classify.py,sha256=y4EnHX3IpAUAfhGScoJGv2hLLpy6OtmGvkqxAow3_Kw,3046
9
- cellfinder/core/classify/cube_generator.py,sha256=01PAvXlcddSx8KY3VKKGlcWT6cPWP1Uaq0H9bYEf2uo,16614
9
+ cellfinder/core/classify/cube_generator.py,sha256=4zMaa7bb7vr1QxBFpzqqitnWaNdzgEzZ2gt2xFeN37A,16586
10
10
  cellfinder/core/classify/resnet.py,sha256=976Qt0li7ySYx_w2m9QfRMaDAc0rEkA8rO9SYP5a6uw,10057
11
11
  cellfinder/core/classify/tools.py,sha256=UqABKS9KnkwR3PIhGRl7xHJbADP0mgVRSxXI5dybpqA,2547
12
12
  cellfinder/core/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  cellfinder/core/config/cellfinder.conf,sha256=5i8axif7ekMutKDiVnZRs-LiJrgVQljg_beltidqtNk,56
14
14
  cellfinder/core/detect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- cellfinder/core/detect/detect.py,sha256=mMrLt2mi01y6pxhipfuPDPBFGaPwHWkM7mdmTcGxPlg,6832
15
+ cellfinder/core/detect/detect.py,sha256=bsiOPyxM3D_YZiYxqyauTxe7C4C2-rb_9yYiJiYaylM,9102
16
16
  cellfinder/core/detect/filters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  cellfinder/core/detect/filters/setup_filters.py,sha256=sUyZ2dKbfwhn4YTq3kLVTHkckZZ460AVozLfoz7hJK8,2084
18
18
  cellfinder/core/detect/filters/plane/__init__.py,sha256=lybcPbVDnurEQeq2FiLq0zR8p_ztarQOlajhdh1Q2-4,40
19
- cellfinder/core/detect/filters/plane/classical_filter.py,sha256=SI-tgWzGvqUah_2bSeQnjWyK8Espz9q73-t4lQxjggE,626
19
+ cellfinder/core/detect/filters/plane/classical_filter.py,sha256=Nton_nrvv6LhNlK10HnS0TIHsjapBiMJPURgzYh2myY,1331
20
20
  cellfinder/core/detect/filters/plane/plane_filter.py,sha256=PWcAHDKB3qn3i9aSmQzUTEi3tgmccrf24DipjnYbOg0,2746
21
21
  cellfinder/core/detect/filters/plane/tile_walker.py,sha256=lhKrq-MB35bR0_JYgj7WMR4otuaZ6XRDDhJhKyH1yS8,3001
22
22
  cellfinder/core/detect/filters/volume/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- cellfinder/core/detect/filters/volume/ball_filter.py,sha256=lOpqugdDLuYgnZfLFHfXy667pblgrVIe-lT1TxqPQCI,11173
23
+ cellfinder/core/detect/filters/volume/ball_filter.py,sha256=nNGYAjDWst4Ufh6o6LAb5CVi5hFqYi2ObyrwgChbkQw,11175
24
24
  cellfinder/core/detect/filters/volume/structure_detection.py,sha256=8lSyDFiqn5lYoE_mDLpXTUOsWGVm2-suGttZBdDAqmA,9152
25
- cellfinder/core/detect/filters/volume/structure_splitting.py,sha256=8CHQ7Qgwb1KYbOFsgzo-vOR3LjpTXSxyGFltcisNxDo,5693
26
- cellfinder/core/detect/filters/volume/volume_filter.py,sha256=Ul3ikwCGJqmkjFArCcXOJ_4ayLSP9v7X35KlbQrLWd4,6704
25
+ cellfinder/core/detect/filters/volume/structure_splitting.py,sha256=VuXKAOHG6f63ommPxZC08JRoegbQgZUReZAmvY5nm_U,7656
26
+ cellfinder/core/detect/filters/volume/volume_filter.py,sha256=nBc9kZhsZ2jsyuzizXcx6s0i5-Vc7BfUfDqtIxhGUSo,6873
27
27
  cellfinder/core/download/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- cellfinder/core/download/cli.py,sha256=XgWU_a_cA-3dOyWmrR_cB6KdOoJmWoINRo9_QXy9mEg,1777
29
- cellfinder/core/download/download.py,sha256=wf-vWuINuj2Lng4JczXTjOzWf8kSIi2wKfxumd4mp0s,3002
28
+ cellfinder/core/download/cli.py,sha256=uejrDBoveybeyMq-tcn8460r2Hf3iC1FhpEC2sXs7EI,1807
29
+ cellfinder/core/download/download.py,sha256=k7DZwox6UWhL4Efz1CK0VqG6jNVkotcomKHMZIlunTQ,3797
30
30
  cellfinder/core/download/models.py,sha256=mLb6wAUn-tH1FtgvQU6ooLXqXetTP4D5Jd4_gPN-kJo,1359
31
31
  cellfinder/core/tools/IO.py,sha256=q-euCP1s3iuWYvD2V72sPSBfwr5A1ETcL6Z_K41mLV0,1235
32
32
  cellfinder/core/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  cellfinder/core/tools/array_operations.py,sha256=LDbqWA_N2YtxeKS877QYdJRL2FCMC1R1ExJtdb6-vEA,3371
34
34
  cellfinder/core/tools/geometry.py,sha256=xlEQQmVQ9jcXqRzUqU554P8VxkaWkc5J_YhjkkKlI0Q,1124
35
35
  cellfinder/core/tools/image_processing.py,sha256=z27bGjf3Iv3G4Nt1OYzpEnIYQNc4nNomj_QitqvZB78,2269
36
- cellfinder/core/tools/prep.py,sha256=OdE722NIIBE_zAYum_h7oPZf1WXFDdkdof-_4p4QKPQ,2505
37
- cellfinder/core/tools/source_files.py,sha256=J3gThEoJGaNeJXHUG3finw0kg4qQ_zuLJ9i5O-2srp4,249
36
+ cellfinder/core/tools/prep.py,sha256=A1tfHZmkGFpbb7cjLdfP8KavZNqRwzdum92NW8Et01o,2553
37
+ cellfinder/core/tools/source_files.py,sha256=NCPeA_kvnPWgS_fxWarc-9nBNjxkgL1xMFg-Co_kPlA,791
38
38
  cellfinder/core/tools/system.py,sha256=6iSOLrz2mtKgOIdwjp_VS0-74XQCuaQYCvWAViJgEus,2232
39
39
  cellfinder/core/tools/tf.py,sha256=jNU97-gIB51rLube9EoZ9bREkbqWRk-xPZhDTXG_jE4,1713
40
40
  cellfinder/core/tools/tiff.py,sha256=NzIz6wq2GzxmcIhawFMwZADe-uQO2rIG46H7xkpGKLs,2899
41
41
  cellfinder/core/tools/tools.py,sha256=G8oDGNRuWkzEJDnnC4r3SNGgpVbqbelCZR5ODk9JRzs,4867
42
42
  cellfinder/core/train/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
- cellfinder/core/train/train_yml.py,sha256=n-mvcPM6u413JpzW7S6ow-5dJC2hEPGkezvXzT0yXhA,13139
44
- cellfinder/napari/__init__.py,sha256=STu4EgG0BBSD2yXQUVgP2coXt1LZLsCEP3SI-latdcM,73
45
- cellfinder/napari/curation.py,sha256=2n8_M2Wqr1JyP19V4-vZyiYmZbv61tzzu4WF-n5OKfs,19844
43
+ cellfinder/core/train/train_yml.py,sha256=TO_8e6Uz1S-P49o53gB_4i97Afz6RCHqmyyIrA70TZ8,13214
44
+ cellfinder/napari/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
+ cellfinder/napari/curation.py,sha256=jz7GekuuzOdwiEFLarvvO5Bho68k8eL7AjObMdEoPXw,21447
46
46
  cellfinder/napari/input_container.py,sha256=tkm0dkPt7kSL8Xkvs1fh8M6vKWw57QLIt_wv74HFkGk,2150
47
47
  cellfinder/napari/napari.yaml,sha256=WMR1CIAmYIVyQngbdbomTRZLvlDbb6LxsXsvTRClQnE,921
48
48
  cellfinder/napari/sample_data.py,sha256=oUST23q09MM8dxHbUCmO0AjtXG6OlR_32LLqP0EU2UA,732
49
- cellfinder/napari/utils.py,sha256=m8kmpkebS5_S71uMs1OTEMX0ndjnEZtglfkhRnP57-g,4765
49
+ cellfinder/napari/utils.py,sha256=mCy4Lr_gE2wWgRUSEiApNUjgLLAz0SSahTZnmUnRbgQ,2592
50
50
  cellfinder/napari/detect/__init__.py,sha256=BD9Bg9NTAr6yRTq2A_p58U6j4w5wbY0sdXwhPJ3MSMY,34
51
51
  cellfinder/napari/detect/detect.py,sha256=Jr4-lDGIntwcxSQ1Ne4xoA0SaKd4kf4zUT8MPyyO5ic,7443
52
52
  cellfinder/napari/detect/detect_containers.py,sha256=C_tN7qwWNHhYFRRn_2yQ7QDyxpj3pQXLZalO0UQ_cbw,5020
53
53
  cellfinder/napari/detect/thread_worker.py,sha256=BchLapCyML4SBDfihsqNLvqGM8FVcXEejaGg8MSkMCs,2604
54
54
  cellfinder/napari/images/brainglobe.png,sha256=IT2zfikD2vKTOg--e6bMRHr5mC2x3s-eOt_t0eblvQ0,21216
55
55
  cellfinder/napari/train/__init__.py,sha256=xo4CK-DvSecInGEc2ohcTgQYlH3iylFnGvKTCoq2WkI,35
56
- cellfinder/napari/train/train.py,sha256=CF5im8kYbO4oMkvNVoCJ5KM28gdBHM9Vqy9bsaAhxrA,5886
56
+ cellfinder/napari/train/train.py,sha256=Cv8ivPWZoJJ-Iq1vW1JUmViaSnBPeqs07jBvZfOZQLc,6061
57
57
  cellfinder/napari/train/train_containers.py,sha256=mYI_M1QssWgPBpkqViqgj1Ls32lVRUAjt6OGTqcrKI4,4390
58
- cellfinder-1.1.0.dist-info/LICENSE,sha256=Tw8iMytIDXLSmcIUsbQmRWojstl9yOWsPCx6ZT6dZLY,1564
59
- cellfinder-1.1.0.dist-info/METADATA,sha256=RmB9QJEct2Ax-9kuF18H1ml_HtVeUQcIoWNLrrwbpds,6633
60
- cellfinder-1.1.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
61
- cellfinder-1.1.0.dist-info/entry_points.txt,sha256=cKKjU8GPiN-TRelG2sT2JCKAcB9XDCjP6g9atE9pSoY,247
62
- cellfinder-1.1.0.dist-info/top_level.txt,sha256=jyTQzX-tDjbsMr6s-E71Oy0IKQzmHTXSk4ZhpG5EDSE,11
63
- cellfinder-1.1.0.dist-info/RECORD,,
58
+ cellfinder-1.1.2.dist-info/LICENSE,sha256=Tw8iMytIDXLSmcIUsbQmRWojstl9yOWsPCx6ZT6dZLY,1564
59
+ cellfinder-1.1.2.dist-info/METADATA,sha256=uP0C6fbKBWInZTmOxAfR24SIgOomzbvZZW82CYjXYXI,6652
60
+ cellfinder-1.1.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
61
+ cellfinder-1.1.2.dist-info/entry_points.txt,sha256=cKKjU8GPiN-TRelG2sT2JCKAcB9XDCjP6g9atE9pSoY,247
62
+ cellfinder-1.1.2.dist-info/top_level.txt,sha256=jyTQzX-tDjbsMr6s-E71Oy0IKQzmHTXSk4ZhpG5EDSE,11
63
+ cellfinder-1.1.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5