lesiondiffusion-mlx 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.

Potentially problematic release.


This version of lesiondiffusion-mlx might be problematic. Click here for more details.

@@ -0,0 +1,66 @@
1
+ """Native MLX implementation of LesionDiffusion, with lazy public imports."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib import import_module
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from lesiondiffusion_mlx._schedules import (
10
+ lesion_diffusion_timesteps as lesion_diffusion_timesteps,
11
+ )
12
+ from lesiondiffusion_mlx.mlx_model import (
13
+ LINET_CONFIG as LINET_CONFIG,
14
+ )
15
+ from lesiondiffusion_mlx.mlx_model import (
16
+ LMNET_CONFIG as LMNET_CONFIG,
17
+ )
18
+ from lesiondiffusion_mlx.mlx_model import (
19
+ BiomedTextEncoder as BiomedTextEncoder,
20
+ )
21
+ from lesiondiffusion_mlx.mlx_model import (
22
+ LdmUnet as LdmUnet,
23
+ )
24
+ from lesiondiffusion_mlx.mlx_model import (
25
+ SourceLayers as SourceLayers,
26
+ )
27
+ from lesiondiffusion_mlx.mlx_model import (
28
+ UnetConfig as UnetConfig,
29
+ )
30
+ from lesiondiffusion_mlx.mlx_model import (
31
+ VqAutoencoder as VqAutoencoder,
32
+ )
33
+ from lesiondiffusion_mlx.mlx_sampling import DdimSampler as DdimSampler
34
+ from lesiondiffusion_mlx.mlx_weights import write_conversion as write_conversion
35
+
36
+ __version__ = "0.2.1"
37
+
38
+ __all__ = [
39
+ "LINET_CONFIG",
40
+ "LMNET_CONFIG",
41
+ "BiomedTextEncoder",
42
+ "DdimSampler",
43
+ "LdmUnet",
44
+ "SourceLayers",
45
+ "UnetConfig",
46
+ "VqAutoencoder",
47
+ "lesion_diffusion_timesteps",
48
+ "write_conversion",
49
+ ]
50
+
51
+
52
+ def __getattr__(name: str) -> object:
53
+ if name not in __all__:
54
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
55
+ module = (
56
+ "_schedules"
57
+ if name == "lesion_diffusion_timesteps"
58
+ else "mlx_weights"
59
+ if name == "write_conversion"
60
+ else "mlx_sampling"
61
+ if name == "DdimSampler"
62
+ else "mlx_model"
63
+ )
64
+ value = getattr(import_module(f"lesiondiffusion_mlx.{module}"), name)
65
+ globals()[name] = value
66
+ return value
@@ -0,0 +1,24 @@
1
+ """Host arrays shared by conversion, NIfTI I/O and reference tooling."""
2
+
3
+ import numpy as np
4
+
5
+ type HostArray = np.ndarray[tuple[int, ...], np.dtype[np.generic]]
6
+ type FloatArray = np.ndarray[tuple[int, ...], np.dtype[np.float32]]
7
+ type BoolArray = np.ndarray[tuple[int, ...], np.dtype[np.bool_]]
8
+ type NumericScalar = (
9
+ np.bool_
10
+ | np.int8
11
+ | np.int16
12
+ | np.int32
13
+ | np.int64
14
+ | np.uint8
15
+ | np.uint16
16
+ | np.uint32
17
+ | np.uint64
18
+ | np.float16
19
+ | np.float32
20
+ | np.float64
21
+ | np.complex64
22
+ | np.complex128
23
+ )
24
+ type NumericArray = np.ndarray[tuple[int, ...], np.dtype[NumericScalar]]
@@ -0,0 +1,73 @@
1
+ """The NIfTI surface used here, verified against NiBabel's image/header APIs."""
2
+
3
+ from os import PathLike
4
+ from typing import Literal, Protocol, cast
5
+
6
+ import nibabel
7
+ import numpy as np
8
+ from nibabel.filebasedimages import ImageFileError as ImageFileError
9
+
10
+ from lesiondiffusion_mlx._arrays import HostArray
11
+
12
+ type PathArg = str | PathLike[str]
13
+ type SpatialAffine = np.ndarray[tuple[int, ...], np.dtype[np.float64]]
14
+
15
+
16
+ class ArrayBuffer(Protocol):
17
+ def __array__(
18
+ self, dtype: np.dtype[np.generic] | None = None, copy: bool | None = None
19
+ ) -> HostArray: ...
20
+
21
+
22
+ class NiftiHeader(Protocol):
23
+ def get_zooms(self) -> tuple[float, ...]: ...
24
+ def get_xyzt_units(self) -> tuple[str, str]: ...
25
+ def set_xyzt_units(self, xyz: str, t: str) -> None: ...
26
+ def set_data_dtype(self, dtype: type[np.generic]) -> None: ...
27
+
28
+
29
+ class HeaderFactory(Protocol):
30
+ def from_header(self, header: NiftiHeader) -> NiftiHeader: ...
31
+
32
+
33
+ class NiftiImage(Protocol):
34
+ shape: tuple[int, ...]
35
+ affine: SpatialAffine
36
+ dataobj: ArrayBuffer | HostArray
37
+ header: NiftiHeader
38
+
39
+ def get_qform(self, *, coded: Literal[True]) -> tuple[SpatialAffine | None, int]: ...
40
+ def get_sform(self, *, coded: Literal[True]) -> tuple[SpatialAffine | None, int]: ...
41
+ def set_qform(self, affine: HostArray, code: int) -> None: ...
42
+ def set_sform(self, affine: HostArray, code: int) -> None: ...
43
+
44
+
45
+ class ImageFactory(Protocol):
46
+ def __call__(
47
+ self, dataobj: HostArray, affine: HostArray, header: NiftiHeader | None = None
48
+ ) -> NiftiImage: ...
49
+
50
+
51
+ class OrientationFunctions(Protocol):
52
+ """The subset of ``nibabel.orientations`` used to resample to RAS 1 mm."""
53
+
54
+ def axcodes2ornt(self, axcodes: tuple[str, str, str]) -> HostArray: ...
55
+ def ornt_transform(self, start_ornt: HostArray, end_ornt: HostArray) -> HostArray: ...
56
+ def apply_orientation(self, data: HostArray, ornt: HostArray) -> HostArray: ...
57
+ def inv_ornt_aff(self, ornt: HostArray, shape: tuple[int, ...]) -> HostArray: ...
58
+
59
+
60
+ class Nibabel(Protocol):
61
+ Nifti1Header: HeaderFactory
62
+ Nifti1Image: ImageFactory
63
+ orientations: OrientationFunctions
64
+
65
+ def load(self, filename: PathArg) -> NiftiImage: ...
66
+ def save(self, image: NiftiImage, filename: PathArg) -> None: ...
67
+ def aff2axcodes(self, affine: HostArray) -> tuple[str | None, ...]: ...
68
+ def io_orientation(self, affine: HostArray) -> HostArray: ...
69
+
70
+
71
+ # This adapter is used only for .nii/.nii.gz inputs and NIfTI output; the wider
72
+ # NiBabel loader supports unrelated image classes outside the runner contract.
73
+ nib = cast(Nibabel, nibabel)
@@ -0,0 +1,38 @@
1
+ """Pinned upstream uniform DDIM timestep schedule, with explicit invalid-index errors."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ def _is_runtime_integer(value: object) -> bool:
7
+ return isinstance(value, int) and not isinstance(value, bool)
8
+
9
+
10
+ def lesion_diffusion_timesteps(steps: int, total: int = 1000) -> tuple[int, ...]:
11
+ """Return the pinned upstream uniform DDIM timestep indices, validated.
12
+
13
+ The indices are ``value + 1`` for ``value`` in ``range(0, total, total // steps)``,
14
+ in ascending order; the sampler walks them in reverse. The tuple has at least
15
+ ``steps`` entries and has more when ``steps`` does not divide ``total`` (7 entries
16
+ for ``steps=6``), so callers report ``len(...)`` as the actual step count. An
17
+ out-of-range index is rejected rather than clipped, to keep the schedule upstream's.
18
+
19
+ Args:
20
+ steps: Requested number of DDIM steps, a positive integer (not a bool).
21
+ total: Length of the training noise schedule (``alphas_cumprod``).
22
+
23
+ Raises:
24
+ ValueError: If ``steps`` or ``total`` is not an integer, ``steps < 1``,
25
+ ``total < steps``, or the last index would reach ``total`` (for example
26
+ ``steps=3`` or ``steps=1000`` with ``total=1000``).
27
+ """
28
+ if not _is_runtime_integer(steps) or steps < 1:
29
+ raise ValueError("LesionDiffusion DDIM steps must be a positive integer")
30
+ if not _is_runtime_integer(total) or total < steps:
31
+ raise ValueError("LesionDiffusion DDIM schedule must contain at least as many timesteps")
32
+ timesteps = tuple(value + 1 for value in range(0, total, total // steps))
33
+ if timesteps[-1] >= total:
34
+ raise ValueError(
35
+ f"LesionDiffusion DDIM steps {steps} produce out-of-bounds indices "
36
+ f"for a {total}-timestep schedule; choose another diffusion_steps value"
37
+ )
38
+ return timesteps