Skip to content

Commit fbd029c

Browse files
committed
Add source names to DetectorData
1 parent 6c1c1b5 commit fbd029c

2 files changed

Lines changed: 50 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
@@ -1368,6 +1371,21 @@ class ShimadzuHPVX2Conditions(ConditionsBase):
13681371
}
13691372

13701373

1374+
class SourceExprChecker(ast.NodeVisitor):
1375+
def visit_Call(self, node):
1376+
raise ValueError("Function calls not allowed in source name patterns")
1377+
1378+
1379+
class SourceNameFormatter(Formatter):
1380+
"""String formatter that evaluates simple operations like {modno + 2}"""
1381+
1382+
def get_field(self, field_name, args, kwargs):
1383+
node = ast.parse(field_name, "<source pattern>", "eval")
1384+
SourceExprChecker().visit(node)
1385+
obj = eval(compile(node, "<source pattern>", "eval"), kwargs)
1386+
return obj, 0
1387+
1388+
13711389
@dataclass
13721390
class DetectorModule:
13731391
"""Detector module.
@@ -1402,12 +1420,17 @@ class DetectorModule:
14021420
module_number: int | None
14031421
detector_type: str
14041422
legacy_uuid: int | None # Deprecated, do not use
1423+
source_name: str
14051424

14061425
def __post_init__(self):
14071426
if self.module_number is None:
14081427
# Try to fill in module number if missing.
14091428
self.module_number = int(re.findall(r"\d+", self.aggregator)[-1])
14101429

1430+
if self.source_name is not None:
1431+
self.source_name = SourceNameFormatter().format(
1432+
self.source_name, modno=self.module_number)
1433+
14111434
@property
14121435
def ccv_params(self):
14131436
"""PDU arguments as needed for write_ccv()."""
@@ -1465,7 +1488,7 @@ def get_da(x):
14651488
item['id'], item['physical_name'], item['karabo_da'],
14661489
self.identifier, item['virtual_device_name'], i,
14671490
item['module_number'], item['detector_type']['name'],
1468-
item['uuid'])
1491+
item['uuid'], self._source_name_pattern)
14691492
else:
14701493
item.module_index = i
14711494

@@ -1598,6 +1621,13 @@ def source_name_pattern(self) -> str:
15981621
'incomplete detector entry in CalCat'
15991622
return self._source_name_pattern
16001623

1624+
@property
1625+
def source_names(self) -> str:
1626+
"""Source names."""
1627+
assert self._source_name_pattern is not None, \
1628+
'incomplete detector entry in CalCat'
1629+
return [pdu.source_name for pdu in self.pdus]
1630+
16011631
@property
16021632
def first_module_index(self) -> int:
16031633
"""Module index of the first module."""

tests/test_calibration.py

Lines changed: 19 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).
@@ -399,12 +400,15 @@ def test_DetectorData_from_identifier():
399400
assert list(agipd)[0] == next(iter(agipd.keys())) == 'AGIPD00'
400401
assert isinstance(next(iter(agipd.values())), DetectorModule)
401402
assert agipd[0] == agipd['AGIPD00']
403+
assert all([name == f'SPB_DET_AGIPD1M-1/DET/{i}CH0:xtdf' for i, name
404+
in enumerate(agipd.source_names)])
402405

403406
# PDU
404407
pdu = agipd[0]
405408
assert pdu.aggregator == 'AGIPD00'
406409
assert pdu.ccv_params == (
407410
'AGIPD_SIV1_AGIPDV11_M517', 101003000000, 'AGIPD-Type')
411+
assert pdu.source_name == 'SPB_DET_AGIPD1M-1/DET/0CH0:xtdf'
408412

409413
# FXE-JFHZ, single-module detector with partial CalCat entries
410414
jfhz = DetectorData.from_identifier('FXE_XAD_JFHZ', **pdu_date_kw)
@@ -417,6 +421,9 @@ def test_DetectorData_from_identifier():
417421
with pytest.raises(AssertionError):
418422
jfhz.first_module_index
419423

424+
with pytest.raises(AssertionError):
425+
jfhz.source_names
426+
420427
# SQS-DSSC, PDU-less detector
421428
dssc = DetectorData.from_identifier('SQS_DET_DSSC1M-1', **pdu_date_kw)
422429
repr(dssc)
@@ -456,3 +463,14 @@ def test_DetectorData_list_by_instrument():
456463
def test_DetectorData_from_CalibrationData():
457464
agipd_cd = CalibrationData.from_report(3757)
458465
assert agipd_cd.detector.identifier == 'SPB_DET_AGIPD1M-1'
466+
467+
468+
def test_DetectorData_SourceNameFormatter():
469+
fmt = SourceNameFormatter()
470+
471+
assert 'SPB_DET_AGIPD1M-1/DET/4CH0:xtdf' == \
472+
fmt.format('SPB_DET_AGIPD1M-1/DET/{modno}CH0:xtdf', modno=4)
473+
assert 'SPB_IRDA_JF4M/DET/JNGFR03:daqOutput' == \
474+
fmt.format('SPB_IRDA_JF4M/DET/JNGFR{modno:02d}:daqOutput', modno=3)
475+
assert 'HED_TST_AGIPDHZ3/DET/84CH0:xtdf' == \
476+
fmt.format('HED_TST_AGIPDHZ3/DET/{modno+83}CH0:xtdf', modno=1)

0 commit comments

Comments
 (0)