dicube 0.1.4__cp311-cp311-macosx_11_0_arm64.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.
dicube/__init__.py ADDED
@@ -0,0 +1,140 @@
1
+ """DiCube: Python library for efficient storage and processing of 3D medical images.
2
+
3
+ DiCube provides functionality for working with DICOM image data while preserving
4
+ complete metadata. It offers efficient storage formats, image processing capabilities,
5
+ and interoperability with various medical image formats.
6
+
7
+ Main functionality:
8
+ - Load/save 3D medical images with complete DICOM metadata
9
+ - Efficient binary storage format with multiple compression options
10
+ - Spatial transformation and orientation handling
11
+ - Conversion between different medical image formats
12
+
13
+ Example:
14
+ >>> import dicube
15
+ >>> # Load from DICOM folder
16
+ >>> image = dicube.load_from_dicom_folder("path/to/dicom_folder")
17
+ >>> # Save to DCB file
18
+ >>> dicube.save(image, "output.dcb")
19
+ >>> # Access the data
20
+ >>> pixel_data = image.get_fdata()
21
+ """
22
+
23
+ from .core.image import DicomCubeImage
24
+ from .core.io import DicomCubeImageIO
25
+ from .dicom import (
26
+ CommonTags,
27
+ DicomMeta,
28
+ DicomStatus,
29
+ SortMethod,
30
+ get_dicom_status,
31
+ read_dicom_dir,
32
+ )
33
+
34
+ from importlib.metadata import version as _pkg_version, PackageNotFoundError
35
+
36
+ try:
37
+ __version__ = _pkg_version("dicube")
38
+ except PackageNotFoundError:
39
+ # editable install / source tree
40
+ __version__ = "0.1.0+unknown"
41
+
42
+ # Top-level convenience methods
43
+ def load(file_path: str, num_threads: int = 4, **kwargs) -> DicomCubeImage:
44
+ """Load a DicomCubeImage from a file.
45
+
46
+ Args:
47
+ file_path (str): Path to the input file.
48
+ num_threads (int): Number of parallel decoding threads. Defaults to 4.
49
+ **kwargs: Additional parameters passed to the underlying reader.
50
+
51
+ Returns:
52
+ DicomCubeImage: The loaded image object.
53
+ """
54
+ return DicomCubeImageIO.load(file_path, num_threads, **kwargs)
55
+
56
+
57
+ def save(
58
+ image: DicomCubeImage,
59
+ file_path: str,
60
+ file_type: str = "s",
61
+ num_threads: int = 4,
62
+ **kwargs
63
+ ) -> None:
64
+ """Save a DicomCubeImage to a file.
65
+
66
+ Args:
67
+ image (DicomCubeImage): The image object to save.
68
+ file_path (str): Output file path.
69
+ file_type (str): File type, "s" (speed priority), "a" (compression priority),
70
+ or "l" (lossy compression). Defaults to "s".
71
+ num_threads (int): Number of parallel encoding threads. Defaults to 4.
72
+ **kwargs: Additional parameters passed to the underlying writer.
73
+ """
74
+ return DicomCubeImageIO.save(image, file_path, file_type, num_threads, **kwargs)
75
+
76
+
77
+ def load_from_dicom_folder(
78
+ folder_path: str,
79
+ sort_method: SortMethod = SortMethod.INSTANCE_NUMBER_ASC,
80
+ **kwargs
81
+ ) -> DicomCubeImage:
82
+ """Load a DicomCubeImage from a DICOM folder.
83
+
84
+ Args:
85
+ folder_path (str): Path to the DICOM folder.
86
+ sort_method (SortMethod): Method to sort DICOM files.
87
+ Defaults to SortMethod.INSTANCE_NUMBER_ASC.
88
+ **kwargs: Additional parameters.
89
+
90
+ Returns:
91
+ DicomCubeImage: The loaded image object.
92
+ """
93
+ return DicomCubeImageIO.load_from_dicom_folder(folder_path, sort_method, **kwargs)
94
+
95
+
96
+ def load_from_nifti(file_path: str, **kwargs) -> DicomCubeImage:
97
+ """Load a DicomCubeImage from a NIfTI file.
98
+
99
+ Args:
100
+ file_path (str): Path to the NIfTI file.
101
+ **kwargs: Additional parameters.
102
+
103
+ Returns:
104
+ DicomCubeImage: The loaded image object.
105
+ """
106
+ return DicomCubeImageIO.load_from_nifti(file_path, **kwargs)
107
+
108
+
109
+ def save_to_dicom_folder(
110
+ image: DicomCubeImage,
111
+ folder_path: str,
112
+ **kwargs
113
+ ) -> None:
114
+ """Save a DicomCubeImage as a DICOM folder.
115
+
116
+ Args:
117
+ image (DicomCubeImage): The image object to save.
118
+ folder_path (str): Output directory path.
119
+ **kwargs: Additional parameters.
120
+ """
121
+ return DicomCubeImageIO.save_to_dicom_folder(image, folder_path)
122
+
123
+
124
+ __all__ = [
125
+ "DicomCubeImage",
126
+ "DicomMeta",
127
+ "read_dicom_dir",
128
+ "DicomStatus",
129
+ "get_dicom_status",
130
+ "CommonTags",
131
+ "SortMethod",
132
+ # Top-level convenience methods
133
+ "load",
134
+ "save",
135
+ "load_from_dicom_folder",
136
+ "load_from_nifti",
137
+ "save_to_dicom_folder",
138
+ # IO class (for direct use if needed)
139
+ "DicomCubeImageIO",
140
+ ]
@@ -0,0 +1,152 @@
1
+ from __future__ import annotations
2
+
3
+ """Image codec sub-package for dicube.
4
+
5
+ This package defines the codec registry and base interface for image codecs.
6
+ Currently supports JPH format with extensible design for future formats.
7
+ """
8
+
9
+ from typing import Dict, Protocol, runtime_checkable, Optional, Union, Any, Tuple
10
+ import numpy as np
11
+ from pathlib import Path
12
+
13
+ __all__ = [
14
+ "ImageCodec",
15
+ "get_codec",
16
+ "list_codecs",
17
+ "register_codec",
18
+ "is_codec_available",
19
+ ]
20
+
21
+
22
+ @runtime_checkable
23
+ class ImageCodec(Protocol):
24
+ """Base interface that all image codecs must implement.
25
+
26
+ Attributes:
27
+ id (int): Unique numeric ID for the codec.
28
+ name (str): Codec name (e.g., "jph").
29
+ extensions (Tuple[str, ...]): Supported file extensions (e.g., (".j2k",)).
30
+ """
31
+
32
+ id: int
33
+ name: str
34
+ extensions: Tuple[str, ...]
35
+
36
+ def encode(
37
+ self,
38
+ image: np.ndarray,
39
+ /,
40
+ **kwargs: Any
41
+ ) -> bytes:
42
+ """Encode numpy array to compressed bytes.
43
+
44
+ Args:
45
+ image (np.ndarray): Input image array.
46
+ **kwargs: Codec-specific parameters.
47
+
48
+ Returns:
49
+ bytes: Compressed image data.
50
+ """
51
+ ...
52
+
53
+ def decode(
54
+ self,
55
+ data: bytes,
56
+ /,
57
+ **kwargs: Any
58
+ ) -> np.ndarray:
59
+ """Decode compressed bytes to numpy array.
60
+
61
+ Args:
62
+ data (bytes): Compressed image data.
63
+ **kwargs: Codec-specific parameters.
64
+
65
+ Returns:
66
+ np.ndarray: Decoded image as numpy array.
67
+ """
68
+ ...
69
+
70
+ def is_available(self) -> bool:
71
+ """Check if codec is available and functional.
72
+
73
+ Returns:
74
+ bool: True if the codec is available and operational.
75
+ """
76
+ ...
77
+
78
+ def get_version(self) -> str:
79
+ """Get codec version information.
80
+
81
+ Returns:
82
+ str: Version information string.
83
+ """
84
+ ...
85
+
86
+
87
+ # ---------------------------------------------------------------------------
88
+ # Registry implementation ---------------------------------------------------
89
+ # ---------------------------------------------------------------------------
90
+
91
+ _codec_registry: Dict[str, ImageCodec] = {}
92
+
93
+
94
+ def register_codec(codec: ImageCodec) -> None:
95
+ """Register a codec in the global registry.
96
+
97
+ Args:
98
+ codec (ImageCodec): The codec instance to register.
99
+ """
100
+ _codec_registry[codec.name.lower()] = codec
101
+
102
+
103
+ def get_codec(name: str) -> ImageCodec:
104
+ """Get codec by name (case-insensitive).
105
+
106
+ Args:
107
+ name (str): Codec name (e.g., "jph").
108
+
109
+ Returns:
110
+ ImageCodec: Codec instance.
111
+
112
+ Raises:
113
+ ValueError: If codec not found.
114
+ """
115
+ try:
116
+ return _codec_registry[name.lower()]
117
+ except KeyError as err:
118
+ available = list(_codec_registry.keys())
119
+ raise ValueError(f"Unknown codec '{name}'. Available: {available}") from err
120
+
121
+
122
+ def list_codecs() -> list[str]:
123
+ """List all registered codec names.
124
+
125
+ Returns:
126
+ list[str]: List of registered codec names.
127
+ """
128
+ return list(_codec_registry.keys())
129
+
130
+
131
+ def is_codec_available(name: str) -> bool:
132
+ """Check if a codec is available.
133
+
134
+ Args:
135
+ name (str): Codec name (e.g., "jph").
136
+
137
+ Returns:
138
+ bool: True if codec is available and functional, False otherwise.
139
+ """
140
+ try:
141
+ codec = get_codec(name)
142
+ return codec.is_available()
143
+ except ValueError:
144
+ return False
145
+
146
+
147
+ # Import and register concrete implementations
148
+ try:
149
+ from .jph.codec import JphCodec
150
+ register_codec(JphCodec())
151
+ except ImportError:
152
+ pass # JPH codec not available
@@ -0,0 +1,15 @@
1
+ """JPEG 2000 (HTJ2K) codec module for DiCube.
2
+
3
+ This module provides JPEG 2000 High Throughput (HTJ2K) compression functionality
4
+ through the OpenJPH library. It implements the ImageCodec interface for seamless
5
+ integration with DiCube's storage system.
6
+
7
+ Classes:
8
+ JphCodec: Implementation of the JPEG 2000 codec using OpenJPH.
9
+ """
10
+
11
+ from .codec import JphCodec
12
+
13
+ __all__ = [
14
+ "JphCodec",
15
+ ]
@@ -0,0 +1,161 @@
1
+ """JPH codec adapter implementing ImageCodec interface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import numpy as np
6
+ from pathlib import Path
7
+ from typing import Union, Any, Tuple
8
+
9
+ from .ojph_complete import encode_image
10
+ from .ojph_decode_complete import decode_image
11
+
12
+
13
+ class JphCodec:
14
+ """JPEG 2000 codec (OpenJPH) implementing ImageCodec interface.
15
+
16
+ Attributes:
17
+ id (int): Unique numeric ID for the codec.
18
+ name (str): Codec name ("jph").
19
+ extensions (Tuple[str, ...]): Supported file extensions (.j2k, .j2c, .jp2).
20
+ """
21
+
22
+ id: int = 2
23
+ name: str = "jph"
24
+ extensions: Tuple[str, ...] = (".j2k", ".j2c", ".jp2")
25
+
26
+ def encode(
27
+ self,
28
+ image: np.ndarray,
29
+ /,
30
+ reversible: bool = True,
31
+ num_decompositions: int = 5,
32
+ block_size: tuple = (64, 64),
33
+ precinct_size: tuple = None,
34
+ progression_order: str = "RPCL",
35
+ color_transform: bool = False,
36
+ profile: str = None,
37
+ **kwargs: Any
38
+ ) -> bytes:
39
+ """Encode numpy array to JPEG 2000 bytes.
40
+
41
+ Args:
42
+ image (np.ndarray): Input image array.
43
+ reversible (bool): Whether to use reversible transform. Defaults to True.
44
+ num_decompositions (int): Number of wavelet decompositions. Defaults to 5.
45
+ block_size (tuple): Code block size as (width, height). Defaults to (64, 64).
46
+ precinct_size (tuple, optional): Precinct size for each level as (width, height).
47
+ Defaults to None.
48
+ progression_order (str): Progression order, one of LRCP, RLCP, RPCL, PCRL, CPRL.
49
+ Defaults to "RPCL".
50
+ color_transform (bool): Whether to use color transform. Defaults to False.
51
+ profile (str, optional): Profile to use, one of None, IMF, BROADCAST.
52
+ Defaults to None.
53
+ **kwargs: Additional parameters (ignored for compatibility).
54
+
55
+ Returns:
56
+ bytes: Compressed JPEG 2000 data.
57
+
58
+ Raises:
59
+ ValueError: If the image dimensions or block size are invalid.
60
+ """
61
+ # Parameter validation
62
+ if len(image.shape) not in (2, 3):
63
+ raise ValueError("Image must be 2D or 3D array")
64
+
65
+ # Validate code block size
66
+ if not all(
67
+ size > 0 and size <= 64 and (size & (size - 1)) == 0 for size in block_size
68
+ ):
69
+ raise ValueError(
70
+ "Code block dimensions must be powers of 2 and not larger than 64"
71
+ )
72
+
73
+ # Ensure data is contiguous
74
+ if not image.flags["C_CONTIGUOUS"]:
75
+ image = np.ascontiguousarray(image)
76
+
77
+ # Call C++ implementation
78
+ return encode_image(
79
+ image,
80
+ reversible=reversible,
81
+ num_decompositions=num_decompositions,
82
+ block_size=block_size,
83
+ precinct_size=precinct_size if precinct_size is not None else (0, 0),
84
+ progression_order=progression_order,
85
+ color_transform=color_transform,
86
+ profile="" if profile is None else profile,
87
+ )
88
+
89
+ def encode_lossless(
90
+ self,
91
+ image: np.ndarray,
92
+ /,
93
+ **kwargs: Any
94
+ ) -> bytes:
95
+ """Encode numpy array to lossless JPEG 2000 bytes.
96
+
97
+ This is a convenience method that calls encode() with reversible=True.
98
+
99
+ Args:
100
+ image (np.ndarray): Input image array.
101
+ **kwargs: Additional parameters passed to encode().
102
+
103
+ Returns:
104
+ bytes: Compressed JPEG 2000 data.
105
+ """
106
+ return self.encode(image, reversible=True, **kwargs)
107
+
108
+ def decode(
109
+ self,
110
+ data: bytes,
111
+ /,
112
+ level: int = 0,
113
+ resilient: bool = False,
114
+ **kwargs: Any
115
+ ) -> np.ndarray:
116
+ """Decode JPEG 2000 bytes to numpy array.
117
+
118
+ Args:
119
+ data (bytes): Compressed JPEG 2000 data.
120
+ level (int): Resolution level to decode at (0 = full resolution).
121
+ Defaults to 0.
122
+ resilient (bool): Whether to enable resilient decoding. Defaults to False.
123
+ **kwargs: Additional parameters (ignored for compatibility).
124
+
125
+ Returns:
126
+ np.ndarray: Decoded image as numpy array.
127
+ """
128
+ # Use C++ implementation
129
+ return decode_image(data, level=level, resilient=resilient)
130
+
131
+
132
+ def is_available(self) -> bool:
133
+ """Check if JPEG 2000 codec is available and functional.
134
+
135
+ Returns:
136
+ bool: True if the codec is available and operational.
137
+ """
138
+ try:
139
+ # Test with a small image
140
+ test_image = np.ones((10, 10), dtype=np.uint8)
141
+ encoded = self.encode(test_image)
142
+ decoded = self.decode(encoded)
143
+ return decoded.shape == test_image.shape
144
+ except Exception:
145
+ return False
146
+
147
+ def get_version(self) -> str:
148
+ """Get JPEG 2000 codec version.
149
+
150
+ Returns:
151
+ str: Version information string.
152
+ """
153
+ return "OpenJPH" # TODO: Get actual version from OpenJPH
154
+
155
+ def __repr__(self) -> str:
156
+ """Get string representation of the codec.
157
+
158
+ Returns:
159
+ str: String representation.
160
+ """
161
+ return f"<{self.__class__.__name__} id={self.id} name='{self.name}' version='{self.get_version()}'>"
@@ -0,0 +1,21 @@
1
+ """Core module for DiCube library.
2
+
3
+ This module provides the main interfaces and core functionality for working with
4
+ medical images in the DiCube format, including image representation, metadata handling,
5
+ and file I/O operations.
6
+
7
+ Classes:
8
+ DicomCubeImage: Main class for representing DICOM image data with metadata.
9
+ PixelDataHeader: Header class for storing pixel data information.
10
+ DicomCubeImageIO: Static I/O utility class for file operations.
11
+ """
12
+
13
+ from .image import DicomCubeImage
14
+ from .pixel_header import PixelDataHeader
15
+ from .io import DicomCubeImageIO
16
+
17
+ __all__ = [
18
+ "DicomCubeImage",
19
+ "PixelDataHeader",
20
+ "DicomCubeImageIO",
21
+ ]