Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2024 12 08 test dependencies #49

Merged
merged 7 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 21 additions & 64 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
@@ -1,77 +1,34 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Python package

on:
push:
branches: [ master ]
tags: '*'
pull_request:
branches: [ master ]

jobs:
build:

runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.11", "3.12", "3.13"]

build-python-package:
uses: nion-software/github-workflows/.github/workflows/build-python-package.yml@main
secrets:
anaconda-token: ${{ secrets.ANACONDA_TOKEN }}
pypi-publish:
name: Upload release to PyPI
needs: [build-python-package]
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
environment: release
permissions:
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Display Python version
run: python -c "import sys; print(sys.version)"
- name: Build Distribution
run: |
python -m pip install --upgrade pip
python -m pip install build
python -m build
- name: Install Test Dependencies
run: |
pip install -r test-requirements.txt
python -m pip install mypy
mypy --version
- name: Type Checking
run: |
# use mypy -p to work around mypy issue 8944
mypy --namespace-packages --ignore-missing-imports --follow-imports=silent --strict --no-warn-redundant-casts --no-warn-unused-ignores -p nionswift_plugin.usim
- name: Test
- name: Download All Artifacts
uses: actions/download-artifact@v4
- name: Copy files to upload to PyPI to dist.
shell: bash
run: |
python -m unittest discover -s nionswift_plugin/usim/test/ -p "*_test.py"
- name: Upload Artifacts
uses: actions/upload-artifact@v4
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
with:
name: distribution
path: dist
- name: Publish package to PyPI
mkdir dist
find . -name "*.whl" -exec cp {} dist \;
ls -lR .
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
with:
skip-existing: true
user: __token__
password: ${{ secrets.pypi_password }}
- name: Set up Miniconda for conda-build
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
uses: conda-incubator/setup-miniconda@v3
with:
auto-update-conda: true
python-version: '3.11'
- name: Build/publish anaconda package
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
shell: bash -l {0}
run: |
# make a directory to avoid name conflicts with the channel. argh.
mkdir conda_build
pushd conda_build
conda update -n base --all -y
conda update --all -y
conda install conda-build anaconda-client -y
conda build -q --python ${{ matrix.python-version }} -c nion -c conda-forge --override-channels --skip-existing --user nion --token ${{ secrets.anaconda_token }} ..
popd
print-hash: true
1 change: 1 addition & 0 deletions meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ requirements:

test:
imports:
- nion.usim_device
- nionswift_plugin.usim
- nionswift_plugin.usim.test

Expand Down
9 changes: 9 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Global options:

[mypy]
ignore_missing_imports = True
follow_imports = silent
strict = True
warn_redundant_casts = False
warn_unused_ignores = False
packages = nionswift_plugin.usim, nion.usim_device
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,33 @@

# other plug-ins
from nion.instrumentation import camera_base
from . import RonchigramCameraSimulator
from . import EELSCameraSimulator
from nion.instrumentation import stem_controller
from nion.device_kit import InstrumentDevice
from nion.device_kit import ScanDevice

if typing.TYPE_CHECKING:
from . import InstrumentDevice
from . import CameraSimulator
from . import ScanDevice

_NDArray = numpy.typing.NDArray[typing.Any]

_ = gettext.gettext


class CameraSimulatorLike(typing.Protocol):
def close(self) -> None: ...
def get_dimensional_calibrations(self, readout_area: typing.Optional[Geometry.IntRect], binning_shape: typing.Optional[Geometry.IntSize]) -> typing.Sequence[Calibration.Calibration]: ...
def get_frame_data(self, readout_area: Geometry.IntRect, binning_shape: Geometry.IntSize, exposure_s: float, scan_context: stem_controller.ScanContext, probe_position: typing.Optional[Geometry.FloatPoint]) -> DataAndMetadata.DataAndMetadata: ...


class Camera(camera_base.CameraDevice3):
"""Implement a camera device."""

def __init__(self, camera_id: str, camera_type: str, camera_name: str, instrument: InstrumentDevice.Instrument):
def __init__(self, camera_id: str, camera_type: str, camera_name: str, simulator: CameraSimulatorLike, instrument: InstrumentDevice.Instrument):
self.camera_id = camera_id
self.camera_type = camera_type
self.camera_name = camera_name
self.__camera_task: typing.Optional[CameraTask] = None
self.__instrument = instrument
self.__scan_device: typing.Optional[ScanDevice.Device] = None
self.__simulator: CameraSimulator.CameraSimulator
if camera_type == "ronchigram":
self.__simulator = RonchigramCameraSimulator.RonchigramCameraSimulator(instrument, Geometry.IntSize.make(instrument.camera_sensor_dimensions("ronchigram")), instrument.counts_per_electron, instrument.stage_size_nm)
elif camera_type == "eels":
self.__simulator = EELSCameraSimulator.EELSCameraSimulator(instrument, Geometry.IntSize.make(instrument.camera_sensor_dimensions("eels")), instrument.counts_per_electron)
else:
raise ValueError(f"Unsupported camera type '{camera_type}'.")
self.__simulator = simulator
self.__sensor_dimensions = instrument.camera_sensor_dimensions(camera_type)
self.__readout_area = instrument.camera_readout_area(camera_type)
self.__symmetric_binning = True
Expand All @@ -74,9 +71,10 @@ def __init__(self, camera_id: str, camera_type: str, camera_name: str, instrumen
self.__thread.start()

# Also register the camera device and use a unique name for it so that we can directly access it
if Registry.get_component(f"usim_{camera_type}_camera_device"):
raise RuntimeError(f"Component 'usim_{camera_type}_camera_device' is already registered.")
Registry.register_component(self, {f"usim_{camera_type}_camera_device"})
camera_device_id = f"{camera_id}_device"
if Registry.get_component(camera_device_id):
raise RuntimeError(f"Component '{camera_device_id}' is already registered.")
Registry.register_component(self, {camera_device_id})

# TODO Define external trigger interface and the mechanism for aqcuiring multiple sequences
self._external_trigger = False # Just here for testing, we need to decide on how to specify external trigger mode in the base class
Expand Down Expand Up @@ -124,7 +122,7 @@ def mask_array(self) -> typing.Optional[_NDArray]:
return self.__mask_array

@property
def simulator(self) -> CameraSimulator.CameraSimulator:
def simulator(self) -> CameraSimulatorLike:
return self.__simulator

def get_expected_dimensions(self, binning: int) -> typing.Tuple[int, int]:
Expand Down Expand Up @@ -344,7 +342,7 @@ def acquire_synchronized_begin(self, camera_frame_parameters: camera_base.Camera
scan_controller = self.__instrument.scan_controller
assert scan_controller
scan_device = scan_controller.scan_device
self.__scan_device = typing.cast("ScanDevice.Device", scan_device)
self.__scan_device = typing.cast(ScanDevice.Device, scan_device)
self.__camera_task = CameraTask(self, camera_frame_parameters, collection_shape)
self.__camera_task.start()
return camera_base.PartialData(self.__camera_task._xdata_ex, False, False, None, 0)
Expand Down Expand Up @@ -432,7 +430,7 @@ def grab_partial(self, *, update_period: float = 1.0) -> typing.Tuple[bool, bool
return True, True, 0


class CameraSettings:
class CameraSettings(camera_base.CameraSettings):

def __init__(self, camera_id: str):
# these events must be defined
Expand Down Expand Up @@ -581,16 +579,3 @@ def __init__(self, stem_controller_id: str, camera_device: Camera, camera_settin
self.camera_device = camera_device
self.camera_settings = camera_settings
self.priority = 20


def run(instrument: InstrumentDevice.Instrument) -> None:
component_types = {"camera_module"} # the set of component types that this component represents
camera_device = Camera("usim_ronchigram_camera", "ronchigram", _("uSim Ronchigram Camera"), instrument)
setattr(camera_device, "camera_panel_type", "ronchigram")
camera_settings = CameraSettings("usim_ronchigram_camera")
Registry.register_component(CameraModule("usim_stem_controller", camera_device, camera_settings), component_types)

camera_device = Camera("usim_eels_camera", "eels", _("uSim EELS Camera"), instrument)
setattr(camera_device, "camera_panel_type", "eels")
camera_settings = CameraSettings("usim_eels_camera")
Registry.register_component(CameraModule("usim_stem_controller", camera_device, camera_settings), component_types)
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@

from nion.data import Calibration
from nion.data import DataAndMetadata
from nion.device_kit import InstrumentDevice
from nion.instrumentation import stem_controller
from nion.utils import Geometry
from nion.utils import Registry
from nion.usim_device import InstrumentDevice as InstrumentDevice_

if typing.TYPE_CHECKING:
from . import InstrumentDevice
from . import ScanDevice
from nion.instrumentation import stem_controller

_NDArray = numpy.typing.NDArray[typing.Any]

Expand All @@ -28,11 +26,15 @@ class FrameSettings:
sample_name: str


def get_value_manager(instrument: InstrumentDevice.Instrument) -> InstrumentDevice_.ValueManager:
return typing.cast(InstrumentDevice_.ValueManager, instrument.value_manager)


class CameraSimulator:

depends_on: typing.List[str] = list() # subclasses should define the controls and attributes they depend on here

def __init__(self, instrument: InstrumentDevice.Instrument, camera_type: str, sensor_dimensions: Geometry.IntSize, counts_per_electron: int) -> None:
def __init__(self, instrument: InstrumentDevice_.Instrument, camera_type: str, sensor_dimensions: Geometry.IntSize, counts_per_electron: int) -> None:
self.__instrument = instrument
self._camera_type = camera_type
self._sensor_dimensions = sensor_dimensions
Expand All @@ -44,7 +46,7 @@ def property_changed(name: str) -> None:
if name in self.depends_on:
self._needs_recalculation = True

self.__property_changed_event_listener = instrument.property_changed_event.listen(property_changed)
self.__property_changed_event_listener = get_value_manager(instrument).property_changed_event.listen(property_changed)

# we also need to inform the cameras about changes to the (parked) probe position
def probe_state_changed(probe_state: str, probe_position: typing.Optional[Geometry.FloatPoint]) -> None:
Expand All @@ -64,7 +66,7 @@ def _camera_shape(self) -> Geometry.IntSize:
return self._sensor_dimensions

@property
def instrument(self) -> InstrumentDevice.Instrument:
def instrument(self) -> InstrumentDevice_.Instrument:
return self.__instrument

def get_dimensional_calibrations(self, readout_area: typing.Optional[Geometry.IntRect], binning_shape: typing.Optional[Geometry.IntSize]) -> typing.Sequence[Calibration.Calibration]:
Expand Down Expand Up @@ -102,4 +104,5 @@ def _get_frame_settings(self, readout_area: Geometry.IntRect, binning_shape: Geo
probe_position = scan_device.current_probe_position
elif self.instrument.probe_state == "parked" and parked_probe_position is not None:
probe_position = parked_probe_position
return FrameSettings(readout_area, binning_shape, exposure_s, copy.deepcopy(scan_context), probe_position, self.instrument.sample.title)
scan_data_generator = typing.cast(InstrumentDevice_.ScanDataGenerator, self.instrument.scan_data_generator)
return FrameSettings(readout_area, binning_shape, exposure_s, copy.deepcopy(scan_context), probe_position, scan_data_generator.sample.title)
45 changes: 45 additions & 0 deletions nion/usim_device/DeviceConfiguration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import gettext

from nion.device_kit import CameraDevice
from nion.instrumentation import camera_base
from nion.instrumentation import scan_base
from nion.instrumentation import stem_controller
from nion.usim_device import EELSCameraSimulator
from nion.usim_device import InstrumentDevice
from nion.usim_device import RonchigramCameraSimulator
from nion.usim_device import ScanDevice
from nion.utils import Geometry


_ = gettext.gettext


class AcquisitionContextConfiguration:
def __init__(self, *, sample_index: int = 0) -> None:
self.instrument_id = "usim_stem_controller"
value_manager = InstrumentDevice.ValueManager()
axis_manager = InstrumentDevice.AxisManager()
scan_data_generator = InstrumentDevice.ScanDataGenerator(sample_index=sample_index)
instrument = InstrumentDevice.Instrument(self.instrument_id, value_manager, axis_manager, scan_data_generator)
self._instrument = instrument
self.instrument: stem_controller.STEMController = instrument
self.ronchigram_camera_device_id = "usim_ronchigram_camera"
self.eels_camera_device_id = "usim_eels_camera"
self._scan_module = ScanDevice.ScanModule(instrument)
self.scan_module: scan_base.ScanModule = self._scan_module

ronchigram_simulator = RonchigramCameraSimulator.RonchigramCameraSimulator(instrument, Geometry.IntSize.make(instrument.camera_sensor_dimensions("ronchigram")), instrument.counts_per_electron, instrument.stage_size_nm)
self._ronchigram_camera_device = CameraDevice.Camera("usim_ronchigram_camera", "ronchigram", _("uSim Ronchigram Camera"), ronchigram_simulator, instrument)
self._ronchigram_camera_settings = CameraDevice.CameraSettings(self.ronchigram_camera_device_id)

eels_camera_simulator = EELSCameraSimulator.EELSCameraSimulator(instrument, Geometry.IntSize.make(instrument.camera_sensor_dimensions("eels")), instrument.counts_per_electron)
self._eels_camera_device = CameraDevice.Camera("usim_eels_camera", "eels", _("uSim EELS Camera"), eels_camera_simulator, instrument)
self._eels_camera_settings = CameraDevice.CameraSettings(self.eels_camera_device_id)

self.ronchigram_camera_device: camera_base.CameraDevice3 = self._ronchigram_camera_device
self.ronchigram_camera_settings: camera_base.CameraSettings = self._ronchigram_camera_settings
self.eels_camera_device: camera_base.CameraDevice3 = self._eels_camera_device
self.eels_camera_settings: camera_base.CameraSettings = self._eels_camera_settings

value_manager.ronchigram_camera = self._ronchigram_camera_device
value_manager.eels_camera = self._eels_camera_device
Loading
Loading