mxcubecore 1.408.0__py3-none-any.whl → 1.410.0__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.

Potentially problematic release.


This version of mxcubecore might be problematic. Click here for more details.

@@ -75,9 +75,7 @@ class EpicsCommand(CommandObject):
75
75
  time.sleep(0.1)
76
76
  self.pv_connected = self.pv.connect(timeout=0.1)
77
77
 
78
- if self.pv_connected:
79
- self.value_changed(self.pv.get(as_string=self.read_as_str, timeout=0.1))
80
- else:
78
+ if not self.pv_connected:
81
79
  logging.getLogger("HWR").error(
82
80
  "EpicsCommand: Error connecting to pv %s.", self.pv_name
83
81
  )
@@ -4,7 +4,7 @@ import shutil
4
4
  from collections import defaultdict
5
5
  from datetime import datetime, timedelta
6
6
  from pathlib import Path
7
- from typing import TYPE_CHECKING, List, Optional
7
+ from typing import TYPE_CHECKING, Any, List, Optional
8
8
  from zoneinfo import ZoneInfo
9
9
 
10
10
  import requests
@@ -231,7 +231,6 @@ class ICATLIMS(AbstractLims):
231
231
  def __add_download_path_to_processing_plan(
232
232
  self, processing_plan, downloads: List[Download]
233
233
  ):
234
- # Build a lookup for file_path by filename
235
234
  file_path_lookup = {d.filename: d.path for d in downloads}
236
235
  group_paths = defaultdict(list)
237
236
  for d in downloads:
@@ -240,21 +239,23 @@ class ICATLIMS(AbstractLims):
240
239
 
241
240
  # Enrich the processing_plan
242
241
  for item in processing_plan:
243
- if item["key"] == "Advanced_processing":
244
- for pipeline in item["value"]:
245
- # Update reference
246
- ref = pipeline.get("reference")
247
- if ref in file_path_lookup:
248
- pipeline["reference"] = {"filepath": file_path_lookup[ref]}
249
-
250
- # Update search_models
251
- if "search_models" in pipeline:
252
- models = json.loads(pipeline["search_models"])
253
- for model in models:
254
- group = model.get("pdb_group")
255
- if group in group_paths:
256
- model["file_paths"] = group_paths[group]
257
- pipeline["search_models"] = models
242
+ key = item.get("key")
243
+ value = item.get("value")
244
+ if (
245
+ key == "reference"
246
+ and isinstance(value, str)
247
+ and value in file_path_lookup
248
+ ):
249
+ item["value"] = {"filepath": file_path_lookup[value]}
250
+ if key == "search_models":
251
+ models = value
252
+ if isinstance(models, str):
253
+ models = json.loads(models)
254
+ for model in models:
255
+ group = model.get("pdb_group")
256
+ if group in group_paths:
257
+ model["file_paths"] = group_paths[group]
258
+ item["value"] = models
258
259
 
259
260
  def _safe_json_loads(self, json_str):
260
261
  try:
@@ -262,25 +263,7 @@ class ICATLIMS(AbstractLims):
262
263
  except Exception:
263
264
  return str(json_str)
264
265
 
265
- def __to_sample(
266
- self, tracking_sample: dict, puck: dict, sample_sheets: List[SampleSheet]
267
- ) -> dict:
268
- """
269
- Convert a tracking sample and associated metadata into the internal
270
- sample data structure.
271
- - Extracts relevant sample metadata.
272
- - Resolves protein acronym from the sample sheet if available.
273
- - Maps experiment plan details into a diffraction plan dictionary.
274
- - Assembles all relevant fields into a structured sample dictionary.
275
-
276
- Args:
277
- tracking_sample (dict): The raw sample data from tracking.
278
- puck (dict): The puck (container) metadata associated with the sample.
279
- sample_sheets (List[SampleSheet]): List of sample sheets used for lookup.
280
-
281
- Returns:
282
- dict: A dictionary representing the standardized internal sample format.
283
- """
266
+ def __extract_sample_identifiers(self, tracking_sample: dict, puck: dict) -> dict:
284
267
  # Basic identifiers
285
268
  sample_name = str(tracking_sample.get("name"))
286
269
 
@@ -302,66 +285,8 @@ class ICATLIMS(AbstractLims):
302
285
  puck_name = puck.get("name", "UnknownPuck")
303
286
  parcel_name = puck.get("parcelName")
304
287
  parcel_id = puck.get("parcelId")
305
- # Determine protein acronym using sample sheet if available
306
- protein_acronym = sample_name # Default fallback
307
288
 
308
- sample_sheet = self.get_sample_sheet_by_id(sample_sheets, sample_sheet_id)
309
- if sample_sheet:
310
- protein_acronym = sample_sheet.name
311
-
312
- # This converts to key-value pairs
313
- experiment_plan = {
314
- item["key"]: item["value"]
315
- for item in tracking_sample.get("experimentPlan", {})
316
- }
317
-
318
- processing_plan = tracking_sample.get("processingPlan", [])
319
- downloads: List[Download] = []
320
- if processing_plan:
321
- for item in processing_plan:
322
- # when possible this converts the string to json
323
- item["value"] = self._safe_json_loads(item["value"])
324
-
325
- sample_information = None
326
- try:
327
- sample_information: SampleInformation = (
328
- self.__get_sample_information_by(sample_sheet_id)
329
- )
330
- if sample_information is not None:
331
- destination_folder = (
332
- HWR.beamline.session.get_base_process_directory()
333
- )
334
- msg = "Download resource: "
335
- msg += f"sample_sheet_id={sample_sheet_id} "
336
- msg += f"destination_folder={destination_folder}"
337
- logger.debug(msg)
338
- downloads = self._download_resources(
339
- sample_sheet_id,
340
- sample_information.resources,
341
- destination_folder,
342
- sample_name,
343
- )
344
- msg = f"downloaded {len(downloads)} resources"
345
- logger.debug(msg)
346
- if len(downloads) > 0:
347
- try:
348
- self.__add_download_path_to_processing_plan(
349
- processing_plan, downloads
350
- )
351
- except RuntimeError:
352
- logger.exception(
353
- "Failed __add_download_path_to_processing_plan"
354
- )
355
-
356
- processing_plan = {
357
- item["key"]: item["value"] for item in processing_plan
358
- }
359
-
360
- except RuntimeError as e:
361
- msg = f"error getting sample information {e}"
362
- logger.warning(msg)
363
-
364
- comments = tracking_sample.get("comments")
289
+ protein_acronym = self.__resolve_protein_acronym(sample_name, sample_sheet_id)
365
290
 
366
291
  return {
367
292
  "sampleName": sample_name,
@@ -376,29 +301,125 @@ class ICATLIMS(AbstractLims):
376
301
  "SampleTrackingParcel_id": parcel_id,
377
302
  "SampleTrackingContainer_id": puck_name,
378
303
  "SampleTrackingContainer_name": parcel_id,
379
- "smiles": None, # Placeholder for future chemical structure info
304
+ }
305
+
306
+ def __resolve_protein_acronym(self, sample_name: str, sample_sheet_id: str) -> str:
307
+ sample_sheet = self.get_sample_sheet_by_id(self.sample_sheets, sample_sheet_id)
308
+ return sample_sheet.name if sample_sheet else sample_name
309
+
310
+ def __parse_experiment_plan(self, tracking_sample: dict) -> dict[str, Any]:
311
+ return {
312
+ item["key"]: item["value"]
313
+ for item in tracking_sample.get("experimentPlan", {})
314
+ }
315
+
316
+ def __build_diffraction_plan(self, experiment_plan: dict) -> dict[str, Any]:
317
+ return {
318
+ # "diffractionPlanId": 457980, TODO: do we need this?
319
+ "experimentKind": experiment_plan.get("experimentKind"),
320
+ "numberOfPositions": experiment_plan.get("numberOfPositions"),
321
+ "observedResolution": experiment_plan.get("observedResolution"),
322
+ "preferredBeamDiameter": experiment_plan.get("preferredBeamDiameter"),
323
+ "radiationSensitivity": experiment_plan.get("radiationSensitivity"),
324
+ "requiredCompleteness": experiment_plan.get("requiredCompleteness"),
325
+ "requiredMultiplicity": experiment_plan.get("requiredMultiplicity"),
326
+ "requiredResolution": experiment_plan.get("requiredResolution"),
327
+ }
328
+
329
+ def __prepare_processing_plan(
330
+ self, tracking_sample: dict, sample_sheet_id: str, protein_acronym: str
331
+ ) -> dict[str, Any]:
332
+ processing_plan = tracking_sample.get("processingPlan", [])
333
+ if not processing_plan:
334
+ return {}
335
+
336
+ # Convert string values to JSON if possible
337
+ for item in processing_plan:
338
+ item["value"] = self._safe_json_loads(item.get("value"))
339
+
340
+ downloads = self.__get_or_download_plan_resources(
341
+ sample_sheet_id, protein_acronym
342
+ )
343
+
344
+ if downloads:
345
+ try:
346
+ self.__add_download_path_to_processing_plan(processing_plan, downloads)
347
+ except RuntimeError:
348
+ logger.exception("Failed __add_download_path_to_processing_plan")
349
+ return {item["key"]: item["value"] for item in processing_plan}
350
+
351
+ def __get_or_download_plan_resources(
352
+ self, sample_sheet_id: str, protein_acronym: str
353
+ ) -> List[Download]:
354
+ if not hasattr(self, "_downloads_cache"):
355
+ self._downloads_cache = {}
356
+
357
+ cache_key = (sample_sheet_id, protein_acronym)
358
+ if cache_key in self._downloads_cache:
359
+ logger.debug(f"Reusing cached downloads for {cache_key}")
360
+ return self._downloads_cache[cache_key]
361
+
362
+ sample_information = self.__get_sample_information_by(sample_sheet_id)
363
+ if not sample_information:
364
+ return []
365
+
366
+ # create subfolder per protein acronym
367
+ destination_folder = (
368
+ Path(HWR.beamline.session.get_base_process_directory())
369
+ / "processing_plan_resources"
370
+ / protein_acronym
371
+ )
372
+ destination_folder.mkdir(parents=True, exist_ok=True)
373
+
374
+ logger.debug(
375
+ f"Download resource: sample_sheet_id={sample_sheet_id} "
376
+ f"destination_folder={destination_folder}"
377
+ )
378
+
379
+ downloads = self._download_resources(
380
+ sample_sheet_id, sample_information.resources, destination_folder, ""
381
+ )
382
+
383
+ logger.debug(f"downloaded {len(downloads)} resources")
384
+
385
+ self._downloads_cache[cache_key] = downloads
386
+ return downloads
387
+
388
+ def __to_sample(
389
+ self, tracking_sample: dict, puck: dict, sample_sheets: List[SampleSheet]
390
+ ) -> dict[str, Any]:
391
+ """
392
+ Convert a tracking sample and associated metadata into the internal
393
+ sample data structure.
394
+ - Extracts relevant sample metadata.
395
+ - Resolves protein acronym from the sample sheet if available.
396
+ - Maps experiment plan details into a diffraction plan dictionary.
397
+ - Assembles all relevant fields into a structured sample dictionary.
398
+
399
+ Args:
400
+ tracking_sample (dict): The raw sample data from tracking.
401
+ puck (dict): The puck (container) metadata associated with the sample.
402
+ sample_sheets (List[SampleSheet]): List of sample sheets used for lookup.
403
+
404
+ Returns:
405
+ dict: A dictionary representing the standardized internal sample format.
406
+ """
407
+ sample_id_info = self.__extract_sample_identifiers(tracking_sample, puck)
408
+ experiment_plan = self.__parse_experiment_plan(tracking_sample)
409
+ processing_plan = self.__prepare_processing_plan(
410
+ tracking_sample,
411
+ sample_id_info["sample_sheet_id"],
412
+ sample_id_info["proteinAcronym"],
413
+ )
414
+
415
+ return {
416
+ **sample_id_info,
380
417
  "experimentType": experiment_plan.get("workflowType"),
381
418
  "crystalSpaceGroup": experiment_plan.get("forceSpaceGroup"),
382
- "diffractionPlan": {
383
- # "diffractionPlanId": 457980, TODO: do we need this?
384
- "experimentKind": experiment_plan.get("experimentKind"),
385
- "numberOfPositions": experiment_plan.get("numberOfPositions"),
386
- "observedResolution": experiment_plan.get("observedResolution"),
387
- "preferredBeamDiameter": experiment_plan.get("preferredBeamDiameter"),
388
- "radiationSensitivity": experiment_plan.get("radiationSensitivity"),
389
- "requiredCompleteness": experiment_plan.get("requiredCompleteness"),
390
- "requiredMultiplicity": experiment_plan.get("requiredMultiplicity"),
391
- "requiredResolution": experiment_plan.get("requiredResolution"),
392
- },
393
- "cellA": experiment_plan.get("unit_cell_a"),
394
- "cellB": experiment_plan.get("unit_cell_b"),
395
- "cellC": experiment_plan.get("unit_cell_c"),
396
- "cellAlpha": experiment_plan.get("unit_cell_alpha"),
397
- "cellBeta": experiment_plan.get("unit_cell_beta"),
398
- "cellGamma": experiment_plan.get("unit_cell_gamma"),
419
+ "diffractionPlan": self.__build_diffraction_plan(experiment_plan),
399
420
  "experimentPlan": experiment_plan,
400
421
  "processingPlan": processing_plan,
401
- "comments": comments,
422
+ "comments": tracking_sample.get("comments"),
402
423
  }
403
424
 
404
425
  def create_session(self, session_dict):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mxcubecore
3
- Version: 1.408.0
3
+ Version: 1.410.0
4
4
  Summary: Core libraries for the MXCuBE application
5
5
  License: LGPL-3.0-or-later
6
6
  Keywords: mxcube,mxcube3,mxcubecore
@@ -1,5 +1,5 @@
1
1
  mxcubecore/BaseHardwareObjects.py,sha256=Rzt34pY2_adftj2hTFSfhZyHjnR-FROH_a2CKqDRQTE,41302
2
- mxcubecore/Command/Epics.py,sha256=_LZZEcQXNeTIGKIrJynFBGeL5wMzlCv12U15IHNh2AE,7815
2
+ mxcubecore/Command/Epics.py,sha256=oKzFCpHFVUHe9kLPkQ3vig7V3zYSPRFmvQT2B8Kiktg,7720
3
3
  mxcubecore/Command/Exporter.py,sha256=HL6hncI_EUjjVX7frOsmxGa2Gyc-3PwUPXLUBJk4B20,8076
4
4
  mxcubecore/Command/Mockup.py,sha256=OrGAJC0JjTTjGSMZl7NiWJmVUQeEThMYdSQzuBD-bEc,1949
5
5
  mxcubecore/Command/Pool.py,sha256=ZS8sRafNSAo2Da_F-m-NOpJ1kmHi_l2SidZsdCxa7fY,9274
@@ -196,7 +196,7 @@ mxcubecore/HardwareObjects/GrobMotor.py,sha256=l5A9Us_ceLXlhEF6RNwyXQsI56fJiobWh
196
196
  mxcubecore/HardwareObjects/GrobSampleChanger.py,sha256=LMetcL45fWrwP4C8rtENrEDNydelLpJ77SD1JC5C3go,8113
197
197
  mxcubecore/HardwareObjects/Harvester.py,sha256=0TYUXmz-Pmfd7Dhz7ToUGHzJRuJmnga8-4BK4B0KGQA,26524
198
198
  mxcubecore/HardwareObjects/HarvesterMaintenance.py,sha256=8s4yHDEFG-C1WYyW_RlwrFPSpc8o5hGi14aQuFQFrHs,9405
199
- mxcubecore/HardwareObjects/ICATLIMS.py,sha256=YbYmeHKYxDqgN4I7a2SRA7e-Io5YPgzYAAeXEE4Im-s,54687
199
+ mxcubecore/HardwareObjects/ICATLIMS.py,sha256=VqRcCDTzEh_-yaJ9ZNbBlC1OAt79okKcOz9gdJEb1sc,54937
200
200
  mxcubecore/HardwareObjects/ISARAMaint.py,sha256=I8LHXK6wCfzixsxWmmcqWlrdaL3AOX91XmVeAwT7GPk,8959
201
201
  mxcubecore/HardwareObjects/LNLS/LNLSLIMS.py,sha256=oen9r3Furo_z7YSxo_wAwgXwaON9dWuZP5ob9tvnMO0,1462
202
202
  mxcubecore/HardwareObjects/LNLS/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -452,8 +452,8 @@ mxcubecore/utils/conversion.py,sha256=G1bk2Mi2ZwGbZa5pEeiFaKWxhSVXVGqu1L9_SioyUO
452
452
  mxcubecore/utils/qt_import.py,sha256=yL6hLqk2qF-6SVjlVKTg2HTAa9vawPwmPybRPu39Iks,14720
453
453
  mxcubecore/utils/tango.py,sha256=vwEVrIrWKEFaeaJUz3xbaC7XWHY8ZeJ-pfcSrTfZPIE,2114
454
454
  mxcubecore/utils/units.py,sha256=Gh7ovTUN00XBMUoyDG5W7akCx1pROL-M6pK2z1ouemg,1361
455
- mxcubecore-1.408.0.dist-info/COPYING,sha256=u-Mc8zCecwyo4YoP8UulmzCiZZ_MmCLROd_NBtOcRj0,35148
456
- mxcubecore-1.408.0.dist-info/COPYING.LESSER,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
457
- mxcubecore-1.408.0.dist-info/METADATA,sha256=2Yyedqh2xSCqRlxe6QhkyReMKo5jvyUIQ4w62uhx6nc,4259
458
- mxcubecore-1.408.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
459
- mxcubecore-1.408.0.dist-info/RECORD,,
455
+ mxcubecore-1.410.0.dist-info/COPYING,sha256=u-Mc8zCecwyo4YoP8UulmzCiZZ_MmCLROd_NBtOcRj0,35148
456
+ mxcubecore-1.410.0.dist-info/COPYING.LESSER,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
457
+ mxcubecore-1.410.0.dist-info/METADATA,sha256=QovpJuWhRJORDvJ_H35TDoKUIbiBWt9UXB3lbnddRA4,4259
458
+ mxcubecore-1.410.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
459
+ mxcubecore-1.410.0.dist-info/RECORD,,