Source code for fibomat.sample

"""Provides the :class:`Sample` class."""
from fibomat.linalg.boundingboxes import boundingbox
from fibomat.linalg.boundingboxes.dim_boundingbox import DimBoundingBox
from typing import Optional, List, Union, TypeVar, Type, Set
import re
import dataclasses

from fibomat.site import Site
from fibomat.linalg import DimVectorLike
from fibomat.backend import BackendBase, registry
from fibomat.utils import PathLike
from fibomat.shapes import DimShape
from fibomat.pattern import Pattern
from fibomat.layout import LayoutBase
from fibomat.default_backends import BokehBackend, StubRasterStyle
from fibomat.describable import Describable


@dataclasses.dataclass(frozen=True)
class _Annotation:
    dim_shape: DimShape
    filled: bool
    color: Optional[str]
    description: Optional[str]


BackendType = TypeVar('BackendType')


[docs]class Sample(Describable): """ This class is the glueing between all subcomponents of the library. All shapes and their milling settings are added to this class and can be exported with the help of registered backends . """
[docs] def __init__(self, *, description: Optional[str] = None): """ Args: description (str, optional): Optional description of the project, default to None """ super().__init__(description) self._sites: List[Site] = [] self._annotations: List[_Annotation] = []
[docs] def create_site( self, dim_position: DimVectorLike, dim_fov: Optional[DimVectorLike] = None, description: Optional[str] = None ) -> Site: """ Creates and Site in-place (hence, the Site is automatically added to the sample). Patterns can be added to the returned object. See :class:`fibomat.site.Site.__init__` for argument description. Returns: Site """ new_site: Site = Site(dim_position, dim_fov, description=description) self._sites.append(new_site) return new_site
[docs] def add_site(self, site_like: Site) -> None: """ Adds a Site to the project. Alternatively, the '+=' operator can be used. Args: site_like (Site): new site Returns: None """ if isinstance(site_like, LayoutBase): for site_ in site_like.layout_elements(): self._sites.append(site_) else: self._sites.append(site_like)
@property def number_of_sites(self) -> int: """int: number of added sites""" return len(self._sites) @property def bounding_box(self) -> Optional[DimBoundingBox]: if not self._sites: return None else: bbox = self._sites[0].bounding_box for site in self._sites[1:]: bbox = bbox.extended(site.bounding_box) return bbox def __iadd__(self, site_like): """See :meth:`~Sample.add_site`.""" self.add_site(site_like) return self @staticmethod def _export( backend_class: Type[BackendType], sites: Union[Site, List[Site]], descr_pattern: Optional[Set[str]] = None, **kwargs ) -> BackendType: def _matches(description_) -> bool: for pattern in descr_pattern: if re.match(pattern, description_): return True return False exporter: Type[BackendBase] = backend_class(**kwargs) if isinstance(sites, Site): if descr_pattern: print('Warning: ignoring descriptions in _export for single site export.') exporter.process_site(sites) else: for site_ in sites: if descr_pattern: description = site_.description if description and _matches(description): exporter.process_site(site_) # descriptions.remove(description) else: exporter.process_site(site_) # if descriptions: # raise RuntimeError(f'Could not find sites with descriptions: {descriptions} in sample') return exporter
[docs] def plot(self, show: bool = True, filename: Optional[PathLike] = None, descr_pattern: Optional[Set[str]] = None, **kwargs) -> BokehBackend: """ Plots and save the project using the :class:`~fibomat.default_backends.bokeh_backend.BokehBackend`. Args: show (bool): if true, the plot is opened in a browser automatically filename (PathLike, optional): if filename is given, the plot is saved in this file. The file suffix should be `*.htm` or `*.html`, default to None `**kwargs`: parameters for the bokeh backend. These are directly passed to the __init__ method of the BokehBackend class. The title parameter is automatically set to the :attr:`Sample.description` Returns: None """ plotter: BokehBackend = self._export(BokehBackend, self._sites, descr_pattern=descr_pattern, title=self._description, **kwargs) for annot in self._annotations: raster = StubRasterStyle(2) if annot.filled else StubRasterStyle(1) plotter.process_pattern(Pattern( annot.dim_shape, None, raster, _annotation=True, _color=annot.color, description=annot.description )) plotter.plot() if filename: plotter.save(filename) if show: plotter.show() return plotter
[docs] def export(self, exp_backend: Union[str, Type[BackendBase]], **kwargs) -> BackendBase: """ Exports the project. Note that the method returns the backend object so you will be able to save a file or show a plot. See backends example nd docs for details. .. note:: The export method does not save any files on its one. This must be done by the user manually. See docs of the used backend for details. Args: exp_backend (str or Type[backend.BackendBase]): name of the backend or class. The backend must be registered before. **kwargs: optional arguments are passed to the backend's __init__ method Returns: BackendBase """ if isinstance(exp_backend, str): exp_backend = registry.get(exp_backend) return self._export(exp_backend, self._sites, description=self._description, **kwargs)
[docs] def export_multi(self, exp_backend: Union[str, Type[BackendBase]], **kwargs) -> List[BackendBase]: """ Similar to :meth:`Project.export` but for each :class:`fibomat.site.Site` an individual backend instance is returned. This can be usefull if multiple sites are used within fibomat but the pattern system only supports one site at a time. Returns: List[BackendBase] """ backends: List[BackendBase] = [] if isinstance(exp_backend, str): exp_backend = registry.get(exp_backend) for added_site in self._sites: backends.append(self._export(exp_backend, added_site, description=self._description, **kwargs)) return backends
[docs] def export_with_description( self, exp_backend: Union[str, Type[BackendBase]], descr_pattern: Set[str], **kwargs ) -> BackendBase: """ Exports the project. Note that the method returns the backend object so you will be able to save a file or show a plot. See backends example nd docs for details. Only sites with a description given in `descriptions` will be exported. An exception is raised, if no site is found with a given description. .. note:: The export method does not save any files on its one. This must be done by the user manually. See docs of the used backend for details. Args: exp_backend (str or Type[backend.BackendBase]): name of the backend or class. The backend must be registered before. descr_pattern (Set[str]): a set of descriptions of the sites which should be exported. **kwargs: optional arguments are passed to the backend's __init__ method Returns: BackendBase """ if isinstance(exp_backend, str): exp_backend = registry.get(exp_backend) return self._export(exp_backend, self._sites, description=self._description, descr_pattern=descr_pattern, **kwargs)
[docs] def add_annotation( self, dim_shape: DimShape, filled: bool = False, color: Optional[str] = None, description: Optional[str] = None ) -> None: """ Add `dim_shape` to a annotation layer. This layer is only used to visualize extra shapes and is ignored by the exporting backend. Args: dim_shape (DimShape): shape filled (bool): If True, shape is plotted filled (only possible if shape is closed) color (str, Optional): a color bokeh can understand, default to None description (str, optional): description, default to None Returns: None """ self._annotations.append(_Annotation(dim_shape=dim_shape, filled=filled, color=color, description=description))