arraybridge 0.3.4__tar.gz → 0.3.6__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.
- {arraybridge-0.3.4 → arraybridge-0.3.6}/PKG-INFO +2 -1
- arraybridge-0.3.6/docs/source/release_035_validation.rst +51 -0
- arraybridge-0.3.6/docs/source/release_036_validation.rst +58 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/pyproject.toml +1 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/__init__.py +1 -1
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/decorators.py +41 -16
- arraybridge-0.3.6/tests/test_callable_dtype_default.py +61 -0
- arraybridge-0.3.6/tests/test_durable_decorator_context.py +50 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/.github/workflows/ci.yml +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/.github/workflows/coverage-pages.yml +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/.github/workflows/gpu-tests.yml +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/.github/workflows/publish.yml +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/.gitignore +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/.readthedocs.yml +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/ADDING_NEW_FRAMEWORKS.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/CONTRIBUTING.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/LICENSE +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/README.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/TESTING.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/Makefile +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/archive/CI_ARTIFACT_UPDATE.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/archive/CI_CUPY_FIX.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/archive/CI_IMPROVEMENTS.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/archive/CONFLICT_VISUALIZATION.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/archive/COVERAGE_AUDIT_PLAN.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/archive/DEPLOYMENT_SUMMARY.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/archive/GPU_TESTING_SETUP.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/archive/MERGE_CONFLICT_RESOLUTION.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/archive/MIGRATION_SUMMARY.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/archive/QUICK_FIX_GUIDE.txt +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/archive/README.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/archive/RESOLVED_FILES.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/ci-cd.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/.gitignore +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/Makefile +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/README.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/advanced_topics.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/api_reference.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/ci-cd.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/conf.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/contributing.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/converters.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/decorators.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/examples/basic_conversion.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/examples/decorators.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/examples/index.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/examples/multi_framework.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/gpu_features.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/index.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/installation.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/quickstart.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/stack_utils.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/docs/source/user_guide.rst +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/paper.bib +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/paper.md +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/scripts/release.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/scripts/update_version.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/scripts/verify_release_ready.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/array_geometry.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/array_operations.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/array_payload.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/converters.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/converters_registry.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/dtype_scaling.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/exceptions.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/framework_config.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/framework_ops.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/gpu_cleanup.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/oom_recovery.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/slice_processing.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/stack_utils.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/types.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/src/arraybridge/utils.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/__init__.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/conftest.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/test_array_geometry.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/test_converters.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/test_converters_registry.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/test_decorators.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/test_dtype_scaling.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/test_exceptions.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/test_framework_config.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/test_gpu_cleanup.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/test_integration.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/test_oom_recovery.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/test_registry_integration.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/test_slice_processing.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/test_stack_utils.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/test_types.py +0 -0
- {arraybridge-0.3.4 → arraybridge-0.3.6}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arraybridge
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: Unified API for NumPy, CuPy, PyTorch, TensorFlow, JAX, and pyclesperanto with automatic memory type conversion
|
|
5
5
|
Project-URL: Homepage, https://github.com/OpenHCSDev/arraybridge
|
|
6
6
|
Project-URL: Documentation, https://arraybridge.readthedocs.io
|
|
@@ -36,6 +36,7 @@ Requires-Dist: cupy>=10.0; extra == 'cupy'
|
|
|
36
36
|
Provides-Extra: dev
|
|
37
37
|
Requires-Dist: black>=23.0; extra == 'dev'
|
|
38
38
|
Requires-Dist: build>=1.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: dill>=0.3.8; extra == 'dev'
|
|
39
40
|
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
40
41
|
Requires-Dist: packaging>=23.0; extra == 'dev'
|
|
41
42
|
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
ArrayBridge 0.3.5 release preparation
|
|
2
|
+
===================================
|
|
3
|
+
|
|
4
|
+
Parent integration owner; base ea3f2a4cc91c4810d12343f58f85c1195e1a41e6.
|
|
5
|
+
The reviewed callable-native dtype default and durable decorator-context
|
|
6
|
+
changes are already merged through PR2 and PR3. This release preparation changes
|
|
7
|
+
only the existing package version authority from 0.3.4 to 0.3.5 and records
|
|
8
|
+
validation. It adds no implementation, compatibility API or parallel registry.
|
|
9
|
+
|
|
10
|
+
OpenHCS PR217 requires ``dtype_config_default``. A fresh PyPI ArrayBridge0.3.4
|
|
11
|
+
wheel raises TypeError at its BaSiC adapter's decoration. Its recorded local
|
|
12
|
+
source candidate has that API, but is not a public release. OpenHCS now requires
|
|
13
|
+
ArrayBridge>=0.3.5,<0.4 so ordinary installation cannot select the old API.
|
|
14
|
+
|
|
15
|
+
Persistent evidence::
|
|
16
|
+
|
|
17
|
+
/home/ts/wt/openhcs-issue-batch-20260929/artifact-parent-20261001
|
|
18
|
+
|
|
19
|
+
Focused provider-free checks, using the existing Python3.12 environment::
|
|
20
|
+
|
|
21
|
+
PYTHONPATH=src PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -B -m pytest \
|
|
22
|
+
-o addopts='' -q tests/test_callable_dtype_default.py \
|
|
23
|
+
tests/test_durable_decorator_context.py
|
|
24
|
+
|
|
25
|
+
All seven tests pass in 0.12 seconds. They retain native floating-point values,
|
|
26
|
+
explicit runtime dtype overrides, unchanged default preservation, rejection of
|
|
27
|
+
untyped declarations and durable serialization without thread-local leakage.
|
|
28
|
+
The original complete-suite acceptance of the reviewed feature PRs is separate;
|
|
29
|
+
these seven checks do not claim a full framework/GPU matrix.
|
|
30
|
+
|
|
31
|
+
The wheel was built through ``uv build --offline --wheel`` from this source
|
|
32
|
+
using cached build requirements. Twine6.2 accepts the resulting metadata2.4
|
|
33
|
+
wheel. An initial no-isolation build lacked Hatchling in the installed OpenHCS
|
|
34
|
+
environment and failed; it did not install packages or count as a pass.
|
|
35
|
+
|
|
36
|
+
A fresh process loaded that exact 0.3.5 wheel archive ahead of the actual
|
|
37
|
+
installed OpenHCS candidate and executed the real BaSiC adapter on 24 synthetic
|
|
38
|
+
32x32 SITE observations. It returned finite float32 correction with fractional
|
|
39
|
+
pixels, preserved source pixels and matched the same-fit correction formula.
|
|
40
|
+
Flatfield RMSE against the known synthetic shading field is 0.0007167315491296268.
|
|
41
|
+
``arraybridge-035-installed-fit.log`` records the actual wheel and site-packages
|
|
42
|
+
import paths. This establishes local wheel/API interoperability, not registry
|
|
43
|
+
resolution, a full installed MCP rerun or biological accuracy. The separate
|
|
44
|
+
installed MCP journey is retained with OpenHCS PR217.
|
|
45
|
+
|
|
46
|
+
Publication is not performed by this PR. The parent requested separate owner
|
|
47
|
+
approval for the companion ArrayBridge0.3.5 publication. No tag was created,
|
|
48
|
+
workflow dispatched, registry upload performed or release availability claimed.
|
|
49
|
+
After approval, merge normally, tag the reviewed main version and use the
|
|
50
|
+
existing trusted publishing workflow. Verify the actual registry wheel before
|
|
51
|
+
claiming ordinary OpenHCS dependency installation.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
ArrayBridge typed thread-local release correction
|
|
2
|
+
================================================
|
|
3
|
+
|
|
4
|
+
Parent integration owner. Actual failed original 0.3.5 publisher:
|
|
5
|
+
https://github.com/OpenHCSDev/ArrayBridge/actions/runs/36946585391.
|
|
6
|
+
Ruff and Black passed; mypy rejected decorators.py:284 because current()
|
|
7
|
+
returned Any from the dynamic threading.local context slot. No wheel was
|
|
8
|
+
published. The failure and original v0.3.5 tag are preserved unchanged.
|
|
9
|
+
|
|
10
|
+
The existing ThreadGPUContext runtime owner now uses a threading.local
|
|
11
|
+
subclass declaring its sole context slot. The original per-thread lazy
|
|
12
|
+
initialization and stream identity remain; current() consumes the declared
|
|
13
|
+
optional value, not hasattr plus an untyped foreign attribute. No cast,
|
|
14
|
+
type-ignore, fallback reader, new context registry or serialization path.
|
|
15
|
+
BOUND-7: declare the native storage boundary instead of probing raw attributes.
|
|
16
|
+
Native threading.local retains thread isolation. Durable decorated callables
|
|
17
|
+
continue to refer to the original importable ThreadGPUContext owner.
|
|
18
|
+
|
|
19
|
+
This patch uses version0.3.6 rather than moving/reusing an existing release tag.
|
|
20
|
+
The already reviewed dtype_config_default behavior is unchanged. OpenHCS's
|
|
21
|
+
>=0.3.5,<0.4 requirement admits this corrected patch release normally.
|
|
22
|
+
|
|
23
|
+
Retained bounded local red mypy reproduces the exact hosted no-any-return
|
|
24
|
+
error: exit1,2.65s,96.91MiB. Green mypy checks all17 source files with no
|
|
25
|
+
errors: exit0,2.75s,97.14MiB. Ruff passes src/scripts; Black leaves20 files
|
|
26
|
+
unchanged. Existing interpreter/tooling only; no dependency installation.
|
|
27
|
+
|
|
28
|
+
Original supervisor logs/commands reside at:
|
|
29
|
+
/home/ts/wt/openhcs-issue-batch-20260929/s1-installed-20261001/
|
|
30
|
+
arraybridge036-mypy-red and arraybridge036-mypy-green. Kernel512MiB,
|
|
31
|
+
no swap, oneCPU/thread,60-second shard. Source behavior and release artifact
|
|
32
|
+
verification are recorded below when complete; no registry success claimed
|
|
33
|
+
by source checks alone.
|
|
34
|
+
|
|
35
|
+
Original focused behavior suite passes11 tests,0.30s pytest/0.82s total,
|
|
36
|
+
52.7MiB. It covers thread-local context identity/isolation, framework/device
|
|
37
|
+
stream identity, actual NumPy dtype controls, and dill restore of unpublished
|
|
38
|
+
decorated callables with a live unpicklable runtime handle. Assertions and the
|
|
39
|
+
existing three test files are unchanged; no real GPU runtime is needed here.
|
|
40
|
+
|
|
41
|
+
Original packaged R0 against merged e9aaa262 at source33d5a99f reports zero
|
|
42
|
+
nonzero deltas across both changed product files, exit0,1.93s/43.73MiB. The
|
|
43
|
+
original global NRA R1 remains a separate unfinished OpenHCS tool-owner check;
|
|
44
|
+
this bounded ratchet does not claim that global analysis completed.
|
|
45
|
+
|
|
46
|
+
Original cached offline uv build produces wheel and sdist, exit0,
|
|
47
|
+
1.26s/66.22MiB. Twine accepts both. Local wheel SHA256:
|
|
48
|
+
f7fa23f5dcf1925742592109b396fe17662fb56f15e1755a304a45c90e79a249;
|
|
49
|
+
local sdist SHA256:
|
|
50
|
+
a558dcb5827876725c854cb367a7598765a55a43e658d82678db4f2c541663d4.
|
|
51
|
+
These local bytes are not assumed equal to the independently hosted rebuild.
|
|
52
|
+
|
|
53
|
+
Addresses ArrayBridge issue5. Original source proof does not establish a full
|
|
54
|
+
GPU matrix. The unchanged original publisher owns the complete hosted check,
|
|
55
|
+
build and trusted upload. Merge this reviewed source checkpoint without waiting
|
|
56
|
+
on optional CI, push a new annotated v0.3.6 at actual merged main, and verify
|
|
57
|
+
PyPI installer visibility and the hosted wheel source/API before admitting the
|
|
58
|
+
consumer. Do not replace v0.3.5 or replay the failed original publisher.
|
|
@@ -82,11 +82,13 @@ class DtypeConversionConfig(RuntimeParameterDeclarationABC):
|
|
|
82
82
|
return DtypeConversionConfig
|
|
83
83
|
|
|
84
84
|
@classmethod
|
|
85
|
-
def parameter(
|
|
85
|
+
def parameter(
|
|
86
|
+
cls, *, default_value: "DtypeConversionConfig | None" = None
|
|
87
|
+
) -> inspect.Parameter:
|
|
86
88
|
return inspect.Parameter(
|
|
87
89
|
cls.require_parameter_name(),
|
|
88
90
|
inspect.Parameter.KEYWORD_ONLY,
|
|
89
|
-
default=cls.default_value(),
|
|
91
|
+
default=cls.default_value() if default_value is None else default_value,
|
|
90
92
|
annotation=cls.annotation_type(),
|
|
91
93
|
)
|
|
92
94
|
|
|
@@ -264,12 +266,30 @@ class KeywordOnlySignatureExtension:
|
|
|
264
266
|
return len(parameters)
|
|
265
267
|
|
|
266
268
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
+
class ThreadGPUContextStorage(threading.local):
|
|
270
|
+
"""Declare the existing native thread-local context slot and its type."""
|
|
271
|
+
|
|
272
|
+
context: "ThreadGPUContext | None" = None
|
|
269
273
|
|
|
270
274
|
|
|
271
275
|
class ThreadGPUContext:
|
|
272
|
-
"""
|
|
276
|
+
"""Runtime-owned thread-local streams keyed by framework/device identity.
|
|
277
|
+
|
|
278
|
+
Keep the runtime handle on its importable owner, not in decorator function
|
|
279
|
+
globals. A retained, unpublished decorated callable is serialized by value;
|
|
280
|
+
its durable closure must not pull a ``threading.local`` into history.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
_contexts: ClassVar[ThreadGPUContextStorage] = ThreadGPUContextStorage()
|
|
284
|
+
|
|
285
|
+
@classmethod
|
|
286
|
+
def current(cls) -> "ThreadGPUContext":
|
|
287
|
+
"""Return this thread's runtime context without serializing its handle."""
|
|
288
|
+
context = cls._contexts.context
|
|
289
|
+
if context is None:
|
|
290
|
+
context = cls()
|
|
291
|
+
cls._contexts.context = context
|
|
292
|
+
return context
|
|
273
293
|
|
|
274
294
|
def __init__(self):
|
|
275
295
|
self._streams: dict[tuple[MemoryType, int], Any] = {}
|
|
@@ -301,13 +321,6 @@ class ThreadGPUContext:
|
|
|
301
321
|
return device_id, self._streams[key]
|
|
302
322
|
|
|
303
323
|
|
|
304
|
-
def _get_thread_gpu_context():
|
|
305
|
-
"""Get or create thread-local GPU context."""
|
|
306
|
-
if not hasattr(_thread_gpu_contexts, "context"):
|
|
307
|
-
_thread_gpu_contexts.context = ThreadGPUContext()
|
|
308
|
-
return _thread_gpu_contexts.context
|
|
309
|
-
|
|
310
|
-
|
|
311
324
|
def memory_types(
|
|
312
325
|
input_type: str | MemoryType,
|
|
313
326
|
output_type: str | MemoryType,
|
|
@@ -352,6 +365,7 @@ def wrap_dtype_preserving_callable(
|
|
|
352
365
|
mem_type: MemoryType,
|
|
353
366
|
*,
|
|
354
367
|
slice_by_slice_default: bool = False,
|
|
368
|
+
dtype_config_default: DtypeConversionConfig | None = None,
|
|
355
369
|
):
|
|
356
370
|
"""
|
|
357
371
|
Return a callable with ArrayBridge dtype and slice controls.
|
|
@@ -363,18 +377,25 @@ def wrap_dtype_preserving_callable(
|
|
|
363
377
|
input_memory_type = MemoryType(MemoryContractAttribute.INPUT.read(func, mem_type.value))
|
|
364
378
|
output_memory_type = MemoryType(MemoryContractAttribute.OUTPUT.read(func, mem_type.value))
|
|
365
379
|
scale_func = output_memory_type.scale_dtype
|
|
380
|
+
default_dtype_config = (
|
|
381
|
+
DtypeConversionConfig.default_value()
|
|
382
|
+
if dtype_config_default is None
|
|
383
|
+
else dtype_config_default
|
|
384
|
+
)
|
|
385
|
+
if not isinstance(default_dtype_config, DtypeConversionConfig):
|
|
386
|
+
raise TypeError("Callable dtype default must be a DtypeConversionConfig.")
|
|
366
387
|
|
|
367
388
|
@functools.wraps(func)
|
|
368
389
|
def dtype_wrapper(image, *args, **kwargs):
|
|
369
390
|
# Pipeline runtimes may inject dtype_config; direct calls use the same
|
|
370
|
-
# preserve-input
|
|
391
|
+
# callable-owned default explicitly (preserve-input when undeclared).
|
|
371
392
|
slice_by_slice = kwargs.pop(
|
|
372
393
|
SliceBySliceRuntimeParameter.require_parameter_name(),
|
|
373
394
|
slice_by_slice_default,
|
|
374
395
|
)
|
|
375
396
|
dtype_config: DtypeConversionConfig = kwargs.pop(
|
|
376
397
|
DtypeConversionConfig.require_parameter_name(),
|
|
377
|
-
|
|
398
|
+
default_dtype_config,
|
|
378
399
|
)
|
|
379
400
|
dtype_conversion = dtype_config.default_dtype_conversion
|
|
380
401
|
|
|
@@ -424,7 +445,7 @@ def wrap_dtype_preserving_callable(
|
|
|
424
445
|
)
|
|
425
446
|
)
|
|
426
447
|
dtype_signature = KeywordOnlySignatureExtension(dtype_signature).with_parameter(
|
|
427
|
-
DtypeConversionConfig.parameter()
|
|
448
|
+
DtypeConversionConfig.parameter(default_value=default_dtype_config)
|
|
428
449
|
)
|
|
429
450
|
setattr(dtype_wrapper, "__signature__", dtype_signature)
|
|
430
451
|
|
|
@@ -461,7 +482,7 @@ def _create_gpu_wrapper(func, mem_type: MemoryType, oom_recovery: bool):
|
|
|
461
482
|
# Check if GPU is available for this framework
|
|
462
483
|
if framework is not None and mem_type.available_device_ids(framework):
|
|
463
484
|
# Get thread-local context
|
|
464
|
-
ctx =
|
|
485
|
+
ctx = ThreadGPUContext.current()
|
|
465
486
|
|
|
466
487
|
device_id, stream = ctx.stream_for(mem_type, framework)
|
|
467
488
|
|
|
@@ -510,6 +531,7 @@ def _create_memory_decorator(mem_type: MemoryType):
|
|
|
510
531
|
oom_recovery=True,
|
|
511
532
|
contract=None,
|
|
512
533
|
slice_by_slice_default=False,
|
|
534
|
+
dtype_config_default: DtypeConversionConfig | None = None,
|
|
513
535
|
):
|
|
514
536
|
"""
|
|
515
537
|
Decorator for {mem_type} memory type functions.
|
|
@@ -521,6 +543,8 @@ def _create_memory_decorator(mem_type: MemoryType):
|
|
|
521
543
|
oom_recovery: Enable automatic OOM recovery (default: True)
|
|
522
544
|
contract: Optional validation function for outputs
|
|
523
545
|
slice_by_slice_default: Default for the decorator-owned slice control
|
|
546
|
+
dtype_config_default: Callable-owned dtype policy for direct calls;
|
|
547
|
+
explicit runtime dtype_config still overrides this default.
|
|
524
548
|
|
|
525
549
|
Returns:
|
|
526
550
|
Decorated function with memory type metadata and dtype preservation
|
|
@@ -538,6 +562,7 @@ def _create_memory_decorator(mem_type: MemoryType):
|
|
|
538
562
|
func,
|
|
539
563
|
mem_type,
|
|
540
564
|
slice_by_slice_default=slice_by_slice_default,
|
|
565
|
+
dtype_config_default=dtype_config_default,
|
|
541
566
|
)
|
|
542
567
|
|
|
543
568
|
# Apply GPU wrapper if this is a GPU memory type
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Callable defaults use the existing typed dtype policy and real conversion."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import inspect
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from arraybridge.decorators import (
|
|
10
|
+
DtypeConversion,
|
|
11
|
+
DtypeConversionConfig,
|
|
12
|
+
PreserveInputDtypeConfig,
|
|
13
|
+
numpy as numpy_func,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class NativeConfig(DtypeConversionConfig):
|
|
19
|
+
default_dtype_conversion: DtypeConversion = DtypeConversion.NATIVE_OUTPUT
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_declared_native_output_preserves_fraction_negative_and_large_values():
|
|
23
|
+
native = NativeConfig()
|
|
24
|
+
|
|
25
|
+
@numpy_func(dtype_config_default=native)
|
|
26
|
+
def correct(image):
|
|
27
|
+
return np.array([-1.25, 12.5, 70000.5], dtype=np.float32)
|
|
28
|
+
|
|
29
|
+
output = correct(np.array([0, 1, 65535], dtype=np.uint16))
|
|
30
|
+
np.testing.assert_array_equal(output, [-1.25, 12.5, 70000.5])
|
|
31
|
+
assert output.dtype == np.float32
|
|
32
|
+
assert inspect.signature(correct).parameters["dtype_config"].default is native
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_explicit_runtime_dtype_policy_overrides_callable_default():
|
|
36
|
+
@numpy_func(dtype_config_default=NativeConfig())
|
|
37
|
+
def correct(image):
|
|
38
|
+
return image.astype(np.float32) + 0.25, np.array([2.5], dtype=np.float32)
|
|
39
|
+
|
|
40
|
+
output, sidecar = correct(
|
|
41
|
+
np.array([1, 2], dtype=np.uint16),
|
|
42
|
+
dtype_config=PreserveInputDtypeConfig(),
|
|
43
|
+
)
|
|
44
|
+
assert output.dtype == np.uint16
|
|
45
|
+
assert sidecar.dtype == np.float32
|
|
46
|
+
np.testing.assert_array_equal(sidecar, [2.5])
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_undeclared_defaults_still_preserve_input_dtype():
|
|
50
|
+
@numpy_func
|
|
51
|
+
def correct(image):
|
|
52
|
+
return image.astype(np.float32) + 0.25
|
|
53
|
+
|
|
54
|
+
assert correct(np.array([1, 2], dtype=np.uint16)).dtype == np.uint16
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_untyped_default_is_rejected_at_decoration():
|
|
58
|
+
with pytest.raises(TypeError, match="DtypeConversionConfig"):
|
|
59
|
+
numpy_func(dtype_config_default={"default_dtype_conversion": "native"})(
|
|
60
|
+
lambda image: image
|
|
61
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Retained callable declarations exclude thread-local GPU lifecycle handles."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
5
|
+
|
|
6
|
+
import dill
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from arraybridge.decorators import ThreadGPUContext, cupy, numpy
|
|
11
|
+
from arraybridge.types import MemoryType
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_context_is_stable_per_thread_and_never_shared_between_threads():
|
|
15
|
+
current = ThreadGPUContext.current()
|
|
16
|
+
with ThreadPoolExecutor(max_workers=1) as executor:
|
|
17
|
+
other, repeated = executor.submit(
|
|
18
|
+
lambda: (ThreadGPUContext.current(), ThreadGPUContext.current())
|
|
19
|
+
).result()
|
|
20
|
+
assert ThreadGPUContext.current() is current
|
|
21
|
+
assert other is repeated
|
|
22
|
+
assert other is not current
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.mark.parametrize("decorator", [numpy, cupy])
|
|
26
|
+
def test_unpublished_decorated_callable_serializes_without_runtime_context(decorator):
|
|
27
|
+
# Like a replaced custom function retained by an undo snapshot, this
|
|
28
|
+
# declaration has no importable public alias. Dill must persist its body.
|
|
29
|
+
@decorator
|
|
30
|
+
def declared(image, scale=3):
|
|
31
|
+
return image * scale
|
|
32
|
+
|
|
33
|
+
context = ThreadGPUContext.current()
|
|
34
|
+
key = (MemoryType.CUPY, -1)
|
|
35
|
+
handle = threading.local()
|
|
36
|
+
context._streams[key] = handle
|
|
37
|
+
try:
|
|
38
|
+
restored = dill.loads(dill.dumps(declared))
|
|
39
|
+
assert restored.__name__ == declared.__name__
|
|
40
|
+
assert restored.__wrapped__.__name__ == declared.__wrapped__.__name__
|
|
41
|
+
# CPU NumPy invocation checks actual restored behavior. GPU behavior
|
|
42
|
+
# keeps its existing focused tests; this receipt starts no GPU runtime.
|
|
43
|
+
if decorator is numpy:
|
|
44
|
+
np.testing.assert_array_equal(
|
|
45
|
+
restored(np.array([1, 2], dtype=np.uint16), scale=4), [4, 8]
|
|
46
|
+
)
|
|
47
|
+
assert ThreadGPUContext.current() is context
|
|
48
|
+
assert context._streams[key] is handle
|
|
49
|
+
finally:
|
|
50
|
+
del context._streams[key]
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|