diff --git a/docs/config.md b/docs/config.md index 14984e40..276022a3 100644 --- a/docs/config.md +++ b/docs/config.md @@ -230,6 +230,9 @@ This file holds the specifications of the camera. This file is must be located t **camera_rotation_vs_stage_xy** : In radians, give here the rotation of the position of the rotation axis with respect to the horizontal. Used for diffraction only. Corresponds to the rotation axis in RED and PETS, for example: `-2.24`. You can find the rotation axis for your setup using the script `edtools.find_rotation_axis` available from [here](https://github.com/instamatic-dev/edtools#find_rotation_axispy). +**streamable** +: Boolean value. If present, overwrites the default behavior as implemented in each camera interface class to force the camera to stream (if `True`) or prevent it from streaming (if `False`) all collected data live directly to the GUI. + **stretch_amplitude** : Use `instamatic.stretch_correction` to characterize the lens distortion. The numbers here are used to calculate the XCORR/YCORR maps. The amplitude is the percentage difference between the maximum and minimum eigenvectors of the ellipsoid, i.e. if the amplitude is `2.43`, eig(max)/eig(min) = 1.0243. You can use the program `instamatic.stretch_correction` available [here](https://github.com/instamatic-dev/instamatic/blob/main/docs/programs.md#instamaticstretch_correction) on some powder patterns to define these numbers. diff --git a/src/instamatic/camera/__init__.py b/src/instamatic/camera/__init__.py index 950ec6da..d4db84a7 100644 --- a/src/instamatic/camera/__init__.py +++ b/src/instamatic/camera/__init__.py @@ -1,4 +1,4 @@ from __future__ import annotations -from .camera import Camera +from .camera import get_camera, get_camera_class from .videostream import VideoStream diff --git a/src/instamatic/camera/camera.py b/src/instamatic/camera/camera.py index fa806b07..4deba61f 100644 --- a/src/instamatic/camera/camera.py +++ b/src/instamatic/camera/camera.py @@ -2,18 +2,20 @@ import logging from pathlib import Path +from typing import Optional from instamatic import config +from instamatic.camera.camera_base import CameraBase logger = logging.getLogger(__name__) -__all__ = ['Camera'] +__all__ = ['get_camera', 'get_camera_class'] default_cam_interface = config.camera.interface -def get_cam(interface: str = None): - """Grabs the camera object defined by `interface`""" +def get_camera_class(interface: str) -> type[CameraBase]: + """Grabs the camera class with the specific `interface`""" simulate = config.settings.simulate @@ -39,10 +41,24 @@ def get_cam(interface: str = None): return cam -def Camera(name: str = None, as_stream: bool = False, use_server: bool = False): +def get_camera( + name: Optional[str] = None, + as_stream: bool = False, + use_server: bool = False, +) -> CameraBase: """Initialize the camera identified by the 'name' parameter if `as_stream` is True, it will return a VideoStream object if `as_stream` is False, it - will return the raw Camera object.""" + will return the raw Camera object. + + name: Optional[str] + Specify which camera to use, must be implemented in `instamatic.camera` + as_stream: bool + If True (default False), allow streaming this camera image live. + use_server: bool + Connect to camera server running on the host/port defined in the config + + returns: Camera interface class instance + """ if name is None: name = config.camera.name @@ -56,9 +72,8 @@ def Camera(name: str = None, as_stream: bool = False, use_server: bool = False): from instamatic.camera.camera_client import CamClient cam = CamClient(name=name, interface=interface) - as_stream = False # precaution else: - cam_cls = get_cam(interface) + cam_cls = get_camera_class(interface) if interface in ('timepix', 'pytimepix'): tpx_config = ( @@ -221,7 +236,7 @@ def main_entry(): if __name__ == '__main__': # main_entry() - cam = Camera(use_server=True) + cam = get_camera(use_server=True) arr = cam.get_image(exposure=0.1) print(arr) print(arr.shape) diff --git a/src/instamatic/camera/camera_client.py b/src/instamatic/camera/camera_client.py index dcb63442..6b320657 100644 --- a/src/instamatic/camera/camera_client.py +++ b/src/instamatic/camera/camera_client.py @@ -55,7 +55,6 @@ def __init__( self.name = name self.interface = interface self._bufsize = BUFSIZE - self.streamable = False # overrides cam settings self.verbose = False try: @@ -83,6 +82,7 @@ def __init__( self.buffers: Dict[str, np.ndarray] = {} self.shms = {} + self._attr_dct: dict = {} self._init_dict() self._init_attr_dict() @@ -104,14 +104,14 @@ def connect(self): def __getattr__(self, attr_name): if attr_name in self._dct: + if attr_name in object.__getattribute__(self, '_attr_dct'): + return self._eval_dct({'attr_name': attr_name}) wrapped = self._dct[attr_name] elif attr_name in self._attr_dct: dct = {'attr_name': attr_name} return self._eval_dct(dct) else: - raise AttributeError( - f'`{self.__class__.__name__}` object has no attribute `{attr_name}`' - ) + wrapped = None # AFAIK can't wrap with None, can cause odd errors @wraps(wrapped) def wrapper(*args, **kwargs): @@ -156,9 +156,9 @@ def _eval_dct(self, dct): def _init_dict(self): """Get list of functions and their doc strings from the uninitialized class.""" - from instamatic.camera.camera import get_cam + from instamatic.camera.camera import get_camera_class - cam = get_cam(self.interface) + cam = get_camera_class(self.interface) self._dct = { key: value for key, value in cam.__dict__.items() if not key.startswith('_') diff --git a/src/instamatic/camera/videostream.py b/src/instamatic/camera/videostream.py index 84e4ed3b..13a4d7dd 100644 --- a/src/instamatic/camera/videostream.py +++ b/src/instamatic/camera/videostream.py @@ -10,7 +10,7 @@ import numpy as np -from instamatic.camera import Camera +from instamatic.camera import get_camera from instamatic.camera.camera_base import CameraBase from instamatic.image_utils import autoscale @@ -115,7 +115,7 @@ def from_any( cls: Type[VideoStream_T], cam: Union[CameraBase, str] = 'simulate' ) -> VideoStream_T: """Create a subclass based on passed cam or cam-str stream-ability.""" - cam: CameraBase = Camera(name=cam) if isinstance(cam, str) else cam + cam: CameraBase = get_camera(name=cam) if isinstance(cam, str) else cam if cls is VideoStream: return (LiveVideoStream if cam.streamable else FakeVideoStream)(cam) return cls(cam) @@ -123,7 +123,7 @@ def from_any( def __init__(self, cam: Union[CameraBase, str] = 'simulate') -> None: threading.Thread.__init__(self) - self.cam: CameraBase = Camera(name=cam) if isinstance(cam, str) else cam + self.cam: CameraBase = get_camera(name=cam) if isinstance(cam, str) else cam self.lock = threading.Lock() self.default_exposure = self.cam.default_exposure diff --git a/src/instamatic/config/autoconfig.py b/src/instamatic/config/autoconfig.py index b21c5930..96ef2fe2 100644 --- a/src/instamatic/config/autoconfig.py +++ b/src/instamatic/config/autoconfig.py @@ -125,12 +125,12 @@ def main(): cam_connect = False cam_name = None - from instamatic.camera.camera import get_cam + from instamatic.camera.camera import get_camera_class from instamatic.controller import TEMController from instamatic.microscope import get_microscope_class if cam_connect: - cam = get_cam(cam_name)() if cam_name else None + cam = get_camera_class(cam_name)() if cam_name else None else: cam = None diff --git a/src/instamatic/controller.py b/src/instamatic/controller.py index 2aa69fba..dea63ff2 100644 --- a/src/instamatic/controller.py +++ b/src/instamatic/controller.py @@ -9,7 +9,7 @@ import yaml from instamatic import config -from instamatic.camera import Camera +from instamatic.camera import get_camera from instamatic.camera.camera_base import CameraBase from instamatic.exceptions import TEMControllerError from instamatic.formats import write_tiff @@ -61,7 +61,7 @@ def initialize( print(f'Camera : {cam_name}{cam_tag}') - cam = Camera(cam_name, as_stream=stream, use_server=use_cam_server) + cam = get_camera(cam_name, as_stream=stream, use_server=use_cam_server) else: cam = None diff --git a/src/instamatic/microscope/client.py b/src/instamatic/microscope/client.py index 1d34d27c..52150412 100644 --- a/src/instamatic/microscope/client.py +++ b/src/instamatic/microscope/client.py @@ -125,10 +125,6 @@ def _init_dict(self) -> None: } self._dct['get_attrs'] = None - def _init_attr_dict(self): - """Get list of attrs and their types.""" - self._attr_dct = self.get_attrs() - def __dir__(self) -> list: return list(self._dct.keys()) diff --git a/src/instamatic/microscope/microscope.py b/src/instamatic/microscope/microscope.py index 6f44791e..ac83d1c1 100644 --- a/src/instamatic/microscope/microscope.py +++ b/src/instamatic/microscope/microscope.py @@ -10,7 +10,7 @@ __all__ = ['get_microscope', 'get_microscope_class'] -def get_microscope_class(interface: str) -> 'type[MicroscopeBase]': +def get_microscope_class(interface: str) -> type[MicroscopeBase]: """Grab tem class with the specific 'interface'.""" simulate = config.settings.simulate @@ -42,7 +42,7 @@ def get_microscope(name: Optional[str] = None, use_server: bool = False) -> Micr use_server: bool Connect to microscope server running on the host/port defined in the config file - returns: TEM interface class + returns: TEM interface class instance """ if name is None: interface = default_tem_interface diff --git a/src/instamatic/server/cam_server.py b/src/instamatic/server/cam_server.py index bba3dc8d..2d3c926f 100644 --- a/src/instamatic/server/cam_server.py +++ b/src/instamatic/server/cam_server.py @@ -11,7 +11,7 @@ import numpy as np from instamatic import config -from instamatic.camera import Camera +from instamatic.camera import get_camera from instamatic.utils import high_precision_timers from .serializer import dumper, loader @@ -81,7 +81,7 @@ def copy_data_to_shared_buffer(self, arr): def run(self): """Start server thread.""" - self.cam = Camera(name=self._name, use_server=False) + self.cam = get_camera(name=self._name, use_server=False) self.cam.get_attrs = self.get_attrs print(f'Initialized camera: {self.cam.interface}')