""" Django management command for generating DICOM files from DICOMImage model data. """ import os import numpy as np from django.core.management.base import BaseCommand, CommandError from django.utils import timezone from django.db import models from pydicom.uid import generate_uid from radiology.models import DICOMImage, ImagingSeries, ImagingStudy from radiology.services import DICOMGenerator, DICOMValidator, DICOMUtilities from patients.models import PatientProfile from core.models import Tenant from accounts.models import User class Command(BaseCommand): help = 'Generate DICOM files from DICOMImage model data' def add_arguments(self, parser): parser.add_argument( '--image-id', type=str, help='Generate DICOM for specific DICOMImage ID' ) parser.add_argument( '--series-id', type=str, help='Generate DICOMs for all images in a series' ) parser.add_argument( '--study-id', type=str, help='Generate DICOMs for all images in a study' ) parser.add_argument( '--create-test-data', action='store_true', help='Create test study with DICOM files' ) parser.add_argument( '--modality', type=str, default='CT', choices=['CT', 'MR', 'CR', 'DX', 'US', 'XA', 'RF', 'MG', 'NM', 'PT'], help='Modality for test data (default: CT)' ) parser.add_argument( '--num-images', type=int, default=10, help='Number of images to generate for test data (default: 10)' ) parser.add_argument( '--validate-only', action='store_true', help='Only validate existing DICOM files without generating new ones' ) parser.add_argument( '--generate-missing', action='store_true', help='Generate DICOM files for all database records that are missing files' ) parser.add_argument( '--batch-size', type=int, default=100, help='Number of files to process in each batch (default: 100)' ) parser.add_argument( '--output-dir', type=str, help='Custom output directory for DICOM files' ) def handle(self, *args, **options): generator = DICOMGenerator() # Override output directory if specified if options['output_dir']: generator.dicom_storage_path = options['output_dir'] os.makedirs(generator.dicom_storage_path, exist_ok=True) self.stdout.write(f"Using custom output directory: {generator.dicom_storage_path}") try: if options['create_test_data']: self._create_test_data(generator, options) elif options['validate_only']: self._validate_existing_dicoms() elif options['image_id']: self._generate_single_image(generator, options['image_id']) elif options['series_id']: self._generate_series_images(generator, options['series_id']) elif options['study_id']: self._generate_study_images(generator, options['study_id']) else: self._generate_all_missing_dicoms(generator) except Exception as e: raise CommandError(f'Error generating DICOM files: {str(e)}') def _create_test_data(self, generator, options): """Create test study with DICOM files.""" self.stdout.write("Creating test study with DICOM files...") # Get or create test tenant tenant, created = Tenant.objects.get_or_create( name='Test Hospital', defaults={ 'display_name': 'Test Hospital', 'address_line1': '123 Test Street', 'city': 'Test City', 'state': 'Test State', 'postal_code': '12345', 'phone_number': '+1234567890', 'email': 'test@hospital.com', 'is_active': True } ) if created: self.stdout.write(f"Created test tenant: {tenant.name}") # Get or create test patient patient, created = PatientProfile.objects.get_or_create( mrn='TEST001', tenant=tenant, defaults={ 'first_name': 'John', 'last_name': 'Doe', 'date_of_birth': '1980-01-01', 'gender': 'MALE' } ) if created: self.stdout.write(f"Created test patient: {patient.first_name} {patient.last_name}") # Get or create test physician physician, created = User.objects.get_or_create( username='test_physician', defaults={ 'tenant': tenant, 'first_name': 'Dr. Jane', 'last_name': 'Smith', 'email': 'physician@test.com' } ) if created: self.stdout.write(f"Created test physician: {physician.first_name} {physician.last_name}") # Create test study study = ImagingStudy.objects.create( tenant=tenant, study_instance_uid=generate_uid(), patient=patient, referring_physician=physician, modality=options['modality'], study_description=f"Test {options['modality']} Study", body_part='CHEST', study_datetime=timezone.now(), status='SCHEDULED', priority='ROUTINE', manufacturer='Test Manufacturer', model_name='Test Model', station_name='Test Station' ) self.stdout.write(f"Created test study: {study.accession_number}") # Create test series with DICOM files series = generator.create_test_dicom_series( study=study, modality=options['modality'], num_images=options['num_images'] ) self.stdout.write( self.style.SUCCESS( f"Successfully created test series with {options['num_images']} DICOM files" ) ) self.stdout.write(f"Study ID: {study.study_id}") self.stdout.write(f"Series ID: {series.series_id}") self.stdout.write(f"Accession Number: {study.accession_number}") def _validate_existing_dicoms(self): """Validate all existing DICOM files.""" self.stdout.write("Validating existing DICOM files...") images = DICOMImage.objects.filter(file_path__isnull=False).exclude(file_path='') total_images = images.count() if total_images == 0: self.stdout.write("No DICOM images found with file paths.") return valid_count = 0 invalid_count = 0 for image in images: if image.has_dicom_file(): validation_result = DICOMValidator.validate_dicom_file(image.file_path) if validation_result['valid']: valid_count += 1 self.stdout.write(f"✓ Valid: {image.sop_instance_uid}") else: invalid_count += 1 self.stdout.write( self.style.ERROR( f"✗ Invalid: {image.sop_instance_uid} - {validation_result['errors']}" ) ) else: invalid_count += 1 self.stdout.write( self.style.WARNING(f"⚠ Missing file: {image.sop_instance_uid}") ) self.stdout.write( self.style.SUCCESS( f"Validation complete: {valid_count} valid, {invalid_count} invalid/missing" ) ) def _generate_single_image(self, generator, image_id): """Generate DICOM for a single image.""" try: image = DICOMImage.objects.get(image_id=image_id) except DICOMImage.DoesNotExist: raise CommandError(f'DICOMImage with ID {image_id} not found') self.stdout.write(f"Generating DICOM for image: {image.sop_instance_uid}") # Validate before generation validation = image.validate_for_dicom_generation() if not validation['valid']: raise CommandError(f'Validation failed: {validation["errors"]}') if validation['warnings']: for warning in validation['warnings']: self.stdout.write(self.style.WARNING(f"Warning: {warning}")) # Generate DICOM file file_path = image.generate_dicom_file() self.stdout.write( self.style.SUCCESS(f"Successfully generated DICOM file: {file_path}") ) self.stdout.write(f"File size: {image.file_size_mb} MB") def _generate_series_images(self, generator, series_id): """Generate DICOMs for all images in a series.""" try: series = ImagingSeries.objects.get(series_id=series_id) except ImagingSeries.DoesNotExist: raise CommandError(f'ImagingSeries with ID {series_id} not found') images = series.images.all() if not images.exists(): self.stdout.write(f"No images found in series {series.series_instance_uid}") return self.stdout.write(f"Generating DICOMs for {images.count()} images in series...") generated_count = 0 for image in images: try: validation = image.validate_for_dicom_generation() if validation['valid']: file_path = image.generate_dicom_file() generated_count += 1 self.stdout.write(f"✓ Generated: {image.sop_instance_uid}") else: self.stdout.write( self.style.ERROR( f"✗ Skipped {image.sop_instance_uid}: {validation['errors']}" ) ) except Exception as e: self.stdout.write( self.style.ERROR(f"✗ Error generating {image.sop_instance_uid}: {str(e)}") ) self.stdout.write( self.style.SUCCESS(f"Generated {generated_count} DICOM files") ) def _generate_study_images(self, generator, study_id): """Generate DICOMs for all images in a study.""" try: study = ImagingStudy.objects.get(study_id=study_id) except ImagingStudy.DoesNotExist: raise CommandError(f'ImagingStudy with ID {study_id} not found') images = DICOMImage.objects.filter(series__study=study) if not images.exists(): self.stdout.write(f"No images found in study {study.accession_number}") return self.stdout.write(f"Generating DICOMs for {images.count()} images in study...") generated_count = 0 for image in images: try: validation = image.validate_for_dicom_generation() if validation['valid']: file_path = image.generate_dicom_file() generated_count += 1 self.stdout.write(f"✓ Generated: {image.sop_instance_uid}") else: self.stdout.write( self.style.ERROR( f"✗ Skipped {image.sop_instance_uid}: {validation['errors']}" ) ) except Exception as e: self.stdout.write( self.style.ERROR(f"✗ Error generating {image.sop_instance_uid}: {str(e)}") ) self.stdout.write( self.style.SUCCESS(f"Generated {generated_count} DICOM files for study {study.accession_number}") ) def _generate_all_missing_dicoms(self, generator): """Generate DICOM files for all images that don't have files.""" self.stdout.write("Generating DICOM files for all images without files...") # Find images without DICOM files images_without_files = DICOMImage.objects.filter( models.Q(file_path__isnull=True) | models.Q(file_path='') ) total_images = images_without_files.count() if total_images == 0: self.stdout.write("All DICOM images already have files.") return self.stdout.write(f"Found {total_images} images without DICOM files") generated_count = 0 for image in images_without_files: try: validation = image.validate_for_dicom_generation() if validation['valid']: file_path = image.generate_dicom_file() generated_count += 1 if generated_count % 10 == 0: self.stdout.write(f"Generated {generated_count}/{total_images} files...") else: self.stdout.write( self.style.ERROR( f"✗ Skipped {image.sop_instance_uid}: {validation['errors']}" ) ) except Exception as e: self.stdout.write( self.style.ERROR(f"✗ Error generating {image.sop_instance_uid}: {str(e)}") ) self.stdout.write( self.style.SUCCESS(f"Generated {generated_count} DICOM files") )