Skip to content

Troubleshooting

Zvi Baratz edited this page Oct 29, 2025 · 1 revision

Troubleshooting Guide

Audience: All users

Quick reference for common issues and solutions

This page provides solutions to common issues when working with CSA headers.


Table of Contents


Common Errors

Error: Invalid check bit value

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:

  1. 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")
  2. 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")
  3. 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.


Error: KeyError: 'MosaicRefAcqTimes'

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

Error: struct.error: unpack requires a buffer of X bytes

Full error:

struct.error: unpack requires a buffer of 4 bytes

Cause: The CSA header data is truncated or corrupted.

Solution:

  1. 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")
  2. Try the other CSA tag: If series header fails, try image header.

  3. 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)"

Missing Data Issues

Problem: No CSA Headers Found

Symptoms:

(0x0029, 0x1010) not in dcm  # Returns True
(0x0029, 0x1020) not in dcm  # Returns True

Possible causes:

  1. Not a Siemens file

    # Check manufacturer
    print(f"Manufacturer: {dcm.Manufacturer}")
    # Should contain "SIEMENS"
  2. 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")
  3. 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)

Problem: Missing Slice Timing {#missing-slice-timing}

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 ⚠️ Maybe Earlier versions may not record

Alternatives when slice timing is missing:

  1. 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)
  2. 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)
  3. Skip slice timing correction: Some modern methods are less sensitive.


Problem: Missing B-values or Gradients

Symptoms:

image_info = CsaHeader(dcm[0x0029, 0x1020].value).read()
'B_value' not in image_info  # True

Possible causes:

  1. Reading from wrong header - B-values are in Image Header (0x0029, 0x1020), not Series Header

  2. 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")
  3. 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')

Data Validation Issues

Problem: Slice Count Mismatch {#slice-count-mismatch}

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")

Problem: Gradient Not Normalized

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)}")

Frequently Asked Questions

Q: Can I use csa_header with non-Siemens data?

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

Q: Why are there two CSA headers?

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.


Q: Do I need to know CSA1 vs CSA2 format?

A: No! The CsaHeader class automatically detects and handles both formats transparently. You don't need to worry about format differences.


Q: What's the difference between MrPhoenixProtocol and MrProtocol?

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'))

Q: Can I use this with NIfTI files?

A: No. CSA headers only exist in original DICOM files. Once converted to NIfTI:

  • CSA metadata is lost (unless saved to JSON sidecar)
  • Use dcm2niix with -b y flag 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)

Debugging Tips

Enable Verbose Logging

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()

Inspect Raw CSA Data

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)")

Dump All Available Tags

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')

Compare CSA Parsing with dcmdump

# 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.txt

Getting Help

Before Asking for Help

Please gather this information:

  1. Python and library versions:

    python --version
    pip show csa_header pydicom nibabel
  2. Scanner information:

    print(f"Manufacturer: {dcm.Manufacturer}")
    print(f"Model: {dcm.ManufacturerModelName}")
    print(f"Software: {dcm.SoftwareVersions}")
  3. 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?
  4. Error message: Full traceback if available

Where to Get Help

  1. GitHub Issues: open-dicom/csa_header/issues

    • Bug reports
    • Feature requests
    • Questions about usage
  2. Check existing issues: Your question may already be answered

  3. Documentation:

How to Report a Bug

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 happened

Still having issues? Open an issue on GitHub

Clone this wiki locally