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.
Files changed (92) hide show
  1. {pynutil-0.6.1 → pynutil-0.6.2}/PKG-INFO +1 -1
  2. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/__init__.py +4 -0
  3. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/file_operations.py +21 -19
  4. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/loaders.py +8 -4
  5. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/nifti_writer.py +10 -5
  6. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/section_visualisation.py +8 -10
  7. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/base.py +22 -10
  8. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/damage.py +18 -10
  9. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/deformation.py +19 -11
  10. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/registry.py +42 -28
  11. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/segmentation.py +29 -14
  12. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/visualign_deformations.py +89 -50
  13. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/analysis/data_analysis.py +55 -29
  14. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/analysis/region_counting.py +21 -13
  15. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/atlas_map.py +109 -58
  16. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/pipeline/batch_processor.py +119 -31
  17. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/pipeline/connected_components.py +60 -34
  18. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/pipeline/section_processor.py +55 -28
  19. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/reorientation.py +37 -19
  20. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/utils.py +62 -32
  21. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/results/extraction.py +8 -4
  22. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil.egg-info/PKG-INFO +1 -1
  23. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil.egg-info/SOURCES.txt +1 -0
  24. {pynutil-0.6.1 → pynutil-0.6.2}/benchmarks/benchmark.py +4 -2
  25. pynutil-0.6.2/demos/per_section_example.py +37 -0
  26. {pynutil-0.6.1 → pynutil-0.6.2}/gui/log_manager.py +12 -6
  27. {pynutil-0.6.1 → pynutil-0.6.2}/gui/settings_manager.py +10 -5
  28. {pynutil-0.6.1 → pynutil-0.6.2}/.gitattributes +0 -0
  29. {pynutil-0.6.1 → pynutil-0.6.2}/.github/workflows/benchmark.yml +0 -0
  30. {pynutil-0.6.1 → pynutil-0.6.2}/.github/workflows/build.yml +0 -0
  31. {pynutil-0.6.1 → pynutil-0.6.2}/.github/workflows/main.yml +0 -0
  32. {pynutil-0.6.1 → pynutil-0.6.2}/.github/workflows/publish-to-pypi.yml +0 -0
  33. {pynutil-0.6.1 → pynutil-0.6.2}/.github/workflows/tests.yml +0 -0
  34. {pynutil-0.6.1 → pynutil-0.6.2}/.gitignore +0 -0
  35. {pynutil-0.6.1 → pynutil-0.6.2}/LICENSE +0 -0
  36. {pynutil-0.6.1 → pynutil-0.6.2}/MANIFEST.in +0 -0
  37. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/config.py +0 -0
  38. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/context.py +0 -0
  39. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/image_series.py +0 -0
  40. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/__init__.py +0 -0
  41. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/atlas_loader.py +0 -0
  42. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/colormap.py +0 -0
  43. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/meshview_writer.py +0 -0
  44. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/reconstruct_dzi.py +0 -0
  45. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/io/volume_nifti.py +0 -0
  46. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/logging_utils.py +0 -0
  47. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/__init__.py +0 -0
  48. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/__init__.py +0 -0
  49. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/adapters/anchoring.py +0 -0
  50. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/analysis/__init__.py +0 -0
  51. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/analysis/aggregator.py +0 -0
  52. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/pipeline/__init__.py +0 -0
  53. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/processing/section_volume.py +0 -0
  54. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/results/__init__.py +0 -0
  55. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/results/atlas.py +0 -0
  56. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/results/section.py +0 -0
  57. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil/results/volume.py +0 -0
  58. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil.egg-info/dependency_links.txt +0 -0
  59. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil.egg-info/requires.txt +0 -0
  60. {pynutil-0.6.1 → pynutil-0.6.2}/PyNutil.egg-info/top_level.txt +0 -0
  61. {pynutil-0.6.1 → pynutil-0.6.2}/README.md +0 -0
  62. {pynutil-0.6.1 → pynutil-0.6.2}/benchmarks/profile_detailed.py +0 -0
  63. {pynutil-0.6.1 → pynutil-0.6.2}/demos/basic_example.py +0 -0
  64. {pynutil-0.6.1 → pynutil-0.6.2}/demos/basic_example_custom_atlas.py +0 -0
  65. {pynutil-0.6.1 → pynutil-0.6.2}/demos/basic_example_intensity.py +0 -0
  66. {pynutil-0.6.1 → pynutil-0.6.2}/demos/brainglobe_coordinate_example.py +0 -0
  67. {pynutil-0.6.1 → pynutil-0.6.2}/demos/brainglobe_registration_usage.py +0 -0
  68. {pynutil-0.6.1 → pynutil-0.6.2}/demos/coordinate_example.py +0 -0
  69. {pynutil-0.6.1 → pynutil-0.6.2}/demos/plot_cells_in_brainrender.py +0 -0
  70. {pynutil-0.6.1 → pynutil-0.6.2}/docs/assets/MeshView.mp4 +0 -0
  71. {pynutil-0.6.1 → pynutil-0.6.2}/docs/assets/PyNutil_fig1.png +0 -0
  72. {pynutil-0.6.1 → pynutil-0.6.2}/docs/assets/Siibra.mp4 +0 -0
  73. {pynutil-0.6.1 → pynutil-0.6.2}/gui/Logo_PyNutil.ico +0 -0
  74. {pynutil-0.6.1 → pynutil-0.6.2}/gui/Logo_PyNutil.png +0 -0
  75. {pynutil-0.6.1 → pynutil-0.6.2}/gui/PyNutilGUI.py +0 -0
  76. {pynutil-0.6.1 → pynutil-0.6.2}/gui/gui_smoke_test.py +0 -0
  77. {pynutil-0.6.1 → pynutil-0.6.2}/gui/ui_components.py +0 -0
  78. {pynutil-0.6.1 → pynutil-0.6.2}/gui/validation.py +0 -0
  79. {pynutil-0.6.1 → pynutil-0.6.2}/gui/workers.py +0 -0
  80. {pynutil-0.6.1 → pynutil-0.6.2}/misc/PyNutil_test.json +0 -0
  81. {pynutil-0.6.1 → pynutil-0.6.2}/misc/PyNutil_test.waln +0 -0
  82. {pynutil-0.6.1 → pynutil-0.6.2}/misc/Tiling_script.py +0 -0
  83. {pynutil-0.6.1 → pynutil-0.6.2}/misc/create_test_data.py +0 -0
  84. {pynutil-0.6.1 → pynutil-0.6.2}/misc/download_and_pack_atlases_allen.py +0 -0
  85. {pynutil-0.6.1 → pynutil-0.6.2}/misc/download_and_pack_atlases_waxholm.py +0 -0
  86. {pynutil-0.6.1 → pynutil-0.6.2}/misc/implement_allen_downsampling.py +0 -0
  87. {pynutil-0.6.1 → pynutil-0.6.2}/misc/make_white_images.py +0 -0
  88. {pynutil-0.6.1 → pynutil-0.6.2}/misc/reformat_label_files.py +0 -0
  89. {pynutil-0.6.1 → pynutil-0.6.2}/misc/reorient_allen_volume.py +0 -0
  90. {pynutil-0.6.1 → pynutil-0.6.2}/misc/waln_to_json.py +0 -0
  91. {pynutil-0.6.1 → pynutil-0.6.2}/pyproject.toml +0 -0
  92. {pynutil-0.6.1 → pynutil-0.6.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyNutil
3
- Version: 0.6.1
3
+ Version: 0.6.2
4
4
  Summary: a package to quantify atlas registered brain data
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/Neural-Systems-at-UIO/PyNutil
@@ -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}/whole_series_report/{ctx.prepend}{report_name}",
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
- _save_whole_series_meshview(ctx, output_folder)
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 _save_whole_series_meshview(ctx: SaveContext, output_folder: str):
92
- """Write whole-series MeshView JSONs for pixels and centroids."""
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}/whole_series_meshview/{ctx.prepend}pixels_meshview.json",
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}/whole_series_meshview/{ctx.prepend}objects_meshview.json",
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
- Args:
125
- output_folder: Directory to write output files.
126
- result: ExtractionResult from coordinate extraction.
127
- atlas_labels: Atlas labels DataFrame (or AtlasData — ``.labels`` used).
128
- label_df: Whole-series quantification DataFrame.
129
- colormap: Colormap for MeshView intensity output.
130
- settings_dict: Optional dict written to pynutil_settings.json.
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}/whole_series_report/counts.csv",
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
- Args:
207
- filenames (list): List of file paths.
206
+ Parameters
207
+ ----------
208
+ filenames : list
209
+ List of file paths.
208
210
 
209
- Returns:
210
- list: List of section numbers as integers.
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
- Args:
21
- volume: 3D volume array. Saved as uint8.
22
- voxel_size_um: Isotropic voxel size in microns.
23
- output_path: Output path without extension; ".nii.gz" is appended.
24
- origin_offsets_um: Optional XYZ translation offsets in microns.
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
- (mode, lookup, default_colour)
28
-
29
- mode:
30
- - "direct": lookup is a (max_id+1, 3) uint8 LUT; index directly by atlas ID
31
- - "sorted": lookup is (ids_sorted, rgb_sorted); use searchsorted
32
-
33
- This keeps behaviour consistent with the previous implementation:
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
- Args:
158
- data: RegistrationData with basic anchoring.
159
-
160
- Returns:
161
- RegistrationData with deformation functions added.
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
- Args:
181
- data: RegistrationData (may already have deformation).
182
-
183
- Returns:
184
- RegistrationData with damage masks added.
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
- Args:
26
- slice_info: SliceInfo with anchoring and registration dimensions.
27
- section_grid (dict): Dictionary with grid data (grid, gridx, gridy).
28
- grid_spacing (int): Space between grid marks.
29
-
30
- Returns:
31
- ndarray: Binary mask with damaged areas marked as 0.
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
- Args:
74
- path: Optional separate file with QCAlign damage grids.
75
- If None, uses grids from the anchoring file.
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
- Args:
44
- path: Optional separate file with VisuAlign markers.
45
- If None, uses markers from the anchoring file.
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
- Tuple of (inverse_deform, forward_deform) functions.
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
- Args:
139
- reg_dir: Directory containing deformation field TIFFs.
140
- If None, the directory is taken from SliceInfo metadata.
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
- FileNotFoundError: If deformation field TIFFs are missing, which
151
- should not happen for a valid brainglobe registration output.
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 with composable pipeline.
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
- Args:
69
- path: Path to the registration file.
70
- loader_name: Explicit loader name, or None for auto-detection.
71
- apply_deformation: Whether to apply deformation from the file.
72
- Set False to use only linear anchoring.
73
- apply_damage: Whether to apply damage masks from the file.
74
- deformation_provider: Custom deformation provider to use instead of
75
- the default (VisuAlign for QUINT files).
76
- damage_provider: Custom damage provider to use instead of
77
- the default (QCAlign for QUINT files).
78
-
79
- Returns:
80
- RegistrationData with all components applied.
81
-
82
- Examples:
83
- # Standard QUINT workflow
84
- data = read_alignment("alignment.json")
85
-
86
- # Linear only (no VisuAlign deformation)
87
- data = read_alignment("alignment.json", apply_deformation=False)
88
-
89
- # Separate anchoring and damage files
90
- from .damage import QCAlignDamageProvider
91
- data = read_alignment(
92
- "quicknii.json",
93
- damage_provider=QCAlignDamageProvider("qcalign_output.json")
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
- Args:
106
- segmentation: The loaded segmentation image.
107
- pixel_id: Optional pixel color to match (for color-based formats).
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
- Args:
124
- segmentation: The loaded segmentation image.
125
- binary_mask: The binary mask from create_binary_mask().
126
- min_area: Minimum object area threshold.
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
- List of ObjectInfo for each detected object.
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
- Args:
140
- segmentation: The loaded segmentation image.
150
+ Parameters
151
+ ----------
152
+ segmentation : ndarray
153
+ The loaded segmentation image.
141
154
 
142
- Returns:
143
- List of [R, G, B] values or None if not applicable.
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