Source code for emva1288.report.report

import jinja2
import os
import shutil
from shutil import copytree
from collections import OrderedDict
import posixpath
from matplotlib.figure import Figure
from matplotlib.backends.backend_pdf import FigureCanvas
import numpy as np

from emva1288.process import Results1288
from emva1288.process.plotting import EVMA1288plots


[docs]def info_setup(**kwargs): """Container for setup information. All kwargs are used to update the setup information dictionary. Returns ------- dict : A dictionary containing setup informations. The keys are: - *'Light source'* : The light source type (e.g. integrating sphere). - *'Light source non uniformity'* : The light source introducing non uniformity. - *'Irradiation calibration accuracy'* : The irradiation calibration incertainty. - *'Irradiation measurement error'* : The irradiation measurement incertainty. - *'Standard version'* : The EMVA1288 standard version number used. """ s = OrderedDict() s['Light source'] = None s['Light source non uniformity'] = None s['Irradiation calibration accuracy'] = None s['Irradiation measurement error'] = None s['Standard version'] = None s.update(kwargs) return s
[docs]def info_basic(**kwargs): """Container for basic information. All kwargs are used to update the basic information dictionary for the report. Returns ------- dict : A dictionary containing basic informations for the report. The keys are: - *'vendor'* : The vendor name that manufactures the camera. - *'model'* : The model of the tested camera. - *'data_type'* : The label given to the data used for the test. - *'sensor_type'*: The type of the tested sensor within the camera. - *'sensor_diagonal'* : The number of pixel in the sensor diagonal. - *'lens_category'* : The lens category used for the test. - *'resolution'* : The camera's resolution. - *'pixel_size'* : The sensor's pixel size. - *'readout_type'* : The readout type of the sensor (for CCD sensors). - *'transfer_type'* : The transfer type of the sensor (for CCDs). - *'shutter_type'* : The shutter type of the sensor (for CMOS sensors). - *'overlap_capabilities'* : The overlap capabilities of the sensor (for CMOS sensors). - *'maximum_readout_rate'* : The camera's maximal readout rate. - *'dark_current_compensation'* : If the camera support dark current compensation, specify it in this entry. - *'interface_type'* : The camera's interface type. - *'qe_plot'* : The sensor's quantum efficency plots. """ b = {'vendor': None, 'model': None, 'data_type': None, 'sensor_type': None, 'sensor_diagonal': None, 'lens_category': None, 'resolution': None, 'pixel_size': None, ######### # For CCD 'readout_type': None, 'transfer_type': None, # For CMOS 'shutter_type': None, 'overlap_capabilities': None, ######### 'maximum_readout_rate': None, 'dark_current_compensation': None, 'interface_type': None, 'qe_plot': None } b.update(kwargs) return b
[docs]def info_marketing(**kwargs): """Container for marketing informations. All kwargs are used to update the returned dictionary containing the marketing informations. Returns ------- dict : A dictionary containing the marketing informations. The keys are: - *'logo'* : The path to the logo icon. - *'watermark'* : A text that will be printed on every page of the report in the background in transparent red. - *'missingplot'* : The path to a missing plot icon. - *'cover_page'* : The path to a custom cover page for the report. """ m = {'logo': None, 'watermark': None, 'missingplot': None, 'cover_page': None } m.update(kwargs) return m
[docs]def info_op(): """Container for operation points informations. The returned dictionary must be filled after calling this function. Returns ------- dict : An empty dictionary with the following keys: - *'name'* : The test name. - *'id'* : The test id. - *'summary_only'* : True or False, tells if, for a specific OP, the report should do a summary of the test instead of a full description. - *'results'* : The results of the test. - *'camera_settings'* : (OrderedDict) the dictionary of the camera's settings for the test. - *'test_parameters'* : (OrderedDict) the dictionary of the other test parameters. """ d = {'name': None, 'id': None, 'summary_only': None, 'results': None, 'camera_settings': OrderedDict(), 'test_parameters': OrderedDict()} return d
_CURRDIR = os.path.abspath(os.path.dirname(__file__))
[docs]class Report1288(object): """Class that has the purpose of creating a pdf report of one or more optical tests. This class only creates the report TeX files using the templates. TeX files must be compiled afterwards to generate the pdf files. """
[docs] def __init__(self, outdir, setup=None, basic=None, marketing=None, cover_page=False): """Report generator init method. Informations stored in the report can be specified by the kwargs passed to the object. It can be useful to use the :func:`info_marketing`, :func:`info_setup` and :func:`info_basic` functions to generate the corresponding dictionaries. The report generator uses `jinja2` to render the templates. Upon init, it calls the :meth:`template_renderer` method to get the `jinja2` object that will interact with the templates. Then it creates the output directories and files that will contain the output files. To create the report for different operating point/tests, one must call the :meth:`add` method to add each test he wants to publish in the report. Then, to conclude the report, he must call the :meth:`latex` method to generate the TeX files. Parameters ---------- outdir : str The path to the directory that will contain the report files. setup : dict, optional A dictionary containing the setup informations. If None, the report uses the dictionary of the :func:`info_setup` function. basic : dict, optional A dictionary containing basic informations about the test. If None, the report generator takes the dictionary from the :func:`info_basic` function. marketing : dict, optional A dictionary containing cover_page : str, optional The path to the cover page for the report. If False, no cover page will be included in the report. """ self._outdir = os.path.abspath(outdir) self.renderer = self.template_renderer() self.ops = [] self.marketing = marketing or info_marketing() self.basic = basic or info_basic() self.setup = setup or info_setup() self.cover_page = cover_page self._make_dirs(outdir)
[docs] @staticmethod def template_renderer(dirname=None): """Method that creates the renderer for the TeX report file. Uses the :class:`jinja2:jinja2.Environment` object to create the renderer. Also defines some filters for the environment for the missing numbers and general missings. Parameters ---------- dirname : str, optional The path to the template directory containing the TeX templates. If None, it will get the templates from the `./templates/` directory. Returns ------- The renderer. """ if not dirname: dirname = os.path.join(_CURRDIR, 'templates') renderer = jinja2.Environment( block_start_string='%{', block_end_string='%}', variable_start_string='%{{', variable_end_string='%}}', comment_start_string='%{#', comment_end_string='%#}', loader=jinja2.FileSystemLoader(dirname)) def missingnumber(value, precision): # Filter for missing numbers if value in (None, np.nan): return '-' t = '{:.%df}' % precision return t.format(value) def missingfilter(value, default='-'): # General filter for missing objects that are not numbers if value in (None, np.nan): return default return value renderer.filters['missing'] = missingfilter renderer.filters['missingnumber'] = missingnumber return renderer
def _make_dirs(self, outdir): """Create the directory structure for the report""" try: os.makedirs(self._outdir) except FileExistsError: # pragma: no cover pass print('Output Dir: ', self._outdir) files_dir = os.path.join(self._outdir, 'files') currfiles = os.path.join(_CURRDIR, 'files') try: copytree(currfiles, files_dir) except FileExistsError: # pragma: no cover pass upload_dir = os.path.join(self._outdir, 'upload') try: os.makedirs(upload_dir) except FileExistsError: # pragma: no cover pass def uploaded_file(fname, default): if fname: # pragma: no cover shutil.copy(os.path.abspath(fname), upload_dir) v = posixpath.join( 'upload', os.path.basename(fname)) else: v = posixpath.join('files', default) return v self.marketing['logo'] = uploaded_file(self.marketing['logo'], 'missinglogo.pdf') self.marketing['missingplot'] = uploaded_file( self.marketing['missingplot'], 'missingplot.pdf') self.basic['qe_plot'] = uploaded_file(self.basic['qe_plot'], 'missingplot.pdf') def _write_file(self, name, content): # write content into a file fname = os.path.join(self._outdir, name) with open(fname, 'w') as f: f.write(content) return fname def _stylesheet(self): # generate the stylesheet content stylesheet = self.renderer.get_template('emvadatasheet.sty') return stylesheet.render(marketing=self.marketing, basic=self.basic) def _report(self): # Generate the report contents report = self.renderer.get_template('report.tex') return report.render(marketing=self.marketing, basic=self.basic, setup=self.setup, operation_points=self.ops, cover_page=self.cover_page)
[docs] def latex(self): """Generate report latex files. """ self._write_file('emvadatasheet.sty', self._stylesheet()) self._write_file('report.tex', self._report())
def _results(self, data): return Results1288(data) def _plots(self, results, id_): """Create the plots for the report. The report will include all the plots contained in the `~emva1288.process.plotting.EVMA1288plots` list. All plots will be saved in pdf format in the output directory. """ names = {} savedir = os.path.join(self._outdir, id_) try: os.mkdir(savedir) except FileExistsError: # pragma: no cover pass for plt_cls in EVMA1288plots: figure = Figure() _canvas = FigureCanvas(figure) plot = plt_cls(figure) plot.plot(results) plot.rearrange() fname = plt_cls.__name__ + '.pdf' figure.savefig(os.path.join(savedir, fname)) names[plt_cls.__name__] = posixpath.join(id_, fname) return names
[docs] def add(self, op, data, results=None): """Method that adds an operation point to the report. The data supplied are passed through a :class:`~emva1288.process.results.Results1288` object to be processed for the report. Also creates the plots which will appears in the report. Parameters ---------- op : dict The dictionary containing the operation point informations. This dictionary must absolutely contain a 'name' key. See the :func:`info_op` function to get an idea to what keys to give. data : dict The corresponding operation point data. It must be able to be processed by an instance of the :class:`~emva1288.process.results.Results1288` class. """ n = len(self.ops) + 1 op['id'] = 'OP%d' % (n) if not op['name']: op['name'] = op['id'] if not results: results = self._results(data) op['results'] = results.results_by_section results.id = n op['plots'] = self._plots(results, op['id']) self.ops.append(op)