Skip to content

VoiPosition in image CSA header is 2× correct value on Siemens 7T syngo MR E12 — incorrect SVS NIfTI affine #189

@rami-hamati

Description

@rami-hamati

First I want to say thank you for this beautiful MRS package @wtclarke. It has made MRS analysis seamless and it is a big contribution to the open source community. I have a suggested fix but may not make sense if this issue is just with our scanner at Western, Ontario, Canada. Happy to make a pull request.

Description
When converting Siemens SVS DICOM data acquired on a 7T running syngo MR E12 (Numaris4 Vx), process_siemens_svs_vx() produces a NIfTI with a translation vector that is exactly x2 the correct voxel centre position.

Affected functions
process_siemens_svs_vx() — produces wrong affine for SVS NIfTI
_detect_and_fill_voi() — would produce wrong VOI field for CSI data
Both functions read voxel position from:

imagePositionPatient = img.csa_header['tags']['VoiPosition']['items']

img.csa_header returns the image-level CSA header (tag 0029,1010). On this scanner/firmware combination, VoiPosition in the image CSA is exactly 2× the true voxel centre.

Evidence
Correct coordinates (from ASCCONV block in series CSA, tag 0029,1020/1120):

sSpecPara.sVoI.sPosition.dSag = -2.25665859564
sSpecPara.sVoI.sPosition.dCor = -11.5988196126
sSpecPara.sVoI.sPosition.dTra = 29.2436440678

VoiPosition from image CSA (what spec2nii uses):
[-4.51331755, -23.19763973, 58.48728871]

Ratio: exactly 2.0 on all three axes.

The NIfTI translation vector (affine[:3, 3]) matched the doubled image CSA values, placing the voxel at the wrong location when overlaid with structural images.

The series CSA ASCCONV block was verified against the voxel placement visible in the scanner's planning interface and is correct.

Suggested fix
Prefer the ASCCONV block in the series CSA header (tag 0029,1020 for image-type MRS, 0029,1120 for non-image-type) over VoiPosition from the image CSA:

def _voi_position_from_ascconv(dcm_data):
    for tag in [(0x0029, 0x1020), (0x0029, 0x1120)]:
        if tag in dcm_data:
            raw = dcm_data[tag].value
            text = raw.decode('latin-1', errors='replace') if isinstance(raw, bytes) else str(raw)
            keys = ('sSpecPara.sVoI.sPosition.dSag',
                    'sSpecPara.sVoI.sPosition.dCor',
                    'sSpecPara.sVoI.sPosition.dTra')
            vals = []
            for key in keys:
                m = re.search(rf'{re.escape(key)}\s*=\s*(\S+)', text)
                if m is None:
                    break
                vals.append(float(m.group(1)))
            if len(vals) == 3:
                return vals
    return None

# In process_siemens_svs_vx() and _detect_and_fill_voi():
imagePositionPatient = (_voi_position_from_ascconv(img.dcm_data)
                        or img.csa_header['tags']['VoiPosition']['items'])

This falls back to the existing VoiPosition read for scanners where the ASCCONV block is absent, preserving backward compatibility.

Environment
spec2nii v0.8.7
Scanner: Siemens 7T Investigational_Device_7T_Plus, syngo MR E12
Site: CFMM, University of Western Ontario
Sequence: sLASER SVS
Series types tested: image-type MRS (series with ORIGINAL, PRIMARY scout DICOM) and non-image-type MRS

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions