tessl install tessl/pypi-dcm2niix@1.0.5Command-line application that converts medical imaging data from DICOM format to NIfTI format with BIDS support
Advanced scenarios, vendor-specific considerations, and solutions to common problems.
Symptoms: dcm2niix runs but no NIfTI files are created.
Diagnosis:
from dcm2niix import main
# Run with verbose output to see what's happening
exit_code = main(["-v", "2", "/input/folder"], capture_output=True, text=True)
if exit_code == 2:
print("No valid DICOM files found in directory")
elif exit_code == 5:
print("Input folder is invalid or inaccessible")
elif exit_code == 6:
print("Output folder cannot be created or is invalid")
elif exit_code == 7:
print("Output folder is read-only")Solutions:
from dcm2niix import main
from pathlib import Path
def diagnose_no_output(input_dir, output_dir="/tmp/test_output"):
"""Diagnose why no output is generated."""
input_path = Path(input_dir)
output_path = Path(output_dir)
# Check 1: Input exists
if not input_path.exists():
return f"Input directory does not exist: {input_dir}"
# Check 2: Input contains files
files = list(input_path.rglob("*"))
if not files:
return f"Input directory is empty: {input_dir}"
# Check 3: Output directory writable
try:
output_path.mkdir(parents=True, exist_ok=True)
test_file = output_path / ".test"
test_file.touch()
test_file.unlink()
except PermissionError:
return f"Output directory not writable: {output_dir}"
# Check 4: Run conversion with verbose output
exit_code = main(["-v", "2", "-o", str(output_path), str(input_path)])
if exit_code == 2:
return "No valid DICOM files found (check file extensions and headers)"
elif exit_code == 0:
return f"Success! Check output in {output_path}"
else:
return f"Conversion failed with exit code {exit_code}"
# Usage
diagnosis = diagnose_no_output("/data/problem/folder")
print(diagnosis)Symptoms: Output files are generated but appear corrupted or incomplete.
Common Causes:
Diagnosis:
from dcm2niix import main
# Enable maximum verbosity
exit_code = main(["-v", "2", "/input/folder"])
if exit_code == 4:
print("Corrupt DICOM file detected")
print("Check verbose output for filename")
elif exit_code == 10:
print("Incomplete volumes detected (missing slices)")
print("Some slices may be missing from the series")Solution - Skip Corrupt Files:
from dcm2niix import main, bin
import subprocess
from pathlib import Path
def find_corrupt_dicoms(dicom_dir):
"""Identify corrupt DICOM files."""
result = subprocess.run(
[bin, "-v", "2", dicom_dir],
capture_output=True,
text=True
)
# Parse output for corrupt file mentions
corrupt_files = []
for line in result.stderr.split('\n'):
if 'corrupt' in line.lower() or 'error' in line.lower():
# Extract filename if present
corrupt_files.append(line)
return corrupt_files
# Usage
corrupt = find_corrupt_dicoms("/data/problem/folder")
for file_info in corrupt:
print(f"Corrupt: {file_info}")Symptoms: Images appear flipped or rotated incorrectly.
Understanding: dcm2niix automatically converts DICOM LPS (Left-Posterior-Superior) to NIfTI RAS+ (Right-Anterior-Superior) coordinate system.
Verification:
import json
from pathlib import Path
def check_orientation(nifti_file):
"""Check orientation from BIDS JSON sidecar."""
json_file = Path(nifti_file).with_suffix('.json')
if not json_file.exists():
return "No JSON sidecar found (run with -b y)"
with open(json_file, 'r') as f:
metadata = json.load(f)
orientation = metadata.get('ImageOrientationPatientDICOM', 'Not found')
print(f"Image Orientation: {orientation}")
# Check if non-standard orientation
if orientation != [1, 0, 0, 0, 1, 0]:
print("Warning: Non-standard orientation detected")
return orientation
# Usage
check_orientation("/output/T1_MPRAGE.nii.gz")Solution: Orientation is correct by default. If issues persist, check source DICOM headers.
Symptoms: Too many output files, including scouts, localizers, and derived images.
Solution - Filter Output:
from dcm2niix import main
# Ignore derived, localizer, and 2D images
exit_code = main([
"-i", "y", # Ignore derived/localizer
"-z", "y", # Compress
"/input/folder"
])Solution - Select Specific Series:
from dcm2niix import main, bin
import subprocess
import re
def convert_only_primary_sequences(dicom_dir, output_dir):
"""Convert only T1, T2, and FLAIR sequences."""
# Step 1: Get series list
result = subprocess.run(
[bin, "-q", "l", dicom_dir],
capture_output=True,
text=True
)
# Step 2: Filter for desired sequences
desired_crcs = []
for line in result.stdout.split('\n'):
match = re.search(r'(.+?) \[(\d+)\]', line)
if match:
name = match.group(1)
crc = match.group(2)
# Check if it's a sequence we want
if any(seq in name.upper() for seq in ['T1', 'T2', 'FLAIR']):
desired_crcs.append(crc)
print(f"Selected: {name} (CRC: {crc})")
# Step 3: Convert selected series
if desired_crcs:
args = ["-z", "y", "-b", "y", "-o", output_dir]
for crc in desired_crcs:
args.extend(["-n", crc])
args.append(dicom_dir)
exit_code = main(args)
return exit_code in [0, 8]
else:
print("No matching sequences found")
return False
# Usage
success = convert_only_primary_sequences("/data/exam", "/data/nifti")Mosaic Format:
Siemens EPI data is often stored in "mosaic" format (multi-slice image stored as single 2D image). dcm2niix automatically detects and converts this.
from dcm2niix import main
# Mosaic format handled automatically
exit_code = main(["-z", "y", "/siemens/epi/folder"])CSA Header Information:
Siemens private tags (CSA headers) are automatically extracted to BIDS JSON:
import json
# Check CSA-derived fields in JSON
with open("output.json", 'r') as f:
metadata = json.load(f)
# Siemens-specific fields
coil_name = metadata.get('CoilString', 'Not found')
sequence_name = metadata.get('SequenceName', 'Not found')
print(f"Coil: {coil_name}, Sequence: {sequence_name}")Diffusion Directions:
# Diffusion gradient directions extracted from CSA header
# Available in BIDS JSON as DiffusionGradientDirectionCritical: Precise Float Scaling
Philips DICOM includes two scaling factors:
Always use -p y for quantitative analysis:
from dcm2niix import main
# REQUIRED for Philips quantitative imaging
exit_code = main([
"-p", "y", # Precise float scaling (RWV)
"-l", "o", # Preserve original values
"-z", "y",
"/philips/dwi/folder"
])Verification:
import json
def verify_philips_scaling(json_file):
"""Verify Philips scaling parameters."""
with open(json_file, 'r') as f:
metadata = json.load(f)
# Check for Philips-specific fields
rwv_slope = metadata.get('PhilipsRWVSlope')
rwv_intercept = metadata.get('PhilipsRWVIntercept')
if rwv_slope is not None:
print(f"Philips RWV Slope: {rwv_slope}")
print(f"Philips RWV Intercept: {rwv_intercept}")
print("✓ Precise scaling applied")
else:
print("⚠ Philips RWV parameters not found")
# Usage
verify_philips_scaling("/output/DWI.json")Enhanced DICOM:
Philips enhanced DICOM (multi-frame) is automatically detected and converted.
Diffusion Gradient Cycling:
GE diffusion sequences may use gradient cycling modes. dcm2niix auto-detects, but can be overridden:
# Auto-detect (default)
dcm2niix /ge/dwi/folder
# Override if needed
dcm2niix --diffCyclingModeGE 0 /ge/dwi/folderfrom dcm2niix import main
# Override gradient cycling mode
exit_code = main([
"--diffCyclingModeGE", "0",
"/ge/dwi/folder"
])Internal Pulse Sequence Names:
import json
# GE internal sequence names in JSON
with open("output.json", 'r') as f:
metadata = json.load(f)
internal_name = metadata.get('InternalPulseSequenceName', 'Not found')
print(f"GE Internal Sequence: {internal_name}")from dcm2niix import main
import subprocess
def convert_with_timeout(input_dir, output_dir, timeout_seconds=600):
"""Convert with timeout for large datasets."""
try:
exit_code = main(
["-z", "y", "-o", output_dir, input_dir],
timeout=timeout_seconds
)
return True, exit_code
except subprocess.TimeoutExpired:
print(f"Conversion timed out after {timeout_seconds} seconds")
return False, -1
# Usage for very large dataset
success, code = convert_with_timeout("/large/dataset", "/output", timeout_seconds=1800)from dcm2niix import main
import psutil
import os
def convert_with_memory_monitoring(input_dir, output_dir):
"""Monitor memory usage during conversion."""
process = psutil.Process(os.getpid())
initial_memory = process.memory_info().rss / 1024 / 1024 # MB
print(f"Initial memory: {initial_memory:.1f} MB")
exit_code = main(["-z", "y", "-o", output_dir, input_dir])
final_memory = process.memory_info().rss / 1024 / 1024 # MB
memory_used = final_memory - initial_memory
print(f"Final memory: {final_memory:.1f} MB")
print(f"Memory used: {memory_used:.1f} MB")
return exit_code
# Usage
exit_code = convert_with_memory_monitoring("/data/dicom", "/data/nifti")# Multi-echo automatically separated
# Output: scan_e1.nii, scan_e2.nii, scan_e3.niiVerification:
# Check for echo-separated files
ls /output/*_e*.nii.gz# Complex images automatically separated
# Output: scan_real.nii, scan_imaginary.nii, scan_ph.nii# Uncombined coil data automatically separated
# Output: scan_c1.nii, scan_c2.nii, ..., scan_c32.niiimport json
def verify_anonymization(json_file):
"""Verify patient info was anonymized."""
with open(json_file, 'r') as f:
metadata = json.load(f)
# Check for PHI fields
phi_fields = [
'PatientName',
'PatientID',
'PatientBirthDate',
'StudyInstanceUID',
'SeriesInstanceUID'
]
found_phi = []
for field in phi_fields:
if field in metadata:
found_phi.append(field)
if found_phi:
print(f"⚠ PHI fields found: {', '.join(found_phi)}")
print("Use -ba y to anonymize")
return False
else:
print("✓ No PHI fields found")
return True
# Usage
verify_anonymization("/output/scan.json")from dcm2niix import main
# HIPAA Safe Harbor method requires removing 18 identifiers
# dcm2niix -ba y removes direct identifiers but may not be sufficient
# for full HIPAA compliance
def hipaa_compliant_conversion(input_dir, output_dir):
"""
Convert with maximum anonymization.
Note: This removes direct identifiers but does not guarantee
full HIPAA compliance. Consult with compliance officer.
"""
exit_code = main([
"-b", "y", # Generate JSON
"-ba", "y", # Anonymize
"-c", "", # Clear comment field
"-f", "scan_%s", # Generic filename (no patient info)
"-o", output_dir,
input_dir
])
return exit_code in [0, 8]
# Usage
success = hipaa_compliant_conversion("/phi/data", "/deidentified/data")Symptom: -z y falls back to internal compression or fails.
Solution:
# Install pigz
# macOS
brew install pigz
# Ubuntu/Debian
sudo apt-get install pigz
# CentOS/RHEL
sudo yum install pigzVerify:
which pigz
pigz --versionFallback:
from dcm2niix import main
import shutil
def convert_with_compression_fallback(input_dir, output_dir):
"""Use pigz if available, otherwise internal compression."""
if shutil.which("pigz"):
print("Using pigz for compression")
compression = "y"
else:
print("pigz not found, using internal compression")
compression = "i"
exit_code = main([
"-z", compression,
"-o", output_dir,
input_dir
])
return exit_code in [0, 8]
# Usage
success = convert_with_compression_fallback("/data/dicom", "/data/nifti")Symptom: Error about unsupported transfer syntax.
Diagnosis:
# Check dcm2niix capabilities
dcm2niix --version
# Look for: JPEGLS, JP2:OpenJPEG, etc.Common unsupported formats:
Solution: Request uncompressed or RLE transfer syntax from PACS, or build dcm2niix from source with optional libraries.
from dcm2niix import main, bin, __version__
from pathlib import Path
import subprocess
import json
def full_diagnostic(input_dir, output_dir="/tmp/dcm2niix_test"):
"""Complete diagnostic for troubleshooting."""
print("=== dcm2niix Diagnostic ===\n")
# 1. Version info
print(f"1. Version: {__version__}")
result = subprocess.run([bin, "--version"], capture_output=True, text=True)
print(f" Binary: {result.stdout.strip()}\n")
# 2. Input validation
input_path = Path(input_dir)
print(f"2. Input: {input_dir}")
print(f" Exists: {input_path.exists()}")
if input_path.exists():
files = list(input_path.rglob("*"))
print(f" Files: {len(files)}\n")
else:
print(" ERROR: Input does not exist\n")
return
# 3. Output validation
output_path = Path(output_dir)
print(f"3. Output: {output_dir}")
try:
output_path.mkdir(parents=True, exist_ok=True)
print(f" Writable: True\n")
except Exception as e:
print(f" ERROR: {e}\n")
return
# 4. Test conversion
print("4. Test conversion (verbose):")
exit_code = main([
"-v", "2",
"-z", "y",
"-b", "y",
"-o", str(output_path),
str(input_path)
])
print(f" Exit code: {exit_code}")
# 5. Check output
output_files = list(output_path.glob("*"))
print(f" Output files: {len(output_files)}")
for f in output_files[:5]: # Show first 5
print(f" - {f.name}")
# 6. Interpretation
print("\n5. Interpretation:")
if exit_code == 0:
print(" ✓ SUCCESS")
elif exit_code == 2:
print(" ✗ No valid DICOM files found")
elif exit_code == 4:
print(" ✗ Corrupt DICOM file detected")
elif exit_code == 8:
print(" ⚠ Partial success")
else:
print(f" ✗ Failed with exit code {exit_code}")
# Usage
full_diagnostic("/data/problem/folder")