diff --git a/package/lib/plots.py b/package/lib/plots.py index 9268f08..ae6c3b1 100755 --- a/package/lib/plots.py +++ b/package/lib/plots.py @@ -58,9 +58,9 @@ from mpl_toolkits.axes_grid1.anchored_artists import AnchoredDirectionArrows, An from scipy.ndimage import zoom as sc_zoom try: - from .utils import PCconf, princ_angle, rot2D, sci_not + from .utils import PCconf, princ_angle, rot2D, sci_not, cursor_data except ImportError: - from utils import PCconf, princ_angle, rot2D, sci_not + from utils import PCconf, princ_angle, rot2D, sci_not, cursor_data def plot_obs(data_array, headers, rectangle=None, shifts=None, savename=None, plots_folder="", **kwargs): @@ -363,10 +363,10 @@ def polarization_map( fig = plt.figure(figsize=(7 * ratiox, 7 * ratioy), layout="constrained") if ax is None: ax = fig.add_subplot(111, projection=wcs) - ax.set(aspect="equal", fc="k") # , ylim=[-0.05 * stkI.shape[0], 1.05 * stkI.shape[0]]) + ax.set(aspect="equal") # , fc="w", ylim=[-0.05 * stkI.shape[0], 1.05 * stkI.shape[0]]) # fig.subplots_adjust(hspace=0, wspace=0, left=0.102, right=1.02) - # ax.coords.grid(True, color='white', ls='dotted', alpha=0.5) + ax.coords.grid(True, color="grey", ls="dotted", alpha=0.5) ax.coords[0].set_axislabel("Right Ascension (J2000)") ax.coords[0].set_axislabel_position("t") ax.coords[0].set_ticklabel_position("t") @@ -403,10 +403,10 @@ def polarization_map( "gist_ncar", "viridis", ]: - ax.set_facecolor("black") + ax.set_facecolor(plt.get_cmap(kwargs["cmap"])(0.0)) font_color = "white" else: - ax.set_facecolor("white") + ax.set_facecolor(plt.get_cmap(kwargs["cmap"])(0.0)) font_color = "black" if display.lower() in ["i", "intensity"]: @@ -549,39 +549,39 @@ def polarization_map( text_props={"ec": "k", "fc": font_color, "alpha": 1, "lw": 0.5}, arrow_props={"ec": "k", "fc": font_color, "alpha": 1, "lw": 1}, ) + ax.add_artist(px_sc) + ax.add_artist(north_dir) if display.lower() in ["i", "s_i", "snri", "pf", "p", "pa", "s_p", "snrp", "confp"] and step_vec != 0: if scale_vec == -1: poldata[np.isfinite(poldata)] = 1.0 / 2.0 step_vec = 1 scale_vec = 2.0 - X, Y = np.meshgrid(np.arange(stkI.shape[1]), np.arange(stkI.shape[0])) - U, V = (poldata * np.cos(np.pi / 2.0 + pangdata * np.pi / 180.0), poldata * np.sin(np.pi / 2.0 + pangdata * np.pi / 180.0)) - ax.quiver( - X[::step_vec, ::step_vec], - Y[::step_vec, ::step_vec], - U[::step_vec, ::step_vec], - V[::step_vec, ::step_vec], - units="xy", - angles="uv", - scale=1.0 / scale_vec, - scale_units="xy", - pivot="mid", - headwidth=0.0, - headlength=0.0, - headaxislength=0.0, - width=kwargs["width"], - linewidth=kwargs["linewidth"], - color="w", - edgecolor="k", - ) - pol_sc = AnchoredSizeBar( - ax.transData, scale_vec, r"$P$= 100 %", 4, pad=0.25, sep=5, borderpad=0.25, frameon=False, size_vertical=0.005, color=font_color - ) - - ax.add_artist(pol_sc) - ax.add_artist(px_sc) - ax.add_artist(north_dir) + if maskP.any(): + X, Y = np.meshgrid(np.arange(stkI.shape[1]), np.arange(stkI.shape[0])) + U, V = (poldata * np.cos(np.pi / 2.0 + pangdata * np.pi / 180.0), poldata * np.sin(np.pi / 2.0 + pangdata * np.pi / 180.0)) + ax.quiver( + X[::step_vec, ::step_vec], + Y[::step_vec, ::step_vec], + U[::step_vec, ::step_vec], + V[::step_vec, ::step_vec], + units="xy", + angles="uv", + scale=1.0 / scale_vec, + scale_units="xy", + pivot="mid", + headwidth=0.0, + headlength=0.0, + headaxislength=0.0, + width=kwargs["width"], + linewidth=kwargs["linewidth"], + color="w", + edgecolor="k", + ) + pol_sc = AnchoredSizeBar( + ax.transData, scale_vec, r"$P$= 100 %", 4, pad=0.25, sep=5, borderpad=0.25, frameon=False, size_vertical=0.005, color=font_color + ) + ax.add_artist(pol_sc) ax.annotate( r"$F_{{\lambda}}^{{int}}$({0:.0f} $\AA$) = {1} $ergs \cdot cm^{{-2}} \cdot s^{{-1}} \cdot \AA^{{-1}}$".format( @@ -3057,12 +3057,15 @@ class pol_map(object): def display(self, fig=None, ax=None, flux_lim=None): kwargs = dict([]) + self.data, self.error, self.fmt = None, None, None if self.display_selection is None: self.display_selection = "total_flux" if flux_lim is None: flux_lim = self.flux_lim if self.display_selection.lower() in ["total_flux"]: self.data = self.I * self.map_convert + self.error = self.I_ERR * self.map_convert + self.fmt = "{0:.2e} ± {1:.2e}" if flux_lim is None: vmin, vmax = (1.0 / 2.0 * np.median(self.data[self.data > 0.0]), np.max(self.data[self.data > 0.0])) else: @@ -3071,6 +3074,8 @@ class pol_map(object): label = r"$F_{\lambda}$ [$ergs \cdot cm^{-2} \cdot s^{-1} \cdot \AA^{-1}$]" elif self.display_selection.lower() in ["pol_flux"]: self.data = self.I * self.map_convert * self.P + self.error = self.I_ERR * self.map_convert + self.fmt = "{0:.2e} ± {1:.2e}" if flux_lim is None: vmin, vmax = (1.0 / 2.0 * np.median(self.I[self.I > 0.0] * self.map_convert), np.max(self.I[self.I > 0.0] * self.map_convert)) else: @@ -3079,11 +3084,21 @@ class pol_map(object): label = r"$P \cdot F_{\lambda}$ [$ergs \cdot cm^{-2} \cdot s^{-1} \cdot \AA^{-1}$]" elif self.display_selection.lower() in ["pol_deg"]: self.data = self.P * 100.0 +<<<<<<< Updated upstream kwargs["vmin"], kwargs["vmax"] = 0.0, np.max(self.data[self.P > self.P_ERR]) +||||||| constructed merge base + kwargs["vmin"], kwargs["vmax"] = 0.0, min(np.max(self.data[self.P > self.P_ERR]), 100.0) +======= + self.error = self.P_ERR * 100.0 + self.fmt = "{0:.2f} ± {1:.2f} %" + kwargs["vmin"], kwargs["vmax"] = 0.0, min(np.max(self.data[self.P > self.P_ERR]), 100.0) +>>>>>>> Stashed changes kwargs["alpha"] = 1.0 - 0.75 * (self.P < self.P_ERR) label = r"$P$ [%]" elif self.display_selection.lower() in ["pol_ang"]: self.data = princ_angle(self.PA) + self.error = self.PA_ERR + self.fmt = "{0:.2f} ± {1:.2f} °" kwargs["vmin"], kwargs["vmax"] = 0, 180.0 kwargs["alpha"] = 1.0 - 0.75 * (self.P < self.P_ERR) label = r"$\theta_{P}$ [°]" @@ -3114,6 +3129,10 @@ class pol_map(object): self.im = ax.imshow(self.data, aspect="equal", cmap="inferno", **kwargs) plt.rcParams.update({"font.size": 14}) self.cbar = fig.colorbar(self.im, ax=ax, aspect=50, shrink=0.75, pad=0.025, label=label) + if self.error is not None: + my_cursor = cursor_data(self.im, error=self.error, fmt=self.fmt) + self.im.get_cursor_data = my_cursor.get + self.im.format_cursor_data = my_cursor.format plt.rcParams.update({"font.size": 10}) fig.canvas.draw_idle() return self.im @@ -3123,6 +3142,8 @@ class pol_map(object): ax.set_ylim(0, self.data.shape[0]) plt.rcParams.update({"font.size": 14}) fig.colorbar(im, ax=ax, aspect=50, shrink=0.75, pad=0.025, label=label) + if format_cursor_data is not None: + self.im.format_cursor_data = format_cursor_data plt.rcParams.update({"font.size": 10}) fig.canvas.draw_idle() return im @@ -3506,6 +3527,7 @@ if __name__ == "__main__": savename="_".join([Stokes_UV[0].header["FILENAME"], args.type]), plots_folder=args.static_pdf, display=args.type, + cmap="hot_r", ) else: polarization_map( diff --git a/package/lib/utils.py b/package/lib/utils.py index 583f44d..943de37 100755 --- a/package/lib/utils.py +++ b/package/lib/utils.py @@ -1,4 +1,5 @@ import numpy as np +from matplotlib.transforms import Bbox, BboxTransform def rot2D(ang): @@ -154,6 +155,50 @@ def sci_not(v, err, rnd=1, out=str): return *output[1:], -power +class cursor_data: + """ + Object to overwrite data getter and formatter in interactive plots. + """ + + def __init__(self, im, error=None, fmt=None) -> None: + self.im = im + self.data = im.get_array() + self.fmt = "{:.2f}" if fmt is None else fmt + self.err = error + + def set_err(self, err) -> None: + if self.data.shape != err.shape: + raise ValueError("Error and Data don't have the same shape") + else: + self.err = err + + def set_fmt(self, fmt) -> None: + self.fmt = fmt + + def get(self, event): + xmin, xmax, ymin, ymax = self.im.get_extent() + if self.im.origin == "upper": + ymin, ymax = ymax, ymin + data_extent = Bbox([[xmin, ymin], [xmax, ymax]]) + array_extent = Bbox([[0, 0], [self.data.shape[1], self.data.shape[0]]]) + trans = self.im.get_transform().inverted() + trans += BboxTransform(boxin=data_extent, boxout=array_extent) + point = trans.transform([event.x, event.y]) + if any(np.isnan(point)): + return None + j, i = point.astype(int) + # Clip the coordinates at array bounds + if not (0 <= i < self.data.shape[0]) or not (0 <= j < self.data.shape[1]): + return None + elif self.err is not None: + return self.data[i, j], self.err[i, j] + else: + return self.data + + def format(self, y) -> str: + return self.fmt.format(*y) + + def wcs_CD_to_PC(CD): """ Return the position angle in degrees to the North direction of a wcs