dicom-info 0.1.0__py3-none-any.whl → 0.2.0__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.
@@ -1,11 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dicom-info
3
- Version: 0.1.0
4
- Summary: Prints information about DIOCM files.
3
+ Version: 0.2.0
4
+ Summary: Prints information about DICOM files.
5
5
  Requires-Python: >=3.13
6
6
  Description-Content-Type: text/markdown
7
7
  License-File: LICENSE
8
8
  Requires-Dist: pydicom>=3.0.1
9
+ Requires-Dist: matplotlib>=3.7.0
10
+ Requires-Dist: numpy>=1.24.0
9
11
  Dynamic: license-file
10
12
 
11
13
  # dicom-info
@@ -14,12 +16,14 @@ A command-line tool for displaying DICOM file information.
14
16
 
15
17
  ## Description
16
18
 
17
- dicom-info is a simple Python utility that reads and displays information from DICOM (Digital Imaging and Communications in Medicine) files. It provides a straightforward way to inspect DICOM file metadata from the command line.
19
+ dicom-info is a simple Python utility that reads and displays information from DICOM (Digital Imaging and Communications in Medicine) files. It provides a straightforward way to inspect DICOM file metadata from the command line, and can also display DICOM images interactively.
18
20
 
19
21
  ## Requirements
20
22
 
21
23
  - Python >= 3.13
22
24
  - pydicom >= 3.0.1
25
+ - matplotlib >= 3.7.0 (for image display)
26
+ - numpy >= 1.24.0 (for image display)
23
27
 
24
28
  ## Installation
25
29
 
@@ -43,20 +47,37 @@ The basic syntax is:
43
47
  dicom-info FILE [FILE ...]
44
48
  ```
45
49
 
46
- Example:
50
+ ### Displaying Metadata
47
51
 
48
- ```
52
+ To display DICOM file metadata:
53
+
54
+ ```bash
49
55
  dicom-info path/to/file.dcm
50
56
  dicom-info file1.dcm file2.dcm file3.dcm
51
57
  ```
52
58
 
53
59
  The tool will display information for each DICOM file, including all available metadata and attributes.
54
60
 
61
+ ### Displaying Images
62
+
63
+ To display DICOM images interactively, use the `--display` or `-d` flag:
64
+
65
+ ```bash
66
+ dicom-info --display path/to/file.dcm
67
+ dicom-info -d file1.dcm file2.dcm file3.dcm
68
+ ```
69
+
70
+ Image display features:
71
+ - **2D images**: Displayed as grayscale plots with a colorbar
72
+ - **3D images**: Displayed with an interactive slider to navigate through slices
73
+ - **Multiple files**: Each image is shown in its own subplot with the filename as the title
74
+
55
75
  ## Error Handling
56
76
 
57
77
  The tool will exit with status code 1 if:
58
78
  - Files are not found
59
79
  - Files are not valid DICOM files
80
+ - When using `--display`, if no files contain pixel data
60
81
 
61
82
  ## License
62
83
 
@@ -0,0 +1,7 @@
1
+ dicom_info-0.2.0.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
2
+ dicominfo/__init__.py,sha256=MwdwA_dnMxEdk8mNT8PHcKCQMIzkdwxLOvyC-Qf_CtQ,5670
3
+ dicom_info-0.2.0.dist-info/METADATA,sha256=X-NKpFoNL6H08SPWBZ5OE_iFb6Ev3M-nrkopdkYPsco,2041
4
+ dicom_info-0.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
5
+ dicom_info-0.2.0.dist-info/entry_points.txt,sha256=tCOO5Vkl04B0doIO8msDBl5suKLYAbQwjanb9XxtgKw,46
6
+ dicom_info-0.2.0.dist-info/top_level.txt,sha256=Sb5T-FcQW-zE4FXaF23fa5YaZ1bVGFtaKGm-FT45rHs,10
7
+ dicom_info-0.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
dicominfo/__init__.py CHANGED
@@ -4,12 +4,30 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
+ # Typing
8
+ from typing import TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from matplotlib.axes import Axes
12
+ from matplotlib.image import AxesImage
13
+ from numpy import ndarray
14
+
15
+
7
16
  # Python imports
8
17
  import argparse
18
+ import logging
9
19
  import sys
20
+ from pathlib import Path
21
+ from typing import Callable
10
22
 
11
23
  # Module imports
24
+ import matplotlib.pyplot as plt
12
25
  import pydicom
26
+ import pydicom.errors
27
+ from matplotlib.widgets import Slider
28
+ from mpl_toolkits.axes_grid1 import make_axes_locatable
29
+
30
+ logger = logging.getLogger(__name__)
13
31
 
14
32
 
15
33
  def print_stats(files: list[str]) -> None:
@@ -26,6 +44,120 @@ def print_stats(files: list[str]) -> None:
26
44
  print(banner, dcm, sep="\n")
27
45
 
28
46
 
47
+ def display_images(
48
+ files: list[str],
49
+ max_cols: int | None = None,
50
+ ) -> None:
51
+ """Display DICOM images with interactive controls."""
52
+ try:
53
+ dcms = [pydicom.dcmread(f) for f in files]
54
+
55
+ except (FileNotFoundError, pydicom.errors.InvalidDicomError) as err:
56
+ print(f"Files could not be read due to {err}")
57
+ sys.exit(1)
58
+
59
+ # Check if any files have pixel data
60
+ files_with_pixels = [
61
+ (f, dcm) for f, dcm in zip(files, dcms, strict=True)
62
+ if hasattr(dcm, "pixel_array")
63
+ ]
64
+
65
+ if not files_with_pixels:
66
+ print("No DICOM files with pixel data found.")
67
+ sys.exit(1)
68
+
69
+ # Create figure with subplots for each image
70
+ num_images = len(files_with_pixels)
71
+ max_cols = int(num_images ** .5) if max_cols is None else max_cols
72
+ cols = min(num_images, max_cols)
73
+ rows = (num_images - 1) // cols + 1
74
+ fig = plt.figure(figsize=(5 * cols, 4 * rows))
75
+
76
+ # Store references to manage 3D sliders
77
+ sliders = []
78
+ axes_images: list[
79
+ tuple[Axes, AxesImage, Slider | None, ndarray | None]
80
+ ] = []
81
+
82
+ for idx, (filepath, dcm) in enumerate(files_with_pixels, start=1):
83
+ pixel_array = dcm.pixel_array
84
+ filename = Path(filepath).name
85
+
86
+ # Determine if 2D or 3D
87
+ if len(pixel_array.shape) == 2: # noqa: PLR2004
88
+ # 2D image - simple display
89
+ ax = fig.add_subplot(rows, cols, idx)
90
+ im = ax.imshow(pixel_array, cmap="gray")
91
+ ax.set_title(filename)
92
+ ax.axis("off")
93
+ plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
94
+ axes_images.append((ax, im, None, None))
95
+
96
+ elif len(pixel_array.shape) == 3: # noqa: PLR2004
97
+ # 3D image - display with slider
98
+ ax = fig.add_subplot(rows, cols, idx)
99
+
100
+ # Start with the first slice
101
+ initial_slice = 0
102
+ im = ax.imshow(pixel_array[initial_slice], cmap="gray")
103
+ ax.set_title(
104
+ f"{filename}\nSlice {initial_slice + 1}/{pixel_array.shape[0]}",
105
+ )
106
+ ax.axis("off")
107
+
108
+ # Create slider axes to the right of the image
109
+ divider = make_axes_locatable(ax)
110
+ slider_ax = divider.append_axes("right", size="5%", pad=0.1)
111
+ slider = Slider(
112
+ slider_ax,
113
+ "Slice",
114
+ 0,
115
+ pixel_array.shape[0] - 1,
116
+ valinit=initial_slice,
117
+ valstep=1,
118
+ orientation="vertical",
119
+ )
120
+
121
+ # Update function for slider
122
+ def make_update(
123
+ image_obj: AxesImage,
124
+ axis: Axes,
125
+ data: ndarray,
126
+ fname: str,
127
+ sldr: Slider,
128
+ ) -> Callable[[float], None]:
129
+ def update(val: float) -> None:
130
+ slice_idx = int(sldr.val)
131
+ image_obj.set_data(data[slice_idx])
132
+ axis.set_title(
133
+ f"{fname}\nSlice {slice_idx + 1}/{data.shape[0]}",
134
+ )
135
+ fig.canvas.draw_idle()
136
+ logger.debug("Slider at slice %f for %s", val, fname)
137
+
138
+ return update
139
+
140
+ slider.on_changed(
141
+ make_update(im, ax, pixel_array, filename, slider),
142
+ )
143
+ sliders.append(slider)
144
+ axes_images.append((ax, im, slider, pixel_array))
145
+
146
+ else:
147
+ logger.warning(
148
+ "%s has unsupported dimensions: %s",
149
+ filename,
150
+ pixel_array.shape,
151
+ )
152
+
153
+ plt.tight_layout()
154
+ if len(files_with_pixels) == 1 and axes_images[0][2] is not None:
155
+ # Adjust layout for single 3D image with slider
156
+ plt.subplots_adjust(bottom=0.15)
157
+
158
+ plt.show()
159
+
160
+
29
161
  def main() -> None:
30
162
  """Entry point for the CLI."""
31
163
  parser = argparse.ArgumentParser(
@@ -39,11 +171,29 @@ def main() -> None:
39
171
  type=str,
40
172
  )
41
173
 
174
+ parser.add_argument(
175
+ "-d",
176
+ "--display",
177
+ help="Display DICOM images instead of printing metadata.",
178
+ action="store_true",
179
+ )
180
+
181
+ parser.add_argument(
182
+ "-c",
183
+ "--columns",
184
+ help="Maximum number of columns for image display.",
185
+ type=int,
186
+ default=None,
187
+ )
188
+
42
189
  args = parser.parse_args()
43
190
 
44
- # Print dicom info
45
191
  print_stats(args.file)
46
192
 
193
+ # Display images
194
+ if args.display:
195
+ display_images(args.file, max_cols=args.columns)
196
+
47
197
 
48
198
  if __name__ == "__main__":
49
199
  main()
@@ -1,7 +0,0 @@
1
- dicom_info-0.1.0.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
2
- dicominfo/__init__.py,sha256=d4lS1hVqMRghlmhuzgi743x1pMDyNgQVEcEsXw4pPWc,1024
3
- dicom_info-0.1.0.dist-info/METADATA,sha256=74grN25OiNkTQ2n3QylEclvKDQBwFcewWNr8FyhYDJA,1289
4
- dicom_info-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
5
- dicom_info-0.1.0.dist-info/entry_points.txt,sha256=tCOO5Vkl04B0doIO8msDBl5suKLYAbQwjanb9XxtgKw,46
6
- dicom_info-0.1.0.dist-info/top_level.txt,sha256=Sb5T-FcQW-zE4FXaF23fa5YaZ1bVGFtaKGm-FT45rHs,10
7
- dicom_info-0.1.0.dist-info/RECORD,,