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- session_id : int
32- id : int
33- grid_square_id : int
34- x_location : Optional [float ] = None
35- y_location : Optional [float ] = None
36- x_stage_position : Optional [float ] = None
37- y_stage_position : Optional [float ] = None
38- readout_area_x : Optional [int ] = None
39- readout_area_y : Optional [int ] = None
40- thumbnail_size_x : Optional [int ] = None
41- thumbnail_size_y : Optional [int ] = None
42- pixel_size : Optional [float ] = None
43- image : str = ""
44- diameter : Optional [float ] = None
45-
46-
47- class GridSquare (NamedTuple ):
48- session_id : int
49- id : int
50- x_location : Optional [float ] = None
51- y_location : Optional [float ] = None
52- x_stage_position : Optional [float ] = None
53- y_stage_position : Optional [float ] = None
54- readout_area_x : Optional [int ] = None
55- readout_area_y : Optional [int ] = None
56- thumbnail_size_x : Optional [int ] = None
57- thumbnail_size_y : Optional [int ] = None
58- pixel_size : Optional [float ] = None
59- image : str = ""
60- tag : str = ""
61-
62-
63- def _get_grid_square_atlas_positions (xml_path : Path , grid_square : str = "" ) -> Dict [
64- str ,
65- Tuple [
66- Optional [int ],
67- Optional [int ],
68- Optional [float ],
69- Optional [float ],
70- Optional [int ],
71- Optional [int ],
72- Optional [float ],
73- ],
74- ]:
75- with open (
76- xml_path ,
77- "r" ,
78- ) as dm :
79- atlas_data = xmltodict .parse (dm .read ())
80- tile_info = atlas_data ["AtlasSessionXml" ]["Atlas" ]["TilesEfficient" ]["_items" ][
81- "TileXml"
82- ]
83- gs_pix_positions : Dict [
84- str ,
85- Tuple [
86- Optional [int ],
87- Optional [int ],
88- Optional [float ],
89- Optional [float ],
90- Optional [int ],
91- Optional [int ],
92- Optional [float ],
93- ],
94- ] = {}
95- for ti in tile_info :
96- try :
97- nodes = ti ["Nodes" ]["KeyValuePairs" ]
98- except KeyError :
99- continue
100- required_key = ""
101- for key in nodes .keys ():
102- if key .startswith ("KeyValuePairOfintNodeXml" ):
103- required_key = key
104- break
105- if not required_key :
106- continue
107- for gs in nodes [required_key ]:
108- if not isinstance (gs , dict ):
109- continue
110- if not grid_square or gs ["key" ] == grid_square :
111- gs_pix_positions [gs ["key" ]] = (
112- int (float (gs ["value" ]["b:PositionOnTheAtlas" ]["c:Center" ]["d:x" ])),
113- int (float (gs ["value" ]["b:PositionOnTheAtlas" ]["c:Center" ]["d:y" ])),
114- float (gs ["value" ]["b:PositionOnTheAtlas" ]["c:Physical" ]["d:x" ])
115- * 1e9 ,
116- float (gs ["value" ]["b:PositionOnTheAtlas" ]["c:Physical" ]["d:y" ])
117- * 1e9 ,
118- int (
119- float (gs ["value" ]["b:PositionOnTheAtlas" ]["c:Size" ]["d:width" ])
120- ),
121- int (
122- float (gs ["value" ]["b:PositionOnTheAtlas" ]["c:Size" ]["d:height" ])
123- ),
124- float (gs ["value" ]["b:PositionOnTheAtlas" ]["c:Rotation" ]),
125- )
126- if grid_square :
127- break
128- return gs_pix_positions
129-
130-
13137def _file_transferred_to (
13238 environment : MurfeyInstanceEnvironment , source : Path , file_path : Path
13339):
@@ -150,17 +56,6 @@ def _file_transferred_to(
15056 )
15157
15258
153- def _grid_square_from_file (f : Path ) -> int :
154- for p in f .parts :
155- if p .startswith ("GridSquare" ):
156- return int (p .split ("_" )[1 ])
157- raise ValueError (f"Grid square ID could not be determined from path { f } " )
158-
159-
160- def _foil_hole_from_file (f : Path ) -> int :
161- return int (f .name .split ("_" )[1 ])
162-
163-
16459def _grid_square_metadata_file (
16560 f : Path , data_directories : List [Path ], visit : str , grid_square : int
16661) -> Path :
@@ -180,97 +75,6 @@ def _grid_square_metadata_file(
18075 )
18176
18277
183- def _grid_square_data (xml_path : Path , grid_square : int , session_id : int ) -> GridSquare :
184- image_paths = list (
185- (xml_path .parent .parent ).glob (
186- f"Images-Disc*/GridSquare_{ grid_square } /GridSquare_*.jpg"
187- )
188- )
189- if image_paths :
190- image_paths .sort (key = lambda x : x .stat ().st_ctime )
191- image_path = image_paths [- 1 ]
192- with open (Path (image_path ).with_suffix (".xml" )) as gs_xml :
193- gs_xml_data = xmltodict .parse (gs_xml .read ())
194- readout_area = gs_xml_data ["MicroscopeImage" ]["microscopeData" ]["acquisition" ][
195- "camera"
196- ]["ReadoutArea" ]
197- pixel_size = gs_xml_data ["MicroscopeImage" ]["SpatialScale" ]["pixelSize" ]["x" ][
198- "numericValue"
199- ]
200- full_size = (int (readout_area ["a:width" ]), int (readout_area ["a:height" ]))
201- return GridSquare (
202- id = grid_square ,
203- session_id = session_id ,
204- readout_area_x = full_size [0 ] if image_path else None ,
205- readout_area_y = full_size [1 ] if image_path else None ,
206- thumbnail_size_x = int ((512 / max (full_size )) * full_size [0 ]),
207- thumbnail_size_y = int ((512 / max (full_size )) * full_size [1 ]),
208- pixel_size = float (pixel_size ) if image_path else None ,
209- image = str (image_path ),
210- )
211- return GridSquare (id = grid_square , session_id = session_id )
212-
213-
214- def _foil_hole_data (
215- xml_path : Path , foil_hole : int , grid_square : int , session_id : int
216- ) -> FoilHole :
217- with open (xml_path , "r" ) as xml :
218- for_parsing = xml .read ()
219- data = xmltodict .parse (for_parsing )
220- data = data ["GridSquareXml" ]
221- serialization_array = data ["TargetLocations" ]["TargetLocationsEfficient" ][
222- "a:m_serializationArray"
223- ]
224- required_key = ""
225- for key in serialization_array .keys ():
226- if key .startswith ("b:KeyValuePairOfintTargetLocation" ):
227- required_key = key
228- break
229- if required_key :
230- image_paths = list (
231- (xml_path .parent .parent ).glob (
232- f"Images-Disc*/GridSquare_{ grid_square } /FoilHoles/FoilHole_{ foil_hole } _*.jpg"
233- )
234- )
235- image_paths .sort (key = lambda x : x .stat ().st_ctime )
236- image_path : Path | str = image_paths [- 1 ] if image_paths else ""
237- if image_path :
238- with open (Path (image_path ).with_suffix (".xml" )) as fh_xml :
239- fh_xml_data = xmltodict .parse (fh_xml .read ())
240- readout_area = fh_xml_data ["MicroscopeImage" ]["microscopeData" ][
241- "acquisition"
242- ]["camera" ]["ReadoutArea" ]
243- pixel_size = fh_xml_data ["MicroscopeImage" ]["SpatialScale" ]["pixelSize" ][
244- "x"
245- ]["numericValue" ]
246- full_size = (int (readout_area ["a:width" ]), int (readout_area ["a:height" ]))
247- for fh_block in serialization_array [required_key ]:
248- pix = fh_block ["b:value" ]["PixelCenter" ]
249- stage = fh_block ["b:value" ]["StagePosition" ]
250- diameter = fh_block ["b:value" ]["PixelWidthHeight" ]["c:width" ]
251- if int (fh_block ["b:key" ]) == foil_hole :
252- return FoilHole (
253- id = foil_hole ,
254- grid_square_id = grid_square ,
255- session_id = session_id ,
256- x_location = float (pix ["c:x" ]),
257- y_location = float (pix ["c:y" ]),
258- x_stage_position = float (stage ["c:X" ]),
259- y_stage_position = float (stage ["c:Y" ]),
260- readout_area_x = full_size [0 ] if image_path else None ,
261- readout_area_y = full_size [1 ] if image_path else None ,
262- thumbnail_size_x = None ,
263- thumbnail_size_y = None ,
264- pixel_size = float (pixel_size ) if image_path else None ,
265- image = str (image_path ),
266- diameter = diameter ,
267- )
268- logger .warning (
269- f"Foil hole positions could not be determined from metadata file { xml_path } for foil hole { foil_hole } "
270- )
271- return FoilHole (id = foil_hole , grid_square_id = grid_square , session_id = session_id )
272-
273-
27478def _get_source (file_path : Path , environment : MurfeyInstanceEnvironment ) -> Path | None :
27579 for s in environment .sources :
27680 if file_path .is_relative_to (s ):
@@ -566,8 +370,8 @@ def _position_analysis(
566370 environment : MurfeyInstanceEnvironment ,
567371 source : Path ,
568372 machine_config : dict ,
569- ) -> int :
570- grid_square = _grid_square_from_file (transferred_file )
373+ ) -> Optional [ int ] :
374+ grid_square = grid_square_from_file (transferred_file )
571375 grid_square_metadata_file = _grid_square_metadata_file (
572376 transferred_file ,
573377 [Path (p ) for p in machine_config ["data_directories" ]],
@@ -596,6 +400,9 @@ def _position_analysis(
596400 .json ()
597401 .get (str (source ), {})
598402 )
403+ if not data_collection_group :
404+ logger .info ("Data collection group has not yet been made" )
405+ return None
599406 if data_collection_group .get ("atlas" ):
600407 visit_path = ""
601408 for p in transferred_file .parts :
@@ -606,15 +413,14 @@ def _position_analysis(
606413 local_atlas_path = (
607414 Path (visit_path ) / environment .samples [source ].atlas
608415 )
609- gs_pix_position = _get_grid_square_atlas_positions (
416+ gs_pix_position = get_grid_square_atlas_positions (
610417 local_atlas_path ,
611418 grid_square = str (grid_square ),
612419 )[str (grid_square )]
613420 gs_url = f"{ str (environment .url .geturl ())} /sessions/{ environment .murfey_session } /grid_square/{ grid_square } "
614- gs = _grid_square_data (
421+ gs = grid_square_data (
615422 grid_square_metadata_file ,
616423 grid_square ,
617- environment .murfey_session ,
618424 )
619425 metadata_source = Path (
620426 (
@@ -647,18 +453,17 @@ def _position_analysis(
647453 "angle" : gs_pix_position [6 ],
648454 },
649455 )
650- foil_hole = _foil_hole_from_file (transferred_file )
456+ foil_hole = foil_hole_from_file (transferred_file )
651457 if foil_hole not in self ._foil_holes [grid_square ]:
652458 fh_url = f"{ str (environment .url .geturl ())} /sessions/{ environment .murfey_session } /grid_square/{ grid_square } /foil_hole"
653459 if (
654460 grid_square_metadata_file .is_file ()
655461 and environment .murfey_session is not None
656462 ):
657- fh = _foil_hole_data (
463+ fh = foil_hole_data (
658464 grid_square_metadata_file ,
659465 foil_hole ,
660466 grid_square ,
661- environment .murfey_session ,
662467 )
663468 metadata_source = Path (
664469 (
0 commit comments