Rhapso 0.1.92__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.
Files changed (101) hide show
  1. Rhapso/__init__.py +1 -0
  2. Rhapso/data_prep/__init__.py +2 -0
  3. Rhapso/data_prep/n5_reader.py +188 -0
  4. Rhapso/data_prep/s3_big_stitcher_reader.py +55 -0
  5. Rhapso/data_prep/xml_to_dataframe.py +215 -0
  6. Rhapso/detection/__init__.py +5 -0
  7. Rhapso/detection/advanced_refinement.py +203 -0
  8. Rhapso/detection/difference_of_gaussian.py +324 -0
  9. Rhapso/detection/image_reader.py +117 -0
  10. Rhapso/detection/metadata_builder.py +130 -0
  11. Rhapso/detection/overlap_detection.py +327 -0
  12. Rhapso/detection/points_validation.py +49 -0
  13. Rhapso/detection/save_interest_points.py +265 -0
  14. Rhapso/detection/view_transform_models.py +67 -0
  15. Rhapso/fusion/__init__.py +0 -0
  16. Rhapso/fusion/affine_fusion/__init__.py +2 -0
  17. Rhapso/fusion/affine_fusion/blend.py +289 -0
  18. Rhapso/fusion/affine_fusion/fusion.py +601 -0
  19. Rhapso/fusion/affine_fusion/geometry.py +159 -0
  20. Rhapso/fusion/affine_fusion/io.py +546 -0
  21. Rhapso/fusion/affine_fusion/script_utils.py +111 -0
  22. Rhapso/fusion/affine_fusion/setup.py +4 -0
  23. Rhapso/fusion/affine_fusion_worker.py +234 -0
  24. Rhapso/fusion/multiscale/__init__.py +0 -0
  25. Rhapso/fusion/multiscale/aind_hcr_data_transformation/__init__.py +19 -0
  26. Rhapso/fusion/multiscale/aind_hcr_data_transformation/compress/__init__.py +3 -0
  27. Rhapso/fusion/multiscale/aind_hcr_data_transformation/compress/czi_to_zarr.py +698 -0
  28. Rhapso/fusion/multiscale/aind_hcr_data_transformation/compress/zarr_writer.py +265 -0
  29. Rhapso/fusion/multiscale/aind_hcr_data_transformation/models.py +81 -0
  30. Rhapso/fusion/multiscale/aind_hcr_data_transformation/utils/__init__.py +3 -0
  31. Rhapso/fusion/multiscale/aind_hcr_data_transformation/utils/utils.py +526 -0
  32. Rhapso/fusion/multiscale/aind_hcr_data_transformation/zeiss_job.py +249 -0
  33. Rhapso/fusion/multiscale/aind_z1_radial_correction/__init__.py +21 -0
  34. Rhapso/fusion/multiscale/aind_z1_radial_correction/array_to_zarr.py +257 -0
  35. Rhapso/fusion/multiscale/aind_z1_radial_correction/radial_correction.py +557 -0
  36. Rhapso/fusion/multiscale/aind_z1_radial_correction/run_capsule.py +98 -0
  37. Rhapso/fusion/multiscale/aind_z1_radial_correction/utils/__init__.py +3 -0
  38. Rhapso/fusion/multiscale/aind_z1_radial_correction/utils/utils.py +266 -0
  39. Rhapso/fusion/multiscale/aind_z1_radial_correction/worker.py +89 -0
  40. Rhapso/fusion/multiscale_worker.py +113 -0
  41. Rhapso/fusion/neuroglancer_link_gen/__init__.py +8 -0
  42. Rhapso/fusion/neuroglancer_link_gen/dispim_link.py +235 -0
  43. Rhapso/fusion/neuroglancer_link_gen/exaspim_link.py +127 -0
  44. Rhapso/fusion/neuroglancer_link_gen/hcr_link.py +368 -0
  45. Rhapso/fusion/neuroglancer_link_gen/iSPIM_top.py +47 -0
  46. Rhapso/fusion/neuroglancer_link_gen/link_utils.py +239 -0
  47. Rhapso/fusion/neuroglancer_link_gen/main.py +299 -0
  48. Rhapso/fusion/neuroglancer_link_gen/ng_layer.py +1434 -0
  49. Rhapso/fusion/neuroglancer_link_gen/ng_state.py +1123 -0
  50. Rhapso/fusion/neuroglancer_link_gen/parsers.py +336 -0
  51. Rhapso/fusion/neuroglancer_link_gen/raw_link.py +116 -0
  52. Rhapso/fusion/neuroglancer_link_gen/utils/__init__.py +4 -0
  53. Rhapso/fusion/neuroglancer_link_gen/utils/shader_utils.py +85 -0
  54. Rhapso/fusion/neuroglancer_link_gen/utils/transfer.py +43 -0
  55. Rhapso/fusion/neuroglancer_link_gen/utils/utils.py +303 -0
  56. Rhapso/fusion/neuroglancer_link_gen_worker.py +30 -0
  57. Rhapso/matching/__init__.py +0 -0
  58. Rhapso/matching/load_and_transform_points.py +458 -0
  59. Rhapso/matching/ransac_matching.py +544 -0
  60. Rhapso/matching/save_matches.py +120 -0
  61. Rhapso/matching/xml_parser.py +302 -0
  62. Rhapso/pipelines/__init__.py +0 -0
  63. Rhapso/pipelines/ray/__init__.py +0 -0
  64. Rhapso/pipelines/ray/aws/__init__.py +0 -0
  65. Rhapso/pipelines/ray/aws/alignment_pipeline.py +227 -0
  66. Rhapso/pipelines/ray/aws/config/__init__.py +0 -0
  67. Rhapso/pipelines/ray/evaluation.py +71 -0
  68. Rhapso/pipelines/ray/interest_point_detection.py +137 -0
  69. Rhapso/pipelines/ray/interest_point_matching.py +110 -0
  70. Rhapso/pipelines/ray/local/__init__.py +0 -0
  71. Rhapso/pipelines/ray/local/alignment_pipeline.py +167 -0
  72. Rhapso/pipelines/ray/matching_stats.py +104 -0
  73. Rhapso/pipelines/ray/param/__init__.py +0 -0
  74. Rhapso/pipelines/ray/solver.py +120 -0
  75. Rhapso/pipelines/ray/split_dataset.py +78 -0
  76. Rhapso/solver/__init__.py +0 -0
  77. Rhapso/solver/compute_tiles.py +562 -0
  78. Rhapso/solver/concatenate_models.py +116 -0
  79. Rhapso/solver/connected_graphs.py +111 -0
  80. Rhapso/solver/data_prep.py +181 -0
  81. Rhapso/solver/global_optimization.py +410 -0
  82. Rhapso/solver/model_and_tile_setup.py +109 -0
  83. Rhapso/solver/pre_align_tiles.py +323 -0
  84. Rhapso/solver/save_results.py +97 -0
  85. Rhapso/solver/view_transforms.py +75 -0
  86. Rhapso/solver/xml_to_dataframe_solver.py +213 -0
  87. Rhapso/split_dataset/__init__.py +0 -0
  88. Rhapso/split_dataset/compute_grid_rules.py +78 -0
  89. Rhapso/split_dataset/save_points.py +101 -0
  90. Rhapso/split_dataset/save_xml.py +377 -0
  91. Rhapso/split_dataset/split_images.py +537 -0
  92. Rhapso/split_dataset/xml_to_dataframe_split.py +219 -0
  93. rhapso-0.1.92.dist-info/METADATA +39 -0
  94. rhapso-0.1.92.dist-info/RECORD +101 -0
  95. rhapso-0.1.92.dist-info/WHEEL +5 -0
  96. rhapso-0.1.92.dist-info/licenses/LICENSE +21 -0
  97. rhapso-0.1.92.dist-info/top_level.txt +2 -0
  98. tests/__init__.py +1 -0
  99. tests/test_detection.py +17 -0
  100. tests/test_matching.py +21 -0
  101. tests/test_solving.py +21 -0
@@ -0,0 +1,377 @@
1
+ import numpy as np
2
+ import boto3
3
+ import re
4
+ from xml.etree import ElementTree as ET
5
+
6
+ class SaveXML:
7
+ def __init__(self, data_global, new_split_interest_points, self_definition, xml_file, xml_output_path):
8
+ self.data_global = data_global
9
+ self.new_split_interest_points = new_split_interest_points
10
+ self.self_definition = self_definition
11
+ self.xml_file = xml_file
12
+ self.xml_output_path = xml_output_path
13
+
14
+ def wrap_image_loader_for_split(self, xml: str) -> str:
15
+ root = ET.fromstring(xml)
16
+
17
+ def tn(el): return el.tag.split('}')[-1]
18
+ def find_one(tag):
19
+ el = root.find(f'.//{{*}}{tag}')
20
+ return el if el is not None else root.find(tag)
21
+
22
+ seq = find_one('SequenceDescription')
23
+ if seq is None:
24
+ return xml
25
+
26
+ # find the first immediate ImageLoader under SequenceDescription
27
+ loaders = [ch for ch in list(seq) if tn(ch) == 'ImageLoader']
28
+ if not loaders:
29
+ return xml
30
+
31
+ inner = loaders[0]
32
+
33
+ fmt = (inner.get('format') or '').lower()
34
+ if fmt == 'split.viewerimgloader':
35
+ return xml
36
+
37
+ # handle the case where the *outer* wrapper already exists
38
+ if any(tn(ch) == 'ImageLoader' for ch in list(inner)) and fmt.startswith('bdv'):
39
+ return xml
40
+
41
+ # wrap the current loader
42
+ idx = list(seq).index(inner)
43
+ seq.remove(inner)
44
+ wrapper = ET.Element('ImageLoader', {'format': 'split.viewerimgloader'})
45
+ wrapper.append(inner)
46
+ seq.insert(idx, wrapper)
47
+
48
+ try:
49
+ ET.indent(root, space=" ")
50
+ except Exception:
51
+ pass
52
+ return ET.tostring(root, encoding='unicode')
53
+
54
+ def save_view_interest_points(self, xml):
55
+ root = ET.fromstring(xml)
56
+
57
+ def find_one(tag):
58
+ el = root.find(f'.//{{*}}{tag}')
59
+ if el is None:
60
+ el = root.find(tag)
61
+ return el
62
+
63
+ def parse_tp_setup(n5_path, key=None):
64
+ if isinstance(n5_path, str):
65
+ m = re.search(r'tpId_(\d+)_viewSetupId_(\d+)', n5_path)
66
+ if m:
67
+ return str(m.group(1)), int(m.group(2))
68
+ if isinstance(key, (tuple, list)) and len(key) == 2:
69
+ t, s = key
70
+ return str(t), int(s)
71
+ if isinstance(key, str):
72
+ m = re.search(r'timepoint:\s*(\d+).*setup:\s*(\d+)', key)
73
+ if m:
74
+ return str(m.group(1)), int(m.group(2))
75
+ return "0", 0
76
+
77
+ # Ensure <ViewInterestPoints> exists
78
+ vip = find_one('ViewInterestPoints')
79
+ if vip is None:
80
+ vip = ET.Element('ViewInterestPoints')
81
+ root.append(vip)
82
+
83
+ # Remove ALL existing entries
84
+ for child in list(vip):
85
+ vip.remove(child)
86
+
87
+ # Write new entries
88
+ seen = set()
89
+ for key, label_entries in self.new_split_interest_points.items():
90
+ for entry in label_entries:
91
+ if isinstance(entry, dict) and 'ip_list' in entry:
92
+ label = entry.get('label') or entry.get('key') or entry.get('name')
93
+ ip_list = entry['ip_list']
94
+ elif isinstance(entry, (list, tuple)) and len(entry) == 2:
95
+ label, ip_list = entry
96
+ else:
97
+ ip_list = entry
98
+ label = None
99
+
100
+ # Pull fields
101
+ n5_path = ip_list.get('xml_n5_path') or ip_list.get('path') or ''
102
+ params = ip_list.get('parameters', None)
103
+ if label is None and isinstance(n5_path, str) and '/' in n5_path:
104
+ label = n5_path.rsplit('/', 1)[-1]
105
+
106
+ t, s = parse_tp_setup(n5_path, key)
107
+ label = "" if label is None else str(label)
108
+
109
+ sig = (t, s, label, n5_path, params)
110
+ if sig in seen:
111
+ continue
112
+ seen.add(sig)
113
+
114
+ attrs = {
115
+ 'timepoint': str(t),
116
+ 'setup': str(s),
117
+ 'label': label,
118
+ }
119
+ if params is not None:
120
+ attrs['params'] = str(params)
121
+
122
+ elem = ET.SubElement(vip, 'ViewInterestPointsFile', attrs)
123
+ elem.text = n5_path
124
+
125
+ try:
126
+ ET.indent(root, space=" ")
127
+ except Exception:
128
+ pass
129
+
130
+ return ET.tostring(root, encoding='unicode')
131
+
132
+ def save_view_registrations_to_xml(self, xml):
133
+ root = ET.fromstring(xml)
134
+
135
+ def tagname(el):
136
+ return el.tag.split('}')[-1]
137
+
138
+ def find_one(tag):
139
+ el = root.find(f'.//{{*}}{tag}')
140
+ if el is None:
141
+ el = root.find(tag)
142
+ return el
143
+
144
+ # Find or create <ViewRegistrations>
145
+ view_regs = find_one('ViewRegistrations')
146
+ if view_regs is None:
147
+ view_regs = ET.Element('ViewRegistrations')
148
+ root.append(view_regs)
149
+
150
+ # --- only OLD ids here ---
151
+ targets = set()
152
+ for view in self.self_definition:
153
+ if 'old_view' in view:
154
+ tp_str, setup_val = view['old_view']
155
+ t = str(tp_str)
156
+ s = int(setup_val)
157
+ else:
158
+ t = str(view.get('timepoint', '0'))
159
+ s = int(view['setup'])
160
+ targets.add((t, s))
161
+
162
+ # Remove existing ViewRegistration nodes for those pairs
163
+ for vr in list(view_regs):
164
+ if tagname(vr) != 'ViewRegistration':
165
+ continue
166
+ tp = vr.get('timepoint')
167
+ st = vr.get('setup')
168
+ if tp is not None and st is not None and (tp, int(st)) in targets:
169
+ view_regs.remove(vr)
170
+
171
+ # Rebuild registrations (only OLD ids)
172
+ for view in self.self_definition:
173
+ tp_str, setup_val = view['old_view']
174
+ t = str(tp_str)
175
+ setup_id = str(view['new_view'][1])
176
+ old_models = list(view.get('old_models', []))
177
+
178
+ vr = ET.SubElement(view_regs, 'ViewRegistration', {
179
+ 'timepoint': t,
180
+ 'setup': setup_id,
181
+ })
182
+
183
+ for tr in old_models:
184
+ vt = ET.SubElement(vr, 'ViewTransform', {'type': tr.get('type', 'affine')})
185
+ nm = ET.SubElement(vt, 'Name')
186
+ nm.text = str(tr.get('name', ''))
187
+
188
+ aff = ET.SubElement(vt, 'affine')
189
+ raw = tr.get('affine', '')
190
+ txt = raw.get('string', raw) if isinstance(raw, dict) else raw
191
+ nums = re.findall(r'[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?', str(txt))
192
+ aff.text = ' '.join(nums[:12] if len(nums) >= 12 else nums)
193
+
194
+ if (nm.text or '').strip().lower() == 'image splitting':
195
+ aff.text = ' '.join(f'{float(x):.1f}' for x in nums[:12])
196
+
197
+ try:
198
+ ET.indent(root, space=" ")
199
+ except Exception:
200
+ pass
201
+
202
+ return ET.tostring(root, encoding='unicode')
203
+
204
+ def save_setup_id_to_xml(self, xml):
205
+ root = ET.fromstring(xml)
206
+
207
+ def tagname(el):
208
+ return el.tag.split('}')[-1]
209
+
210
+ def find_one(tag):
211
+ el = root.find(f'.//{{*}}{tag}')
212
+ if el is None:
213
+ el = root.find(tag)
214
+ return el
215
+
216
+ seq = find_one('SequenceDescription')
217
+ regs = find_one('ViewRegistrations')
218
+ setup_ids = find_one('SetupIds')
219
+ if setup_ids is None:
220
+ setup_ids = ET.Element('SetupIds')
221
+ kids = list(root)
222
+ insert_idx = len(kids)
223
+ if regs is not None and regs in kids:
224
+ insert_idx = kids.index(regs)
225
+ elif seq is not None and seq in kids:
226
+ insert_idx = kids.index(seq) + 1
227
+ root.insert(insert_idx, setup_ids)
228
+
229
+ view_setups = None
230
+ for ch in list(root):
231
+ if tagname(ch) == 'ViewSetups':
232
+ view_setups = ch
233
+ break
234
+ if view_setups is None:
235
+ view_setups = find_one('ViewSetups')
236
+ if view_setups is None:
237
+ view_setups = ET.Element('ViewSetups')
238
+ kids = list(root)
239
+ after_idx = -1
240
+ for i, ch in enumerate(kids):
241
+ if tagname(ch) in ('ImageLoader', 'SequenceDescription'):
242
+ after_idx = i
243
+ root.insert(after_idx + 1 if after_idx >= 0 else len(kids), view_setups)
244
+
245
+ def _norm_id(raw):
246
+ if isinstance(raw, (tuple, list)):
247
+ if len(raw) >= 2:
248
+ return int(raw[1])
249
+ return int(raw[0])
250
+ return int(raw)
251
+
252
+ target_ids = set(_norm_id(v['new_view']) for v in self.self_definition)
253
+
254
+ # ViewSetup cleanup
255
+ for child in list(view_setups):
256
+ if tagname(child) != 'ViewSetup':
257
+ continue
258
+ id_el = child.find('id') or child.find('{*}id')
259
+ if id_el is not None and id_el.text:
260
+ try:
261
+ if int(id_el.text.strip()) in target_ids:
262
+ view_setups.remove(child)
263
+ except Exception:
264
+ pass
265
+
266
+ # SetupIdDefinition cleanup
267
+ for sid in list(setup_ids):
268
+ if tagname(sid) != 'SetupIdDefinition':
269
+ continue
270
+ nid_el = sid.find('NewId') or sid.find('{*}NewId')
271
+ if nid_el is not None and nid_el.text:
272
+ try:
273
+ if int(nid_el.text.strip()) in target_ids:
274
+ setup_ids.remove(sid)
275
+ except Exception:
276
+ pass
277
+
278
+ for view in self.self_definition:
279
+ new_id = _norm_id(view['new_view'])
280
+ old_id = _norm_id(view['old_view'])
281
+ angle = view['angle']
282
+ channel = view['channel']
283
+ illumination = view['illumination']
284
+ tile = new_id
285
+ voxel_unit = view['voxel_unit']
286
+ voxel_size = view['voxel_dim']
287
+
288
+ mins = np.array(view["interval"][0], dtype=np.int64)
289
+ maxs = np.array(view["interval"][1], dtype=np.int64)
290
+ size = (maxs - mins + 1).tolist()
291
+
292
+ # <SetupIds>/<SetupIdDefinition>
293
+ def_el = ET.SubElement(setup_ids, 'SetupIdDefinition')
294
+ ET.SubElement(def_el, 'NewId').text = str(new_id)
295
+ ET.SubElement(def_el, 'OldId').text = str(old_id)
296
+ ET.SubElement(def_el, 'min').text = f"{int(mins[0])} {int(mins[1])} {int(mins[2])}"
297
+ ET.SubElement(def_el, 'max').text = f"{int(maxs[0])} {int(maxs[1])} {int(maxs[2])}"
298
+
299
+ # <ViewSetups>/<ViewSetup>
300
+ vs = ET.SubElement(view_setups, 'ViewSetup')
301
+ ET.SubElement(vs, 'id').text = str(new_id)
302
+ ET.SubElement(vs, 'size').text = f"{int(size[0])} {int(size[1])} {int(size[2])}"
303
+
304
+ vx = ET.SubElement(vs, 'voxelSize')
305
+ ET.SubElement(vx, 'unit').text = str(voxel_unit)
306
+ if isinstance(voxel_size, str):
307
+ ET.SubElement(vx, 'size').text = voxel_size.strip()
308
+ else:
309
+ ET.SubElement(vx, 'size').text = " ".join(str(x) for x in voxel_size)
310
+
311
+ attrs = ET.SubElement(vs, 'attributes')
312
+ ET.SubElement(attrs, 'illumination').text = str(int(illumination))
313
+ ET.SubElement(attrs, 'channel').text = str(int(channel))
314
+ ET.SubElement(attrs, 'tile').text = str(int(tile))
315
+ ET.SubElement(attrs, 'angle').text = str(int(angle))
316
+
317
+ try:
318
+ ET.indent(root, space=" ")
319
+ except Exception:
320
+ pass
321
+
322
+ return ET.tostring(root, encoding='unicode')
323
+
324
+ def save_setup_id_definition_to_xml(self, xml):
325
+ root = ET.fromstring(xml)
326
+
327
+ # find existing nodes (namespace-agnostic)
328
+ def tagname(el): return el.tag.split('}')[-1]
329
+ children = list(root)
330
+ regs_idx = next((i for i, ch in enumerate(children) if tagname(ch) == 'ViewRegistrations'), None)
331
+ seq_idx = next((i for i, ch in enumerate(children) if tagname(ch) == 'SequenceDescription'), None)
332
+ setup_ids = next((ch for ch in children if tagname(ch) == 'SetupIds'), None)
333
+
334
+ # create/position <SetupIds>
335
+ if setup_ids is None:
336
+ setup_ids = ET.Element('SetupIds')
337
+ insert_idx = regs_idx if regs_idx is not None else ((seq_idx + 1) if seq_idx is not None else len(children))
338
+ root.insert(insert_idx, setup_ids)
339
+ else:
340
+ setup_ids.clear()
341
+
342
+ for view in self.self_definition:
343
+ new_id = view['new_view']
344
+ old_id = view['old_view']
345
+ min_bound = view['interval'][0]
346
+ max_bound = view['interval'][1]
347
+
348
+ nid = int(new_id[1] if isinstance(new_id, (tuple, list)) else new_id)
349
+ oid = int(old_id[1] if isinstance(old_id, (tuple, list)) else old_id)
350
+
351
+ def_el = ET.SubElement(setup_ids, 'SetupIdDefinition')
352
+ ET.SubElement(def_el, 'NewId').text = str(nid)
353
+ ET.SubElement(def_el, 'OldId').text = str(oid)
354
+ ET.SubElement(def_el, 'min').text = f"{int(min_bound[0])} {int(min_bound[1])} {int(min_bound[2])}"
355
+ ET.SubElement(def_el, 'max').text = f"{int(max_bound[0])} {int(max_bound[1])} {int(max_bound[2])}"
356
+
357
+ try:
358
+ ET.indent(root, space=" ")
359
+ except Exception:
360
+ pass
361
+
362
+ return ET.tostring(root, encoding='unicode')
363
+
364
+ def run(self):
365
+ xml = self.save_setup_id_definition_to_xml(self.xml_file)
366
+ xml = self.save_setup_id_to_xml(xml)
367
+ xml = self.save_view_registrations_to_xml(xml)
368
+ xml = self.save_view_interest_points(xml)
369
+ xml = self.wrap_image_loader_for_split(xml)
370
+
371
+ if self.xml_output_path:
372
+ if self.xml_output_path.startswith("s3://"):
373
+ bucket, key = self.xml_output_path.replace("s3://", "", 1).split("/", 1)
374
+ boto3.client("s3").put_object(Bucket=bucket, Key=key, Body=xml.encode('utf-8'))
375
+ else:
376
+ with open(self.xml_output_path, "w", encoding="utf-8") as f:
377
+ f.write(xml)