Skip to content

MRD Upgrade tool#66

Draft
naegelejd wants to merge 5 commits into
mainfrom
naegelejd/stream-upgrader
Draft

MRD Upgrade tool#66
naegelejd wants to merge 5 commits into
mainfrom
naegelejd/stream-upgrader

Conversation

@naegelejd
Copy link
Copy Markdown
Collaborator

Introduces a new Python tool to upgrade MRD files to the current version of MRD.

Initially, it supports the upgrade path from v2.2.0 (previous) to v2.2.1 (current).

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a Python tool (mrd-upgrade) plus library API to upgrade existing MRD binary files from older schema versions (initially v2.2.0) to the current schema (v2.2.1). Also refactors how the MRD version string is propagated through builds (reading the top-level VERSION file directly instead of relying on the MRD_VERSION_STRING env var) and includes an unrelated refactor of mrd_to_ismrmrd.py.

Changes:

  • New upgrade tool: python/mrd/tools/{upgrade.py,_schema_registry.py,_v220_reader.py} with CLI entry point, version detection, v2.2.0 read-only serializers, and a chained upgrade dispatcher.
  • End-to-end test harness under test/upgrade/ (shell driver + v2.2.0 generator + verifier) and a just upgrade-test recipe.
  • Version-string plumbing reworked: setup.py, conda recipes, CMake, Matlab, justfile, Dockerfile, devcontainer and CI now derive the version from the VERSION file / _version.py; the MRD_VERSION_STRING env var is removed.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
UPGRADE.md Design/rationale document for the upgrade tool.
docs/python/upgrade.md User-facing docs for mrd-upgrade.
docs/.vitepress/config.mts Adds upgrade page to docs sidebar.
python/pyproject.toml Adds mrd-upgrade script entry; bumps ismrmrd dep.
python/setup.py Reads version from mrd/_version.py instead of env var.
python/mrd/_version.py Generated version literal (__version__ = "2.2.1").
python/mrd/tools/upgrade.py CLI + upgrade_mrd_file public API and chained-step dispatcher.
python/mrd/tools/_schema_registry.py Embedded v2.2.0 schema literal and identify_file_version.
python/mrd/tools/_v220_reader.py v2.2.0 read-only serializers producing v2.2.1 objects.
python/mrd/tools/mrd_to_ismrmrd.py Refactor to use constructor kwargs; unrelated to upgrade tool.
python/conda/meta.yaml, cpp/conda/meta.yaml Read version from VERSION via load_file_regex.
cpp/CMakeLists.txt Prefer PKG_VERSION then VERSION file; remove env-var path.
matlab/buildToolbox.m Read version from VERSION file.
docker/Dockerfile, docker/build-images.sh Drop MRD_VERSION_STRING build-arg; copy VERSION into image.
justfile Add upgrade-test; generate _version.py; drop env var.
environment.yml Add ismrmrd-python>=1.15.0; drop pip ismrmrd==1.14.2.
.readthedocs.yaml, .devcontainer/devcontainer.bashrc, .github/workflows/mrd_build.yml Remove env-var setup; fetch tags in CI for git archive v2.2.0.
test/upgrade/test-upgrade.sh End-to-end test driver.
test/upgrade/generate_v220.py Produces a v2.2.0 stream under the v2.2.0 codecs.
test/upgrade/verify_upgrade.py Validates the upgraded v2.2.1 file.
Comments suppressed due to low confidence (11)

python/mrd/tools/mrd_to_ismrmrd.py:107

  • Behavior change: previously the code only set meas.patientPosition if meas_info.patient_position in pos_map. Now pos_map[meas_info.patient_position] is accessed unconditionally, which will raise KeyError if a PatientPosition enum value is ever added to MRD but not yet mirrored in pos_map. Consider using pos_map.get(...) with a sensible default (as you did for trajectory at line 266) or restore the membership guard.
        meas = ismrmrd.xsd.measurementInformationType(
            patientPosition=pos_map[meas_info.patient_position]
        )

python/mrd/tools/mrd_to_ismrmrd.py:290

  • The previous code guarded all four attribute accesses (if par_img.acceleration_factor, plus per-field is not None checks). The new code unconditionally accesses par_img.acceleration_factor.kspace_encoding_step_1 and .kspace_encoding_step_2. If par_img.acceleration_factor is None (which can occur if the MRD producer omitted it even though the schema marks it required), this will raise AttributeError. Also, previously a None kspace_encoding_step_* was skipped; now None is forwarded into accelerationFactorType, which may not accept it.
            acc = ismrmrd.xsd.accelerationFactorType(
                kspace_encoding_step_1=par_img.acceleration_factor.kspace_encoding_step_1,
                kspace_encoding_step_2=par_img.acceleration_factor.kspace_encoding_step_2
            )
            parallel = ismrmrd.xsd.parallelImagingType(accelerationFactor=acc)

python/mrd/tools/mrd_to_ismrmrd.py:205

  • limits is now constructed and assigned to enc.encodingLimits unconditionally, even when enc_mrd.encoding_limits is falsy. Previously the field was only set when limits were actually present. This is likely benign but is a behavior change: the resulting ISMRMRD XML will now always contain an (empty) encodingLimits element. Confirm this is intended.
        limits = ismrmrd.xsd.encodingLimitsType()
        if enc_mrd.encoding_limits:

python/mrd/tools/mrd_to_ismrmrd.py:156

  • Behavior change: previously coilNumber / coilName were only assigned when non-None. Now they're passed in directly. If label.coil_number is None (the MRD type may permit it depending on producer behavior), the coilLabelType constructor may reject None for an integer field. Consider preserving the previous guarded assignment.
                coil = ismrmrd.xsd.coilLabelType(
                    coilNumber=label.coil_number,
                    coilName=label.coil_name
                )

python/mrd/tools/_schema_registry.py:57

  • identify_file_version reads the 4-byte binary format version with stream.read_view(4) but never validates it. BinaryProtocolReader.__init__ does check version != CURRENT_BINARY_FORMAT_VERSION. As a result, a file written with a future incompatible Yardl binary format could still be misidentified by schema string match without raising a clear error. Consider validating the format version here too (or document why it's intentionally skipped).
        _fmt_version = stream.read_view(4)
        schema = string_serializer.read(stream)

python/mrd/tools/upgrade.py:158

  • When --in-place is used, the upgraded file is created via tempfile.mkstemp (which creates the file with mode 0600) and then os.replaced over the source. This means an in-place upgrade will silently strip the source file's original permissions (e.g. world-readable scans become user-only). Consider copying the source's st_mode (and possibly st_uid/st_gid if running as root) to the temp file before the rename, or document this side effect.
        # Write to a temp file next to the source, then atomically replace.
        src_dir = os.path.dirname(os.path.abspath(src))
        fd, tmp_path = tempfile.mkstemp(dir=src_dir, suffix=".tmp")
        os.close(fd)
        try:
            upgrade_mrd_file(src, tmp_path)
            os.replace(tmp_path, src)

python/mrd/tools/upgrade.py:169

  • When args.output is not provided and --in-place is not used, the destination is computed as src + ".upgraded". If that file already exists it is silently overwritten. Consider checking os.path.exists(dst) and refusing (or requiring --force) to avoid clobbering an existing file.
        dst = args.output if args.output else src + ".upgraded"
        upgrade_mrd_file(src, dst)
        print(f"Upgraded {src!r} → {dst!r}")

test/upgrade/test-upgrade.sh:24

  • git archive v2.2.0 python/mrd/ requires the v2.2.0 tag to be present in the local clone. CI fetches with fetch-depth: 0 and fetch-tags: true (added in this PR), but developers running just upgrade-test on a shallow clone, or bash test/upgrade/test-upgrade.sh directly, will get a confusing fatal: not a valid object name error. Consider an upfront check (git rev-parse v2.2.0^{tag}) with a clearer message, or auto-fetching the tag if missing.
# Extract all v2.2.0 files from git.
git -C "$WORKSPACE" archive v2.2.0 python/mrd/ | tar -x --strip-components=2 -C "$V220_MOD"

test/upgrade/test-upgrade.sh:40

  • test-upgrade.sh relies on the side effect that git archive ... | tar -x --strip-components=2 -C "$V220_MOD" extracts the v2.2.0 tree into $V220_MOD (which is .../mrd_v220/mrd). Because the v2.2.0 tag predates this PR, the extracted tree won't contain mrd/tools/ — so from mrd.tools._schema_registry import identify_file_version (used in the inline Python snippet on lines 32-40) only works because the python3 interpreter that runs that snippet uses PYTHONPATH=${WORKSPACE}/python:... (line 14), where tools/ exists. This is subtle and easy to break later if PYTHONPATH ordering or the inline snippet's working dir changes. A short comment explaining which PYTHONPATH "wins" for each invocation would make this more robust.
PYTHONPATH="$WORKDIR/mrd_v220:${PYTHONPATH:-}" python3 "$TESTDIR/generate_v220.py" "$V220"

echo "  Verifying source file is detected as v2.2.0 ..."
detected=$(python3 - "$V220" <<'EOF'
import sys
from mrd.tools._schema_registry import identify_file_version
v = identify_file_version(sys.argv[1])
if v != "2.2.0":
    raise RuntimeError(f"Expected 2.2.0, got {v!r}")
print(v)
EOF
)

python/mrd/tools/_schema_registry.py:56

  • Linting/static-analysis nit: _fmt_version is read and stored but never used. Either drop the assignment (just stream.read_view(4) for side-effect of advancing the stream) or validate it against CURRENT_BINARY_FORMAT_VERSION from _binary.py. As written, some lint configs will flag this as an unused variable.
        _fmt_version = stream.read_view(4)

test/upgrade/test-upgrade.sh:64

  • The error-case test only covers "already-current file". It doesn't cover the other two ValueError paths in upgrade_mrd_file: (a) unrecognised schema (file with magic bytes but unknown schema string) and (b) a supported-version-but-broken-chain failure mode. Given how easy these are to exercise (a single malformed temp file), adding even one assertion for the "unrecognised" branch would catch a meaningful regression.
echo "  Testing error cases ..."
python3 - "$V221" <<'EOF'
import sys
from mrd.tools.upgrade import upgrade_mrd_file
try:
    upgrade_mrd_file(sys.argv[1], sys.argv[1] + ".should_not_exist")
    raise RuntimeError("Expected ValueError for already-current file")
except ValueError:
    pass  # expected
EOF

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +37 to +38
exp = ismrmrd.xsd.experimentalConditionsType(H1resonanceFrequency_Hz=mrd_header.experimental_conditions.h1resonance_frequency_hz)
header = ismrmrd.xsd.ismrmrdHeader(experimentalConditions=exp)
_PAST_SCHEMA_TO_VERSION: dict[str, str] = {v: k for k, v in _PAST_SCHEMAS.items()}


def identify_file_version(path: str) -> str | None:
Comment on lines +19 to +29
echo " Building mrd v2.2.0 module from git tag ..."
V220_MOD="$WORKDIR/mrd_v220/mrd"
mkdir -p "$V220_MOD"

# Extract all v2.2.0 files from git.
git -C "$WORKSPACE" archive v2.2.0 python/mrd/ | tar -x --strip-components=2 -C "$V220_MOD"

echo " Generating v2.2.0 stream ..."
V220="$WORKDIR/test_v220.mrd"
# Prepend the v2.2.0 module dir so it shadows the current mrd-python install.
PYTHONPATH="$WORKDIR/mrd_v220:${PYTHONPATH:-}" python3 "$TESTDIR/generate_v220.py" "$V220"
# Frozen schema literals for past versions
# ---------------------------------------------------------------------------

_V220_SCHEMA = r"""{"protocol":{"name":"Mrd","sequence":[{"name":"header","type":[null,"Mrd.Header"]},{"name":"data","type":{"stream":{"items":"Mrd.StreamItem"}}}]},"types":[{"name":"AccelerationFactorType","fields":[{"name":"kspaceEncodingStep1","type":"uint32"},{"name":"kspaceEncodingStep2","type":"uint32"}]},{"name":"Acquisition","fields":[{"name":"head","type":"Mrd.AcquisitionHeader"},{"name":"data","type":"Mrd.AcquisitionData"},{"name":"trajectory","type":"Mrd.TrajectoryData"}]},{"name":"Acquisition","fields":[{"name":"head","type":"Mrd.AcquisitionHeader"},{"name":"data","type":"Mrd.AcquisitionData"},{"name":"trajectory","type":"Mrd.TrajectoryData"}]},{"name":"AcquisitionBucket","fields":[{"name":"data","type":{"vector":{"items":"Mrd.Acquisition"}}},{"name":"ref","type":{"vector":{"items":"Mrd.Acquisition"}}},{"name":"datastats","type":{"vector":{"items":"Mrd.EncodingLimitsType"}}},{"name":"refstats","type":{"vector":{"items":"Mrd.EncodingLimitsType"}}},{"name":"waveforms","type":{"vector":{"items":"Mrd.WaveformUint32"}}}]},{"name":"AcquisitionData","type":{"array":{"items":"complexfloat32","dimensions":[{"name":"coils"},{"name":"samples"}]}}},{"name":"AcquisitionFlags","base":"uint64","values":[{"symbol":"firstInEncodeStep1","value":1},{"symbol":"lastInEncodeStep1","value":2},{"symbol":"firstInEncodeStep2","value":4},{"symbol":"lastInEncodeStep2","value":8},{"symbol":"firstInAverage","value":16},{"symbol":"lastInAverage","value":32},{"symbol":"firstInSlice","value":64},{"symbol":"lastInSlice","value":128},{"symbol":"firstInContrast","value":256},{"symbol":"lastInContrast","value":512},{"symbol":"firstInPhase","value":1024},{"symbol":"lastInPhase","value":2048},{"symbol":"firstInRepetition","value":4096},{"symbol":"lastInRepetition","value":8192},{"symbol":"firstInSet","value":16384},{"symbol":"lastInSet","value":32768},{"symbol":"firstInSegment","value":65536},{"symbol":"lastInSegment","value":131072},{"symbol":"isNoiseMeasurement","value":262144},{"symbol":"isParallelCalibration","value":524288},{"symbol":"isParallelCalibrationAndImaging","value":1048576},{"symbol":"isReverse","value":2097152},{"symbol":"isNavigationData","value":4194304},{"symbol":"isPhasecorrData","value":8388608},{"symbol":"lastInMeasurement","value":16777216},{"symbol":"isHpfeedbackData","value":33554432},{"symbol":"isDummyscanData","value":67108864},{"symbol":"isRtfeedbackData","value":134217728},{"symbol":"isSurfacecoilcorrectionscanData","value":268435456},{"symbol":"isPhaseStabilizationReference","value":536870912},{"symbol":"isPhaseStabilization","value":1073741824}]},{"name":"AcquisitionHeader","fields":[{"name":"flags","type":"Mrd.AcquisitionFlags"},{"name":"idx","type":"Mrd.EncodingCounters"},{"name":"measurementUid","type":"uint32"},{"name":"scanCounter","type":[null,"uint32"]},{"name":"acquisitionTimeStampNs","type":[null,"uint64"]},{"name":"physiologyTimeStampNs","type":{"vector":{"items":"uint64"}}},{"name":"channelOrder","type":{"vector":{"items":"uint32"}}},{"name":"discardPre","type":[null,"uint32"]},{"name":"discardPost","type":[null,"uint32"]},{"name":"centerSample","type":[null,"uint32"]},{"name":"encodingSpaceRef","type":[null,"uint32"]},{"name":"sampleTimeNs","type":[null,"uint64"]},{"name":"position","type":{"array":{"items":"float32","dimensions":[{"length":3}]}}},{"name":"readDir","type":{"array":{"items":"float32","dimensions":[{"length":3}]}}},{"name":"phaseDir","type":{"array":{"items":"float32","dimensions":[{"length":3}]}}},{"name":"sliceDir","type":{"array":{"items":"float32","dimensions":[{"length":3}]}}},{"name":"patientTablePosition","type":{"array":{"items":"float32","dimensions":[{"length":3}]}}},{"name":"userInt","type":{"vector":{"items":"int32"}}},{"name":"userFloat","type":{"vector":{"items":"float32"}}}]},{"name":"AcquisitionSystemInformationType","fields":[{"name":"systemVendor","type":[null,"string"]},{"name":"systemModel","type":[null,"string"]},{"name":"systemFieldStrengthT","type":[null,"float32"]},{"name":"relativeReceiverNoiseBandwidth","type":[null,"float32"]},{"name":"receiverChannels","type":[null,"uint32"]},{"name":"coilLabel","type":{"vector":{"items":"Mrd.CoilLabelType"}}},{"name":"institutionName","type":[null,"string"]},{"name":"stationName","type":[null,"string"]},{"name":"deviceID","type":[null,"string"]},{"name":"deviceSerialNumber","type":[null,"string"]}]},{"name":"Array","typeParameters":["T"],"type":{"array":{"items":"T"}}},{"name":"ArrayComplexFloat","type":{"name":"Mrd.Array","typeArguments":["complexfloat32"]}},{"name":"Calibration","values":[{"symbol":"separable2D","value":0},{"symbol":"full3D","value":1},{"symbol":"other","value":2}]},{"name":"CalibrationMode","values":[{"symbol":"noacceleration","value":0},{"symbol":"embedded","value":1},{"symbol":"interleaved","value":2},{"symbol":"separate","value":3},{"symbol":"external","value":4},{"symbol":"other","value":5}]},{"name":"CoilLabelType","fields":[{"name":"coilNumber","type":"uint32"},{"name":"coilName","type":"string"}]},{"name":"DiffusionDimension","values":[{"symbol":"average","value":0},{"symbol":"contrast","value":1},{"symbol":"phase","value":2},{"symbol":"repetition","value":3},{"symbol":"set","value":4},{"symbol":"segment","value":5},{"symbol":"user0","value":6},{"symbol":"user1","value":7},{"symbol":"user2","value":8},{"symbol":"user3","value":9},{"symbol":"user4","value":10},{"symbol":"user5","value":11},{"symbol":"user6","value":12},{"symbol":"user7","value":13}]},{"name":"DiffusionType","fields":[{"name":"gradientDirection","type":"Mrd.GradientDirectionType"},{"name":"bvalue","type":"float32"}]},{"name":"EncodingCounters","fields":[{"name":"kspaceEncodeStep1","type":[null,"uint32"]},{"name":"kspaceEncodeStep2","type":[null,"uint32"]},{"name":"average","type":[null,"uint32"]},{"name":"slice","type":[null,"uint32"]},{"name":"contrast","type":[null,"uint32"]},{"name":"phase","type":[null,"uint32"]},{"name":"repetition","type":[null,"uint32"]},{"name":"set","type":[null,"uint32"]},{"name":"segment","type":[null,"uint32"]},{"name":"user","type":{"vector":{"items":"uint32"}}}]},{"name":"EncodingLimitsType","fields":[{"name":"kspaceEncodingStep0","type":[null,"Mrd.LimitType"]},{"name":"kspaceEncodingStep1","type":[null,"Mrd.LimitType"]},{"name":"kspaceEncodingStep2","type":[null,"Mrd.LimitType"]},{"name":"average","type":[null,"Mrd.LimitType"]},{"name":"slice","type":[null,"Mrd.LimitType"]},{"name":"contrast","type":[null,"Mrd.LimitType"]},{"name":"phase","type":[null,"Mrd.LimitType"]},{"name":"repetition","type":[null,"Mrd.LimitType"]},{"name":"set","type":[null,"Mrd.LimitType"]},{"name":"segment","type":[null,"Mrd.LimitType"]},{"name":"user0","type":[null,"Mrd.LimitType"]},{"name":"user1","type":[null,"Mrd.LimitType"]},{"name":"user2","type":[null,"Mrd.LimitType"]},{"name":"user3","type":[null,"Mrd.LimitType"]},{"name":"user4","type":[null,"Mrd.LimitType"]},{"name":"user5","type":[null,"Mrd.LimitType"]},{"name":"user6","type":[null,"Mrd.LimitType"]},{"name":"user7","type":[null,"Mrd.LimitType"]}]},{"name":"EncodingSpaceType","fields":[{"name":"matrixSize","type":"Mrd.MatrixSizeType"},{"name":"fieldOfViewMm","type":"Mrd.FieldOfViewMm"}]},{"name":"EncodingType","fields":[{"name":"encodedSpace","type":"Mrd.EncodingSpaceType"},{"name":"reconSpace","type":"Mrd.EncodingSpaceType"},{"name":"encodingLimits","type":"Mrd.EncodingLimitsType"},{"name":"trajectory","type":"Mrd.Trajectory"},{"name":"trajectoryDescription","type":[null,"Mrd.TrajectoryDescriptionType"]},{"name":"parallelImaging","type":[null,"Mrd.ParallelImagingType"]},{"name":"echoTrainLength","type":[null,"int64"]}]},{"name":"ExperimentalConditionsType","fields":[{"name":"h1resonanceFrequencyHz","type":"int64"}]},{"name":"FieldOfViewMm","fields":[{"name":"x","type":"float32"},{"name":"y","type":"float32"},{"name":"z","type":"float32"}]},{"name":"GradientDirectionType","fields":[{"name":"rl","type":"float32"},{"name":"ap","type":"float32"},{"name":"fh","type":"float32"}]},{"name":"Header","fields":[{"name":"version","type":[null,"int64"]},{"name":"subjectInformation","type":[null,"Mrd.SubjectInformationType"]},{"name":"studyInformation","type":[null,"Mrd.StudyInformationType"]},{"name":"measurementInformation","type":[null,"Mrd.MeasurementInformationType"]},{"name":"acquisitionSystemInformation","type":[null,"Mrd.AcquisitionSystemInformationType"]},{"name":"experimentalConditions","type":"Mrd.ExperimentalConditionsType"},{"name":"encoding","type":{"vector":{"items":"Mrd.EncodingType"}}},{"name":"sequenceParameters","type":[null,"Mrd.SequenceParametersType"]},{"name":"userParameters","type":[null,"Mrd.UserParametersType"]},{"name":"waveformInformation","type":{"vector":{"items":"Mrd.WaveformInformationType"}}}]},{"name":"Image","typeParameters":["T"],"fields":[{"name":"head","type":"Mrd.ImageHeader"},{"name":"data","type":{"name":"Mrd.ImageData","typeArguments":["T"]}},{"name":"meta","type":"Mrd.ImageMeta"}]},{"name":"ImageArray","fields":[{"name":"data","type":{"array":{"items":"complexfloat32","dimensions":[{"name":"loc"},{"name":"s"},{"name":"n"},{"name":"channel"},{"name":"z"},{"name":"y"},{"name":"x"}]}}},{"name":"headers","type":{"array":{"items":"Mrd.ImageHeader","dimensions":[{"name":"loc"},{"name":"s"},{"name":"n"}]}}},{"name":"meta","type":{"array":{"items":"Mrd.ImageMeta","dimensions":[{"name":"loc"},{"name":"s"},{"name":"n"}]}}},{"name":"waveforms","type":{"vector":{"items":"Mrd.WaveformUint32"}}}]},{"name":"ImageComplexDouble","type":{"name":"Mrd.Image","typeArguments":["complexfloat64"]}},{"name":"ImageComplexFloat","type":{"name":"Mrd.Image","typeArguments":["complexfloat32"]}},{"name":"ImageData","typeParameters":["Y"],"type":{"array":{"items":"Y","dimensions":[{"name":"channel"},{"name":"z"},{"name":"y"},{"name":"x"}]}}},{"name":"ImageDouble","type":{"name":"Mrd.Image","typeArguments":["float64"]}},{"name":"ImageFlags","base":"uint64","values":[{"symbol":"isNavigationData","value":1},{"symbol":"firstInAverage","value":16},{"symbol":"lastInAverage","value":32},{"symbol":"firstInSlice","value":64},{"symbol":"lastInSlice","value":128},{"symbol":"firstInContrast","value":256},{"symbol":"lastInContrast","value":512},{"symbol":"firstInPhase","value":1024},{"symbol":"lastInPhase","value":2048},{"symbol":"firstInRepetition","value":4096},{"symbol":"lastInRepetition","value":8192},{"symbol":"firstInSet","value":16384},{"symbol":"lastInSet","value":32768}]},{"name":"ImageFloat","type":{"name":"Mrd.Image","typeArguments":["float32"]}},{"name":"ImageHeader","fields":[{"name":"flags","type":"Mrd.ImageFlags"},{"name":"measurementUid","type":"uint32"},{"name":"fieldOfView","type":{"array":{"items":"float32","dimensions":[{"length":3}]}}},{"name":"position","type":{"array":{"items":"float32","dimensions":[{"length":3}]}}},{"name":"colDir","type":{"array":{"items":"float32","dimensions":[{"length":3}]}}},{"name":"lineDir","type":{"array":{"items":"float32","dimensions":[{"length":3}]}}},{"name":"sliceDir","type":{"array":{"items":"float32","dimensions":[{"length":3}]}}},{"name":"patientTablePosition","type":{"array":{"items":"float32","dimensions":[{"length":3}]}}},{"name":"average","type":[null,"uint32"]},{"name":"slice","type":[null,"uint32"]},{"name":"contrast","type":[null,"uint32"]},{"name":"phase","type":[null,"uint32"]},{"name":"repetition","type":[null,"uint32"]},{"name":"set","type":[null,"uint32"]},{"name":"acquisitionTimeStampNs","type":[null,"uint64"]},{"name":"physiologyTimeStampNs","type":{"vector":{"items":"uint64"}}},{"name":"imageType","type":"Mrd.ImageType"},{"name":"imageIndex","type":[null,"uint32"]},{"name":"imageSeriesIndex","type":[null,"uint32"]},{"name":"userInt","type":{"vector":{"items":"int32"}}},{"name":"userFloat","type":{"vector":{"items":"float32"}}}]},{"name":"ImageInt16","type":{"name":"Mrd.Image","typeArguments":["int16"]}},{"name":"ImageInt32","type":{"name":"Mrd.Image","typeArguments":["int32"]}},{"name":"ImageMeta","type":{"map":{"keys":"string","values":{"vector":{"items":"Mrd.ImageMetaValue"}}}}},{"name":"ImageMetaValue","type":[{"tag":"string","type":"string"},{"tag":"int64","type":"int64"},{"tag":"float64","type":"float64"}]},{"name":"ImageType","values":[{"symbol":"magnitude","value":1},{"symbol":"phase","value":2},{"symbol":"real","value":3},{"symbol":"imag","value":4},{"symbol":"complex","value":5}]},{"name":"ImageUint16","type":{"name":"Mrd.Image","typeArguments":["uint16"]}},{"name":"ImageUint32","type":{"name":"Mrd.Image","typeArguments":["uint32"]}},{"name":"InterleavingDimension","values":[{"symbol":"phase","value":0},{"symbol":"repetition","value":1},{"symbol":"contrast","value":2},{"symbol":"average","value":3},{"symbol":"other","value":4}]},{"name":"LimitType","fields":[{"name":"minimum","type":"uint32"},{"name":"maximum","type":"uint32"},{"name":"center","type":"uint32"}]},{"name":"MatrixSizeType","fields":[{"name":"x","type":"uint32"},{"name":"y","type":"uint32"},{"name":"z","type":"uint32"}]},{"name":"MeasurementDependencyType","fields":[{"name":"dependencyType","type":"string"},{"name":"measurementID","type":"string"}]},{"name":"MeasurementInformationType","fields":[{"name":"measurementID","type":[null,"string"]},{"name":"seriesDate","type":[null,"date"]},{"name":"seriesTime","type":[null,"time"]},{"name":"patientPosition","type":"Mrd.PatientPosition"},{"name":"relativeTablePosition","type":[null,"Mrd.ThreeDimensionalFloat"]},{"name":"initialSeriesNumber","type":[null,"int64"]},{"name":"protocolName","type":[null,"string"]},{"name":"sequenceName","type":[null,"string"]},{"name":"seriesDescription","type":[null,"string"]},{"name":"measurementDependency","type":{"vector":{"items":"Mrd.MeasurementDependencyType"}}},{"name":"seriesInstanceUIDRoot","type":[null,"string"]},{"name":"frameOfReferenceUID","type":[null,"string"]},{"name":"referencedImageSequence","type":[null,"Mrd.ReferencedImageSequenceType"]}]},{"name":"MultibandSpacingType","fields":[{"name":"dZ","type":{"vector":{"items":"float32"}}}]},{"name":"MultibandType","fields":[{"name":"spacing","type":{"vector":{"items":"Mrd.MultibandSpacingType"}}},{"name":"deltaKz","type":"float32"},{"name":"multibandFactor","type":"uint32"},{"name":"calibration","type":"Mrd.Calibration"},{"name":"calibrationEncoding","type":"uint64"}]},{"name":"ParallelImagingType","fields":[{"name":"accelerationFactor","type":"Mrd.AccelerationFactorType"},{"name":"calibrationMode","type":[null,"Mrd.CalibrationMode"]},{"name":"interleavingDimension","type":[null,"Mrd.InterleavingDimension"]},{"name":"multiband","type":[null,"Mrd.MultibandType"]}]},{"name":"PatientGender","values":[{"symbol":"m","value":0},{"symbol":"f","value":1},{"symbol":"o","value":2}]},{"name":"PatientPosition","values":[{"symbol":"hFP","value":0},{"symbol":"hFS","value":1},{"symbol":"hFDR","value":2},{"symbol":"hFDL","value":3},{"symbol":"fFP","value":4},{"symbol":"fFS","value":5},{"symbol":"fFDR","value":6},{"symbol":"fFDL","value":7}]},{"name":"ReconAssembly","fields":[{"name":"data","type":"Mrd.ReconBuffer"},{"name":"ref","type":[null,"Mrd.ReconBuffer"]}]},{"name":"ReconBuffer","fields":[{"name":"data","type":{"array":{"items":"complexfloat32","dimensions":[{"name":"loc"},{"name":"s"},{"name":"n"},{"name":"chan"},{"name":"e2"},{"name":"e1"},{"name":"e0"}]}}},{"name":"trajectory","type":{"array":{"items":"float32","dimensions":[{"name":"loc"},{"name":"s"},{"name":"n"},{"name":"e2"},{"name":"e1"},{"name":"basis"},{"name":"samples"}]}}},{"name":"density","type":[null,{"array":{"items":"float32","dimensions":[{"name":"loc"},{"name":"s"},{"name":"n"},{"name":"e2"},{"name":"e1"},{"name":"e0"}]}}]},{"name":"headers","type":{"array":{"items":"Mrd.AcquisitionHeader","dimensions":[{"name":"loc"},{"name":"s"},{"name":"n"},{"name":"e2"},{"name":"e1"}]}}},{"name":"sampling","type":"Mrd.SamplingDescription"}]},{"name":"ReconData","fields":[{"name":"buffers","type":{"vector":{"items":"Mrd.ReconAssembly"}}}]},{"name":"ReferencedImageSequenceType","fields":[{"name":"referencedSOPInstanceUID","type":{"vector":{"items":"string"}}}]},{"name":"SamplingDescription","fields":[{"name":"encodedFOV","type":"Mrd.FieldOfViewMm"},{"name":"reconFOV","type":"Mrd.FieldOfViewMm"},{"name":"encodedMatrix","type":"Mrd.MatrixSizeType"},{"name":"reconMatrix","type":"Mrd.MatrixSizeType"},{"name":"samplingLimits","type":"Mrd.SamplingLimits"}]},{"name":"SamplingLimits","fields":[{"name":"kspaceEncodingStep0","type":"Mrd.LimitType"},{"name":"kspaceEncodingStep1","type":"Mrd.LimitType"},{"name":"kspaceEncodingStep2","type":"Mrd.LimitType"}]},{"name":"SequenceParametersType","fields":[{"name":"tR","type":{"vector":{"items":"float32"}}},{"name":"tE","type":{"vector":{"items":"float32"}}},{"name":"tI","type":{"vector":{"items":"float32"}}},{"name":"flipAngleDeg","type":{"vector":{"items":"float32"}}},{"name":"sequenceType","type":[null,"string"]},{"name":"echoSpacing","type":{"vector":{"items":"float32"}}},{"name":"diffusionDimension","type":[null,"Mrd.DiffusionDimension"]},{"name":"diffusion","type":{"vector":{"items":"Mrd.DiffusionType"}}},{"name":"diffusionScheme","type":[null,"string"]}]},{"name":"StreamItem","type":[{"tag":"Acquisition","type":"Mrd.Acquisition"},{"tag":"WaveformUint32","type":"Mrd.WaveformUint32"},{"tag":"ImageUint16","type":"Mrd.ImageUint16"},{"tag":"ImageInt16","type":"Mrd.ImageInt16"},{"tag":"ImageUint32","type":"Mrd.ImageUint32"},{"tag":"ImageInt32","type":"Mrd.ImageInt32"},{"tag":"ImageFloat","type":"Mrd.ImageFloat"},{"tag":"ImageDouble","type":"Mrd.ImageDouble"},{"tag":"ImageComplexFloat","type":"Mrd.ImageComplexFloat"},{"tag":"ImageComplexDouble","type":"Mrd.ImageComplexDouble"},{"tag":"AcquisitionBucket","type":"Mrd.AcquisitionBucket"},{"tag":"ReconData","type":"Mrd.ReconData"},{"tag":"ArrayComplexFloat","type":"Mrd.ArrayComplexFloat"},{"tag":"ImageArray","type":"Mrd.ImageArray"}]},{"name":"StudyInformationType","fields":[{"name":"studyDate","type":[null,"date"]},{"name":"studyTime","type":[null,"time"]},{"name":"studyID","type":[null,"string"]},{"name":"accessionNumber","type":[null,"int64"]},{"name":"referringPhysicianName","type":[null,"string"]},{"name":"studyDescription","type":[null,"string"]},{"name":"studyInstanceUID","type":[null,"string"]},{"name":"bodyPartExamined","type":[null,"string"]}]},{"name":"SubjectInformationType","fields":[{"name":"patientName","type":[null,"string"]},{"name":"patientWeightKg","type":[null,"float32"]},{"name":"patientHeightM","type":[null,"float32"]},{"name":"patientID","type":[null,"string"]},{"name":"patientBirthdate","type":[null,"date"]},{"name":"patientGender","type":[null,"Mrd.PatientGender"]}]},{"name":"ThreeDimensionalFloat","fields":[{"name":"x","type":"float32"},{"name":"y","type":"float32"},{"name":"z","type":"float32"}]},{"name":"Trajectory","values":[{"symbol":"cartesian","value":0},{"symbol":"epi","value":1},{"symbol":"radial","value":2},{"symbol":"goldenangle","value":3},{"symbol":"spiral","value":4},{"symbol":"other","value":5}]},{"name":"TrajectoryData","type":{"array":{"items":"float32","dimensions":[{"name":"basis"},{"name":"samples"}]}}},{"name":"TrajectoryDescriptionType","fields":[{"name":"identifier","type":"string"},{"name":"userParameterLong","type":{"vector":{"items":"Mrd.UserParameterLongType"}}},{"name":"userParameterDouble","type":{"vector":{"items":"Mrd.UserParameterDoubleType"}}},{"name":"userParameterString","type":{"vector":{"items":"Mrd.UserParameterStringType"}}},{"name":"comment","type":[null,"string"]}]},{"name":"UserParameterBase64Type","fields":[{"name":"name","type":"string"},{"name":"value","type":"string"}]},{"name":"UserParameterDoubleType","fields":[{"name":"name","type":"string"},{"name":"value","type":"float64"}]},{"name":"UserParameterLongType","fields":[{"name":"name","type":"string"},{"name":"value","type":"int64"}]},{"name":"UserParameterStringType","fields":[{"name":"name","type":"string"},{"name":"value","type":"string"}]},{"name":"UserParametersType","fields":[{"name":"userParameterLong","type":{"vector":{"items":"Mrd.UserParameterLongType"}}},{"name":"userParameterDouble","type":{"vector":{"items":"Mrd.UserParameterDoubleType"}}},{"name":"userParameterString","type":{"vector":{"items":"Mrd.UserParameterStringType"}}},{"name":"userParameterBase64","type":{"vector":{"items":"Mrd.UserParameterBase64Type"}}}]},{"name":"Waveform","typeParameters":["T"],"fields":[{"name":"flags","type":"uint64"},{"name":"measurementUid","type":"uint32"},{"name":"scanCounter","type":"uint32"},{"name":"timeStampNs","type":"uint64"},{"name":"sampleTimeNs","type":"uint64"},{"name":"waveformId","type":"uint32"},{"name":"data","type":{"name":"Mrd.WaveformSamples","typeArguments":["T"]}}]},{"name":"WaveformInformationType","fields":[{"name":"waveformName","type":"string"},{"name":"waveformType","type":"Mrd.WaveformType"},{"name":"userParameters","type":"Mrd.UserParametersType"}]},{"name":"WaveformSamples","typeParameters":["T"],"type":{"array":{"items":"T","dimensions":[{"name":"channels"},{"name":"samples"}]}}},{"name":"WaveformType","values":[{"symbol":"ecg","value":0},{"symbol":"pulse","value":1},{"symbol":"respiratory","value":2},{"symbol":"trigger","value":3},{"symbol":"gradientwaveform","value":4},{"symbol":"other","value":5}]},{"name":"WaveformUint32","type":{"name":"Mrd.Waveform","typeArguments":["uint32"]}},{"name":"WaveformUint32","type":{"name":"Mrd.Waveform","typeArguments":["uint32"]}}]}"""
Comment on lines +34 to +38
with V220MrdReader(src) as reader:
header = reader.read_header()
with BinaryMrdWriter(dst) as writer:
writer.write_header(header)
writer.write_data(reader.read_data())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants