def plot_rmswfe_rrmswfe_scatter(db, ylim=(None, None), fig=None, ax=None): """Make a scatter plot of residual RMS WFE vs RMS WFE from a database. This plot can be used as a simplified means of understanding the capture range of MTF-based wavefront sensing. Parameters ---------- db : `iris.data.Database` database of simulation results ylim : `iterable`, optional lower, upper y axis limits fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot Returns ------- fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot """ fig, ax = share_fig_ax(fig, ax) ax.scatter(db.df.truth_rmswfe, db.df.rrmswfe_final) ax.set(xlabel=r'RMS WFE [$\lambda$]', ylim=ylim, ylabel=r'Residual RMS WFE [$\lambda$]', yscale='log') return fig, ax
def plot2d(self, fig=None, ax=None): ''' Creates a 2D plot of the phase error of the pupil Args: fig (pyplot.figure): Figure to draw plot in ax (pyplot.axis): Axis to draw plot in Returns: (pyplot.figure, pyplot.axis): Figure and axis containing the plot ''' epd = self.epd fig, ax = share_fig_ax(fig, ax) im = ax.imshow(convert_phase(self.phase, self), extent=[-epd / 2, epd / 2, -epd / 2, epd / 2], cmap='RdYlBu', interpolation='lanczos', origin='lower') cb = fig.colorbar(im, label=f'OPD [{self._opd_str}]', ax=ax, fraction=0.046) cb.outline.set_edgecolor('k') cb.outline.set_linewidth(0.5) ax.set(xlabel=r'Pupil $\xi$ [mm]', ylabel=r'Pupil $\eta$ [mm]') return fig, ax
def plot_final_cost_rrmswfe_scatter(db, ylim=(None, None), fig=None, ax=None): """Plot final cost residual RMS WFE vs final cost function value. This plot can be used as a simplified means of understanding a uniqueness problem in MTF-based wavefront sensing. Parameters ---------- db : `iris.data.Database` database of simulation results ylim : iterable, optional Description fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot Returns ------- fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot """ fig, ax = share_fig_ax(fig, ax) ax.scatter(db.df.cost_final, db.df.rrmswfe_final) ax.set(xlabel='Cost Function Value', ylim=ylim, ylabel=r'Residual RMS WFE [$\lambda$]', yscale='log') return fig, ax
def _render_rrmswfe_vs_angle_plot(db, sph_amount, other='coma', fig=None, ax=None): other_amounts = [0.05, 0.10, 0.20] if other.lower() == 'coma': lbl = '|Z7,Z8| ' else: lbl = '|Z5,Z6| ' fig, ax = share_fig_ax(fig, ax) for ca in other_amounts: ids = _filter_db_for_sph_and_coma_or_ast_mags(db, other, sph_amount, ca) cost, rmswfe, rrmswfe, coma_angle = _get_cost_rmswfe_rrmswfe_coma_or_ast_angle( db, ids, other) ax.plot(coma_angle, rrmswfe, label=f'{ca}') ax.set(ylim=(1e-4, 1e-0), yscale='log', ylabel=r'Residual RMS WFE [$\lambda$]', xlabel=r'$\Theta(Z)$ [deg]') ax.legend(title=lbl + r'$\lambda{}$ RMS', loc='lower left', fancybox=True, framealpha=1) return fig, ax
def plot_image_from_cfg_codex_params_focus(config, codex, params, focuses, gamma=1.5, titles=('Tangential', 'Sagittal'), fig=None, axs=None): """Make a 2D image of (frequency, focus) from data used to do a simulation. Parameters ---------- config : `prysm.macros.SimulationConfig` a simulationconfig codex : `dict` dictionary with keys of integers and values of 'Zxx' strings, see `iris.rings` params : iterable set of wavefront parameters focuses : iterable set of focus values, in um gamma : `float`, optional gamma value to stretch plot by titles : iterable, optional titles to use to label plots fig : `matplotlib.figure.Figure` Figure containing the plot axs : iterable of `matplotlib.axes.Axis` Axes containing the plot Returns ------- fig : `matplotlib.figure.Figure` Figure containing the plot axs : iterable of `matplotlib.axes.Axis` Axes containing the plot """ imgt, imgs = config_codex_params_to_imgs(config, codex, params, focuses) extx = [config.freqs[0], config.freqs[-1]] exty = [focuses[0], focuses[-1]] fig, axs = share_fig_ax(fig, axs, numax=2) for data, ax, title in zip([imgt, imgs], axs, titles): im = ax.imshow(data, extent=[*extx, *exty], origin='lower', aspect='auto', cmap='inferno', norm=mpl.colors.PowerNorm(1 / gamma), clim=(0, 1), interpolation='lanczos') ax.set(xlim=extx, xlabel='Spatial Frequency [cy/mm]', ylim=exty, ylabel=r'Focus [$\mu m$]', title=title) fig.tight_layout() fig.colorbar(im, ax=axs, label='MTF [Rel. 1.0]') return fig, axs
def show(self, interp_method=None, fig=None, ax=None): ''' Displays the image. Args: interp_method (`string`): interpolation technique used in display. fig (`matplotlib.figure`): figure to display in. ax (`matplotlib.axis`): axis to display in. Returns: `tuple` containing: `matplotlib.figure`: figure containing the plot. `matplotlib.axis`: axis containing the plot. ''' lims = (0, 1) fig, ax = share_fig_ax(fig, ax) ax.imshow(self.data, cmap='Greys_r', interpolation=interp_method, clim=lims, origin='lower') ax.set_axis_off() return fig, ax
def plot_mtf_thrufocus(self, field_index, focus_range, numpts, freqs, fig=None, ax=None): focus, mtfs = self._make_mtf_thrufocus(field_index, focus_range, numpts) t = [] s = [] for mtf in mtfs: t.append(mtf.exact_polar(freqs, 0)) s.append(mtf.exact_polar(freqs, 90)) t, s = np.asarray(t), np.asarray(s) fig, ax = share_fig_ax(fig, ax) for idx, freq in enumerate(freqs): l, = ax.plot(focus, t[:, idx], lw=2, label=freq) ax.plot(focus, s[:, idx], lw=2, ls='--', c=l.get_color()) ax.legend(title=r'$\nu$ [cy/mm]') ax.set(xlim=(focus[0], focus[-1]), xlabel=r'Defocus [$\mu m$]', ylim=(0, 1), ylabel='MTF [Rel. 1.0]', title='Through Focus MTF') return fig, ax
def interferogram(self, visibility=1, passes=2, fig=None, ax=None): ''' Creates an interferogram of the :class:`Pupil~. Args: visibility (`float`): Visibility of the interferogram passes (`float`): number of passes (double-pass, quadra-pass, etc.) fig (pyplot.figure): Figure to draw plot in ax (pyplot.axis): Axis to draw plot in Returns: `tuple` containing: :class:`~matplotlib.pyplot.figure`: Figure containing the plot :class:`~matplotlib.pyplot.axis`: Axis containing the plot ''' epd = self.epd fig, ax = share_fig_ax(fig, ax) plotdata = (visibility * sin(2 * pi * passes * self.phase)) im = ax.imshow(plotdata, extent=[-epd / 2, epd / 2, -epd / 2, epd / 2], cmap='Greys_r', interpolation='lanczos', clim=(-1, 1), origin='lower') fig.colorbar(im, label=r'Wrapped Phase [$\lambda$]', ax=ax, fraction=0.046) ax.set(xlabel=r'Pupil $\xi$ [mm]', ylabel=r'Pupil $\eta$ [mm]') return fig, ax
def log_kde(data, xlim, num_pts=100, shade=True, bw_method=None, gridlines_below=True, fig=None, ax=None): """Create a Kernel Density Estimation based 'histogram' on a logarithmic x axis. Parameters ---------- data: `numpy.ndarray` data to plot xlim: iterable of length 2 lower and upper x limits to plot num_pts: `int`, optional number of points to sample along x axis shade: `bool`, optional whether to shade the area under the curve bw_method: `str` or `float`, optional passed to `scipy.stats.gaussian_kde` to set the bandwidth during estimation gridlines_below: `bool` whether to set axis gridlines to be below the graphics fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot Returns ------- fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot """ d = np.log10(data) kde = gaussian_kde(d, bw_method) xpts = np.linspace(np.log10(xlim[0]), np.log10(xlim[1]), num_pts) # in transformed space data = kde(xpts) real_xpts = 10**xpts data = data / data.sum() * 100 fig, ax = share_fig_ax(fig, ax) if shade is True: z = np.zeros(real_xpts.shape) ax.fill_between(real_xpts, data, z) ax.plot(real_xpts, data) ax.set(xlim=xlim, xlabel=r'Residual RMS WFE [$\lambda$]', xscale='log', ylim=(0, None), ylabel='Probability Density [%]', axisbelow=gridlines_below) return fig, ax
def zernike_barplot(zerndict, barwidth=0.8, alpha=1.0, shift=0, label=None, fig=None, ax=None): """Summary Parameters ---------- zerndict : TYPE dictionary with keys 'Zxxx' and numeric values barwidth : float, optional width of bars; values greater than 1 may cause poor appearance alpha : `float`, optional transparency of the bars shift : `float`, amount to shift the x values by, used for combining multiple barplots label : `str` label for the set of bars fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot Returns ------- fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot """ nums = [int(x[1:]) + shift for x in zerndict.keys()] base_adj = barwidth / 2 base_y = [0, 0] base_x = [min(nums) - base_adj, max(nums) + base_adj] fig, ax = share_fig_ax(fig, ax) ax.plot(base_x, base_y, lw=0.5, c='k') ax.bar(nums, zerndict.values(), width=barwidth, alpha=alpha, zorder=4, label=label) ax.set(xticks=nums, xticklabels=zerndict.keys(), ylabel=r'Amplitude RMS [$\lambda$]') for tick in ax.get_xticklabels(): tick.set_rotation(45) return fig, ax
def plot_mtf_vs_field(self, num_pts, freqs=[10, 20, 30, 40, 50], title='MTF vs Field', minorgrid=True, fig=None, ax=None): ''' Generates a plot of the MTF vs Field for the lens. Args: num_pts (`int`): number of field points to evaluate. freqs (`iterable`): frequencies to evaluate the MTF at. fig (`matplotlib.pyplot.figure`): figure to plot inside. ax (`matplotlib.pyplot.axis`): axis to plot ini. Return: `tuple` containing: `matplotlib.pyplot.figure`: figure containing the plot. `matplotlib.pyplot.axis`: axis containing the plot. ''' data_s, data_t = self.mtf_vs_field(num_pts, freqs) flds_abs = np.linspace(0, self.fov_y, num_pts) fig, ax = share_fig_ax(fig, ax) for i in range(len(freqs)): ln, = ax.plot(flds_abs, data_s[:, i], lw=3, ls='--') ax.plot(flds_abs, data_t[:, i], lw=3, color=ln.get_color(), label=f'{freqs[i]}lp/mm') ax.plot(0, 0, color='k', ls='--', label='Sag') ax.plot(0, 0, color='k', label='Tan') # todo: respect units of `self` if minorgrid is True: ax.set_yticks([0.1, 0.3, 0.5, 0.7, 0.9], minor=True) ax.grid(True, which='minor') ax.set(xlim=(0, self.fov_y), xlabel='Image Height [mm]', ylim=(0, 1), ylabel='MTF [Rel. 1.0]', title=title) ax.legend() return fig, ax
def plot_axial_df_2d(df, gamma=1.5, titles=('Tangential', 'Sagittal'), fig=None, axs=None): """Make a 2D plot of (frequency, focus) from a dataframe containing axial data. Parameters ---------- df : `pandas.DataFrame` a pandas df with columns Focus, Field, Freq, Azimuth, and MTF gamma : `float`, optional gamma value to stretch plot by titles : iterable, optional titles for the left and right panes fig : `matplotlib.figure.Figure` Figure containing the plot axs : iterable of `matplotlib.axes.Axis` Axes containing the plot Returns ------- fig : `matplotlib.figure.Figure` Figure containing the plot axs : iterable of `matplotlib.axes.Axis` Axes containing the plot """ focuspos, ax_t, ax_s = _axial_df_to_image_focus(df) extx, exty = [df.Freq.min(), df.Freq.max()], [focuspos[0], focuspos[-1]] fig, axs = share_fig_ax(fig, axs, numax=2) for data, ax, title in zip([ax_t, ax_s], axs, titles): im = ax.imshow(data, extent=[*extx, *exty], origin='lower', aspect='auto', cmap='inferno', norm=mpl.colors.PowerNorm(1 / gamma), clim=(0, 1), interpolation='lanczos') ax.set(xlim=extx, xlabel='Spatial Frequency [cy/mm]', ylim=exty, ylabel=r'Focus [$\mu m$]', title=title) fig.tight_layout() fig.colorbar(im, ax=axs, label='MTF [Rel. 1.0]') return fig, axs
def cie_1976_wavelength_annotations(wavelengths, fig=None, ax=None): ''' Draws lines normal to the spectral locust on a CIE 1976 diagram and writes the text for each wavelength. Args: wavelengths (`iterable`): set of wavelengths to annotate. fig (`matplotlib.figure.Figure`): figure to draw on. ax (`matplotlib.axes.Axis`): axis to draw in. Returns: `tuple` containing: `matplotlib.figure.Figure`: figure containing the annotations. `matplotlib.axes.Axis`: axis containing the annotations. Notes: see SE: https://stackoverflow.com/questions/26768934/annotation-along-a-curve-in-matplotlib ''' # some tick parameters tick_length = 0.025 text_offset = 0.06 # convert wavelength to u' v' coordinates wavelengths = np.asarray(wavelengths) idx = np.arange(1, len(wavelengths) - 1, dtype=int) wvl_lbl = wavelengths[idx] uv = XYZ_to_uvprime(wavelength_to_XYZ(wavelengths)) u, v = uv[..., 0][idx], uv[..., 1][idx] u_last, v_last = uv[..., 0][idx - 1], uv[..., 1][idx - 1] u_next, v_next = uv[..., 0][idx + 1], uv[..., 1][idx + 1] angle = atan2(v_next - v_last, u_next - u_last) + pi / 2 cos_ang, sin_ang = cos(angle), sin(angle) u1, v1 = u + tick_length * cos_ang, v + tick_length * sin_ang u2, v2 = u + text_offset * cos_ang, v + text_offset * sin_ang fig, ax = share_fig_ax(fig, ax) tick_lines = LineCollection(np.c_[u, v, u1, v1].reshape(-1, 2, 2), color='0.25', lw=1.25) ax.add_collection(tick_lines) for i in range(len(idx)): ax.text(u2[i], v2[i], str(wvl_lbl[i]), va="center", ha="center", clip_on=True) return fig, ax
def cct_duv_diagram(samples=100, fig=None, ax=None): ''' Creates a CCT-Duv diagram, for more information see Calculation of CCT and Duv and Practical Conversion Formulae, Yoshi Ohno, 2011. Args: samples (`int`): number of samples on the background. fig (`matplotlib.figure.Figure`): figure to plot in. ax (`matplotlib.axes.Axis`): axis to plot in. Returns: `tuple` containing: `matplotlib.figure.Figure`: figure containing the plot. `matplotlib.axes.Axis`: Axis containing the plot. ''' # raise UserWarning('this type of plot is not yet properly implemented') xlim = (2000, 10000) ylim = (-0.03, 0.03) cct = np.linspace(xlim[0], xlim[1], samples) # todo: even sampling along log, not linear duv = np.linspace(ylim[0], ylim[1], samples) upvp = multi_cct_duv_to_upvp(cct, duv) cct, duv = np.meshgrid(cct, duv) xy = uvprime_to_xy(upvp) xyz = xy_to_XYZ(xy) dat = XYZ_to_sRGB(xyz) maximum = np.max(dat, axis=-1) dat /= maximum[..., np.newaxis] dat = np.clip(dat, 0, 1) fig, ax = share_fig_ax(fig, ax) ax.imshow(dat, extent=[*xlim, *ylim], interpolation='bilinear', origin='lower', aspect='auto') ax.set(xlim=xlim, xlabel='CCT [K]', ylim=ylim, ylabel='Duv [a.u.]') return fig, ax
def plot_2d_optframe(data, max_freq=None, focus_range=None, focus_unit=r'$\mu m$', fig=None, ax=None): """Plot an image view of the data. Parameters ---------- data : `numpy.ndarray` 2D data with first dimension spatial frequency, second dimension focus max_freq : `float`, optional maximum spatial frequency to plot, x axis range will be [0,max_freq] focus_range : `float`, optional maximum defocus value to plot, y axis range will be [-focus_range,focus_range] focus_unit : str, optional unit of focus, e.g. um or waves fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot Returns ------- fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot """ fig, ax = share_fig_ax(fig, ax) im = ax.imshow(data, extent=[0, max_freq, -focus_range, focus_range], aspect='auto', origin='lower', interpolation=None) fig.colorbar(im) if max_freq is not None and focus_range is not None: ax.set(xlim=(0, max_freq), xlabel='Spatial Frequency [cy/mm]', ylim=(-focus_range, focus_range), ylabel=f'Focus [{focus_unit}]') return fig, ax
def plot2d(self, log=False, max_freq=200, fig=None, ax=None): ''' Creates a 2D plot of the MTF. Args: log (`bool`): If true, plots on log scale. max_freq (`float`): Maximum frequency to plot to. Axis limits will be ((-max_freq, max_freq), (-max_freq, max_freq)). fig (:class:`~matplotlib.pyplot.figure`): Figure to plot in. ax (:class:`~matplotlib.pyplot.axis`): Axis to plot in. Returns: `tuple` containing: fig (:class:`~matplotlib.pyplot.figure`): Figure containing the plot ax (:class:`~matplotlib.pyplot.axis`): Axis containing the plot. ''' if log: fcn = 20 * np.log10(1e-24 + self.data) label_str = 'MTF [dB]' lims = (-120, 0) else: fcn = correct_gamma(self.data) label_str = 'MTF [Rel 1.0]' lims = (0, 1) left, right = self.unit_x[0], self.unit_x[-1] bottom, top = self.unit_y[0], self.unit_y[-1] fig, ax = share_fig_ax(fig, ax) im = ax.imshow(fcn.T, extent=[left, right, bottom, top], origin='lower', cmap='Greys_r', interpolation='lanczos', clim=lims) cb = fig.colorbar(im, label=label_str, ax=ax, fraction=0.046) cb.outline.set_edgecolor('k') cb.outline.set_linewidth(0.5) ax.set(xlabel=r'$\nu_x$ [cy/mm]', ylabel=r'$\nu_y$ [cy/mm]', xlim=(-max_freq, max_freq), ylim=(-max_freq, max_freq)) return fig, ax
def plot_costfunction_history_global(document, sqrt=False, log=True, ylim=(None, None), ls=None, fig=None, ax=None): """Plot the cost function history of a global optimization run. Parameters ---------- document : `dict` document produced by utilities.prepare_document_global sqrt : `bool` whether to take the sqrt of the cost function log : `bool` whether to plot with logarithmic y axis ylim : iterable of length 2 lower, upper y axis limits ls : `str` line style fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot Returns ------- fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot """ if log is True: yscale = 'log' else: yscale = None fig, ax = share_fig_ax(fig, ax) if sqrt: data = [[_sqrt(x) for x in y] for y in document['cost_iter']] label = r'$\sqrt{Cost\, Function\,}$ [a.u.]' else: data = document['cost_iter'] label = 'Cost Function [a.u.]' _plot_attribute_global(data, ax=ax, ls=ls) ax.set(ylabel=label, yscale=yscale, ylim=ylim, xlabel='Iteration') return fig, ax
def plot_fourier_chain(pupil, psf, mtf, fig=None, axs=None, sizex=12, sizey=6): '''Plots a :class:`Pupil`, :class:`PSF`, and :class:`MTF` demonstrating the process of fourier optics simulation Args: pupil (prysm.Pupil): The pupil of an optical system psf (prysm.PSF): The psf of an optical system mtf (prysm.MTF): The MTF of an optical system fig (pyplot.figure): A figure object axs (list of pyplot.axes): axes to place the plots in sizex (number): size of the figure in x sizey (number): size of the figure in y Returns: fig, axs. A figure and axes containing the plot. ''' fig, axs = share_fig_ax(fig, axs, numax=3) pupil.interferogram(fig=fig, ax=axs[0]) psf.plot2d(fig=fig, ax=axs[1]) mtf.plot2d(fig=fig, ax=axs[2]) axs[0].set(title='Pupil') axs[1].set(title='PSF') axs[2].set(title='MTF') bbox_props = dict(boxstyle="rarrow", fill=None, lw=1) axs[0].text(1.385, 1.07, r'|Fourier Transform|$^2$', ha='center', va='center', bbox=bbox_props, transform=axs[0].transAxes) axs[0].text(3.15, 1.07, r'|Fourier Transform|', ha='center', va='center', bbox=bbox_props, transform=axs[0].transAxes) fig.set_size_inches(sizex, sizey) fig.tight_layout() return fig, axs
def plot_rrmswfe_history_global(document, log=True, ylim=(None, None), ls=None, fig=None, ax=None): """Plot the residual RMS WFE history of a global optimization run. Parameters ---------- document : `dict` document produced by utilities.prepare_document_global log : `bool` whether to plot with logarithmic y axis ylim : iterable of length 2 lower, upper y axis limits ls : `str` line style fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot Returns ------- fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot """ if log is True: yscale = 'log' else: yscale = None fig, ax = share_fig_ax(fig, ax) _plot_attribute_global(document['rrmswfe_iter'], ax=ax, ls=ls) ax.set(ylabel=r'Residual RMS WFE [$\lambda$]', yscale=yscale, ylim=ylim, xlabel='Iteration') return fig, ax
def plot_slice_xy(self, log=False, axlim=20, fig=None, ax=None): ''' Makes a 1D plot of X and Y slices through the PSF. Args: log (`bool`): if true, plot in log scale. if false, plot in linear scale. axlim (`float`): limits of axis, will plot [-axlim, axlim]. fig (pyplot.figure): figure to plot in. ax (pyplot.axis): axis to plot in. Returns: pyplot.fig, pyplot.axis. Figure and axis containing the plot. ''' ux, x = self.slice_x uy, y = self.slice_y if log: fcn_x = 20 * np.log10(1e-100 + x) fcn_y = 20 * np.log10(1e-100 + y) label_str = 'Normalized Intensity [dB]' lims = (-120, 0) else: fcn_x = x fcn_y = y label_str = 'Normalized Intensity [a.u.]' lims = (0, 1) fig, ax = share_fig_ax(fig, ax) ax.plot(ux, fcn_x, label='Slice X', lw=3) ax.plot(uy, fcn_y, label='Slice Y', lw=3) ax.set(xlabel=r'Image Plane X [$\mu m$]', ylabel=label_str, xlim=(-axlim, axlim), ylim=lims) plt.legend(loc='upper right') return fig, ax
def plot_psf_vs_field(self, num_pts, fig=None, axes=None, axlim=25): ''' Creates a figure showing the evolution of the PSF over the field of view. Args: num_pts (`int`): Number of points between (0,1) to create a PSF for Returns: `tuple` containing: `matplotlib.pyplot.figure`: figure containing the plots. `list`: the axes the plots are placed in. ''' psfs = self.psf_vs_field(num_pts) fig, axes = share_fig_ax(fig, axes, numax=num_pts, sharex=True, sharey=True) for idx, (psf, axis) in enumerate(zip(psfs, axes)): show_labels = False show_colorbar = False if idx == 0: show_labels = True elif idx == num_pts - 1: show_colorbar = True psf.plot2d(fig=fig, ax=axis, axlim=axlim, show_axlabels=show_labels, show_colorbar=show_colorbar) fig_width = 15 fig.set_size_inches(fig_width, fig_width / num_pts) fig.tight_layout() return fig, axes
def plot_reference_spots(self, fig=None, ax=None): ''' Create a plot of the reference positions of lenslets. Args: fig (`matplotlib.figure.Figure`): figure object to draw in. ax (`matplotlib.axes.Axis`): axis object to draw in. Returns: `tuple` containing: `matplotlib.figure.Figure`: figure containing the plot. `matplotlib.axes.Axis`: axis containing the plot. ''' fig, ax = share_fig_ax(fig, ax) ax.scatter(self.refx / 1e3, self.refy / 1e3, c='k', s=8) ax.set(xlim=(0, self.sensor_size[0]), xlabel='Detector Position X [mm]', ylim=(0, self.sensor_size[1]), ylabel='Detector Position Y [mm]', aspect='equal') return fig, ax
def plot_mtf_focus_grid(data, frequencies, focus, fig=None, ax=None): """Plot a 2D view of MTF through frequency and focus. Parameters ---------- data : `numpy.ndarray` 2D ndarray with dimensions of frequency,focus frequencies : iterable fequencies the data is given at; x axes, cy/mm focus : iterable focus points the data is given at; y axes, microns fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot Returns ------- fig : `matplotlib.figure.Figure` Figure containing the plot ax : `matplotlib.axes.Axis` Axis containing the plot """ xlims = (frequencies[0], frequencies[-1]) ylims = (focus[0], focus[-1]) fig, ax = share_fig_ax(fig, ax) ax.imshow(data, extent=[*xlims, *ylims], origin='lower', aspect='auto', interpolation='lanczos') ax.set(xlim=xlims, xlabel='Spatial Frequency $\nu$ [cy/mm]', ylim=ylims, ylabel='Focus [$\mu$ m]') return fig, ax
def show_fourier(self, interp_method='lanczos', fig=None, ax=None): ''' Displays the fourier transform of the image. Args: interp_method (`string`): method used to interpolate the data for display. fig (`matplotlib.figure`): figure to plot in. ax (`matplotlib.axis`): axis to plot in. Returns: `tuple` containing: `matplotlib.figure`: figure containing the plot. `matplotlib.axis`: axis containing the plot. ''' dat = abs(fftshift(fft2(pad2d(self.data)))) dat /= dat.max() unit_x = forward_ft_unit(self.sample_spacing, self.samples_x) unit_y = forward_ft_unit(self.sample_spacing, self.samples_y) xmin, xmax = unit_x[0], unit_x[-1] ymin, ymax = unit_y[0], unit_y[-1] fig, ax = share_fig_ax(fig, ax) im = ax.imshow(dat**0.1, extent=[xmin, xmax, ymin, ymax], cmap='Greys_r', interpolation=interp_method, origin='lower') fig.colorbar(im) ax.set(xlabel='Spatial Frequency X [cy/mm]', ylabel='Spatial Frequency Y [cy/mm]', title='Normalized FT of image, to 0.1 power') return fig, ax
def plot_encircled_energy(self, azimuth=None, axlim=20, fig=None, ax=None): ''' Makes a 1D plot of the encircled energy at the given azimuth. Args: azimuth (`float`): azimuth to plot at, in degrees. axlim (`float`): limits of axis, will plot [0, axlim]. fig (pyplot.figure): figure to plot in. ax (pyplot.axis): axis to plot in. Returns: pyplot.fig, pyplot.axis. Figure and axis containing the plot ''' unit, data = self.encircled_energy(azimuth) fig, ax = share_fig_ax(fig, ax) ax.plot(unit, data, lw=3) ax.set(xlabel=r'Image Plane Distance [$\mu m$]', ylabel=r'Encircled Energy [Rel 1.0]', xlim=(0, axlim)) return fig, ax
def plot_slice_xy(self, fig=None, ax=None): ''' Creates a plot of slices through the X and Y axes of the pupil Args: fig (pyplot.figure): Figure to draw plot in ax (pyplot.axis): Axis to draw plot in Returns: (pyplot.figure, pyplot.axis): Figure and axis containing the plot ''' u, x = self.slice_x _, y = self.slice_y fig, ax = share_fig_ax(fig, ax) x = convert_phase(x, self) y = convert_phase(y, self) ax.plot(u, x, lw=3, label='Slice X') ax.plot(u, y, lw=3, label='Slice Y') ax.set(xlabel=r'Pupil $\rho$ [mm]', ylabel=f'OPD [{self._opd_str}]') plt.legend() return fig, ax
def plot_simple_result(self, result_index=-1, type='quiver', fig=None, ax=None): ''' Plots the simple version of the most recent result. Args: result_index (`int`): which capture to plot, defaults to most recent. type (`str`): what type of plot to make. Either "quiver" or "dots". fig (`matplotlib.figure.Figure`): figure to plot in. ax (`matplotlib.axes.Axis`): axis to plot in. Returns: `tuple` containing: `matplotlib.figure.Figure`: figure containing the plot. `matplotlib.axes.Axis`: axis containing the plot. ''' idx = result_index fig, ax = share_fig_ax(fig, ax) mx, my = self.captures_simple[idx]['x'], self.captures_simple[idx]['y'] if type.lower() in ('q', 'quiver'): dx, dy = mx - self.refx, my - self.refy ax.quiver(self.refx, self.refy, dx, dy, scale=1, units='xy', scale_units='xy') elif type.lower() in ('d', 'dots'): ax.scatter(self.refx, self.refy, c='k', s=8) ax.scatter(mx, my, c='r', s=8) ax.set(xlim=(0, self.sensor_size[0] * 1e3), xlabel=r'Detector X [$\mu m$]', ylim=(0, self.sensor_size[1] * 1e3), ylabel=r'Detector Y [$\mu m$]', aspect='equal') return fig, ax
def plot_spectrum(spectrum_dict, xrange=(370, 730), yrange=(0, 100), smoothing=None, fig=None, ax=None): ''' Plots the spectrum. Args: xrange (`iterable`): pair of lower and upper x bounds. yrange (`iterable`): pair of lower and upper y bounds. smoothing (`float`): number of nanometers to smooth data by. If None, do no smoothing. fig (`matplotlib.figure.Figure`): figure to plot in. ax (`matplotlib.axes.Axis`): axis to plot in. Returns: `tuple` containing: `matplotlib.figure.Figure`: figure containign the plot. `matplotlib.axes.Axis`: axis containing the plot. ''' wvl, values = spectrum_dict['wvl'], spectrum_dict['values'] if smoothing is not None: dx = wvl[1] - wvl[0] window_width = int(smoothing / dx) values = smooth(values, window_width, window='flat') lc = colorline(wvl, values, wvl, cmin=400, cmax=700, lw=5) fig, ax = share_fig_ax(fig, ax) ax.add_collection(lc) ax.set(xlim=xrange, xlabel=r'Wavelength $\lambda$ [nm]', ylim=yrange, ylabel='Transmission [%]') return fig, ax
def plot_tan_sag(self, max_freq=200, fig=None, ax=None, labels=('Tangential', 'Sagittal')): ''' Creates a plot of the tangential and sagittal MTF. Args: max_freq (`float`): Maximum frequency to plot to. Axis limits will be ((-max_freq, max_freq), (-max_freq, max_freq)). fig (:class:`~matplotlib.pyplot.figure`): Figure to plot in. ax (:class:`~matplotlib.pyplot.axis`): Axis to plot in. labels (`iterable`): set of labels for the two lines that will be plotted. Returns: `tuple` containing: fig (:class:`~matplotlib.pyplot.figure`): Figure containing the plot. ax (:class:`~matplotlib.pyplot.axis`): Axis containing the plot. ''' ut, tan = self.tan us, sag = self.sag fig, ax = share_fig_ax(fig, ax) ax.plot(ut, tan, label=labels[0], linestyle='-', lw=3) ax.plot(us, sag, label=labels[1], linestyle='--', lw=3) ax.set(xlabel='Spatial Frequency [cy/mm]', ylabel='MTF [Rel 1.0]', xlim=(0, max_freq), ylim=(0, 1)) plt.legend(loc='lower left') return fig, ax
def cie_1976_plankian_locust(trange=(2000, 10000), num_points=100, isotemperature_lines_at=None, isotemperature_du=0.025, fig=None, ax=None): ''' draws the plankian locust on the CIE 1976 color diagram. Args: trange (`iterable`): (min,max) color temperatures. num_points (`int`): number of points to compute. isotemperature_lines_at (`iterable`): CCTs to plot isotemperature lines at, defaults to [2000, 3000, 4000, 5000, 6500, 10000] if None. set to False to not plot lines. isotemperature_du (`float`): delta-u, parameter, length in x of the isotemperature lines. fig (`matplotlib.figure.Figure`): figure to plot in. ax (`matplotlib.axes.Axis`): axis to plot in. Returns: `tuple` containing: `matplotlib.figure.Figure`. figure containing the plot. `matplotlib.axes.Axis`: axis containing the plot. ''' cct = prepare_robertson_cct_data() cct_K, cct_u, cct_v, cct_dvdu = cct['K'], cct['u'], cct['v'], cct['dvdu'] # compute the u', v' coordinates of the temperatures temps = np.linspace(trange[0], trange[1], num_points) interpf_u = interp1d(cct_K, cct_u) interpf_v = interp1d(cct_K, cct_v) u = interpf_u(temps) v = interpf_v(temps) * 1.5 # x1.5 converts 1960 uv to 1976 u' v' # if plotting isotemperature lines, compute the upper and lower points of # each line and connect them. plot_isotemp = True if isotemperature_lines_at is None: isotemperature_lines_at = np.asarray([2000, 3000, 4000, 5000, 6500, 10000]) u_iso = interpf_u(isotemperature_lines_at) v_iso = interpf_v(isotemperature_lines_at) interpf_dvdu = interp1d(cct_u, cct_dvdu) dvdu = interpf_dvdu(u_iso) du = isotemperature_du / dvdu u_high = u_iso + du / 2 u_low = u_iso - du / 2 v_high = (v_iso + du / 2 * dvdu) * 1.5 # factors of 1.5 convert from uv to u'v' v_low = (v_iso - du / 2 * dvdu) * 1.5 elif isotemperature_lines_at is False: plot_isotemp = False fig, ax = share_fig_ax(fig, ax) ax.plot(u, v, c='0.15') if plot_isotemp is True: for ul, uh, vl, vh in zip(u_low, u_high, v_low, v_high): ax.plot([ul, uh], [vl, vh], c='0.15') return fig, ax