def _make_mtf_vs_field_vs_focus(self, num_fields, focus_range, num_focus, freqs): ''' TODO: docstring ''' self._uniformly_spaced_fields(num_fields) net_mtfs = [None] * num_fields for idx in range(num_fields): focus, net_mtfs[idx] = self._make_mtf_thrufocus( idx, focus_range, num_focus) fields = (self.fields[-1] * self.fov_y) * m.linspace(0, 1, num_fields) t_cube = m.empty((num_focus, num_fields, len(freqs))) s_cube = m.empty((num_focus, num_fields, len(freqs))) for idx, mtfs in enumerate(net_mtfs): for idx2, submtf in enumerate(mtfs): t = submtf.exact_polar(freqs, 0) s = submtf.exact_polar(freqs, 90) t_cube[idx2, idx, :] = t s_cube[idx2, idx, :] = s TCube = MTFvFvF(data=t_cube, focus=focus, field=fields, freq=freqs, azimuth='Tan') SCube = MTFvFvF(data=s_cube, focus=focus, field=fields, freq=freqs, azimuth='Sag') return TCube, SCube
def mtf_vs_field(self, num_pts, freqs=[10, 20, 30, 40, 50]): ''' Generates a 2D array of MTF vs field values for the given spatial frequencies. Args: num_pts (`int`): Number of points to compute the MTF at. freqs (`iterable`): set of frequencies to compute at. Returns: `tuple` containing: `numpy.ndarray` (Tan) a 3D ndnarray where the columns correspond to fields and the rows correspond to spatial frequencies. `numpy.ndarray` (Sag) a 3D ndnarray where the columns correspond to fields and the rows correspond to spatial frequencies. ''' self._uniformly_spaced_fields(num_pts) mtfs_t = m.empty((num_pts, len(freqs))) mtfs_s = m.empty((num_pts, len(freqs))) for idx in range(num_pts): mtf = self._make_mtf(idx) vals_t = mtf.exact_polar(freqs, 0) vals_s = mtf.exact_polar(freqs, 90) mtfs_t[idx, :] = vals_t mtfs_s[idx, :] = vals_s return mtfs_s, mtfs_t
def thrufocus_mtf_from_wavefront_array(focused_wavefront, sim_params): """Create a thru-focus T/S MTF curve at each frequency requested from a focused wavefront. TODO: refactor Parameters ---------- focused_wavefront : `Pupil` a pupil object sim_params : `SimulationConfig` a SimulationConfig namedtuple Returns ------- `pandas.DataFrame` dataframe of data Notes ----- see marcros.DEFAULT_SIM_PARAMS for an example config. """ s = sim_params focusdiv_wvs = m.linspace(-s.focus_range_waves, s.focus_range_waves, s.focus_planes) tt, ss = m.empty((s.focus_planes, len(s.freqs))), m.empty((s.focus_planes, len(s.freqs))) for idx, focus in enumerate(focusdiv_wvs): if s.focus_zernike: defocus = FringeZernike(base=1, Z4=focus, rms_norm=s.focus_normed, samples=s.samples, epd=s.efl / s.fno, wavelength=s.wvl) else: defocus = Seidel(W020=focus, epd=s.efl / s.fno, samples=s.samples, wavelength=s.wvl) mtf = MTF.from_pupil(focused_wavefront + defocus, efl=s.efl) tan, sag = mtf_ts_extractor(mtf, s.freqs) tt[idx, :] = tan ss[idx, :] = sag return tt, ss
def plot2d_rgbgrid(self, axlim=25, interp_method='lanczos', pix_grid=None, fig=None, ax=None): """Create a 2D color plot of the PSF and R,G,B components. Parameters ---------- axlim : `float` limits of axis, symmetric. xlim=(-axlim,axlim), ylim=(-axlim, axlim) interp_method : `str` method used to interpolate the image between samples of the PSF pix_grid : float if not None, overlays gridlines with spacing equal to pix_grid. Intended to show the collection into camera pixels while still in the oversampled domain fig : `matplotlib.figure.Figure`, optional Figure containing the plot ax : `matplotlib.axes.Axis`, optional: Axis containing the plot fig : `matplotlib.figure.Figure`, optional Figure containing the plot ax : `matplotlib.axes.Axis`, optional: Axis containing the plot Notes ----- Need to refine internal workings at some point. """ # make the arrays for the RGB images dat = m.empty((self.samples_y, self.samples_x, 3)) datr = m.zeros((self.samples_y, self.samples_x, 3)) datg = m.zeros((self.samples_y, self.samples_x, 3)) datb = m.zeros((self.samples_y, self.samples_x, 3)) dat[:, :, 0] = self.R dat[:, :, 1] = self.G dat[:, :, 2] = self.B datr[:, :, 0] = self.R datg[:, :, 1] = self.G datb[:, :, 2] = self.B left, right = self.unit[0], self.unit[-1] ax_width = 2 * axlim # generate a figure and axes to plot in fig, ax = share_fig_ax(fig, ax) axr, axg, axb = make_rgb_axes(ax) ax.imshow(dat, extent=[left, right, left, right], interpolation=interp_method, origin='lower') axr.imshow(datr, extent=[left, right, left, right], interpolation=interp_method, origin='lower') axg.imshow(datg, extent=[left, right, left, right], interpolation=interp_method, origin='lower') axb.imshow(datb, extent=[left, right, left, right], interpolation=interp_method, origin='lower') for axs in (ax, axr, axg, axb): ax.set(xlim=(-axlim, axlim), ylim=(-axlim, axlim)) if pix_grid is not None: # if pixel grid is desired, add it mult = m.m.floor(axlim / pix_grid) gmin, gmax = -mult * pix_grid, mult * pix_grid pts = m.arange(gmin, gmax, pix_grid) ax.set_yticks(pts, minor=True) ax.set_xticks(pts, minor=True) ax.yaxis.grid(True, which='minor') ax.xaxis.grid(True, which='minor') ax.set(xlabel=r'Image Plane X [$\mu m$]', ylabel=r'Image Plane Y [$\mu m$]') axr.text(-axlim + 0.1 * ax_width, axlim - 0.2 * ax_width, 'R', color='white') axg.text(-axlim + 0.1 * ax_width, axlim - 0.2 * ax_width, 'G', color='white') axb.text(-axlim + 0.1 * ax_width, axlim - 0.2 * ax_width, 'B', color='white') return fig, ax
def plot2d(self, axlim=25, interp_method='lanczos', pix_grid=None, fig=None, ax=None): '''Create a 2D color plot of the PSF. Parameters ---------- axlim : `float` limits of axis, symmetric. xlim=(-axlim,axlim), ylim=(-axlim, axlim) interp_method : `str` method used to interpolate the image between samples of the PSF pix_grid : `float` if not None, overlays gridlines with spacing equal to pix_grid. Intended to show the collection into camera pixels while still in the oversampled domain fig : `matplotlib.figure.Figure`, optional Figure containing the plot ax : `matplotlib.axes.Axis`, optional: Axis containing the plot Returns ------- fig : `matplotlib.figure.Figure`, optional Figure containing the plot ax : `matplotlib.axes.Axis`, optional: Axis containing the plot ''' dat = m.empty((self.samples_x, self.samples_y, 3)) dat[:, :, 0] = self.R dat[:, :, 1] = self.G dat[:, :, 2] = self.B left, right = self.unit_y[0], self.unit_y[-1] bottom, top = self.unit_x[0], self.unit_x[-1] fig, ax = share_fig_ax(fig, ax) ax.imshow(dat, extent=[left, right, bottom, top], interpolation=interp_method, origin='lower') ax.set(xlabel=r'Image Plane X [$\mu m$]', ylabel=r'Image Plane Y [$\mu m$]', xlim=(-axlim, axlim), ylim=(-axlim, axlim)) if pix_grid is not None: # if pixel grid is desired, add it mult = m.floor(axlim / pix_grid) gmin, gmax = -mult * pix_grid, mult * pix_grid pts = m.arange(gmin, gmax, pix_grid) ax.set_yticks(pts, minor=True) ax.set_xticks(pts, minor=True) ax.yaxis.grid(True, which='minor') ax.xaxis.grid(True, which='minor') return fig, ax
def radial_mtf_to_mtfffd_data(tan, sag, imagehts, azimuths, upsample): """Take radial MTF data and map it to inputs to the MTFFFD constructor. Performs upsampling/interpolation in cartesian coordinates Parameters ---------- tan : `np.ndarray` tangential data sag : `np.ndarray` sagittal data imagehts : `np.ndarray` array of image heights azimuths : iterable azimuths corresponding to the first dimension of the tan/sag arrays upsample : `float` upsampling factor Returns ------- out_x : `np.ndarray` x coordinates of the output data out_y : `np.ndarray` y coordinates of the output data tan : `np.ndarray` tangential data sag : `np.ndarray` sagittal data """ azimuths = m.asarray(azimuths) imagehts = m.asarray(imagehts) if imagehts[0] > imagehts[-1]: # distortion profiled, values "reversed" # just flip imagehts, since spacing matters and not exact values imagehts = imagehts[::-1] amin, amax = min(azimuths), max(azimuths) imin, imax = min(imagehts), max(imagehts) aq = m.linspace(amin, amax, int(len(azimuths) * upsample)) iq = m.linspace(imin, imax, int( len(imagehts) * 4)) # hard-code 4x linear upsample, change later aa, ii = m.meshgrid(aq, iq, indexing='ij') # for each frequency, build an interpolating function and upsample up_t = m.empty((len(aq), tan.shape[1], len(iq))) up_s = m.empty((len(aq), sag.shape[1], len(iq))) for idx in range(tan.shape[1]): t, s = tan[:, idx, :], sag[:, idx, :] interpft = RGI((azimuths, imagehts), t, method='linear') interpfs = RGI((azimuths, imagehts), s, method='linear') up_t[:, idx, :] = interpft((aa, ii)) up_s[:, idx, :] = interpfs((aa, ii)) # compute the locations of the samples on a cartesian grid xd, yd = m.outer(m.cos(m.radians(aq)), iq), m.outer(m.sin(m.radians(aq)), iq) samples = m.stack([xd.ravel(), yd.ravel()], axis=1) # for the output cartesian grid, figure out the x-y coverage and build a regular grid absamin = min(abs(azimuths)) closest_to_90 = azimuths[m.argmin(azimuths - 90)] xfctr = m.cos(m.radians(absamin)) yfctr = m.cos(m.radians(closest_to_90)) xmin, xmax = imin * xfctr, imax * xfctr ymin, ymax = imin * yfctr, imax * yfctr xq, yq = m.linspace(xmin, xmax, len(iq)), m.linspace(ymin, ymax, len(iq)) xx, yy = m.meshgrid(xq, yq) outt, outs = [], [] # for each frequency, interpolate onto the cartesian grid for idx in range(up_t.shape[1]): datt = griddata(samples, up_t[:, idx, :].ravel(), (xx, yy), method='linear') dats = griddata(samples, up_s[:, idx, :].ravel(), (xx, yy), method='linear') outt.append(datt.reshape(xx.shape)) outs.append(dats.reshape(xx.shape)) outt, outs = m.rollaxis(m.asarray(outt), 0, 3), m.rollaxis(m.asarray(outs), 0, 3) return xq, yq, outt, outs