Break up pds module into package, update slits device class, add slits to startup, update tests - #83
Conversation
…s to startup, update tests
There was a problem hiding this comment.
🟡 Changes recommended
The new hextools.photon_delivery_system package currently has blocking import/API mismatches (missing exports in __init__.py and a Slits constructor signature mismatch with tests) that will break imports and CI.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR refactors the photon delivery system (PDS) from a single module into a package, introduces a richer Slits device (gap/center derived signals), wires slits into the HEX collection profile baseline, and reorganizes the test suite to match the new package layout.
Changes:
- Split
hextools.photon_delivery_systeminto a package with separatedclm,filters,shutter, andslitsmodules (plus packagedfilters.yml). - Added a derived-signal-based
Slitsimplementation and included slit devices in the collection profile baseline metadata. - Replaced the monolithic PDS test file with per-component tests (
dclm,filters,shutter,slits).
File summaries
| File | Description |
|---|---|
| tests/test_photon_delivery_system.py | Removes monolithic PDS tests in favor of per-module tests. |
| tests/photon_delivery_system/test_dclm.py | Adds DCLM/energy-change plan tests aligned to the new module structure. |
| tests/photon_delivery_system/test_filters.py | Adds filter parsing/motion tests using packaged YAML config. |
| tests/photon_delivery_system/test_shutter.py | Adds shutter open/close behavior tests for the new shutter module. |
| tests/photon_delivery_system/test_slits.py | Adds tests for slit gap/center derived signals and bps.mv integration. |
| src/hextools/utils.py | Adds helper to fetch typed objects from the IPython user namespace. |
| src/hextools/profiles/collection.py | Adds slit devices to startup and includes them in baseline metadata. |
| src/hextools/photon_delivery_system/init.py | Defines the new public package surface (__all__) for PDS devices/plans. |
| src/hextools/photon_delivery_system/dclm.py | Introduces DCLM device and updated change_energy plan (namespace lookup + shutter finalize). |
| src/hextools/photon_delivery_system/filters.py | Introduces Filter device classes and YAML-driven load_filters(). |
| src/hextools/photon_delivery_system/filters.yml | Adds packaged filter configuration consumed by load_filters(). |
| src/hextools/photon_delivery_system/shutter.py | Introduces shutter device module. |
| src/hextools/photon_delivery_system/slits.py | Introduces slit device with derived gap/center signals and multi-axis set. |
| src/hextools/photon_delivery_system.py | Removes the old monolithic PDS module. |
Review details
- Files reviewed: 13/14 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| def __init__(self, prefix: str, name: str = ""): | ||
| with self.add_children_as_readables(Format.CHILD): | ||
| self.inboard = AsyncEpicsMotor(prefix + "I}Mtr") | ||
| self.outboard = AsyncEpicsMotor(prefix + "O}Mtr") | ||
| self.bottom = AsyncEpicsMotor(prefix + "B}Mtr") | ||
| self.top = AsyncEpicsMotor(prefix + "T}Mtr") |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
There is a confirmed import-time syntax/indentation error in filters.py and the new slits tests still use the old Slits(prefix, num) constructor signature, which will break the test suite.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
tests/photon_delivery_system/test_slits.py:78
- This test also uses the old
Slits(prefix, num)constructor;Slitsnow expects(prefix, name=...). Update the instantiation to pass a stringnameand a prefix that matches the new slits PV layout.
with init_devices(mock=True):
slits = Slits("XF:TEST:", 1)
for motor in (slits.inboard, slits.outboard, slits.bottom, slits.top):
- Files reviewed: 13/14 changed files
- Comments generated: 2
- Review effort level: Lite
| async with init_devices(mock=True): | ||
| device = Slits("XF:TEST:", 1) |
There was a problem hiding this comment.
🟡 Changes recommended
There are test-breaking and runtime-breaking issues (notably an IndentationError in filters.py and a Slits constructor mismatch in tests) that must be fixed before merge.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (3)
tests/photon_delivery_system/test_slits.py:12
Slitsno longer accepts a numericnumargument (constructor isSlits(prefix: str, name: str = "")). This test still callsSlits("XF:TEST:", 1), which will raiseTypeErrorand prevent the test suite from running.
async with init_devices(mock=True):
device = Slits("XF:TEST:", 1)
tests/photon_delivery_system/test_slits.py:78
- Same constructor mismatch as above:
Slits("XF:TEST:", 1)will raiseTypeErrorwith the updatedSlitsAPI.
with init_devices(mock=True):
slits = Slits("XF:TEST:", 1)
for motor in (slits.inboard, slits.outboard, slits.bottom, slits.top):
src/hextools/photon_delivery_system/filters.py:81
_get_descriptionis currently defined at module scope, which prematurely ends theFilterclass block and leaves the subsequent indented@AsyncStatus.wrap/setmethod at an invalid indentation level. As-is, this file will fail to import with anIndentationError, andself._get_descriptionwon't exist onFilter.
def _get_description(self, in_pos: bool, motor_pos: float) -> str:
"""Get the description of the current filter setting based on motor position.
Parameters
----------
- Files reviewed: 15/17 changed files
- Comments generated: 2
- Review effort level: Lite
| from .dclm import DCLM, change_energy | ||
| from .filters import Filter, FilterPosition, load_filters | ||
| from .shutter import Shutter | ||
| from .slits import Slits | ||
|
|
||
| __all__ = [ | ||
| "Shutter", | ||
| "Filter", | ||
| "load_filters", | ||
| "FilterPosition", | ||
| "Slits", | ||
| "DCLM", | ||
| "change_energy", | ||
| ] |
| coarse_angle_range : float | ||
| Half-width of the coarse pitch scan in degrees. | ||
| coarse_num_steps : int | ||
| Number of points in the coarse scan. | ||
| fine_angle_range : float | ||
| Half-width of the fine pitch scan in degrees. | ||
| fine_num_steps : int | ||
| Number of points in the fine scan. | ||
| photon_shutter : Shutter, optional | ||
| Shutter to close on exit. Falls back to the ``photon_shutter`` in the | ||
| IPython namespace when not provided. | ||
|
|
||
| Raises | ||
| ------ | ||
| RuntimeError | ||
| If the monochromator is not in monochromatic mode. | ||
| """ |
There was a problem hiding this comment.
🟡 Changes recommended
The new Slits API is not reflected in the newly added slits tests, and there are import hygiene issues in the collection profile that are likely to fail lint/type checks.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (3)
tests/photon_delivery_system/test_slits.py:12
Slitswas updated to take just(prefix, name=...), but the test still constructs it with the old(prefix, num)signature, which will raiseTypeErrorand prevent these tests from running.
async with init_devices(mock=True):
device = Slits("XF:TEST:", 1)
tests/photon_delivery_system/test_slits.py:77
- This test still uses the legacy
Slits(prefix, num)constructor; the newSlitsclass accepts only(prefix, name=...).
with init_devices(mock=True):
slits = Slits("XF:TEST:", 1)
src/hextools/profiles/collection.py:185
- Device names are used as data keys in Bluesky; using a hyphen can make downstream access awkward (e.g., attribute-style access and some serialization assumptions). Using underscores keeps naming consistent with the other devices in this profile.
name="perkin-elmer",
- Files reviewed: 16/18 changed files
- Comments generated: 4
- Review effort level: Lite
| from ophyd_async.epics.adkinetix import KinetixDetector | ||
| from ophyd_async.epics.adcore import ADWriterFactory | ||
|
|
||
| def kinetix_factory(num: int, path_provider, name: str): |
| ps = PeakStats( | ||
| dclm.xtal2_pitch.name, | ||
| fs_camera.get_plugin_by_name(fs_stats_plugin_name, NDStatsIO).total.name, | ||
| ) |
| value : tuple[tuple[float, float], tuple[float, float]] | ||
| ((horizontal_gap, horizontal_center), (vertical_gap, vertical_center)) | ||
| """ |
| from pathlib import PureWindowsPath | ||
| from nslsii.ophyd_async.providers import NSLS2PathProvider | ||
| from ophyd_async.epics.adcore import ADWriterFactory, NDStatsIO, PluginSignalDataLogic | ||
| from ophyd_async.epics.adcore import ADWriterFactory, NDStatsIO, PluginSignalDataLogic, ContAcqDetector | ||
| from ophyd_async.epics.adkinetix import KinetixDetector | ||
| from ophyd_async.epics.advimba import VimbaDetector | ||
| from ophyd_async.fastcs.panda import HDFPanda | ||
| from tiled.client import from_uri, simple | ||
| from bluesky import plans as bp, plan_stubs as bps, preprocessors as bpp | ||
| from bluesky.suspenders import SuspendFloor |
There was a problem hiding this comment.
🟡 Changes recommended
The startup profile currently references undefined variables (wb_slits/pb_slits) and the new slits tests use an outdated Slits(...) constructor signature, which will cause immediate import/test failures.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (3)
tests/photon_delivery_system/test_slits.py:13
- The
Slitsconstructor now takes(prefix: str, name: str = ""), but the test passes an integer as the second argument (Slits("XF:TEST:", 1)), which will be treated asnameand can break device construction. Update the test to pass the fully-qualified slits PV prefix and an optional string name.
async with init_devices(mock=True):
device = Slits("XF:TEST:", 1)
# Mirror each motor setpoint to its readback so moves complete in mock mode.
tests/photon_delivery_system/test_slits.py:78
- Same constructor mismatch as above:
Slits("XF:TEST:", 1)passes an int where a name string is expected. Use the slits PV prefix and optional string name sobps.mv(slits, ...)drives the correct device.
with init_devices(mock=True):
slits = Slits("XF:TEST:", 1)
for motor in (slits.inboard, slits.outboard, slits.bottom, slits.top):
src/hextools/detectors/kinetix.py:7
- PEP 8 recommends two blank lines between top-level definitions. Add a blank line between the imports and
kinetix_factory()to keep module formatting consistent with the rest of the codebase.
from ophyd_async.epics.adkinetix import KinetixDetector
from ophyd_async.epics.adcore import ADWriterFactory
def kinetix_factory(num: int, path_provider, name: str):
"""Factory function to create a KinetixDetector with HDF writer."""
- Files reviewed: 18/19 changed files
- Comments generated: 4
- Review effort level: Lite
| # Attempt to auto-deduce the CameraObjective from a string input. | ||
| if isinstance(value, str): | ||
| for possible_value in CameraObjective: | ||
| if value.upper() in possible_value.name: | ||
| value = possible_value | ||
| break | ||
|
|
| pe_path_provider = NSLS2PathProvider(RE.md, base_write_dir=PureWindowsPath("Z:\\proposals")) | ||
| perkin_elmer = ContAcqDetector( | ||
| "XF:27ID1-ES{PE-Det:1}", | ||
| ADWriterFactory.hdf(pe_path_provider), | ||
| name="perkin-elmer", | ||
| proc_suffix="Proc1:", | ||
| ) | ||
|
|
||
| # TODO: Figure out why the '-' character in the name is being | ||
| # replaced with '_' in the ctx manager | ||
| perkin_elmer._name = "perkin-elmer" |
There was a problem hiding this comment.
🟡 Changes recommended
Critical lint and test-collection failures, along with unresolved API compatibility regressions, block approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (10)
Previously missed (3) — in code that hasn't changed since the last review.
src/hextools/photon_delivery_system/dclm.py:149
- The public plan's positional API changed from
change_energy(dclm, energy)tochange_energy(energy, dclm=...)without a compatibility layer. Existing callers using the previous signature now pass aDCLMobject asenergyand fail during numeric validation; retain a compatible entry point or deliberately migrate all supported consumers.
src/hextools/photon_delivery_system/slits.py:172 - For an invalid two-element value such as
(10, 10), this condition callslen()on each float and raisesTypeErrorinstead of reaching the explicitValueErrorbelow. Check that each element is a nested tuple before taking its length so malformed set values receive the documented validation error.
tests/photon_delivery_system/test_slits.py:77 - The second slit test constructs the device with the same obsolete
(prefix, number)call. It should use the new full prefix as well; otherwise this test uses different PVs from the startup device and can pass without validating the intended configuration.
src/hextools/motors.py:234
- Because these adjacent string literals are joined without whitespace, the resulting exception says
matchingone of its names. That makes invalid objective input diagnostics misleading; include a trailing space aftermatching.
f"Invalid objective value: {value}. "
"Must be a CameraObjective or a string matching" \
"one of its names."
src/hextools/motors.py:176
- This new class name contains underscores and lowercase words, which violates the repository's Ruff
N801CapWords class naming rule selected inpyproject.toml:140-156; the repository-wide pre-commit check will reject it. Rename it to a CapWords form (and update its startup/tests) or add an explicit, justified lint exception.
class FOV_2_4_mm_Camera(StandardReadable, EpicsDevice, AsyncMovable[CameraObjective | str]):
src/hextools/photon_delivery_system/init.py:4
- The module split drops
BeamModeandFilterSettingfrom the package-level API even though both were importable fromhextools.photon_delivery_systembefore this refactor. Existing callers using those imports now fail; re-export them here or provide an explicit compatibility path.
from .dclm import DCLM, change_energy
from .filters import Filter, FilterPosition, load_filters
src/hextools/profiles/collection.py:30
- The newly added imports duplicate
bppand leavebp,bps,bpp,SuspendFloor, andshow_docsunused because the corresponding scan/suspender/baseline code below is commented out. Ruff is run over the whole repository in CI, so this profile now fails with unused/redefinition errors; remove the unused imports or restore the code that consumes them.
from bluesky import plans as bp, plan_stubs as bps, preprocessors as bpp
from bluesky.suspenders import SuspendFloor
from hextools.utils import show_docs
src/hextools/profiles/collection.py:136
- These lines remove
kinetix2,kinetix3, andkinetix4from the collection profile; those detector objects were initialized before this change, so their globals and acquisition capability disappear at startup. This is an operational regression unrelated to splitting the PDS module unless the detector removal is intentional and coordinated.
# kinetix2 = kinetix_factory(2, path_provider, name="kinetix2")
# kinetix3 = kinetix_factory(3, path_provider, name="kinetix3")
# kinetix4 = kinetix_factory(4, path_provider, name="kinetix4")
src/hextools/profiles/collection.py:170
- Commenting out
fs_windowremoves the collection profile's configured fluorescence camera, so the newchange_energy(..., fs_camera=...)auto-tuning path cannot be used from normal startup. Coordinate this operational change or keep the device wiring if the camera is expected to remain available.
# fs_window = VimbaDetector(
# "XF:27IDA-BI{FS:1-Cam:1}",
# ADWriterFactory.hdf(path_provider),
# name="fs_window",
# plugins={"stats1": fs_window_stats},
tests/photon_delivery_system/test_slits.py:12
Slitsnow takes an already-expanded PV prefix and a stringname; it no longer accepts a numeric slit number. This call passes1as the name and createsXF:TEST:I}Mtrrather than the{Slt:1-Ax:PVs used by startup, so the fixture does not exercise the configured device. Use the expanded test prefix and a string name.
device = Slits("XF:TEST:", 1)
- Files reviewed: 18/19 changed files
- Comments generated: 5
- Review effort level: Lite
| from ophyd_async.epics.adkinetix import KinetixDetector | ||
| from ophyd_async.epics.adcore import ADWriterFactory | ||
|
|
||
| def kinetix_factory(num: int, path_provider, name: str): |
|
|
||
|
|
||
| class DoubleObjCamera(StandardReadable, EpicsDevice, AsyncMovable[CameraObjective]): | ||
| class FOV_2_4_mm_Camera(StandardReadable, EpicsDevice, AsyncMovable[CameraObjective | str]): |
|
|
||
|
|
||
| class WideFOVCamera(StandardReadable, EpicsDevice): | ||
| class FOV_20_40_mm_Camera(StandardReadable, EpicsDevice): |
|
|
||
| @pytest.fixture | ||
| async def slits() -> Slits: | ||
| async with init_devices(mock=True): |
| The indentation level for the current device. | ||
| """ | ||
| x = [] | ||
| _make_tree_body(x, device) |
There was a problem hiding this comment.
🟡 Changes recommended
Critical and moderate findings remain unresolved, including detector initialization, tomography cleanup, API compatibility, and test/lint failures.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (22)
src/hextools/detectors/germ.py:133
- The new public signal is misspelled as
trigger_recieved, while the corresponding signal elsewhere in the same detector API istrigger_received. This exposes an inconsistent attribute name and makes callers likely to miss the signal; correct the spelling.
channel: A[SignalRW[int], PvSuffix("Channel")]
src/hextools/detectors/phantom.py:533
- Completion now depends solely on
selected_cine.cine_content_saved. The existing Phantom acquisition path and tests only advancedownload_count(tests/detectors/test_phantom.py:263-274) and never set this save signal, so once the counter stops this loop raises a timeout instead of completing. Preserve target-count completion or update the IOC/test contract and reset/observe the save state consistently.
async for saved in observe_value(
selected_cine.cine_content_saved, done_timeout=DEFAULT_TIMEOUT
):
if saved:
return
src/hextools/detectors/phantom.py:500
- This new availability check runs before every Phantom download, but the existing
PhantomAcquireLogictests and mock callbacks do not initializetotal_frame_count; the mock therefore reports no available frames andstart_acquiringraises before issuing the download. Update all mock setups/full-stack callbacks to provide the new readback, or guard the check when that readback is unavailable.
if total_download_frames > available_frames:
raise RuntimeError(
f"Requested {total_download_frames} frames to download, but only {available_frames} are available!"
)
src/hextools/detectors/phantom.py:490
- The newly added comment misspells
received; correct it so the acquisition logic documentation is clear.
# If we recieved the trigger, we know at this point how many frames we'll have access to,
src/hextools/detectors/phantom.py:546
- This newly added
printemits diagnostic output from the detector's timeout-recovery path during normal RunEngine operation. Use the project's logging mechanism or remove the debug output so detector plans do not write uncontrolled stdout.
print(
f"Last value: {last_download_count}, Current value: {current}, Saved: {saved}"
src/hextools/motors.py:178
- Renaming
DoubleObjCameratoFOV_2_4_mm_Cameraremoves an import still used bytests/test_motors.py:7and its fixtures/tests, so test collection raisesImportError. Keep a compatibility alias or update all consumers in the same change.
class FOV_2_4_mm_Camera(
StandardReadable, EpicsDevice, AsyncMovable[CameraObjective | str]
):
src/hextools/motors.py:237
- The two adjacent string literals concatenate without a space, so invalid-objective errors say
matchingone of its names, which is unclear to users.
raise ValueError(
f"Invalid objective value: {value}. "
"Must be a CameraObjective or a string matching"
"one of its names."
src/hextools/photon_delivery_system/init.py:6
- The package split drops
BeamModeandFilterSettingfrom the package-level API. They were top-level names in the deletedphoton_delivery_system.py, so existingfrom hextools.photon_delivery_system import ...callers now fail even though the classes still exist in submodules. Re-export them to preserve the package API.
from .dclm import DCLM, change_energy
from .filters import Filter, FilterPosition, load_filters
from .shutter import Shutter
from .slits import Slits
src/hextools/photon_delivery_system/slits.py:32
- The constructor now takes a complete PV prefix, but both new slit tests still call
Slits("XF:TEST:", 1). That binds1tonameand produces motor PVs such asXF:TEST:I}Mtrinstead of the intendedXF:TEST:{Slt:1-Ax:I}Mtr, so the tests do not exercise the configured device.
def __init__(self, prefix: str, name: str = ""):
src/hextools/profiles/collection.py:25
Pathis a newly added unused import, so the all-files Ruff check will fail with F401. Remove it unless a real path annotation/use is added.
from pathlib import Path
src/hextools/profiles/collection.py:38
- The newly added
bp,bps,bpp,SuspendFloor, andshow_docsimports are not referenced by executable code; their only apparent uses are commented-out snippets. Ruff F401 is enabled and CI lints all files, so remove these dead imports or restore the code that uses them.
from bluesky import plans as bp, plan_stubs as bps, preprocessors as bpp
from bluesky.suspenders import SuspendFloor
from hextools.utils import show_docs
src/hextools/tomography/flyscans.py:161
- Cleanup also closes the photon shutter regardless of
use_shutter, so disabling shutter handling still actuates hardware during finalization. Make this cleanup conditional on the same flag.
yield from ensure_shutter_closed(photon_shutter, allow_actuation=True)
src/hextools/tomography/flyscans.py:12
get_obj_from_ipython_nsis imported but never used; the plan resolves devices throughensure_available. Ruff F401 is enabled and this added import will fail the all-files lint job.
from hextools.utils import ensure_available, get_obj_from_ipython_ns
src/hextools/tomography/radiography.py:109
- These lookups run even when
use_shutter=False, so the documented no-shutter mode still raisesValueErroroutside an IPython namespace. Resolve the optional shutters only inside theuse_shutterbranch, as their later use is already guarded.
src/hextools/tomography/radiography.py:158 - The finalizer never executes this plan: calling a generator function without
yield fromreturns a generator object, and_cleanupitself has no yield, so the photon shutter is not closed (andfinalize_wrappermay receiveNone). Yield from the helper here.
src/hextools/tomography/radiography.py:52 nslsii.detectorsandget_obj_from_ipython_nsare unused in this module; onlyAreaDetector,DetectorTrigger,TriggerInfo, the shutter helpers, andensure_availableare referenced. These added imports trigger Ruff F401 in the repository's all-files lint job.
src/hextools/tomography/radiography.py:62- This signature no longer accepts the
front_end_shutter/photon_shutterpositional arguments or theframes_per_burst,num_bursts, andwait_between_burstskeywords still used bytests/tomography/test_take_radiograph.py. Once the import is fixed, that test will fail withTypeError; update the test to the new API or preserve a compatibility path.
src/hextools/utils.py:281 indentis documented as an indentation level but is ignored, so every call renders at column zero. Pass an initial prefix derived from the requested indentation.
_make_tree_body(x, device)
src/hextools/utils.py:7
OrderedDictis imported but never used. Ruff F401 is enabled for this repository and runs on all files in CI, so this new import blocks the lint gate.
from typing import Any, OrderedDict, TypeVar
src/hextools/utils.py:193
- The new public
verboseparameter controls whether connection failure reasons are printed, but it is missing from the function's Parameters section. Document its default and effect so callers can use the new behavior intentionally.
def auto_init_devices(timeout: float = 1.0, verbose: bool = False) -> DeviceProcessor:
"""Create a DeviceProcessor that connects devices, printing status for each.
Parameters
----------
timeout : float
The timeout in seconds for each device connection attempt.
Returns
-------
DeviceProcessor
A DeviceProcessor that connects devices and prints their connection status.
"""
tests/detectors/test_germ.py:14
GeRMDetectoris imported in this new test module but never referenced; Ruff F401 will fail the all-files lint job. Remove the unused symbol from the import list.
GeRMDetector,
tests/photon_delivery_system/test_slits.py:12
- The new
Slitsconstructor takes a complete PV prefix and an optional stringname; passing1as the second positional argument uses it as the device name and leaves the motor PVs asXF:TEST:I}Mtr, so this fixture no longer tests slit number 1. Pass the complete...{Slt:1-Ax:prefix instead.
device = Slits("XF:TEST:", 1)
- Files reviewed: 26/27 changed files
- Comments generated: 9
- Review effort level: Lite
| self.add_detector_logics(GeRMTriggerLogic(self.driver)) | ||
| self.add_detector_logics(GeRMAcquireLogic(self.driver)) | ||
| self.hdf = NDFileHDF5IO(prefix + "MCA1:HDF1:", name="hdf") | ||
| self.add_detector_logics( | ||
| ADHDFDataLogic( |
| invalid: A[SignalR[bool], PvSuffix("State_RBV.B0")] | ||
| complete_and_valid: A[SignalR[bool], PvSuffix("State_RBV.B1")] | ||
| waiting_for_trigger: A[SignalR[bool], PvSuffix("State_RBV.B2")] | ||
| trigger_recieved: A[SignalR[bool], PvSuffix("State_RBV.B3")] |
| from collections.abc import Mapping, Sequence | ||
| from tkinter.font import names | ||
| from typing import Annotated as A |
| if isinstance(value, str): | ||
| for possible_value in CameraObjective: | ||
| if value.upper() in possible_value.name: | ||
| value = possible_value | ||
| break |
| fe_shutter = ensure_available(Shutter, fe_shutter=fe_shutter) | ||
| photon_shutter = ensure_available(Shutter, photon_shutter=photon_shutter) |
| if acquire_period is None: | ||
| acquire_period = exposure_time + FRAME_PERIOD_MARGIN |
| yield from ensure_shutter_open( | ||
| photon_shutter, allow_actuation=True, group="prepare", wait=False | ||
| ) |
| async def default_trigger_info(self) -> TriggerInfo: | ||
| livetime = await self.driver.acquire_time.get_value() | ||
| print(livetime) | ||
| return TriggerInfo( |
…ns; also updated test_radiography.py with names
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved runtime compatibility, functional, and lint/test issues remain.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (22)
Previously missed (1) — in code that hasn't changed since the last review.
src/hextools/tomography/radiography.py:110
- These lookups run even when
use_shutteris left at its documented default ofFalse, so a shutterless radiograph with the optional arguments omitted raises before the plan starts. Resolve the optional shutters only inside theuse_shutterbranch.
src/hextools/detectors/germ.py:5
- The new
tkinter.fontimport is unused andtkinteris not a declared runtime dependency. Importinghextools.detectors.germcan therefore make detector/profile startup depend on an optional GUI module for no reason; remove the accidental import.
from collections.abc import Mapping, Sequence
from tkinter.font import names
from typing import Annotated as A
src/hextools/detectors/germ.py:310
- This unconditional
printemits the detector livetime every time default trigger information is requested, polluting profile/RunEngine output. Remove the debug print and leave the value available through the returnedTriggerInfo.
print(livetime)
src/hextools/detectors/germ.py:370
StandardDetectoris initialized with onlyname, unlike the workingPhantomDetectorconstruction, which passes its driver, prefix, acquire logic, trigger logic, and writers tosuper().__init__. This will not construct a usable GeRM detector (and likely raises for the missing required driver); wire these components through the base constructor instead of callingadd_detector_logicsbefore base initialization.
super().__init__(name=name)
src/hextools/detectors/phantom.py:500
- This new guard treats the mock/default
total_frame_countof zero as authoritative, so the existing Phantom acquire tests—which set the requested download range but do not seedtotal_frame_count—now raise before starting the download. Update the test fixtures to provide the available frame count, or define how an unavailable readback should be handled instead of treating it as zero.
available_frames, total_download_frames = await asyncio.gather(
self.driver.total_frame_count.get_value(),
self.driver.total_download_frames.get_value(),
)
if total_download_frames > available_frames:
raise RuntimeError(
f"Requested {total_download_frames} frames to download, but only {available_frames} are available!"
)
src/hextools/detectors/phantom.py:521
cinesis created with keys1..num_cines, but the existing mock setup leavesselected_cineat its default0; this indexing therefore raisesKeyErrorbeforewait_for_idlecan observe completion. Normalize the IOC's cine numbering or handle an invalid/unset selection explicitly, and update the mock setup accordingly.
selected_cine_num, last_download_count = await asyncio.gather(
self.driver.selected_cine.get_value(),
self.driver.download_count.get_value(),
)
selected_cine = self.driver.cines[selected_cine_num]
src/hextools/detectors/phantom.py:490
- Correct the spelling in this newly added comment.
# If we recieved the trigger, we know at this point how many frames we'll have access to,
src/hextools/detectors/phantom.py:133
- This newly added signal name is misspelled as
trigger_recieved, while the correspondingPhantomIOsignal istrigger_received; the typo becomes part of the publicPhantomCineIOAPI and is easy for callers to miss.
trigger_recieved: A[SignalR[bool], PvSuffix("State_RBV.B3")]
src/hextools/detectors/phantom.py:533
wait_for_idlenow returns only when the selected cine'scine_content_savedbecomes true, but the existing Phantom tests and full-stack mock callback still simulate completion solely by incrementingdownload_count(tests/detectors/test_phantom.py:263-274, 355-360). Those tests will time out even after all requested frames are downloaded; either retain a counter-based completion path or update the mocks and assertions to set the cine-saved signal.
async for saved in observe_value(
selected_cine.cine_content_saved, done_timeout=DEFAULT_TIMEOUT
):
if saved:
return
src/hextools/motors.py:178
- Renaming
DoubleObjCameraremoves the symbol that the unchangedtests/test_motors.pystill imports and annotates, causing test collection to fail withImportError. Keep a backwards-compatible alias or update that test and other callers as part of this rename.
class FOV_2_4_mm_Camera(
StandardReadable, EpicsDevice, AsyncMovable[CameraObjective | str]
):
src/hextools/motors.py:231
- The documented string form does not work:
"Right Objective".upper()becomes"RIGHT OBJECTIVE", which is compared to the enum nameRIGHT_2MM, so the loop never converts the value and the following check raises. Normalize and compare both enum names and their wire values.
if isinstance(value, str):
for possible_value in CameraObjective:
if value.upper() in possible_value.name:
value = possible_value
break
src/hextools/photon_delivery_system/init.py:4
- The old module exposed
BeamModeandFilterSettingathextools.photon_delivery_system, and the deleted test imported both from that root. Omitting them from the package exports breaks existing consumers during the module-to-package split; re-export the previously public types or explicitly provide a compatibility layer.
from .dclm import DCLM, change_energy
from .filters import Filter, FilterPosition, load_filters
src/hextools/photon_delivery_system/slits.py:36
- This constructor no longer accepts the old
(prefix, num, name)form or inserts the{Slt:<num>-Ax:segment, but the newtests/photon_delivery_system/test_slits.pystill callsSlits("XF:TEST:", 1). That passes1as the name and produces the wrong motor PVs (and may violate the device name type). Preserve the old signature or update all callers to pass the complete prefix.
def __init__(self, prefix: str, name: str = ""):
with self.add_children_as_readables(Format.CHILD):
self.inboard = AsyncEpicsMotor(prefix + "I}Mtr")
self.outboard = AsyncEpicsMotor(prefix + "O}Mtr")
self.bottom = AsyncEpicsMotor(prefix + "B}Mtr")
src/hextools/profiles/collection.py:38
- This adds a second
bppimport while also introducingbp/bpsthat are only referenced in comments; the profile now contains multiple unused/redefined imports. Ruff F401/F811 checks run on all Python files in CI, so the profile will fail linting before it can be used.
from bluesky import plans as bp, plan_stubs as bps, preprocessors as bpp
from bluesky.suspenders import SuspendFloor
from hextools.utils import show_docs
src/hextools/tomography/flyscans.py:67
- These lookups are unconditional even though
fe_shutterandphoton_shutterare optional anduse_shuttercan beFalse; consequently the documented no-shutter mode raises before resolving the actual scan devices. Guard these lookups withuse_shutter.
fe_shutter = ensure_available(Shutter, fe_shutter=fe_shutter)
photon_shutter = ensure_available(Shutter, photon_shutter=photon_shutter)
src/hextools/tomography/flyscans.py:135
- This shutter actuation is unconditional, so
use_shutter=Falsestill dereferences and opensphoton_shutter(which is allowed to beNone). Put this operation behind the sameuse_shutterguard as the front-end check.
yield from ensure_shutter_open(
photon_shutter, allow_actuation=True, group="prepare", wait=False
)
src/hextools/tomography/flyscans.py:161
- The finalizer also closes the photon shutter regardless of
use_shutter, so disabling shutter handling still requires a shutter and can fail during cleanup. Guard the cleanup operation withuse_shutter.
def _cleanup():
yield from ensure_shutter_closed(photon_shutter, allow_actuation=True)
src/hextools/tomography/flyscans.py:76
- Unlike
take_radiograph, this path does not reject anacquire_periodat or belowexposure_time; it then constructsTriggerInfo(deadtime=acquire_period - exposure_time)with a zero or negative deadtime. Validate the period before preparing any devices.
if acquire_period is None:
acquire_period = exposure_time + FRAME_PERIOD_MARGIN
src/hextools/tomography/flyscans.py:163
- Passing
_bodyand_cleanuphere passes generator functions tofinalize_wrapper, unlikeradiography.py, which passes_body()and_cleanup(). The wrapper expects plan iterators, so this flyscan will fail when the RunEngine tries to execute the plan; call both functions before wrapping.
yield from bpp.finalize_wrapper(_body, _cleanup)
src/hextools/utils.py:281
- The public
indentargument is ignored:_make_tree_bodyis always called with an empty prefix, soprint_device_tree(device, indent=2)produces the same output asindent=0despite the docstring promising an indentation level.
_make_tree_body(x, device)
tests/photon_delivery_system/test_slits.py:12
Slitsno longer accepts a numeric slit number: its second argument is nowname, and the prefix must already include the{Slt:<n>-Ax:portion. Passing1here constructs the device with an integer name (and the wrong PVs), so this fixture cannot run against the new class.
device = Slits("XF:TEST:", 1)
tests/photon_delivery_system/test_slits.py:77
- This second fixture repeats the removed
(prefix, num)constructor form. The newSlitsclass expects a complete slit prefix and a string name, so this test also needs to use the new constructor.
slits = Slits("XF:TEST:", 1)
- Files reviewed: 27/28 changed files
- Comments generated: 4
- Review effort level: Lite
| DetectorAcquireLogic, | ||
| DetectorDataLogic, | ||
| DetectorTrigger, | ||
| DetectorTriggerLogic, | ||
| PathProvider, |
| ensure_shutter_closed, | ||
| ensure_shutter_open, | ||
| ) | ||
| from hextools.utils import ensure_available, get_obj_from_ipython_ns |
| from nslsii import detectors | ||
| from ophyd_async.epics.adcore import AreaDetector | ||
| from ophyd_async.core import DetectorTrigger, TriggerInfo | ||
| from hextools.photon_delivery_system.shutter import ensure_shutter_closed, ensure_shutter_open | ||
| from hextools.utils import ensure_available, get_obj_from_ipython_ns |
| from hextools.detectors.germ import ( | ||
| GeRMTriggerLogic, | ||
| GeRMAcquireLogic, | ||
| GeRMDetector, | ||
| GeRMDetectorIO, | ||
| ) |
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved functional, compatibility, lint, and test issues remain.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (23)
Previously missed (1) — in code that hasn't changed since the last review.
src/hextools/utils.py:7
OrderedDictis newly imported but never used, so the repository's Ruff F401 check will fail on this file.
src/hextools/detectors/germ.py:370
GeRMDetectorregisters its driver and detector logics before callingStandardDetector.__init__, then calls the base initializer without the driver, path provider, or data/writer configuration. The profile'sGeRMDetector(...)construction will therefore fail or produce an uninitializedStandardDetector; initialize the base using the same contract asPhantomDetector.
super().__init__(name=name)
src/hextools/detectors/germ.py:4
- This unused
tkinterimport adds an unrelated runtime dependency to the detector module and is rejected by the repository's Ruff F401 check. Remove it.
from tkinter.font import names
src/hextools/detectors/germ.py:310
- This unconditional debug print runs whenever the detector supplies default trigger information, polluting beamline console output during normal RunEngine operation. Remove it or route diagnostic information through the project's logging mechanism.
print(livetime)
src/hextools/detectors/germ.py:133
- The new signal attribute is misspelled as
trigger_recieved, while the corresponding driver signal istrigger_received. This creates an inconsistent public API and can make the cine trigger signal impossible to discover by its expected name.
channel: A[SignalRW[int], PvSuffix("Channel")]
src/hextools/detectors/phantom.py:133
- The new public cine signal is misspelled
trigger_recieved, while the corresponding driver signal istrigger_received; callers using the expected spelling cannot access this readback.
trigger_recieved: A[SignalR[bool], PvSuffix("State_RBV.B3")]
src/hextools/detectors/phantom.py:500
- This new check makes existing Phantom acquisition tests fail before download because their mock setup does not set
total_frame_count(the mock remains zero while frames are requested). Update the tests and acquisition fixtures to provide the available-frame readback, or ensure the check is only applied when that IOC readback is valid.
available_frames, total_download_frames = await asyncio.gather(
self.driver.total_frame_count.get_value(),
self.driver.total_download_frames.get_value(),
)
if total_download_frames > available_frames:
raise RuntimeError(
f"Requested {total_download_frames} frames to download, but only {available_frames} are available!"
)
src/hextools/detectors/phantom.py:532
wait_for_idlenow waits only forcines[selected_cine].cine_content_saved, but the unchanged Phantom tests simulate completion by incrementingdownload_countand never set this new signal. Those tests will time out; retain a valid download-counter completion path or update all fixtures to drive the cine-saved signal.
async for saved in observe_value(
selected_cine.cine_content_saved, done_timeout=DEFAULT_TIMEOUT
):
if saved:
src/hextools/motors.py:178
- Renaming the public
DoubleObjCameraclass without an alias or updating all consumers leaves the unchangedtests/test_motors.pyunable to import the class at collection time. Preserve a compatibility alias or include the required consumer/test migration in this PR.
class FOV_2_4_mm_Camera(
StandardReadable, EpicsDevice, AsyncMovable[CameraObjective | str]
):
src/hextools/motors.py:178
- These new class names use underscores and lowercase segments, which violates the repository's Ruff N801 CapWords rule and will fail the lint gate. Use CapWords class names (for example,
FOV2_4mmCamera) and keep any screen-facing alias as a separate variable if needed.
class FOV_2_4_mm_Camera(
StandardReadable, EpicsDevice, AsyncMovable[CameraObjective | str]
):
src/hextools/photon_delivery_system/init.py:6
- The old
hextools.photon_delivery_systemmodule exposedBeamModeandFilterSetting, but the new package initializer omits both. Code using the existing top-level API will now fail withImportErrorafter this refactor; re-export these names and include them in__all__, or provide a compatibility path.
from .dclm import DCLM, change_energy
from .filters import Filter, FilterPosition, load_filters
from .shutter import Shutter
from .slits import Slits
src/hextools/photon_delivery_system/dclm.py:149
- This refactor changes the public positional call from the old
change_energy(dclm, energy, ...)tochange_energy(energy, dclm=...). Existing callers that pass the monochromator first will now treat aDCLMas the numeric energy and fail innp.isfinite; preserve the old positional order or add a compatibility wrapper while introducing the new keyword-based form.
def change_energy(
energy: float,
dclm: DCLM | None = None,
fs_camera: AreaDetector | None = None,
src/hextools/profiles/collection.py:36
- This newly added grouped import rebinds
bpp, which is already imported on line 13, and also introduces unusedbp/bpsnames. Ruff's F811/F401 checks will reject the profile; keep one import and only the names used by executable code.
from bluesky import plans as bp, plan_stubs as bps, preprocessors as bpp
src/hextools/profiles/collection.py:219
- This direct assignment to the private
_nameattribute violates the repository's configured SLF001 lint rule and bypasses the public naming API. Useset_name, which the profile's device initialization code already uses.
perkin_elmer._name = "perkin-elmer"
src/hextools/tomography/flyscans.py:67
- These lookups are unconditional even though the API makes shutters optional and exposes
use_shutter=False. A shutterless flyscan therefore fails before it can use the validpandaandmotorarguments; resolve these devices only when shutter use is enabled.
fe_shutter = ensure_available(Shutter, fe_shutter=fe_shutter)
photon_shutter = ensure_available(Shutter, photon_shutter=photon_shutter)
src/hextools/tomography/flyscans.py:135
- The photon shutter is actuated unconditionally here, so
use_shutter=Falsestill dereferences and opensphoton_shutter(which is allowed to beNone). Guard this prepare-time operation with the same flag used for the front-end shutter.
yield from ensure_shutter_open(
photon_shutter, allow_actuation=True, group="prepare", wait=False
)
src/hextools/tomography/flyscans.py:76
- An explicitly supplied
acquire_periodat or belowexposure_timeis not corrected or rejected, yet line 105 converts it into a negativeTriggerInfo.deadtime. Apply the same validation/margin rule used bytake_radiograph(or raise a clear error).
if acquire_period is None:
acquire_period = exposure_time + FRAME_PERIOD_MARGIN
src/hextools/tomography/flyscans.py:12
get_obj_from_ipython_nsis newly imported but never used in this module, so Ruff F401 will reject the changed file.
from hextools.utils import ensure_available, get_obj_from_ipython_ns
src/hextools/tomography/radiography.py:110
- These lookups run even when
use_shutter=False, so the documented shutter-disabled mode still raises iffe_shutterandphoton_shutterare not supplied or present in IPython. Resolve the shutters only inside theuse_shutterbranch.
fe_shutter = ensure_available(Shutter, fe_shutter=fe_shutter)
photon_shutter = ensure_available(Shutter, photon_shutter=photon_shutter)
src/hextools/tomography/radiography.py:52
- The new
nslsii.detectorsandget_obj_from_ipython_nsimports are never referenced (the function uses itsdetectorsargument andensure_availableinstead). With Ruff F401 enabled, these imports will fail pre-commit and should be removed.
from nslsii import detectors
from ophyd_async.epics.adcore import AreaDetector
from ophyd_async.core import DetectorTrigger, TriggerInfo
from hextools.photon_delivery_system.shutter import ensure_shutter_closed, ensure_shutter_open
from hextools.utils import ensure_available, get_obj_from_ipython_ns
src/hextools/utils.py:281
print_device_treeaccepts anindentargument and documents it as the current indentation level, but this call discards the argument, so every requested indentation produces identical output.
_make_tree_body(x, device)
tests/photon_delivery_system/test_slits.py:12
- The new
Slitsconstructor takes the complete PV prefix, but this test still passes the old numericnumargument. It therefore supplies1as the device name and never exercises the intended{Slt:1-Ax:PV prefix.
device = Slits("XF:TEST:", 1)
tests/photon_delivery_system/test_slits.py:77
- This second fixture still uses the removed
(prefix, num)constructor, so the test does not construct the slit PVs through the new API.
slits = Slits("XF:TEST:", 1)
- Files reviewed: 27/28 changed files
- Comments generated: 2
- Review effort level: Lite
| def _cleanup(): | ||
| yield from ensure_shutter_closed(photon_shutter, allow_actuation=True) |
| exposure_time: float, # screen: Exposure Time | ||
| num_images: int, # screen: Num Images | ||
| num_acquisitions: int = 1, # screen: Number of acquisitions | ||
| acquire_period: float = 0.0, # screen: Acquire Time |
Anthony Sligar (sligara7)
left a comment
There was a problem hiding this comment.
Work in progress. Will address CI checks failures and copilot comments in future PRs. All 4 main detectors are complete, mock tested and verified at HEX.
There was a problem hiding this comment.
🟡 Changes recommended
The flyscan finalizer and shutter handling contain runtime failures, with additional detector and compatibility issues.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (20)
src/hextools/tomography/flyscans.py:67
- The shutter arguments are optional when
use_shutter=False, but these unconditional lookups still raise when neither shutter is supplied. Resolve the shutters only in the branch that uses them.
fe_shutter = ensure_available(Shutter, fe_shutter=fe_shutter)
photon_shutter = ensure_available(Shutter, photon_shutter=photon_shutter)
src/hextools/tomography/flyscans.py:135
use_shutter=Falseis ignored here: the plan still opens the photon shutter. This defeats the flag and can actuate hardware for callers explicitly asking for a shutterless scan.
yield from ensure_shutter_open(
photon_shutter, allow_actuation=True, group="prepare", wait=False
)
src/hextools/tomography/flyscans.py:161
- The finalizer also closes the photon shutter unconditionally, so disabling shutter use still changes shutter state on exit. Guard this cleanup with the same
use_shuttercondition as the open operation.
def _cleanup():
yield from ensure_shutter_closed(photon_shutter, allow_actuation=True)
src/hextools/tomography/flyscans.py:76
- A caller can pass an
acquire_periodshorter thanexposure_time, producing a negativeTriggerInfo.deadtimeand invalid timing. Apply the same fallback/validation used bytake_radiographbefore constructing the trigger info.
if acquire_period is None:
acquire_period = exposure_time + FRAME_PERIOD_MARGIN
tests/photon_delivery_system/test_slits.py:77
- This second fixture repeats the obsolete
(prefix, number)constructor call. Use the complete slit prefix and pass a string name so the test exercises the same PV layout as the profile.
slits = Slits("XF:TEST:", 1)
src/hextools/motors.py:178
- Renaming this public class removes
DoubleObjCamera, buttests/test_motors.pyand existing callers still import and instantiate that name. The test suite will fail during collection withImportError; retain a compatibility alias or update all consumers in this change.
class FOV_2_4_mm_Camera(
StandardReadable, EpicsDevice, AsyncMovable[CameraObjective | str]
):
src/hextools/detectors/germ.py:370
StandardDetectoris initialized without the driver, acquire logic, trigger logic, or writer configuration. UnlikePhantomDetector, this leaves the detector base unconfigured (and may raise for missing required constructor arguments), so the profile's newgermdevice cannot be used. Initialize it through theStandardDetectorconstructor withself.driver, the logic objects, and the HDF writer/data logic.
self.add_detector_logics(GeRMTriggerLogic(self.driver))
self.add_detector_logics(GeRMAcquireLogic(self.driver))
self.hdf = NDFileHDF5IO(prefix + "MCA1:HDF1:", name="hdf")
self.add_detector_logics(
ADHDFDataLogic(
NDArrayDescription(
[self.driver.num_elements, self.driver.num_energy_bins],
self.driver.data_type,
self.driver.color_mode,
),
path_provider,
self.hdf,
)
)
self.add_config_signals(*config_sigs)
super().__init__(name=name)
src/hextools/utils.py:281
- The public
indentparameter is documented as controlling the current indentation level, but it is ignored here. Calls such asprint_device_tree(device, indent=2)produce the same output asindent=0. Pass an initial prefix based on the requested level.
x = []
_make_tree_body(x, device)
src/hextools/detectors/germ.py:4
- This newly added import is unused and also introduces a GUI-only dependency into the detector module. On headless installations without Tk, importing
hextools.detectors.germcan fail before the detector is even constructed; remove it.
from tkinter.font import names
src/hextools/detectors/germ.py:310
default_trigger_info()now prints the acquisition time on every call, which pollutes RunEngine/profile output and is not part of the detector API. Remove this debug print or replace it with appropriately gated logging.
async def default_trigger_info(self) -> TriggerInfo:
livetime = await self.driver.acquire_time.get_value()
print(livetime)
src/hextools/tomography/flyscans.py:12
get_obj_from_ipython_nsis imported but never used in this module. The repository's Ruff F401 check runs on all Python files, so this new import will fail CI.
from hextools.utils import ensure_available, get_obj_from_ipython_ns
src/hextools/tomography/radiography.py:48
- This new
nslsii.detectorsimport is unused; the function parameter with the same name is used instead. Ruff F401 is enabled for all Python files, so remove this import to keep pre-commit passing.
from nslsii import detectors
src/hextools/tomography/radiography.py:52
get_obj_from_ipython_nsis imported but never referenced in this module. Ruff F401 is enabled in the repository and will reject this new import during pre-commit.
from hextools.utils import ensure_available, get_obj_from_ipython_ns
src/hextools/profiles/collection.py:36
- This import block introduces
bp,bps, and a duplicatebpp, but none is referenced by the active profile code. Ruff F401/F811 will fail pre-commit; remove the unused additions and the now-unused duplicate imports rather than leaving them in the startup module.
from bluesky import plans as bp, plan_stubs as bps, preprocessors as bpp
src/hextools/profiles/collection.py:29
Path,NDStatsIO, andPluginSignalDataLogicare no longer referenced by the active profile after the related devices were commented out. These newly retained imports trigger Ruff F401 in the all-files pre-commit hook.
from pathlib import Path
from ophyd_async.epics.adcore import (
ADWriterFactory,
NDStatsIO,
PluginSignalDataLogic,
src/hextools/profiles/collection.py:32
- The profile now constructs Kinetix devices through
kinetix_factory, so this directKinetixDetectorimport is unused and will fail Ruff F401 during pre-commit.
from ophyd_async.epics.adkinetix import KinetixDetector
src/hextools/profiles/collection.py:37
SuspendFlooris imported but its only use is commented out below. Ruff F401 is enabled for the profile, so remove this now-unused import or restore an active suspender.
from bluesky.suspenders import SuspendFloor
src/hextools/profiles/collection.py:38
show_docsis not referenced by the startup profile. This added unused import violates the repository's Ruff F401 check and prevents pre-commit from passing.
from hextools.utils import show_docs
src/hextools/detectors/germ.py:10
DetectorDataLogicis no longer used after the old GeRM data-logic implementation was replaced. Ruff F401 will reject this unused import in CI.
DetectorDataLogic,
src/hextools/photon_delivery_system/init.py:6
- The package no longer re-exports
BeamModeorFilterSetting, although both were public names from the formerhextools.photon_delivery_systemmodule. Existing imports such as those in the removed monolithic-module tests will break after the module-to-package split; preserve these exports for compatibility.
from .dclm import DCLM, change_energy
from .filters import Filter, FilterPosition, load_filters
from .shutter import Shutter
from .slits import Slits
- Files reviewed: 27/28 changed files
- Comments generated: 5
- Review effort level: Lite
| def _cleanup(): | ||
| yield from ensure_shutter_closed(photon_shutter, allow_actuation=True) | ||
|
|
||
| yield from bpp.finalize_wrapper(_body, _cleanup) |
| @pytest.fixture | ||
| async def slits() -> Slits: | ||
| async with init_devices(mock=True): | ||
| device = Slits("XF:TEST:", 1) |
| ADWriterFactory, | ||
| NDArrayDescription, | ||
| NDFileHDF5IO, | ||
| NDPluginBaseIO, | ||
| NDProcessIO, |
| fe_shutter = ensure_available(Shutter, fe_shutter=fe_shutter) | ||
| photon_shutter = ensure_available(Shutter, photon_shutter=photon_shutter) |
| from collections.abc import MutableMapping | ||
| from datetime import datetime | ||
| from typing import Any | ||
| from typing import Any, OrderedDict, TypeVar |
Uh oh!
There was an error while loading. Please reload this page.