Source code for giant.opnav_class

# Copyright 2021 United States Government as represented by the Administrator of the National Aeronautics and Space
# Administration.  No copyright is claimed in the United States under Title 17, U.S. Code. All Other Rights Reserved.


"""
The opnav_class module provides an OpNav object that serves as the foundation for other high-level user
interface objects throughout GIANT.

Essentially, the OpNav class serves as a container for both a :class:`.Camera` and
:class:`.ImageProcessing` instance, and then provides aliases (in the way of properties) to be able to access a few
of the attributes of these instances directly from the OpNav class instance.

Example
_______

In general, the OpNav class is not used directly in any setups.  Instead, it is used as the super class for other high
level user interface classes, such as :class:`.StellarOpNav` and :class:`.RelativeOpNav`.  For instance, say we want to
create a new high-level interface class called MyAwesomeNewOpNav.  If we subclass the OpNav class when creating this
new class then we automatically get a ``camera`` attribute, a ``image_processing`` attribute, and a few aliases to the
attributes of the camera and image processing instances

    >>> from giant.opnav_class import OpNav
    >>> from giant.camera import Camera
    >>> class MyAwesomeNewOpNav(OpNav):
    ...     def __init__(self, camera, image_processing, image_processing_kwargs):
    ...         super().__init__(camera, image_processing=image_processing,
    ...                          image_processing_kwargs=image_processing_kwargs)
    ...         self.new_attribute = 2
    ...
    >>> inst = MyAwesomeNewOpNav(Camera())
    >>> hasattr(inst, 'camera')
    True
    >>> hasattr(inst, 'image_processing')
    True
"""

from typing import Callable, Union, Iterable, Optional
import warnings

from giant._typing import ARRAY_LIKE_2D, PATH
from giant.image_processing import ImageProcessing
from giant.camera import Camera
from giant.camera_models import CameraModel


[docs]class OpNav: """ This serves as a container for :class:`.Camera` and :class:`.ImageProcessing` instances and provides aliases to quickly access their attributes from an instance of this class. This class is rarely used as is, and instead is used as a super class for new OpNav user interfaces. """ def __init__(self, camera: Camera, image_processing: Optional[ImageProcessing] = None, image_processing_kwargs: Optional[dict] = None): """ :param camera: An instance of :class:`.Camera` that is to have OpNav performed on it :param image_processing: An already initialized instance of :class:`.ImageProcessing` (or a subclass). If not ``None`` then ``image_processing_kwargs`` are ignored. :param image_processing_kwargs: The keyword arguments to pass to the :class:`.ImageProcessing` class constructor. These are ignored if argument ``image_processing`` is not ``None`` """ self._camera = None self.camera = camera if image_processing is None: if image_processing_kwargs is not None: self._image_processing = ImageProcessing(**image_processing_kwargs) else: self._image_processing = ImageProcessing() else: self._image_processing = image_processing # store the initial image processing key_word_arguments self._initial_image_processing_kwargs = image_processing_kwargs def __repr__(self) -> str: ip_dict = {} for key, value in self._image_processing.__dict__.items(): if not key.startswith("_"): ip_dict[key] = value return (self.__module__ + "." + self.__class__.__name__ + "(" + repr(self._camera) + ", image_processing_kwargs=" + str(ip_dict) + ")") def __str__(self) -> str: ip_dict = {} for key, value in self._image_processing.__dict__.items(): if isinstance(value, Callable): value = value.__module__ + "." + value.__name__ if not key.startswith("_"): ip_dict[key] = value return (self.__module__ + "." + self.__class__.__name__ + "(" + str(self._camera) + ", image_processing_kwargs=" + str(ip_dict) + ")") # ____________________________________________Camera Properties____________________________________________ @property def camera(self) -> Camera: """ The camera instance to perform OpNav on. This should be an instance of the :class:`.Camera` class or one of its subclasses. See the :class:`.Camera` class documentation for more details """ return self._camera @camera.setter def camera(self, val): if isinstance(val, Camera): self._camera = val else: warnings.warn("The camera should probably be an object that subclasses the Camera class\n" "We'll assume you know what you're doing for now but " "see the Camera documentation for details") self._camera = val
[docs] def add_images(self, data: Union[Iterable[Union[PATH, ARRAY_LIKE_2D]], PATH, ARRAY_LIKE_2D], parse_data: bool = True, preprocessor: bool = True): """ This method adds new images to be processed. Generally this is an alias to the :meth:`.Camera.add_images` method. In some implementations, however, this method adds some functionality to the original method as well. (such as in the :class:`.StellarOpNav` class) See :meth:`.Camera.add_images` for a description of the valid input for `data` :param data: The image data to be stored in the :attr:`.images` list :param parse_data: A flag to specify whether to attempt to parse the metadata automatically for the images :param preprocessor: A flag to specify whether to run the preprocessor after loading an image. """ self.camera.add_images(data, parse_data=parse_data, preprocessor=preprocessor)
@property def model(self) -> CameraModel: """ This alias returns the current camera model from the camera attribute. It is provided for convenience since the camera model is used frequently. """ return self._camera.model @model.setter def model(self, val: CameraModel): self._camera.model = val # ____________________________________________ImageProcessing Aliases____________________________________________ @property def image_processing(self) -> ImageProcessing: """ The ImageProcessing instance to use when doing image processing on the images This must be an instance of the :class:`.ImageProcessing` class. See the :class:`.ImageProcessing` class documentation for more details """ return self._image_processing @image_processing.setter def image_processing(self, val): if isinstance(val, ImageProcessing): self._image_processing = val else: warnings.warn("The image_processing object should probably subclass the ImageProcessing class\n" "We'll assume you know what you're doing for now, but " "see the ImageProcessing documentation for details") self._image_processing = val # ____________________________________________METHODS____________________________________________
[docs] def reset_image_processing(self): """ This method replaces the existing image processing instance with a new instance using the initial ``image_processing_kwargs`` argument passed to the constructor. A new instance of the object is created, therefore there is no backwards reference whatsoever to the state before a call to this method. """ if self._initial_image_processing_kwargs is not None: self._image_processing = ImageProcessing(**self._initial_image_processing_kwargs) else: self._image_processing = ImageProcessing()
[docs] def update_image_processing(self, image_processing_update: Optional[dict] = None): """ This method updates the attributes of the :attr:`image_processing` instance. See the :class:`.ImageProcessing` class for accepted attribute values. If a supplied attribute is not found in the :attr:`image_processing` attribute then this will print a warning and ignore the attribute. Any attributes that are not supplied are left alone. :param image_processing_update: A dictionary of attribute->value pairs to update the attributes of the :attr:`image_processing` attribute with. """ if image_processing_update is not None: for key, val in image_processing_update.items(): if hasattr(self._image_processing, key): setattr(self._image_processing, key, val) else: warnings.warn("The attribute {0} was not found.\n" "Cannot update ImageProcessing instance".format(key))