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/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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.43.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ mrd