Skip to content

Commit 883fdbb

Browse files
authored
Merge pull request #479 from European-XFEL/feat/detdata-source-names
Add source names to DetectorData
2 parents 85507ed + 100a485 commit 883fdbb

2 files changed

Lines changed: 51 additions & 2 deletions

File tree

src/extra/calibration.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
2+
import ast
13
import json
24
import re
35
from collections.abc import Mapping
@@ -8,6 +10,7 @@
810
from functools import lru_cache
911
from operator import index
1012
from pathlib import Path
13+
from string import Formatter
1114
from typing import Dict, List, Optional, Union
1215
from urllib.parse import urljoin
1316
from warnings import warn
@@ -1431,6 +1434,21 @@ class ShimadzuHPVX2Conditions(ConditionsBase):
14311434
}
14321435

14331436

1437+
class SourceExprChecker(ast.NodeVisitor):
1438+
def visit_Call(self, node):
1439+
raise ValueError("Function calls not allowed in source name patterns")
1440+
1441+
1442+
class SourceNameFormatter(Formatter):
1443+
"""String formatter that evaluates simple operations like {modno + 2}"""
1444+
1445+
def get_field(self, field_name, args, kwargs):
1446+
node = ast.parse(field_name, "<source pattern>", "eval")
1447+
SourceExprChecker().visit(node)
1448+
obj = eval(compile(node, "<source pattern>", "eval"), kwargs)
1449+
return obj, 0
1450+
1451+
14341452
@dataclass
14351453
class DetectorModule:
14361454
"""Detector module.
@@ -1465,12 +1483,17 @@ class DetectorModule:
14651483
module_number: int | None
14661484
detector_type: str
14671485
legacy_uuid: int | None # Deprecated, do not use
1486+
source_name: str | None
14681487

14691488
def __post_init__(self):
14701489
if self.module_number is None:
14711490
# Try to fill in module number if missing.
14721491
self.module_number = int(re.findall(r"\d+", self.aggregator)[-1])
14731492

1493+
if self.source_name is not None:
1494+
self.source_name = SourceNameFormatter().format(
1495+
self.source_name, modno=self.module_number)
1496+
14741497
@property
14751498
def ccv_params(self):
14761499
"""PDU arguments as needed for write_ccv()."""
@@ -1528,7 +1551,7 @@ def get_da(x):
15281551
item['id'], item['physical_name'], item['karabo_da'],
15291552
self.identifier, item['virtual_device_name'], i,
15301553
item['module_number'], item['detector_type']['name'],
1531-
item['uuid'])
1554+
item['uuid'], self._source_name_pattern)
15321555
else:
15331556
item.module_index = i
15341557

@@ -1661,6 +1684,13 @@ def source_name_pattern(self) -> str:
16611684
'incomplete detector entry in CalCat'
16621685
return self._source_name_pattern
16631686

1687+
@property
1688+
def source_names(self) -> str:
1689+
"""Source names."""
1690+
assert self._source_name_pattern is not None, \
1691+
'incomplete detector entry in CalCat'
1692+
return [pdu.source_name for pdu in self.pdus]
1693+
16641694
@property
16651695
def first_module_index(self) -> int:
16661696
"""Module index of the first module."""

tests/test_calibration.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
LPDConditions,
1919
SingleConstant,
2020
DetectorData,
21-
DetectorModule
21+
DetectorModule,
22+
SourceNameFormatter
2223
)
2324

2425
# Most of these tests use saved HTTP responses by default (with pytest-recording).
@@ -405,12 +406,16 @@ def test_DetectorData_from_identifier():
405406
assert list(agipd)[0] == next(iter(agipd.keys())) == 'AGIPD00'
406407
assert isinstance(next(iter(agipd.values())), DetectorModule)
407408
assert agipd[0] == agipd['AGIPD00']
409+
assert agipd.source_names == [
410+
f'SPB_DET_AGIPD1M-1/DET/{i}CH0:xtdf'
411+
for i in range(len(agipd.source_names))]
408412

409413
# PDU
410414
pdu = agipd[0]
411415
assert pdu.aggregator == 'AGIPD00'
412416
assert pdu.ccv_params == (
413417
'AGIPD_SIV1_AGIPDV11_M517', 101003000000, 'AGIPD-Type')
418+
assert pdu.source_name == 'SPB_DET_AGIPD1M-1/DET/0CH0:xtdf'
414419

415420
# FXE-JFHZ, single-module detector with partial CalCat entries
416421
jfhz = DetectorData.from_identifier('FXE_XAD_JFHZ', **pdu_date_kw)
@@ -423,6 +428,9 @@ def test_DetectorData_from_identifier():
423428
with pytest.raises(AssertionError):
424429
jfhz.first_module_index
425430

431+
with pytest.raises(AssertionError):
432+
jfhz.source_names
433+
426434
# SQS-DSSC, PDU-less detector
427435
dssc = DetectorData.from_identifier('SQS_DET_DSSC1M-1', **pdu_date_kw)
428436
repr(dssc)
@@ -462,3 +470,14 @@ def test_DetectorData_list_by_instrument():
462470
def test_DetectorData_from_CalibrationData():
463471
agipd_cd = CalibrationData.from_report(3757)
464472
assert agipd_cd.detector.identifier == 'SPB_DET_AGIPD1M-1'
473+
474+
475+
def test_DetectorData_SourceNameFormatter():
476+
fmt = SourceNameFormatter()
477+
478+
assert 'SPB_DET_AGIPD1M-1/DET/4CH0:xtdf' == \
479+
fmt.format('SPB_DET_AGIPD1M-1/DET/{modno}CH0:xtdf', modno=4)
480+
assert 'SPB_IRDA_JF4M/DET/JNGFR03:daqOutput' == \
481+
fmt.format('SPB_IRDA_JF4M/DET/JNGFR{modno:02d}:daqOutput', modno=3)
482+
assert 'HED_TST_AGIPDHZ3/DET/84CH0:xtdf' == \
483+
fmt.format('HED_TST_AGIPDHZ3/DET/{modno+83}CH0:xtdf', modno=1)

0 commit comments

Comments
 (0)