Example #1
0
def _test_cross_arbitrary_basis(testj=4, nterms=10, npix=500):
    """Verify the functions are orthogonal, by taking the
    integrals of a given mode times N other ones.

    This is a helper function for test_cross_arbitrary_basis.

    Parameters :
    --------------
    testj : int
        Index of the Zernike-like polynomial to test against the others
    nterms : int
        Test that polynomial against those from 1 to this N
    npix : int
        Size of array to use for this test
    """
    # choose a square aperture for the test
    square_aperture = optics.SquareAperture(size=1.).sample(npix=npix,grid_size=1.1)
    square_basis = zernike.arbitrary_basis(square_aperture,nterms=nterms)

    test_mode = square_basis[testj - 1]
    assert np.sum(np.isfinite(test_mode)) > 0, "Basis function calculation failure; all NaNs."
    for idx, array in enumerate(square_basis):
        j = idx + 1
        if j == testj or j == 1:
            continue  # discard piston term and self
        prod = array * test_mode
        wg = np.where(np.isfinite(prod))
        cross_sum = np.abs(prod[wg].sum())

        # Threshold was originally 1e-9, but we ended up getting 1.19e-9 on some machines (not always)
        # this seems acceptable, so relaxing criteria slightly
        assert cross_sum < 2e-9, (
            "orthogonality failure, Sum[Mode(j={}) * Mode(j={})] = {} (> 2e-9)".format(
                j, testj, cross_sum)
        )
Example #2
0
def _test_cross_arbitrary_basis(testj=4, nterms=10, npix=500):
    """Verify the functions are orthogonal, by taking the
    integrals of a given mode times N other ones.

    This is a helper function for test_cross_arbitrary_basis.

    Parameters :
    --------------
    testj : int
        Index of the Zernike-like polynomial to test against the others
    nterms : int
        Test that polynomial against those from 1 to this N
    npix : int
        Size of array to use for this test
    """
    # choose a square aperture for the test
    square_aperture = optics.SquareAperture(size=1.).sample(npix=npix,grid_size=1.1)
    square_basis = zernike.arbitrary_basis(square_aperture,nterms=nterms)

    test_mode = square_basis[testj - 1]
    assert np.sum(np.isfinite(test_mode)) > 0, "Basis function calculation failure; all NaNs."
    for idx, array in enumerate(square_basis):
        j = idx + 1
        if j == testj or j == 1:
            continue  # discard piston term and self
        prod = array * test_mode
        wg = np.where(np.isfinite(prod))
        cross_sum = np.abs(prod[wg].sum())

        # Threshold was originally 1e-9, but we ended up getting 1.19e-9 on some machines (not always)
        # this seems acceptable, so relaxing criteria slightly
        assert cross_sum < 2e-9, (
            "orthogonality failure, Sum[Mode(j={}) * Mode(j={})] = {} (> 2e-9)".format(
                j, testj, cross_sum)
        )
Example #3
0
def test_arbitrary_basis_rms(nterms=10, size=500):
    """Verify RMS(Zernike[n,m]) == 1."""
    # choose a square aperture for the test
    square_aperture = optics.SquareAperture(size=1.).sample(npix=size,grid_size=1.1)
    square_basis = zernike.arbitrary_basis(square_aperture,nterms=nterms)

    assert np.nanstd(square_basis[0]) == 0.0, "Mode(j=0) has nonzero RMS"
    for j in range(1, nterms):
        rms = np.nanstd(square_basis[j])  # exclude masked pixels
        assert abs(1.0 - rms) < 0.001, "Mode(j={}) has RMS value of {}".format(j, rms)
Example #4
0
def test_arbitrary_basis_rms(nterms=10, size=500):
    """Verify RMS(Zernike[n,m]) == 1."""
    # choose a square aperture for the test
    square_aperture = optics.SquareAperture(size=1.).sample(npix=size,grid_size=1.1)
    square_basis = zernike.arbitrary_basis(square_aperture,nterms=nterms)

    assert np.nanstd(square_basis[0]) == 0.0, "Mode(j=0) has nonzero RMS"
    for j in range(1, nterms):
        rms = np.nanstd(square_basis[j])  # exclude masked pixels
        assert abs(1.0 - rms) < 0.001, "Mode(j={}) has RMS value of {}".format(j, rms)
Example #5
0
def zernike_projection(beam, Nx=101, Nz=101, N_zern= 8):
	x = beam.getshonecol(1)
	z = beam.getshonecol(3)
	path = beam.getshonecol(13)

	x_lin = np.linspace(np.min(x), np.max(x), Nx)
	z_lin = np.linspace(np.min(z), np.max(z), Nx)
	X, Z = np.meshgrid(x_lin,z_lin)
	wfe = interpolate.griddata((x,z),path,(X,Z),method='linear')

	import sys
	import numpy
	sys.path.insert(0,"/Users/awojdyla/python/poppy/")

	from poppy import zernike
	# commented import astropy and import poppy_core in poppy repo

	#4 defocus
	mask2 = np.where(numpy.isnan(wfe))
	wfe_proj = wfe.copy()
	wfe_proj[mask2] = 0

	N_Zern = 8
	#define an aperture for the Zernike basis (non-nans)
	aperture = wfe.copy()*0+1
	aperture[mask2] = 0
	ZZ = zernike.arbitrary_basis(aperture, nterms=N_Zern, rho=None, theta=None, outside=0)

	proj = np.zeros(N_Zern)
	for i in np.arange(N_Zern):
	    Zerni = ZZ[i,:,:]/np.sqrt(np.sum(ZZ[i,:,:]*ZZ[i,:,:]))
	    proj[i] = np.sum(wfe_proj*Zerni)

	proj[0]=0;

	fig = plt.stem(np.arange(N_Zern), proj*1e3)
	plt.title("Zernike projections")
	plt.xlabel("Noll index")
	plt.ylabel("coefficient [a.u.]")
	plt.show()

	return proj
Example #6
0
def fit_dm_rotation_ellipticity(camstream, dmstream, dm_mask, nimages, cmd_value=0.5):
    
    # get tip/tilt terms
    zbasis_dm = zernike.arbitrary_basis(dm_mask, outside=0., nterms=3)[1:] 
    
    # measure psf displacement from tip/tilt
    value = cmd_value

    # measure
    tt_ims = []
    tt_ims.append(np.mean(camstream.grab_many(nimages), axis=0)) #ref
    sleep(1.0)
    for tt in zbasis_dm:
        dmstream.write((tt * value).astype(dmstream.buffer.dtype))
        sleep(1.0)
        tt_ims.append(np.mean(camstream.grab_many(nimages), axis=0))
    dmstream.write(np.zeros(dmstream.buffer.shape, dtype=dmstream.buffer.dtype))

    # compute displacements
    tt_coms = []
    for im in tt_ims:
        im_com = np.squeeze(np.where(im == im.max()))
        tt_coms.append(im_com)
    tt_coms = np.vstack(tt_coms)
    tt_coms -= tt_coms[0]

    # get angles of displacement
    angle1 = np.arctan2(tt_coms[1][0], tt_coms[1][1])
    angle2 = np.arctan2(tt_coms[2][0], tt_coms[2][1]) - np.pi/2.
    mean_angle = (angle1 + angle2) / 2.
    logger.info(f'Found rotation: {mean_angle} rad, {np.rad2deg(mean_angle)} deg')

    # get ratio of displacement
    disp1 = np.sqrt(tt_coms[1][0]**2 + tt_coms[1][1]**2)
    disp2 = np.sqrt(tt_coms[2][0]**2 + tt_coms[2][1]**2)
    disp_ratio = disp1 / disp2
    logger.info(f'Found x/y displacement ratio: {disp_ratio}')
    
    return mean_angle, disp_ratio
Example #7
0
def get_control_matrix_from_hadamard_measurements(hmeas,
                                                  hmodes,
                                                  hval,
                                                  dm_map,
                                                  dm_mask,
                                                  wfsthresh=0.5,
                                                  dmthresh=0.5,
                                                  ninterp=2,
                                                  nmodes=None,
                                                  deltas=False,
                                                  remove_modes=None):
    '''
    Given a measurement cube of WFS measurements of +/- hadamard modes with amplitude hval,
    return the reconstructed IF matrix, DMmodes, WFSmodes, DM and WFS maps/masks, and the control/reconstructor matrix
    '''

    # get nact
    nact = np.count_nonzero(dm_map)

    # construct IF cube
    #print(hmeas.shape, hmodes.shape)
    ifcube = get_ifcube_from_hmeas(hmeas,
                                   hmodes,
                                   hval,
                                   nact=nact,
                                   deltas=deltas)

    # remove modes if requested
    if remove_modes is not None:
        mask = np.mean(ifcube, axis=0) != 0
        modes = arbitrary_basis(mask, nterms=remove_modes, outside=0)
        ifcube = remove_modes_from_if(ifcube, modes, mask)

    # get DM and WFS maps and masks
    wfs_ctrl_map, wfs_ctrl_mask = get_wfs_ctrl_map_mask(ifcube,
                                                        threshold=wfsthresh)
    dm_ctrl_map, dm_ctrl_mask = get_dm_ctrl_map_mask(ifcube,
                                                     dm_map,
                                                     dm_mask,
                                                     threshold=dmthresh)

    # reduce ifcube to only include pixels in wfs_ctrl_mask
    ifcube_active = ifcube[:, wfs_ctrl_mask]
    ifcube_active -= np.mean(ifcube_active, axis=0)  # remove the mean

    # get SVD
    wfsmodes, singvals, dmmodes = get_svd_from_ifcube(ifcube_active)

    #print(ifcube_active.shape, wfsmodes.shape, singvals.shape, dmmodes.shape)
    #return wfs_ctrl_map, wfs_ctrl_mask, dm_ctrl_map, dm_ctrl_mask, ifcube, wfsmodes, singvals, dmmodes

    # interpolate DM modes
    dmmodes_interp = interpolate_dm_modes(dmmodes,
                                          dm_ctrl_mask,
                                          dm_map,
                                          dm_mask,
                                          n=ninterp)

    # get control matrix
    ctrl = get_control_matrix(dmmodes_interp,
                              wfsmodes,
                              singvals,
                              nmodes=nmodes)

    return {
        'ifmat': ifcube_active,
        'wfsmap': wfs_ctrl_map,
        'wfsmask': wfs_ctrl_mask,
        'dmmap': dm_ctrl_map,
        'dmmask': dm_ctrl_mask,
        'wfsmodes': wfsmodes,
        'dmmodes': dmmodes_interp,
        'singvals': singvals,
        'ctrlmat': ctrl
    }
Example #8
0
def projected_basis(nterms, angle, fill_fraction, actuator_mask):
    '''
    Return a set of Zernike modes stretched such that
    they form ordinary Zernike shapes when deprojected.

    Values outside the beam footprint are extrapolated
    by evaluting the terms for rho > 1.

    Zernike modes are ordered by the Noll convention
    and normalized such that they have a unit RMS in
    the deprojected beam footprint.

    Parameters:
        nterms : int
            Number of terms in the basis. Always skips piston.
        angle : float
            Incidence angle of the beam on the DM, given in
            degrees. Angles are always assumed to be in the x-z
            plane.
        fill_fraction : float
            Fraction of the DM in the beam footprint (defined
            along the x axis) [=0.96 for the 2K]
        actuator_mask : str
            Path to FITS file with actuators mapped from 1 to n actuators.

    Returns: list of nterms modes of shape nact x act 
    '''
    from poppy.zernike import zernike_basis, arbitrary_basis

    if isinstance(actuator_mask, str):
        with fits.open(actuator_mask) as f:
            dm_mask = f[0].data.astype(bool)
    else:
        dm_mask = actuator_mask

    nact = dm_mask.shape[0]

    # generate the radial and theta arrays that account for geometrical
    # projection
    cos = np.cos(np.deg2rad(angle))
    x = np.linspace(
        -(nact - 1) / nact,
        (nact - 1) / nact, num=nact, endpoint=True) / fill_fraction
    xx, yy = np.meshgrid(x, x)
    rho = np.sqrt(xx**2 + (yy / cos)**2)
    theta = np.arctan2(yy / cos, xx)

    # make the modes
    #zbasis = zernike_basis(nterms=nterms, npix=nact, outside=0., rho=rho, theta=theta)
    # generate nterms+1 because we're throwing away piston later
    zbasis = arbitrary_basis(aperture=rho <= 1.0,
                             nterms=nterms + 1,
                             outside=0.,
                             rho=rho,
                             theta=theta)

    # find actuators outside the beam footprint
    footprint = zbasis[0] != 0
    outside = dm_mask & ~footprint
    outyx = np.dstack(np.where(outside))[0]

    # interpolate from nearest neighbors
    for y, x in outyx:
        yneighbor, xneighbor = find_nearest((y, x), footprint, num=1)
        zbasis[:, y, x] = zbasis[:, yneighbor, xneighbor]

    # toss piston
    return zbasis[1:], footprint
Example #9
0
def take_measurements_from_config(config_params,
                                  dm_cmds=None,
                                  client=None,
                                  dmstream=None,
                                  dmdivstream=None,
                                  camstream=None,
                                  darkim=None,
                                  restore_dm=True):

    skip_indi = config_params.get_param('diversity', 'skip_indi', str2bool)

    print('INDI STATE ', skip_indi)

    if (client is None) and (not skip_indi):
        # open indi client connection
        client = INDIClient('localhost',
                            config_params.get_param('diversity', 'port', int))
        client.start()

    if dmstream is None:
        # open shmims
        dmstream = ImageStream(
            config_params.get_param('diversity', 'dmchannel', str))
    if dmdivstream is None:
        dmdivstream = ImageStream(
            config_params.get_param('diversity', 'dmdivchannel', str))
    if camstream is None:
        camname = config_params.get_param('camera', 'name', str)
        camstream = ImageStream(camname)

    if darkim is None and (not skip_indi):
        # take a dark (eventually replace this with the INDI dark [needs some kind of check to see if we have a dark, I guess])
        darkim = take_dark(camstream, client, camname,
                           config_params.get_param('diversity', 'ndark', int))
    else:
        darkim = None

    dmdelay = config_params.get_param('diversity', 'dmdelay', float)
    indidelay = config_params.get_param('diversity', 'indidelay', float)

    # measure
    div_type = config_params.get_param('diversity', 'type', str)
    if div_type.lower() == 'dm':
        # get defocus mode
        with fits.open(config_params.get_param('interaction', 'dm_mask',
                                               str)) as f:
            dm_mask = f[0].data
        zbasis = arbitrary_basis(dm_mask, nterms=4, outside=0)
        defocus_mode = zbasis[-1]
        imcube = measure_dm_diversity(
            camstream,
            dmstream,
            dmdivstream,
            defocus_mode,
            config_params.get_param('diversity', 'values', float),
            config_params.get_param('diversity', 'navg', float),
            darkim=darkim,
            dm_cmds=dm_cmds,
            dmdelay=dmdelay,
            indidelay=indidelay,
            restore_dm=restore_dm)
    else:  # stage diversity
        positions = np.asarray(
            config_params.get_param('diversity', 'values',
                                    float)) + config_params.get_param(
                                        'diversity', 'stage_focus', float)
        imcube = measure_stage_diversity(
            client,
            camstream,
            dmstream,
            config_params.get_param('diversity', 'camstage', str),
            positions,
            config_params.get_param('diversity', 'navg', float),
            darkim=darkim,
            dm_cmds=dm_cmds,
            dmdelay=dmdelay,
            final_position=positions[0],
            restore_dm=restore_dm)
    print(imcube.shape)
    # clip if needed
    shape = imcube.shape
    naxes = len(shape)
    N = config_params.get_param('estimation', 'N', int)
    if N < shape[-1]:  # assume the camera image is square
        logger.info(
            f'Expected shape {N}x{N} but got shape {shape[-2:]}. Clipping to {N}x{N} about center of mass.'
        )
        imcube_reduced = []
        for im in imcube:
            if naxes == 4:
                # in this case, im is actually a cube
                # so define a slice around the mean center of mass
                # (e.g., response matrix measurements)
                im0 = np.mean(im, axis=0)
                com = center_of_mass(im0)
                totalslice = (slice(None), ) + slice_to_valid_shape(
                    im0, com, N, return_slice=True)
                imcube_reduced.append(im[totalslice])
            elif naxes == 3:
                # here, im is actually an image
                # (e.g., measurements for a single estimate)
                com = center_of_mass(im)
                newim = slice_to_valid_shape(im, com, N)
                imcube_reduced.append(newim)
        imcube = np.asarray(imcube_reduced)
    if N > shape[-1]:  # assume the camera image is square
        logger.warning(
            f'Camera frames are smaller than expected. Expected {N}x{N} but got {shape[-2:]}.'
        )

    return imcube