PyNutil 0.6.1__tar.gz → 0.6.2__tar.gz
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.
- {pynutil-0.6.1 → pynutil-0.6.2}/PKG-INFO +1 -1
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/__init__.py +4 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/file_operations.py +21 -19
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/loaders.py +8 -4
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/nifti_writer.py +10 -5
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/section_visualisation.py +8 -10
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/base.py +22 -10
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/damage.py +18 -10
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/deformation.py +19 -11
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/registry.py +42 -28
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/segmentation.py +29 -14
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/visualign_deformations.py +89 -50
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/analysis/data_analysis.py +55 -29
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/analysis/region_counting.py +21 -13
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/atlas_map.py +109 -58
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/pipeline/batch_processor.py +119 -31
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/pipeline/connected_components.py +60 -34
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/pipeline/section_processor.py +55 -28
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/reorientation.py +37 -19
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/utils.py +62 -32
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/results/extraction.py +8 -4
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil.egg-info/PKG-INFO +1 -1
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil.egg-info/SOURCES.txt +1 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/benchmarks/benchmark.py +4 -2
- pynutil-0.6.2/demos/per_section_example.py +37 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/gui/log_manager.py +12 -6
- {pynutil-0.6.1 → pynutil-0.6.2}/gui/settings_manager.py +10 -5
- {pynutil-0.6.1 → pynutil-0.6.2}/.gitattributes +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/.github/workflows/benchmark.yml +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/.github/workflows/build.yml +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/.github/workflows/main.yml +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/.github/workflows/publish-to-pypi.yml +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/.github/workflows/tests.yml +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/.gitignore +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/LICENSE +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/MANIFEST.in +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/config.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/context.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/image_series.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/__init__.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/atlas_loader.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/colormap.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/meshview_writer.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/reconstruct_dzi.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/volume_nifti.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/logging_utils.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/__init__.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/__init__.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/anchoring.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/analysis/__init__.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/analysis/aggregator.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/pipeline/__init__.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/section_volume.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/results/__init__.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/results/atlas.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/results/section.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/results/volume.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil.egg-info/dependency_links.txt +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil.egg-info/requires.txt +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil.egg-info/top_level.txt +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/README.md +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/benchmarks/profile_detailed.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/demos/basic_example.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/demos/basic_example_custom_atlas.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/demos/basic_example_intensity.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/demos/brainglobe_coordinate_example.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/demos/brainglobe_registration_usage.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/demos/coordinate_example.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/demos/plot_cells_in_brainrender.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/docs/assets/MeshView.mp4 +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/docs/assets/PyNutil_fig1.png +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/docs/assets/Siibra.mp4 +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/gui/Logo_PyNutil.ico +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/gui/Logo_PyNutil.png +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/gui/PyNutilGUI.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/gui/gui_smoke_test.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/gui/ui_components.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/gui/validation.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/gui/workers.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/misc/PyNutil_test.json +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/misc/PyNutil_test.waln +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/misc/Tiling_script.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/misc/create_test_data.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/misc/download_and_pack_atlases_allen.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/misc/download_and_pack_atlases_waxholm.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/misc/implement_allen_downsampling.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/misc/make_white_images.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/misc/reformat_label_files.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/misc/reorient_allen_volume.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/misc/waln_to_json.py +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/pyproject.toml +0 -0
- {pynutil-0.6.1 → pynutil-0.6.2}/setup.cfg +0 -0
|
@@ -4,7 +4,9 @@ from .processing.adapters import read_alignment
|
|
|
4
4
|
from .io.atlas_loader import load_custom_atlas
|
|
5
5
|
from .image_series import Section, ImageSeries
|
|
6
6
|
from .processing.pipeline.batch_processor import (
|
|
7
|
+
read_segmentation,
|
|
7
8
|
read_segmentation_dir,
|
|
9
|
+
read_image,
|
|
8
10
|
read_image_dir,
|
|
9
11
|
seg_to_coords,
|
|
10
12
|
image_to_coords,
|
|
@@ -24,7 +26,9 @@ __all__ = [
|
|
|
24
26
|
"load_custom_atlas",
|
|
25
27
|
"Section",
|
|
26
28
|
"ImageSeries",
|
|
29
|
+
"read_segmentation",
|
|
27
30
|
"read_segmentation_dir",
|
|
31
|
+
"read_image",
|
|
28
32
|
"read_image_dir",
|
|
29
33
|
"seg_to_coords",
|
|
30
34
|
"image_to_coords",
|
|
@@ -38,11 +38,6 @@ class SaveContext:
|
|
|
38
38
|
|
|
39
39
|
def _ensure_analysis_output_dirs(output_folder: str) -> None:
|
|
40
40
|
os.makedirs(output_folder, exist_ok=True)
|
|
41
|
-
for subdir in (
|
|
42
|
-
"whole_series_report",
|
|
43
|
-
"whole_series_meshview",
|
|
44
|
-
):
|
|
45
|
-
os.makedirs(f"{output_folder}/{subdir}", exist_ok=True)
|
|
46
41
|
|
|
47
42
|
|
|
48
43
|
def save_analysis_output(ctx: SaveContext, output_folder: str):
|
|
@@ -62,7 +57,7 @@ def save_analysis_output(ctx: SaveContext, output_folder: str):
|
|
|
62
57
|
if ctx.label_df is not None:
|
|
63
58
|
report_name = "intensity.csv" if ctx.is_intensity else "counts.csv"
|
|
64
59
|
ctx.label_df.to_csv(
|
|
65
|
-
f"{output_folder}/
|
|
60
|
+
f"{output_folder}/{ctx.prepend}{report_name}",
|
|
66
61
|
sep=";",
|
|
67
62
|
na_rep="",
|
|
68
63
|
index=False,
|
|
@@ -74,7 +69,7 @@ def save_analysis_output(ctx: SaveContext, output_folder: str):
|
|
|
74
69
|
)
|
|
75
70
|
|
|
76
71
|
if ctx.points is not None:
|
|
77
|
-
|
|
72
|
+
_save_meshview(ctx, output_folder)
|
|
78
73
|
|
|
79
74
|
_save_settings_json(ctx, output_folder)
|
|
80
75
|
|
|
@@ -88,13 +83,13 @@ def _save_settings_json(ctx: SaveContext, output_folder: str) -> None:
|
|
|
88
83
|
json.dump(ctx.settings_dict, f, indent=4)
|
|
89
84
|
|
|
90
85
|
|
|
91
|
-
def
|
|
92
|
-
"""Write
|
|
86
|
+
def _save_meshview(ctx: SaveContext, output_folder: str):
|
|
87
|
+
"""Write MeshView JSONs for pixels and centroids."""
|
|
93
88
|
write_hemi_points_to_meshview(
|
|
94
89
|
ctx.points,
|
|
95
90
|
ctx.point_labels,
|
|
96
91
|
ctx.points_hemi_labels,
|
|
97
|
-
f"{output_folder}/
|
|
92
|
+
f"{output_folder}/{ctx.prepend}pixels_meshview.json",
|
|
98
93
|
ctx.atlas_labels,
|
|
99
94
|
ctx.point_values,
|
|
100
95
|
colormap=ctx.colormap,
|
|
@@ -104,7 +99,7 @@ def _save_whole_series_meshview(ctx: SaveContext, output_folder: str):
|
|
|
104
99
|
ctx.objects,
|
|
105
100
|
ctx.object_labels,
|
|
106
101
|
ctx.objects_hemi_labels,
|
|
107
|
-
f"{output_folder}/
|
|
102
|
+
f"{output_folder}/{ctx.prepend}objects_meshview.json",
|
|
108
103
|
ctx.atlas_labels,
|
|
109
104
|
colormap=ctx.colormap,
|
|
110
105
|
)
|
|
@@ -121,13 +116,20 @@ def save_analysis(
|
|
|
121
116
|
):
|
|
122
117
|
"""Save analysis output to the specified directory.
|
|
123
118
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
output_folder : str
|
|
122
|
+
Directory to write output files.
|
|
123
|
+
result : ExtractionResult
|
|
124
|
+
ExtractionResult from coordinate extraction.
|
|
125
|
+
atlas_labels : DataFrame or AtlasData
|
|
126
|
+
Atlas labels DataFrame (or AtlasData — ``.labels`` used).
|
|
127
|
+
label_df : DataFrame, optional
|
|
128
|
+
Whole-series quantification DataFrame.
|
|
129
|
+
colormap : str
|
|
130
|
+
Colormap for MeshView intensity output.
|
|
131
|
+
settings_dict : dict, optional
|
|
132
|
+
Optional dict written to pynutil_settings.json.
|
|
131
133
|
"""
|
|
132
134
|
atlas_labels = resolve_atlas_labels(atlas_labels)
|
|
133
135
|
|
|
@@ -171,7 +173,7 @@ def save_analysis(
|
|
|
171
173
|
remapped["idx"] = remapped["original_idx"]
|
|
172
174
|
remapped = remapped.drop(columns=["original_idx"])
|
|
173
175
|
remapped.to_csv(
|
|
174
|
-
f"{output_folder}/
|
|
176
|
+
f"{output_folder}/counts.csv",
|
|
175
177
|
sep=";",
|
|
176
178
|
index=False,
|
|
177
179
|
)
|
|
@@ -203,11 +203,15 @@ def read_seg_file(file: str) -> np.ndarray:
|
|
|
203
203
|
def number_sections(filenames):
|
|
204
204
|
"""Extract section numbers from a list of filenames.
|
|
205
205
|
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
Parameters
|
|
207
|
+
----------
|
|
208
|
+
filenames : list
|
|
209
|
+
List of file paths.
|
|
208
210
|
|
|
209
|
-
Returns
|
|
210
|
-
|
|
211
|
+
Returns
|
|
212
|
+
-------
|
|
213
|
+
list
|
|
214
|
+
List of section numbers as integers.
|
|
211
215
|
"""
|
|
212
216
|
filenames = [os.path.basename(filename) for filename in filenames]
|
|
213
217
|
section_numbers = []
|
|
@@ -17,11 +17,16 @@ def write_nifti(
|
|
|
17
17
|
|
|
18
18
|
The header is written with both qform and sform set and units set to microns.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
volume : ndarray
|
|
23
|
+
3D volume array. Saved as uint8.
|
|
24
|
+
voxel_size_um : float
|
|
25
|
+
Isotropic voxel size in microns.
|
|
26
|
+
output_path : str
|
|
27
|
+
Output path without extension; ".nii.gz" is appended.
|
|
28
|
+
origin_offsets_um : ndarray, optional
|
|
29
|
+
Optional XYZ translation offsets in microns.
|
|
25
30
|
"""
|
|
26
31
|
|
|
27
32
|
if origin_offsets_um is None:
|
|
@@ -23,16 +23,14 @@ def _build_color_lookup(
|
|
|
23
23
|
) -> Tuple[str, Union[np.ndarray, Tuple[np.ndarray, np.ndarray]], Tuple[int, int, int]]:
|
|
24
24
|
"""Build a fast color lookup for atlas IDs.
|
|
25
25
|
|
|
26
|
-
Returns
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- atlas id 0 maps to black
|
|
35
|
-
- unmapped ids map to grey (default_colour)
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
tuple
|
|
29
|
+
(mode, lookup, default_colour) where *mode* is ``"direct"`` if
|
|
30
|
+
lookup is a (max_id+1, 3) uint8 LUT indexed directly by atlas ID,
|
|
31
|
+
or ``"sorted"`` if lookup is (ids_sorted, rgb_sorted) for use with
|
|
32
|
+
searchsorted. Atlas ID 0 maps to black; unmapped IDs map to
|
|
33
|
+
*default_colour*.
|
|
36
34
|
"""
|
|
37
35
|
if atlas_labels is None or len(atlas_labels) == 0:
|
|
38
36
|
lut = np.empty((2, 3), dtype=np.uint8)
|
|
@@ -154,11 +154,17 @@ class DeformationProvider(ABC):
|
|
|
154
154
|
def apply(self, data: RegistrationData) -> RegistrationData:
|
|
155
155
|
"""Add deformation functions to all slices.
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
data : RegistrationData
|
|
160
|
+
Registration data whose slices contain linear atlas anchoring.
|
|
161
|
+
The provider augments each slice with inverse and forward
|
|
162
|
+
non-linear deformation functions.
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
RegistrationData
|
|
167
|
+
With deformation functions added.
|
|
162
168
|
"""
|
|
163
169
|
pass
|
|
164
170
|
|
|
@@ -177,10 +183,16 @@ class DamageProvider(ABC):
|
|
|
177
183
|
def apply(self, data: RegistrationData) -> RegistrationData:
|
|
178
184
|
"""Add damage masks to all slices.
|
|
179
185
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
data : RegistrationData
|
|
189
|
+
Registration data whose slices contain linear anchoring and may
|
|
190
|
+
also contain deformation functions. The provider augments each
|
|
191
|
+
slice with a damage/exclusion mask.
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
RegistrationData
|
|
196
|
+
With damage masks added.
|
|
185
197
|
"""
|
|
186
198
|
pass
|
|
@@ -22,13 +22,19 @@ from ...io.loaders import load_json_file
|
|
|
22
22
|
def create_damage_mask(slice_info, section_grid, grid_spacing):
|
|
23
23
|
"""Create a binary damage mask from grid information in the given section.
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
slice_info : SliceInfo
|
|
28
|
+
SliceInfo with anchoring and registration dimensions.
|
|
29
|
+
section_grid : dict
|
|
30
|
+
Dictionary with grid data (grid, gridx, gridy).
|
|
31
|
+
grid_spacing : int
|
|
32
|
+
Space between grid marks.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
ndarray
|
|
37
|
+
Binary mask with damaged areas marked as 0.
|
|
32
38
|
"""
|
|
33
39
|
width = slice_info.width
|
|
34
40
|
height = slice_info.height
|
|
@@ -70,9 +76,11 @@ class QCAlignDamageProvider(DamageProvider):
|
|
|
70
76
|
def __init__(self, path: Optional[str] = None):
|
|
71
77
|
"""Initialize provider.
|
|
72
78
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
path : str, optional
|
|
82
|
+
Optional separate file with QCAlign damage grids.
|
|
83
|
+
If None, uses grids from the anchoring file.
|
|
76
84
|
"""
|
|
77
85
|
self.path = path
|
|
78
86
|
|
|
@@ -40,9 +40,11 @@ class VisuAlignDeformationProvider(DeformationProvider):
|
|
|
40
40
|
def __init__(self, path: Optional[str] = None):
|
|
41
41
|
"""Initialize provider.
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
path : str, optional
|
|
46
|
+
Optional separate file with VisuAlign markers.
|
|
47
|
+
If None, uses markers from the anchoring file.
|
|
46
48
|
"""
|
|
47
49
|
self.path = path
|
|
48
50
|
|
|
@@ -63,8 +65,10 @@ class VisuAlignDeformationProvider(DeformationProvider):
|
|
|
63
65
|
) -> Tuple[DeformationFunction, DeformationFunction]:
|
|
64
66
|
"""Create deformation functions from VisuAlign markers.
|
|
65
67
|
|
|
66
|
-
Returns
|
|
67
|
-
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
tuple
|
|
71
|
+
(inverse_deform, forward_deform) functions.
|
|
68
72
|
- inverse_deform: maps from deformed to original (transform_vec)
|
|
69
73
|
- forward_deform: maps from original to deformed (forwardtransform_vec)
|
|
70
74
|
"""
|
|
@@ -135,9 +139,11 @@ class BrainGlobeDeformationProvider(DeformationProvider):
|
|
|
135
139
|
def __init__(self, reg_dir: Optional[str] = None):
|
|
136
140
|
"""Initialize provider.
|
|
137
141
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
142
|
+
Parameters
|
|
143
|
+
----------
|
|
144
|
+
reg_dir : str, optional
|
|
145
|
+
Directory containing deformation field TIFFs.
|
|
146
|
+
If None, the directory is taken from SliceInfo metadata.
|
|
141
147
|
"""
|
|
142
148
|
self.reg_dir = reg_dir
|
|
143
149
|
|
|
@@ -146,9 +152,11 @@ class BrainGlobeDeformationProvider(DeformationProvider):
|
|
|
146
152
|
) -> Tuple[np.ndarray, np.ndarray]:
|
|
147
153
|
"""Load deformation field TIFFs from *reg_dir*.
|
|
148
154
|
|
|
149
|
-
Raises
|
|
150
|
-
|
|
151
|
-
|
|
155
|
+
Raises
|
|
156
|
+
------
|
|
157
|
+
FileNotFoundError
|
|
158
|
+
If deformation field TIFFs are missing, which should not happen
|
|
159
|
+
for a valid brainglobe registration output.
|
|
152
160
|
"""
|
|
153
161
|
f0_path = os.path.join(reg_dir, "deformation_field_0.tiff")
|
|
154
162
|
f1_path = os.path.join(reg_dir, "deformation_field_1.tiff")
|
|
@@ -7,6 +7,7 @@ and damage providers.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import os
|
|
10
11
|
from typing import Dict, Optional, Type
|
|
11
12
|
|
|
12
13
|
from .base import (
|
|
@@ -60,39 +61,52 @@ def read_alignment(
|
|
|
60
61
|
deformation_provider: Optional[DeformationProvider] = None,
|
|
61
62
|
damage_provider: Optional[DamageProvider] = None,
|
|
62
63
|
) -> RegistrationData:
|
|
63
|
-
"""Load registration data
|
|
64
|
+
"""Load registration data for downstream PyNutil processing.
|
|
64
65
|
|
|
65
66
|
This is the main entry point for loading registration data. It supports
|
|
66
67
|
mixing and matching different components.
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
path
|
|
72
|
+
Path to a registration file produced by a supported workflow such as
|
|
73
|
+
QuickNII, VisuAlign, or BrainGlobe registration.
|
|
74
|
+
loader_name
|
|
75
|
+
Explicit loader name to use. If ``None``, PyNutil attempts to detect
|
|
76
|
+
the appropriate loader automatically from the file.
|
|
77
|
+
apply_deformation
|
|
78
|
+
If ``True``, apply non-linear deformation when supported by the input
|
|
79
|
+
registration source. Set to ``False`` to keep only the linear
|
|
80
|
+
anchoring transform.
|
|
81
|
+
apply_damage
|
|
82
|
+
If ``True``, load and attach damage masks when available.
|
|
83
|
+
deformation_provider
|
|
84
|
+
Optional custom deformation provider to use instead of the default
|
|
85
|
+
provider selected for the detected registration type.
|
|
86
|
+
damage_provider
|
|
87
|
+
Optional custom damage provider to use instead of the default QCAlign
|
|
88
|
+
integration.
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
RegistrationData
|
|
93
|
+
Registration metadata and per-section transforms used by the
|
|
94
|
+
segmentation, intensity, and coordinate pipelines.
|
|
95
|
+
|
|
96
|
+
Examples
|
|
97
|
+
--------
|
|
98
|
+
Load a standard QUINT alignment file:
|
|
99
|
+
|
|
100
|
+
>>> registration = read_alignment("alignment.json")
|
|
101
|
+
|
|
102
|
+
Load BrainGlobe registration output in the same way:
|
|
103
|
+
|
|
104
|
+
>>> registration = read_alignment("brainglobe-registration.json")
|
|
95
105
|
"""
|
|
106
|
+
# Accept both str and os.PathLike (e.g. pathlib.Path); loaders below
|
|
107
|
+
# rely on string operations like ``path.endswith(...)``.
|
|
108
|
+
path = os.fspath(path)
|
|
109
|
+
|
|
96
110
|
# 1. Load anchoring
|
|
97
111
|
if loader_name:
|
|
98
112
|
loader = AnchoringLoaderRegistry.get(loader_name)
|
|
@@ -102,11 +102,16 @@ class SegmentationAdapter(ABC):
|
|
|
102
102
|
) -> np.ndarray:
|
|
103
103
|
"""Create a binary mask from the segmentation.
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
segmentation : ndarray
|
|
108
|
+
The loaded segmentation image.
|
|
109
|
+
pixel_id : list of int, optional
|
|
110
|
+
Pixel color to match (for color-based formats).
|
|
108
111
|
|
|
109
|
-
Returns
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
ndarray
|
|
110
115
|
Boolean 2D array where True indicates foreground.
|
|
111
116
|
"""
|
|
112
117
|
pass
|
|
@@ -120,13 +125,19 @@ class SegmentationAdapter(ABC):
|
|
|
120
125
|
) -> List[ObjectInfo]:
|
|
121
126
|
"""Extract individual objects from the segmentation.
|
|
122
127
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
segmentation : ndarray
|
|
131
|
+
The loaded segmentation image.
|
|
132
|
+
binary_mask : ndarray
|
|
133
|
+
The binary mask from create_binary_mask().
|
|
134
|
+
min_area : int, optional
|
|
135
|
+
Minimum object area threshold.
|
|
127
136
|
|
|
128
|
-
Returns
|
|
129
|
-
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
list of ObjectInfo
|
|
140
|
+
For each detected object.
|
|
130
141
|
"""
|
|
131
142
|
pass
|
|
132
143
|
|
|
@@ -136,11 +147,15 @@ class SegmentationAdapter(ABC):
|
|
|
136
147
|
Override this method for formats that use color-coded pixels.
|
|
137
148
|
Default implementation returns None (not applicable).
|
|
138
149
|
|
|
139
|
-
|
|
140
|
-
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
segmentation : ndarray
|
|
153
|
+
The loaded segmentation image.
|
|
141
154
|
|
|
142
|
-
Returns
|
|
143
|
-
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
list of int or None
|
|
158
|
+
[R, G, B] values or None if not applicable.
|
|
144
159
|
"""
|
|
145
160
|
return None
|
|
146
161
|
|