nnInteractive 2.3.0__tar.gz → 2.3.2__tar.gz
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.
- {nninteractive-2.3.0 → nninteractive-2.3.2}/PKG-INFO +1 -1
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/inference/remote/remote_session.py +52 -5
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/inference/remote/serialization.py +15 -6
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/inference/server/app.py +61 -30
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive.egg-info/PKG-INFO +1 -1
- {nninteractive-2.3.0 → nninteractive-2.3.2}/pyproject.toml +1 -1
- {nninteractive-2.3.0 → nninteractive-2.3.2}/LICENSE +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/inference/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/inference/cvpr2025_challenge_baseline/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/inference/cvpr2025_challenge_baseline/predict.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/inference/inference_session.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/inference/remote/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/inference/remote/_protocol.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/inference/server/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/inference/server/main.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/interaction/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/interaction/point.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/setup.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/metadata.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/reader.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/run.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/automatic_mask_generator.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/benchmark.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/build_sam.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/modeling/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/modeling/backbones/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/modeling/backbones/hieradet.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/modeling/backbones/image_encoder.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/modeling/backbones/utils.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/modeling/memory_attention.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/modeling/memory_encoder.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/modeling/position_encoding.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/modeling/sam/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/modeling/sam/mask_decoder.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/modeling/sam/prompt_encoder.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/modeling/sam/transformer.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/modeling/sam2_base.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/modeling/sam2_utils.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/sam2_image_predictor.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/sam2_video_predictor.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/sam2_video_predictor_legacy.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/utils/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/utils/amg.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/utils/misc.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/utils/transforms.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/setup.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/dataset/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/dataset/sam2_datasets.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/dataset/transforms.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/dataset/utils.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/dataset/vos_dataset.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/dataset/vos_raw_dataset.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/dataset/vos_sampler.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/dataset/vos_segment_loader.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/loss_fns.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/model/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/model/sam2.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/optimizer.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/scripts/sav_frame_extraction_submitit.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/train.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/trainer.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/utils/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/utils/checkpoint_utils.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/utils/data_utils.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/utils/distributed.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/utils/logger.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/utils/train_utils.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/supervoxel.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/trainer/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/trainer/nnInteractiveTrainer.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/utils/__init__.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/utils/bboxes.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/utils/checkpoint_cleansing.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/utils/crop.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/utils/erosion_dilation.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/utils/inference_helpers.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/utils/os_shennanigans.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/utils/rounding.py +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive.egg-info/SOURCES.txt +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive.egg-info/dependency_links.txt +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive.egg-info/entry_points.txt +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive.egg-info/requires.txt +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive.egg-info/top_level.txt +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/readme.md +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/setup.cfg +0 -0
- {nninteractive-2.3.0 → nninteractive-2.3.2}/setup.py +0 -0
{nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/inference/remote/remote_session.py
RENAMED
|
@@ -42,6 +42,16 @@ from nnInteractive.inference.remote._protocol import (
|
|
|
42
42
|
from nnInteractive.inference.remote.serialization import pack_array, unpack_array
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
def _compression_threads() -> int:
|
|
46
|
+
"""blosc2 thread count for client-side upload compression.
|
|
47
|
+
|
|
48
|
+
Full logical CPU count: blosc2 scales measurably onto SMT siblings, so use them all to
|
|
49
|
+
minimize upload latency. Per-call only (passed to pack_array → compress2), so it never
|
|
50
|
+
mutates blosc2's global nthreads.
|
|
51
|
+
"""
|
|
52
|
+
return max(1, os.cpu_count() or 1)
|
|
53
|
+
|
|
54
|
+
|
|
45
55
|
class SessionExpiredError(RuntimeError):
|
|
46
56
|
"""Raised when the server reports the client's lease no longer exists.
|
|
47
57
|
|
|
@@ -122,6 +132,13 @@ class nnInteractiveRemoteInferenceSession:
|
|
|
122
132
|
the server before the client gives up. Default 60s matches observed
|
|
123
133
|
prediction times (100ms..~10s) with headroom for slow links. On
|
|
124
134
|
expiry: ``httpx.ReadTimeout``.
|
|
135
|
+
set_image_read_timeout:
|
|
136
|
+
Read timeout (seconds) used *only* for ``set_image``. After the volume
|
|
137
|
+
is uploaded, the server decompresses and preprocesses the full image
|
|
138
|
+
before responding, which can take far longer than a prediction on a
|
|
139
|
+
large volume. ``set_image`` therefore gets its own generous read
|
|
140
|
+
timeout instead of the much tighter ``read_timeout`` used for
|
|
141
|
+
predictions. On expiry: ``httpx.ReadTimeout``.
|
|
125
142
|
write_timeout:
|
|
126
143
|
Seconds to finish uploading the request body. ``set_image`` uploads
|
|
127
144
|
the full 4D volume so this is the longest-running upload. On expiry:
|
|
@@ -136,6 +153,7 @@ class nnInteractiveRemoteInferenceSession:
|
|
|
136
153
|
api_key: Optional[str] = None,
|
|
137
154
|
connect_timeout: float = 10.0,
|
|
138
155
|
read_timeout: float = 60.0,
|
|
156
|
+
set_image_read_timeout: float = 600.0,
|
|
139
157
|
write_timeout: float = 120.0,
|
|
140
158
|
pool_timeout: float = 10.0,
|
|
141
159
|
):
|
|
@@ -156,6 +174,15 @@ class nnInteractiveRemoteInferenceSession:
|
|
|
156
174
|
),
|
|
157
175
|
headers=headers,
|
|
158
176
|
)
|
|
177
|
+
# Per-request timeout override for set_image: same connect/write/pool as
|
|
178
|
+
# the client default, but a much longer read budget for server-side
|
|
179
|
+
# decompression + preprocessing of the full volume.
|
|
180
|
+
self._set_image_timeout = httpx.Timeout(
|
|
181
|
+
connect=connect_timeout,
|
|
182
|
+
read=set_image_read_timeout,
|
|
183
|
+
write=write_timeout,
|
|
184
|
+
pool=pool_timeout,
|
|
185
|
+
)
|
|
159
186
|
self._lease_token: Optional[str] = None
|
|
160
187
|
|
|
161
188
|
# Claim a session on the server. The lease token is then attached to
|
|
@@ -232,12 +259,21 @@ class nnInteractiveRemoteInferenceSession:
|
|
|
232
259
|
resp.raise_for_status()
|
|
233
260
|
return resp
|
|
234
261
|
|
|
235
|
-
def _post_binary(
|
|
262
|
+
def _post_binary(
|
|
263
|
+
self,
|
|
264
|
+
path: str,
|
|
265
|
+
meta: dict,
|
|
266
|
+
array_bytes: bytes,
|
|
267
|
+
timeout: Union[httpx.Timeout, float, None] = None,
|
|
268
|
+
) -> httpx.Response:
|
|
236
269
|
headers = {
|
|
237
270
|
META_HEADER: json.dumps(_to_jsonable(meta), separators=(",", ":")),
|
|
238
271
|
"Content-Type": CONTENT_TYPE_OCTET_STREAM,
|
|
239
272
|
}
|
|
240
|
-
|
|
273
|
+
# httpx treats timeout=None as "no override" only when the arg is
|
|
274
|
+
# omitted; pass it through explicitly only when a caller supplied one.
|
|
275
|
+
kwargs = {} if timeout is None else {"timeout": timeout}
|
|
276
|
+
resp = self._http.post(path, content=array_bytes, headers=headers, **kwargs)
|
|
241
277
|
_raise_for_lease_errors(resp)
|
|
242
278
|
resp.raise_for_status()
|
|
243
279
|
return resp
|
|
@@ -290,7 +326,12 @@ class nnInteractiveRemoteInferenceSession:
|
|
|
290
326
|
def set_image(self, image: np.ndarray, image_properties: Optional[dict] = None) -> None:
|
|
291
327
|
assert image.ndim == 4, f"expected a 4d image as input, got {image.ndim}d. Shape {image.shape}"
|
|
292
328
|
meta = {"image_properties": image_properties or {}}
|
|
293
|
-
resp = self._post_binary(
|
|
329
|
+
resp = self._post_binary(
|
|
330
|
+
PATH_SET_IMAGE,
|
|
331
|
+
meta,
|
|
332
|
+
pack_array(image, nthreads=_compression_threads()),
|
|
333
|
+
timeout=self._set_image_timeout,
|
|
334
|
+
)
|
|
294
335
|
info = resp.json()
|
|
295
336
|
self.original_image_shape = tuple(info["original_image_shape"])
|
|
296
337
|
|
|
@@ -402,7 +443,9 @@ class nnInteractiveRemoteInferenceSession:
|
|
|
402
443
|
"interaction_bbox": ([list(b) for b in interaction_bbox] if interaction_bbox is not None else None),
|
|
403
444
|
}
|
|
404
445
|
# Interactions (scribble/lasso masks) compress best with NOFILTER; skip auto-selection.
|
|
405
|
-
resp = self._post_binary(
|
|
446
|
+
resp = self._post_binary(
|
|
447
|
+
path, meta, pack_array(mask_image, filters=[blosc2.Filter.NOFILTER], nthreads=_compression_threads())
|
|
448
|
+
)
|
|
406
449
|
self._apply_prediction_response(resp)
|
|
407
450
|
|
|
408
451
|
def add_initial_seg_interaction(
|
|
@@ -428,7 +471,11 @@ class nnInteractiveRemoteInferenceSession:
|
|
|
428
471
|
"override_capability_checks": bool(override_capability_checks),
|
|
429
472
|
}
|
|
430
473
|
# Segmentations compress best with NOFILTER; skip auto-selection.
|
|
431
|
-
resp = self._post_binary(
|
|
474
|
+
resp = self._post_binary(
|
|
475
|
+
PATH_ADD_INITIAL_SEG,
|
|
476
|
+
meta,
|
|
477
|
+
pack_array(initial_seg, filters=[blosc2.Filter.NOFILTER], nthreads=_compression_threads()),
|
|
478
|
+
)
|
|
432
479
|
self._apply_prediction_response(resp)
|
|
433
480
|
|
|
434
481
|
# ------------------------------------------------------------------ #
|
|
@@ -51,18 +51,21 @@ _ID_CODEC = {v: k for k, v in _CODEC_ID.items()}
|
|
|
51
51
|
_SELECT_FILTER_CROP_FRACTION = 0.25
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
def _compress_all(
|
|
54
|
+
def _compress_all(
|
|
55
|
+
raw: memoryview, total: int, codec: blosc2.Codec, clevel: int, filters: list, nthreads: Optional[int]
|
|
56
|
+
) -> int:
|
|
55
57
|
"""Compressed byte length of ``raw`` under ``filters``, chunked exactly as pack_array does."""
|
|
58
|
+
extra = {} if nthreads is None else {"nthreads": nthreads}
|
|
56
59
|
size = 0
|
|
57
60
|
nchunks = (total + _CHUNK_SIZE - 1) // _CHUNK_SIZE
|
|
58
61
|
for i in range(nchunks):
|
|
59
62
|
start = i * _CHUNK_SIZE
|
|
60
63
|
end = min(start + _CHUNK_SIZE, total)
|
|
61
|
-
size += len(blosc2.compress2(raw[start:end], codec=codec, clevel=clevel, filters=filters))
|
|
64
|
+
size += len(blosc2.compress2(raw[start:end], codec=codec, clevel=clevel, filters=filters, **extra))
|
|
62
65
|
return size
|
|
63
66
|
|
|
64
67
|
|
|
65
|
-
def _select_filter(arr: np.ndarray, codec: blosc2.Codec, clevel: int) -> "blosc2.Filter":
|
|
68
|
+
def _select_filter(arr: np.ndarray, codec: blosc2.Codec, clevel: int, nthreads: Optional[int]) -> "blosc2.Filter":
|
|
66
69
|
"""Pick NOFILTER vs SHUFFLE for ``arr`` by trial-compressing a small centered crop.
|
|
67
70
|
|
|
68
71
|
Uses ``compress2`` on the raw bytes — exactly the path pack_array takes — so the decision
|
|
@@ -79,7 +82,7 @@ def _select_filter(arr: np.ndarray, codec: blosc2.Codec, clevel: int) -> "blosc2
|
|
|
79
82
|
|
|
80
83
|
best_filter, best_bytes = blosc2.Filter.NOFILTER, None
|
|
81
84
|
for f in (blosc2.Filter.NOFILTER, blosc2.Filter.SHUFFLE):
|
|
82
|
-
cb = _compress_all(raw, total, codec, clevel, [f])
|
|
85
|
+
cb = _compress_all(raw, total, codec, clevel, [f], nthreads)
|
|
83
86
|
if best_bytes is None or cb < best_bytes:
|
|
84
87
|
best_bytes, best_filter = cb, f
|
|
85
88
|
return best_filter
|
|
@@ -95,6 +98,7 @@ def pack_array(
|
|
|
95
98
|
codec: blosc2.Codec = blosc2.Codec.ZSTD,
|
|
96
99
|
clevel: int = 3,
|
|
97
100
|
filters: Optional[list] = None,
|
|
101
|
+
nthreads: Optional[int] = None,
|
|
98
102
|
) -> bytes:
|
|
99
103
|
"""Serialize a numpy array to a self-describing compressed byte string.
|
|
100
104
|
|
|
@@ -104,6 +108,10 @@ def pack_array(
|
|
|
104
108
|
know the optimum (interactions and segmentations compress best with NOFILTER) should pass
|
|
105
109
|
``[blosc2.Filter.NOFILTER]`` to skip the selection. The chosen filter is self-describing
|
|
106
110
|
inside the blosc2 frame, so unpack_array (decompress2) needs no changes.
|
|
111
|
+
|
|
112
|
+
``nthreads`` is the per-call blosc2 thread count for compression. ``None`` (the default)
|
|
113
|
+
inherits blosc2's global ``nthreads`` (= core count). Passing an explicit value overrides
|
|
114
|
+
it for this call only, without mutating global state.
|
|
107
115
|
"""
|
|
108
116
|
arr = np.ascontiguousarray(arr)
|
|
109
117
|
dtype_str = arr.dtype.str.lstrip("<>|=").encode("ascii")
|
|
@@ -137,13 +145,14 @@ def pack_array(
|
|
|
137
145
|
if filters is None:
|
|
138
146
|
# Auto-select the better filter from a small centered crop, using the same
|
|
139
147
|
# compress2 path as below for consistency.
|
|
140
|
-
filters = [_select_filter(arr, codec, clevel)]
|
|
148
|
+
filters = [_select_filter(arr, codec, clevel, nthreads)]
|
|
141
149
|
|
|
150
|
+
extra = {} if nthreads is None else {"nthreads": nthreads}
|
|
142
151
|
parts = [header, shape_bytes, struct.pack("<I", nchunks)]
|
|
143
152
|
for i in range(nchunks):
|
|
144
153
|
start = i * _CHUNK_SIZE
|
|
145
154
|
end = min(start + _CHUNK_SIZE, total)
|
|
146
|
-
chunk = blosc2.compress2(raw[start:end], codec=codec, clevel=clevel, filters=filters)
|
|
155
|
+
chunk = blosc2.compress2(raw[start:end], codec=codec, clevel=clevel, filters=filters, **extra)
|
|
147
156
|
parts.append(struct.pack("<QQ", end - start, len(chunk)))
|
|
148
157
|
parts.append(chunk)
|
|
149
158
|
return b"".join(parts)
|
|
@@ -31,6 +31,15 @@ Concurrency model:
|
|
|
31
31
|
prediction runs at a time.
|
|
32
32
|
- The acquisition order is always (session lock → gpu lock) so there is no
|
|
33
33
|
deadlock potential.
|
|
34
|
+
- The endpoints that carry large payloads (``set_image`` and the mask
|
|
35
|
+
interactions) are ``async`` so they can ``await`` the upload, but their
|
|
36
|
+
CPU-bound work (blosc2 decompression, image preprocessing, prediction,
|
|
37
|
+
response compression) is dispatched to a worker thread via
|
|
38
|
+
``run_in_threadpool``. This keeps the event loop free during a long
|
|
39
|
+
``set_image``/predict so lightweight endpoints — ``/heartbeat``,
|
|
40
|
+
``/healthz`` — and the background reaper stay responsive, and so two
|
|
41
|
+
clients can genuinely preprocess concurrently. Acquiring a session/gpu
|
|
42
|
+
lock therefore also happens off the loop, never stalling it.
|
|
34
43
|
"""
|
|
35
44
|
|
|
36
45
|
from __future__ import annotations
|
|
@@ -50,6 +59,7 @@ import blosc2
|
|
|
50
59
|
import numpy as np
|
|
51
60
|
import torch
|
|
52
61
|
from fastapi import Depends, FastAPI, HTTPException, Header, Request, Response, status
|
|
62
|
+
from starlette.concurrency import run_in_threadpool
|
|
53
63
|
|
|
54
64
|
from nnInteractive.inference.inference_session import nnInteractiveInferenceSession
|
|
55
65
|
from nnInteractive.inference.remote._protocol import (
|
|
@@ -443,7 +453,9 @@ def make_app(
|
|
|
443
453
|
session._last_paste_bbox = None
|
|
444
454
|
return Response(
|
|
445
455
|
# Segmentations compress best with NOFILTER; skip auto-selection.
|
|
446
|
-
content=pack_array(
|
|
456
|
+
content=pack_array(
|
|
457
|
+
sub, filters=[blosc2.Filter.NOFILTER], nthreads=min(session.torch_n_threads, os.cpu_count())
|
|
458
|
+
),
|
|
447
459
|
media_type=CONTENT_TYPE_OCTET_STREAM,
|
|
448
460
|
headers={META_HEADER: json.dumps(meta, separators=(",", ":"))},
|
|
449
461
|
)
|
|
@@ -568,17 +580,24 @@ def make_app(
|
|
|
568
580
|
async def set_image(request: Request, entry: SessionEntry = lease) -> dict:
|
|
569
581
|
meta = _parse_meta_header(request.headers.get(META_HEADER))
|
|
570
582
|
body = await request.body()
|
|
571
|
-
image = unpack_array(body)
|
|
572
583
|
image_properties = meta.get("image_properties") or {}
|
|
573
584
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
return {"original_image_shape": list(session.original_image_shape)}
|
|
585
|
+
# Decompression + full-volume preprocessing are CPU-bound and can run
|
|
586
|
+
# for many seconds on a large image. Run them in a worker thread so the
|
|
587
|
+
# event loop keeps servicing heartbeats/healthz and the reaper.
|
|
588
|
+
def _work():
|
|
589
|
+
image = unpack_array(body)
|
|
580
590
|
|
|
581
|
-
|
|
591
|
+
def _do(session):
|
|
592
|
+
session.set_image(image, image_properties)
|
|
593
|
+
# set_image preprocesses in a background thread; force completion
|
|
594
|
+
# so subsequent calls can safely use original_image_shape.
|
|
595
|
+
session._finish_preprocessing_and_initialize_interactions()
|
|
596
|
+
return {"original_image_shape": list(session.original_image_shape)}
|
|
597
|
+
|
|
598
|
+
return _under_session_lock(entry, _do)
|
|
599
|
+
|
|
600
|
+
return await run_in_threadpool(_work)
|
|
582
601
|
|
|
583
602
|
@app.post(PATH_SET_TARGET_BUFFER, dependencies=[auth])
|
|
584
603
|
def set_target_buffer(payload: dict, entry: SessionEntry = lease) -> dict:
|
|
@@ -650,41 +669,53 @@ def make_app(
|
|
|
650
669
|
async def _handle_mask_interaction(request: Request, entry: SessionEntry, kind: str) -> Response:
|
|
651
670
|
meta = _parse_meta_header(request.headers.get(META_HEADER))
|
|
652
671
|
body = await request.body()
|
|
653
|
-
mask = unpack_array(body)
|
|
654
672
|
run_prediction = bool(meta.get("run_prediction", True))
|
|
655
673
|
interaction_bbox = meta.get("interaction_bbox")
|
|
656
674
|
if interaction_bbox is not None:
|
|
657
675
|
interaction_bbox = [list(b) for b in interaction_bbox]
|
|
658
676
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
bool(meta["include_interaction"]),
|
|
664
|
-
run_prediction=run_prediction,
|
|
665
|
-
override_capability_checks=bool(meta.get("override_capability_checks", False)),
|
|
666
|
-
interaction_bbox=interaction_bbox,
|
|
667
|
-
)
|
|
668
|
-
return _build_prediction_response(session, run_prediction)
|
|
677
|
+
# Decompression + prediction + response compression are CPU/GPU-bound;
|
|
678
|
+
# run them off the event loop (see set_image).
|
|
679
|
+
def _work():
|
|
680
|
+
mask = unpack_array(body)
|
|
669
681
|
|
|
670
|
-
|
|
682
|
+
def _do(session):
|
|
683
|
+
method = session.add_scribble_interaction if kind == "scribble" else session.add_lasso_interaction
|
|
684
|
+
method(
|
|
685
|
+
mask,
|
|
686
|
+
bool(meta["include_interaction"]),
|
|
687
|
+
run_prediction=run_prediction,
|
|
688
|
+
override_capability_checks=bool(meta.get("override_capability_checks", False)),
|
|
689
|
+
interaction_bbox=interaction_bbox,
|
|
690
|
+
)
|
|
691
|
+
return _build_prediction_response(session, run_prediction)
|
|
692
|
+
|
|
693
|
+
return _under_session_and_gpu_lock(entry, _do)
|
|
694
|
+
|
|
695
|
+
return await run_in_threadpool(_work)
|
|
671
696
|
|
|
672
697
|
@app.post(PATH_ADD_INITIAL_SEG, dependencies=[auth])
|
|
673
698
|
async def add_initial_seg_interaction(request: Request, entry: SessionEntry = lease) -> Response:
|
|
674
699
|
meta = _parse_meta_header(request.headers.get(META_HEADER))
|
|
675
700
|
body = await request.body()
|
|
676
|
-
initial_seg = unpack_array(body)
|
|
677
701
|
run_prediction = bool(meta.get("run_prediction", False))
|
|
678
702
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
override_capability_checks=bool(meta.get("override_capability_checks", False)),
|
|
684
|
-
)
|
|
685
|
-
return _build_prediction_response(session, run_prediction)
|
|
703
|
+
# Decompression + (optional) prediction are CPU/GPU-bound; run them off
|
|
704
|
+
# the event loop (see set_image).
|
|
705
|
+
def _work():
|
|
706
|
+
initial_seg = unpack_array(body)
|
|
686
707
|
|
|
687
|
-
|
|
708
|
+
def _do(session):
|
|
709
|
+
session.add_initial_seg_interaction(
|
|
710
|
+
initial_seg=initial_seg,
|
|
711
|
+
run_prediction=run_prediction,
|
|
712
|
+
override_capability_checks=bool(meta.get("override_capability_checks", False)),
|
|
713
|
+
)
|
|
714
|
+
return _build_prediction_response(session, run_prediction)
|
|
715
|
+
|
|
716
|
+
return _under_session_and_gpu_lock(entry, _do)
|
|
717
|
+
|
|
718
|
+
return await run_in_threadpool(_work)
|
|
688
719
|
|
|
689
720
|
return app
|
|
690
721
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/benchmark.py
RENAMED
|
File without changes
|
{nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/build_sam.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/utils/__init__.py
RENAMED
|
File without changes
|
{nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/utils/amg.py
RENAMED
|
File without changes
|
{nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/sam2/utils/misc.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/loss_fns.py
RENAMED
|
File without changes
|
|
File without changes
|
{nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/model/sam2.py
RENAMED
|
File without changes
|
{nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/optimizer.py
RENAMED
|
File without changes
|
|
File without changes
|
{nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/train.py
RENAMED
|
File without changes
|
{nninteractive-2.3.0 → nninteractive-2.3.2}/nnInteractive/supervoxel/src/sam2/training/trainer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|