-
Notifications
You must be signed in to change notification settings - Fork 3
Troubleshooting
Audience: All users
Quick reference for common issues and solutions
This page provides solutions to common issues when working with CSA headers.
- Common Errors
- Missing Data Issues
- Data Validation Issues
- Frequently Asked Questions
- Debugging Tips
- Getting Help
Full error:
CsaReadError: Invalid check bit value: 0 (expected 77 or 205)
Cause: The CSA header may be corrupted or you're reading from the wrong DICOM tag.
Solution:
-
Verify it's a Siemens file:
import pydicom dcm = pydicom.dcmread('file.dcm') if hasattr(dcm, 'Manufacturer'): print(f"Manufacturer: {dcm.Manufacturer}") if 'SIEMENS' not in dcm.Manufacturer.upper(): print("⚠️ Not a Siemens file - CSA headers not available") else: print("⚠️ No manufacturer information found")
-
Check tag exists:
# Check for CSA tags if (0x0029, 0x1010) in dcm: print("✓ CSA Series Header found") else: print("✗ CSA Series Header NOT found") if (0x0029, 0x1020) in dcm: print("✓ CSA Image Header found") else: print("✗ CSA Image Header NOT found")
-
Try reading a different tag: Some files only have one CSA header, not both.
Note
Not all Siemens files contain CSA headers. Some older scanners or certain sequence types may not include them.
Full error:
KeyError: 'MosaicRefAcqTimes'Cause: The specific tag you're looking for doesn't exist in this CSA header.
Solution:
1. Check if tag exists before accessing:
series_info = CsaHeader(dcm[0x0029, 0x1010].value).read()
if 'MosaicRefAcqTimes' in series_info:
slice_times = series_info['MosaicRefAcqTimes']['value']
print(f"Slice times: {slice_times}")
else:
print("⚠️ Slice timing not available for this sequence")2. Use .get() for safe access:
# Safe access with default
slice_times = series_info.get('MosaicRefAcqTimes', {}).get('value', [])
if slice_times:
print(f"Found {len(slice_times)} slice times")
else:
print("No slice timing available")3. List available tags:
# See what's actually available
print("Available tags:")
for tag_name in series_info.keys():
print(f" - {tag_name}")Why slice timing might be missing:
- Single-slice acquisitions (no timing needed)
- Non-EPI sequences (3D acquisitions)
- Some older scanner software versions
- Non-mosaic acquisitions
Full error:
struct.error: unpack requires a buffer of 4 bytes
Cause: The CSA header data is truncated or corrupted.
Solution:
-
Check the data length:
data = dcm[0x0029, 0x1010].value print(f"CSA header size: {len(data)} bytes") # Minimum viable CSA header is ~100 bytes if len(data) < 100: print("⚠️ CSA header appears to be too small or corrupted")
-
Try the other CSA tag: If series header fails, try image header.
-
Validate the DICOM file: The file may be corrupted.
# Use dcmdump to validate dcmdump file.dcm > /dev/null # Or with pydicom python -c "import pydicom; pydicom.dcmread('file.dcm', force=True)"
Symptoms:
(0x0029, 0x1010) not in dcm # Returns True
(0x0029, 0x1020) not in dcm # Returns TruePossible causes:
-
Not a Siemens file
# Check manufacturer print(f"Manufacturer: {dcm.Manufacturer}") # Should contain "SIEMENS"
-
Anonymized/de-identified file - Private tags may have been stripped
# Check if any private tags exist private_tags = [tag for tag in dcm.keys() if tag.group % 2 == 1] print(f"Found {len(private_tags)} private tags") if len(private_tags) == 0: print("⚠️ No private tags - file may be anonymized")
-
Non-standard DICOM export - Some PACS systems strip private tags
Solutions:
- Use original DICOM files from scanner if possible
- Check PACS export settings to preserve private tags
- For GE/Philips data, CSA headers won't exist (manufacturer-specific)
Why slice timing isn't available:
| Sequence Type | Slice Timing Available? | Reason |
|---|---|---|
| EPI (2D) | ✅ Yes | Sequential slice acquisition |
| EPI Mosaic | ✅ Yes | Multi-slice mosaic |
| 3D Sequences | ❌ No | Volumetric acquisition (simultaneous) |
| Single-slice | ❌ No | Only one slice |
| Old software | Earlier versions may not record |
Alternatives when slice timing is missing:
-
Use interleaved/ascending assumption:
# For interleaved acquisition (common default) n_slices = 36 tr_ms = 2000 # Interleaved odd-even slice_times = [] for i in range(0, n_slices, 2): # Odd slices slice_times.append(i * tr_ms / n_slices) for i in range(1, n_slices, 2): # Even slices slice_times.append(i * tr_ms / n_slices)
-
Extract from protocol (if available):
protocol = series_info.get('MrPhoenixProtocol', {}).get('value', {}) if 'sSliceArray' in protocol: # May contain slice order information slice_array = protocol['sSliceArray'] print(slice_array)
-
Skip slice timing correction: Some modern methods are less sensitive.
Symptoms:
image_info = CsaHeader(dcm[0x0029, 0x1020].value).read()
'B_value' not in image_info # TruePossible causes:
-
Reading from wrong header - B-values are in Image Header (0x0029, 0x1020), not Series Header
-
B0 image - Reference images have b-value = 0, gradient = [0,0,0]
b_value = image_info.get('B_value', {}).get('value', 0) gradient = image_info.get('DiffusionGradientDirection', {}).get('value', [0,0,0]) if b_value == 0: print("ℹ️ This is a B0 reference image")
-
Non-diffusion sequence - Not all MRI sequences are DWI
Solution:
def check_diffusion_params(dicom_path):
"""Check what diffusion parameters are available."""
dcm = pydicom.dcmread(dicom_path)
print(f"Series Description: {dcm.SeriesDescription}")
if (0x0029, 0x1020) in dcm:
image_info = CsaHeader(dcm[0x0029, 0x1020].value).read()
# Check for diffusion tags
has_bval = 'B_value' in image_info
has_grad = 'DiffusionGradientDirection' in image_info
print(f"B-value tag present: {has_bval}")
print(f"Gradient tag present: {has_grad}")
if has_bval:
print(f"B-value: {image_info['B_value']['value']} s/mm²")
if has_grad:
print(f"Gradient: {image_info['DiffusionGradientDirection']['value']}")
else:
print("⚠️ No CSA Image Header found")
check_diffusion_params('dwi_001.dcm')Symptoms:
n_slices_csa = series_info['NumberOfImagesInMosaic']['value'] # 36
n_slice_times = len(series_info['MosaicRefAcqTimes']['value']) # 38
# Mismatch!Cause: Some sequences record extra timing points (e.g., dummy scans, oversampling).
Solution:
# Use the official slice count, trim timing
n_slices = series_info['NumberOfImagesInMosaic']['value']
slice_times = series_info['MosaicRefAcqTimes']['value'][:n_slices]
print(f"Using {len(slice_times)} of {len(series_info['MosaicRefAcqTimes']['value'])} timing points")Symptoms:
gradient = [0.5, 0.5, 0.5]
magnitude = np.linalg.norm(gradient) # 0.866, not 1.0!Cause: Some gradients are stored unnormalized.
Solution:
import numpy as np
def normalize_gradient(gradient):
"""Normalize gradient direction to unit vector."""
gradient = np.array(gradient)
magnitude = np.linalg.norm(gradient)
if np.isclose(magnitude, 0.0):
# B0 image - no gradient
return [0.0, 0.0, 0.0]
# Normalize to unit length
return (gradient / magnitude).tolist()
# Usage
raw_gradient = image_info['DiffusionGradientDirection']['value']
normalized = normalize_gradient(raw_gradient)
print(f"Raw: {raw_gradient}")
print(f"Normalized: {normalized}")
print(f"Magnitude: {np.linalg.norm(normalized)}")A: No. CSA headers are Siemens-specific proprietary structures.
Alternatives for other manufacturers:
- GE: Use standard DICOM tags + private tags in (0x0043, xxxx)
- Philips: Private tags in (0x2001, xxxx) and (0x2005, xxxx)
- Canon/Toshiba: Limited private tag documentation
A: CSA information is split hierarchically:
- Series Header (0x0029, 0x1010): Series-wide parameters (slice timing, protocol, mosaic info)
- Image Header (0x0029, 0x1020): Image-specific parameters (b-values, gradients, slice position)
This allows series-level metadata to be shared across images while image-specific data varies per slice/volume.
A: No! The CsaHeader class automatically detects and handles both formats transparently. You don't need to worry about format differences.
A: Both contain ASCCONV protocol data:
-
MrPhoenixProtocol: Modern scanners (2010+) -
MrProtocol: Older scanners (pre-2010)
Check both:
protocol = (series_info.get('MrPhoenixProtocol', {}).get('value') or
series_info.get('MrProtocol', {}).get('value'))A: No. CSA headers only exist in original DICOM files. Once converted to NIfTI:
- CSA metadata is lost (unless saved to JSON sidecar)
- Use
dcm2niixwith-b yflag to preserve CSA info in JSON
Workflow:
# Convert with JSON sidecar
dcm2niix -b y -z y -o output/ input_dicoms/
# Now you have:
# - image.nii.gz (NIfTI image)
# - image.json (metadata including some CSA info)import logging
# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
from csa_header import CsaHeader
# Now parsing will show detailed debug info
csa = CsaHeader(raw_bytes)
info = csa.read()import pydicom
dcm = pydicom.dcmread('file.dcm')
if (0x0029, 0x1010) in dcm:
raw_csa = dcm[0x0029, 0x1010].value
print(f"CSA data length: {len(raw_csa)} bytes")
print(f"First 20 bytes: {raw_csa[:20]}")
print(f"First 20 bytes (hex): {raw_csa[:20].hex()}")
# Check for CSA2 identifier
if raw_csa[:4] == b'SV10':
print("✓ CSA2 format detected (SV10)")
else:
print("✓ CSA1 format detected (no identifier)")def dump_csa_tags(dicom_path: str):
"""Print all available CSA tags."""
import pydicom
from csa_header import CsaHeader
dcm = pydicom.dcmread(dicom_path)
for header_name, tag in [('Series', (0x0029, 0x1010)), ('Image', (0x0029, 0x1020))]:
print(f"\n=== {header_name} Header ===")
if tag not in dcm:
print(f" {header_name} header not found")
continue
try:
csa_info = CsaHeader(dcm[tag].value).read()
print(f" Found {len(csa_info)} tags:")
for tag_name, tag_data in sorted(csa_info.items()):
value = tag_data['value']
# Truncate long values
if isinstance(value, list) and len(value) > 5:
value_str = f"[{value[0]}, ..., {value[-1]}] ({len(value)} items)"
elif isinstance(value, dict):
value_str = f"{{...}} ({len(value)} keys)"
else:
value_str = str(value)[:50]
print(f" {tag_name}: {value_str}")
except Exception as e:
print(f" Error parsing {header_name} header: {e}")
dump_csa_tags('scan.dcm')# Use DCMTK's dcmdump to see raw CSA data
dcmdump +P 0029,1010 file.dcm
# Or save to file
dcmdump +P 0029,1010 file.dcm > csa_raw.txtPlease gather this information:
-
Python and library versions:
python --version pip show csa_header pydicom nibabel
-
Scanner information:
print(f"Manufacturer: {dcm.Manufacturer}") print(f"Model: {dcm.ManufacturerModelName}") print(f"Software: {dcm.SoftwareVersions}")
-
Minimal reproducible example:
import pydicom from csa_header import CsaHeader dcm = pydicom.dcmread('file.dcm') # What fails? csa = CsaHeader(dcm[0x0029, 0x1010].value) info = csa.read() # Error here?
-
Error message: Full traceback if available
-
GitHub Issues: open-dicom/csa_header/issues
- Bug reports
- Feature requests
- Questions about usage
-
Check existing issues: Your question may already be answered
-
Documentation:
- Quick Start - Getting started guide
- User Guide - Practical examples
- Technical Reference - Format specifications
Good bug report template:
## Bug Description
Brief description of what's wrong
## Environment
- csa_header version: X.Y.Z
- Python version: 3.X
- OS: Linux/Mac/Windows
## Scanner Information
- Manufacturer: SIEMENS
- Model: Prisma/Skyra/etc
- Software version: VE11C/etc
## Minimal Example
```python
# Code that reproduces the issue
```
## Error Message
```
Full error traceback
```
## Expected Behavior
What you expected to happen
## Actual Behavior
What actually happenedStill having issues? Open an issue on GitHub
Version: 1.0+