mrd-python 2.0.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mrd/protocols.py ADDED
@@ -0,0 +1,180 @@
1
+ # This file was generated by the "yardl" tool. DO NOT EDIT.
2
+
3
+ # pyright: reportUnusedImport=false
4
+
5
+ import abc
6
+ import collections.abc
7
+ import datetime
8
+ import typing
9
+
10
+ import numpy as np
11
+ import numpy.typing as npt
12
+
13
+ from .types import *
14
+ from .yardl_types import ProtocolError
15
+ from . import yardl_types as yardl
16
+
17
+ class MrdWriterBase(abc.ABC):
18
+ """Abstract writer for the Mrd protocol."""
19
+
20
+
21
+ def __init__(self) -> None:
22
+ self._state = 0
23
+
24
+ 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":"flags","type":"Mrd.AcquisitionFlags"},{"name":"idx","type":"Mrd.EncodingCounters"},{"name":"measurementUid","type":"uint32"},{"name":"scanCounter","type":[null,"uint32"]},{"name":"acquisitionTimeStamp","type":[null,"uint32"]},{"name":"physiologyTimeStamp","type":{"vector":{"items":"uint32"}}},{"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":"sampleTimeUs","type":[null,"float32"]},{"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":"data","type":"Mrd.AcquisitionData"},{"name":"trajectory","type":"Mrd.TrajectoryData"}]},{"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":"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":"Calibration","values":[{"symbol":"separable2D","value":0},{"symbol":"full3D","value":1},{"symbol":"other","value":2}]},{"name":"CalibrationMode","values":[{"symbol":"embedded","value":0},{"symbol":"interleaved","value":1},{"symbol":"separate","value":2},{"symbol":"external","value":3},{"symbol":"other","value":4}]},{"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":"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":"acquisitionTimeStamp","type":[null,"uint32"]},{"name":"physiologyTimeStamp","type":{"vector":{"items":"uint32"}}},{"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":"data","type":{"name":"Mrd.ImageData","typeArguments":["T"]}},{"name":"meta","type":{"map":{"keys":"string","values":{"vector":{"items":"string"}}}}}]},{"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":"ImageInt","type":{"name":"Mrd.Image","typeArguments":["int32"]}},{"name":"ImageInt16","type":{"name":"Mrd.Image","typeArguments":["int16"]}},{"name":"ImageType","values":[{"symbol":"magnitude","value":1},{"symbol":"phase","value":2},{"symbol":"real","value":3},{"symbol":"imag","value":4},{"symbol":"complex","value":5}]},{"name":"ImageUint","type":{"name":"Mrd.Image","typeArguments":["uint32"]}},{"name":"ImageUint16","type":{"name":"Mrd.Image","typeArguments":["uint16"]}},{"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":"ReferencedImageSequenceType","fields":[{"name":"referencedSOPInstanceUID","type":{"vector":{"items":"string"}}}]},{"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":"ImageUint","type":"Mrd.ImageUint"},{"tag":"ImageInt","type":"Mrd.ImageInt"},{"tag":"ImageFloat","type":"Mrd.ImageFloat"},{"tag":"ImageDouble","type":"Mrd.ImageDouble"},{"tag":"ImageComplexFloat","type":"Mrd.ImageComplexFloat"},{"tag":"ImageComplexDouble","type":"Mrd.ImageComplexDouble"}]},{"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":"timeStamp","type":"uint32"},{"name":"sampleTimeUs","type":"float32"},{"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"]}}]}"""
25
+
26
+ def close(self) -> None:
27
+ if self._state == 3:
28
+ try:
29
+ self._end_stream()
30
+ return
31
+ finally:
32
+ self._close()
33
+ self._close()
34
+ if self._state != 4:
35
+ expected_method = self._state_to_method_name((self._state + 1) & ~1)
36
+ raise ProtocolError(f"Protocol writer closed before all steps were called. Expected to call to '{expected_method}'.")
37
+
38
+ def __enter__(self):
39
+ return self
40
+
41
+ def __exit__(self, exc_type: typing.Optional[type[BaseException]], exc: typing.Optional[BaseException], traceback: object) -> None:
42
+ try:
43
+ self.close()
44
+ except Exception as e:
45
+ if exc is None:
46
+ raise e
47
+
48
+ def write_header(self, value: typing.Optional[Header]) -> None:
49
+ """Ordinal 0"""
50
+
51
+ if self._state != 0:
52
+ self._raise_unexpected_state(0)
53
+
54
+ self._write_header(value)
55
+ self._state = 2
56
+
57
+ def write_data(self, value: collections.abc.Iterable[StreamItem]) -> None:
58
+ """Ordinal 1"""
59
+
60
+ if self._state & ~1 != 2:
61
+ self._raise_unexpected_state(2)
62
+
63
+ self._write_data(value)
64
+ self._state = 3
65
+
66
+ @abc.abstractmethod
67
+ def _write_header(self, value: typing.Optional[Header]) -> None:
68
+ raise NotImplementedError()
69
+
70
+ @abc.abstractmethod
71
+ def _write_data(self, value: collections.abc.Iterable[StreamItem]) -> None:
72
+ raise NotImplementedError()
73
+
74
+ @abc.abstractmethod
75
+ def _close(self) -> None:
76
+ pass
77
+
78
+ @abc.abstractmethod
79
+ def _end_stream(self) -> None:
80
+ pass
81
+
82
+ def _raise_unexpected_state(self, actual: int) -> None:
83
+ expected_method = self._state_to_method_name(self._state)
84
+ actual_method = self._state_to_method_name(actual)
85
+ raise ProtocolError(f"Expected to call to '{expected_method}' but received call to '{actual_method}'.")
86
+
87
+ def _state_to_method_name(self, state: int) -> str:
88
+ if state == 0:
89
+ return 'write_header'
90
+ if state == 2:
91
+ return 'write_data'
92
+ return "<unknown>"
93
+
94
+ class MrdReaderBase(abc.ABC):
95
+ """Abstract reader for the Mrd protocol."""
96
+
97
+
98
+ def __init__(self) -> None:
99
+ self._state = 0
100
+
101
+ def close(self) -> None:
102
+ self._close()
103
+ if self._state != 4:
104
+ if self._state % 2 == 1:
105
+ previous_method = self._state_to_method_name(self._state - 1)
106
+ raise ProtocolError(f"Protocol reader closed before all data was consumed. The iterable returned by '{previous_method}' was not fully consumed.")
107
+ else:
108
+ expected_method = self._state_to_method_name(self._state)
109
+ raise ProtocolError(f"Protocol reader closed before all data was consumed. Expected call to '{expected_method}'.")
110
+
111
+
112
+ schema = MrdWriterBase.schema
113
+
114
+ def __enter__(self):
115
+ return self
116
+
117
+ def __exit__(self, exc_type: typing.Optional[type[BaseException]], exc: typing.Optional[BaseException], traceback: object) -> None:
118
+ try:
119
+ self.close()
120
+ except Exception as e:
121
+ if exc is None:
122
+ raise e
123
+
124
+ @abc.abstractmethod
125
+ def _close(self) -> None:
126
+ raise NotImplementedError()
127
+
128
+ def read_header(self) -> typing.Optional[Header]:
129
+ """Ordinal 0"""
130
+
131
+ if self._state != 0:
132
+ self._raise_unexpected_state(0)
133
+
134
+ value = self._read_header()
135
+ self._state = 2
136
+ return value
137
+
138
+ def read_data(self) -> collections.abc.Iterable[StreamItem]:
139
+ """Ordinal 1"""
140
+
141
+ if self._state != 2:
142
+ self._raise_unexpected_state(2)
143
+
144
+ value = self._read_data()
145
+ self._state = 3
146
+ return self._wrap_iterable(value, 4)
147
+
148
+ def copy_to(self, writer: MrdWriterBase) -> None:
149
+ writer.write_header(self.read_header())
150
+ writer.write_data(self.read_data())
151
+
152
+ @abc.abstractmethod
153
+ def _read_header(self) -> typing.Optional[Header]:
154
+ raise NotImplementedError()
155
+
156
+ @abc.abstractmethod
157
+ def _read_data(self) -> collections.abc.Iterable[StreamItem]:
158
+ raise NotImplementedError()
159
+
160
+ T = typing.TypeVar('T')
161
+ def _wrap_iterable(self, iterable: collections.abc.Iterable[T], final_state: int) -> collections.abc.Iterable[T]:
162
+ yield from iterable
163
+ self._state = final_state
164
+
165
+ def _raise_unexpected_state(self, actual: int) -> None:
166
+ actual_method = self._state_to_method_name(actual)
167
+ if self._state % 2 == 1:
168
+ previous_method = self._state_to_method_name(self._state - 1)
169
+ raise ProtocolError(f"Received call to '{actual_method}' but the iterable returned by '{previous_method}' was not fully consumed.")
170
+ else:
171
+ expected_method = self._state_to_method_name(self._state)
172
+ raise ProtocolError(f"Expected to call to '{expected_method}' but received call to '{actual_method}'.")
173
+
174
+ def _state_to_method_name(self, state: int) -> str:
175
+ if state == 0:
176
+ return 'read_header'
177
+ if state == 2:
178
+ return 'read_data'
179
+ return "<unknown>"
180
+
@@ -0,0 +1,39 @@
1
+ import sys
2
+ import argparse
3
+ import numpy as np
4
+ from PIL import Image
5
+
6
+ import mrd
7
+
8
+
9
+ def export(input, output):
10
+ with mrd.BinaryMrdReader(input) as r:
11
+ header = r.read_header()
12
+ image_count = 0
13
+ for item in r.read_data():
14
+ if not isinstance(item, mrd.StreamItem.ImageFloat):
15
+ raise RuntimeError("Stream must contain only floating point images")
16
+
17
+ image = item.value
18
+ image.data *= 255 / image.data.max()
19
+ pixels = image.data.astype(np.uint8)
20
+
21
+ for c in range(image.channels()):
22
+ for s in range(image.slices()):
23
+ im = Image.fromarray(pixels[c, s, :, :], 'L')
24
+ filename = f"{output}{image_count:04d}.png"
25
+ im.save(filename, format='PNG')
26
+ image_count += 1
27
+
28
+
29
+ if __name__ == "__main__":
30
+ parser = argparse.ArgumentParser(description="Reconstructs an MRD stream")
31
+ parser.add_argument('-i', '--input', type=str, required=False,
32
+ help="Input file (default stdin)")
33
+ parser.add_argument('-o', '--output-prefix', type=str, required=False,
34
+ help="Output filename prefix", default="image_")
35
+ args = parser.parse_args()
36
+
37
+ input = open(args.input, "rb") if args.input is not None else sys.stdin.buffer
38
+
39
+ export(input, args.output_prefix)
mrd/tools/phantom.py ADDED
@@ -0,0 +1,161 @@
1
+ import sys
2
+ import argparse
3
+ import numpy as np
4
+
5
+ from typing import Generator
6
+
7
+ import mrd
8
+ from mrd.tools import simulation
9
+ from mrd.tools.transform import image_to_kspace
10
+
11
+
12
+ def generate(output_file, matrix, coils, oversampling, repetitions, noise_level):
13
+ output = sys.stdout.buffer
14
+ if output_file is not None:
15
+ output = output_file
16
+
17
+ nx = matrix
18
+ ny = matrix
19
+ nkx = oversampling * nx
20
+ nky = ny
21
+ fov = 300
22
+ slice_thickness = 5
23
+
24
+ h = mrd.Header()
25
+
26
+ s = mrd.SubjectInformationType()
27
+ s.patient_id = "1234BGVF"
28
+ s.patient_name = "John Doe"
29
+ h.subject_information = s
30
+
31
+ exp = mrd.ExperimentalConditionsType()
32
+ exp.h1resonance_frequency_hz = 128000000
33
+ h.experimental_conditions = exp
34
+
35
+ sys_info = mrd.AcquisitionSystemInformationType()
36
+ sys_info.receiver_channels = coils
37
+ h.acquisition_system_information = sys_info
38
+
39
+ e = mrd.EncodingSpaceType()
40
+ e.matrix_size = mrd.MatrixSizeType(x=nkx, y=nky, z=1)
41
+ e.field_of_view_mm = mrd.FieldOfViewMm(x=oversampling*fov, y=fov, z=slice_thickness)
42
+
43
+ r = mrd.EncodingSpaceType()
44
+ r.matrix_size = mrd.MatrixSizeType(x=nx, y=ny, z=1)
45
+ r.field_of_view_mm = mrd.FieldOfViewMm(x=fov, y=fov, z=slice_thickness)
46
+
47
+ limits1 = mrd.LimitType()
48
+ limits1.minimum = 0
49
+ limits1.center = round(ny / 2)
50
+ limits1.maximum = ny - 1
51
+
52
+ limits_rep = mrd.LimitType()
53
+ limits_rep.minimum = 0
54
+ limits_rep.center = round(repetitions / 2)
55
+ limits_rep.maximum = repetitions - 1
56
+
57
+ limits = mrd.EncodingLimitsType()
58
+ limits.kspace_encoding_step_1 = limits1
59
+ limits.repetition = limits_rep
60
+
61
+ enc = mrd.EncodingType()
62
+ enc.trajectory = mrd.Trajectory.CARTESIAN
63
+ enc.encoded_space = e
64
+ enc.recon_space = r
65
+ enc.encoding_limits = limits
66
+ h.encoding.append(enc)
67
+
68
+ phantom = generate_coil_kspace(matrix, coils, oversampling)
69
+
70
+ def generate_data() -> Generator[mrd.StreamItem, None, None]:
71
+ # We'll reuse this Acquisition object
72
+ acq = mrd.Acquisition()
73
+
74
+ acq.data.resize((coils, nkx))
75
+ acq.channel_order = list(range(coils))
76
+ acq.center_sample = round(nkx / 2)
77
+ acq.read_dir[0] = 1.0
78
+ acq.phase_dir[1] = 1.0
79
+ acq.slice_dir[2] = 1.0
80
+
81
+ scan_counter = 0
82
+
83
+ # Write out a few noise scans
84
+ for n in range(32):
85
+ noise = generate_noise((coils, nkx), noise_level)
86
+ # Here's where we would make the noise correlated
87
+ acq.scan_counter = scan_counter
88
+ scan_counter += 1
89
+ acq.flags = mrd.AcquisitionFlags.IS_NOISE_MEASUREMENT
90
+ acq.data[:] = noise
91
+ yield mrd.StreamItem.Acquisition(acq)
92
+
93
+ # Loop over the repetitions, add noise and serialize
94
+ # Simulating a T-SENSE type scan
95
+ for r in range(repetitions):
96
+ noise = generate_noise(phantom.shape, noise_level)
97
+ # Here's where we would make the noise correlated
98
+ kspace = phantom + noise
99
+
100
+ for line in range(nky):
101
+ acq.scan_counter = scan_counter
102
+ scan_counter += 1
103
+
104
+ acq.flags = mrd.AcquisitionFlags(0)
105
+ if line == 0:
106
+ acq.flags |= mrd.AcquisitionFlags.FIRST_IN_ENCODE_STEP_1
107
+ acq.flags |= mrd.AcquisitionFlags.FIRST_IN_SLICE
108
+ acq.flags |= mrd.AcquisitionFlags.FIRST_IN_REPETITION
109
+ if line == nky - 1:
110
+ acq.flags |= mrd.AcquisitionFlags.LAST_IN_ENCODE_STEP_1
111
+ acq.flags |= mrd.AcquisitionFlags.LAST_IN_SLICE
112
+ acq.flags |= mrd.AcquisitionFlags.LAST_IN_REPETITION
113
+
114
+ acq.idx.kspace_encode_step_1 = line
115
+ acq.idx.kspace_encode_step_2 = 0
116
+ acq.idx.slice = 0
117
+ acq.idx.repetition = r
118
+ acq.data[:] = kspace[:, 0, line, :]
119
+ yield mrd.StreamItem.Acquisition(acq)
120
+
121
+ with mrd.BinaryMrdWriter(output) as w:
122
+ w.write_header(h)
123
+ w.write_data(generate_data())
124
+
125
+
126
+ def generate_noise(shape, noise_sigma, mean=0.0):
127
+ rng = np.random.default_rng()
128
+ noise = rng.normal(mean, noise_sigma, shape) + 1j * rng.normal(mean, noise_sigma, shape)
129
+ return noise.astype(np.complex64)
130
+
131
+ def generate_coil_kspace(matrix_size, ncoils, oversampling):
132
+ phan = simulation.generate_shepp_logan_phantom(matrix_size)
133
+ coils = simulation.generate_birdcage_sensitivities(matrix_size, ncoils, 1.5)
134
+ coils = phan * coils
135
+ if oversampling > 1:
136
+ c = (0,0)
137
+ padding = round((oversampling * matrix_size - matrix_size) / 2)
138
+ coils = np.pad(coils, (c, c, c, (padding,padding)), mode='constant')
139
+ coils = image_to_kspace(coils, dim=(-1, -2)).astype(np.complex64)
140
+ return coils
141
+
142
+
143
+ if __name__ == "__main__":
144
+ parser = argparse.ArgumentParser(description="Generates a phantom dataset in MRD format")
145
+ parser.add_argument('-o', '--output', type=str, required=False, help="Output file, defaults to stdout")
146
+ parser.add_argument('-m', '--matrix', type=int, required=False, help="Matrix size")
147
+ parser.add_argument('-c', '--coils', type=int, required=False, help="Number of coils")
148
+ parser.add_argument('-s', '--oversampling', type=int, required=False, help="Oversampling")
149
+ parser.add_argument('-r', '--repetitions', type=int, required=False, help="Number of repetitions")
150
+ # parser.add_argument('-a', '--acceleration', type=int, required=False, help="Acceleration", default=1)
151
+ parser.add_argument('-n', '--noise-level', type=float, required=False, help="Noise level")
152
+ parser.set_defaults(
153
+ output=None,
154
+ coils=8,
155
+ matrix=256,
156
+ oversampling=2,
157
+ repetitions=1,
158
+ noise_level=0.05
159
+ )
160
+ args = parser.parse_args()
161
+ generate(args.output, args.matrix, args.coils, args.oversampling, args.repetitions, args.noise_level)
@@ -0,0 +1,173 @@
1
+ """
2
+ Tools for generating coil sensitivities and phantoms
3
+ """
4
+ import numpy as np
5
+ from mrd.tools.transform import kspace_to_image, image_to_kspace
6
+
7
+
8
+ def generate_shepp_logan_phantom(matrix_size) -> np.ndarray:
9
+ """Generates a modified Shepp Logan phantom with improved contrast"""
10
+ return generate_phantom(matrix_size, modified_shepp_logan_ellipses())
11
+
12
+
13
+ def generate_phantom(matrix_size, ellipses):
14
+ """
15
+ Create a Shepp-Logan or modified Shepp-Logan phantom::
16
+
17
+ phantom (n = 256, phantom_type = 'Modified Shepp-Logan', ellipses = None)
18
+
19
+ Parameters
20
+ ----------
21
+ matrix_size: int
22
+ Size of imaging matrix in pixels
23
+
24
+ ellipses: list of PhantomEllipse
25
+ Custom set of ellipses to use.
26
+
27
+ Returns
28
+ -------
29
+ np.ndarray
30
+ Phantom image with shape (1, 1, matrix_size, matrix_size)
31
+
32
+ Notes
33
+ -----
34
+ The image bounding box in the algorithm is ``[-1, -1], [1, 1]``,
35
+ so the values of ``a``, ``b``, ``x0``, ``y0`` should all be specified with
36
+ respect to this box.
37
+
38
+ References:
39
+
40
+ Shepp, L. A.; Logan, B. F.; Reconstructing Interior Head Tissue
41
+ from X-Ray Transmissions, IEEE Transactions on Nuclear Science,
42
+ Feb. 1974, p. 232.
43
+
44
+ Toft, P.; "The Radon Transform - Theory and Implementation",
45
+ Ph.D. thesis, Department of Mathematical Modelling, Technical
46
+ University of Denmark, June 1996.
47
+ """
48
+ shape = (1, 1, matrix_size, matrix_size)
49
+ out = np.zeros(shape, dtype=np.complex64)
50
+ for e in ellipses:
51
+ for y in range(matrix_size):
52
+ y_co = (float(y) - (matrix_size >> 1)) / (matrix_size >> 1)
53
+ for x in range(matrix_size):
54
+ x_co = (float(x) - (matrix_size >> 1)) / (matrix_size >> 1)
55
+ if e.is_inside(x_co, y_co):
56
+ out[0, 0, y, x] += e.get_amplitude() + 0.j
57
+ return out
58
+
59
+ def generate_birdcage_sensitivities(matrix_size, ncoils, relative_radius=1.5) -> np.ndarray:
60
+ """Generates birdcage coil sensitivities.
61
+
62
+ This function is heavily inspired by the mri_birdcage.m Matlab script in
63
+ Jeff Fessler's IRT package: http://web.eecs.umich.edu/~fessler/code/
64
+
65
+ Parameters
66
+ ----------
67
+ matrix_size: int
68
+ Size of imaging matrix in pixels
69
+ ncoils: int
70
+ Number of simulated coils
71
+ relative_radius: float, optional
72
+ Relative radius of birdcage (default 1.5)
73
+
74
+ Returns
75
+ -------
76
+ np.ndarray
77
+ Birdcage coil sensitivities with shape (ncoils, 1, matrix_size, matrix_size)
78
+ """
79
+ shape = (ncoils, 1, matrix_size, matrix_size)
80
+ out = np.zeros(shape, dtype=np.complex64)
81
+ for c in range(ncoils):
82
+ coilx = relative_radius * np.cos(c * (2 * np.pi / ncoils))
83
+ coily = relative_radius * np.sin(c * (2 * np.pi / ncoils))
84
+ coil_phase = -1.0 * c * (2 * np.pi / ncoils)
85
+ for y in range(matrix_size):
86
+ y_co = (float(y) - (matrix_size >> 1)) / (matrix_size >> 1) - coily
87
+ for x in range(matrix_size):
88
+ x_co = (float(x) - (matrix_size >> 1)) / (matrix_size >> 1) - coilx
89
+ rr = np.sqrt(x_co * x_co + y_co * y_co)
90
+ phi = np.arctan2(x_co, -y_co) + coil_phase
91
+ out[c, 0, y, x] = complex(1 / rr * np.cos(phi), 1 / rr * np.sin(phi))
92
+ return out
93
+
94
+
95
+ class PhantomEllipse:
96
+ """Ellipse object for phantom generation"""
97
+
98
+ def __init__(self, A, a, b, x0, y0, phi):
99
+ """Construct a PhantomEllipse
100
+
101
+ Parameters
102
+ ----------
103
+ A: float
104
+ Additive intensity of the ellipse.
105
+ a: float
106
+ Length of the major axis.
107
+ b: float
108
+ Length of the minor axis.
109
+ x0: float
110
+ Horizontal offset of the centre of the ellipse.
111
+ y0: float
112
+ Vertical offset of the centre of the ellipse.
113
+ phi: float
114
+ Counterclockwise rotation of the ellipse in degrees,
115
+ measured as the angle between the horizontal axis and
116
+ the ellipse major axis.
117
+ """
118
+ self.A = A
119
+ self.a = a
120
+ self.b = b
121
+ self.x0 = x0
122
+ self.y0 = y0
123
+ self.phi = phi
124
+
125
+ def is_inside(self, x, y):
126
+ """Determine whether an (x,y) coordinate is inside the ellipse"""
127
+ asq = self.a * self.a
128
+ bsq = self.b * self.b
129
+ phi = self.phi * np.pi / 180
130
+ x0 = x - self.x0
131
+ y0 = y - self.y0
132
+ cosp = np.cos(phi)
133
+ sinp = np.sin(phi)
134
+ return (
135
+ ((x0 * cosp + y0 * sinp) * (x0 * cosp + y0 * sinp)) / asq +
136
+ ((y0 * cosp - x0 * sinp) * (y0 * cosp - x0 * sinp)) / bsq
137
+ <= 1)
138
+
139
+ def get_amplitude(self):
140
+ return self.A
141
+
142
+
143
+ def shepp_logan_ellipses():
144
+ """Standard head phantom, taken from Shepp & Logan"""
145
+ return [
146
+ PhantomEllipse(1.0, 0.6900, 0.9200, 0.00, 0.0000, 0.0),
147
+ PhantomEllipse(-0.98, 0.6624, 0.8740, 0.00, -0.0184, 0.0),
148
+ PhantomEllipse(-0.02, 0.1100, 0.3100, 0.22, 0.0000, -18.0),
149
+ PhantomEllipse(-0.02, 0.1600, 0.4100, -0.22, 0.0000, 18.0),
150
+ PhantomEllipse(0.01, 0.2100, 0.2500, 0.00, 0.3500, 0.0),
151
+ PhantomEllipse(0.01, 0.0460, 0.0460, 0.00, 0.1000, 0.0),
152
+ PhantomEllipse(0.01, 0.0460, 0.0460, 0.00, -0.1000, 0.0),
153
+ PhantomEllipse(0.01, 0.0460, 0.0230, -0.08, -0.6050, 0.0),
154
+ PhantomEllipse(0.01, 0.0230, 0.0230, 0.00, -0.6060, 0.0),
155
+ PhantomEllipse(0.01, 0.0230, 0.0460, 0.06, -0.6050, 0.0),
156
+ ]
157
+
158
+ def modified_shepp_logan_ellipses():
159
+ """Modified version of Shepp & Logan's head phantom, adjusted to
160
+ improve contrast. Taken from Toft.
161
+ """
162
+ return [
163
+ PhantomEllipse(1.0, .6900, .9200, 0.00, 0.0000, 0.0),
164
+ PhantomEllipse(-0.8, .6624, .8740, 0.00, -0.0184, 0.0),
165
+ PhantomEllipse(-0.2, .1100, .3100, 0.22, 0.0000, -18.0),
166
+ PhantomEllipse(-0.2, .1600, .4100, -0.22, 0.0000, 18.0),
167
+ PhantomEllipse(0.1, .2100, .2500, 0.00, 0.3500, 0.0),
168
+ PhantomEllipse(0.1, .0460, .0460, 0.00, 0.1000, 0.0),
169
+ PhantomEllipse(0.1, .0460, .0460, 0.00, -0.1000, 0.0),
170
+ PhantomEllipse(0.1, .0460, .0230, -0.08, -0.6050, 0.0),
171
+ PhantomEllipse(0.1, .0230, .0230, 0.00, -0.6060, 0.0),
172
+ PhantomEllipse(0.1, .0230, .0460, 0.06, -0.6050, 0.0),
173
+ ]