From 2f39b674b0269fcdc6583a88ef54933377cfefdd Mon Sep 17 00:00:00 2001 From: teutoburg Date: Fri, 4 Aug 2023 17:15:03 +0200 Subject: [PATCH 1/6] Improved plot methods using mpl OOP API and more configs --- scopesim/effects/detector_list.py | 23 ++--- scopesim/effects/spectral_trace_list.py | 19 +++-- scopesim/effects/spectral_trace_list_utils.py | 85 +++++++++++++------ 3 files changed, 83 insertions(+), 44 deletions(-) diff --git a/scopesim/effects/detector_list.py b/scopesim/effects/detector_list.py index 405e21e6..d093401c 100644 --- a/scopesim/effects/detector_list.py +++ b/scopesim/effects/detector_list.py @@ -4,6 +4,8 @@ from astropy import units as u from astropy.table import Table +from matplotlib import pyplot as plt + from ..base_classes import FOVSetupBase from .effects import Effect from .apertures import ApertureMask @@ -251,22 +253,21 @@ def detector_headers(self, ids=None): return hdrs - def plot(self): - import matplotlib.pyplot as plt - plt.gcf().clf() + def plot(self, axes=None): + if axes is None: + _, axes = plt.subplots() for hdr in self.detector_headers(): x_mm, y_mm = calc_footprint(hdr, "D") - x_cen, y_cen = np.average(x_mm), np.average(y_mm) - x_mm = list(x_mm) + [x_mm[0]] - y_mm = list(y_mm) + [y_mm[0]] - plt.gca().plot(x_mm, y_mm) - plt.gca().text(x_cen, y_cen, hdr["ID"]) + axes.plot(np.append(x_mm, x_mm[0]), np.append(y_mm, y_mm[0])) + axes.text(*np.mean((x_mm, y_mm), axis=1), hdr["ID"], + ha="center", va="center") - plt.gca().set_aspect("equal") - plt.ylabel("Size [mm]") + axes.set_aspect("equal") + axes.set_xlabel("Size [mm]") + axes.set_ylabel("Size [mm]") - return plt.gcf() + return axes class DetectorWindow(DetectorList): diff --git a/scopesim/effects/spectral_trace_list.py b/scopesim/effects/spectral_trace_list.py index 553c7879..fa935d79 100644 --- a/scopesim/effects/spectral_trace_list.py +++ b/scopesim/effects/spectral_trace_list.py @@ -7,8 +7,10 @@ from pathlib import Path import logging +from itertools import cycle import numpy as np +from matplotlib import pyplot as plt from astropy.io import fits from astropy.table import Table @@ -318,23 +320,22 @@ def rectify_cube(self, hdulist): """Rectify traces and combine into a cube""" raise(NotImplementedError) - def plot(self, wave_min=None, wave_max=None, **kwargs): + def plot(self, wave_min=None, wave_max=None, axes=None, **kwargs): if wave_min is None: wave_min = from_currsys("!SIM.spectral.wave_min") if wave_max is None: wave_max = from_currsys("!SIM.spectral.wave_max") - from matplotlib import pyplot as plt - from matplotlib._pylab_helpers import Gcf - if len(Gcf.figs) == 0: - plt.figure(figsize=(12, 12)) + if axes is None: + fig, axes = plt.subplots(figsize=(12, 12)) + else: + fig = axes.figure if self.spectral_traces is not None: - clrs = "rgbcymk" * (1 + len(self.spectral_traces) // 7) - for spt, c in zip(self.spectral_traces.values(), clrs): - spt.plot(wave_min, wave_max, c=c) + for spt, c in zip(self.spectral_traces.values(), cycle("rgbcymk")): + spt.plot(wave_min, wave_max, c=c, axes=axes, **kwargs) - return plt.gcf() + return fig def __repr__(self): # "\n".join([spt.__repr__() for spt in self.spectral_traces]) diff --git a/scopesim/effects/spectral_trace_list_utils.py b/scopesim/effects/spectral_trace_list_utils.py index d43df124..815b831e 100644 --- a/scopesim/effects/spectral_trace_list_utils.py +++ b/scopesim/effects/spectral_trace_list_utils.py @@ -17,7 +17,7 @@ from scipy.interpolate import interp1d from matplotlib import pyplot as plt -from astropy.table import Table +from astropy.table import Table, vstack from astropy.modeling import fitting from astropy.io import fits from astropy import units as u @@ -481,43 +481,80 @@ def footprint(self, wave_min=None, wave_max=None, xi_min=None, xi_max=None): return ([x_edge.min(), x_edge.max(), x_edge.max(), x_edge.min()], [y_edge.min(), y_edge.min(), y_edge.max(), y_edge.max()]) - def plot(self, wave_min=None, wave_max=None, c="r"): + def plot(self, wave_min=None, wave_max=None, xi_min=None, xi_max=None, *, + c="r", axes=None, plot_footprint=True, plot_text=True, + plot_ctrlpnts=True, plot_outline=False, plot_trace_id=False): """Plot control points of the SpectralTrace""" + if axes is None: + _, axes = plt.subplots() # Footprint (rectangle enclosing the trace) xlim, ylim = self.footprint(wave_min=wave_min, wave_max=wave_max) if xlim is None: - return + return axes xlim.append(xlim[0]) ylim.append(ylim[0]) - plt.plot(xlim, ylim) + if plot_footprint: + axes.plot(xlim, ylim) + + # for convenience... + xname = self.meta["x_colname"] + yname = self.meta["y_colname"] + wname = self.meta["wave_colname"] + sname = self.meta["s_colname"] # Control points - waves = self.table[self.meta["wave_colname"]] + waves = self.table[wname] if wave_min is None: wave_min = waves.min() if wave_max is None: wave_max = waves.max() - - mask = (waves >= wave_min) * (waves <= wave_max) - if sum(mask) > 2: - w = waves[mask] - - x = self.table[self.meta["x_colname"]][mask] - y = self.table[self.meta["y_colname"]][mask] - plt.plot(x, y, "o", c=c) - - for wave in np.unique(w): - xx = x[w==wave] - xx.sort() - dx = xx[-1] - xx[-2] - - plt.text(x[w==wave].max() + 0.5 * dx, - y[w==wave].mean(), - str(wave), va='center', ha='left') - - plt.gca().set_aspect("equal") + xis = self.table[sname] + if xi_min is None: + xi_min = xis.min() + if xi_max is None: + xi_max = xis.max() + + mask = ((waves >= wave_min) & (waves <= wave_max) + & (xis >= xi_min)& (xis <= xi_max)) + if sum(mask) <= 2: + return axes + + w = waves[mask] + + x = self.table[xname][mask] + y = self.table[yname][mask] + if plot_ctrlpnts: + axes.plot(x, y, "o", c=c) + + if plot_outline: + blue_end = self.table[mask][w == w.min()] + red_end = self.table[mask][w == w.max()] + blue_end.sort(sname) + red_end.sort(sname) + corners = vstack([blue_end[[0, -1]][xname, yname], + red_end[[-1, 0]][xname, yname], + blue_end[0][xname, yname]]) + axes.plot(corners[xname], corners[yname], c=c) + + if plot_trace_id: + axes.text(corners[xname][:-1].mean(), corners[yname][:-1].mean(), + self.meta["trace_id"], c=c, rotation="vertical", + ha="center", va="center") + + for wave in np.unique(w): + xx = x[w==wave] + xx.sort() + dx = xx[-1] - xx[-2] + + if plot_text: + axes.text(x[w==wave].max() + 0.5 * dx, + y[w==wave].mean(), + str(wave), va="center", ha="left") + + axes.set_aspect("equal") + return axes @property def trace_id(self): From 50f92dec97be4c8ce050c06770914192b4c8aedc Mon Sep 17 00:00:00 2001 From: teutoburg Date: Fri, 4 Aug 2023 17:15:32 +0200 Subject: [PATCH 2/6] Define some _repr_pretty_ methods for IPython display --- scopesim/detector/detector_array.py | 15 +++++++++++++++ scopesim/optics/optical_element.py | 7 +++++++ scopesim/optics/optical_train.py | 30 +++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/scopesim/detector/detector_array.py b/scopesim/detector/detector_array.py index 10321eb9..c073b27b 100644 --- a/scopesim/detector/detector_array.py +++ b/scopesim/detector/detector_array.py @@ -101,6 +101,21 @@ def readout(self, image_planes, array_effects=[], dtcr_effects=[], **kwargs): return self.latest_exposure + def __repr__(self): + msg = (f"{self.__class__.__name__}" + f"({self.detector_list!r}, **{self.meta!r})") + return msg + + def __str__(self): + return f"{self.__class__.__name__} with {self.detector_list!s}" + + def _repr_pretty_(self, p, cycle): + """For ipython""" + if cycle: + p.text(f"{self.__class__.__name__}(...)") + else: + p.text(str(self)) + def make_primary_hdu(meta): """Create the primary header from meta data""" diff --git a/scopesim/optics/optical_element.py b/scopesim/optics/optical_element.py index f2b46545..7f630c49 100644 --- a/scopesim/optics/optical_element.py +++ b/scopesim/optics/optical_element.py @@ -215,6 +215,13 @@ def __repr__(self): def __str__(self): return f"{self.__class__.__name__}: \"{self.display_name}\"" + def _repr_pretty_(self, p, cycle): + """For ipython""" + if cycle: + p.text(f"{self.__class__.__name__}(...)") + else: + p.text(str(self)) + @property def properties_str(self): prop_str = "" diff --git a/scopesim/optics/optical_train.py b/scopesim/optics/optical_train.py index d0ed34dc..65178279 100644 --- a/scopesim/optics/optical_train.py +++ b/scopesim/optics/optical_train.py @@ -497,6 +497,36 @@ def __repr__(self): def __str__(self): return self._description + def _repr_pretty_(self, p, cycle): + """For ipython""" + if cycle: + p.text(f"{self.__class__.__name__}(...)") + else: + p.text(f"{self.__class__.__name__} ") + p.text(f"for {self.cmds['!OBS.instrument']} ") + p.text(f"@ {self.cmds['!TEL.telescope']}:") + p.breakable() + p.text("UserCommands:") + p.breakable() + p.pretty(self.cmds) + p.breakable() + p.text("OpticalElements:") + with p.indent(2): + for item in self: + p.breakable() + p.pretty(item) + p.breakable() + p.text("DetectorArrays:") + with p.indent(2): + for item in self.detector_arrays: + p.breakable() + p.pretty(item) + p.breakable() + p.text("Effects:") + p.breakable() + with p.indent(2): + p.pretty(self.effects) + def __getitem__(self, item): return self.optics_manager[item] From 92de6b48bfd2fd0e03e41f7ee15f1587edf706f2 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Fri, 4 Aug 2023 20:34:56 +0200 Subject: [PATCH 3/6] Fix failing report because of fig/ax change --- scopesim/effects/effects.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scopesim/effects/effects.py b/scopesim/effects/effects.py index 00dbe9e9..2581e87b 100644 --- a/scopesim/effects/effects.py +++ b/scopesim/effects/effects.py @@ -239,7 +239,12 @@ def report(self, filename=None, output="rst", rst_title_chars="*+", """ if params["report_plot_include"] and hasattr(self, "plot"): + from matplotlib.figure import Figure fig = self.plot() + # HACK: plot methods should always return the same, while this is + # not sorted out, deal with both fig and ax + if not isinstance(fig, Figure): + fig = fig.figure if fig is not None: path = params["report_image_path"] From f416dede524d155eb84fc12cd1ffc35e51a74000 Mon Sep 17 00:00:00 2001 From: teutoburg Date: Fri, 4 Aug 2023 23:31:21 +0200 Subject: [PATCH 4/6] Minor improvements along the way... --- scopesim/effects/effects.py | 35 ++++++++--------- scopesim/effects/spectral_trace_list.py | 17 ++++----- scopesim/effects/spectral_trace_list_utils.py | 38 +++++++++---------- 3 files changed, 42 insertions(+), 48 deletions(-) diff --git a/scopesim/effects/effects.py b/scopesim/effects/effects.py index 2581e87b..4f62ec3d 100644 --- a/scopesim/effects/effects.py +++ b/scopesim/effects/effects.py @@ -113,15 +113,12 @@ def display_name(self): @property def meta_string(self): - meta_str = "" - max_key_len = max(len(key) for key in self.meta.keys()) - padlen = max_key_len + 4 - for key in self.meta: - if key not in {"comments", "changes", "description", "history", - "report_table_caption", "report_plot_caption", - "table"}: - meta_str += f"{key:>{padlen}} : {self.meta[key]}\n" - + padlen = 4 + len(max(self.meta, key=len)) + exclude = {"comments", "changes", "description", "history", + "report_table_caption", "report_plot_caption", "table"} + meta_str = "\n".join(f"{key:>{padlen}} : {value}" + for key, value in self.meta.items() + if key not in exclude) return meta_str def report(self, filename=None, output="rst", rst_title_chars="*+", @@ -199,7 +196,7 @@ def report(self, filename=None, output="rst", rst_title_chars="*+", """ changes = self.meta.get("changes", []) - changes_str = "- " + "\n- ".join([str(entry) for entry in changes]) + changes_str = "- " + "\n- ".join(str(entry) for entry in changes) cls_doc = self.__doc__ if self.__doc__ is not None else "" cls_descr = cls_doc.lstrip().splitlines()[0] @@ -262,6 +259,9 @@ def report(self, filename=None, output="rst", rst_title_chars="*+", # params["report_rst_path"]) # rel_file_path = os.path.join(rel_path, fname) + # TODO: fname is set in a loop above, so using it here in the + # fstring will only access the last value from the loop, + # is that intended? rst_str += f""" .. figure:: {fname} :name: {"fig:" + params.get("name", "")} @@ -292,16 +292,11 @@ def report(self, filename=None, output="rst", rst_title_chars="*+", return rst_str def info(self): - """ - Prints basic information on the effect, notably the description - """ - text = str(self) - - desc = self.meta.get("description") - if desc is not None: - text += f"\nDescription: {desc}" - - print(text) + """Print basic information on the effect, notably the description.""" + if (desc := self.meta.get("description")) is not None: + print(f"{self}\nDescription: {desc}") + else: + print(self) def __repr__(self): return f"{self.__class__.__name__}(**{self.meta!r})" diff --git a/scopesim/effects/spectral_trace_list.py b/scopesim/effects/spectral_trace_list.py index fa935d79..0ef92a1f 100644 --- a/scopesim/effects/spectral_trace_list.py +++ b/scopesim/effects/spectral_trace_list.py @@ -181,7 +181,7 @@ def apply_to(self, obj, **kwargs): ex_vol["meta"].update(vol) ex_vol["meta"].pop("wave_min") ex_vol["meta"].pop("wave_max") - new_vols_list += extracted_vols + new_vols_list.extend(extracted_vols) obj.volumes = new_vols_list @@ -197,8 +197,7 @@ def apply_to(self, obj, **kwargs): logging.info("Making cube") obj.cube = obj.make_cube_hdu() - trace_id = obj.meta["trace_id"] - spt = self.spectral_traces[trace_id] + spt = self.spectral_traces[obj.meta["trace_id"]] obj.hdu = spt.map_spectra_to_focal_plane(obj) return obj @@ -210,11 +209,11 @@ def footprint(self): xfoot, yfoot = [], [] for spt in self.spectral_traces.values(): xtrace, ytrace = spt.footprint() - xfoot += xtrace - yfoot += ytrace + xfoot.extend(xtrace) + yfoot.extend(ytrace) - xfoot = [np.min(xfoot), np.max(xfoot), np.max(xfoot), np.min(xfoot)] - yfoot = [np.min(yfoot), np.min(yfoot), np.max(yfoot), np.max(yfoot)] + xfoot = [min(xfoot), max(xfoot), max(xfoot), min(xfoot)] + yfoot = [min(yfoot), min(yfoot), max(yfoot), max(yfoot)] return xfoot, yfoot @@ -301,7 +300,7 @@ def rectify_traces(self, hdulist, xi_min=None, xi_max=None, interps=None, #pdu.header['FILTER'] = from_currsys("!OBS.filter_name_fw1") outhdul = fits.HDUList([pdu]) - for i, trace_id in enumerate(self.spectral_traces): + for i, trace_id in enumerate(self.spectral_traces, start=1): hdu = self[trace_id].rectify(hdulist, interps=interps, bin_width=bin_width, @@ -309,7 +308,7 @@ def rectify_traces(self, hdulist, xi_min=None, xi_max=None, interps=None, wave_min=wave_min, wave_max=wave_max) if hdu is not None: # ..todo: rectify does not do that yet outhdul.append(hdu) - outhdul[0].header[f"EXTNAME{i+1}"] = trace_id + outhdul[0].header[f"EXTNAME{i}"] = trace_id outhdul[0].header.update(inhdul[0].header) diff --git a/scopesim/effects/spectral_trace_list_utils.py b/scopesim/effects/spectral_trace_list_utils.py index 815b831e..e47d3ac3 100644 --- a/scopesim/effects/spectral_trace_list_utils.py +++ b/scopesim/effects/spectral_trace_list_utils.py @@ -86,7 +86,6 @@ def fov_grid(self): Spatial limits are determined by the `ApertureMask` effect and are not returned here. """ - trace_id = self.meta["trace_id"] aperture_id = self.meta["aperture_id"] lam_arr = self.table[self.meta["wave_colname"]] @@ -94,7 +93,7 @@ def fov_grid(self): wave_min = np.min(lam_arr) return {"wave_min": wave_min, "wave_max": wave_max, - "trace_id": trace_id, "aperture_id": aperture_id} + "trace_id": self.trace_id, "aperture_id": aperture_id} def compute_interpolation_functions(self): """ @@ -118,7 +117,7 @@ def compute_interpolation_functions(self): self._xiy2x = Transform2D.fit(xi_arr, y_arr, x_arr) self._xiy2lam = Transform2D.fit(xi_arr, y_arr, lam_arr) - if self.dispersion_axis == 'unknown': + if self.dispersion_axis == "unknown": dlam_dx, dlam_dy = self.xy2lam.gradient() wave_mid = 0.5 * (self.wave_min + self.wave_max) xi_mid = np.mean(xi_arr) @@ -142,7 +141,7 @@ def map_spectra_to_focal_plane(self, fov): The method returns a section of the fov image along with info on where this image lies in the focal plane. """ - logging.info("Mapping %s", fov.meta['trace_id']) + logging.info("Mapping %s", fov.meta["trace_id"]) # Initialise the image based on the footprint of the spectral # trace and the focal plane WCS wave_min = fov.meta["wave_min"].value # [um] @@ -176,7 +175,7 @@ def map_spectra_to_focal_plane(self, fov): ## Check if spectral trace footprint is outside FoV if xmax < 0 or xmin > naxis1d or ymax < 0 or ymin > naxis2d: logging.info("Spectral trace %s: footprint is outside FoV", - fov.meta['trace_id']) + fov.meta["trace_id"]) return None # Only work on parts within the FoV @@ -207,7 +206,7 @@ def map_spectra_to_focal_plane(self, fov): xilam = XiLamImage(fov, self.dlam_per_pix) self._xilamimg = xilam # ..todo: remove or make available with a debug flag? except ValueError: - print(f" ---> {self.meta['trace_id']} gave ValueError") + print(f" ---> {self.trace_id} gave ValueError") npix_xi, npix_lam = xilam.npix_xi, xilam.npix_lam xilam_wcs = xilam.wcs @@ -321,7 +320,7 @@ def rectify(self, hdulist, interps=None, wcs=None, **kwargs): bin_width = np.abs(self.dlam_per_pix.y).min() logging.info(" Bin width %.02g um", bin_width) - pixscale = from_currsys(self.meta['pixel_scale']) + pixscale = from_currsys(self.meta["pixel_scale"]) # Temporary solution to get slit length xi_min = kwargs.get("xi_min", None) @@ -341,8 +340,8 @@ def rectify(self, hdulist, interps=None, wcs=None, **kwargs): if wcs is None: wcs = WCS(naxis=2) - wcs.wcs.ctype = ['WAVE', 'LINEAR'] - wcs.wcs.cunit = ['um', 'arcsec'] + wcs.wcs.ctype = ["WAVE", "LINEAR"] + wcs.wcs.cunit = ["um", "arcsec"] wcs.wcs.crpix = [1, 1] wcs.wcs.cdelt = [bin_width, pixscale] # PIXSCALE @@ -372,13 +371,14 @@ def rectify(self, hdulist, interps=None, wcs=None, **kwargs): rect_spec = np.zeros_like(Xarr, dtype=np.float32) ihdu = 0 + # TODO: make this more iteratory for hdu in hdulist: if not isinstance(hdu, fits.ImageHDU): continue wcs_fp = WCS(hdu.header, key="D") - n_x = hdu.header['NAXIS1'] - n_y = hdu.header['NAXIS2'] + n_x = hdu.header["NAXIS1"] + n_y = hdu.header["NAXIS2"] iarr, jarr = wcs_fp.all_world2pix(Xarr, Yarr, 0) mask = (iarr > 0) * (iarr < n_x) * (jarr > 0) * (jarr < n_y) if np.any(mask): @@ -388,7 +388,7 @@ def rectify(self, hdulist, interps=None, wcs=None, **kwargs): ihdu += 1 header = wcs.to_header() - header['EXTNAME'] = self.trace_id + header["EXTNAME"] = self.trace_id return fits.ImageHDU(data=rect_spec, header=header) @@ -540,7 +540,7 @@ def plot(self, wave_min=None, wave_max=None, xi_min=None, xi_max=None, *, if plot_trace_id: axes.text(corners[xname][:-1].mean(), corners[yname][:-1].mean(), - self.meta["trace_id"], c=c, rotation="vertical", + self.trace_id, c=c, rotation="vertical", ha="center", va="center") for wave in np.unique(w): @@ -559,7 +559,7 @@ def plot(self, wave_min=None, wave_max=None, xi_min=None, xi_max=None, *, @property def trace_id(self): """Return the name of the trace""" - return self.meta['trace_id'] + return self.meta["trace_id"] def _set_dispersion(self, wave_min, wave_max, pixsize=None): """Computation of dispersion dlam_per_pix along xi=0 @@ -575,8 +575,8 @@ def _set_dispersion(self, wave_min, wave_max, pixsize=None): dlam_grad = self.xy2lam.gradient()[0] # dlam_by_dx else: dlam_grad = self.xy2lam.gradient()[1] # dlam_by_dy - pixsize = (from_currsys(self.meta['pixel_scale']) / - from_currsys(self.meta['plate_scale'])) + pixsize = (from_currsys(self.meta["pixel_scale"]) / + from_currsys(self.meta["plate_scale"])) self.dlam_per_pix = interp1d(lam, dlam_grad(x_mm, y_mm) * pixsize, fill_value="extrapolate") @@ -585,7 +585,7 @@ def __repr__(self): return f"{self.__class__.__name__}({self.table!r}, **{self.meta!r})" def __str__(self): - msg = (f" \"{self.meta['trace_id']}\" : " + msg = (f" \"{self.trace_id}\" : " f"[{self.wave_min:.4f}, {self.wave_max:.4f}]um : " f"Ext {self.meta['extension_id']} : " f"Aperture {self.meta['aperture_id']} : " @@ -929,8 +929,8 @@ def make_image_interpolations(hdulist, **kwargs): for hdu in hdulist: if isinstance(hdu, fits.ImageHDU): interps.append( - RectBivariateSpline(np.arange(hdu.header['NAXIS1']), - np.arange(hdu.header['NAXIS2']), + RectBivariateSpline(np.arange(hdu.header["NAXIS1"]), + np.arange(hdu.header["NAXIS2"]), hdu.data, **kwargs) ) return interps From fd72a2ec248040e73a55d3ea08cce0407f96c10d Mon Sep 17 00:00:00 2001 From: teutoburg Date: Sat, 5 Aug 2023 00:16:46 +0200 Subject: [PATCH 5/6] Mostly docstrings --- scopesim/effects/spectral_trace_list.py | 22 ++++++++- scopesim/effects/spectral_trace_list_utils.py | 45 +++++++++++++++++-- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/scopesim/effects/spectral_trace_list.py b/scopesim/effects/spectral_trace_list.py index 0ef92a1f..9c06037f 100644 --- a/scopesim/effects/spectral_trace_list.py +++ b/scopesim/effects/spectral_trace_list.py @@ -314,12 +314,32 @@ def rectify_traces(self, hdulist, xi_min=None, xi_max=None, interps=None, return outhdul - def rectify_cube(self, hdulist): """Rectify traces and combine into a cube""" raise(NotImplementedError) def plot(self, wave_min=None, wave_max=None, axes=None, **kwargs): + """Plot every spectral trace in the spectral trace list. + + Parameters + ---------- + wave_min : float, optional + Minimum wavelength, if any. If None, value from_currsys is used. + wave_max : float, optional + Maximum wavelength, if any. If None, value from_currsys is used. + axes : matplotlib axes, optional + The axes object to use for the plot. If None (default), a new + figure with one axes will be created. + **kwargs : dict + Any other parameters passed along to the plot method of the + individual spectral traces. + + Returns + ------- + fig : matplotlib figure + DESCRIPTION. + + """ if wave_min is None: wave_min = from_currsys("!SIM.spectral.wave_min") if wave_max is None: diff --git a/scopesim/effects/spectral_trace_list_utils.py b/scopesim/effects/spectral_trace_list_utils.py index e47d3ac3..bb3bc1d3 100644 --- a/scopesim/effects/spectral_trace_list_utils.py +++ b/scopesim/effects/spectral_trace_list_utils.py @@ -482,9 +482,48 @@ def footprint(self, wave_min=None, wave_max=None, xi_min=None, xi_max=None): [y_edge.min(), y_edge.min(), y_edge.max(), y_edge.max()]) def plot(self, wave_min=None, wave_max=None, xi_min=None, xi_max=None, *, - c="r", axes=None, plot_footprint=True, plot_text=True, + c="r", axes=None, plot_footprint=True, plot_wave=True, plot_ctrlpnts=True, plot_outline=False, plot_trace_id=False): - """Plot control points of the SpectralTrace""" + """Plot control points (and/or footprint) of the SpectralTrace. + + Parameters + ---------- + wave_min : float, optional + Minimum wavelength, if any. + wave_max : float, optional + Maximum wavelength, if any. + xi_min : float, optional + Minimum slit, if any. + xi_max : float, optional + Maximum slit, if any. + c : str, optional + Colour, any valid matplotlib colour string. The default is "r". + axes : matplotlib axes, optional + The axes object to use for the plot. If None (default), a new + figure with one axes will be created. + + Returns + ------- + axes : matplotlib axes + The axes object containing the plot. + + Other Parameters + ---------------- + plot_footprint : bool, optional + Plot a rectangle encompassing all control points, which may be + larger than the area actually covered by the trace, if the trace is + not exactly perpendicular to the detector. The default is True. + plot_wave : bool, optional + Annotate the wavelength points. The default is True. + plot_ctrlpnts : bool, optional + Plot the individual control points as makers. The default is True. + plot_outline : bool, optional + Plot the smallest tetragon encompassing all control points. + The default is False. + plot_trace_id : bool, optional + Write the trace ID in the middle of the trace. + The default is False. + """ if axes is None: _, axes = plt.subplots() @@ -548,7 +587,7 @@ def plot(self, wave_min=None, wave_max=None, xi_min=None, xi_max=None, *, xx.sort() dx = xx[-1] - xx[-2] - if plot_text: + if plot_wave: axes.text(x[w==wave].max() + 0.5 * dx, y[w==wave].mean(), str(wave), va="center", ha="left") From 331e12a654c5cf83d6712fbb8003477e58f88dab Mon Sep 17 00:00:00 2001 From: teutoburg <73600109+teutoburg@users.noreply.github.com> Date: Wed, 9 Aug 2023 09:07:26 +0200 Subject: [PATCH 6/6] oczosked --- scopesim/effects/spectral_trace_list_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scopesim/effects/spectral_trace_list_utils.py b/scopesim/effects/spectral_trace_list_utils.py index bb3bc1d3..47e0511a 100644 --- a/scopesim/effects/spectral_trace_list_utils.py +++ b/scopesim/effects/spectral_trace_list_utils.py @@ -556,7 +556,7 @@ def plot(self, wave_min=None, wave_max=None, xi_min=None, xi_max=None, *, xi_max = xis.max() mask = ((waves >= wave_min) & (waves <= wave_max) - & (xis >= xi_min)& (xis <= xi_max)) + & (xis >= xi_min) & (xis <= xi_max)) if sum(mask) <= 2: return axes