mrd-python 2.0.0__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/__init__.py +96 -0
- mrd/_binary.py +1339 -0
- mrd/_dtypes.py +89 -0
- mrd/_ndjson.py +1194 -0
- mrd/binary.py +680 -0
- mrd/ndjson.py +2716 -0
- mrd/protocols.py +180 -0
- mrd/tools/export_png_images.py +39 -0
- mrd/tools/minimal_example.py +27 -0
- mrd/tools/phantom.py +161 -0
- mrd/tools/simulation.py +173 -0
- mrd/tools/stream_recon.py +184 -0
- mrd/tools/transform.py +37 -0
- mrd/types.py +1840 -0
- mrd/yardl_types.py +303 -0
- mrd_python-2.0.0.dist-info/METADATA +19 -0
- mrd_python-2.0.0.dist-info/RECORD +19 -0
- mrd_python-2.0.0.dist-info/WHEEL +5 -0
- mrd_python-2.0.0.dist-info/top_level.txt +1 -0
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)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import mrd
|
|
2
|
+
|
|
3
|
+
# Produce Acquisitions via a Python generator - simulating a stream
|
|
4
|
+
def generate_data():
|
|
5
|
+
nreps = 2
|
|
6
|
+
for _ in range(nreps):
|
|
7
|
+
acq = mrd.Acquisition()
|
|
8
|
+
# Populate Acquisition
|
|
9
|
+
# ...
|
|
10
|
+
yield mrd.StreamItem.Acquisition(acq)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
header = mrd.Header()
|
|
14
|
+
# Populate Header
|
|
15
|
+
# ...
|
|
16
|
+
|
|
17
|
+
with mrd.BinaryMrdWriter("test.bin") as w:
|
|
18
|
+
w.write_header(header)
|
|
19
|
+
w.write_data(generate_data())
|
|
20
|
+
|
|
21
|
+
with mrd.BinaryMrdReader("test.bin") as r:
|
|
22
|
+
header = r.read_header()
|
|
23
|
+
data_stream = r.read_data()
|
|
24
|
+
for item in data_stream:
|
|
25
|
+
# Process StreamItem (Acquisition, Image, or Waveform)
|
|
26
|
+
# ...
|
|
27
|
+
pass
|
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)
|
mrd/tools/simulation.py
ADDED
|
@@ -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
|
+
]
|