1111
1212from __future__ import annotations
1313
14+ import json
15+ import logging
16+ import sys
1417import glob
1518import os
16- import re
1719import warnings
1820from abc import ABC , abstractmethod
1921from collections .abc import Callable , Iterable , Iterator , Sequence
2022from dataclasses import dataclass
2123from pathlib import Path
2224from typing import TYPE_CHECKING , Any
2325
26+ from monai .apps .utils import get_logger
2427import numpy as np
2528from torch .utils .data ._utils .collate import np_str_obj_array_pattern
2629
5154 pydicom , has_pydicom = optional_import ("pydicom" )
5255 nrrd , has_nrrd = optional_import ("nrrd" , allow_namespace_pkg = True )
5356
57+ DEFAULT_FMT = "%(asctime)s %(levelname)s %(filename)s:%(lineno)d - %(message)s"
58+
59+ logger = get_logger (module_name = __name__ , fmt = DEFAULT_FMT )
60+ logger = logging .getLogger (__name__ )
61+ handler = logging .StreamHandler (sys .stdout )
62+ handler .setFormatter (logging .Formatter ('%(asctime)s %(levelname)s %(message)s' ))
63+ logger .addHandler (handler )
64+ logger .setLevel (logging .DEBUG )
65+
66+
5467__all__ = ["ImageReader" , "ITKReader" , "NibabelReader" , "NumpyReader" , "PILReader" , "PydicomReader" , "NrrdReader" ]
5568
5669
@@ -98,8 +111,10 @@ def read(self, data: Sequence[PathLike] | PathLike, **kwargs) -> Sequence[Any] |
98111 kwargs: additional args for actual `read` API of 3rd party libs.
99112
100113 """
114+ #self.update_json(input_file=data)
101115 raise NotImplementedError (f"Subclass { self .__class__ .__name__ } must implement this method." )
102116
117+
103118 @abstractmethod
104119 def get_data (self , img ) -> tuple [np .ndarray , dict ]:
105120 """
@@ -147,6 +162,24 @@ def _stack_images(image_list: list, meta_dict: dict):
147162 meta_dict [MetaKeys .ORIGINAL_CHANNEL_DIM ] = 0
148163 return np .stack (image_list , axis = 0 )
149164
165+ def update_json (input_file = None , output_file = None ):
166+ record_path = "img-label.json"
167+
168+ if not os .path .exists (record_path ) or os .stat (record_path ).st_size == 0 :
169+ with open (record_path , 'w' ) as f :
170+ json .dump ([], f )
171+
172+ with open (record_path , 'r+' ) as f :
173+ records = json .load (f )
174+ if input_file :
175+ new_record = {"image" : input_file , "label" : []}
176+ records .append (new_record )
177+ elif output_file and records :
178+ records [- 1 ]["label" ].append (output_file )
179+
180+ f .seek (0 )
181+ json .dump (records , f , indent = 4 )
182+
150183
151184@require_pkg (pkg_name = "itk" )
152185class ITKReader (ImageReader ):
@@ -168,8 +201,8 @@ class ITKReader(ImageReader):
168201 series_name: the name of the DICOM series if there are multiple ones.
169202 used when loading DICOM series.
170203 reverse_indexing: whether to use a reversed spatial indexing convention for the returned data array.
171- If ``False``, the spatial indexing convention is reversed to be compatible with ITK ;
172- otherwise, the spatial indexing follows the numpy convention . Default is ``False``.
204+ If ``False``, the spatial indexing follows the numpy convention ;
205+ otherwise, the spatial indexing convention is reversed to be compatible with ITK . Default is ``False``.
173206 This option does not affect the metadata.
174207 series_meta: whether to load the metadata of the DICOM series (using the metadata from the first slice).
175208 This flag is checked only when loading DICOM series. Default is ``False``.
@@ -225,6 +258,7 @@ def read(self, data: Sequence[PathLike] | PathLike, **kwargs):
225258 img_ = []
226259
227260 filenames : Sequence [PathLike ] = ensure_tuple (data )
261+ update_json (input_file = filenames )
228262 kwargs_ = self .kwargs .copy ()
229263 kwargs_ .update (kwargs )
230264 for name in filenames :
@@ -332,6 +366,25 @@ def _get_affine(self, img, lps_to_ras: bool = True):
332366 affine [:sr , - 1 ] = origin [:sr ]
333367 if lps_to_ras :
334368 affine = orientation_ras_lps (affine )
369+ logger .debug ("lps is changed to ras" )
370+
371+ # 使用 Logger 輸出信息
372+
373+ logger .info ("\n Origin[:sr]:" )
374+ logger .info (", " .join (f"{ x :.10f} " for x in origin [:sr ]))
375+
376+ logger .info ("\n Direction[:sr, :sr]:" )
377+ for row in direction [:sr , :sr ]:
378+ logger .info (", " .join (f"{ x :.15f} " for x in row ))
379+
380+ logger .info ("\n Spacing[:sr]:" )
381+ logger .info (", " .join (f"{ x :.15f} " for x in spacing [:sr ]))
382+
383+
384+ # affine = numpy.round(affine, decimals=5)
385+
386+ logger .debug (f"Affine matrix:\n { affine } " )
387+
335388 return affine
336389
337390 def _get_spatial_shape (self , img ):
@@ -404,12 +457,8 @@ class PydicomReader(ImageReader):
404457 label_dict: label of the dicom data. If provided, it will be used when loading segmentation data.
405458 Keys of the dict are the classes, and values are the corresponding class number. For example:
406459 for TCIA collection "C4KC-KiTS", it can be: {"Kidney": 0, "Renal Tumor": 1}.
407- fname_regex: a regular expression to match the file names when the input is a folder.
408- If provided, only the matched files will be included. For example, to include the file name
409- "image_0001.dcm", the regular expression could be `".*image_(\\ d+).dcm"`. Default to `""`.
410- Set it to `None` to use `pydicom.misc.is_dicom` to match valid files.
411460 kwargs: additional args for `pydicom.dcmread` API. more details about available args:
412- https://pydicom.github.io/pydicom/stable/reference/generated/pydicom.filereader.dcmread.html
461+ https://pydicom.github.io/pydicom/stable/reference/generated/pydicom.filereader.dcmread.html#pydicom.filereader.dcmread
413462 If the `get_data` function will be called
414463 (for example, when using this reader with `monai.transforms.LoadImage`), please ensure that the argument
415464 `stop_before_pixels` is `True`, and `specific_tags` covers all necessary tags, such as `PixelSpacing`,
@@ -423,7 +472,6 @@ def __init__(
423472 swap_ij : bool = True ,
424473 prune_metadata : bool = True ,
425474 label_dict : dict | None = None ,
426- fname_regex : str = "" ,
427475 ** kwargs ,
428476 ):
429477 super ().__init__ ()
@@ -433,7 +481,6 @@ def __init__(
433481 self .swap_ij = swap_ij
434482 self .prune_metadata = prune_metadata
435483 self .label_dict = label_dict
436- self .fname_regex = fname_regex
437484
438485 def verify_suffix (self , filename : Sequence [PathLike ] | PathLike ) -> bool :
439486 """
@@ -465,6 +512,7 @@ def read(self, data: Sequence[PathLike] | PathLike, **kwargs):
465512 img_ = []
466513
467514 filenames : Sequence [PathLike ] = ensure_tuple (data )
515+ update_json (input_file = filenames )
468516 kwargs_ = self .kwargs .copy ()
469517 kwargs_ .update (kwargs )
470518
@@ -474,16 +522,9 @@ def read(self, data: Sequence[PathLike] | PathLike, **kwargs):
474522 name = f"{ name } "
475523 if Path (name ).is_dir ():
476524 # read DICOM series
477- if self .fname_regex is not None :
478- series_slcs = [slc for slc in glob .glob (os .path .join (name , "*" )) if re .match (self .fname_regex , slc )]
479- else :
480- series_slcs = [slc for slc in glob .glob (os .path .join (name , "*" )) if pydicom .misc .is_dicom (slc )]
481- slices = []
482- for slc in series_slcs :
483- try :
484- slices .append (pydicom .dcmread (fp = slc , ** kwargs_ ))
485- except pydicom .errors .InvalidDicomError as e :
486- warnings .warn (f"Failed to read { slc } with exception: \n { e } ." , stacklevel = 2 )
525+ series_slcs = glob .glob (os .path .join (name , "*" ))
526+ series_slcs = [slc for slc in series_slcs if "LICENSE" not in slc ]
527+ slices = [pydicom .dcmread (fp = slc , ** kwargs_ ) for slc in series_slcs ]
487528 img_ .append (slices if len (slices ) > 1 else slices [0 ])
488529 if len (slices ) > 1 :
489530 self .has_series = True
@@ -913,9 +954,11 @@ def read(self, data: Sequence[PathLike] | PathLike, **kwargs):
913954 https://github.com/nipy/nibabel/blob/master/nibabel/loadsave.py
914955
915956 """
957+ logger .info (f"Reading NIfTI data from: { data } " )
916958 img_ : list [Nifti1Image ] = []
917959
918960 filenames : Sequence [PathLike ] = ensure_tuple (data )
961+ update_json (input_file = filenames )
919962 kwargs_ = self .kwargs .copy ()
920963 kwargs_ .update (kwargs )
921964 for name in filenames :
@@ -1076,13 +1119,14 @@ def read(self, data: Sequence[PathLike] | PathLike, **kwargs):
10761119 img_ : list [Nifti1Image ] = []
10771120
10781121 filenames : Sequence [PathLike ] = ensure_tuple (data )
1122+ update_json (input_file = filenames )
10791123 kwargs_ = self .kwargs .copy ()
10801124 kwargs_ .update (kwargs )
10811125 for name in filenames :
10821126 img = np .load (name , allow_pickle = True , ** kwargs_ )
10831127 if Path (name ).name .endswith (".npz" ):
10841128 # load expected items from NPZ file
1085- npz_keys = list ( img . keys ()) if self .npz_keys is None else self .npz_keys
1129+ npz_keys = [ f"arr_ { i } " for i in range ( len ( img ))] if self .npz_keys is None else self .npz_keys
10861130 for k in npz_keys :
10871131 img_ .append (img [k ])
10881132 else :
@@ -1173,6 +1217,7 @@ def read(self, data: Sequence[PathLike] | PathLike | np.ndarray, **kwargs):
11731217 img_ : list [PILImage .Image ] = []
11741218
11751219 filenames : Sequence [PathLike ] = ensure_tuple (data )
1220+ update_json (input_file = filenames )
11761221 kwargs_ = self .kwargs .copy ()
11771222 kwargs_ .update (kwargs )
11781223 for name in filenames :
@@ -1297,10 +1342,11 @@ def read(self, data: Sequence[PathLike] | PathLike, **kwargs) -> Sequence[Any] |
12971342 """
12981343 img_ : list = []
12991344 filenames : Sequence [PathLike ] = ensure_tuple (data )
1345+ update_json (input_file = filenames )
13001346 kwargs_ = self .kwargs .copy ()
13011347 kwargs_ .update (kwargs )
13021348 for name in filenames :
1303- nrrd_image = NrrdImage (* nrrd .read (name , index_order = self .index_order , ** kwargs_ ))
1349+ nrrd_image = NrrdImage (* nrrd .read (name , index_order = self .index_order , * kwargs_ ))
13041350 img_ .append (nrrd_image )
13051351 return img_ if len (filenames ) > 1 else img_ [0 ]
13061352
@@ -1323,7 +1369,7 @@ def get_data(self, img: NrrdImage | list[NrrdImage]) -> tuple[np.ndarray, dict]:
13231369 header = dict (i .header )
13241370 if self .index_order == "C" :
13251371 header = self ._convert_f_to_c_order (header )
1326- header [MetaKeys .ORIGINAL_AFFINE ] = self ._get_affine (header )
1372+ header [MetaKeys .ORIGINAL_AFFINE ] = self ._get_affine (i )
13271373
13281374 if self .affine_lps_to_ras :
13291375 header = self ._switch_lps_ras (header )
@@ -1344,7 +1390,7 @@ def get_data(self, img: NrrdImage | list[NrrdImage]) -> tuple[np.ndarray, dict]:
13441390
13451391 return _stack_images (img_array , compatible_meta ), compatible_meta
13461392
1347- def _get_affine (self , header : dict ) -> np .ndarray :
1393+ def _get_affine (self , img : NrrdImage ) -> np .ndarray :
13481394 """
13491395 Get the affine matrix of the image, it can be used to correct
13501396 spacing, orientation or execute spatial transforms.
@@ -1353,8 +1399,8 @@ def _get_affine(self, header: dict) -> np.ndarray:
13531399 img: A `NrrdImage` loaded from image file
13541400
13551401 """
1356- direction = header ["space directions" ]
1357- origin = header ["space origin" ]
1402+ direction = img . header ["space directions" ]
1403+ origin = img . header ["space origin" ]
13581404
13591405 x , y = direction .shape
13601406 affine_diam = min (x , y ) + 1
0 commit comments