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.
- Rhapso/__init__.py +1 -0
- Rhapso/data_prep/__init__.py +2 -0
- Rhapso/data_prep/n5_reader.py +188 -0
- Rhapso/data_prep/s3_big_stitcher_reader.py +55 -0
- Rhapso/data_prep/xml_to_dataframe.py +215 -0
- Rhapso/detection/__init__.py +5 -0
- Rhapso/detection/advanced_refinement.py +203 -0
- Rhapso/detection/difference_of_gaussian.py +324 -0
- Rhapso/detection/image_reader.py +117 -0
- Rhapso/detection/metadata_builder.py +130 -0
- Rhapso/detection/overlap_detection.py +327 -0
- Rhapso/detection/points_validation.py +49 -0
- Rhapso/detection/save_interest_points.py +265 -0
- Rhapso/detection/view_transform_models.py +67 -0
- Rhapso/fusion/__init__.py +0 -0
- Rhapso/fusion/affine_fusion/__init__.py +2 -0
- Rhapso/fusion/affine_fusion/blend.py +289 -0
- Rhapso/fusion/affine_fusion/fusion.py +601 -0
- Rhapso/fusion/affine_fusion/geometry.py +159 -0
- Rhapso/fusion/affine_fusion/io.py +546 -0
- Rhapso/fusion/affine_fusion/script_utils.py +111 -0
- Rhapso/fusion/affine_fusion/setup.py +4 -0
- Rhapso/fusion/affine_fusion_worker.py +234 -0
- Rhapso/fusion/multiscale/__init__.py +0 -0
- Rhapso/fusion/multiscale/aind_hcr_data_transformation/__init__.py +19 -0
- Rhapso/fusion/multiscale/aind_hcr_data_transformation/compress/__init__.py +3 -0
- Rhapso/fusion/multiscale/aind_hcr_data_transformation/compress/czi_to_zarr.py +698 -0
- Rhapso/fusion/multiscale/aind_hcr_data_transformation/compress/zarr_writer.py +265 -0
- Rhapso/fusion/multiscale/aind_hcr_data_transformation/models.py +81 -0
- Rhapso/fusion/multiscale/aind_hcr_data_transformation/utils/__init__.py +3 -0
- Rhapso/fusion/multiscale/aind_hcr_data_transformation/utils/utils.py +526 -0
- Rhapso/fusion/multiscale/aind_hcr_data_transformation/zeiss_job.py +249 -0
- Rhapso/fusion/multiscale/aind_z1_radial_correction/__init__.py +21 -0
- Rhapso/fusion/multiscale/aind_z1_radial_correction/array_to_zarr.py +257 -0
- Rhapso/fusion/multiscale/aind_z1_radial_correction/radial_correction.py +557 -0
- Rhapso/fusion/multiscale/aind_z1_radial_correction/run_capsule.py +98 -0
- Rhapso/fusion/multiscale/aind_z1_radial_correction/utils/__init__.py +3 -0
- Rhapso/fusion/multiscale/aind_z1_radial_correction/utils/utils.py +266 -0
- Rhapso/fusion/multiscale/aind_z1_radial_correction/worker.py +89 -0
- Rhapso/fusion/multiscale_worker.py +113 -0
- Rhapso/fusion/neuroglancer_link_gen/__init__.py +8 -0
- Rhapso/fusion/neuroglancer_link_gen/dispim_link.py +235 -0
- Rhapso/fusion/neuroglancer_link_gen/exaspim_link.py +127 -0
- Rhapso/fusion/neuroglancer_link_gen/hcr_link.py +368 -0
- Rhapso/fusion/neuroglancer_link_gen/iSPIM_top.py +47 -0
- Rhapso/fusion/neuroglancer_link_gen/link_utils.py +239 -0
- Rhapso/fusion/neuroglancer_link_gen/main.py +299 -0
- Rhapso/fusion/neuroglancer_link_gen/ng_layer.py +1434 -0
- Rhapso/fusion/neuroglancer_link_gen/ng_state.py +1123 -0
- Rhapso/fusion/neuroglancer_link_gen/parsers.py +336 -0
- Rhapso/fusion/neuroglancer_link_gen/raw_link.py +116 -0
- Rhapso/fusion/neuroglancer_link_gen/utils/__init__.py +4 -0
- Rhapso/fusion/neuroglancer_link_gen/utils/shader_utils.py +85 -0
- Rhapso/fusion/neuroglancer_link_gen/utils/transfer.py +43 -0
- Rhapso/fusion/neuroglancer_link_gen/utils/utils.py +303 -0
- Rhapso/fusion/neuroglancer_link_gen_worker.py +30 -0
- Rhapso/matching/__init__.py +0 -0
- Rhapso/matching/load_and_transform_points.py +458 -0
- Rhapso/matching/ransac_matching.py +544 -0
- Rhapso/matching/save_matches.py +120 -0
- Rhapso/matching/xml_parser.py +302 -0
- Rhapso/pipelines/__init__.py +0 -0
- Rhapso/pipelines/ray/__init__.py +0 -0
- Rhapso/pipelines/ray/aws/__init__.py +0 -0
- Rhapso/pipelines/ray/aws/alignment_pipeline.py +227 -0
- Rhapso/pipelines/ray/aws/config/__init__.py +0 -0
- Rhapso/pipelines/ray/evaluation.py +71 -0
- Rhapso/pipelines/ray/interest_point_detection.py +137 -0
- Rhapso/pipelines/ray/interest_point_matching.py +110 -0
- Rhapso/pipelines/ray/local/__init__.py +0 -0
- Rhapso/pipelines/ray/local/alignment_pipeline.py +167 -0
- Rhapso/pipelines/ray/matching_stats.py +104 -0
- Rhapso/pipelines/ray/param/__init__.py +0 -0
- Rhapso/pipelines/ray/solver.py +120 -0
- Rhapso/pipelines/ray/split_dataset.py +78 -0
- Rhapso/solver/__init__.py +0 -0
- Rhapso/solver/compute_tiles.py +562 -0
- Rhapso/solver/concatenate_models.py +116 -0
- Rhapso/solver/connected_graphs.py +111 -0
- Rhapso/solver/data_prep.py +181 -0
- Rhapso/solver/global_optimization.py +410 -0
- Rhapso/solver/model_and_tile_setup.py +109 -0
- Rhapso/solver/pre_align_tiles.py +323 -0
- Rhapso/solver/save_results.py +97 -0
- Rhapso/solver/view_transforms.py +75 -0
- Rhapso/solver/xml_to_dataframe_solver.py +213 -0
- Rhapso/split_dataset/__init__.py +0 -0
- Rhapso/split_dataset/compute_grid_rules.py +78 -0
- Rhapso/split_dataset/save_points.py +101 -0
- Rhapso/split_dataset/save_xml.py +377 -0
- Rhapso/split_dataset/split_images.py +537 -0
- Rhapso/split_dataset/xml_to_dataframe_split.py +219 -0
- rhapso-0.1.92.dist-info/METADATA +39 -0
- rhapso-0.1.92.dist-info/RECORD +101 -0
- rhapso-0.1.92.dist-info/WHEEL +5 -0
- rhapso-0.1.92.dist-info/licenses/LICENSE +21 -0
- rhapso-0.1.92.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/test_detection.py +17 -0
- tests/test_matching.py +21 -0
- 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)
|