Rhapso 0.1.92__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.
- rhapso-0.1.92/LICENSE +21 -0
- rhapso-0.1.92/PKG-INFO +39 -0
- rhapso-0.1.92/README.md +361 -0
- rhapso-0.1.92/Rhapso/__init__.py +1 -0
- rhapso-0.1.92/Rhapso/data_prep/__init__.py +2 -0
- rhapso-0.1.92/Rhapso/data_prep/n5_reader.py +188 -0
- rhapso-0.1.92/Rhapso/data_prep/s3_big_stitcher_reader.py +55 -0
- rhapso-0.1.92/Rhapso/data_prep/xml_to_dataframe.py +215 -0
- rhapso-0.1.92/Rhapso/detection/__init__.py +5 -0
- rhapso-0.1.92/Rhapso/detection/advanced_refinement.py +203 -0
- rhapso-0.1.92/Rhapso/detection/difference_of_gaussian.py +324 -0
- rhapso-0.1.92/Rhapso/detection/image_reader.py +117 -0
- rhapso-0.1.92/Rhapso/detection/metadata_builder.py +130 -0
- rhapso-0.1.92/Rhapso/detection/overlap_detection.py +327 -0
- rhapso-0.1.92/Rhapso/detection/points_validation.py +49 -0
- rhapso-0.1.92/Rhapso/detection/save_interest_points.py +265 -0
- rhapso-0.1.92/Rhapso/detection/view_transform_models.py +67 -0
- rhapso-0.1.92/Rhapso/fusion/__init__.py +0 -0
- rhapso-0.1.92/Rhapso/fusion/affine_fusion/__init__.py +2 -0
- rhapso-0.1.92/Rhapso/fusion/affine_fusion/blend.py +289 -0
- rhapso-0.1.92/Rhapso/fusion/affine_fusion/fusion.py +601 -0
- rhapso-0.1.92/Rhapso/fusion/affine_fusion/geometry.py +159 -0
- rhapso-0.1.92/Rhapso/fusion/affine_fusion/io.py +546 -0
- rhapso-0.1.92/Rhapso/fusion/affine_fusion/script_utils.py +111 -0
- rhapso-0.1.92/Rhapso/fusion/affine_fusion/setup.py +4 -0
- rhapso-0.1.92/Rhapso/fusion/affine_fusion_worker.py +234 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/__init__.py +0 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/aind_hcr_data_transformation/__init__.py +19 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/aind_hcr_data_transformation/compress/__init__.py +3 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/aind_hcr_data_transformation/compress/czi_to_zarr.py +698 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/aind_hcr_data_transformation/compress/zarr_writer.py +265 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/aind_hcr_data_transformation/models.py +81 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/aind_hcr_data_transformation/utils/__init__.py +3 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/aind_hcr_data_transformation/utils/utils.py +526 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/aind_hcr_data_transformation/zeiss_job.py +249 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/aind_z1_radial_correction/__init__.py +21 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/aind_z1_radial_correction/array_to_zarr.py +257 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/aind_z1_radial_correction/radial_correction.py +557 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/aind_z1_radial_correction/run_capsule.py +98 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/aind_z1_radial_correction/utils/__init__.py +3 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/aind_z1_radial_correction/utils/utils.py +266 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale/aind_z1_radial_correction/worker.py +89 -0
- rhapso-0.1.92/Rhapso/fusion/multiscale_worker.py +113 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen/__init__.py +8 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen/dispim_link.py +235 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen/exaspim_link.py +127 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen/hcr_link.py +368 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen/iSPIM_top.py +47 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen/link_utils.py +239 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen/main.py +299 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen/ng_layer.py +1434 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen/ng_state.py +1123 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen/parsers.py +336 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen/raw_link.py +116 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen/utils/__init__.py +4 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen/utils/shader_utils.py +85 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen/utils/transfer.py +43 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen/utils/utils.py +303 -0
- rhapso-0.1.92/Rhapso/fusion/neuroglancer_link_gen_worker.py +30 -0
- rhapso-0.1.92/Rhapso/matching/__init__.py +0 -0
- rhapso-0.1.92/Rhapso/matching/load_and_transform_points.py +458 -0
- rhapso-0.1.92/Rhapso/matching/ransac_matching.py +544 -0
- rhapso-0.1.92/Rhapso/matching/save_matches.py +120 -0
- rhapso-0.1.92/Rhapso/matching/xml_parser.py +302 -0
- rhapso-0.1.92/Rhapso/pipelines/__init__.py +0 -0
- rhapso-0.1.92/Rhapso/pipelines/ray/__init__.py +0 -0
- rhapso-0.1.92/Rhapso/pipelines/ray/aws/__init__.py +0 -0
- rhapso-0.1.92/Rhapso/pipelines/ray/aws/alignment_pipeline.py +227 -0
- rhapso-0.1.92/Rhapso/pipelines/ray/aws/config/__init__.py +0 -0
- rhapso-0.1.92/Rhapso/pipelines/ray/evaluation.py +71 -0
- rhapso-0.1.92/Rhapso/pipelines/ray/interest_point_detection.py +137 -0
- rhapso-0.1.92/Rhapso/pipelines/ray/interest_point_matching.py +110 -0
- rhapso-0.1.92/Rhapso/pipelines/ray/local/__init__.py +0 -0
- rhapso-0.1.92/Rhapso/pipelines/ray/local/alignment_pipeline.py +167 -0
- rhapso-0.1.92/Rhapso/pipelines/ray/matching_stats.py +104 -0
- rhapso-0.1.92/Rhapso/pipelines/ray/param/__init__.py +0 -0
- rhapso-0.1.92/Rhapso/pipelines/ray/solver.py +120 -0
- rhapso-0.1.92/Rhapso/pipelines/ray/split_dataset.py +78 -0
- rhapso-0.1.92/Rhapso/solver/__init__.py +0 -0
- rhapso-0.1.92/Rhapso/solver/compute_tiles.py +562 -0
- rhapso-0.1.92/Rhapso/solver/concatenate_models.py +116 -0
- rhapso-0.1.92/Rhapso/solver/connected_graphs.py +111 -0
- rhapso-0.1.92/Rhapso/solver/data_prep.py +181 -0
- rhapso-0.1.92/Rhapso/solver/global_optimization.py +410 -0
- rhapso-0.1.92/Rhapso/solver/model_and_tile_setup.py +109 -0
- rhapso-0.1.92/Rhapso/solver/pre_align_tiles.py +323 -0
- rhapso-0.1.92/Rhapso/solver/save_results.py +97 -0
- rhapso-0.1.92/Rhapso/solver/view_transforms.py +75 -0
- rhapso-0.1.92/Rhapso/solver/xml_to_dataframe_solver.py +213 -0
- rhapso-0.1.92/Rhapso/split_dataset/__init__.py +0 -0
- rhapso-0.1.92/Rhapso/split_dataset/compute_grid_rules.py +78 -0
- rhapso-0.1.92/Rhapso/split_dataset/save_points.py +101 -0
- rhapso-0.1.92/Rhapso/split_dataset/save_xml.py +377 -0
- rhapso-0.1.92/Rhapso/split_dataset/split_images.py +537 -0
- rhapso-0.1.92/Rhapso/split_dataset/xml_to_dataframe_split.py +219 -0
- rhapso-0.1.92/Rhapso.egg-info/PKG-INFO +39 -0
- rhapso-0.1.92/Rhapso.egg-info/SOURCES.txt +105 -0
- rhapso-0.1.92/Rhapso.egg-info/dependency_links.txt +1 -0
- rhapso-0.1.92/Rhapso.egg-info/requires.txt +15 -0
- rhapso-0.1.92/Rhapso.egg-info/top_level.txt +2 -0
- rhapso-0.1.92/pyproject.toml +3 -0
- rhapso-0.1.92/setup.cfg +4 -0
- rhapso-0.1.92/setup.py +53 -0
- rhapso-0.1.92/tests/__init__.py +1 -0
- rhapso-0.1.92/tests/test_detection.py +17 -0
- rhapso-0.1.92/tests/test_matching.py +21 -0
- rhapso-0.1.92/tests/test_solving.py +21 -0
rhapso-0.1.92/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Allen Institute for Neural Dynamics
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
rhapso-0.1.92/PKG-INFO
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Rhapso
|
|
3
|
+
Version: 0.1.92
|
|
4
|
+
Summary: A python package for aligning and stitching light sheet fluorescence microscopy images together
|
|
5
|
+
Author: ND
|
|
6
|
+
Author-email: sean.fite@alleninstitute.org
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Natural Language :: English
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.7
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: pandas
|
|
19
|
+
Requires-Dist: dask[array]==2024.12.1
|
|
20
|
+
Requires-Dist: zarr==2.18.3
|
|
21
|
+
Requires-Dist: scipy==1.13.1
|
|
22
|
+
Requires-Dist: scikit-image
|
|
23
|
+
Requires-Dist: bioio==1.3.0
|
|
24
|
+
Requires-Dist: bioio-tifffile==1.0.0
|
|
25
|
+
Requires-Dist: tifffile==2025.1.10
|
|
26
|
+
Requires-Dist: dask-image==2024.5.3
|
|
27
|
+
Requires-Dist: boto3==1.35.92
|
|
28
|
+
Requires-Dist: numcodecs==0.13.1
|
|
29
|
+
Requires-Dist: matplotlib==3.10.0
|
|
30
|
+
Requires-Dist: memory-profiler==0.61.0
|
|
31
|
+
Requires-Dist: s3fs==2024.12.0
|
|
32
|
+
Requires-Dist: scikit-learn
|
|
33
|
+
Dynamic: author
|
|
34
|
+
Dynamic: author-email
|
|
35
|
+
Dynamic: classifier
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
Dynamic: requires-dist
|
|
38
|
+
Dynamic: requires-python
|
|
39
|
+
Dynamic: summary
|
rhapso-0.1.92/README.md
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
# Rhapso
|
|
2
|
+
|
|
3
|
+
**Rhapso** is a modular Python toolkit for interest point based registration, alignment, and fusing of large-scale microscopy datasets.
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://www.python.org/downloads/release/python-3110/)
|
|
7
|
+
[](https://github.com/AllenNeuralDynamics/Rhapso/wiki)
|
|
8
|
+
|
|
9
|
+
<!-- ## Example Usage Media Content Coming Soon....
|
|
10
|
+
-- -->
|
|
11
|
+
|
|
12
|
+
<br>
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
- [Summary](#summary)
|
|
16
|
+
- [Contact](#contact)
|
|
17
|
+
- [Features](#features)
|
|
18
|
+
- [Performance](#performance)
|
|
19
|
+
- [Layout](#layout)
|
|
20
|
+
- [Installation](#installation)
|
|
21
|
+
- [Ray](#ray)
|
|
22
|
+
- [Run Locally w/ Ray](#run-locally-with-ray)
|
|
23
|
+
- [Run on AWS Cluster w/ Ray](#run-on-aws-cluster-with-ray)
|
|
24
|
+
- [Access Ray Dashboard](#access-ray-dashboard)
|
|
25
|
+
- [Parameters](#parameters)
|
|
26
|
+
- [Tuning Guide](#tuning-guide)
|
|
27
|
+
- [Build Package](#build-package)
|
|
28
|
+
- [Using the Built `.whl` File](#using-the-built-whl-file)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
<br>
|
|
33
|
+
|
|
34
|
+
**Update 11/26/25**
|
|
35
|
+
--------
|
|
36
|
+
Rhapso is still loading... and while we wrap up development, a couple things to know if you are outside the Allen Institute:
|
|
37
|
+
- This process requires a very specific XML structure to work.
|
|
38
|
+
- Fusion/Mutliscale is included but still under testing and development
|
|
39
|
+
|
|
40
|
+
<br>
|
|
41
|
+
|
|
42
|
+
## Summary
|
|
43
|
+
Rhapso is a set of Python components for registration, alignment, and stitching of large-scale, 3D, overlapping tile-based, multiscale microscopy datasets.
|
|
44
|
+
|
|
45
|
+
Rhapso was developed by the Allen Institute for Neural Dynamics. Rhapso is comprised of stateless components. You can call these components using a pipeline script, with the option to run on a single machine or scale out with Ray to cloud based (currently only supporting AWS) clusters.
|
|
46
|
+
|
|
47
|
+
Current data loaders support Zarr and Tiff.
|
|
48
|
+
|
|
49
|
+
<br>
|
|
50
|
+
|
|
51
|
+
## Contact
|
|
52
|
+
Questions or want to contribute? Please open an issue..
|
|
53
|
+
|
|
54
|
+
<br>
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
- **Interest Point Detection** - using DOG based feature detection
|
|
58
|
+
- **Interest Point Matching** - using descriptor based RANSAC to match feature points
|
|
59
|
+
- **Global Optimization** - aligning matched features per tile, globally
|
|
60
|
+
- **Validation and Visualization Tools** - validate component specific results for the best output
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
<br>
|
|
65
|
+
|
|
66
|
+
## High Level Approach to Registration, Alignment, and Fusion
|
|
67
|
+
|
|
68
|
+
We first run **interest point detection** to capture feature points in the dataset, focusing on overlapping regions between tiles. These points drive all downstream alignment.
|
|
69
|
+
|
|
70
|
+
Next, we perform **alignment** in two-three stages, with regularized models:
|
|
71
|
+
|
|
72
|
+
1. **Rigid matching + solver** – Match interest points with a rigid model and solve for globally consistent rigid transforms between all tiles.
|
|
73
|
+
2. **Affine matching + solver** – Starting from the rigid solution, repeat matching with an affine model to recover more precise tile transforms.
|
|
74
|
+
3. **Split affine matching + solver** – For very large z-stacks, we recommend first running the split dataset component to chunk tiles into smaller Z-bounds, then repeating affine matching and solving in “split affine” mode to refine local alignment.
|
|
75
|
+
|
|
76
|
+
All resulting transforms are written back into the input XML.
|
|
77
|
+
|
|
78
|
+
Whether you split or not, once the XML contains your final transforms, you are ready for **fusion**. We recommend viewing the aligned XML in FIJI/BDV to visually confirm alignment quality before running fusion.
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
<br>
|
|
84
|
+
|
|
85
|
+
## Performance
|
|
86
|
+
|
|
87
|
+
**Interest Point Detection Performance Example (130TB Zarr dataset)**
|
|
88
|
+
|
|
89
|
+
| Environment | Resources | Avg runtime |
|
|
90
|
+
|:----------------------|:---------------------|:-----------:|
|
|
91
|
+
| Local single machine | 10 CPU, 10 GB RAM | ~120 min |
|
|
92
|
+
| AWS Ray cluster | 560 CPU, 4.4 TB RAM | ~30 min |
|
|
93
|
+
|
|
94
|
+
<br>
|
|
95
|
+
*Actual times vary by pipeline components, dataset size, tiling, and parameter choices.*
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
<br>
|
|
100
|
+
|
|
101
|
+
## Layout
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
Rhapso/
|
|
105
|
+
└── Rhapso/
|
|
106
|
+
├── data_prep/ # Custom data loaders
|
|
107
|
+
├── detection/
|
|
108
|
+
├── evaluation/
|
|
109
|
+
├── fusion/
|
|
110
|
+
├── image_split/
|
|
111
|
+
├── matching/
|
|
112
|
+
├── pipelines/
|
|
113
|
+
│ └── ray/
|
|
114
|
+
│ ├── aws/
|
|
115
|
+
│ │ ├── config/ # Cluster templates (edit for your account)
|
|
116
|
+
│ │ └── alignment_pipeline.py # AWS Ray pipeline entry point
|
|
117
|
+
│ ├── local/
|
|
118
|
+
│ │ └── alignment_pipeline.py # Local Ray pipeline entry point
|
|
119
|
+
│ ├── param/ # Run parameter files (customize per run)
|
|
120
|
+
│ ├── interest_point_detection.py # Detection pipeline script
|
|
121
|
+
│ ├── interest_point_matching.py # Matching pipeline script
|
|
122
|
+
│ └── solver.py # Global solver script
|
|
123
|
+
├── solver/
|
|
124
|
+
└── visualization/ # Validation tools
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
<br>
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
## Installation
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
# clone the repo
|
|
136
|
+
git clone https://github.com/AllenNeuralDynamics/Rhapso.git
|
|
137
|
+
|
|
138
|
+
# create and activate a virtual environment
|
|
139
|
+
python -m venv .venv && source .venv/bin/activate
|
|
140
|
+
# or: conda create -n rhapso python=3.11 && conda activate rhapso
|
|
141
|
+
|
|
142
|
+
# install deps
|
|
143
|
+
pip install -r requirements.txt
|
|
144
|
+
```
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
<br>
|
|
148
|
+
|
|
149
|
+
## Ray
|
|
150
|
+
|
|
151
|
+
**Ray** is a Python framework for parallel and distributed computing. It lets you run regular Python functions in parallel on a single machine **or** scale them out to a cluster (e.g., AWS) with minimal code changes. In Rhapso, we use Ray to process large scale datasets.
|
|
152
|
+
|
|
153
|
+
- Convert a function into a distributed task with `@ray.remote`
|
|
154
|
+
- Control scheduling with resource hints (CPUs, memory)
|
|
155
|
+
|
|
156
|
+
<br>
|
|
157
|
+
|
|
158
|
+
> [!TIP]
|
|
159
|
+
> Ray schedules **greedily** by default and each task reserves **1 CPU**, so if you fire many tasks, Ray will try to run as many as your machine advertises—often too much for a laptop. Throttle concurrency explicitly so you don’t overload your system. Use your machine's activity monitor to track this or the Ray dashboard to monitor this on your cluster:
|
|
160
|
+
>
|
|
161
|
+
> - **Cap by CPUs**:
|
|
162
|
+
> ```python
|
|
163
|
+
> @ray.remote(num_cpus=3) # Ray will schedule each time 3 cpus are available
|
|
164
|
+
> ```
|
|
165
|
+
> - **Cap by Memory and CPU** if Tasks are RAM-Heavy (bytes):
|
|
166
|
+
> ```python
|
|
167
|
+
> @ray.remote(num_cpus=2, memory=4 * 1024**3) # 4 GiB and 2 CPU per task>
|
|
168
|
+
> ```
|
|
169
|
+
> - **No Cap** on Resources:
|
|
170
|
+
> ```python
|
|
171
|
+
> @ray.remote
|
|
172
|
+
> ```
|
|
173
|
+
> - **Good Local Default:**
|
|
174
|
+
> ```python
|
|
175
|
+
> @ray.remote(num_cpus=2)
|
|
176
|
+
> ```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
<br>
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
## Run Locally with Ray
|
|
184
|
+
|
|
185
|
+
### 1. Edit or create param file (templates in codebase)
|
|
186
|
+
```python
|
|
187
|
+
Rhapso/Rhapso/pipelines/param/
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 2. Update alignment pipeline script to point to param file
|
|
191
|
+
```python
|
|
192
|
+
with open("Rhapso/pipelines/ray/param/your_param_file.yml", "r") as file:
|
|
193
|
+
config = yaml.safe_load(file)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 3. Run local alignment pipeline script
|
|
197
|
+
```python
|
|
198
|
+
python Rhapso/pipelines/ray/local/alignment_pipeline.py
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
<br>
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
## Run on AWS Cluster with Ray
|
|
208
|
+
|
|
209
|
+
### 1. Edit/create param file (templates in codebase)
|
|
210
|
+
```python
|
|
211
|
+
Rhapso/pipelines/ray/param/
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### 2. Update alignment pipeline script to point to param file
|
|
215
|
+
```python
|
|
216
|
+
with open("Rhapso/pipelines/ray/param/your_param_file.yml", "r") as file:
|
|
217
|
+
config = yaml.safe_load(file)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 3. Edit/create config file (templates in codebase)
|
|
221
|
+
```python
|
|
222
|
+
Rhapso/pipelines/ray/aws/config/
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### 4. Update config file to point to whl location in setup_commands
|
|
226
|
+
```python
|
|
227
|
+
- aws s3 cp s3://rhapso-whl-v2/Rhapso-0.1.8-py3-none-any.whl /tmp/Rhapso-0.1.8-py3-none-any.whl
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 5. Update alignment pipeline script to point to config file
|
|
231
|
+
```python
|
|
232
|
+
unified_yml = "your_cluster_config_file_name.yml"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 6. Create whl file and upload to s3
|
|
236
|
+
```python
|
|
237
|
+
python setup.py sdist bdist_wheel
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### 7. Run AWS alignment pipeline script
|
|
241
|
+
```python
|
|
242
|
+
python Rhapso/pipelines/ray/aws/alignment_pipeline.py
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
> [!TIP]
|
|
246
|
+
> - The pipeline script is set to always spin the cluster down, it is a good practice to double check in AWS.
|
|
247
|
+
> - If you experience a sticky cache on run params, you may have forgotten to spin your old cluster down.
|
|
248
|
+
|
|
249
|
+
<br>
|
|
250
|
+
|
|
251
|
+
## Access Ray Dashboard
|
|
252
|
+
|
|
253
|
+
**This is a great place to tune your cluster's performance.**
|
|
254
|
+
1. Find public IP of head node.
|
|
255
|
+
2. Replace the ip address and PEM file location to ssh into head node.
|
|
256
|
+
```
|
|
257
|
+
ssh -i /You/path/to/ssh/key.pem -L port:localhost:port ubuntu@public.ip.address
|
|
258
|
+
```
|
|
259
|
+
4. Go to dashboard.
|
|
260
|
+
```
|
|
261
|
+
http://localhost:8265
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
<br>
|
|
267
|
+
|
|
268
|
+
## Parameters
|
|
269
|
+
|
|
270
|
+
### Detection
|
|
271
|
+
```
|
|
272
|
+
| Parameter | Feature / step | What it does | Typical range\* |
|
|
273
|
+
| :----------------- | :--------------------- | :-------------------------------------------------------------------------------------------- | :-------------------------------- |
|
|
274
|
+
| `dsxy` | Downsampling (XY) | Reduces XY resolution before detection; speeds up & denoises, but raises minimum feature size | 16 |
|
|
275
|
+
| `dsz` | Downsampling (Z) | Reduces Z resolution; often lower than XY due to anisotropy | 16 |
|
|
276
|
+
| `min_intensity` | Normalization | Lower bound for intensity normalization prior to DoG | 1 |
|
|
277
|
+
| `max_intensity` | Normalization | Upper bound for intensity normalization prior to DoG | 5 |
|
|
278
|
+
| `sigma` | DoG blur | Gaussian blur scale (sets feature size); higher = smoother, fewer peaks | 1.5 - 2.5 |
|
|
279
|
+
| `threshold` | Peak detection (DoG) | Peak threshold (initial min peak ≈ `threshold / 3`); higher = fewer, stronger peaks | 0.0008 - .05 |
|
|
280
|
+
| `median_filter` | Pre-filter (XY) | Median filter size to suppress speckle/isolated noise before DoG | 1-10 |
|
|
281
|
+
| `combine_distance` | Post-merge (DoG peaks) | Merge radius (voxels) to de-duplicate nearby detections | 0.5 |
|
|
282
|
+
| `chunks_per_bound` | Tiling/parallelism | Sub-partitions per tile/bound; higher improves parallelism but adds overhead | 12-18 |
|
|
283
|
+
| `max_spots` | Post-cap | Maximum detections per bound to prevent domination by dense regions | 8,0000 - 10,000 |
|
|
284
|
+
```
|
|
285
|
+
<br>
|
|
286
|
+
|
|
287
|
+
### Matching
|
|
288
|
+
```
|
|
289
|
+
# Candidate Selection
|
|
290
|
+
| Parameter | Feature / step | What it does | Typical range |
|
|
291
|
+
| :----------------------------- | :------------------ | :---------------------------------------------------------------- | :------------- |
|
|
292
|
+
| `num_neighbors` | Candidate search | Number of nearest neighbors to consider per point | 3 |
|
|
293
|
+
| `redundancy` | Candidate search | Extra neighbors added for robustness beyond `num_neighbors` | 0 - 1 |
|
|
294
|
+
| `significance` | Ratio test | Strictness of descriptor ratio test; larger = stricter acceptance | 3 |
|
|
295
|
+
| `search_radius` | Spatial gating | Max spatial distance for candidate matches (in downsampled units) | 100 - 300 |
|
|
296
|
+
| `num_required_neighbors` | Candidate filtering | Minimum neighbors required to keep a candidate point | 3 |
|
|
297
|
+
|
|
298
|
+
# Ransac
|
|
299
|
+
| Parameter | Feature / step | What it does | Typical range |
|
|
300
|
+
| :---------------------------- | :------------------- | :---------------------------------------------------------------- | :------------- |
|
|
301
|
+
| `model_min_matches` | RANSAC | Minimum correspondences to estimate a rigid transform | 18 – 32 |
|
|
302
|
+
| `inlier_factor` | RANSAC | Inlier tolerance scaling; larger = looser inlier threshold | 30 – 100 |
|
|
303
|
+
| `lambda_value` | RANSAC | Regularization strength during model fitting | 0.1 – 0.05 |
|
|
304
|
+
| `num_iterations` | RANSAC | Number of RANSAC trials; higher = more robust, slower | 10,0000 |
|
|
305
|
+
| `regularization_weight` | RANSAC | Weight applied to the regularization term | 1.0 |
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
<br>
|
|
309
|
+
|
|
310
|
+
### Solver
|
|
311
|
+
```
|
|
312
|
+
| Parameter | Feature / step | What it does | Typical range |
|
|
313
|
+
| :------------------- | :------------- | :----------------------------------------------------------------- | :------------------ |
|
|
314
|
+
| `relative_threshold` | Graph pruning | Reject edges with residuals above dataset-relative cutoff | 3.5 |
|
|
315
|
+
| `absolute_threshold` | Graph pruning | Reject edges above an absolute error bound (detection-space units) | 7.0 |
|
|
316
|
+
| `min_matches` | Graph pruning | Minimum matches required to retain an edge between tiles | 3 |
|
|
317
|
+
| `damp` | Optimization | Damping for iterative solver; higher can stabilize tough cases | 1.0 |
|
|
318
|
+
| `max_iterations` | Optimization | Upper bound on solver iterations | 10,0000 |
|
|
319
|
+
| `max_allowed_error` | Optimization | Overall error cap; `inf` disables hard stop by error | `inf` |
|
|
320
|
+
| `max_plateauwidth` | Early stopping | Stagnation window before stopping on no improvement | 200 |
|
|
321
|
+
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
<br>
|
|
327
|
+
|
|
328
|
+
## Tuning Guide
|
|
329
|
+
|
|
330
|
+
- **Start with Detection.** The quality and density of interest points strongly determine alignment outcomes.
|
|
331
|
+
|
|
332
|
+
- **Target Counts (exaSPIM):** ~25–35k points per tile in dense regions; ~10k for sparser tiles. Going much higher usually increases runtime without meaningful accuracy gains.
|
|
333
|
+
|
|
334
|
+
- **Inspect Early.** After detection, run the visualization script and verify that peaks form **clustered shapes/lines** with a **good spatial spread**—a good sign for robust rigid matches.
|
|
335
|
+
|
|
336
|
+
- **Rigid → Affine Dependency.** Weak rigid matches produce poor rigid transforms, which then degrade affine matching (points don’t land close enough). If tiles fail to align:
|
|
337
|
+
- Check **match counts** for the problem tile and its neighbors.
|
|
338
|
+
- Adjust high-impact detection knobs—`sigma`, `threshold`, and `median_filter`—within sensible ranges.
|
|
339
|
+
- Revisit `max_spots` and `combine_distance` to balance density vs. duplicate detections.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
<br>
|
|
344
|
+
|
|
345
|
+
## Build Package
|
|
346
|
+
|
|
347
|
+
### Using the Built `.whl` File
|
|
348
|
+
|
|
349
|
+
1. **Build the `.whl` File in the root of this repo:**
|
|
350
|
+
```sh
|
|
351
|
+
cd /path/to/Rhapso
|
|
352
|
+
pip install setuptools wheel
|
|
353
|
+
python setup.py sdist bdist_wheel
|
|
354
|
+
```
|
|
355
|
+
The `.whl` file will appear in the `dist` directory. Do not rename it to ensure compatibility (e.g., `rhapso-0.1-py3-none-any.whl`).
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
<br>
|
|
360
|
+
<br>
|
|
361
|
+
<br>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# -----------------------------------------------------------------------------
|
|
2
|
+
# n5_reader.py – N5 data verification script
|
|
3
|
+
#
|
|
4
|
+
# Setup & run:
|
|
5
|
+
# 1. python -m venv n5Venv # create a fresh Python virtual environment
|
|
6
|
+
# 2. source n5Venv/bin/activate # activate the virtual environment
|
|
7
|
+
# 3. pip install .[n5_reader] # install n5_reader dependencies from setup.py
|
|
8
|
+
# 4. python Rhapso/data_prep/n5_reader.py
|
|
9
|
+
# # run the N5 reader for inspecting datasets
|
|
10
|
+
# -----------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
import zarr
|
|
13
|
+
import s3fs
|
|
14
|
+
import os
|
|
15
|
+
import numpy as np
|
|
16
|
+
import matplotlib.pyplot as plt
|
|
17
|
+
import json
|
|
18
|
+
from zarr.storage import FSStore
|
|
19
|
+
|
|
20
|
+
def list_files_under_prefix(node, path):
|
|
21
|
+
try:
|
|
22
|
+
for item in node[path]:
|
|
23
|
+
new_path = f"{path}/{item}"
|
|
24
|
+
if isinstance(node[new_path], zarr.hierarchy.Group):
|
|
25
|
+
print(f"Group: {new_path}")
|
|
26
|
+
list_files_under_prefix(node, new_path)
|
|
27
|
+
else:
|
|
28
|
+
print(f"Dataset: {new_path} - {node[new_path].shape}")
|
|
29
|
+
except KeyError:
|
|
30
|
+
print(f"No items found under the path {path}")
|
|
31
|
+
|
|
32
|
+
# Amount of interest points in view 18,0 is 1061
|
|
33
|
+
# Max value for view 18,0 in corr ip index is 1017
|
|
34
|
+
|
|
35
|
+
def read_n5_data(n5_path):
|
|
36
|
+
import zarr, s3fs, os
|
|
37
|
+
|
|
38
|
+
# guard missing local path
|
|
39
|
+
if not n5_path.startswith("s3://") and not os.path.isdir(n5_path):
|
|
40
|
+
print(f"❌ Local N5 path not found: {n5_path}")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# open the store (S3 or local N5)
|
|
44
|
+
if n5_path.startswith("s3://"):
|
|
45
|
+
s3 = s3fs.S3FileSystem(anon=False)
|
|
46
|
+
store = s3fs.S3Map(root=n5_path, s3=s3)
|
|
47
|
+
else:
|
|
48
|
+
store = zarr.N5Store(n5_path)
|
|
49
|
+
|
|
50
|
+
print(f"\n🔍 Reading N5 data at: {n5_path}")
|
|
51
|
+
root = zarr.open(store, mode='r')
|
|
52
|
+
|
|
53
|
+
def visit_fn(path, node):
|
|
54
|
+
if isinstance(node, zarr.Array):
|
|
55
|
+
print(f"\n📂 Dataset: {path}")
|
|
56
|
+
print(f" 🔢 dtype: {node.dtype}")
|
|
57
|
+
shape = node.shape
|
|
58
|
+
print(f" 📏 shape: {shape}")
|
|
59
|
+
if len(shape) > 1:
|
|
60
|
+
print(f" 📊 count: {shape[0]} arrays of shape {shape[1:]}")
|
|
61
|
+
else:
|
|
62
|
+
print(f" 📊 count: {shape[0]} elements")
|
|
63
|
+
print(f" 🗂 chunks: {node.chunks}")
|
|
64
|
+
print(f" 🛠 compressor: {node.compressor}")
|
|
65
|
+
|
|
66
|
+
print(" 🔎 first 5 entries:")
|
|
67
|
+
sample = node[:5]
|
|
68
|
+
for i, entry in enumerate(sample, start=1):
|
|
69
|
+
# ensure nested array is printed clearly
|
|
70
|
+
val = entry.tolist() if hasattr(entry, "tolist") else entry
|
|
71
|
+
print(f" {i}. {val}")
|
|
72
|
+
|
|
73
|
+
root.visititems(visit_fn)
|
|
74
|
+
|
|
75
|
+
# # read_n5_data('/home/martin/Documents/Allen/Data/IP_TIFF_XML_2/interestpoints.n5')
|
|
76
|
+
|
|
77
|
+
def read_correspondences(dataset_path):
|
|
78
|
+
if dataset_path.startswith("s3://"):
|
|
79
|
+
store = zarr.storage.FSStore(dataset_path, mode="r")
|
|
80
|
+
root = zarr.open(store, mode="r")
|
|
81
|
+
else:
|
|
82
|
+
store = zarr.N5Store(dataset_path)
|
|
83
|
+
root = zarr.open(store, mode="r")
|
|
84
|
+
|
|
85
|
+
if "data" not in root:
|
|
86
|
+
print("Key 'data' not found in root.")
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
group = root["data"]
|
|
90
|
+
data = group[:]
|
|
91
|
+
print(f"Loaded {len(data)} entries.")
|
|
92
|
+
|
|
93
|
+
# for i, entry in enumerate(data):
|
|
94
|
+
# print(f"{i}: {entry}")
|
|
95
|
+
|
|
96
|
+
# print("hi")
|
|
97
|
+
|
|
98
|
+
# Big Stitcher Output
|
|
99
|
+
# # base_path = "/Users/seanfite/Desktop/interest_point_detection/interestpoints.n5"
|
|
100
|
+
# base_path = "/Users/seanfite/Desktop/ip_rigid_alignment/interestpoints.n5"
|
|
101
|
+
# # base_path = "/Users/seanfite/Desktop/ip_affine_alignment/interestpoints.n5"
|
|
102
|
+
# # base_path = "s3://rhapso-matching-test/output/interestpoints.n5"
|
|
103
|
+
# for tp_id in [0]:
|
|
104
|
+
# for setup_id in range(20):
|
|
105
|
+
# path = f"{base_path}/tpId_{tp_id}_viewSetupId_{setup_id}/beads/correspondences"
|
|
106
|
+
# print(f"Reading: {path}")
|
|
107
|
+
# read_correspondences(path)
|
|
108
|
+
|
|
109
|
+
def read_interest_points(full_path):
|
|
110
|
+
if full_path.startswith("s3://"):
|
|
111
|
+
# s3 = s3fs.S3FileSystem(anon=False)
|
|
112
|
+
# store = s3fs.S3Map(root=full_path, s3=s3)
|
|
113
|
+
# zarray = zarr.open_array(store, mode='r')
|
|
114
|
+
# data = zarray[:]
|
|
115
|
+
|
|
116
|
+
path = full_path.replace("s3://", "", 1)
|
|
117
|
+
bucket = path.split("/")[0]
|
|
118
|
+
prefix = "/".join(path.split("/")[1:])
|
|
119
|
+
|
|
120
|
+
s3 = s3fs.S3FileSystem()
|
|
121
|
+
store = FSStore(f"{bucket}/{prefix}", fs=s3, mode='r')
|
|
122
|
+
root = zarr.open(store, mode="r")
|
|
123
|
+
|
|
124
|
+
group = root["data"]
|
|
125
|
+
data = group[:]
|
|
126
|
+
count = len(data)
|
|
127
|
+
print(count)
|
|
128
|
+
print("")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
else:
|
|
132
|
+
full_path = full_path.rstrip("/") # remove trailing slash if any
|
|
133
|
+
components = full_path.split("/")
|
|
134
|
+
|
|
135
|
+
# Find index of the N5 root (assumes .n5 marks the root)
|
|
136
|
+
try:
|
|
137
|
+
n5_index = next(i for i, c in enumerate(components) if c.endswith(".n5"))
|
|
138
|
+
except StopIteration:
|
|
139
|
+
raise ValueError("No .n5 directory found in path")
|
|
140
|
+
|
|
141
|
+
dataset_path = "/".join(components[:n5_index + 1]) # the store root
|
|
142
|
+
dataset_rel_path = "/".join(components[n5_index + 1:]) # relative dataset path
|
|
143
|
+
|
|
144
|
+
# Open N5 store and dataset
|
|
145
|
+
store = zarr.N5Store(dataset_path)
|
|
146
|
+
root = zarr.open(store, mode='r')
|
|
147
|
+
|
|
148
|
+
if dataset_rel_path not in root:
|
|
149
|
+
print(f"Skipping: {dataset_rel_path} (not found)")
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
zarray = root[dataset_rel_path + "/loc"]
|
|
153
|
+
data = zarray[:]
|
|
154
|
+
|
|
155
|
+
print("\n--- Detection Stats (Raw Rhapso Output) ---")
|
|
156
|
+
print(f"Total Points: {len(data)}")
|
|
157
|
+
|
|
158
|
+
# for dim, name in zip(range(3), ['X', 'Y', 'Z']):
|
|
159
|
+
# values = data[:, dim]
|
|
160
|
+
# print(f"{name} Range: {values.min():.2f} – {values.max():.2f} | Spread (std): {values.std():.2f}")
|
|
161
|
+
|
|
162
|
+
# volume = np.ptp(data[:, 0]) * np.ptp(data[:, 1]) * np.ptp(data[:, 2])
|
|
163
|
+
# density = len(data) / (volume / 1e9) if volume > 0 else 0
|
|
164
|
+
# print(f"Estimated Density: {density:.2f} points per 1000³ volume")
|
|
165
|
+
# print("-----------------------")
|
|
166
|
+
|
|
167
|
+
# # --- 3D Plot ---
|
|
168
|
+
# max_points = 1000000000000
|
|
169
|
+
# sample = data if len(data) <= max_points else data[np.random.choice(len(data), max_points, replace=False)]
|
|
170
|
+
|
|
171
|
+
# fig = plt.figure(figsize=(10, 8))
|
|
172
|
+
# ax = fig.add_subplot(111, projection='3d')
|
|
173
|
+
# ax.scatter(sample[:, 0], sample[:, 1], sample[:, 2], c='blue', alpha=0.5, s=1)
|
|
174
|
+
# ax.set_xlabel('X')
|
|
175
|
+
# ax.set_ylabel('Y')
|
|
176
|
+
# ax.set_zlabel('Z')
|
|
177
|
+
# ax.set_title(f"Interest Points in 3D (showing {len(sample)} points)")
|
|
178
|
+
# plt.tight_layout()
|
|
179
|
+
# plt.show()
|
|
180
|
+
|
|
181
|
+
# base_path = "s3://rhapso-matching-test/output/interestpoints.n5"
|
|
182
|
+
# base_path = "/Users/seanfite/Desktop/IP_TIFF_XML/interestpoints.n5"
|
|
183
|
+
base_path = "/Users/seanfite/Desktop/interestpoints.n5"
|
|
184
|
+
for tp_id in [0]:
|
|
185
|
+
for setup_id in range(20):
|
|
186
|
+
path = f"{base_path}/tpId_{tp_id}_viewSetupId_{setup_id}/beads/interestpoints"
|
|
187
|
+
print(f"For view: {setup_id}")
|
|
188
|
+
read_interest_points(path)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import s3fs
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Utility class for downloading BigStitcher outputs from S3 to local storage for N5 reader compatibility
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
class S3BigStitcherReader:
|
|
10
|
+
def __init__(self, s3_uri, local_directory):
|
|
11
|
+
self.s3_uri = s3_uri
|
|
12
|
+
self.local_directory = local_directory
|
|
13
|
+
|
|
14
|
+
def download_n5_from_s3_to_local(self):
|
|
15
|
+
"""
|
|
16
|
+
Recursively download an N5 dataset from S3 to a local directory.
|
|
17
|
+
"""
|
|
18
|
+
s3 = s3fs.S3FileSystem(anon=False)
|
|
19
|
+
s3_path = self.s3_uri.replace("s3://", "")
|
|
20
|
+
all_keys = s3.find(s3_path, detail=True)
|
|
21
|
+
|
|
22
|
+
for key, obj in all_keys.items():
|
|
23
|
+
if obj["type"] == "file":
|
|
24
|
+
rel_path = key.replace(s3_path + "/", "")
|
|
25
|
+
local_file_path = os.path.join(self.local_directory, rel_path)
|
|
26
|
+
os.makedirs(os.path.dirname(local_file_path), exist_ok=True)
|
|
27
|
+
s3.get(key, local_file_path)
|
|
28
|
+
|
|
29
|
+
# Check for the specific interestpoints path
|
|
30
|
+
if rel_path.endswith("beads/interestpoints/attributes.json") and "interestpoints.n5" in rel_path:
|
|
31
|
+
# Construct the path to the attributes file
|
|
32
|
+
attributes_path = os.path.join(os.path.dirname(local_file_path), "attributes.json")
|
|
33
|
+
attributes_data = {
|
|
34
|
+
"pointcloud": "1.0.0",
|
|
35
|
+
"type": "list",
|
|
36
|
+
"list version": "1.0.0"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
with open(attributes_path, "w") as f:
|
|
40
|
+
json.dump(attributes_data, f, indent=2)
|
|
41
|
+
|
|
42
|
+
def run(self):
|
|
43
|
+
self.download_n5_from_s3_to_local()
|
|
44
|
+
|
|
45
|
+
s3_path = self.s3_uri.replace("s3://", "")
|
|
46
|
+
full_local_path = os.path.join(self.local_directory, s3_path)
|
|
47
|
+
|
|
48
|
+
# Final paths
|
|
49
|
+
xml_input_path = os.path.join(full_local_path, "bigstitcher_ip.xml")
|
|
50
|
+
n5_output_path = os.path.join(full_local_path, "interestpoints.n5")
|
|
51
|
+
|
|
52
|
+
print("XML Input Path:", xml_input_path)
|
|
53
|
+
print("N5 Output Path:", n5_output_path)
|
|
54
|
+
|
|
55
|
+
return xml_input_path, n5_output_path
|