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/yardl_types.py
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
# pyright: reportUnknownArgumentType=false
|
|
5
|
+
# pyright: reportUnknownVariableType=false
|
|
6
|
+
|
|
7
|
+
from abc import ABC
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Annotated, Generic, TypeVar, Union
|
|
10
|
+
import numpy as np
|
|
11
|
+
import datetime
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ProtocolError(Exception):
|
|
16
|
+
"""Raised when the contract of a protocol is not respected."""
|
|
17
|
+
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class OutOfRangeEnum(Enum):
|
|
22
|
+
"""Enum that allows values outside of the its defined values."""
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def _missing_(cls, value: object):
|
|
26
|
+
if not isinstance(value, int):
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
obj = object.__new__(cls)
|
|
30
|
+
obj._value_ = value
|
|
31
|
+
obj._name_ = ""
|
|
32
|
+
return obj
|
|
33
|
+
|
|
34
|
+
def __eq__(self, other: object):
|
|
35
|
+
return isinstance(other, self.__class__) and self.value == other.value
|
|
36
|
+
|
|
37
|
+
def __hash__(self) -> int:
|
|
38
|
+
return hash(self.value)
|
|
39
|
+
|
|
40
|
+
def __str__(self) -> str:
|
|
41
|
+
if self._name_ != "":
|
|
42
|
+
return super().__str__()
|
|
43
|
+
|
|
44
|
+
return f"{self.__class__.__name__}({self.value})"
|
|
45
|
+
|
|
46
|
+
def __repr__(self) -> str:
|
|
47
|
+
if self._name_ != "":
|
|
48
|
+
return super().__repr__()
|
|
49
|
+
|
|
50
|
+
return f"<{self.__class__.__name__}: {self.value}>"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DateTime:
|
|
54
|
+
"""A basic datetime with nanosecond precision, always in UTC."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, nanoseconds_from_epoch: Union[int, np.datetime64] = 0):
|
|
57
|
+
if isinstance(nanoseconds_from_epoch, np.datetime64):
|
|
58
|
+
if nanoseconds_from_epoch.dtype != "datetime64[ns]":
|
|
59
|
+
self._value = np.datetime64(nanoseconds_from_epoch, "ns")
|
|
60
|
+
else:
|
|
61
|
+
self._value = nanoseconds_from_epoch
|
|
62
|
+
else:
|
|
63
|
+
self._value = np.datetime64(nanoseconds_from_epoch, "ns")
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def numpy_value(self) -> np.datetime64:
|
|
67
|
+
return self._value
|
|
68
|
+
|
|
69
|
+
def to_datetime(self) -> datetime.datetime:
|
|
70
|
+
return datetime.datetime.utcfromtimestamp(self._value.astype(int) / 1e9)
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def from_components(
|
|
74
|
+
year: int,
|
|
75
|
+
month: int,
|
|
76
|
+
day: int,
|
|
77
|
+
hour: int = 0,
|
|
78
|
+
minute: int = 0,
|
|
79
|
+
second: int = 0,
|
|
80
|
+
nanosecond: int = 0,
|
|
81
|
+
) -> "DateTime":
|
|
82
|
+
if not 0 <= nanosecond <= 999_999_999:
|
|
83
|
+
raise ValueError("nanosecond must be in 0..999_999_999", nanosecond)
|
|
84
|
+
|
|
85
|
+
return DateTime(
|
|
86
|
+
int(datetime.datetime(year, month, day, hour, minute, second).timestamp())
|
|
87
|
+
* 1_000_000_000
|
|
88
|
+
+ nanosecond
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def from_datetime(dt: datetime.datetime) -> "DateTime":
|
|
93
|
+
return DateTime(round(dt.timestamp() * 1e6) * 1000)
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def parse(s: str) -> "DateTime":
|
|
97
|
+
return DateTime(np.datetime64(s, "ns"))
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def now() -> "DateTime":
|
|
101
|
+
return DateTime(time.time_ns())
|
|
102
|
+
|
|
103
|
+
def __str__(self) -> str:
|
|
104
|
+
return str(self._value)
|
|
105
|
+
|
|
106
|
+
def __repr__(self) -> str:
|
|
107
|
+
return f"DateTime({self})"
|
|
108
|
+
|
|
109
|
+
def __eq__(self, other: object) -> bool:
|
|
110
|
+
return (
|
|
111
|
+
self._value == other._value
|
|
112
|
+
if isinstance(other, DateTime)
|
|
113
|
+
else (isinstance(other, np.datetime64) and self._value == other)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def __hash__(self) -> int:
|
|
117
|
+
return hash(self._value)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class Time:
|
|
121
|
+
"""A basic time of day with nanosecond precision. It is not timezone-aware and is meant
|
|
122
|
+
to represent a wall clock time.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
_NANOSECONDS_PER_DAY = 24 * 60 * 60 * 1_000_000_000
|
|
126
|
+
|
|
127
|
+
def __init__(self, nanoseconds_since_midnight: Union[int, np.timedelta64] = 0):
|
|
128
|
+
if isinstance(nanoseconds_since_midnight, np.timedelta64):
|
|
129
|
+
if nanoseconds_since_midnight.dtype != "timedelta64[ns]":
|
|
130
|
+
self._value = np.timedelta64(nanoseconds_since_midnight, "ns")
|
|
131
|
+
nanoseconds_since_midnight = nanoseconds_since_midnight.astype(int)
|
|
132
|
+
else:
|
|
133
|
+
self._value = nanoseconds_since_midnight
|
|
134
|
+
else:
|
|
135
|
+
self._value = np.timedelta64(nanoseconds_since_midnight, "ns")
|
|
136
|
+
|
|
137
|
+
if (
|
|
138
|
+
nanoseconds_since_midnight < 0
|
|
139
|
+
or nanoseconds_since_midnight >= Time._NANOSECONDS_PER_DAY
|
|
140
|
+
):
|
|
141
|
+
raise ValueError(
|
|
142
|
+
"TimeOfDay must be between 00:00:00 and 23:59:59.999999999"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def numpy_value(self) -> np.timedelta64:
|
|
147
|
+
return self._value
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def from_components(
|
|
151
|
+
hour: int, minute: int, second: int = 0, nanosecond: int = 0
|
|
152
|
+
) -> "Time":
|
|
153
|
+
if not 0 <= hour <= 23:
|
|
154
|
+
raise ValueError("hour must be in 0..23", hour)
|
|
155
|
+
if not 0 <= minute <= 59:
|
|
156
|
+
raise ValueError("minute must be in 0..59", minute)
|
|
157
|
+
if not 0 <= second <= 59:
|
|
158
|
+
raise ValueError("second must be in 0..59", second)
|
|
159
|
+
if not 0 <= nanosecond <= 999_999_999:
|
|
160
|
+
raise ValueError("nanosecond must be in 0..999_999_999", nanosecond)
|
|
161
|
+
|
|
162
|
+
return Time(
|
|
163
|
+
hour * 3_600_000_000_000
|
|
164
|
+
+ minute * 60_000_000_000
|
|
165
|
+
+ second * 1_000_000_000
|
|
166
|
+
+ nanosecond
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def from_time(t: datetime.time) -> "Time":
|
|
171
|
+
return Time(
|
|
172
|
+
t.hour * 3_600_000_000_000
|
|
173
|
+
+ t.minute * 60_000_000_000
|
|
174
|
+
+ t.second * 1_000_000_000
|
|
175
|
+
+ t.microsecond * 1_000
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def parse(s: str) -> "Time":
|
|
180
|
+
components = s.split(":")
|
|
181
|
+
if len(components) == 2:
|
|
182
|
+
hour = int(components[0])
|
|
183
|
+
minute = int(components[1])
|
|
184
|
+
return Time(hour * 3_600_000_000_000 + minute * 60_000_000_000)
|
|
185
|
+
if len(components) == 3:
|
|
186
|
+
hour = int(components[0])
|
|
187
|
+
minute = int(components[1])
|
|
188
|
+
second_components = components[2].split(".")
|
|
189
|
+
if len(second_components) <= 2:
|
|
190
|
+
second = int(second_components[0])
|
|
191
|
+
if len(second_components) == 2:
|
|
192
|
+
fraction = int(second_components[1].ljust(9, "0")[:9])
|
|
193
|
+
else:
|
|
194
|
+
fraction = 0
|
|
195
|
+
return Time(
|
|
196
|
+
hour * 3_600_000_000_000
|
|
197
|
+
+ minute * 60_000_000_000
|
|
198
|
+
+ second * 1_000_000_000
|
|
199
|
+
+ fraction
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
raise ValueError("TimeOfDay must be in the format HH:MM:SS[.fffffffff]")
|
|
203
|
+
|
|
204
|
+
def __str__(self) -> str:
|
|
205
|
+
nanoseconds_since_midnight = self._value.astype(int)
|
|
206
|
+
hours, r = divmod(nanoseconds_since_midnight, 3_600_000_000_000)
|
|
207
|
+
minutes, r = divmod(r, 60_000_000_000)
|
|
208
|
+
seconds, nanoseconds = divmod(r, 1_000_000_000)
|
|
209
|
+
if nanoseconds == 0:
|
|
210
|
+
return f"{hours:02}:{minutes:02}:{seconds:02}"
|
|
211
|
+
|
|
212
|
+
return f"{hours:02}:{minutes:02}:{seconds:02}.{str(nanoseconds).rjust(9, '0').rstrip('0')}"
|
|
213
|
+
|
|
214
|
+
def __repr__(self) -> str:
|
|
215
|
+
return f"Time({self})"
|
|
216
|
+
|
|
217
|
+
def __eq__(self, other: object) -> bool:
|
|
218
|
+
return (
|
|
219
|
+
self._value == other._value
|
|
220
|
+
if isinstance(other, Time)
|
|
221
|
+
else (isinstance(other, np.timedelta64) and self._value == other)
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
Int8 = Annotated[int, "Int8"]
|
|
226
|
+
UInt8 = Annotated[int, "UInt8"]
|
|
227
|
+
Int16 = Annotated[int, "Int16"]
|
|
228
|
+
UInt16 = Annotated[int, "UInt16"]
|
|
229
|
+
Int32 = Annotated[int, "Int32"]
|
|
230
|
+
UInt32 = Annotated[int, "UInt32"]
|
|
231
|
+
Int64 = Annotated[int, "Int64"]
|
|
232
|
+
UInt64 = Annotated[int, "UInt64"]
|
|
233
|
+
Size = Annotated[int, "Size"]
|
|
234
|
+
Float32 = Annotated[float, "Float32"]
|
|
235
|
+
Float64 = Annotated[float, "Float64"]
|
|
236
|
+
ComplexFloat = Annotated[complex, "ComplexFloat"]
|
|
237
|
+
ComplexDouble = Annotated[complex, "ComplexDouble"]
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def structural_equal(a: object, b: object) -> bool:
|
|
241
|
+
if a is None:
|
|
242
|
+
return b is None
|
|
243
|
+
|
|
244
|
+
if isinstance(a, list):
|
|
245
|
+
if not isinstance(b, list):
|
|
246
|
+
if isinstance(b, np.ndarray):
|
|
247
|
+
return b.shape == (len(a),) and all(
|
|
248
|
+
structural_equal(x, y) for x, y in zip(a, b)
|
|
249
|
+
)
|
|
250
|
+
return False
|
|
251
|
+
return len(a) == len(b) and all(structural_equal(x, y) for x, y in zip(a, b))
|
|
252
|
+
|
|
253
|
+
if isinstance(a, np.ndarray):
|
|
254
|
+
if not isinstance(b, np.ndarray):
|
|
255
|
+
if isinstance(b, list):
|
|
256
|
+
return a.shape == (len(b),) and all(
|
|
257
|
+
structural_equal(x, y) for x, y in zip(a, b)
|
|
258
|
+
)
|
|
259
|
+
return False
|
|
260
|
+
if a.dtype.hasobject: # pyright: ignore [reportUnknownMemberType]
|
|
261
|
+
return (
|
|
262
|
+
a.dtype == b.dtype # pyright: ignore [reportUnknownMemberType]
|
|
263
|
+
and a.shape == b.shape
|
|
264
|
+
and all(structural_equal(x, y) for x, y in zip(a, b))
|
|
265
|
+
)
|
|
266
|
+
return np.array_equal(a, b)
|
|
267
|
+
|
|
268
|
+
if isinstance(a, np.void):
|
|
269
|
+
if not isinstance(b, np.void):
|
|
270
|
+
return b == a
|
|
271
|
+
return a.dtype == b.dtype and all(
|
|
272
|
+
structural_equal(x, y)
|
|
273
|
+
for x, y in zip(a, b) # pyright: ignore [reportGeneralTypeIssues]
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
if isinstance(b, np.void):
|
|
277
|
+
return a == b
|
|
278
|
+
|
|
279
|
+
return a == b
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
_T = TypeVar("_T")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class UnionCase(ABC, Generic[_T]):
|
|
286
|
+
index: int
|
|
287
|
+
tag: str
|
|
288
|
+
|
|
289
|
+
def __init__(self, value: _T) -> None:
|
|
290
|
+
self.value = value
|
|
291
|
+
|
|
292
|
+
def __str__(self) -> str:
|
|
293
|
+
return str(self.value)
|
|
294
|
+
|
|
295
|
+
def __repr__(self) -> str:
|
|
296
|
+
return f"{self.__class__.__name__}({self.value})"
|
|
297
|
+
|
|
298
|
+
def __eq__(self, other: object) -> bool:
|
|
299
|
+
# Note we could codegen a more efficient version of this that does not
|
|
300
|
+
# (always) call structural_equal
|
|
301
|
+
return type(self) == type(other) and structural_equal(
|
|
302
|
+
self.value, other.value # pyright: ignore
|
|
303
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: mrd-python
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Library and tools for working with data in the ISMRM Raw Data (MRD) format.
|
|
5
|
+
Project-URL: Homepage, https://ismrmrd.github.io/mrd
|
|
6
|
+
Project-URL: Documentation, https://ismrmrd.github.io/mrd
|
|
7
|
+
Project-URL: Repository, https://github.com/ismrmrd/mrd
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: numpy >=1.22.0
|
|
17
|
+
Requires-Dist: pillow >=9.2.0
|
|
18
|
+
|
|
19
|
+
Library and tools for working with data in the ISMRM Raw Data (MRD) format.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
mrd/__init__.py,sha256=drUoXRSnt-Z2nh-vDjlcpzg9cUFS2yexVEliTpwsvjU,2275
|
|
2
|
+
mrd/_binary.py,sha256=zir3uLbk5r1cpER8YL4Hk2qZdKZHlOj4gk9PN8sgcdg,46614
|
|
3
|
+
mrd/_dtypes.py,sha256=R8jl2IPI2lUXotCGFXfDCMjHZcsmEWni-yO_0SpMSZc,3390
|
|
4
|
+
mrd/_ndjson.py,sha256=VnDjo-X0yI2u-5qYWpDXRVasnC7BhAX1EdkLD864WRY,39791
|
|
5
|
+
mrd/binary.py,sha256=am3aHLTVhl6h9lH1WGFyUn9Kvo_8BrwxL3t56069rIw,53801
|
|
6
|
+
mrd/ndjson.py,sha256=vYhPBW4RuciHrv5UFceXKiV2IqK648ijFh-XmEFWs8Y,169451
|
|
7
|
+
mrd/protocols.py,sha256=eETX65nTfoExyvVrgrABajPuW6nvdchhf8vX4dsUwPc,23566
|
|
8
|
+
mrd/types.py,sha256=aHvFwPZsjkE972ncft6yIg2OTm9M928th1vts-ZMDOE,97266
|
|
9
|
+
mrd/yardl_types.py,sha256=i1L_uFUPWdcCt8-DzA8yaRtJC_XQtwsiE26_CAc3ff0,9563
|
|
10
|
+
mrd/tools/export_png_images.py,sha256=77YLfoG45beKYwpb2cB1Me9SNKcEVQn1kaQsTdOTdhY,1372
|
|
11
|
+
mrd/tools/minimal_example.py,sha256=ULr-o6UDoq454XehknEH2zJCzHFBOBWz48o9jrFIw1I,642
|
|
12
|
+
mrd/tools/phantom.py,sha256=MUEykvImaEpMBd9DHnmZc583I528xipHHcoJ1UvwroA,5866
|
|
13
|
+
mrd/tools/simulation.py,sha256=JIIgy2__GVJF3b_lF9mj6gVJFlApXDCSyizQp6QuY8A,6220
|
|
14
|
+
mrd/tools/stream_recon.py,sha256=adDgxejRBxsV6QPb9jurW-oYbmET2aWypNUqg3KcX5k,8396
|
|
15
|
+
mrd/tools/transform.py,sha256=s4A9K2OFbO6ZAABzzSeXhlNfLO6qfbBOdze87tWbEuI,1337
|
|
16
|
+
mrd_python-2.0.0.dist-info/METADATA,sha256=JMCI1GshGqaK2haUBp3YPYYYJRnUKOCPsiHFCCGHzo8,827
|
|
17
|
+
mrd_python-2.0.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
18
|
+
mrd_python-2.0.0.dist-info/top_level.txt,sha256=joUr2Pp4ds1cn6mxUoE_jPSUeIFkN2DvB2Qymkbfq-I,4
|
|
19
|
+
mrd_python-2.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mrd
|