Source code for pyimcom.diagnostics.report

"""
PyIMCOM diagnostic report base classes.

Classes
-------
ReportSection
    Section of the validataion report.
ValidationReport
    An entire validation report, encompassing multiple sections.

"""

import os
import subprocess
import warnings
from datetime import datetime

import numpy as np

from ..compress.compressutils import ReadFile
from ..config import Config, Settings


[docs] class ReportSection: """ Contains a report section. Parameters ---------- rpt : pyimcom.diagnostics.report.ValidationReport The report that this section belongs to. Attributes ---------- tex : str LaTeX source. data : str Machine readable data block, to be embedded in the report. result : str Result of test (N/A for not completed). Methods ------- __init__ Constructor infile Construct the input file name for a given block. build Build this report section. Notes ----- The build method can be redefined in inherited classes, but should keep the signature. The optional nblockmax is used to restrict to a maximum size nblockmax x nblockmax sub-mosaic (this is mostly used for testing if you want to see if your section compiles without waiting for everything to run). """
[docs] def __init__(self, rpt):
[docs] self.stem = rpt.stem
[docs] self.fnsuffix = rpt.fnsuffix
[docs] self.LegacyName = rpt.LegacyName
[docs] self.cfg = rpt.cfg
[docs] self.dstem = rpt.dstem
[docs] self.datadir = rpt.datadir
[docs] self.datastem = rpt.datastem
[docs] self.datastem_from_dir = rpt.datastem_from_dir
[docs] self.tex = "\n" + "%" * 72 + "\n"
[docs] self.data = ""
[docs] self.result = "N/A"
[docs] def infile(self, in_x, in_y): """ Get the input file name for a given block. Parameters ---------- in_x : int Horizontal position of the block. in_y : int Vertical position of the block. Returns ------- str File name. """ if in_x >= 0 and in_x < self.cfg.nblock and in_y >= 0 and in_y < self.cfg.nblock: return self.stem + f"_{in_x:02d}_{in_y:02d}" + self.fnsuffix else: raise ValueError("pyimcom.validation.report.ReportSection: infile: block selection out of range")
[docs] def build(self, nblockmax=100): """ Builds report section. Intended to be overwritten. Parameters ---------- nblockmax : int, optional Maximum block size allowed. Returns ------- None """ self.tex += "\\section{Base class section}\nHello world.\n" self.data += "HI, I AM A BOT. HOORAY!\n"
[docs] class ValidationReport: """ Contains the validation report. Parameters ---------- fname : str Block file name. dstem : str Stem for output files. clear_all : bool, optional Removes existing files. Attributes ---------- tex : dict of str LaTeX source, dictionary with keys 'preamble', 'head', 'body', 'appendix', and 'end'. stem: str Stem for the input files. suffix : str Suffix for the input files. LegacyName : bool Legacy naming convention for the input files with _map.fits? Should be False in the future. cfg : pyimcom.config.Config The configuration used to generate the mosaic. dstem : str Stem for output files. datadir : str Directory for associated data files. datastem : str Stem for associated datafiles. datastem_from_dir : str Stem for datafiles, local from compilation directory (for inclusion in LaTeX). Methods ------- __init__ Constructor. addsections Add sections to the report. texout Generate LaTeX output. writeto Writes to a file. compile Compile the LaTeX source into a PDF. """
[docs] def __init__(self, fname, dstem, clear_all=False): """Constructor.""" with ReadFile(fname) as f: c = f["CONFIG"].data["text"] n = len(c) cf = "" for j in range(n): cf += c[j] + "\n" self.nlayer = np.shape(f["PRIMARY"].data)[-3] self.im_dtype = f["PRIMARY"].data.dtype
[docs] self.cfg = Config(cf)
[docs] self.dstem = dstem
# if the data directory doesn't exist yet, make it
[docs] self.datadir = dstem + "_data"
if not os.path.exists(self.datadir): os.mkdir(self.datadir) head, tail = os.path.split(self.dstem)
[docs] self.datastem = self.datadir + "/" + tail
[docs] self.datastem_from_dir = tail + "_data/" + tail
# ... and remove files if desired if clear_all: rmlist = [] n = len(tail) for rfname in os.listdir(head): if rfname[: n + 1] == tail + "_" and not os.path.isdir(head + "/" + rfname): rmlist.append(head + "/" + rfname) for rfname in os.listdir(self.datadir): if rfname[: n + 1] == tail + "_" and not os.path.isdir(self.datadir + "/" + rfname): rmlist.append(self.datadir + "/" + rfname) print("Removing:", rmlist) for rf in rmlist: os.remove(rf) # get the file coordinates
[docs] self.LegacyName = False
[docs] self.stem = fname[:-11]
[docs] self.fnsuffix = ".fits"
if fname[-9:] == "_map.fits": self.LegacyName = True self.stem = fname[:-15] self.fnsuffix = "_map.fits" if fname[-12:] == ".cpr.fits.gz": # compressed option self.stem = fname[:-18] self.fnsuffix = ".cpr.fits.gz" # LaTeX skeleton
[docs] self.tex = { "preamble": "\\documentclass[11pt]{article}\n", "head": "\\begin{document}\n\\title{IMCOM Validation report}\n\\date{" + datetime.now().strftime("%B %d, %Y") + "}\n", "body": "\n", "appendix": "\\appendix\n\n", "end": "\\end{document}\n", }
# put in margins self.tex["preamble"] += "\\setlength{\\hoffset}{0pt}\n" self.tex["preamble"] += "\\setlength{\\voffset}{0pt}\n" self.tex["preamble"] += "\\setlength{\\topmargin}{-23pt}\n" self.tex["preamble"] += "\\setlength{\\headheight}{12pt}\n" self.tex["preamble"] += "\\setlength{\\headsep}{23pt}\n" self.tex["preamble"] += "\\setlength{\\oddsidemargin}{0pt}\n" self.tex["preamble"] += "\\setlength{\\textheight}{648pt}\n" self.tex["preamble"] += "\\setlength{\\textwidth}{468pt}\n" # packages self.tex["preamble"] += "\\usepackage{graphicx}\n" self.tex["preamble"] += "\\usepackage{rotating}\n" # put in title & summary self.tex["head"] += "\\maketitle\n\\tableofcontents\n" self.tex["head"] += "\n\\section{Summary}\n" self.tex["head"] += ( "\nThis is a report on the IMCOM run in " + Settings.RomanFilters[self.cfg.use_filter] + " band centered at:\n" ) self.tex["head"] += ( "\\begin{verbatim}RA = " + f"{self.cfg.ra:8.4f}" + " DEC = " + f"{self.cfg.dec:8.4f}" + " LONPOLE = " + f"{self.cfg.lonpole:8.4f}" + "\\end{verbatim}\n" ) self.tex["head"] += "The tests returned the following results.\n\n" # appendix on configuration file self.tex[ "appendix" ] += "\\section{Configuration file}\n\\label{app:config}\n{\\scriptsize\n\\begin{verbatim}\n" self.tex["appendix"] += self.cfg.to_file(None) self.tex["appendix"] += "\\end{verbatim}}\n\n"
[docs] def addsections(self, sectionlist): """ Add the list of sections to the report. Parameters ---------- sectionlist : list of pyimcom.diagnostics.report.ReportSection Sections to add to the report. Returns ------- None """ for section in sectionlist: # header section thisresult = f"{type(section).__name__[:16]:16s}" + ":" + section.result usechar = None for xchar in ["+", "`", "|", "@", "$", "*"]: if xchar not in thisresult: usechar = xchar if usechar is not None: self.tex["head"] += "\\noindent\\begin{verbatim}\n" + thisresult + "\\end{verbatim}" # '\\noindent\\verb' + usechar + thisresult + usechar + '\n' # '\\noindent\\begin{verbatim}\n' + thisresult + '\n\\end{verbatim}\n' # else: raise Exception("Can't print" + thisresult + "in LaTeX verbatim mode") self.tex["head"] += "\n" # body sections self.tex["body"] += "\n" + section.tex + "\n" self.tex["body"] += "\\begin{verbatim}\n$$$START " + type(section).__name__ + "\n" self.tex["body"] += section.data self.tex["body"] += "\n$$$END " + type(section).__name__ + "\n\\end{verbatim}\n"
[docs] def texout(self): """Returns the entire LaTeX file as one string.""" return ( self.tex["preamble"] + self.tex["head"] + self.tex["body"] + self.tex["appendix"] + self.tex["end"] )
[docs] def writeto(self): """Write the LaTeX file.""" # clear logfiles here for ending in ["aux", "log", "toc"]: fn = self.dstem + "_main." + ending if os.path.exists(fn): os.remove(fn) with open(self.dstem + "_main.tex", "w") as f: f.write(self.texout())
[docs] def compile(self, ntimes=2, warn_pdf_err=False): """ Compile the LaTeX into a PDF. Parameters ---------- ntimes : int, optional Number of times to compile (may have to run twice to get all the references). warn_pdf_err : bool, optional If True, compilation failures result in a warning. If False (default), result in an exception. Returns ------- None """ self.writeto() pwd = os.getcwd() head, tail = os.path.split(self.dstem) os.chdir(head) for _ in range(ntimes): try: print("compiling from", os.getcwd()) self.compileproc = subprocess.run( ["pdflatex", "-interaction=nonstopmode", tail + "_main.tex"], capture_output=True ) except subprocess.CalledProcessError as exc: if warn_pdf_err: warnings.warn("ERROR *** LaTeX failed to compile! ***\n") else: raise RuntimeError("LaTeX failed to compile.") from exc os.chdir(pwd)