cellfinder 1.3.3__py3-none-any.whl → 1.4.0a0__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.
@@ -0,0 +1,45 @@
1
+ import pooch
2
+
3
+
4
+ def fetch_pooch_directory(
5
+ registry: pooch.Pooch,
6
+ directory_name: str,
7
+ processor=None,
8
+ downloader=None,
9
+ progressbar=False,
10
+ ):
11
+ """
12
+ Fetches files from the Pooch registry that belong to a specific directory.
13
+ Parameters:
14
+ registry (pooch.Pooch): The Pooch registry object.
15
+ directory_name (str):
16
+ The remote relative path of the directory to fetch files from.
17
+ processor (callable, optional):
18
+ A function to process the fetched files. Defaults to None.
19
+ downloader (callable, optional):
20
+ A function to download the files. Defaults to None.
21
+ progressbar (bool, optional):
22
+ Whether to display a progress bar during the fetch.
23
+ Defaults to False.
24
+ Returns:
25
+ str: The local absolute path to the fetched directory.
26
+ """
27
+ names = []
28
+ for name in registry.registry_files:
29
+ if name.startswith(f"{directory_name}/"):
30
+ names.append(name)
31
+
32
+ if not names:
33
+ raise FileExistsError(
34
+ f"Unable to find files in directory {directory_name}"
35
+ )
36
+
37
+ for name in names:
38
+ registry.fetch(
39
+ name,
40
+ processor=processor,
41
+ downloader=downloader,
42
+ progressbar=progressbar,
43
+ )
44
+
45
+ return str(registry.abspath / directory_name)
@@ -0,0 +1,380 @@
1
+ """
2
+ Provides classes that can run a function in another thread or process and
3
+ allow passing data to and from the threads/processes. It also passes on any
4
+ exceptions that occur in the secondary thread/sub-process in the main thread
5
+ or when it exits.
6
+
7
+ If using a sub-process and pytorch Tensors are sent from/to the main
8
+ process, pytorch will memory map the tensor so the same data is shared and
9
+ edits in the main process will be visible in the sub-process. However, there
10
+ are limitations (such so not re-sharing a tensor shared with us). See
11
+ https://pytorch.org/docs/stable/multiprocessing.html#sharing-cuda-tensors for
12
+ details.
13
+
14
+ Typical example::
15
+
16
+ from cellfinder.core.tools.threading import ThreadWithException, \\
17
+ EOFSignal, ProcessWithException
18
+ import torch
19
+
20
+
21
+ def worker(thread: ThreadWithException, power: float):
22
+ while True:
23
+ # if the main thread wants us to exit, it'll wake us up
24
+ msg = thread.get_msg_from_mainthread()
25
+ # we were asked to exit
26
+ if msg == EOFSignal:
27
+ return
28
+
29
+ tensor_id, tensor, add = msg
30
+ # tensors are memory mapped for subprocess (and obv threads) so we
31
+ # can do the work inplace and result will be visible in main thread
32
+ tensor += add
33
+ tensor.pow_(power)
34
+
35
+ # we should not share a tensor shared with us as per pytorch docs,
36
+ # just send the id back
37
+ thread.send_msg_to_mainthread(tensor_id)
38
+
39
+ # we can also handle errors here, which will be re-raised in the main
40
+ # process
41
+ if tensor_id == 7:
42
+ raise ValueError("I fell asleep")
43
+
44
+
45
+ if __name__ == "__main__":
46
+ data = torch.rand((5, 10))
47
+ thread = ThreadWithException(
48
+ target=worker, args=(2.5,), pass_self=True
49
+ )
50
+ # use thread or sub-process
51
+ # thread = ProcessWithException(
52
+ # target=worker, args=(2.5,), pass_self=True
53
+ # )
54
+ thread.start()
55
+
56
+ try:
57
+ for i in range(10):
58
+ thread.send_msg_to_thread((i, data, i / 2))
59
+ # if the thread raises an exception, get_msg_from_thread will
60
+ # re-raise it here
61
+ msg = thread.get_msg_from_thread()
62
+ if msg == EOFSignal:
63
+ # thread exited for whatever reason (not exception)
64
+ break
65
+
66
+ print(f"Thread processed tensor {i}")
67
+ finally:
68
+ # whatever happens, make sure thread is told to finish so it
69
+ # doesn't get stuck
70
+ thread.notify_to_end_thread()
71
+ thread.join()
72
+
73
+ When run, this prints::
74
+
75
+ Thread processed tensor 0
76
+ Thread processed tensor 1
77
+ Thread processed tensor 2
78
+ Thread processed tensor 3
79
+ Thread processed tensor 4
80
+ Thread processed tensor 5
81
+ Thread processed tensor 6
82
+ Thread processed tensor 7
83
+ Traceback (most recent call last):
84
+ File "threading.py", line 139, in user_func_runner
85
+ self.user_func(self, *self.args)
86
+ File "play.py", line 24, in worker
87
+ raise ValueError("I fell asleep")
88
+ ValueError: I fell asleep
89
+
90
+ The above exception was the direct cause of the following exception:
91
+
92
+ Traceback (most recent call last):
93
+ File "play.py", line 38, in <module>
94
+ msg = thread.get_msg_from_thread()
95
+ File "threading.py", line 203, in get_msg_from_thread
96
+ raise ExecutionFailure(
97
+ ExecutionFailure: Reporting failure from other thread/process
98
+ """
99
+
100
+ from queue import Queue
101
+ from threading import Thread
102
+ from typing import Any, Callable, Optional
103
+
104
+ import torch.multiprocessing as mp
105
+
106
+
107
+ class EOFSignal:
108
+ """
109
+ This class object (not instance) is returned by the thread/process as an
110
+ indicator that someone exited or that you should exit.
111
+ """
112
+
113
+ pass
114
+
115
+
116
+ class ExecutionFailure(Exception):
117
+ """
118
+ Exception class raised in the main thread when the function running in the
119
+ thread/process raises an exception.
120
+
121
+ Get the original exception using the `__cause__` property of this
122
+ exception.
123
+ """
124
+
125
+ pass
126
+
127
+
128
+ class ExceptionWithQueueMixIn:
129
+ """
130
+ A mix-in that can be used with a secondary thread or sub-process to
131
+ facilitate communication with them and the passing back of exceptions and
132
+ signals such as when the thread/sub-process exits or when the main
133
+ thread/process want the sub-process/thread to end.
134
+
135
+ Communication happens bi-directionally via queues. These queues are not
136
+ limited in buffer size, so if there's potential for the queue to be backed
137
+ up because the main thread or sub-thread/process is unable to read and
138
+ process messages quickly enough, you must implement some kind of
139
+ back-pressure to prevent the queue from growing unlimitedly in size.
140
+ E.g. using a limited number of tokens that must be held to send a message
141
+ to prevent a sender from thrashing the queue. And these tokens can be
142
+ passed between main thread to sub-thread/process etc on each message and
143
+ when the message is done processing.
144
+
145
+ The main process uses `get_msg_from_thread` to raise exceptions in the
146
+ main thread that occurred in the sub-thread/process. So that must be
147
+ called in the main thread if you want to know if the sub-thread/process
148
+ exited.
149
+ """
150
+
151
+ to_thread_queue: Queue
152
+ """
153
+ Internal queue used to send messages to the thread from the main thread.
154
+ """
155
+
156
+ from_thread_queue: Queue
157
+ """
158
+ Internal queue used to send messages from the thread to the main thread.
159
+ """
160
+
161
+ args: tuple = ()
162
+ """
163
+ The args provided by the caller that will be passed to the function running
164
+ in the thread/process when it starts.
165
+ """
166
+
167
+ # user_func_runner must end with an eof msg to the main thread. This tracks
168
+ # whether the main thread saw the eof. If it didn't, we know there are more
169
+ # msgs in the queue waiting to be read
170
+ _saw_eof: bool = False
171
+
172
+ def __init__(self, target: Callable, pass_self: bool = False):
173
+ self.user_func = target
174
+ self.pass_self = pass_self
175
+
176
+ def user_func_runner(self) -> None:
177
+ """
178
+ The internal function that runs the target function provided by the
179
+ user.
180
+ """
181
+ try:
182
+ if self.user_func is not None:
183
+ if self.pass_self:
184
+ self.user_func(self, *self.args)
185
+ else:
186
+ self.user_func(*self.args)
187
+ except BaseException as e:
188
+ self.from_thread_queue.put(
189
+ ("exception", e), block=True, timeout=None
190
+ )
191
+ finally:
192
+ self.from_thread_queue.put(("eof", None), block=True, timeout=None)
193
+
194
+ def send_msg_to_mainthread(self, value: Any) -> None:
195
+ """
196
+ Sends the `value` to the main thread, from the sub-thread/process.
197
+
198
+ The value must be pickleable if it's running in a sub-process. The main
199
+ thread then can read it using `get_msg_from_thread`.
200
+
201
+ The queue is not limited in size, so if there's potential for the
202
+ queue to be backed up because the main thread is unable to read quickly
203
+ enough, you must implement some kind of back-pressure to prevent
204
+ the queue from growing unlimitedly in size.
205
+ """
206
+ self.from_thread_queue.put(("user", value), block=True, timeout=None)
207
+
208
+ def clear_remaining(self) -> None:
209
+ """
210
+ Celled in the main-thread as part of cleanup when we expect the
211
+ secondary thread to have exited (e.g. we sent it a message telling it
212
+ to).
213
+
214
+ It will drop any waiting messages sent by the secondary thread, but
215
+ more importantly, it will handle exceptions raised in the secondary
216
+ thread before it exited, that may not have yet been processed in the
217
+ main thread (e.g. we stopped listening to messages from the secondary
218
+ thread before we got an eof from it).
219
+ """
220
+ while not self._saw_eof:
221
+ self.get_msg_from_thread()
222
+
223
+ def get_msg_from_thread(self, timeout: Optional[float] = None) -> Any:
224
+ """
225
+ Gets a message from the sub-thread/process sent to the main thread.
226
+
227
+ This blocks forever until the message is sent by the sub-thread/process
228
+ and received by us. If `timeout` is not None, that's how long we block
229
+ here, in seconds, before raising an `Empty` Exception if no message was
230
+ received by then.
231
+
232
+ If the return value is the `EOFSignal` object, it means the
233
+ sub-thread/process has or is about to exit.
234
+
235
+ If the sub-thread/process has raised an exception, that exception is
236
+ caught and re-raised in the main thread when this method is called.
237
+ The exception raised is an `ExecutionFailure` and its `__cause__`
238
+ property is the original exception raised in the sub-thread/process.
239
+
240
+ A typical pattern is::
241
+
242
+ >>> try:
243
+ ... msg = thread.get_msg_from_thread()
244
+ ... if msg == EOFSignal:
245
+ ... # thread exited
246
+ ... pass
247
+ ... else:
248
+ ... # do something with the msg
249
+ ... pass
250
+ ... except ExecutionFailure as e:
251
+ ... print(f"got exception {type(e.__cause__)}")
252
+ ... print(f"with message {e.__cause__.args[0]}")
253
+ """
254
+ msg, value = self.from_thread_queue.get(block=True, timeout=timeout)
255
+ if msg == "eof":
256
+ self._saw_eof = True
257
+ return EOFSignal
258
+ if msg == "exception":
259
+ raise ExecutionFailure(
260
+ "Reporting failure from other thread/process"
261
+ ) from value
262
+
263
+ return value
264
+
265
+ def send_msg_to_thread(self, value: Any) -> None:
266
+ """
267
+ Sends the `value` to the sub-thread/process, from the main thread.
268
+
269
+ The value must be pickleable if it's sent to a sub-process. The thread
270
+ then can read it using `get_msg_from_mainthread`.
271
+
272
+ The queue is not limited in size, so if there's potential for the
273
+ queue to be backed up because the thread is unable to read quickly
274
+ enough, you must implement some kind of back-pressure to prevent
275
+ the queue from growing unlimitedly in size.
276
+ """
277
+ self.to_thread_queue.put(("user", value), block=True, timeout=None)
278
+
279
+ def notify_to_end_thread(self) -> None:
280
+ """
281
+ Sends a message to the sub-process/thread that the main process wants
282
+ them to end. The sub-process/thread sees it by receiving an `EOFSignal`
283
+ message from `get_msg_from_mainthread` and it should exit asap.
284
+ """
285
+ self.to_thread_queue.put(("eof", None), block=True, timeout=None)
286
+
287
+ def get_msg_from_mainthread(self, timeout: Optional[float] = None) -> Any:
288
+ """
289
+ Gets a message from the main thread sent to the sub-thread/process.
290
+
291
+ This blocks forever until the message is sent by the main thread
292
+ and received by us. If `timeout` is not None, that's how long we block
293
+ here, in seconds, before raising an `Empty` Exception if no message was
294
+ received by then.
295
+
296
+ If the return value is the `EOFSignal` object, it means the
297
+ main thread has sent us an EOF message using `notify_to_end_thread`
298
+ because it wants us to exit.
299
+
300
+ A typical pattern is::
301
+
302
+ >>> msg = thread.get_msg_from_mainthread()
303
+ ... if msg == EOFSignal:
304
+ ... # we should exit asap
305
+ ... return
306
+ ... # do something with the msg
307
+ """
308
+ msg, value = self.to_thread_queue.get(block=True, timeout=timeout)
309
+ if msg == "eof":
310
+ return EOFSignal
311
+
312
+ return value
313
+
314
+
315
+ class ThreadWithException(ExceptionWithQueueMixIn):
316
+ """
317
+ Runs a target function in a secondary thread.
318
+ """
319
+
320
+ thread: Thread = None
321
+ """The thread running the function."""
322
+
323
+ def __init__(self, target, args=(), **kwargs):
324
+ super().__init__(target=target, **kwargs)
325
+ self.to_thread_queue = Queue(maxsize=0)
326
+ self.from_thread_queue = Queue(maxsize=0)
327
+ self.args = args
328
+ self.thread = Thread(target=self.user_func_runner)
329
+
330
+ def start(self) -> None:
331
+ """Starts the thread that runs the target function."""
332
+ self.thread.start()
333
+
334
+ def join(self, timeout: Optional[float] = None) -> None:
335
+ """
336
+ Waits and blocks until the thread exits. If timeout is given,
337
+ it's the duration to wait, in seconds, before returning.
338
+
339
+ To know if it exited, you need to check `is_alive` of the `thread`.
340
+ """
341
+ self.thread.join(timeout=timeout)
342
+
343
+
344
+ class ProcessWithException(ExceptionWithQueueMixIn):
345
+ """
346
+ Runs a target function in a sub-process.
347
+
348
+ Any data sent between the processes must be pickleable.
349
+
350
+ We run the function using `torch.multiprocessing`. Any tensors sent between
351
+ the main process and sub-process is memory mapped so that it doesn't copy
352
+ the tensor. So any edits in the main process/sub-process is seen in the
353
+ other as well. See https://pytorch.org/docs/stable/multiprocessing.html
354
+ for more details on this.
355
+ """
356
+
357
+ process: mp.Process = None
358
+ """The sub-process running the function."""
359
+
360
+ def __init__(self, target, args=(), **kwargs):
361
+ super().__init__(target=target, **kwargs)
362
+ ctx = mp.get_context("spawn")
363
+ self.to_thread_queue = ctx.Queue(maxsize=0)
364
+ self.from_thread_queue = ctx.Queue(maxsize=0)
365
+ self.process = ctx.Process(target=self.user_func_runner)
366
+
367
+ self.args = args
368
+
369
+ def start(self) -> None:
370
+ """Starts the sub-process that runs the target function."""
371
+ self.process.start()
372
+
373
+ def join(self, timeout: Optional[float] = None) -> None:
374
+ """
375
+ Waits and blocks until the process exits. If timeout is given,
376
+ it's the duration to wait, in seconds, before returning.
377
+
378
+ To know if it exited, you need to check `is_alive` of the `process`.
379
+ """
380
+ self.process.join(timeout=timeout)
@@ -1,19 +1,141 @@
1
+ from functools import wraps
1
2
  from random import getrandbits, uniform
2
- from typing import Optional
3
+ from typing import Callable, Optional, Type
3
4
 
4
5
  import numpy as np
6
+ import torch
5
7
  from natsort import natsorted
6
8
 
7
9
 
8
- def get_max_possible_value(obj_in: np.ndarray) -> int:
10
+ def inference_wrapper(func):
9
11
  """
10
- Returns the maximum allowed value for a numpy array of integer data type.
12
+ Decorator that makes the decorated function/method run with
13
+ `torch.inference_mode` set to True.
14
+ """
15
+
16
+ @wraps(func)
17
+ def inner_function(*args, **kwargs):
18
+ with torch.inference_mode(True):
19
+ return func(*args, **kwargs)
20
+
21
+ return inner_function
22
+
23
+
24
+ def get_max_possible_int_value(dtype: Type[np.number]) -> int:
25
+ """
26
+ Returns the maximum allowed integer for a numpy array of given type.
27
+
28
+ If dtype is of integer type, it's the maximum value. If it's a floating
29
+ type, it's the maximum integer that can be accurately represented.
30
+ E.g. for float32, only integers up to 2**24 can be represented (due to
31
+ the number of bits representing the mantissa (significand).
11
32
  """
12
- dtype = obj_in.dtype
13
33
  if np.issubdtype(dtype, np.integer):
14
34
  return np.iinfo(dtype).max
15
- else:
16
- raise ValueError("obj_in must be a numpy array of integer data type.")
35
+ if np.issubdtype(dtype, np.floating):
36
+ mant = np.finfo(dtype).nmant
37
+ return 2**mant
38
+ raise ValueError("datatype must be of integer or floating data type")
39
+
40
+
41
+ def get_min_possible_int_value(dtype: Type[np.number]) -> int:
42
+ """
43
+ Returns the minimum allowed integer for a numpy array of given type.
44
+
45
+ If dtype is of integer type, it's the minimum value. If it's a floating
46
+ type, it's the minimum integer that can be accurately represented.
47
+ E.g. for float32, only integers up to -2**24 can be represented (due to
48
+ the number of bits representing the mantissa (significand).
49
+ """
50
+ if np.issubdtype(dtype, np.integer):
51
+ return np.iinfo(dtype).min
52
+ if np.issubdtype(dtype, np.floating):
53
+ mant = np.finfo(dtype).nmant
54
+ # the sign bit is separate so we have the full mantissa for value
55
+ return -(2**mant)
56
+ raise ValueError("datatype must be of integer or floating data type")
57
+
58
+
59
+ def get_data_converter(
60
+ src_dtype: Type[np.number], dest_dtype: Type[np.floating]
61
+ ) -> Callable[[np.ndarray], np.ndarray]:
62
+ """
63
+ Returns a function that can be called to convert one data-type to another,
64
+ scaling the data down as needed.
65
+
66
+ If the maximum value supported by the input data-type is smaller than that
67
+ supported by destination data-type, the data will be scaled by the ratio
68
+ of maximum integer representable by the `output / input` data-types.
69
+ If the max is equal or less, it's simply converted to the target type.
70
+
71
+ Parameters
72
+ ----------
73
+ src_dtype : np.dtype
74
+ The data-type of the input data.
75
+ dest_dtype : np.dtype
76
+ The data-type of the returned data. Currently, it must be a floating
77
+ type and `np.float32` or `np.float64`.
78
+
79
+ Returns
80
+ -------
81
+ callable: function
82
+ A function that takes a single input data parameter and returns
83
+ the converted data.
84
+ """
85
+ if not np.issubdtype(dest_dtype, np.float32) and not np.issubdtype(
86
+ dest_dtype, np.float64
87
+ ):
88
+ raise ValueError(
89
+ f"Destination dtype must be a float32 or float64, "
90
+ f"but it is {dest_dtype}"
91
+ )
92
+
93
+ in_min = get_min_possible_int_value(src_dtype)
94
+ in_max = get_max_possible_int_value(src_dtype)
95
+ out_min = get_min_possible_int_value(dest_dtype)
96
+ out_max = get_max_possible_int_value(dest_dtype)
97
+ in_abs_max = max(in_max, abs(in_min))
98
+ out_abs_max = max(out_max, abs(out_min))
99
+
100
+ def unchanged(data: np.ndarray) -> np.ndarray:
101
+ return np.asarray(data)
102
+
103
+ def float_to_float_scale_down(data: np.ndarray) -> np.ndarray:
104
+ return ((np.asarray(data) / in_abs_max) * out_abs_max).astype(
105
+ dest_dtype
106
+ )
107
+
108
+ def int_to_float_scale_down(data: np.ndarray) -> np.ndarray:
109
+ # data must fit in float64
110
+ data = np.asarray(data).astype(np.float64)
111
+ return ((data / in_abs_max) * out_abs_max).astype(dest_dtype)
112
+
113
+ def to_float_unscaled(data: np.ndarray) -> np.ndarray:
114
+ return np.asarray(data).astype(dest_dtype)
115
+
116
+ if src_dtype == dest_dtype:
117
+ return unchanged
118
+
119
+ # out can hold the largest in values - just convert to float
120
+ if out_min <= in_min < in_max <= out_max:
121
+ return to_float_unscaled
122
+
123
+ # need to scale down before converting to float
124
+ if np.issubdtype(src_dtype, np.integer):
125
+ # if going to float32 and it didn't fit input must fit in 64-bit float
126
+ # so we can temp store it there to scale. If going to float64, same.
127
+ if in_max > get_max_possible_int_value(
128
+ np.float64
129
+ ) or in_min < get_min_possible_int_value(np.float64):
130
+ raise ValueError(
131
+ f"The input datatype {src_dtype} cannot fit in a "
132
+ f"64-bit float"
133
+ )
134
+ return int_to_float_scale_down
135
+
136
+ # for float input, however big it is, we can always scale it down in the
137
+ # input data type before changing type
138
+ return float_to_float_scale_down
17
139
 
18
140
 
19
141
  def union(a, b):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cellfinder
3
- Version: 1.3.3
3
+ Version: 1.4.0a0
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
@@ -33,7 +33,7 @@ Requires-Dist: numpy
33
33
  Requires-Dist: scikit-image
34
34
  Requires-Dist: scikit-learn
35
35
  Requires-Dist: keras==3.5.0
36
- Requires-Dist: torch>=2.1.0
36
+ Requires-Dist: torch!=2.4,>=2.1.0
37
37
  Requires-Dist: tifffile
38
38
  Requires-Dist: tqdm
39
39
  Provides-Extra: dev
@@ -46,6 +46,7 @@ Requires-Dist: pytest-qt; extra == "dev"
46
46
  Requires-Dist: pytest-timeout; extra == "dev"
47
47
  Requires-Dist: pytest; extra == "dev"
48
48
  Requires-Dist: tox; extra == "dev"
49
+ Requires-Dist: pooch>=1; extra == "dev"
49
50
  Provides-Extra: napari
50
51
  Requires-Dist: brainglobe-napari-io; extra == "napari"
51
52
  Requires-Dist: magicgui; extra == "napari"
@@ -1,32 +1,33 @@
1
1
  cellfinder/__init__.py,sha256=S5oQ3EORuyQTMYC4uUuzGKZ23J3Ya6q-1DOBib1KfiA,1166
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=t2mkq6iieEytbPckehBB43juwN5E-vhzstLSs620vdM,3625
4
+ cellfinder/core/main.py,sha256=QiLo8qtK6hRT_cu-xSP9k93EE-dZjmb2xxocsY-hTwU,3836
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
- cellfinder/core/classify/classify.py,sha256=uhvBGp3uFRp6uZeUtAIIS4BbIotQzZ_rql2zT_l-9Ok,3523
8
+ cellfinder/core/classify/classify.py,sha256=UN5j4-5RlsKNt9wd1A8udCjW4t-D4MySEyQ9BIkCckI,3529
9
9
  cellfinder/core/classify/cube_generator.py,sha256=jC5aogTVy213PHouViSR9CgKkuOks3yk6csQC5kRoOE,17125
10
10
  cellfinder/core/classify/resnet.py,sha256=vGa85y_NcQnOXwAt5EtatLx5mrO8IoShCcNKtJ5-EFg,10034
11
11
  cellfinder/core/classify/tools.py,sha256=s5PEKAsZVbVuoferZ_nqMUM0f2bV_8WEKsdKe3SXEuE,2560
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=iEYaYuYCSR4VJXmlFbEE1ti9Pjpv6m4rSfFILSOYmhE,9592
15
+ cellfinder/core/detect/detect.py,sha256=cd6D3XOzP5HEA_Hb3Ik4AVmNzu0im4sQxV46cCY6izU,8056
16
16
  cellfinder/core/detect/filters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- cellfinder/core/detect/filters/setup_filters.py,sha256=sUyZ2dKbfwhn4YTq3kLVTHkckZZ460AVozLfoz7hJK8,2084
17
+ cellfinder/core/detect/filters/setup_filters.py,sha256=gvWhdGVdGrK_Ab-t0kKPnXaf43qLi78ed_-vNPI0nE4,14554
18
18
  cellfinder/core/detect/filters/plane/__init__.py,sha256=lybcPbVDnurEQeq2FiLq0zR8p_ztarQOlajhdh1Q2-4,40
19
- cellfinder/core/detect/filters/plane/classical_filter.py,sha256=Nton_nrvv6LhNlK10HnS0TIHsjapBiMJPURgzYh2myY,1331
20
- cellfinder/core/detect/filters/plane/plane_filter.py,sha256=PWcAHDKB3qn3i9aSmQzUTEi3tgmccrf24DipjnYbOg0,2746
21
- cellfinder/core/detect/filters/plane/tile_walker.py,sha256=lhKrq-MB35bR0_JYgj7WMR4otuaZ6XRDDhJhKyH1yS8,3001
19
+ cellfinder/core/detect/filters/plane/classical_filter.py,sha256=X5k266tbl9EHRVY5dls53B5IZlmP7U0UB9BsZ1ey_pc,13250
20
+ cellfinder/core/detect/filters/plane/plane_filter.py,sha256=6FbAjxgt39_RdGDXC_pQ9ndtZtknuHJeTWtbR8cReEA,6102
21
+ cellfinder/core/detect/filters/plane/tile_walker.py,sha256=IiQibvWKnYlgl9h414fRklV7C2xZ0vXNmJ9t89DhYuI,4863
22
22
  cellfinder/core/detect/filters/volume/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- cellfinder/core/detect/filters/volume/ball_filter.py,sha256=ZEg8FNty-VD5N9j4FLZ18G-BuSvxjHs2-QTblwKcZgw,13894
24
- cellfinder/core/detect/filters/volume/structure_detection.py,sha256=mTHi0_A9FCK39I6fZm2dfMpDxIkfaidOIf6eCjTw-70,11307
25
- cellfinder/core/detect/filters/volume/structure_splitting.py,sha256=DnYcCkcdEaubzaWxbM7Ee8icyHq3dN8CcvZj_i0QvGM,7880
26
- cellfinder/core/detect/filters/volume/volume_filter.py,sha256=BJ8bStlNvMWsGlBekeqXpS8d9YKoLe3ifMfrEPpva2Q,7035
23
+ cellfinder/core/detect/filters/volume/ball_filter.py,sha256=qIDo206f558B46tpxrOGXasP8VE6pZxju-duvhPRuNA,14440
24
+ cellfinder/core/detect/filters/volume/structure_detection.py,sha256=AIHq-5u5VFpKBBLEsE1Py-MlndbL8T0zXu0Bq2CI16Y,12916
25
+ cellfinder/core/detect/filters/volume/structure_splitting.py,sha256=0LGE8cQ6EJIsKqWWmd087Q3At8m-PUDmmPjCZKV1TdY,10138
26
+ cellfinder/core/detect/filters/volume/volume_filter.py,sha256=o9_4IsITedhqkhfwRsIhE53_zLNcFjgQzXbMMddH_q4,20544
27
27
  cellfinder/core/download/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  cellfinder/core/download/cli.py,sha256=X9L9ZIkWqs58hX8G8q_0AKN4gk8BhydGxI9nLpdHQQE,1764
29
29
  cellfinder/core/download/download.py,sha256=YQP4NMM9Xt0rECRJEV5z6RCZNi5uCFhohFnDreH0qHA,3277
30
+ cellfinder/core/tools/IO.py,sha256=jK1R_6YahQ_MbPlwg1IM-Fp-din-hLBNq71Jxn3q2WQ,1356
30
31
  cellfinder/core/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
32
  cellfinder/core/tools/array_operations.py,sha256=LDbqWA_N2YtxeKS877QYdJRL2FCMC1R1ExJtdb6-vEA,3371
32
33
  cellfinder/core/tools/geometry.py,sha256=xlEQQmVQ9jcXqRzUqU554P8VxkaWkc5J_YhjkkKlI0Q,1124
@@ -34,8 +35,9 @@ cellfinder/core/tools/image_processing.py,sha256=z27bGjf3Iv3G4Nt1OYzpEnIYQNc4nNo
34
35
  cellfinder/core/tools/prep.py,sha256=YU4VDyjEsOXn3S3gymEfPUzka-WYiHmR4Q4XdRebhDI,2175
35
36
  cellfinder/core/tools/source_files.py,sha256=vvwsIMe1ULKvXg_x22L75iqvCyMjEbUjJsDFQk3GYzY,856
36
37
  cellfinder/core/tools/system.py,sha256=WvEzPr7v-ohLDnzf4n13TMcN5OYIAXOEkaSmrHzdnwc,2438
38
+ cellfinder/core/tools/threading.py,sha256=EmDLF22ZvXJnsWg7g6sv4cZgNweC0YrVw2o7QXjrO9s,14094
37
39
  cellfinder/core/tools/tiff.py,sha256=NzIz6wq2GzxmcIhawFMwZADe-uQO2rIG46H7xkpGKLs,2899
38
- cellfinder/core/tools/tools.py,sha256=G8oDGNRuWkzEJDnnC4r3SNGgpVbqbelCZR5ODk9JRzs,4867
40
+ cellfinder/core/tools/tools.py,sha256=opMGC5GBBsId0dmL8V0KQrI4U70w_D_KtGQYpZNeHYQ,9390
39
41
  cellfinder/core/train/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
42
  cellfinder/core/train/train_yml.py,sha256=9QXv2wk24G8hYskMnBij7OngEELUWySK2fH4NFbYWw4,13260
41
43
  cellfinder/napari/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -51,9 +53,9 @@ cellfinder/napari/detect/thread_worker.py,sha256=PWM3OE-FpK-dpdhaE_Gi-2lD3u8sL-S
51
53
  cellfinder/napari/train/__init__.py,sha256=xo4CK-DvSecInGEc2ohcTgQYlH3iylFnGvKTCoq2WkI,35
52
54
  cellfinder/napari/train/train.py,sha256=zJY7zKcLqDTDtD76thmbwViEU4tTFCmXZze-zHsTpoo,5941
53
55
  cellfinder/napari/train/train_containers.py,sha256=1wZ_GPe7B5XsLYs5XIx4m8GMw5KeVhg6SchhPtXu4V8,4386
54
- cellfinder-1.3.3.dist-info/LICENSE,sha256=Tw8iMytIDXLSmcIUsbQmRWojstl9yOWsPCx6ZT6dZLY,1564
55
- cellfinder-1.3.3.dist-info/METADATA,sha256=27H5g4lHvr3tyLQNPD7H3OnqUN2Y_LZFNOB15M9gS5s,7074
56
- cellfinder-1.3.3.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
57
- cellfinder-1.3.3.dist-info/entry_points.txt,sha256=cKKjU8GPiN-TRelG2sT2JCKAcB9XDCjP6g9atE9pSoY,247
58
- cellfinder-1.3.3.dist-info/top_level.txt,sha256=jyTQzX-tDjbsMr6s-E71Oy0IKQzmHTXSk4ZhpG5EDSE,11
59
- cellfinder-1.3.3.dist-info/RECORD,,
56
+ cellfinder-1.4.0a0.dist-info/LICENSE,sha256=Tw8iMytIDXLSmcIUsbQmRWojstl9yOWsPCx6ZT6dZLY,1564
57
+ cellfinder-1.4.0a0.dist-info/METADATA,sha256=X5NbLqZZpyJC2_JDAEo03MHDAjgzYN7SJsh_x2vkS48,7122
58
+ cellfinder-1.4.0a0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
59
+ cellfinder-1.4.0a0.dist-info/entry_points.txt,sha256=cKKjU8GPiN-TRelG2sT2JCKAcB9XDCjP6g9atE9pSoY,247
60
+ cellfinder-1.4.0a0.dist-info/top_level.txt,sha256=jyTQzX-tDjbsMr6s-E71Oy0IKQzmHTXSk4ZhpG5EDSE,11
61
+ cellfinder-1.4.0a0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5