arraybridge 0.3.3__py3-none-any.whl → 0.3.6__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.
arraybridge/__init__.py CHANGED
@@ -3,37 +3,49 @@ arraybridge: Unified API for NumPy, CuPy, PyTorch, TensorFlow, JAX, and pyclespe
3
3
 
4
4
  This package provides automatic memory type conversion, declarative decorators,
5
5
  and unified utilities for working with multiple array/tensor frameworks.
6
- """
7
6
 
8
- __version__ = "0.3.3"
7
+ Package-root imports stay lightweight: submodule exports load on first
8
+ attribute access instead of at import time. Declaration-only consumers
9
+ (memory-type tables, configuration modules) therefore do not pay the
10
+ NumPy/numcodecs import cost.
11
+ """
9
12
 
10
- from . import decorators as _decorators
11
- from .array_geometry import ArrayGeometry
12
- from .array_payload import ArrayPayload
13
- from .converters import convert_memory, detect_memory_type
14
- from .dtype_scaling import SCALING_FUNCTIONS
15
- from .exceptions import MemoryConversionError
16
- from .framework_config import _FRAMEWORK_CONFIG
17
- from .framework_ops import _FRAMEWORK_OPS
18
- from .gpu_cleanup import cleanup_all_gpu_frameworks
19
- from .oom_recovery import _execute_with_oom_recovery
20
- from .slice_processing import process_slices
21
- from .stack_utils import stack_slices, unstack_slices
22
- from .types import (
23
- CPU_MEMORY_TYPES,
24
- GPU_MEMORY_TYPES,
25
- SUPPORTED_MEMORY_TYPES,
26
- MemoryContractAttribute,
27
- MemoryType,
28
- )
29
- from .utils import _ensure_module, _get_device_id, _supports_dlpack
13
+ __version__ = "0.3.6"
30
14
 
31
- DtypeConversion = _decorators.DtypeConversion
32
- SliceBySliceRuntimeParameter = _decorators.SliceBySliceRuntimeParameter
33
- memory_types = _decorators.memory_types
34
- wrap_dtype_preserving_callable = _decorators.wrap_dtype_preserving_callable
35
- for _memory_type in MemoryType:
36
- globals()[_memory_type.value] = getattr(_decorators, _memory_type.value)
15
+ _LAZY_EXPORTS: dict[str, str] = {
16
+ "MemoryType": ".types",
17
+ "MemoryContractAttribute": ".types",
18
+ "ArrayPayload": ".array_payload",
19
+ "ArrayGeometry": ".array_geometry",
20
+ "CPU_MEMORY_TYPES": ".types",
21
+ "GPU_MEMORY_TYPES": ".types",
22
+ "SUPPORTED_MEMORY_TYPES": ".types",
23
+ "convert_memory": ".converters",
24
+ "detect_memory_type": ".converters",
25
+ "memory_types": ".decorators",
26
+ "DtypeConversion": ".decorators",
27
+ "SliceBySliceRuntimeParameter": ".decorators",
28
+ "wrap_dtype_preserving_callable": ".decorators",
29
+ "stack_slices": ".stack_utils",
30
+ "unstack_slices": ".stack_utils",
31
+ "process_slices": ".slice_processing",
32
+ "cleanup_all_gpu_frameworks": ".gpu_cleanup",
33
+ "MemoryConversionError": ".exceptions",
34
+ "SCALING_FUNCTIONS": ".dtype_scaling",
35
+ "_FRAMEWORK_CONFIG": ".framework_config",
36
+ "_FRAMEWORK_OPS": ".framework_ops",
37
+ "_execute_with_oom_recovery": ".oom_recovery",
38
+ "_ensure_module": ".utils",
39
+ "_supports_dlpack": ".utils",
40
+ "_get_device_id": ".utils",
41
+ # Decorator exports named after each memory type (numpy, cupy, torch, ...).
42
+ "numpy": ".decorators",
43
+ "cupy": ".decorators",
44
+ "torch": ".decorators",
45
+ "tensorflow": ".decorators",
46
+ "jax": ".decorators",
47
+ "pyclesperanto": ".decorators",
48
+ }
37
49
 
38
50
  __all__ = [
39
51
  # Types
@@ -72,4 +84,26 @@ __all__ = [
72
84
  "_ensure_module",
73
85
  "_supports_dlpack",
74
86
  "_get_device_id",
75
- ] + [memory_type.value for memory_type in MemoryType]
87
+ "numpy",
88
+ "cupy",
89
+ "torch",
90
+ "tensorflow",
91
+ "jax",
92
+ "pyclesperanto",
93
+ ]
94
+
95
+
96
+ def __getattr__(name: str):
97
+ module_name = _LAZY_EXPORTS.get(name)
98
+ if module_name is None:
99
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
100
+
101
+ import importlib
102
+
103
+ value = getattr(importlib.import_module(module_name, __name__), name)
104
+ globals()[name] = value
105
+ return value
106
+
107
+
108
+ def __dir__() -> list[str]:
109
+ return sorted(set(globals()) | set(_LAZY_EXPORTS))
arraybridge/decorators.py CHANGED
@@ -82,11 +82,13 @@ class DtypeConversionConfig(RuntimeParameterDeclarationABC):
82
82
  return DtypeConversionConfig
83
83
 
84
84
  @classmethod
85
- def parameter(cls) -> inspect.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
- # Thread-local storage for GPU streams and contexts
268
- _thread_gpu_contexts = threading.local()
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
- """Thread-local streams keyed by framework-local device identity."""
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 default explicitly.
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
- DtypeConversionConfig.default_value(),
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 = _get_thread_gpu_context()
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
arraybridge/types.py CHANGED
@@ -5,6 +5,8 @@ This module defines the MemoryType enum and related constants for managing
5
5
  different array/tensor frameworks.
6
6
  """
7
7
 
8
+ from __future__ import annotations
9
+
8
10
  import importlib
9
11
  import importlib.util
10
12
  import logging
@@ -15,17 +17,10 @@ from contextlib import AbstractContextManager, contextmanager, nullcontext
15
17
  from dataclasses import dataclass
16
18
  from enum import Enum
17
19
  from pathlib import Path
18
- from typing import Any, TypeVar, cast
19
-
20
- from arraybridge.array_operations import (
21
- CUPY_OPERATIONS,
22
- JAX_OPERATIONS,
23
- NUMPY_OPERATIONS,
24
- PYCLESPERANTO_OPERATIONS,
25
- TENSORFLOW_OPERATIONS,
26
- TORCH_OPERATIONS,
27
- ArrayOperations,
28
- )
20
+ from typing import TYPE_CHECKING, Any, TypeVar, cast
21
+
22
+ if TYPE_CHECKING:
23
+ from arraybridge.array_operations import ArrayOperations
29
24
 
30
25
  T = TypeVar("T")
31
26
  logger = logging.getLogger(__name__)
@@ -561,7 +556,7 @@ class _MemoryTypeFields:
561
556
  module_aliases: tuple[str, ...]
562
557
  import_environment: tuple[tuple[str, str], ...]
563
558
  _runtime: FrameworkRuntime
564
- _operations: ArrayOperations
559
+ _operations_name: str
565
560
 
566
561
 
567
562
  class MemoryType(_MemoryTypeFields, Enum):
@@ -571,7 +566,7 @@ class MemoryType(_MemoryTypeFields, Enum):
571
566
  cls,
572
567
  value: str,
573
568
  *declaration: Any,
574
- ) -> "MemoryType":
569
+ ) -> MemoryType:
575
570
  (
576
571
  import_name,
577
572
  display_name,
@@ -589,9 +584,18 @@ class MemoryType(_MemoryTypeFields, Enum):
589
584
  member.module_aliases = cast(tuple[str, ...], module_aliases)
590
585
  member.import_environment = cast(tuple[tuple[str, str], ...], import_environment)
591
586
  member._runtime = cast(FrameworkRuntime, runtime)
592
- member._operations = cast(ArrayOperations, operations)
587
+ member._operations_name = cast(str, operations)
593
588
  return member
594
589
 
590
+ @property
591
+ def _operations(self) -> ArrayOperations:
592
+ """Resolve this declaration's operations registry on first access."""
593
+
594
+ from arraybridge import array_operations
595
+
596
+ operations: ArrayOperations = getattr(array_operations, self._operations_name)
597
+ return operations
598
+
595
599
  NUMPY = (
596
600
  "numpy",
597
601
  "numpy",
@@ -600,7 +604,7 @@ class MemoryType(_MemoryTypeFields, Enum):
600
604
  (),
601
605
  (),
602
606
  FrameworkRuntime(oom_matcher=_numpy_oom),
603
- NUMPY_OPERATIONS,
607
+ "NUMPY_OPERATIONS",
604
608
  )
605
609
  CUPY = (
606
610
  "cupy",
@@ -624,7 +628,7 @@ class MemoryType(_MemoryTypeFields, Enum):
624
628
  oom_matcher=_cupy_oom,
625
629
  subprocess_environment=_nvidia_wheel_subprocess_environment,
626
630
  ),
627
- CUPY_OPERATIONS,
631
+ "CUPY_OPERATIONS",
628
632
  )
629
633
  TORCH = (
630
634
  "torch",
@@ -647,7 +651,7 @@ class MemoryType(_MemoryTypeFields, Enum):
647
651
  dlpack_exporter=_protocol_dlpack_export,
648
652
  oom_matcher=_torch_oom,
649
653
  ),
650
- TORCH_OPERATIONS,
654
+ "TORCH_OPERATIONS",
651
655
  )
652
656
  TENSORFLOW = (
653
657
  "tensorflow",
@@ -666,7 +670,7 @@ class MemoryType(_MemoryTypeFields, Enum):
666
670
  dlpack_validator=_tensorflow_dlpack,
667
671
  oom_matcher=_tensorflow_oom,
668
672
  ),
669
- TENSORFLOW_OPERATIONS,
673
+ "TENSORFLOW_OPERATIONS",
670
674
  )
671
675
  JAX = (
672
676
  "jax",
@@ -684,7 +688,7 @@ class MemoryType(_MemoryTypeFields, Enum):
684
688
  dlpack_exporter=_protocol_dlpack_export,
685
689
  oom_matcher=_jax_oom,
686
690
  ),
687
- JAX_OPERATIONS,
691
+ "JAX_OPERATIONS",
688
692
  )
689
693
  PYCLESPERANTO = (
690
694
  "pyclesperanto",
@@ -701,7 +705,7 @@ class MemoryType(_MemoryTypeFields, Enum):
701
705
  move_to_active_device=_pyclesperanto_device_move,
702
706
  oom_matcher=_pyclesperanto_oom,
703
707
  ),
704
- PYCLESPERANTO_OPERATIONS,
708
+ "PYCLESPERANTO_OPERATIONS",
705
709
  )
706
710
 
707
711
  @property
@@ -953,7 +957,7 @@ class MemoryType(_MemoryTypeFields, Enum):
953
957
  payload = data if isinstance(data, DLPackPayload) else DLPackPayload(data, data)
954
958
  return importer(payload, framework)
955
959
 
956
- def convert_to(self, data: Any, target: "MemoryType", device_id: int) -> Any:
960
+ def convert_to(self, data: Any, target: MemoryType, device_id: int) -> Any:
957
961
  """Convert one array through the source and target declarations."""
958
962
 
959
963
  if self is target:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arraybridge
3
- Version: 0.3.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'
@@ -1,10 +1,10 @@
1
- arraybridge/__init__.py,sha256=TmDOlpJsQxwY_k9aQ6YlGav2mZLLPzmh2tWbAOY1FyQ,2326
1
+ arraybridge/__init__.py,sha256=rhst2HuRsZE1bp4f23SjK6cTHNpjQUAsSnqgh9R6QUA,3184
2
2
  arraybridge/array_geometry.py,sha256=CopfGmFVKwV5l-lOTImhm1YVh18weUng0ReEF58qlPI,1689
3
3
  arraybridge/array_operations.py,sha256=fp69o9BYcCzfhmLcWrH68SVGSfPL3nb6Cty-Amd_oQM,13082
4
4
  arraybridge/array_payload.py,sha256=6_jJwPRya0MHrUNSA9kXPlTISpLa0G74rqDValGXKx4,1079
5
5
  arraybridge/converters.py,sha256=q44E6Pg1rJz8yq6WQIPLOZPv1lqqcUcEG4Gh4D5M1_o,2649
6
6
  arraybridge/converters_registry.py,sha256=_hHtbd-3SMIKUw8fvNu3twPWJY7qHyNyQ_NhVp3eSHQ,5647
7
- arraybridge/decorators.py,sha256=mYIxBbCuWQVN8dhAE2M7i2Yo0KbcMyY4MZmsfGXGm-0,19975
7
+ arraybridge/decorators.py,sha256=KPm_R3C9TMHvNARf3vKAZQY-3gKSlvYrimxBQMyI5A4,21271
8
8
  arraybridge/dtype_scaling.py,sha256=KDgYyCvxgsQXLJJl_bs5nH46zQimsA7c1Olv6xJ2z1o,831
9
9
  arraybridge/exceptions.py,sha256=fhej5ZS39QcBFWzNeNYjoqeLVnOgwwp-cWIme25jyuo,752
10
10
  arraybridge/framework_config.py,sha256=5yTTt9NWcwzKDMXbc-fqCdiFl-piKOt3SUnxmRXXCQk,744
@@ -13,9 +13,9 @@ arraybridge/gpu_cleanup.py,sha256=ADI4wMLk9GIO9rJ6BJuNk3uZIyANoAqJ7VFTnLJzrbE,25
13
13
  arraybridge/oom_recovery.py,sha256=hGvOuJHVSopiIqblzoMFlqW8_oK6ZlAGF_l4pfEljeI,2403
14
14
  arraybridge/slice_processing.py,sha256=5R10OmHW8BSbJFwT1URd2Sz3P15o5EhaI2VnVUUIvkQ,4450
15
15
  arraybridge/stack_utils.py,sha256=FVikN0XG-Xryw-1p9MaSRUTZet7kLcy0gTEERSjc9r4,6915
16
- arraybridge/types.py,sha256=F_aJLJnZ46KBmCziHjtfkkvpqzHkc-kDjbsuRc98uak,35588
16
+ arraybridge/types.py,sha256=e5R2DVflQAjXrfTk033KRdiiwoip3IQAvgDCYUjPdiQ,35807
17
17
  arraybridge/utils.py,sha256=RqThtYScPEOEQG491dA8b_VvUTjgcFhwLbh0erQu5Ns,4833
18
- arraybridge-0.3.3.dist-info/METADATA,sha256=eloa3YEUoJnRo0S3n6JgClhYWy8J8sdAtINIwsroOf8,5275
19
- arraybridge-0.3.3.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
20
- arraybridge-0.3.3.dist-info/licenses/LICENSE,sha256=xagEoeTAj1WT64RmyR3E6HH-eTGdgXN6gqPMUUt7L_Y,1070
21
- arraybridge-0.3.3.dist-info/RECORD,,
18
+ arraybridge-0.3.6.dist-info/METADATA,sha256=uOSPAN8b0foxSeEJslCFyv0VhJR7suC1TWL9RBCrLTo,5318
19
+ arraybridge-0.3.6.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
20
+ arraybridge-0.3.6.dist-info/licenses/LICENSE,sha256=xagEoeTAj1WT64RmyR3E6HH-eTGdgXN6gqPMUUt7L_Y,1070
21
+ arraybridge-0.3.6.dist-info/RECORD,,