Merge branch 'deduplicate-CaptureInfo' into 'v3'

Change the classes for gallery cards information to be XxxGalleryInfo

Closes #814

See merge request openflexure/openflexure-microscope-server!643
This commit is contained in:
Joe Knapper 2026-07-02 18:25:10 +00:00
commit eac055d352
6 changed files with 23 additions and 23 deletions

Binary file not shown.

View file

@ -45,7 +45,7 @@ class NotEnoughFreeSpaceError(InvocationError):
"""An exception raised if there is not enough free space on disk to scan.""" """An exception raised if there is not enough free space on disk to scan."""
class ScanInfo(BaseModel): class ScanGalleryInfo(BaseModel):
"""Summary information for the UI about a scan folder.""" """Summary information for the UI about a scan folder."""
name: str name: str
@ -71,8 +71,8 @@ class BaseScanData(BaseModel):
Separating historic and active data allows workflows to use any BaseModel for its Separating historic and active data allows workflows to use any BaseModel for its
settings, but for the data to be reloaded even if that model has updated or is not settings, but for the data to be reloaded even if that model has updated or is not
available. Historic scan data loaded from disk is used for stitching and for available. Historic scan data loaded from disk is used for stitching and for
creating a ScanInfo object for communicating with the UI. These uses are clearly creating a ScanGalleryInfo object for communicating with the UI. These uses are
typed by this model. clearly typed by this model.
This serialises into a human readable format where possible with This serialises into a human readable format where possible with
@ -339,16 +339,16 @@ class ScanDirectoryManager:
@requires_lock @requires_lock
def all_scans_info( def all_scans_info(
self, *, ongoing: Optional[str] = None, include_ongoing: bool = True self, *, ongoing: Optional[str] = None, include_ongoing: bool = True
) -> list[ScanInfo]: ) -> list[ScanGalleryInfo]:
"""Return a lists of ScanInfo objects for each scan. """Return a lists of ScanGalleryInfo objects for each scan.
:param ongoing: The name of the ongoing scan (or None if no scan is ongoing). :param ongoing: The name of the ongoing scan (or None if no scan is ongoing).
:param include_ongoing: True (dfault) to include the scan info of the ongoing :param include_ongoing: True (dfault) to include the scan info of the ongoing
scan in the return. False to exclude it. scan in the return. False to exclude it.
:return: A list of ScanInfo objects for each scan. :return: A list of ScanGalleryInfo objects for each scan.
""" """
all_info: list[ScanInfo] = [] all_info: list[ScanGalleryInfo] = []
for scan_name in self.all_scans: for scan_name in self.all_scans:
scan_is_ongoing = scan_name == ongoing scan_is_ongoing = scan_name == ongoing
if scan_is_ongoing and not include_ongoing: if scan_is_ongoing and not include_ongoing:
@ -591,7 +591,7 @@ class ScanDirectory:
LOGGER.warning(f"Could not validate scan data for {self.name}.") LOGGER.warning(f"Could not validate scan data for {self.name}.")
return None return None
def scan_info(self, skip_json: bool = False) -> ScanInfo: def scan_info(self, skip_json: bool = False) -> ScanGalleryInfo:
"""Return the information to be used in the UI for the scan.""" """Return the information to be used in the UI for the scan."""
scan_files = self.get_scan_files() scan_files = self.get_scan_files()
scan_images = self._extract_scan_images(scan_files) scan_images = self._extract_scan_images(scan_files)
@ -608,7 +608,7 @@ class ScanDirectory:
else scan_data.duration.total_seconds() else scan_data.duration.total_seconds()
) )
return ScanInfo( return ScanGalleryInfo(
name=self.name, name=self.name,
created=self.created_time, created=self.created_time,
modified=self.get_modified_time(), modified=self.get_modified_time(),

View file

@ -206,7 +206,7 @@ class CaptureMode(BaseModel):
"""The resolution to save the image. Use None to save as captured.""" """The resolution to save the image. Use None to save as captured."""
class CaptureInfo(BaseModel): class CaptureGalleryInfo(BaseModel):
"""Summary information for the UI about an image.""" """Summary information for the UI about an image."""
name: str name: str
@ -296,9 +296,9 @@ class BaseCamera(OFMThing, ABC):
_show_data_in_gallery = True _show_data_in_gallery = True
@property @property
def gallery_data_schema(self) -> type[CaptureInfo]: def gallery_data_schema(self) -> type[CaptureGalleryInfo]:
"""The schema (BaseModel) for passing data to the gallery.""" """The schema (BaseModel) for passing data to the gallery."""
return CaptureInfo return CaptureGalleryInfo
def _all_captures(self) -> list[str]: def _all_captures(self) -> list[str]:
"""Return the full path for all captures on disk.""" """Return the full path for all captures on disk."""
@ -310,10 +310,10 @@ class BaseCamera(OFMThing, ABC):
captures.append(full_path) captures.append(full_path)
return captures return captures
def get_data_for_gallery(self) -> list[CaptureInfo]: def get_data_for_gallery(self) -> list[CaptureGalleryInfo]:
"""Return all the information about the saved captures.""" """Return all the information about the saved captures."""
return [ return [
CaptureInfo( CaptureGalleryInfo(
name=os.path.basename(capture), name=os.path.basename(capture),
created=os.path.getctime(capture), created=os.path.getctime(capture),
modified=os.path.getmtime(capture), modified=os.path.getmtime(capture),

View file

@ -172,11 +172,11 @@ class SmartScanThing(OFMThing):
_show_data_in_gallery = True _show_data_in_gallery = True
@property @property
def gallery_data_schema(self) -> type[scan_directories.ScanInfo]: def gallery_data_schema(self) -> type[scan_directories.ScanGalleryInfo]:
"""The schema (BaseModel) for passing data to the gallery.""" """The schema (BaseModel) for passing data to the gallery."""
return scan_directories.ScanInfo return scan_directories.ScanGalleryInfo
def get_data_for_gallery(self) -> list[scan_directories.ScanInfo]: def get_data_for_gallery(self) -> list[scan_directories.ScanGalleryInfo]:
"""Return all the information from the scan directories. """Return all the information from the scan directories.
It is preferable to use the method rather than calling It is preferable to use the method rather than calling

View file

@ -17,7 +17,7 @@ from openflexure_microscope_server.scan_directories import (
NotEnoughFreeSpaceError, NotEnoughFreeSpaceError,
ScanDirectory, ScanDirectory,
ScanDirectoryManager, ScanDirectoryManager,
ScanInfo, ScanGalleryInfo,
get_files_in_zip, get_files_in_zip,
) )
@ -202,7 +202,7 @@ def test_scan_sequence_and_listing(caplog):
# (more detailed scan_info tests below) # (more detailed scan_info tests below)
all_scan_info = scan_dir_manager.all_scans_info() all_scan_info = scan_dir_manager.all_scans_info()
for scan_info in all_scan_info: for scan_info in all_scan_info:
assert isinstance(scan_info, ScanInfo) assert isinstance(scan_info, ScanGalleryInfo)
assert scan_info.name.startswith("fake_scan_000") assert scan_info.name.startswith("fake_scan_000")
assert scan_info.number_of_images == 0 assert scan_info.number_of_images == 0
assert scan_info.duration is not None assert scan_info.duration is not None
@ -216,7 +216,7 @@ def test_scan_sequence_and_listing(caplog):
# Re-read the data with bad data for fake_scan_0004 # Re-read the data with bad data for fake_scan_0004
all_scan_info = scan_dir_manager.all_scans_info() all_scan_info = scan_dir_manager.all_scans_info()
for scan_info in all_scan_info: for scan_info in all_scan_info:
assert isinstance(scan_info, ScanInfo) assert isinstance(scan_info, ScanGalleryInfo)
assert scan_info.name.startswith("fake_scan_000") assert scan_info.name.startswith("fake_scan_000")
assert scan_info.number_of_images == 0 assert scan_info.number_of_images == 0
# Bad data for scan 0004. So duration is None. # Bad data for scan 0004. So duration is None.
@ -235,7 +235,7 @@ def test_scan_sequence_and_listing(caplog):
# Re-read the data again claiming fake_scan_0004 is ongoing # Re-read the data again claiming fake_scan_0004 is ongoing
all_scan_info = scan_dir_manager.all_scans_info(ongoing="fake_scan_0004") all_scan_info = scan_dir_manager.all_scans_info(ongoing="fake_scan_0004")
for scan_info in all_scan_info: for scan_info in all_scan_info:
assert isinstance(scan_info, ScanInfo) assert isinstance(scan_info, ScanGalleryInfo)
assert scan_info.name.startswith("fake_scan_000") assert scan_info.name.startswith("fake_scan_000")
assert scan_info.number_of_images == 0 assert scan_info.number_of_images == 0
# scan 0004 is marked as ongoing, so duration is None. # scan 0004 is marked as ongoing, so duration is None.

View file

@ -21,7 +21,7 @@ from pydantic import ValidationError
import labthings_fastapi as lt import labthings_fastapi as lt
from openflexure_microscope_server.things.background_detect import ChannelDeviationLUV from openflexure_microscope_server.things.background_detect import ChannelDeviationLUV
from openflexure_microscope_server.things.camera import CaptureInfo, simulation from openflexure_microscope_server.things.camera import CaptureGalleryInfo, simulation
from openflexure_microscope_server.things.camera.simulation import SimulatedCamera from openflexure_microscope_server.things.camera.simulation import SimulatedCamera
from openflexure_microscope_server.things.stage.dummy import DummyStage from openflexure_microscope_server.things.stage.dummy import DummyStage
@ -297,7 +297,7 @@ def test_gallery_responses(camera, mocker, caplog):
with tempfile.TemporaryDirectory() as tmpdir, caplog.at_level(logging.INFO): with tempfile.TemporaryDirectory() as tmpdir, caplog.at_level(logging.INFO):
camera._data_dir = tmpdir camera._data_dir = tmpdir
assert camera.gallery_data_schema == CaptureInfo assert camera.gallery_data_schema == CaptureGalleryInfo
# When we start there are no captures # When we start there are no captures
assert camera.get_data_for_gallery() == [] assert camera.get_data_for_gallery() == []