dicom_to_image.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. #!/usr/bin/env python3
  2. """
  3. Convert DICOM files to common image formats (PNG, JPEG, TIFF).
  4. Usage:
  5. python dicom_to_image.py input.dcm output.png
  6. python dicom_to_image.py input.dcm output.jpg --format JPEG
  7. python dicom_to_image.py input.dcm output.tiff --apply-windowing
  8. """
  9. import argparse
  10. import sys
  11. from pathlib import Path
  12. try:
  13. import pydicom
  14. import numpy as np
  15. from PIL import Image
  16. except ImportError as e:
  17. print(f"Error: Required package not installed: {e}")
  18. print("Install with: pip install pydicom pillow numpy")
  19. sys.exit(1)
  20. def apply_windowing(pixel_array, ds):
  21. """Apply VOI LUT windowing if available."""
  22. try:
  23. from pydicom.pixel_data_handlers.util import apply_voi_lut
  24. return apply_voi_lut(pixel_array, ds)
  25. except (ImportError, AttributeError):
  26. return pixel_array
  27. def normalize_to_uint8(pixel_array):
  28. """Normalize pixel array to uint8 (0-255) range."""
  29. if pixel_array.dtype == np.uint8:
  30. return pixel_array
  31. # Normalize to 0-1 range
  32. pix_min = pixel_array.min()
  33. pix_max = pixel_array.max()
  34. if pix_max > pix_min:
  35. normalized = (pixel_array - pix_min) / (pix_max - pix_min)
  36. else:
  37. normalized = np.zeros_like(pixel_array, dtype=float)
  38. # Scale to 0-255
  39. return (normalized * 255).astype(np.uint8)
  40. def convert_dicom_to_image(input_path, output_path, image_format='PNG',
  41. apply_window=False, frame=0):
  42. """
  43. Convert DICOM file to standard image format.
  44. Args:
  45. input_path: Path to input DICOM file
  46. output_path: Path to output image file
  47. image_format: Output format (PNG, JPEG, TIFF, etc.)
  48. apply_window: Whether to apply VOI LUT windowing
  49. frame: Frame number for multi-frame DICOM files
  50. """
  51. try:
  52. # Read DICOM file
  53. ds = pydicom.dcmread(input_path)
  54. # Get pixel array
  55. pixel_array = ds.pixel_array
  56. # Handle multi-frame DICOM
  57. if len(pixel_array.shape) == 3 and pixel_array.shape[0] > 1:
  58. if frame >= pixel_array.shape[0]:
  59. return False, f"Frame {frame} out of range (0-{pixel_array.shape[0]-1})"
  60. pixel_array = pixel_array[frame]
  61. print(f"Extracting frame {frame} of {ds.NumberOfFrames}")
  62. # Apply windowing if requested
  63. if apply_window and hasattr(ds, 'WindowCenter'):
  64. pixel_array = apply_windowing(pixel_array, ds)
  65. # Handle color images
  66. if len(pixel_array.shape) == 3 and pixel_array.shape[2] in [3, 4]:
  67. # RGB or RGBA image
  68. if ds.PhotometricInterpretation in ['YBR_FULL', 'YBR_FULL_422']:
  69. # Convert from YBR to RGB
  70. try:
  71. from pydicom.pixel_data_handlers.util import convert_color_space
  72. pixel_array = convert_color_space(pixel_array,
  73. ds.PhotometricInterpretation, 'RGB')
  74. except ImportError:
  75. print("Warning: Could not convert color space, using as-is")
  76. image = Image.fromarray(pixel_array)
  77. else:
  78. # Grayscale image - normalize to uint8
  79. pixel_array = normalize_to_uint8(pixel_array)
  80. image = Image.fromarray(pixel_array, mode='L')
  81. # Save image
  82. image.save(output_path, format=image_format)
  83. return True, {
  84. 'shape': ds.pixel_array.shape,
  85. 'modality': ds.Modality if hasattr(ds, 'Modality') else 'Unknown',
  86. 'bits_allocated': ds.BitsAllocated if hasattr(ds, 'BitsAllocated') else 'Unknown',
  87. }
  88. except Exception as e:
  89. return False, str(e)
  90. def main():
  91. parser = argparse.ArgumentParser(
  92. description='Convert DICOM files to common image formats',
  93. formatter_class=argparse.RawDescriptionHelpFormatter,
  94. epilog="""
  95. Examples:
  96. python dicom_to_image.py input.dcm output.png
  97. python dicom_to_image.py input.dcm output.jpg --format JPEG
  98. python dicom_to_image.py input.dcm output.tiff --apply-windowing
  99. python dicom_to_image.py multiframe.dcm frame5.png --frame 5
  100. """
  101. )
  102. parser.add_argument('input', type=str, help='Input DICOM file')
  103. parser.add_argument('output', type=str, help='Output image file')
  104. parser.add_argument('--format', type=str, choices=['PNG', 'JPEG', 'TIFF', 'BMP'],
  105. help='Output image format (default: inferred from extension)')
  106. parser.add_argument('--apply-windowing', action='store_true',
  107. help='Apply VOI LUT windowing if available')
  108. parser.add_argument('--frame', type=int, default=0,
  109. help='Frame number for multi-frame DICOM files (default: 0)')
  110. parser.add_argument('-v', '--verbose', action='store_true',
  111. help='Show detailed conversion information')
  112. args = parser.parse_args()
  113. # Validate input file exists
  114. input_path = Path(args.input)
  115. if not input_path.exists():
  116. print(f"Error: Input file '{args.input}' not found")
  117. sys.exit(1)
  118. # Determine output format
  119. if args.format:
  120. image_format = args.format
  121. else:
  122. # Infer from extension
  123. ext = Path(args.output).suffix.upper().lstrip('.')
  124. image_format = ext if ext in ['PNG', 'JPEG', 'JPG', 'TIFF', 'BMP'] else 'PNG'
  125. # Convert the file
  126. print(f"Converting: {args.input} -> {args.output}")
  127. success, result = convert_dicom_to_image(args.input, args.output,
  128. image_format, args.apply_windowing,
  129. args.frame)
  130. if success:
  131. print(f"✓ Successfully converted to {image_format}")
  132. if args.verbose:
  133. print(f"\nImage information:")
  134. print(f" - Shape: {result['shape']}")
  135. print(f" - Modality: {result['modality']}")
  136. print(f" - Bits Allocated: {result['bits_allocated']}")
  137. else:
  138. print(f"✗ Error: {result}")
  139. sys.exit(1)
  140. if __name__ == '__main__':
  141. main()