#!/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()