Source code for prose.core.block

import inspect
from time import time
from typing import Union

from prose.console_utils import warning
from prose.core.image import Buffer, Image

import contextlib


@contextlib.contextmanager
def _exception_context(msg):
    try:
        yield
    except Exception as ex:
        if ex.args:
            msg = f"[{msg}] {ex.args[0]}"
        else:
            str(msg)
        ex.args = (msg,) + ex.args[1:]
        raise


[docs]class Block(object): """Single unit of processing acting on the :py:class:`~prose.Image` object Reading, processing and writing :py:class:`~prose.Image` attributes. When placed in a sequence, it goes through two steps: 1. :py:meth:`~prose.Block.run` on each image fed to the :py:class:`~prose.Sequence` 2. :py:meth:`~prose.Block.terminate` called after the :py:class:`~prose.Sequence` is terminated Parameters ---------- name : str, optional name of the block, by default None size: int, optional number of images processed by the block, by default 1 All prose blocks must be child of this parent class """
[docs] def __init__(self, name=None, verbose=False, size=1, read=None): assert size % 2 == 1, "block size must be odd" _name = self.__class__.__name__ _issue = f"https://github.com/lgrcia/prose/issues/new?title=Missing+doc+for+{_name}&body=Documentation+is+missing+for+block+{_name}" self.__doc__ = f"[**click to ask for documentation**]({_issue})" self.name = name self.unit_data = None self.processing_time = 0 self.runs = 0 self.in_sequence = False self.verbose = verbose self._data_block = False self.size = size if read is not None: assert isinstance(read, list), "require must be a list" self.read = read else: self.read = []
@property def args(self): return self._args def _check_require(self, image): for _require in self.read: if _require == "sources": if len(image.sources) == 0: raise AttributeError(f"Image must have sources (0 found)") elif _require == "wcs": if not image.plate_solved: raise AttributeError(f"Image must have valid WCS") if not hasattr(image, _require) and not hasattr(image.computed, _require): raise AttributeError( f"[{self.__class__.__name__}] Image must have attribute '{_require}'" ) def _run(self, buffer): t0 = time() if isinstance(buffer, Buffer): image = buffer[0] if self.size == 1 else buffer elif isinstance(buffer, Image): image = buffer else: raise ValueError("block must be run on a Buffer or an Image") with _exception_context(self.__class__.__name__): self._check_require(image) self.run(image) self.processing_time += time() - t0 self.runs += 1
[docs] def run(self, image: Image): """Running on a image (must be overwritten when subclassed) Parameters ---------- image : prose.Image image to be processed """ raise NotImplementedError()
[docs] def terminate(self): """Method called after block's :py:class:`~prose.Sequence` is finished (if any)""" pass
def _terminate(self): with _exception_context(self.__class__.__name__): self.terminate() @property def citations(self) -> list: """ Returns a string listing the packages used by the block. Returns ------- str A string listing the packages used by the block. """ return ["astropy", "prose", "numpy", "scipy"] @staticmethod def _doc(): return "" def __call__(self, image): image_copy = image.copy() self._run(image_copy) if image_copy.discard: warning(f"{self.__class__.__name__} discarded Image") return image_copy
# rewrite the tested function to accept Block instances as well as strings # requires pytest and is not used in the docs for now def is_tested(block_class: Union[Block, str]) -> bool: """Check if a block is tested Parameters ---------- block_class : Block or str block to be tested Returns ------- bool True if the block is tested, False otherwise Raises ------ TypeError if block is not a Block subclass or a string """ import pytest if isinstance(block_class, str): block_name = block_class elif issubclass(block_class, Block): block_name = block_class.__name__ else: raise TypeError(f"block must be a Block subclass or a string") result = pytest.main(["-q", "-k", block_name]) return result == 0