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/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,8 @@
1
+ The MIT License (MIT)
2
+ Copyright © 2024 ISMRMRD Steering Committee
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.1
2
+ Name: mrd-python
3
+ Version: 2.0.0rc1
4
+ Summary: Libraries and tools for working with data in the ISMRM Raw Data (ISMRMRD or MRD).
5
+ License: The MIT License (MIT)
6
+ Copyright © 2024 ISMRMRD Steering Committee
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13
+
14
+ Project-URL: Homepage, https://github.com/ismrmrd/mrd
15
+ Project-URL: Documentation, https://github.com/ismrmrd/mrd
16
+ Project-URL: Repository, https://github.com/ismrmrd/mrd
17
+ Classifier: Development Status :: 5 - Production/Stable
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: License :: OSI Approved :: MIT License
20
+ Classifier: Operating System :: OS Independent
21
+ Classifier: Intended Audience :: Science/Research
22
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: numpy >=1.22.0
27
+ Requires-Dist: pillow >=9.2.0
28
+
@@ -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=vMVa9MBBsXkwOehCNDbT3iVwaHJ9d-vluhi1sqJtV2Q,93782
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/phantom.py,sha256=MUEykvImaEpMBd9DHnmZc583I528xipHHcoJ1UvwroA,5866
12
+ mrd/tools/simulation.py,sha256=JIIgy2__GVJF3b_lF9mj6gVJFlApXDCSyizQp6QuY8A,6220
13
+ mrd/tools/stream_recon.py,sha256=adDgxejRBxsV6QPb9jurW-oYbmET2aWypNUqg3KcX5k,8396
14
+ mrd/tools/transform.py,sha256=s4A9K2OFbO6ZAABzzSeXhlNfLO6qfbBOdze87tWbEuI,1337
15
+ mrd_python-2.0.0rc1.dist-info/LICENSE,sha256=dROqWaV3PdJDrrpWY_T-L-FSnW98mhaTAL3N-8JgjIY,1099
16
+ mrd_python-2.0.0rc1.dist-info/METADATA,sha256=vjqZoOdYvmZYzEPbQQROI1hbuSGAKbKhNyYNsgO5Znk,1957
17
+ mrd_python-2.0.0rc1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
18
+ mrd_python-2.0.0rc1.dist-info/top_level.txt,sha256=joUr2Pp4ds1cn6mxUoE_jPSUeIFkN2DvB2Qymkbfq-I,4
19
+ mrd_python-2.0.0rc1.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