from typing import Optional, Dict, Any, Callable
import collections
import datetime
import configparser
import numpy as np
from fibomat.backend import BackendBase
from fibomat import site, units, pattern, shapes, utils
from fibomat.linalg import BoundingBox
from fibomat.pattern import Pattern
from fibomat.rasterizedpattern import RasterizedPattern
def _default_save_impl(filename: utils.PathLike, dwell_points: np.ndarray, parameters: Dict[str, Any]):
# if parameters['base_dwell_time']:
# dwell_points[:, 2] /= parameters['base_dwell_time']
header = configparser.ConfigParser()
header_dict = collections.OrderedDict()
header_dict['length_unit'] = f'{parameters["length_unit"]:~P}'
header_dict['time_unit'] = f'{parameters["time_unit"]:~P}'
header_dict['fov'] = f'{parameters["fov"].width}, {parameters["fov"].height}'
header_dict['center'] = f'{tuple(parameters["fov"].center)}'
header_dict['base_dwell_time'] = f'{parameters["base_dwell_time"]}'
header_dict['total_dwell_time'] = f'{parameters["total_dwell_time"]}'
header_dict['number_of_points'] = f'{parameters["number_of_points"]}'
header_dict['description'] = f'{parameters["description"]}'
header_dict['time_stamp'] = f'{parameters["time_stamp"]}'
header['Info'] = header_dict
with open(filename, 'w') as fp:
header.write(fp)
fp.write('[Points]\n')
np.savetxt(fp, dwell_points, "%.5f %.5f %d")
[docs]class SpotListBackend(BackendBase):
name: str = 'spotlist'
[docs] def __init__(
self,
#cformat_template: Optional[Tuple[str, jinja2.Template]] = None,
save_impl: Callable = None,
base_dwell_time: Optional[units.TimeQuantity] = None,
length_unit: Optional[units.LengthUnit] = None,
time_unit: Optional[units.TimeUnit] = None,
description: Optional[str] = None,
**kwargs
):
"""Spotlist backend.
This backend rasterize all patterns and is able to output list of dwell points in the form of ::
x1, y1, t1
x2, y2, t2
...
The exact formatting can be controlled by a custom formatting function.
The formatting function (called save_impl in class parameters) must have the following form ::
def func(filename: utils.PathLike, dwell_points: np.ndarray, parameters: Dict[str, Any]):
# open the file `filename` and save dwell_points there
...
The parameter dictionary holds further metadata. For all available keys, see `_default_save_impl` in the
sourcefile of the spotlist backend.
Args:
save_impl (Callable, optional): custom formatting function.
base_dwell_time (TimeQuantity, optional):
if given, the dwell times are autoamtically expressed as integer multiplicands of base_dwell_time.
length_unit (LengthQuantity, optional): length unit of points.
time_unit (TimeQuantity, optional): time unit of dwell times.
description (str, optional): description.
**kwargs:
"""
super().__init__(**kwargs)
# if format_template:
# if isinstance(format_template, str):
# self._format_template = jinja2.Template(format_template, undefined=jinja2.StrictUndefined)
# elif isinstance(format_template, jinja2.Template):
# self._format_template = format_template
# # self._format_template['undefined'] = jinja2.StrictUndefined
# else:
# raise TypeError('file_format must be `str` or `file_format`')
# else:
# self._format_template = default_format_template
if save_impl:
self._save_impl = save_impl
else:
self._save_impl = _default_save_impl
self._length_unit = length_unit if length_unit else units.U_('µm')
self._time_unit = time_unit if time_unit else units.U_('µs')
if base_dwell_time:
self._base_dwell_time = base_dwell_time # units.scale_to(self._time_unit, base_dwell_time)
else:
self._base_dwell_time = None
self._description = description
self._fov: Optional = None
self._site_count: int = 0
self._site_pos: Optional = None
self._dwell_points = []
[docs] def process_site(self, new_site: site.Site):
self._site_pos = new_site.center.vector_as(self._length_unit)
self._site_count += 1
super().process_site(new_site)
[docs] def save(self, filename: utils.PathLike) -> None:
if self._site_count > 1:
print('Warning: adding merging multiple sites.')
dwell_points = np.concatenate(self._dwell_points)
if self._base_dwell_time:
dwell_points[:, 2] /= units.scale_to(self._time_unit, self._base_dwell_time)
bbox = BoundingBox.from_points(dwell_points[:, :2])
self._save_impl(
filename, dwell_points,
{
'length_unit': self._length_unit, 'time_unit': self._time_unit,
'fov': bbox, 'base_dwell_time': self._base_dwell_time, 'total_dwell_time': np.sum(dwell_points[:, 2]),
'number_of_points': len(dwell_points), 'description': self._description,
'time_stamp': str(datetime.datetime.now()),
}
)
def _rasterize_and_add(self, ptn: pattern.Pattern[shapes.Shape]):
dwell_points = np.array(ptn.raster_style.rasterize(
ptn.dim_shape, ptn.mill, self._length_unit, self._time_unit
).dwell_points)
# dwell_points[:, 2] *= units.scale_to(self._time_unit, ptn.mill.dwell_time)
dwell_points[:, :2] += self._site_pos
self._dwell_points.append(dwell_points)
[docs] def arc_spline(self, ptn: pattern.Pattern[shapes.ArcSpline]) -> None:
self._rasterize_and_add(ptn)
[docs] def rasterized_points(self, ptn: pattern.Pattern[shapes.RasterizedPoints]):
self._rasterize_and_add(ptn)
[docs] def rasterized_pattern(self, ptn: pattern.Pattern[RasterizedPattern]):
self._rasterize_and_add(ptn)
[docs] def spot(self, ptn: pattern.Pattern[shapes.Spot]) -> None:
self._rasterize_and_add(ptn)
[docs] def line(self, ptn: pattern.Pattern[shapes.Line]) -> None:
self._rasterize_and_add(ptn)
[docs] def polyline(self, ptn: pattern.Pattern[shapes.Polyline]) -> None:
self._rasterize_and_add(ptn)
[docs] def polygon(self, ptn: pattern.Pattern[shapes.Polygon]) -> None:
self._rasterize_and_add(ptn)
[docs] def arc(self, ptn: pattern.Pattern[shapes.Arc]) -> None:
self._rasterize_and_add(ptn)
[docs] def circle(self, ptn: pattern.Pattern[shapes.Circle]) -> None:
self._rasterize_and_add(ptn)
[docs] def ellipse(self, ptn: pattern.Pattern[shapes.Ellipse]) -> None:
self._rasterize_and_add(ptn)
[docs] def rect(self, ptn: pattern.Pattern[shapes.Rect]) -> None:
self._rasterize_and_add(ptn)