diff --git a/apsuite/commisslib/meas_ac_orm.py b/apsuite/commisslib/meas_ac_orm.py index 6a4bc89d..ac24857a 100644 --- a/apsuite/commisslib/meas_ac_orm.py +++ b/apsuite/commisslib/meas_ac_orm.py @@ -1,9 +1,12 @@ """Main module.""" +import datetime import operator as _opr import time as _time from copy import deepcopy as _dcopy from functools import reduce as _red +from fpdf import FPDF +from pathlib import Path from threading import Thread as _Thread import matplotlib.pyplot as _mplt @@ -713,7 +716,9 @@ def check_measurement_quality(self, mat_ac=None, mat_dc=None, dthres=1e-3): cond_ok &= (corr_rf[0] < dthres).all() return cond_ok - def plot_comparison_correlations(self, mat_ac=None, mat_dc=None): + def plot_comparison_correlations( + self, mat_ac=None, mat_dc=None, show_fig=True + ): """Plot comparison of measured response matrix with reference respmat. Two graphics will be made, one comparing the column space of both @@ -788,11 +793,12 @@ def plot_comparison_correlations(self, mat_ac=None, mat_dc=None): ) fig.tight_layout() - fig.show() + if show_fig: + fig.show() return fig, (ax, ay), corr def plot_comparison_single_corrector( - self, corr_idx, mat_ac=None, mat_dc=None + self, corr_idx, mat_ac=None, mat_dc=None, show_fig=True ): """Plot single corrector signatures of measured and reference respmat. @@ -835,10 +841,11 @@ def plot_comparison_single_corrector( ay.set_ylabel('Vertical ' + unit) ay.set_xlabel('BPM position') fig.tight_layout() - fig.show() + if show_fig: + fig.show() return fig, (ax, ay) - def plot_scale_conversion_factors(self): + def plot_scale_conversion_factors(self, show_fig=True): """Plot single corrector signatures of measured and reference respmat. Returns: @@ -860,10 +867,13 @@ def plot_scale_conversion_factors(self): ax.set_ylabel('Relative Factor') ax.set_xlabel('Correctors Position [m]') fig.tight_layout() - fig.show() + if show_fig: + fig.show() return fig, ax - def plot_phases_vs_amplitudes(self, title='', corrsidx2highlight=None): + def plot_phases_vs_amplitudes( + self, title='', corrsidx2highlight=None, show_fig=True + ): """.""" fig = _mplt.figure(figsize=(8, 6)) gs = _mplt.GridSpec( @@ -926,11 +936,11 @@ def plot_phases_vs_amplitudes(self, title='', corrsidx2highlight=None): avx.set_xlabel(r'Amplitudes [$\mu$m]') ahx.set_ylabel(r'Phases [$\pi$]') avy.set_ylabel(r'Phases [$\pi$]') - - fig.show() + if show_fig: + fig.show() return fig, ((ahx, ahy), (avy, avx)) - def plot_phases_histogram(self, title=''): + def plot_phases_histogram(self, title='', show_fig=True): """.""" fig = _mplt.figure(figsize=(8, 6)) gs = _mplt.GridSpec( @@ -980,11 +990,11 @@ def plot_phases_histogram(self, title=''): avy.set_ylabel(r'Counts') avx.set_xlabel(r'Phases [$\pi$]') avy.set_xlabel(r'Phases [$\pi$]') - - fig.show() + if show_fig: + fig.show() return fig, ((ahx, ahy), (avy, avx)) - def plot_bpms_fluctuations(self): + def plot_bpms_fluctuations(self, show_fig=True): """Plot BPMs flutuations statistics along BPMs and Correctors. Returns: @@ -1035,11 +1045,12 @@ def plot_bpms_fluctuations(self): ay.set_title('BPMs Variation along BPMs') fig.tight_layout() - fig.show() + if show_fig: + fig.show() return fig, (ax, ay) def plot_orbit_residue_after_fitting( - self, bpm_idx=0, excit_idx=0, time_domain=True + self, bpm_idx=0, excit_idx=0, time_domain=True, show_fig=True ): """Plot orbit residue after fitting. @@ -1130,7 +1141,8 @@ def plot_orbit_residue_after_fitting( ncol=4, ) fig.tight_layout() - fig.show() + if show_fig: + fig.show() return fig, (ax, ay), orbx, orby, dorbx, dorby # ------------------ Auxiliary Methods ------------------ @@ -2366,3 +2378,258 @@ def _wait_cycle_to_finish(self, corr_names=None, timeout=10): def _shift_list(lst, num): """.""" return lst[-num:] + lst[:-num] + + # ----------------- ORM report Class ---------------------------------- + + +class ORMReport(FPDF): + """.""" + + # TODO: change labels "ac mat" or "dc mat" in the analysis plots + # use "measured mat" and "reference mat" instead + # adjust figures sizing + TIME_FMT = '%Y-%m-%d %H:%M:%S' + + def _get_cnpem_logo_path(self): + import apsuite + + return str( + Path(apsuite.__file__).parent / 'resources/cnpem_lnls_logo.jpg' + ) + + def __init__(self, meas_orm=None): + """.""" + super().__init__() + self.WIDTH = 210 + self.HEIGHT = 297 + + self.meas_orm = meas_orm + self.params = None + self.data = None + + self._folder = '' + + def header(self): + """.""" + path = self._get_cnpem_logo_path() + self.image(path, x=10, y=6, w=40, h=15) + self.set_font('Arial', 'B', 14) + self.cell(0, 6, 'SIRIUS AC ORM Report', 0, 0, 'C') + self.set_font('Arial', '', 11) + now = datetime.datetime.now() + stg = '{:s}'.format(now.strftime(self.TIME_FMT)) + self.cell(0, 6, stg, 0, 0, 'R') + self.ln(10) + + def footer(self): + """.""" + self.set_y(-15) + self.set_font('Arial', 'I', 8) + self.set_text_color(129) + self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C') + + def page_title(self, title, loc_y=None): + """.""" + self.set_font('Arial', '', 12) + self.set_fill_color(215) + if loc_y is not None: + self.set_y(loc_y) + self.cell(0, 6, f'{title:s}', 0, 1, 'C', 1) + self.ln(2) + + def meas_fingerprint(self): + """.""" + info = self.data['bpms_noise'] + + tstamp = datetime.datetime.fromtimestamp(info['timestamp']) + tstamp = tstamp.strftime(self.TIME_FMT) + + rows = [ + ('Measurement Timestamp', tstamp), + ('Stored current', f'{info["stored_current"]:.2f} [mA]'), + ('RF Frequency', f'{info["rf_frequency"] / 1e6:.6f} [MHz]'), + ('Neasyred frac. tune X', f'{info["tunex"]:.4f}'), + ('Measured frac. tune y', f'{info["tuney"]:.4f}'), + ('Sampling freq.', f'{info["sampling_frequency"]:.2f} [Hz]'), + ('Acq rate', str(info['acq_rate'])), + ('Switching mode', str(info['switching_mode'])), + ('Switching freq.', f'{info["switching_frequency"]:.2f} [Hz]'), + ] + + self.set_font('Arial', '', 10) + + w = self.WIDTH / 2.5 + h = 5 + x0 = (self.WIDTH - 2 * w) / 2 + + for row in rows: + self.set_x(x0) + for item in row: + self.cell(w, h, str(item), border=1, align='C') + self.ln(h) + + def config_table(self): + """.""" + p = self.params + + rows = [ + ('Timeout BPMs [s]', p.timeout_bpms), + ('Timeout Correctors [s]', p.timeout_correctors), + ('Meas BPM noise', p.meas_bpms_noise), + ('Meas RF', p.meas_rf_line), + ('Meas magnets', p.meas_magnets), + ('CH kick [urad]', p.corrs_ch_kick), + ('CV kick [urad]', p.corrs_cv_kick), + ('CH freqs', str(p.corrs_ch_freqs)), + ('CV freqs', str(p.corrs_cv_freqs)), + ('RF mode', str(p._rf_mode)), + ('RF step [Hz]', p.rf_step_kick), + ('RF phase amp [deg]', p.rf_phase_amp), + ] + + self.set_font('Arial', '', 10) + + w = self.WIDTH / 2.5 + h = 5 + x0 = (self.WIDTH - 2 * w) / 2 + + for row in rows: + self.set_x(x0) + for item in row: + self.cell(w, h, str(item), border=1, align='C') + self.ln(h) + + def add_scale_factors(self): + """.""" + w = self.WIDTH - 30 + x = (self.WIDTH - w) / 3 + + self.image(self._folder + 'scale_factors.png', x=x, y=26, w=w) + + def add_correlations(self): + """.""" + w = self.WIDTH - 30 + x = (self.WIDTH - w) / 3 + + self.image(self._folder + 'correlation.png', x=x, y=130, w=w) + + def add_least_correlated(self): + """.""" + w = self.WIDTH - 30 + x = (self.WIDTH - w) / 2 + + self.image(self._folder + 'least_corr_ch.png', x=x, y=26, w=w) + self.image(self._folder + 'least_corr_cv.png', x=x, y=155, w=w) + + def add_best_correlated(self): + """.""" + w = self.WIDTH - 30 + x = (self.WIDTH - w) / 2 + + self.image(self._folder + 'best_corr_ch.png', x=x, y=26, w=w) + self.image(self._folder + 'best_corr_cv.png', x=x, y=155, w=w) + + def add_rf_column(self): + """.""" + w = self.WIDTH - 30 + x = (self.WIDTH - w) / 2 + + self.image(self._folder + 'rf_column.png', x=x, y=26, w=w) + + def create_report(self, meas_orm, folder=None): + """.""" + # TODO: change methods labels from "mat AC" or "mat DC" + # to "measured mat" and "reference mat", or something similar + if folder is None: + folder = '' + + self.meas_orm = meas_orm + self.params = meas_orm.params + self.data = meas_orm.data + self._folder = folder + + if not meas_orm.analysis: + meas_orm.process_data() + + mat_ac = meas_orm.build_respmat() + mat_dc = meas_orm.get_ref_respmat() + + fig, _ = meas_orm.plot_scale_conversion_factors(show_fig=False) + fig.savefig(folder + 'scale_factors.png', dpi=300) + _mplt.close(fig) # even if show_fig=False, figs can be displayed + # in interactive envs (notebooks specifically) + + fig, _, corr = meas_orm.plot_comparison_correlations( + mat_ac=mat_ac, mat_dc=mat_dc, show_fig=False + ) + fig.savefig(folder + 'correlation.png', dpi=300) + _mplt.close(fig) + + corr_h = corr[:, :120] + corr_v = corr[:, 120:280] + + idcsh = corr_h[1].argsort() + idcsv = corr_v[0].argsort() + 120 + + # least correlated + fig, _ = meas_orm.plot_comparison_single_corrector( + idcsh[-1], mat_ac=mat_ac, mat_dc=mat_dc, show_fig=False + ) + fig.savefig(folder + 'least_corr_ch.png', dpi=300) + _mplt.close(fig) + + fig, _ = meas_orm.plot_comparison_single_corrector( + idcsv[-1], mat_ac=mat_ac, mat_dc=mat_dc, show_fig=False + ) + fig.savefig(folder + 'least_corr_cv.png', dpi=300) + _mplt.close(fig) + + # best correlated + fig, _ = meas_orm.plot_comparison_single_corrector( + idcsh[0], mat_ac=mat_ac, mat_dc=mat_dc, show_fig=False + ) + fig.savefig(folder + 'best_corr_ch.png', dpi=300) + _mplt.close(fig) + + fig, _ = meas_orm.plot_comparison_single_corrector( + idcsv[0], mat_ac=mat_ac, mat_dc=mat_dc, show_fig=False + ) + fig.savefig(folder + 'best_corr_cv.png', dpi=300) + _mplt.close(fig) + + # rf + fig, _ = meas_orm.plot_comparison_single_corrector( + 280, mat_ac=mat_ac, mat_dc=mat_dc, show_fig=False + ) + fig.savefig(folder + 'rf_column.png', dpi=300) + _mplt.close(fig) + + self.add_page() + self.page_title('Measurement fingerprint') + self.meas_fingerprint() + self.page_title('Measurement params. & setup', loc_y=80) + self.set_y(90) + self.config_table() + + self.add_page() + self.page_title('Measurement scale factors') + self.add_scale_factors() + + self.page_title( + 'Measurement correlation residue (from unity)', loc_y=122 + ) + self.add_correlations() + + self.add_page() + self.page_title('Least correlated columns comparison') + self.add_least_correlated() + + self.add_page() + self.page_title('Most correlated columns comparison') + self.add_best_correlated() + + self.add_page() + self.page_title('RF Column (dispersion) comparsion') + self.add_rf_column() + + self.output(folder + 'orm_report.pdf', 'F') diff --git a/apsuite/loco/report.py b/apsuite/loco/report.py index 73632f78..37ca12fd 100644 --- a/apsuite/loco/report.py +++ b/apsuite/loco/report.py @@ -6,6 +6,7 @@ from apsuite.loco.analysis import LOCOAnalysis from apsuite.loco.utils import LOCOUtils +from pathlib import Path TIME_FMT = '%Y-%m-%d %H:%M:%S' @@ -13,6 +14,12 @@ class LOCOReport(FPDF): """.""" + def _get_cnpem_logo_path(self): + import apsuite + return str( + Path(apsuite.__file__).parent / 'resources/cnpem_lnls_logo.jpg' + ) + def __init__(self, loco_data=None): """.""" super().__init__() @@ -28,8 +35,8 @@ def __init__(self, loco_data=None): def header(self): """.""" - path = '/'.join(__file__.split('/')[:-1]) - self.image(path + '/cnpem_lnls_logo.jpg', x=10, y=6, w=40, h=15) + path = self._get_cnpem_logo_path() + self.image(path, x=10, y=6, w=40, h=15) self.set_font('Arial', 'B', 14) self.cell(0, 6, 'SIRIUS LOCO Report', 0, 0, 'C') self.set_font('Arial', '', 11) diff --git a/apsuite/loco/cnpem_lnls_logo.jpg b/apsuite/resources/cnpem_lnls_logo.jpg similarity index 100% rename from apsuite/loco/cnpem_lnls_logo.jpg rename to apsuite/resources/cnpem_lnls_logo.jpg