MNutils 1.2.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.
- mnutils/GEExam.py +495 -0
- mnutils/GESeries.py +1578 -0
- mnutils/__init__.py +54 -0
- mnutils/_scripts.py +177 -0
- mnutils/fitting/AMARES.py +831 -0
- mnutils/fitting/__init__.py +5 -0
- mnutils/matlab/__init__.py +13 -0
- mnutils/matlab/setup_matlab.py +116 -0
- mnutils/plotting/__init__.py +38 -0
- mnutils/plotting/_widgets/__init__.py +5 -0
- mnutils/plotting/_widgets/_html.py +34 -0
- mnutils/plotting/_widgets/_shared/__init__.py +50 -0
- mnutils/plotting/_widgets/_shared/dom.js +124 -0
- mnutils/plotting/_widgets/_shared/theme.css +140 -0
- mnutils/plotting/_widgets/image_grid.css +202 -0
- mnutils/plotting/_widgets/image_grid.js +212 -0
- mnutils/plotting/_widgets/image_grid.py +86 -0
- mnutils/plotting/_widgets/mrsi_inspector.css +376 -0
- mnutils/plotting/_widgets/mrsi_inspector.js +681 -0
- mnutils/plotting/_widgets/mrsi_inspector.py +70 -0
- mnutils/plotting/_widgets/slice_viewer.css +38 -0
- mnutils/plotting/_widgets/slice_viewer.js +41 -0
- mnutils/plotting/_widgets/slice_viewer.py +35 -0
- mnutils/plotting/images.py +1399 -0
- mnutils/plotting/saving.py +138 -0
- mnutils/plotting/spectra.py +357 -0
- mnutils/py.typed +0 -0
- mnutils/testing/__init__.py +3 -0
- mnutils/testing/_spectra.py +338 -0
- mnutils/testing/synthetic.py +373 -0
- mnutils/utils/__init__.py +25 -0
- mnutils/utils/data_loaders.py +218 -0
- mnutils/utils/file_helpers.py +708 -0
- mnutils/utils/images.py +202 -0
- mnutils/utils/nifti.py +379 -0
- mnutils/utils/spectra.py +149 -0
- mnutils-1.2.0.dist-info/METADATA +161 -0
- mnutils-1.2.0.dist-info/RECORD +41 -0
- mnutils-1.2.0.dist-info/WHEEL +4 -0
- mnutils-1.2.0.dist-info/entry_points.txt +7 -0
- mnutils-1.2.0.dist-info/licenses/LICENSE +28 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from ._html import render_html
|
|
5
|
+
from ._shared import load_css, load_esm
|
|
6
|
+
|
|
7
|
+
_DIR = Path(__file__).parent
|
|
8
|
+
_JS = load_esm(_DIR / "mrsi_inspector.js")
|
|
9
|
+
_CSS = load_css(_DIR / "mrsi_inspector.css")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MRSIVoxelInspectorWidget:
|
|
13
|
+
"""Interactive T1 + MRSI overlay with click/keyboard voxel selection.
|
|
14
|
+
|
|
15
|
+
Every anatomical slice is a pre-rendered WebP frame (T1 + MRSI overlay, no
|
|
16
|
+
voxel box baked in). The voxel outline and the spectrum plot are drawn
|
|
17
|
+
client-side from data embedded directly in the displayed HTML: the
|
|
18
|
+
display affine (for projecting a voxel index to display pixel
|
|
19
|
+
coordinates, and for inverting a click back to a voxel index) and a
|
|
20
|
+
compressed spectra buffer (one spectrum per (x, y, MRSI slice) triple, in
|
|
21
|
+
the units already selected by the caller -- magnitude, autophased, or
|
|
22
|
+
real). None of this requires a Python kernel after the widget is
|
|
23
|
+
displayed.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
*,
|
|
29
|
+
left_frames: list[bytes],
|
|
30
|
+
slice_titles: list[str],
|
|
31
|
+
n_anat_slices: int,
|
|
32
|
+
initial_slice: int,
|
|
33
|
+
image_width: int,
|
|
34
|
+
image_height: int,
|
|
35
|
+
mrsi_to_display_affine: list[float],
|
|
36
|
+
display_to_mrsi_affine: list[float],
|
|
37
|
+
grid_shape: tuple[int, int, int],
|
|
38
|
+
mrsi_dims: tuple[int, int],
|
|
39
|
+
initial_voxel: tuple[int, int, int],
|
|
40
|
+
ppm: list[float],
|
|
41
|
+
spectra_bytes: bytes,
|
|
42
|
+
spectra_scale: float,
|
|
43
|
+
spectra_min: float,
|
|
44
|
+
spectra_max: float,
|
|
45
|
+
npts: int,
|
|
46
|
+
spectrum_label: str = "Spectrum",
|
|
47
|
+
) -> None:
|
|
48
|
+
self._data = {
|
|
49
|
+
"left_frames": [base64.b64encode(frame).decode("ascii") for frame in left_frames],
|
|
50
|
+
"slice_titles": slice_titles,
|
|
51
|
+
"n_anat_slices": n_anat_slices,
|
|
52
|
+
"initial_slice": initial_slice,
|
|
53
|
+
"image_width": image_width,
|
|
54
|
+
"image_height": image_height,
|
|
55
|
+
"mrsi_to_display_affine": mrsi_to_display_affine,
|
|
56
|
+
"display_to_mrsi_affine": display_to_mrsi_affine,
|
|
57
|
+
"grid_shape": list(grid_shape),
|
|
58
|
+
"mrsi_dims": list(mrsi_dims),
|
|
59
|
+
"initial_voxel": list(initial_voxel),
|
|
60
|
+
"ppm": ppm,
|
|
61
|
+
"spectra_bytes": base64.b64encode(spectra_bytes).decode("ascii"),
|
|
62
|
+
"spectra_scale": spectra_scale,
|
|
63
|
+
"spectra_min": spectra_min,
|
|
64
|
+
"spectra_max": spectra_max,
|
|
65
|
+
"npts": npts,
|
|
66
|
+
"spectrum_label": spectrum_label,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
def _repr_html_(self) -> str:
|
|
70
|
+
return render_html(_JS, _CSS, self._data)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
.mnutils-slice-viewer {
|
|
2
|
+
margin-top: 1em;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.mnutils-slice-viewer-bar {
|
|
6
|
+
justify-content: flex-start;
|
|
7
|
+
border-bottom: 1px solid var(--mnu-border-soft);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.mnutils-slice-viewer-bar .mnu-lbl,
|
|
11
|
+
.mnutils-slice-viewer-bar .mnu-readout {
|
|
12
|
+
font-size: 1.1em;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.mnutils-slice-viewer-bar .mnu-readout {
|
|
16
|
+
font-family: inherit;
|
|
17
|
+
font-weight: 700;
|
|
18
|
+
line-height: inherit;
|
|
19
|
+
min-width: 0;
|
|
20
|
+
color: var(--mnu-fg-strong);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.mnutils-slice-viewer-bar .mnu-slider {
|
|
24
|
+
flex: none;
|
|
25
|
+
width: min(100%, 320px);
|
|
26
|
+
height: 1.3em;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.mnutils-slice-viewer-panel {
|
|
30
|
+
display: flex;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
border-bottom: none;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.mnutils-slice-viewer-image {
|
|
36
|
+
max-width: 100%;
|
|
37
|
+
height: auto;
|
|
38
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
function renderWidget(data, el) {
|
|
2
|
+
const frames = data.frames;
|
|
3
|
+
const nSlices = data.n_slices;
|
|
4
|
+
const label = data.slice_label;
|
|
5
|
+
const initialIndex = data.initial_index;
|
|
6
|
+
|
|
7
|
+
const urls = frames.map(
|
|
8
|
+
(b64) => URL.createObjectURL(new Blob([b64ToBytes(b64)], { type: "image/png" })),
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const viewer = document.createElement("div");
|
|
12
|
+
viewer.className = "mnu-viewer mnutils-slice-viewer";
|
|
13
|
+
|
|
14
|
+
const panel = document.createElement("div");
|
|
15
|
+
panel.className = "mnu-panel mnutils-slice-viewer-panel";
|
|
16
|
+
|
|
17
|
+
const img = document.createElement("img");
|
|
18
|
+
img.className = "mnutils-slice-viewer-image";
|
|
19
|
+
img.src = urls[initialIndex];
|
|
20
|
+
|
|
21
|
+
panel.append(img);
|
|
22
|
+
|
|
23
|
+
const { lbl, slider, readout } = makeSliceSlider({
|
|
24
|
+
min: 0,
|
|
25
|
+
max: nSlices - 1,
|
|
26
|
+
value: initialIndex,
|
|
27
|
+
label,
|
|
28
|
+
formatReadout: (idx) => String(idx),
|
|
29
|
+
onInput: (idx) => {
|
|
30
|
+
img.src = urls[idx];
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const bar = makeBar(makeGroup(lbl, readout), slider);
|
|
35
|
+
bar.classList.add("mnutils-slice-viewer-bar");
|
|
36
|
+
|
|
37
|
+
viewer.append(bar, panel);
|
|
38
|
+
el.append(viewer);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default renderWidget;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from ._html import render_html
|
|
5
|
+
from ._shared import load_css, load_esm
|
|
6
|
+
|
|
7
|
+
_DIR = Path(__file__).parent
|
|
8
|
+
_JS = load_esm(_DIR / "slice_viewer.js")
|
|
9
|
+
_CSS = load_css(_DIR / "slice_viewer.css")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SliceViewerWidget:
|
|
13
|
+
"""Slider-driven viewer over a stack of pre-rendered PNG frames.
|
|
14
|
+
|
|
15
|
+
All frames are embedded directly in the displayed HTML, so the slider
|
|
16
|
+
works purely client-side -- no Python kernel is needed after the widget
|
|
17
|
+
is displayed, which is what makes it survive a static docs build.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
frames: list[bytes],
|
|
23
|
+
n_slices: int,
|
|
24
|
+
initial_index: int = 0,
|
|
25
|
+
slice_label: str = "Slice",
|
|
26
|
+
) -> None:
|
|
27
|
+
self._data = {
|
|
28
|
+
"frames": [base64.b64encode(frame).decode("ascii") for frame in frames],
|
|
29
|
+
"n_slices": n_slices,
|
|
30
|
+
"initial_index": initial_index,
|
|
31
|
+
"slice_label": slice_label,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def _repr_html_(self) -> str:
|
|
35
|
+
return render_html(_JS, _CSS, self._data)
|