mlx-segment-ct 0.2.1__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.
- mlx_segment_ct/__init__.py +83 -0
- mlx_segment_ct/_types.py +378 -0
- mlx_segment_ct/activations.py +74 -0
- mlx_segment_ct/bundle_transforms.py +778 -0
- mlx_segment_ct/checkpoint_sources.py +45 -0
- mlx_segment_ct/correction.py +24 -0
- mlx_segment_ct/correction_inferer.py +163 -0
- mlx_segment_ct/instance_norm.py +149 -0
- mlx_segment_ct/interactive.py +187 -0
- mlx_segment_ct/labels.py +723 -0
- mlx_segment_ct/medmlx.py +784 -0
- mlx_segment_ct/mlx_correction_weights.py +55 -0
- mlx_segment_ct/mlx_network.py +217 -0
- mlx_segment_ct/mlx_point.py +238 -0
- mlx_segment_ct/mlx_point_window.py +254 -0
- mlx_segment_ct/mlx_upsample.py +73 -0
- mlx_segment_ct/mlx_weights.py +72 -0
- mlx_segment_ct/mlx_worker.py +48 -0
- mlx_segment_ct/projections.py +81 -0
- mlx_segment_ct/py.typed +0 -0
- mlx_segment_ct/restoration.py +306 -0
- mlx_segment_ct/streaming.py +321 -0
- mlx_segment_ct-0.2.1.dist-info/METADATA +99 -0
- mlx_segment_ct-0.2.1.dist-info/RECORD +28 -0
- mlx_segment_ct-0.2.1.dist-info/WHEEL +4 -0
- mlx_segment_ct-0.2.1.dist-info/entry_points.txt +2 -0
- mlx_segment_ct-0.2.1.dist-info/licenses/LICENSE +202 -0
- mlx_segment_ct-0.2.1.dist-info/licenses/THIRD_PARTY_NOTICES.md +361 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Native MLX implementation of NV-Segment-CT (VISTA-3D).
|
|
2
|
+
|
|
3
|
+
Public names load on first access, so importing the package (or the medmlx
|
|
4
|
+
declaration in ``mlx_segment_ct.medmlx``) does not import MLX.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from importlib import import_module
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from mlx_segment_ct.correction import normalized_point_positions
|
|
12
|
+
from mlx_segment_ct.correction_inferer import (
|
|
13
|
+
point_based_window_inferer,
|
|
14
|
+
)
|
|
15
|
+
from mlx_segment_ct.interactive import (
|
|
16
|
+
interactive_labels,
|
|
17
|
+
interactive_logits,
|
|
18
|
+
transform_points,
|
|
19
|
+
)
|
|
20
|
+
from mlx_segment_ct.labels import InteractivePrompt
|
|
21
|
+
from mlx_segment_ct.mlx_network import AutomaticModel, FeatureModel, required_shapes
|
|
22
|
+
from mlx_segment_ct.mlx_point import PointModel, additional_shapes, correction_shapes
|
|
23
|
+
from mlx_segment_ct.mlx_point_window import PointWindowPredictor
|
|
24
|
+
from mlx_segment_ct.mlx_upsample import AutomaticUpsample
|
|
25
|
+
from mlx_segment_ct.mlx_worker import automatic_logits
|
|
26
|
+
from mlx_segment_ct.streaming import LabelReducer, bounded_labels, rolling_logits
|
|
27
|
+
|
|
28
|
+
__version__ = "0.2.1"
|
|
29
|
+
|
|
30
|
+
_EXPORTS = {
|
|
31
|
+
"AutomaticModel": "mlx_segment_ct.mlx_network",
|
|
32
|
+
"AutomaticUpsample": "mlx_segment_ct.mlx_upsample",
|
|
33
|
+
"FeatureModel": "mlx_segment_ct.mlx_network",
|
|
34
|
+
"InteractivePrompt": "mlx_segment_ct.labels",
|
|
35
|
+
"LabelReducer": "mlx_segment_ct.streaming",
|
|
36
|
+
"PointModel": "mlx_segment_ct.mlx_point",
|
|
37
|
+
"PointWindowPredictor": "mlx_segment_ct.mlx_point_window",
|
|
38
|
+
"additional_shapes": "mlx_segment_ct.mlx_point",
|
|
39
|
+
"automatic_logits": "mlx_segment_ct.mlx_worker",
|
|
40
|
+
"bounded_labels": "mlx_segment_ct.streaming",
|
|
41
|
+
"correction_shapes": "mlx_segment_ct.mlx_point",
|
|
42
|
+
"interactive_labels": "mlx_segment_ct.interactive",
|
|
43
|
+
"interactive_logits": "mlx_segment_ct.interactive",
|
|
44
|
+
"normalized_point_positions": "mlx_segment_ct.correction",
|
|
45
|
+
"point_based_window_inferer": "mlx_segment_ct.correction_inferer",
|
|
46
|
+
"required_shapes": "mlx_segment_ct.mlx_network",
|
|
47
|
+
"rolling_logits": "mlx_segment_ct.streaming",
|
|
48
|
+
"transform_points": "mlx_segment_ct.interactive",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
"AutomaticModel",
|
|
53
|
+
"AutomaticUpsample",
|
|
54
|
+
"FeatureModel",
|
|
55
|
+
"InteractivePrompt",
|
|
56
|
+
"LabelReducer",
|
|
57
|
+
"PointModel",
|
|
58
|
+
"PointWindowPredictor",
|
|
59
|
+
"additional_shapes",
|
|
60
|
+
"automatic_logits",
|
|
61
|
+
"bounded_labels",
|
|
62
|
+
"correction_shapes",
|
|
63
|
+
"interactive_labels",
|
|
64
|
+
"interactive_logits",
|
|
65
|
+
"normalized_point_positions",
|
|
66
|
+
"point_based_window_inferer",
|
|
67
|
+
"required_shapes",
|
|
68
|
+
"rolling_logits",
|
|
69
|
+
"transform_points",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def __getattr__(name: str) -> object:
|
|
74
|
+
module = _EXPORTS.get(name)
|
|
75
|
+
if module is None:
|
|
76
|
+
raise AttributeError(f"module 'mlx_segment_ct' has no attribute {name!r}")
|
|
77
|
+
value = getattr(import_module(module), name)
|
|
78
|
+
globals()[name] = value
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def __dir__() -> list[str]:
|
|
83
|
+
return sorted({*globals(), *_EXPORTS})
|
mlx_segment_ct/_types.py
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""Consumed native MLX and host-orchestration contracts.
|
|
2
|
+
|
|
3
|
+
The native graph uses real MLX array/Dtype identities. The orchestration boundary
|
|
4
|
+
is generic because the existing CPU oracle tests inject NumPy arrays there.
|
|
5
|
+
These partial interfaces describe the MLX and medmlx-core versions pinned in uv.lock;
|
|
6
|
+
they do not replace either runtime or claim complete upstream API coverage.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
12
|
+
from os import PathLike
|
|
13
|
+
from typing import TYPE_CHECKING, Literal, NotRequired, Protocol, Required, TypedDict
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
from medmlx_core.typing import MlxRuntime
|
|
17
|
+
from numpy.typing import DTypeLike, NDArray
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from mlx.core import Dtype, array
|
|
21
|
+
|
|
22
|
+
type NumericScalar = (
|
|
23
|
+
np.bool_
|
|
24
|
+
| np.int8
|
|
25
|
+
| np.int16
|
|
26
|
+
| np.int32
|
|
27
|
+
| np.int64
|
|
28
|
+
| np.uint8
|
|
29
|
+
| np.uint16
|
|
30
|
+
| np.uint32
|
|
31
|
+
| np.uint64
|
|
32
|
+
| np.float16
|
|
33
|
+
| np.float32
|
|
34
|
+
| np.float64
|
|
35
|
+
)
|
|
36
|
+
HostArray = NDArray[NumericScalar]
|
|
37
|
+
FloatArray = NDArray[np.float32]
|
|
38
|
+
DoubleArray = NDArray[np.float64]
|
|
39
|
+
IntArray = NDArray[np.int64]
|
|
40
|
+
BoolArray = NDArray[np.bool_]
|
|
41
|
+
type ArrayValue = array | HostArray
|
|
42
|
+
type NumericInput = bool | int | float | ArrayValue | Sequence[NumericInput]
|
|
43
|
+
type ArrayTree = array | Sequence[ArrayTree] | Mapping[str, ArrayTree]
|
|
44
|
+
type PatchCoordinates = Sequence[Sequence[slice]]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ArrayBoundary[ArrayT](Protocol):
|
|
48
|
+
@property
|
|
49
|
+
def array(self) -> Callable[..., ArrayT]: ...
|
|
50
|
+
def eval(self, *args: ArrayT | Sequence[ArrayT] | Mapping[str, ArrayT]) -> None: ...
|
|
51
|
+
def synchronize(self) -> None: ...
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class CoreRuntime(MlxRuntime, Protocol):
|
|
55
|
+
def eval(self, *args: array | Mapping[str, array] | Sequence[array]) -> None: ...
|
|
56
|
+
def synchronize(self) -> None: ...
|
|
57
|
+
def async_eval(self, *args: ArrayTree) -> None: ...
|
|
58
|
+
def ones(self, shape: Sequence[int], dtype: Dtype | None = None) -> array: ...
|
|
59
|
+
def sin(self, a: array) -> array: ...
|
|
60
|
+
def cos(self, a: array) -> array: ...
|
|
61
|
+
def abs(self, a: array) -> array: ...
|
|
62
|
+
def sum(
|
|
63
|
+
self, a: array, axis: int | Sequence[int] | None = None, keepdims: bool = False
|
|
64
|
+
) -> array: ...
|
|
65
|
+
def softmax(
|
|
66
|
+
self, a: array, axis: int | Sequence[int] | None = None, *, precise: bool = False
|
|
67
|
+
) -> array: ...
|
|
68
|
+
def addmm(
|
|
69
|
+
self, c: array, a: array, b: array, alpha: float = 1.0, beta: float = 1.0
|
|
70
|
+
) -> array: ...
|
|
71
|
+
def meshgrid(
|
|
72
|
+
self, *arrays: array, indexing: Literal["xy", "ij"] = "xy", sparse: bool = False
|
|
73
|
+
) -> tuple[array, ...]: ...
|
|
74
|
+
def contiguous(self, a: array, /, *, allow_col_major: bool = False) -> array: ...
|
|
75
|
+
def load(self, file: str, /, format: str | None = None) -> array | dict[str, array]: ...
|
|
76
|
+
def save_safetensors(
|
|
77
|
+
self, file: str, arrays: Mapping[str, array], metadata: Mapping[str, str] | None = None
|
|
78
|
+
) -> None: ...
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class PointPredictor[ArrayT](Protocol):
|
|
82
|
+
def __call__(self, image: ArrayT, positions: ArrayT, codes: ArrayT, target: int) -> ArrayT: ...
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class CoordinatePredictor[ArrayT](Protocol):
|
|
86
|
+
def __call__(self, image: ArrayT, coordinates: PatchCoordinates, /) -> ArrayT: ...
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class PointOptions(TypedDict, total=False):
|
|
90
|
+
transpose: Required[bool]
|
|
91
|
+
sw_batch_size: int
|
|
92
|
+
with_coord: bool
|
|
93
|
+
mode: str
|
|
94
|
+
sw_device: object
|
|
95
|
+
device: object
|
|
96
|
+
overlap: float
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class PointWindowCall[ArrayT](Protocol):
|
|
100
|
+
def __call__(
|
|
101
|
+
self,
|
|
102
|
+
image: ArrayT,
|
|
103
|
+
*,
|
|
104
|
+
point_coords: HostArray,
|
|
105
|
+
point_labels: HostArray,
|
|
106
|
+
class_vector: HostArray | None,
|
|
107
|
+
prompt_class: HostArray | None,
|
|
108
|
+
patch_coords: PatchCoordinates,
|
|
109
|
+
prev_mask: HostArray | None,
|
|
110
|
+
transpose: bool,
|
|
111
|
+
**kwargs: object,
|
|
112
|
+
) -> HostArray: ...
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Metrics(TypedDict):
|
|
116
|
+
patch_seconds: list[float]
|
|
117
|
+
padded_shape: NotRequired[list[int]]
|
|
118
|
+
padding: NotRequired[list[int]]
|
|
119
|
+
streaming_axis: NotRequired[int]
|
|
120
|
+
ring_depth: NotRequired[int]
|
|
121
|
+
predicted_accumulator_bytes: NotRequired[int]
|
|
122
|
+
count_buffer_bytes: NotRequired[int]
|
|
123
|
+
label_buffer_bytes: NotRequired[int]
|
|
124
|
+
accumulator_dtype: NotRequired[str]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class OutputLabel(TypedDict):
|
|
128
|
+
value: int
|
|
129
|
+
name: str | None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class InteractiveParameters(TypedDict):
|
|
133
|
+
roi_size: list[int]
|
|
134
|
+
center_only: bool
|
|
135
|
+
padding_mode: str
|
|
136
|
+
overlap_reduction: str
|
|
137
|
+
threshold: int
|
|
138
|
+
uncovered_label: int
|
|
139
|
+
positive_components: bool
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class Details(TypedDict, total=False):
|
|
143
|
+
mode: Required[str]
|
|
144
|
+
label_prompt: list[int]
|
|
145
|
+
label_names: list[str]
|
|
146
|
+
automatic_branch: bool
|
|
147
|
+
point_start: int
|
|
148
|
+
output_labels: Required[list[OutputLabel]]
|
|
149
|
+
points: list[list[float]]
|
|
150
|
+
point_labels: list[int]
|
|
151
|
+
effective_point_labels: list[int]
|
|
152
|
+
preprocessed_points: list[list[float]]
|
|
153
|
+
preprocessed_affine: list[list[float]]
|
|
154
|
+
preprocessed_shape: Required[list[int]]
|
|
155
|
+
output_shape: Required[list[int]]
|
|
156
|
+
output_dtype: Required[str]
|
|
157
|
+
algorithm: Required[str]
|
|
158
|
+
parameters: InteractiveParameters
|
|
159
|
+
patch_count: Required[int]
|
|
160
|
+
load_preprocess_seconds: Required[float]
|
|
161
|
+
inference_stitch_seconds: Required[float]
|
|
162
|
+
restore_save_seconds: Required[float]
|
|
163
|
+
class_ids: list[int]
|
|
164
|
+
roi_size: list[int]
|
|
165
|
+
overlap: float
|
|
166
|
+
blend_mode: str
|
|
167
|
+
padding_mode: str
|
|
168
|
+
padded_shape: list[int]
|
|
169
|
+
modality: str
|
|
170
|
+
checkpoint: str
|
|
171
|
+
restoration: str
|
|
172
|
+
backend: str
|
|
173
|
+
mlx_version: str
|
|
174
|
+
mlx_default_device: str
|
|
175
|
+
precision: str
|
|
176
|
+
source_revision: str
|
|
177
|
+
mlx_peak_memory_bytes: int | None
|
|
178
|
+
elapsed_seconds: float
|
|
179
|
+
warning: str
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class PublishedDetails(TypedDict, total=False):
|
|
183
|
+
mode: Required[str]
|
|
184
|
+
label_prompt: list[int]
|
|
185
|
+
label_names: list[str]
|
|
186
|
+
automatic_branch: bool
|
|
187
|
+
point_start: int
|
|
188
|
+
output_labels: Required[list[OutputLabel]]
|
|
189
|
+
points: list[list[float]]
|
|
190
|
+
point_labels: list[int]
|
|
191
|
+
effective_point_labels: list[int]
|
|
192
|
+
preprocessed_points: list[list[float]]
|
|
193
|
+
preprocessed_affine: list[list[float]]
|
|
194
|
+
preprocessed_shape: Required[list[int]]
|
|
195
|
+
output_shape: Required[list[int]]
|
|
196
|
+
output_dtype: Required[str]
|
|
197
|
+
algorithm: Required[str]
|
|
198
|
+
parameters: InteractiveParameters
|
|
199
|
+
patch_count: Required[int]
|
|
200
|
+
load_preprocess_seconds: Required[float]
|
|
201
|
+
inference_stitch_seconds: Required[float]
|
|
202
|
+
restore_save_seconds: Required[float]
|
|
203
|
+
class_ids: list[int]
|
|
204
|
+
roi_size: list[int]
|
|
205
|
+
overlap: float
|
|
206
|
+
blend_mode: str
|
|
207
|
+
padding_mode: str
|
|
208
|
+
padded_shape: list[int]
|
|
209
|
+
modality: str
|
|
210
|
+
checkpoint: Required[str]
|
|
211
|
+
restoration: Required[str]
|
|
212
|
+
backend: Required[str]
|
|
213
|
+
mlx_version: Required[str]
|
|
214
|
+
mlx_default_device: Required[str]
|
|
215
|
+
precision: Required[str]
|
|
216
|
+
source_revision: Required[str]
|
|
217
|
+
mlx_peak_memory_bytes: Required[int | None]
|
|
218
|
+
elapsed_seconds: Required[float]
|
|
219
|
+
warning: Required[str]
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class Outputs(TypedDict):
|
|
223
|
+
mask: str
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class RunResult(TypedDict):
|
|
227
|
+
outputs: Outputs
|
|
228
|
+
details: PublishedDetails
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class RunArguments(TypedDict, total=False):
|
|
232
|
+
image_path: str | PathLike[str]
|
|
233
|
+
weights_path: str | PathLike[str]
|
|
234
|
+
output_dir: str | PathLike[str]
|
|
235
|
+
segment_all: bool
|
|
236
|
+
prompt: str | Sequence[str] | None
|
|
237
|
+
label_prompt: Sequence[int] | None
|
|
238
|
+
previous_logits_path: str | PathLike[str] | None
|
|
239
|
+
points: Sequence[Sequence[float]] | None
|
|
240
|
+
point_labels: Sequence[int] | None
|
|
241
|
+
point_start: int
|
|
242
|
+
interactive: bool
|
|
243
|
+
overwrite: bool
|
|
244
|
+
checkpoint: str
|
|
245
|
+
modality: str | None
|
|
246
|
+
restoration: str
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class NiftiHeader(Protocol):
|
|
250
|
+
@property
|
|
251
|
+
def binaryblock(self) -> bytes: ...
|
|
252
|
+
def __getitem__(self, key: str) -> HostArray: ...
|
|
253
|
+
def get(self, key: str) -> HostArray | None: ...
|
|
254
|
+
def get_zooms(self) -> tuple[float, ...]: ...
|
|
255
|
+
def set_zooms(self, zooms: Sequence[float] | HostArray) -> None: ...
|
|
256
|
+
def as_byteswapped(self, endianness: str | None = None) -> NiftiHeader: ...
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class NiftiArrayProxy(Protocol):
|
|
260
|
+
"""File-backed NIfTI data supports conversion without ndarray methods."""
|
|
261
|
+
|
|
262
|
+
def __array__(self, dtype: DTypeLike | None = None) -> NDArray[np.generic]: ...
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class NiftiImage(Protocol):
|
|
266
|
+
@property
|
|
267
|
+
def shape(self) -> tuple[int, ...]: ...
|
|
268
|
+
def get_data_dtype(self) -> np.dtype[NumericScalar]: ...
|
|
269
|
+
@property
|
|
270
|
+
def header(self) -> NiftiHeader: ...
|
|
271
|
+
@property
|
|
272
|
+
def affine(self) -> DoubleArray: ...
|
|
273
|
+
@property
|
|
274
|
+
def dataobj(self) -> HostArray | NiftiArrayProxy: ...
|
|
275
|
+
def get_sform(self) -> DoubleArray: ...
|
|
276
|
+
def get_qform(self) -> DoubleArray: ...
|
|
277
|
+
def set_sform(self, affine: HostArray, code: int | None = None) -> None: ...
|
|
278
|
+
def set_qform(self, affine: HostArray, code: int | None = None) -> None: ...
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class NiftiConstructor(Protocol):
|
|
282
|
+
def __call__(self, dataobj: HostArray, affine: HostArray) -> NiftiImage: ...
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class NiftiRuntime(Protocol):
|
|
286
|
+
@property
|
|
287
|
+
def Nifti1Image(self) -> NiftiConstructor: ...
|
|
288
|
+
def load(self, filename: str) -> object: ...
|
|
289
|
+
def save(self, img: NiftiImage, filename: str) -> None: ...
|
|
290
|
+
def aff2axcodes(self, aff: HostArray) -> tuple[str | None, ...]: ...
|
|
291
|
+
def io_orientation(self, affine: HostArray) -> DoubleArray: ...
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class OrientationRuntime(Protocol):
|
|
295
|
+
def axcodes2ornt(self, axcodes: tuple[str | None, ...], labels: None = None) -> DoubleArray: ...
|
|
296
|
+
def ornt_transform(self, start_ornt: DoubleArray, end_ornt: DoubleArray) -> DoubleArray: ...
|
|
297
|
+
def inv_ornt_aff(self, ornt: DoubleArray, shape: Sequence[int]) -> DoubleArray: ...
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class RollingMetrics(TypedDict):
|
|
301
|
+
patch_seconds: list[float]
|
|
302
|
+
padded_shape: list[int]
|
|
303
|
+
padding: list[int]
|
|
304
|
+
streaming_axis: int
|
|
305
|
+
ring_depth: int
|
|
306
|
+
predicted_accumulator_bytes: int
|
|
307
|
+
count_buffer_bytes: int
|
|
308
|
+
label_buffer_bytes: int
|
|
309
|
+
accumulator_dtype: str
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class SlidingWindowCall(Protocol):
|
|
313
|
+
def __call__(
|
|
314
|
+
self,
|
|
315
|
+
inputs: ArrayValue,
|
|
316
|
+
roi_size: Sequence[int],
|
|
317
|
+
sw_batch_size: int,
|
|
318
|
+
predictor: Callable[..., array],
|
|
319
|
+
*,
|
|
320
|
+
overlap: float,
|
|
321
|
+
mode: str,
|
|
322
|
+
padding_mode: str,
|
|
323
|
+
roi_weight_map: ArrayValue | None,
|
|
324
|
+
with_coord: bool,
|
|
325
|
+
**kwargs: object,
|
|
326
|
+
) -> HostArray: ...
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class SlidingWindowNamespace(Protocol):
|
|
330
|
+
@property
|
|
331
|
+
def sliding_window_inference(self) -> SlidingWindowCall: ...
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class StandInRuntime[ArrayT, DtypeT](ArrayBoundary[ArrayT], Protocol):
|
|
335
|
+
@property
|
|
336
|
+
def float32(self) -> DtypeT: ...
|
|
337
|
+
def sum(self, a: ArrayT) -> ArrayT | NumericScalar: ...
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
type Float32Dtype = Dtype | type[np.float32]
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class Float32Cast[ArrayT, DtypeT](Protocol):
|
|
344
|
+
def astype(self, dtype: DtypeT) -> ArrayT: ...
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class RollingMetricsBuilder(TypedDict):
|
|
348
|
+
patch_seconds: NotRequired[list[float]]
|
|
349
|
+
padded_shape: NotRequired[list[int]]
|
|
350
|
+
padding: NotRequired[list[int]]
|
|
351
|
+
streaming_axis: NotRequired[int]
|
|
352
|
+
ring_depth: NotRequired[int]
|
|
353
|
+
predicted_accumulator_bytes: NotRequired[int]
|
|
354
|
+
count_buffer_bytes: NotRequired[int]
|
|
355
|
+
label_buffer_bytes: NotRequired[int]
|
|
356
|
+
accumulator_dtype: NotRequired[str]
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class AutomaticDetails(TypedDict):
|
|
360
|
+
class_ids: list[int]
|
|
361
|
+
padded_shape: list[int]
|
|
362
|
+
label_names: list[str] | None
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class InteractiveDetails(TypedDict):
|
|
366
|
+
automatic_branch: bool
|
|
367
|
+
effective_point_labels: list[int]
|
|
368
|
+
point_start: int
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
class NumericArrayArithmetic[ArrayT](Protocol):
|
|
372
|
+
"""Native and NumPy arrays accept corresponding arrays and NumPy scalars.
|
|
373
|
+
|
|
374
|
+
MLX 0.32.3 accepts the NumPy reduction scalars used by host-injected point
|
|
375
|
+
fixtures; its bundled array operator declaration omits those scalar types.
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
def __add__(self, other: ArrayValue | NumericScalar | float, /) -> ArrayT: ...
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""GELU with PyTorch 2.14.1's ARM float32 erf and exponential arithmetic."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, cast
|
|
6
|
+
|
|
7
|
+
from medmlx_core.typing import MetalKernel
|
|
8
|
+
|
|
9
|
+
from mlx_segment_ct._types import CoreRuntime
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from mlx.core import array
|
|
13
|
+
|
|
14
|
+
_GELU = """uint i = thread_position_in_grid.x;
|
|
15
|
+
if (i >= SIZE) return;
|
|
16
|
+
float x = X[i], z = x * 0.70710678118654752440f;
|
|
17
|
+
float t = 1.0f / fma(0.3275911f, abs(z), 1.0f);
|
|
18
|
+
float r = fma(1.061405429f, t, -1.453152027f);
|
|
19
|
+
r = fma(r, t, 1.421413741f);
|
|
20
|
+
r = fma(r, t, -0.284496736f);
|
|
21
|
+
r = fma(r, t, 0.254829592f);
|
|
22
|
+
float exponent = -(z * z), exponential;
|
|
23
|
+
if (exponent < -0x1.5d5e2ap+6f) {
|
|
24
|
+
exponential = exp(exponent);
|
|
25
|
+
} else {
|
|
26
|
+
// PyTorch's NEON exp_u20 range reduction and polynomial.
|
|
27
|
+
float n = round(exponent * 0x1.715476p+0f);
|
|
28
|
+
float reduced = fma(-n, 0x1.62e4p-1f, exponent);
|
|
29
|
+
reduced = fma(-n, 0x1.7f7d1cp-20f, reduced);
|
|
30
|
+
float scale = as_type<float>((uint(int(n)) << 23) + 0x3f800000u);
|
|
31
|
+
float square = reduced * reduced;
|
|
32
|
+
float p = fma(reduced, 0x1.0e4020p-7f, 0x1.573e2ep-5f);
|
|
33
|
+
float q = fma(reduced, 0x1.555e66p-3f, 0x1.fffdb6p-2f);
|
|
34
|
+
q = fma(p, square, q);
|
|
35
|
+
float polynomial = fma(q, square, 0x1.ffffecp-1f * reduced);
|
|
36
|
+
exponential = fma(polynomial, scale, scale);
|
|
37
|
+
}
|
|
38
|
+
float erf_value = fma(-t * exponential, r, 1.0f);
|
|
39
|
+
erf_value = as_type<float>(as_type<uint>(erf_value) ^ (as_type<uint>(z) & 0x80000000u));
|
|
40
|
+
Y[i] = (x * 0.5f) * (1.0f + erf_value);
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Gelu:
|
|
45
|
+
"""Source-ordered ARM GELU on Metal; no reference-runtime dependency.
|
|
46
|
+
|
|
47
|
+
The kernel reproduces PyTorch's vectorized erf (a polynomial fit) and exponential
|
|
48
|
+
in the reference's operation order, with floating-point contraction disabled
|
|
49
|
+
except for the explicit ``fma`` calls.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, mx: CoreRuntime) -> None:
|
|
53
|
+
self.mx = mx
|
|
54
|
+
self.kernel = cast(
|
|
55
|
+
MetalKernel,
|
|
56
|
+
mx.fast.metal_kernel(
|
|
57
|
+
name="vista_arm_gelu",
|
|
58
|
+
input_names=["X"],
|
|
59
|
+
output_names=["Y"],
|
|
60
|
+
source=_GELU,
|
|
61
|
+
header="#pragma clang fp contract(off)\n",
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def __call__(self, values: array) -> array:
|
|
66
|
+
"""Exact (erf-based) GELU of a float32 array; the result has the same shape."""
|
|
67
|
+
return self.kernel(
|
|
68
|
+
inputs=[values],
|
|
69
|
+
template=[("SIZE", values.size)],
|
|
70
|
+
grid=(values.size, 1, 1),
|
|
71
|
+
threadgroup=(256, 1, 1),
|
|
72
|
+
output_shapes=[tuple(values.shape)],
|
|
73
|
+
output_dtypes=[self.mx.float32],
|
|
74
|
+
)[0]
|