dicube 0.2.2__cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.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 +174 -0
- dicube/codecs/__init__.py +152 -0
- dicube/codecs/jph/__init__.py +15 -0
- dicube/codecs/jph/codec.py +161 -0
- dicube/codecs/jph/ojph_complete.cpython-38-aarch64-linux-gnu.so +0 -0
- dicube/codecs/jph/ojph_decode_complete.cpython-38-aarch64-linux-gnu.so +0 -0
- dicube/core/__init__.py +21 -0
- dicube/core/image.py +349 -0
- dicube/core/io.py +408 -0
- dicube/core/pixel_header.py +120 -0
- dicube/dicom/__init__.py +13 -0
- dicube/dicom/dcb_streaming.py +248 -0
- dicube/dicom/dicom_io.py +153 -0
- dicube/dicom/dicom_meta.py +740 -0
- dicube/dicom/dicom_status.py +259 -0
- dicube/dicom/dicom_tags.py +121 -0
- dicube/dicom/merge_utils.py +283 -0
- dicube/dicom/space_from_meta.py +70 -0
- dicube/exceptions.py +189 -0
- dicube/storage/__init__.py +17 -0
- dicube/storage/dcb_file.py +824 -0
- dicube/storage/pixel_utils.py +259 -0
- dicube/utils/__init__.py +6 -0
- dicube/validation.py +380 -0
- dicube-0.2.2.dist-info/METADATA +272 -0
- dicube-0.2.2.dist-info/RECORD +27 -0
- dicube-0.2.2.dist-info/WHEEL +6 -0
dicube/core/io.py
ADDED
@@ -0,0 +1,408 @@
|
|
1
|
+
import struct
|
2
|
+
import warnings
|
3
|
+
from typing import Optional, Union
|
4
|
+
|
5
|
+
import numpy as np
|
6
|
+
from spacetransformer import Space, get_space_from_nifti
|
7
|
+
|
8
|
+
from ..dicom import (
|
9
|
+
CommonTags,
|
10
|
+
DicomMeta,
|
11
|
+
DicomStatus,
|
12
|
+
SortMethod,
|
13
|
+
get_dicom_status,
|
14
|
+
read_dicom_dir,
|
15
|
+
get_space_from_DicomMeta,
|
16
|
+
)
|
17
|
+
from ..dicom.dicom_io import save_to_dicom_folder
|
18
|
+
from ..storage.dcb_file import DcbSFile, DcbFile, DcbAFile, DcbLFile
|
19
|
+
from ..storage.pixel_utils import derive_pixel_header_from_array, determine_optimal_nifti_dtype
|
20
|
+
from .pixel_header import PixelDataHeader
|
21
|
+
|
22
|
+
from ..validation import (
|
23
|
+
validate_not_none,
|
24
|
+
validate_file_exists,
|
25
|
+
validate_folder_exists,
|
26
|
+
validate_parameter_type,
|
27
|
+
validate_string_not_empty,
|
28
|
+
validate_numeric_range
|
29
|
+
)
|
30
|
+
from ..exceptions import (
|
31
|
+
InvalidCubeFileError,
|
32
|
+
CodecError,
|
33
|
+
MetaDataError,
|
34
|
+
DataConsistencyError
|
35
|
+
)
|
36
|
+
import os
|
37
|
+
|
38
|
+
|
39
|
+
class DicomCubeImageIO:
|
40
|
+
"""Static I/O utility class responsible for DicomCubeImage file operations.
|
41
|
+
|
42
|
+
Responsibilities:
|
43
|
+
- Provides unified file I/O interface
|
44
|
+
- Automatically detects file formats
|
45
|
+
- Handles conversion between various file formats
|
46
|
+
"""
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
@staticmethod
|
51
|
+
def save(
|
52
|
+
image: "DicomCubeImage",
|
53
|
+
file_path: str,
|
54
|
+
file_type: str = "s",
|
55
|
+
) -> None:
|
56
|
+
"""Save DicomCubeImage to a file.
|
57
|
+
|
58
|
+
Args:
|
59
|
+
image (DicomCubeImage): The DicomCubeImage object to save.
|
60
|
+
file_path (str): Output file path.
|
61
|
+
file_type (str): File type, "s" (speed priority), "a" (compression priority),
|
62
|
+
or "l" (lossy compression). Defaults to "s".
|
63
|
+
|
64
|
+
Raises:
|
65
|
+
InvalidCubeFileError: If the file_type is not supported.
|
66
|
+
"""
|
67
|
+
# Validate required parameters
|
68
|
+
validate_not_none(image, "image", "save operation", DataConsistencyError)
|
69
|
+
validate_string_not_empty(file_path, "file_path", "save operation", InvalidCubeFileError)
|
70
|
+
|
71
|
+
# Validate file_type parameter
|
72
|
+
if file_type not in ("s", "a", "l"):
|
73
|
+
raise InvalidCubeFileError(
|
74
|
+
f"Unsupported file type: {file_type}",
|
75
|
+
context="save operation",
|
76
|
+
details={"file_type": file_type, "supported_types": ["s", "a", "l"]},
|
77
|
+
suggestion="Use 's' for speed priority, 'a' for compression priority, or 'l' for lossy compression"
|
78
|
+
)
|
79
|
+
|
80
|
+
try:
|
81
|
+
# Choose appropriate writer based on file type
|
82
|
+
# The writer will automatically ensure correct file extension
|
83
|
+
if file_type == "s":
|
84
|
+
writer = DcbSFile(file_path, mode="w")
|
85
|
+
elif file_type == "a":
|
86
|
+
writer = DcbAFile(file_path, mode="w")
|
87
|
+
elif file_type == "l":
|
88
|
+
writer = DcbLFile(file_path, mode="w")
|
89
|
+
|
90
|
+
# Update file_path to the corrected path from writer
|
91
|
+
file_path = writer.filename
|
92
|
+
|
93
|
+
# Write to file
|
94
|
+
writer.write(
|
95
|
+
images=image.raw_image,
|
96
|
+
pixel_header=image.pixel_header,
|
97
|
+
dicom_meta=image.dicom_meta,
|
98
|
+
space=image.space,
|
99
|
+
dicom_status=image.dicom_status,
|
100
|
+
)
|
101
|
+
except Exception as e:
|
102
|
+
if isinstance(e, (InvalidCubeFileError, CodecError)):
|
103
|
+
raise
|
104
|
+
raise InvalidCubeFileError(
|
105
|
+
f"Failed to save file: {str(e)}",
|
106
|
+
context="save operation",
|
107
|
+
details={"file_path": file_path, "file_type": file_type}
|
108
|
+
) from e
|
109
|
+
|
110
|
+
@staticmethod
|
111
|
+
def load(file_path: str) -> 'DicomCubeImage':
|
112
|
+
"""Load DicomCubeImage from a file.
|
113
|
+
|
114
|
+
Args:
|
115
|
+
file_path (str): Input file path.
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
DicomCubeImage: The loaded object from the file.
|
119
|
+
|
120
|
+
Raises:
|
121
|
+
ValueError: When the file format is not supported.
|
122
|
+
"""
|
123
|
+
# Validate required parameters
|
124
|
+
validate_not_none(file_path, "file_path", "load operation", InvalidCubeFileError)
|
125
|
+
validate_file_exists(file_path, "load operation", InvalidCubeFileError)
|
126
|
+
|
127
|
+
try:
|
128
|
+
# Read file header to determine format
|
129
|
+
header_size = struct.calcsize(DcbFile.HEADER_STRUCT)
|
130
|
+
with open(file_path, "rb") as f:
|
131
|
+
header_data = f.read(header_size)
|
132
|
+
magic = struct.unpack(DcbFile.HEADER_STRUCT, header_data)[0]
|
133
|
+
|
134
|
+
# Choose appropriate reader based on magic number
|
135
|
+
if magic == DcbAFile.MAGIC:
|
136
|
+
reader = DcbAFile(file_path, mode="r")
|
137
|
+
elif magic == DcbSFile.MAGIC:
|
138
|
+
reader = DcbSFile(file_path, mode="r")
|
139
|
+
else:
|
140
|
+
raise InvalidCubeFileError(
|
141
|
+
f"Unsupported file format",
|
142
|
+
context="load operation",
|
143
|
+
details={"file_path": file_path, "magic_number": magic},
|
144
|
+
suggestion="Ensure the file is a valid DicomCube file"
|
145
|
+
)
|
146
|
+
|
147
|
+
# Read file contents
|
148
|
+
dicom_meta = reader.read_meta()
|
149
|
+
space = reader.read_space()
|
150
|
+
pixel_header = reader.read_pixel_header()
|
151
|
+
dicom_status = reader.read_dicom_status()
|
152
|
+
|
153
|
+
images = reader.read_images()
|
154
|
+
if isinstance(images, list):
|
155
|
+
# Convert list to ndarray if needed
|
156
|
+
images = np.stack(images)
|
157
|
+
|
158
|
+
# Use lazy import to avoid circular dependency
|
159
|
+
from .image import DicomCubeImage
|
160
|
+
|
161
|
+
return DicomCubeImage(
|
162
|
+
raw_image=images,
|
163
|
+
pixel_header=pixel_header,
|
164
|
+
dicom_meta=dicom_meta,
|
165
|
+
space=space,
|
166
|
+
dicom_status=dicom_status,
|
167
|
+
)
|
168
|
+
except Exception as e:
|
169
|
+
if isinstance(e, (InvalidCubeFileError, CodecError)):
|
170
|
+
raise
|
171
|
+
raise InvalidCubeFileError(
|
172
|
+
f"Failed to load file: {str(e)}",
|
173
|
+
context="load operation",
|
174
|
+
details={"file_path": file_path}
|
175
|
+
) from e
|
176
|
+
|
177
|
+
@staticmethod
|
178
|
+
def load_from_dicom_folder(
|
179
|
+
folder_path: str,
|
180
|
+
sort_method: SortMethod = SortMethod.INSTANCE_NUMBER_ASC,
|
181
|
+
) -> 'DicomCubeImage':
|
182
|
+
"""Load DicomCubeImage from a DICOM folder.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
folder_path (str): Path to the DICOM folder.
|
186
|
+
sort_method (SortMethod): Method to sort DICOM files.
|
187
|
+
Defaults to SortMethod.INSTANCE_NUMBER_ASC.
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
DicomCubeImage: The object created from the DICOM folder.
|
191
|
+
|
192
|
+
Raises:
|
193
|
+
ValueError: When the DICOM status is not supported.
|
194
|
+
"""
|
195
|
+
# Validate required parameters
|
196
|
+
validate_not_none(folder_path, "folder_path", "load_from_dicom_folder operation", InvalidCubeFileError)
|
197
|
+
validate_folder_exists(folder_path, "load_from_dicom_folder operation", InvalidCubeFileError)
|
198
|
+
|
199
|
+
try:
|
200
|
+
# Read DICOM folder
|
201
|
+
meta, datasets = read_dicom_dir(folder_path, sort_method=sort_method)
|
202
|
+
images = [d.pixel_array for d in datasets]
|
203
|
+
status = get_dicom_status(meta)
|
204
|
+
|
205
|
+
if status in (
|
206
|
+
DicomStatus.NON_UNIFORM_RESCALE_FACTOR,
|
207
|
+
DicomStatus.MISSING_DTYPE,
|
208
|
+
DicomStatus.NON_UNIFORM_DTYPE,
|
209
|
+
DicomStatus.MISSING_SHAPE,
|
210
|
+
DicomStatus.INCONSISTENT,
|
211
|
+
):
|
212
|
+
raise MetaDataError(
|
213
|
+
f"Unsupported DICOM status: {status}",
|
214
|
+
context="load_from_dicom_folder operation",
|
215
|
+
details={"dicom_status": status, "folder_path": folder_path},
|
216
|
+
suggestion="Ensure DICOM files have consistent metadata and proper format"
|
217
|
+
)
|
218
|
+
|
219
|
+
if status in (
|
220
|
+
DicomStatus.MISSING_SPACING,
|
221
|
+
DicomStatus.NON_UNIFORM_SPACING,
|
222
|
+
DicomStatus.MISSING_ORIENTATION,
|
223
|
+
DicomStatus.NON_UNIFORM_ORIENTATION,
|
224
|
+
DicomStatus.MISSING_LOCATION,
|
225
|
+
DicomStatus.REVERSED_LOCATION,
|
226
|
+
DicomStatus.DWELLING_LOCATION,
|
227
|
+
DicomStatus.GAP_LOCATION,
|
228
|
+
):
|
229
|
+
warnings.warn(f"DICOM status: {status}, cannot calculate space information")
|
230
|
+
space = None
|
231
|
+
else:
|
232
|
+
if get_space_from_DicomMeta is not None:
|
233
|
+
space = get_space_from_DicomMeta(meta, axis_order="zyx")
|
234
|
+
else:
|
235
|
+
space = None
|
236
|
+
|
237
|
+
# Get rescale parameters
|
238
|
+
slope = meta.get_shared_value(CommonTags.RescaleSlope)
|
239
|
+
intercept = meta.get_shared_value(CommonTags.RescaleIntercept)
|
240
|
+
wind_center = meta.get_shared_value(CommonTags.WindowCenter)
|
241
|
+
wind_width = meta.get_shared_value(CommonTags.WindowWidth)
|
242
|
+
try:
|
243
|
+
wind_center = float(wind_center)
|
244
|
+
wind_width = float(wind_width)
|
245
|
+
except:
|
246
|
+
wind_center = None
|
247
|
+
wind_width = None
|
248
|
+
|
249
|
+
# Create pixel_header
|
250
|
+
pixel_header = PixelDataHeader(
|
251
|
+
RescaleSlope=float(slope) if slope is not None else 1.0,
|
252
|
+
RescaleIntercept=float(intercept) if intercept is not None else 0.0,
|
253
|
+
OriginalPixelDtype=str(images[0].dtype),
|
254
|
+
PixelDtype=str(images[0].dtype),
|
255
|
+
WindowCenter=wind_center,
|
256
|
+
WindowWidth=wind_width,
|
257
|
+
)
|
258
|
+
|
259
|
+
# Validate PixelDataHeader initialization success
|
260
|
+
if pixel_header is None:
|
261
|
+
raise MetaDataError(
|
262
|
+
"PixelDataHeader initialization failed",
|
263
|
+
context="load_from_dicom_folder operation",
|
264
|
+
suggestion="Check DICOM metadata for required pixel data information"
|
265
|
+
)
|
266
|
+
|
267
|
+
# Use lazy import to avoid circular dependency
|
268
|
+
from .image import DicomCubeImage
|
269
|
+
|
270
|
+
return DicomCubeImage(
|
271
|
+
raw_image=np.array(images),
|
272
|
+
pixel_header=pixel_header,
|
273
|
+
dicom_meta=meta,
|
274
|
+
space=space,
|
275
|
+
dicom_status=status
|
276
|
+
)
|
277
|
+
except Exception as e:
|
278
|
+
if isinstance(e, (InvalidCubeFileError, MetaDataError)):
|
279
|
+
raise
|
280
|
+
raise MetaDataError(
|
281
|
+
f"Failed to load DICOM folder: {str(e)}",
|
282
|
+
context="load_from_dicom_folder operation",
|
283
|
+
details={"folder_path": folder_path}
|
284
|
+
) from e
|
285
|
+
|
286
|
+
@staticmethod
|
287
|
+
def load_from_nifti(file_path: str) -> 'DicomCubeImage':
|
288
|
+
"""Load DicomCubeImage from a NIfTI file.
|
289
|
+
|
290
|
+
Args:
|
291
|
+
file_path (str): Path to the NIfTI file.
|
292
|
+
|
293
|
+
Returns:
|
294
|
+
DicomCubeImage: The object created from the NIfTI file.
|
295
|
+
|
296
|
+
Raises:
|
297
|
+
ImportError: When nibabel is not installed.
|
298
|
+
"""
|
299
|
+
# Validate required parameters
|
300
|
+
validate_not_none(file_path, "file_path", "load_from_nifti operation", InvalidCubeFileError)
|
301
|
+
validate_file_exists(file_path, "load_from_nifti operation", InvalidCubeFileError)
|
302
|
+
|
303
|
+
try:
|
304
|
+
import nibabel as nib
|
305
|
+
except ImportError:
|
306
|
+
raise ImportError("nibabel is required to read NIfTI files")
|
307
|
+
|
308
|
+
try:
|
309
|
+
nii = nib.load(file_path)
|
310
|
+
space = get_space_from_nifti(nii)
|
311
|
+
|
312
|
+
# Fix numpy array warning
|
313
|
+
raw_image, header = derive_pixel_header_from_array(
|
314
|
+
np.asarray(nii.dataobj, dtype=nii.dataobj.dtype)
|
315
|
+
)
|
316
|
+
|
317
|
+
# Use lazy import to avoid circular dependency
|
318
|
+
from .image import DicomCubeImage
|
319
|
+
|
320
|
+
return DicomCubeImage(raw_image, header, space=space)
|
321
|
+
except Exception as e:
|
322
|
+
if isinstance(e, ImportError):
|
323
|
+
raise
|
324
|
+
raise InvalidCubeFileError(
|
325
|
+
f"Failed to load NIfTI file: {str(e)}",
|
326
|
+
context="load_from_nifti operation",
|
327
|
+
details={"file_path": file_path},
|
328
|
+
suggestion="Ensure the file is a valid NIfTI format and nibabel is installed"
|
329
|
+
) from e
|
330
|
+
|
331
|
+
@staticmethod
|
332
|
+
def save_to_dicom_folder(
|
333
|
+
image: 'DicomCubeImage',
|
334
|
+
folder_path: str,
|
335
|
+
) -> None:
|
336
|
+
"""Save DicomCubeImage as a DICOM folder.
|
337
|
+
|
338
|
+
Args:
|
339
|
+
image (DicomCubeImage): The DicomCubeImage object to save.
|
340
|
+
folder_path (str): Output directory path.
|
341
|
+
"""
|
342
|
+
# Validate required parameters
|
343
|
+
validate_not_none(image, "image", "save_to_dicom_folder operation", DataConsistencyError)
|
344
|
+
validate_string_not_empty(folder_path, "folder_path", "save_to_dicom_folder operation", InvalidCubeFileError)
|
345
|
+
|
346
|
+
if image.dicom_meta is None:
|
347
|
+
warnings.warn("dicom_meta is None, initializing with default values")
|
348
|
+
image.init_meta()
|
349
|
+
|
350
|
+
save_to_dicom_folder(
|
351
|
+
raw_images=image.raw_image,
|
352
|
+
dicom_meta=image.dicom_meta,
|
353
|
+
pixel_header=image.pixel_header,
|
354
|
+
output_dir=folder_path,
|
355
|
+
)
|
356
|
+
|
357
|
+
@staticmethod
|
358
|
+
def save_to_nifti(
|
359
|
+
image: 'DicomCubeImage',
|
360
|
+
file_path: str,
|
361
|
+
) -> None:
|
362
|
+
"""Save DicomCubeImage as a NIfTI file.
|
363
|
+
|
364
|
+
Args:
|
365
|
+
image (DicomCubeImage): The DicomCubeImage object to save.
|
366
|
+
file_path (str): Output file path.
|
367
|
+
|
368
|
+
Raises:
|
369
|
+
ImportError: When nibabel is not installed.
|
370
|
+
InvalidCubeFileError: When saving fails.
|
371
|
+
"""
|
372
|
+
# Validate required parameters
|
373
|
+
validate_not_none(image, "image", "save_to_nifti operation", DataConsistencyError)
|
374
|
+
validate_string_not_empty(file_path, "file_path", "save_to_nifti operation", InvalidCubeFileError)
|
375
|
+
|
376
|
+
try:
|
377
|
+
import nibabel as nib
|
378
|
+
except ImportError:
|
379
|
+
raise ImportError("nibabel is required to write NIfTI files")
|
380
|
+
|
381
|
+
try:
|
382
|
+
if image.space is None:
|
383
|
+
raise InvalidCubeFileError(
|
384
|
+
"Cannot save to NIfTI without space information",
|
385
|
+
context="save_to_nifti operation",
|
386
|
+
suggestion="Ensure the DicomCubeImage has valid space information"
|
387
|
+
)
|
388
|
+
|
389
|
+
# Get affine matrix from space
|
390
|
+
affine = image.space.to_nifti_affine()
|
391
|
+
|
392
|
+
# 根据像素数据和metadata确定最佳的数据类型
|
393
|
+
optimal_data, dtype_name = determine_optimal_nifti_dtype(image.raw_image, image.pixel_header)
|
394
|
+
|
395
|
+
# Create NIfTI image with optimized data type
|
396
|
+
nii = nib.Nifti1Image(optimal_data, affine)
|
397
|
+
|
398
|
+
# Save to file
|
399
|
+
nib.save(nii, file_path)
|
400
|
+
except Exception as e:
|
401
|
+
if isinstance(e, (ImportError, InvalidCubeFileError)):
|
402
|
+
raise
|
403
|
+
raise InvalidCubeFileError(
|
404
|
+
f"Failed to save NIfTI file: {str(e)}",
|
405
|
+
context="save_to_nifti operation",
|
406
|
+
details={"file_path": file_path},
|
407
|
+
suggestion="Check file permissions and ensure space information is valid"
|
408
|
+
) from e
|
@@ -0,0 +1,120 @@
|
|
1
|
+
import json
|
2
|
+
from dataclasses import asdict, dataclass, field
|
3
|
+
from typing import Dict, Optional
|
4
|
+
|
5
|
+
|
6
|
+
@dataclass
|
7
|
+
class PixelDataHeader:
|
8
|
+
"""Header class for storing pixel data information in medical images.
|
9
|
+
|
10
|
+
Stores metadata including:
|
11
|
+
- Rescale factors (slope/intercept)
|
12
|
+
- Original pixel data type
|
13
|
+
- Window settings (center/width)
|
14
|
+
- Value range (min/max)
|
15
|
+
- Additional metadata in extras
|
16
|
+
|
17
|
+
Attributes:
|
18
|
+
RescaleSlope (float): Slope for linear transformation.
|
19
|
+
RescaleIntercept (float): Intercept for linear transformation.
|
20
|
+
PixelDtype (str): Pixel data type string (after convert to dcb file).
|
21
|
+
OriginalPixelDtype (str): Original pixel data type string (before convert to dcb file).
|
22
|
+
WindowCenter (float, optional): Window center value for display.
|
23
|
+
WindowWidth (float, optional): Window width value for display.
|
24
|
+
MaxVal (float, optional): Maximum pixel value.
|
25
|
+
MinVal (float, optional): Minimum pixel value.
|
26
|
+
Extras (Dict[str, any]): Dictionary for additional metadata.
|
27
|
+
"""
|
28
|
+
|
29
|
+
RescaleSlope: float = 1.0
|
30
|
+
RescaleIntercept: float = 0.0
|
31
|
+
OriginalPixelDtype: str = "uint16"
|
32
|
+
PixelDtype: str = "uint16"
|
33
|
+
WindowCenter: Optional[float] = None
|
34
|
+
WindowWidth: Optional[float] = None
|
35
|
+
MaxVal: Optional[float] = None
|
36
|
+
MinVal: Optional[float] = None
|
37
|
+
Extras: Dict[str, any] = field(default_factory=dict)
|
38
|
+
|
39
|
+
def to_dict(self) -> dict:
|
40
|
+
"""Convert the header to a dictionary for serialization.
|
41
|
+
|
42
|
+
Merges extras field into the main dictionary and removes
|
43
|
+
the redundant extras key.
|
44
|
+
|
45
|
+
Returns:
|
46
|
+
dict: Dictionary representation of the header.
|
47
|
+
"""
|
48
|
+
data = asdict(self)
|
49
|
+
data.update(self.Extras) # Merge Extras into dictionary
|
50
|
+
data.pop("Extras", None) # Remove redundant Extras field
|
51
|
+
return data
|
52
|
+
|
53
|
+
@classmethod
|
54
|
+
def from_dict(cls, d: dict):
|
55
|
+
"""Create a PixelDataHeader from a dictionary.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
d (dict): Dictionary containing header data.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
PixelDataHeader: A new instance with values from the dictionary.
|
62
|
+
"""
|
63
|
+
rescale_slope = d.get("RescaleSlope", 1.0)
|
64
|
+
rescale_intercept = d.get("RescaleIntercept", 0.0)
|
65
|
+
original_pixel_dtype = d.get("OriginalPixelDtype", "uint16")
|
66
|
+
pixel_dtype = d.get("PixelDtype", "uint16")
|
67
|
+
window_center = d.get("WindowCenter") # Defaults to None
|
68
|
+
window_width = d.get("WindowWidth") # Defaults to None
|
69
|
+
max_val = d.get("MaxVal") # Defaults to None
|
70
|
+
min_val = d.get("MinVal") # Defaults to None
|
71
|
+
|
72
|
+
# All other keys go into Extras
|
73
|
+
extras = {
|
74
|
+
k: v
|
75
|
+
for k, v in d.items()
|
76
|
+
if k
|
77
|
+
not in {
|
78
|
+
"RescaleSlope",
|
79
|
+
"RescaleIntercept",
|
80
|
+
"OriginalPixelDtype",
|
81
|
+
"PixelDtype",
|
82
|
+
"WindowCenter",
|
83
|
+
"WindowWidth",
|
84
|
+
"MaxVal",
|
85
|
+
"MinVal",
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
return cls(
|
90
|
+
RescaleSlope=rescale_slope,
|
91
|
+
RescaleIntercept=rescale_intercept,
|
92
|
+
OriginalPixelDtype=original_pixel_dtype,
|
93
|
+
PixelDtype=pixel_dtype,
|
94
|
+
WindowCenter=window_center,
|
95
|
+
WindowWidth=window_width,
|
96
|
+
MaxVal=max_val,
|
97
|
+
MinVal=min_val,
|
98
|
+
Extras=extras,
|
99
|
+
)
|
100
|
+
|
101
|
+
def to_json(self) -> str:
|
102
|
+
"""Serialize the header to a JSON string.
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
str: JSON string representation of the header.
|
106
|
+
"""
|
107
|
+
return json.dumps(self.to_dict())
|
108
|
+
|
109
|
+
@classmethod
|
110
|
+
def from_json(cls, json_str: str):
|
111
|
+
"""Create a PixelDataHeader from a JSON string.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
json_str (str): JSON string containing header data.
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
PixelDataHeader: A new instance created from the JSON data.
|
118
|
+
"""
|
119
|
+
obj_dict = json.loads(json_str)
|
120
|
+
return cls.from_dict(obj_dict)
|
dicube/dicom/__init__.py
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
from .dicom_meta import DicomMeta, SortMethod, read_dicom_dir
|
2
|
+
from .dicom_status import DicomStatus, get_dicom_status
|
3
|
+
from .dicom_tags import CommonTags
|
4
|
+
from .space_from_meta import get_space_from_DicomMeta
|
5
|
+
__all__ = [
|
6
|
+
"DicomMeta",
|
7
|
+
"read_dicom_dir",
|
8
|
+
"DicomStatus",
|
9
|
+
"get_dicom_status",
|
10
|
+
"CommonTags",
|
11
|
+
"SortMethod",
|
12
|
+
"get_space_from_DicomMeta",
|
13
|
+
]
|