nipy / heudiconv

Flexible DICOM conversion into structured directory layouts
https://heudiconv.readthedocs.io
Other
235 stars 125 forks source link

Failure due to binary incompatibility in Singularity image for Heudiconv 1.0.0 #720

Closed pcamach2 closed 11 months ago

pcamach2 commented 11 months ago

Summary

I am trying out the new version 1.0.0 release using Singularity and some DICOMs that I have successfully converted using older releases of Heudiconv (v0.11.3, v0.9.0). Local Singularity cache ($CACHESING) and temp ($TMPSING) directories are used.

The project heuristic is:

import os

def create_key(template, outtype=('nii.gz',), annotation_classes=None):
    if template is None or not template:
        raise ValueError('Template must be a valid format string')
    return template, outtype, annotation_classes

def infotodict(seqinfo):
    """Heuristic evaluator for determining which runs belong where

    allowed template fields - follow python string module:

    item: index within category
    subject: participant id
    seqitem: run number during scanning
    subindex: sub index within group
    """

    t1w = create_key('sub-{subject}/{session}/anat/sub-{subject}_{session}_T1w') 
    FLAIR = create_key('sub-{subject}/{session}/anat/sub-{subject}_{session}_FLAIR') 
    dwi = create_key('sub-{subject}/{session}/dwi/sub-{subject}_{session}_run-{item:01d}_dwi')
    rest = create_key('sub-{subject}/{session}/func/sub-{subject}_{session}_task-rest_dir-PA_run-{item:01d}_bold')
    fmap_fmri = create_key('sub-{subject}/{session}/fmap/sub-{subject}_{session}_acq-{acq}_dir-{dir}_run-{item:01d}_epi')
    fmap_dwi = create_key('sub-{subject}/{session}/fmap/sub-{subject}_{session}_acq-{acq}_dir-{dir}_run-{item:01d}_epi')
    fmap_tfmri = create_key('sub-{subject}/{session}/fmap/sub-{subject}_{session}_acq-{acq}_dir-{dir}_run-{item:01d}_epi')
    rest_sbref = create_key('sub-{subject}/{session}/func/sub-{subject}_{session}_task-rest_dir-PA_run-1_sbref')
    tfunc = create_key('sub-{subject}/{session}/func/sub-{subject}_{session}_task-{task}_dir-PA_run-{item:01d}_bold')
    nback_sbref = create_key('sub-{subject}/{session}/func/sub-{subject}_{session}_task-nback_dir-PA_run-1_sbref')
    t2w = create_key('sub-{subject}/{session}/anat/sub-{subject}_{session}_acq-{acq}_run-{item:01d}_T2w')

    info = {t1w: [], dwi: [], t2w: [], FLAIR: [], rest: [], rest_sbref: [], fmap_fmri: [], fmap_tfmri: [], nback_sbref: [], fmap_dwi: [], tfunc: []} 

    for s in seqinfo:
        if ('T1w' in s.series_id) and ('mprage' in s.series_id) and not(s.is_derived) and (s.dim3 >100) and ('AAHead' not in s.series_id):
            info[t1w] = [s.series_id] # assign if a single series meets criteria
        if (('dwi' in s.protocol_name) or ((s.dim1 == 92) and (s.TR == 2.8))) and ('cmrr' in s.protocol_name) and not(s.is_derived):
            info[dwi].append({'item': s.series_id}) # append if multiple series meet criteria
        if (('dwi' in s.protocol_name) and ('fmap' in s.protocol_name)) or (('DTIField' in s.protocol_name) and (s.TR == 8.29)):
            if ('AP' in s.protocol_name):
                info[fmap_dwi].append({'item': s.series_id, 'acq': 'dwi', 'dir': 'AP'})
            else:
                info[fmap_dwi].append({'item': s.series_id, 'acq': 'dwi', 'dir': 'PA'})
        if ('T2w' in s.protocol_name) and ('flair' in s.protocol_name) and ('mprage' not in s.protocol_name):
            info[FLAIR] = [s.series_id]
        if ('T2w' in s.protocol_name) and ('highreshippocampus' in s.protocol_name) and ('mprage' not in s.protocol_name):
            info[t2w].append({'item': s.series_id, 'acq': 'highreshippocampus'})
        if any(substr in s.protocol_name for substr in ('t2_','T2w_')) and (s.dim3 >100) and not(s.is_derived) and ('mprage' not in s.protocol_name) and ('tra' not in s.protocol_name):
            if (not('flair') in s.protocol_name) and (not('FLAIR') in s.protocol_name):
                info[t2w].append({'item': s.series_id, 'acq': 'space'})
        if (s.dim4 > 501) and ('rest' and 'bold' in s.protocol_name) and ('nback' and 'Sternberg' not in s.protocol_name) and ('cmrr' not in s.protocol_name) and ('DTI' not in s.protocol_name):
            info[rest].append({'item': s.series_id})
        if ('SBRef' in s.series_description) and ('rest' or 'REST_PA' in s.protocol_name) and ('fmap' not in s.protocol_name) and ('cmrr' not in s.protocol_name):
            info[rest_sbref] = [s.series_id]
        if ('SBRef' in s.series_description) and ('nback' in s.protocol_name) and ('fmap' not in s.protocol_name) and ('cmrr' not in s.protocol_name):
            info[nback_sbref] = [s.series_id]
        if (s.dim4 > 10) and ('bold' and 'nback' in s.protocol_name) and ('rest' and 'Sternberg' not in s.protocol_name) and ('SBRef' and 'sbref' not in s.protocol_name):
            info[tfunc].append({'item': s.series_id, 'task': 'nback'})
        if (('fmap' in s.protocol_name) and ('rest' in s.protocol_name) and ('nback' and 'DTI' and 'dwi' and 'cmrr' and 'Physio' not in s.protocol_name)):
            if ('AP' in s.protocol_name):
                info[fmap_fmri].append({'item': s.series_id, 'acq': 'fMRIrest', 'dir': 'AP'})
            else:
                info[fmap_fmri].append({'item': s.series_id, 'acq': 'fMRIrest', 'dir': 'PA'})
        if ('fmap' in s.protocol_name) and ('nback' in s.protocol_name):
            if ('AP' in s.protocol_name):
                info[fmap_tfmri].append({'item': s.series_id, 'acq': 'fMRInback', 'dir': 'AP'})
            else:
                info[fmap_tfmri].append({'item': s.series_id, 'acq': 'fMRInback', 'dir': 'PA'})
    return info

The command used was:

SINGULARITY_CACHEDIR=$CACHESING SINGULARITY_TMPDIR=$TMPSING \
singularity run --cleanenv --bind ${projDir}:/datain ${IMAGEDIR}/heudiconv-v1.0.0.sif \
-d /datain/{subject}/{session}/*/SCANS/*/DICOM/*dcm -f /datain/${project}_heuristic.py \
-o /datain/bids/sourcedata -s ${sub} -ss ${ses} -c dcm2niix -b \
--minmeta --overwrite -g accession_number

This fails as follows:

Traceback (most recent call last):
  File "/opt/miniconda-py39_4.12.0/bin/heudiconv", line 5, in <module>
    from heudiconv.cli.run import main
  File "/src/heudiconv/heudiconv/cli/run.py", line 11, in <module>
    from ..main import workflow
  File "/src/heudiconv/heudiconv/main.py", line 11, in <module>
    from .bids import populate_bids_templates, populate_intended_for, tuneup_bids_json_files
  File "/src/heudiconv/heudiconv/bids.py", line 21, in <module>
    import pydicom as dcm
  File "/opt/miniconda-py39_4.12.0/lib/python3.9/site-packages/pydicom/__init__.py", line 32, in <module>
    from pydicom.dataelem import DataElement
  File "/opt/miniconda-py39_4.12.0/lib/python3.9/site-packages/pydicom/dataelem.py", line 18, in <module>
    from pydicom import config  # don't import datetime_conversion directly
  File "/opt/miniconda-py39_4.12.0/lib/python3.9/site-packages/pydicom/config.py", line 381, in <module>
    import pydicom.pixel_data_handlers.pylibjpeg_handler as pylibjpeg_handler  # noqa
  File "/opt/miniconda-py39_4.12.0/lib/python3.9/site-packages/pydicom/pixel_data_handlers/pylibjpeg_handler.py", line 82, in <module>
    import libjpeg
  File "/opt/miniconda-py39_4.12.0/lib/python3.9/site-packages/libjpeg/__init__.py", line 4, in <module>
    from .utils import decode, decode_pixel_data, get_parameters
  File "/opt/miniconda-py39_4.12.0/lib/python3.9/site-packages/libjpeg/utils.py", line 9, in <module>
    import _libjpeg
  File "libjpeg/_libjpeg.pyx", line 1, in init libjpeg._libjpeg
ValueError: numpy.ndarray size changed, may indicate binary incompatibility. Expected 96 from C header, got 80 from PyObject

Platform details:

Choose one:

yarikoptic commented 11 months ago

google is full of "workarounds" which typically consist of reinstalling numpy etc, so -- could you just try may be using newer image, e.g. docker://nipy/heudiconv:master which is already 2 months old but may be would have a better luck

pcamach2 commented 11 months ago

I tried building a new Singularity container from the nipy/heudiconv:1.0.0 base plus pip uninstall -y numpy and pip install numpy, which changed the numpy version from 1.26.0 to 1.26.2 but did not change the error. No change in behavior from building the latest or master images either:

Traceback (most recent call last):
  File "/opt/miniconda-py39_4.12.0/bin/heudiconv", line 5, in <module>
    from heudiconv.cli.run import main
  File "/src/heudiconv/heudiconv/cli/run.py", line 11, in <module>
    from ..main import workflow
  File "/src/heudiconv/heudiconv/main.py", line 11, in <module>
    from .bids import populate_bids_templates, populate_intended_for, tuneup_bids_json_files
  File "/src/heudiconv/heudiconv/bids.py", line 21, in <module>
    import pydicom as dcm
  File "/opt/miniconda-py39_4.12.0/lib/python3.9/site-packages/pydicom/__init__.py", line 32, in <module>
    from pydicom.dataelem import DataElement
  File "/opt/miniconda-py39_4.12.0/lib/python3.9/site-packages/pydicom/dataelem.py", line 18, in <module>
    from pydicom import config  # don't import datetime_conversion directly
  File "/opt/miniconda-py39_4.12.0/lib/python3.9/site-packages/pydicom/config.py", line 381, in <module>
    import pydicom.pixel_data_handlers.pylibjpeg_handler as pylibjpeg_handler  # noqa
  File "/opt/miniconda-py39_4.12.0/lib/python3.9/site-packages/pydicom/pixel_data_handlers/pylibjpeg_handler.py", line 82, in <module>
    import libjpeg
  File "/opt/miniconda-py39_4.12.0/lib/python3.9/site-packages/libjpeg/__init__.py", line 4, in <module>
    from .utils import decode, decode_pixel_data, get_parameters
  File "/opt/miniconda-py39_4.12.0/lib/python3.9/site-packages/libjpeg/utils.py", line 9, in <module>
    import _libjpeg
  File "libjpeg/_libjpeg.pyx", line 1, in init libjpeg._libjpeg
ValueError: numpy.ndarray size changed, may indicate binary incompatibility. Expected 96 from C header, got 80 from PyObject
yarikoptic commented 11 months ago

thanks for trying

pip uninstall -y numpy and pip install numpy, which changed the numpy version from 1.26.0 to 1.26.2 but did not change the error

might not be of desired effect since original installation via conda install.

before below: since I do not see you using --no-home -- try with that option -- may be you have some local numpy installed in some ~/.local/lib/python* and that interferes...?

BTW -- we have singularity images pre-built/reshared from http://datasets.datalad.org/?dir=/repronim/containers/images/nipy , so there is http://datasets.datalad.org/repronim/containers/images/nipy/nipy-heudiconv--1.0.0.sing .

In your case -- do you see the issue you are talking about if you just run smth like singularity run ${IMAGEDIR}/heudiconv-v1.0.0.sif --help or

$> singularity exec nipy-heudiconv--1.0.0.sing /opt/miniconda-py39_4.12.0/bin/python -c 'import pydicom;print("ok")'
ok

(just with your image)? if works ok, try also while adding all the bind mounts etc options -- may be somehow one of them adds side effect? try with the image I gave.

pcamach2 commented 11 months ago

Thank you for your suggestions!

The error is indeed thrown with running:

In your case -- do you see the issue you are talking about if you just run smth like singularity run --cleanenv --no-home ${IMAGEDIR}/heudiconv-11272023-master.sif --help

This happens for both the provided 1.0.0.sing image and those that I built as described earlier. However, adding --contain or --no-home to the Singularity run command resolves the error:

$ SINGULARITY_CACHEDIR=$CACHESING SINGULARITY_TMPDIR=$TMPSING \
singularity run --cleanenv --no-home --bind ${projDir}:/datain ${IMAGEDIR}/heudiconv-11272023-master..sif \
-d /datain/{subject}/{session}/*/SCANS/*/DICOM/*dcm -f /datain/${project}_heuristic.py \
-o /datain/bids/sourcedata -s ${sub} -ss ${ses} -c dcm2niix -b \
--minmeta --overwrite -g accession_number
INFO: Running heudiconv version 1.0.0.post6+g5374088 latest 1.0.0
INFO: Need to process 1 study sessions
INFO: PROCESSING STARTS: {'subject': 'SAY760', 'outdir': '/datain/bids/sourcedata/', 'session': 'B'}
INFO: Processing 11546 dicoms
INFO: Analyzing 11546 dicoms