44from datetime import datetime
55from itertools import count
66from pathlib import Path
7- from typing import Any , Dict , List , NamedTuple , Optional , OrderedDict , Tuple
7+ from typing import Any , Dict , List , Optional , OrderedDict , Tuple
88
99import requests
1010import xmltodict
2121 capture_post ,
2222 get_machine_config_client ,
2323)
24+ from murfey .util .spa_metadata import (
25+ foil_hole_data ,
26+ foil_hole_from_file ,
27+ get_grid_square_atlas_positions ,
28+ grid_square_data ,
29+ grid_square_from_file ,
30+ )
2431
2532logger = logging .getLogger ("murfey.client.contexts.spa" )
2633
2734requests .get , requests .post , requests .put , requests .delete = authorised_requests ()
2835
2936
30- class FoilHole (NamedTuple ):
31- id : int
32- grid_square_id : int
33- x_location : Optional [float ] = None
34- y_location : Optional [float ] = None
35- x_stage_position : Optional [float ] = None
36- y_stage_position : Optional [float ] = None
37- readout_area_x : Optional [int ] = None
38- readout_area_y : Optional [int ] = None
39- thumbnail_size_x : Optional [int ] = None
40- thumbnail_size_y : Optional [int ] = None
41- pixel_size : Optional [float ] = None
42- image : str = ""
43- diameter : Optional [float ] = None
44-
45-
46- class GridSquare (NamedTuple ):
47- session_id : int
48- id : int
49- x_location : Optional [float ] = None
50- y_location : Optional [float ] = None
51- x_stage_position : Optional [float ] = None
52- y_stage_position : Optional [float ] = None
53- readout_area_x : Optional [int ] = None
54- readout_area_y : Optional [int ] = None
55- thumbnail_size_x : Optional [int ] = None
56- thumbnail_size_y : Optional [int ] = None
57- pixel_size : Optional [float ] = None
58- image : str = ""
59- tag : str = ""
60-
61-
62- def _get_grid_square_atlas_positions (xml_path : Path , grid_square : str = "" ) -> Dict [
63- str ,
64- Tuple [
65- Optional [int ],
66- Optional [int ],
67- Optional [float ],
68- Optional [float ],
69- Optional [int ],
70- Optional [int ],
71- Optional [float ],
72- ],
73- ]:
74- with open (
75- xml_path ,
76- "r" ,
77- ) as dm :
78- atlas_data = xmltodict .parse (dm .read ())
79- tile_info = atlas_data ["AtlasSessionXml" ]["Atlas" ]["TilesEfficient" ]["_items" ][
80- "TileXml"
81- ]
82- gs_pix_positions : Dict [
83- str ,
84- Tuple [
85- Optional [int ],
86- Optional [int ],
87- Optional [float ],
88- Optional [float ],
89- Optional [int ],
90- Optional [int ],
91- Optional [float ],
92- ],
93- ] = {}
94- for ti in tile_info :
95- try :
96- nodes = ti ["Nodes" ]["KeyValuePairs" ]
97- except KeyError :
98- continue
99- required_key = ""
100- for key in nodes .keys ():
101- if key .startswith ("KeyValuePairOfintNodeXml" ):
102- required_key = key
103- break
104- if not required_key :
105- continue
106- for gs in nodes [required_key ]:
107- if not isinstance (gs , dict ):
108- continue
109- if not grid_square or gs ["key" ] == grid_square :
110- gs_pix_positions [gs ["key" ]] = (
111- int (float (gs ["value" ]["b:PositionOnTheAtlas" ]["c:Center" ]["d:x" ])),
112- int (float (gs ["value" ]["b:PositionOnTheAtlas" ]["c:Center" ]["d:y" ])),
113- float (gs ["value" ]["b:PositionOnTheAtlas" ]["c:Physical" ]["d:x" ])
114- * 1e9 ,
115- float (gs ["value" ]["b:PositionOnTheAtlas" ]["c:Physical" ]["d:y" ])
116- * 1e9 ,
117- int (
118- float (gs ["value" ]["b:PositionOnTheAtlas" ]["c:Size" ]["d:width" ])
119- ),
120- int (
121- float (gs ["value" ]["b:PositionOnTheAtlas" ]["c:Size" ]["d:height" ])
122- ),
123- float (gs ["value" ]["b:PositionOnTheAtlas" ]["c:Rotation" ]),
124- )
125- if grid_square :
126- break
127- return gs_pix_positions
128-
129-
13037def _file_transferred_to (
13138 environment : MurfeyInstanceEnvironment , source : Path , file_path : Path
13239):
@@ -149,17 +56,6 @@ def _file_transferred_to(
14956 )
15057
15158
152- def _grid_square_from_file (f : Path ) -> int :
153- for p in f .parts :
154- if p .startswith ("GridSquare" ):
155- return int (p .split ("_" )[1 ])
156- raise ValueError (f"Grid square ID could not be determined from path { f } " )
157-
158-
159- def _foil_hole_from_file (f : Path ) -> int :
160- return int (f .name .split ("_" )[1 ])
161-
162-
16359def _grid_square_metadata_file (
16460 f : Path , data_directories : List [Path ], visit : str , grid_square : int
16561) -> Path :
@@ -179,96 +75,6 @@ def _grid_square_metadata_file(
17975 )
18076
18177
182- def _grid_square_data (xml_path : Path , grid_square : int , session_id : int ) -> GridSquare :
183- image_paths = list (
184- (xml_path .parent .parent ).glob (
185- f"Images-Disc*/GridSquare_{ grid_square } /GridSquare_*.jpg"
186- )
187- )
188- if image_paths :
189- image_paths .sort (key = lambda x : x .stat ().st_ctime )
190- image_path = image_paths [- 1 ]
191- with open (Path (image_path ).with_suffix (".xml" )) as gs_xml :
192- gs_xml_data = xmltodict .parse (gs_xml .read ())
193- readout_area = gs_xml_data ["MicroscopeImage" ]["microscopeData" ]["acquisition" ][
194- "camera"
195- ]["ReadoutArea" ]
196- pixel_size = gs_xml_data ["MicroscopeImage" ]["SpatialScale" ]["pixelSize" ]["x" ][
197- "numericValue"
198- ]
199- full_size = (int (readout_area ["a:width" ]), int (readout_area ["a:height" ]))
200- return GridSquare (
201- id = grid_square ,
202- session_id = session_id ,
203- readout_area_x = full_size [0 ] if image_path else None ,
204- readout_area_y = full_size [1 ] if image_path else None ,
205- thumbnail_size_x = int ((512 / max (full_size )) * full_size [0 ]),
206- thumbnail_size_y = int ((512 / max (full_size )) * full_size [1 ]),
207- pixel_size = float (pixel_size ) if image_path else None ,
208- image = str (image_path ),
209- )
210- return GridSquare (id = grid_square , session_id = session_id )
211-
212-
213- def _foil_hole_data (
214- xml_path : Path , foil_hole : int , grid_square : int , session_id : int
215- ) -> FoilHole :
216- with open (xml_path , "r" ) as xml :
217- for_parsing = xml .read ()
218- data = xmltodict .parse (for_parsing )
219- data = data ["GridSquareXml" ]
220- serialization_array = data ["TargetLocations" ]["TargetLocationsEfficient" ][
221- "a:m_serializationArray"
222- ]
223- required_key = ""
224- for key in serialization_array .keys ():
225- if key .startswith ("b:KeyValuePairOfintTargetLocation" ):
226- required_key = key
227- break
228- if required_key :
229- image_paths = list (
230- (xml_path .parent .parent ).glob (
231- f"Images-Disc*/GridSquare_{ grid_square } /FoilHoles/FoilHole_{ foil_hole } _*.jpg"
232- )
233- )
234- image_paths .sort (key = lambda x : x .stat ().st_ctime )
235- image_path : Path | str = image_paths [- 1 ] if image_paths else ""
236- if image_path :
237- with open (Path (image_path ).with_suffix (".xml" )) as fh_xml :
238- fh_xml_data = xmltodict .parse (fh_xml .read ())
239- readout_area = fh_xml_data ["MicroscopeImage" ]["microscopeData" ][
240- "acquisition"
241- ]["camera" ]["ReadoutArea" ]
242- pixel_size = fh_xml_data ["MicroscopeImage" ]["SpatialScale" ]["pixelSize" ][
243- "x"
244- ]["numericValue" ]
245- full_size = (int (readout_area ["a:width" ]), int (readout_area ["a:height" ]))
246- for fh_block in serialization_array [required_key ]:
247- pix = fh_block ["b:value" ]["PixelCenter" ]
248- stage = fh_block ["b:value" ]["StagePosition" ]
249- diameter = fh_block ["b:value" ]["PixelWidthHeight" ]["c:width" ]
250- if int (fh_block ["b:key" ]) == foil_hole :
251- return FoilHole (
252- id = foil_hole ,
253- grid_square_id = grid_square ,
254- x_location = float (pix ["c:x" ]),
255- y_location = float (pix ["c:y" ]),
256- x_stage_position = float (stage ["c:X" ]),
257- y_stage_position = float (stage ["c:Y" ]),
258- readout_area_x = full_size [0 ] if image_path else None ,
259- readout_area_y = full_size [1 ] if image_path else None ,
260- thumbnail_size_x = None ,
261- thumbnail_size_y = None ,
262- pixel_size = float (pixel_size ) if image_path else None ,
263- image = str (image_path ),
264- diameter = diameter ,
265- )
266- logger .warning (
267- f"Foil hole positions could not be determined from metadata file { xml_path } for foil hole { foil_hole } "
268- )
269- return FoilHole (id = foil_hole , grid_square_id = grid_square )
270-
271-
27278def _get_source (file_path : Path , environment : MurfeyInstanceEnvironment ) -> Path | None :
27379 for s in environment .sources :
27480 if file_path .is_relative_to (s ):
@@ -564,8 +370,8 @@ def _position_analysis(
564370 environment : MurfeyInstanceEnvironment ,
565371 source : Path ,
566372 machine_config : dict ,
567- ) -> int :
568- grid_square = _grid_square_from_file (transferred_file )
373+ ) -> Optional [ int ] :
374+ grid_square = grid_square_from_file (transferred_file )
569375 grid_square_metadata_file = _grid_square_metadata_file (
570376 transferred_file ,
571377 [Path (p ) for p in machine_config ["data_directories" ]],
@@ -594,6 +400,9 @@ def _position_analysis(
594400 .json ()
595401 .get (str (source ), {})
596402 )
403+ if not data_collection_group :
404+ logger .info ("Data collection group has not yet been made" )
405+ return None
597406 if data_collection_group .get ("atlas" ):
598407 visit_path = ""
599408 for p in transferred_file .parts :
@@ -604,15 +413,14 @@ def _position_analysis(
604413 local_atlas_path = (
605414 Path (visit_path ) / environment .samples [source ].atlas
606415 )
607- gs_pix_position = _get_grid_square_atlas_positions (
416+ gs_pix_position = get_grid_square_atlas_positions (
608417 local_atlas_path ,
609418 grid_square = str (grid_square ),
610419 )[str (grid_square )]
611420 gs_url = f"{ str (environment .url .geturl ())} /sessions/{ environment .murfey_session } /grid_square/{ grid_square } "
612- gs = _grid_square_data (
421+ gs = grid_square_data (
613422 grid_square_metadata_file ,
614423 grid_square ,
615- environment .murfey_session ,
616424 )
617425 metadata_source = Path (
618426 (
@@ -645,18 +453,17 @@ def _position_analysis(
645453 "angle" : gs_pix_position [6 ],
646454 },
647455 )
648- foil_hole = _foil_hole_from_file (transferred_file )
456+ foil_hole = foil_hole_from_file (transferred_file )
649457 if foil_hole not in self ._foil_holes [grid_square ]:
650458 fh_url = f"{ str (environment .url .geturl ())} /sessions/{ environment .murfey_session } /grid_square/{ grid_square } /foil_hole"
651459 if (
652460 grid_square_metadata_file .is_file ()
653461 and environment .murfey_session is not None
654462 ):
655- fh = _foil_hole_data (
463+ fh = foil_hole_data (
656464 grid_square_metadata_file ,
657465 foil_hole ,
658466 grid_square ,
659- environment .murfey_session ,
660467 )
661468 metadata_source = Path (
662469 (
0 commit comments