Files
2025-11-30 08:30:10 +08:00

173 lines
6.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Convert DICOM files to common image formats (PNG, JPEG, TIFF).
Usage:
python dicom_to_image.py input.dcm output.png
python dicom_to_image.py input.dcm output.jpg --format JPEG
python dicom_to_image.py input.dcm output.tiff --apply-windowing
"""
import argparse
import sys
from pathlib import Path
try:
import pydicom
import numpy as np
from PIL import Image
except ImportError as e:
print(f"Error: Required package not installed: {e}")
print("Install with: pip install pydicom pillow numpy")
sys.exit(1)
def apply_windowing(pixel_array, ds):
"""Apply VOI LUT windowing if available."""
try:
from pydicom.pixel_data_handlers.util import apply_voi_lut
return apply_voi_lut(pixel_array, ds)
except (ImportError, AttributeError):
return pixel_array
def normalize_to_uint8(pixel_array):
"""Normalize pixel array to uint8 (0-255) range."""
if pixel_array.dtype == np.uint8:
return pixel_array
# Normalize to 0-1 range
pix_min = pixel_array.min()
pix_max = pixel_array.max()
if pix_max > pix_min:
normalized = (pixel_array - pix_min) / (pix_max - pix_min)
else:
normalized = np.zeros_like(pixel_array, dtype=float)
# Scale to 0-255
return (normalized * 255).astype(np.uint8)
def convert_dicom_to_image(input_path, output_path, image_format='PNG',
apply_window=False, frame=0):
"""
Convert DICOM file to standard image format.
Args:
input_path: Path to input DICOM file
output_path: Path to output image file
image_format: Output format (PNG, JPEG, TIFF, etc.)
apply_window: Whether to apply VOI LUT windowing
frame: Frame number for multi-frame DICOM files
"""
try:
# Read DICOM file
ds = pydicom.dcmread(input_path)
# Get pixel array
pixel_array = ds.pixel_array
# Handle multi-frame DICOM
if len(pixel_array.shape) == 3 and pixel_array.shape[0] > 1:
if frame >= pixel_array.shape[0]:
return False, f"Frame {frame} out of range (0-{pixel_array.shape[0]-1})"
pixel_array = pixel_array[frame]
print(f"Extracting frame {frame} of {ds.NumberOfFrames}")
# Apply windowing if requested
if apply_window and hasattr(ds, 'WindowCenter'):
pixel_array = apply_windowing(pixel_array, ds)
# Handle color images
if len(pixel_array.shape) == 3 and pixel_array.shape[2] in [3, 4]:
# RGB or RGBA image
if ds.PhotometricInterpretation in ['YBR_FULL', 'YBR_FULL_422']:
# Convert from YBR to RGB
try:
from pydicom.pixel_data_handlers.util import convert_color_space
pixel_array = convert_color_space(pixel_array,
ds.PhotometricInterpretation, 'RGB')
except ImportError:
print("Warning: Could not convert color space, using as-is")
image = Image.fromarray(pixel_array)
else:
# Grayscale image - normalize to uint8
pixel_array = normalize_to_uint8(pixel_array)
image = Image.fromarray(pixel_array, mode='L')
# Save image
image.save(output_path, format=image_format)
return True, {
'shape': ds.pixel_array.shape,
'modality': ds.Modality if hasattr(ds, 'Modality') else 'Unknown',
'bits_allocated': ds.BitsAllocated if hasattr(ds, 'BitsAllocated') else 'Unknown',
}
except Exception as e:
return False, str(e)
def main():
parser = argparse.ArgumentParser(
description='Convert DICOM files to common image formats',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python dicom_to_image.py input.dcm output.png
python dicom_to_image.py input.dcm output.jpg --format JPEG
python dicom_to_image.py input.dcm output.tiff --apply-windowing
python dicom_to_image.py multiframe.dcm frame5.png --frame 5
"""
)
parser.add_argument('input', type=str, help='Input DICOM file')
parser.add_argument('output', type=str, help='Output image file')
parser.add_argument('--format', type=str, choices=['PNG', 'JPEG', 'TIFF', 'BMP'],
help='Output image format (default: inferred from extension)')
parser.add_argument('--apply-windowing', action='store_true',
help='Apply VOI LUT windowing if available')
parser.add_argument('--frame', type=int, default=0,
help='Frame number for multi-frame DICOM files (default: 0)')
parser.add_argument('-v', '--verbose', action='store_true',
help='Show detailed conversion information')
args = parser.parse_args()
# Validate input file exists
input_path = Path(args.input)
if not input_path.exists():
print(f"Error: Input file '{args.input}' not found")
sys.exit(1)
# Determine output format
if args.format:
image_format = args.format
else:
# Infer from extension
ext = Path(args.output).suffix.upper().lstrip('.')
image_format = ext if ext in ['PNG', 'JPEG', 'JPG', 'TIFF', 'BMP'] else 'PNG'
# Convert the file
print(f"Converting: {args.input} -> {args.output}")
success, result = convert_dicom_to_image(args.input, args.output,
image_format, args.apply_windowing,
args.frame)
if success:
print(f"✓ Successfully converted to {image_format}")
if args.verbose:
print(f"\nImage information:")
print(f" - Shape: {result['shape']}")
print(f" - Modality: {result['modality']}")
print(f" - Bits Allocated: {result['bits_allocated']}")
else:
print(f"✗ Error: {result}")
sys.exit(1)
if __name__ == '__main__':
main()