Example #1
0
def test_des_image():
    """Test the whole process with a DES CCD.
    """
    import os
    import fitsio

    image_file = 'y1_test/DECam_00241238_01.fits.fz'
    cat_file = 'y1_test/DECam_00241238_01_psfcat_tb_maxmag_17.0_magcut_3.0_findstars.fits'
    orig_image = galsim.fits.read(image_file)
    psf_file = os.path.join('output', 'pixel_des_psf.fits')

    if __name__ == '__main__':
        # These match what Gary used in fit_des.py
        nstars = None
        scale = 0.15
        size = 41
    else:
        # These are faster and good enough for the unit tests.
        nstars = 25
        scale = 0.2
        size = 21
    stamp_size = 51

    # The configuration dict with the right input fields for the file we're using.
    start_sigma = 1.0 / 2.355  # TODO: Need to make this automatic somehow.
    config = {
        'input': {
            'images': image_file,
            'image_hdu': 1,
            'weight_hdu': 3,
            'badpix_hdu': 2,
            'cats': cat_file,
            'cat_hdu': 2,
            'x_col': 'XWIN_IMAGE',
            'y_col': 'YWIN_IMAGE',
            'sky_col': 'BACKGROUND',
            'stamp_size': stamp_size,
            'ra': 'TELRA',
            'dec': 'TELDEC',
            'gain': 'GAINA',
        },
        'output': {
            'file_name': psf_file,
        },
        'psf': {
            'model': {
                'type': 'PixelGrid',
                'scale': scale,
                'size': size,
                'start_sigma': start_sigma,
            },
            'interp': {
                'type': 'BasisPolynomial',
                'order': 2,
            },
        },
    }
    if __name__ == '__main__': config['verbose'] = 3

    # These tests are slow, and it's really just doing the same thing three times, so
    # only do the first one when running via nosetests.
    if True:
        # Start by doing things manually:
        if __name__ == '__main__':
            logger = piff.config.setup_logger(2)
        else:
            logger = None

        # Largely copied from Gary's fit_des.py, but using the Piff input_handler to
        # read the input files.
        stars, wcs, pointing = piff.Input.process(config['input'],
                                                  logger=logger)
        if nstars is not None:
            stars = stars[:nstars]

        # Make model, force PSF centering
        model = piff.PixelGrid(scale=scale,
                               size=size,
                               interp=piff.Lanczos(3),
                               force_model_center=True,
                               start_sigma=start_sigma,
                               logger=logger)

        # Interpolator will be zero-order polynomial.
        # Find u, v ranges
        interp = piff.BasisPolynomial(order=2, logger=logger)

        # Make a psf
        psf = piff.SimplePSF(model, interp)
        psf.fit(stars, wcs, pointing, logger=logger)

        # The difference between the images of the fitted stars and the originals should be
        # consistent with noise.  Keep track of how many don't meet that goal.
        n_bad = 0  # chisq/dof > 2
        n_marginal = 0  # chisq/dof > 1.1
        n_good = 0  # chisq/dof <= 1.1
        # Note: The 2 and 1.1 values here are very arbitrary!

        for s in psf.stars:
            fitted = psf.drawStar(s)
            orig_stamp = orig_image[fitted.image.bounds] - s['sky']
            fit_stamp = fitted.image

            x0 = int(s['x'] + 0.5)
            y0 = int(s['y'] + 0.5)
            b = galsim.BoundsI(x0 - 3, x0 + 3, y0 - 3, y0 + 3)
            #print('orig center = ',orig_stamp[b].array)
            #print('flux = ',orig_stamp.array.sum())
            #print('fit center = ',fit_stamp[b].array)
            #print('flux = ',fit_stamp.array.sum())
            flux = fitted.fit.flux
            #print('max diff/flux = ',np.max(np.abs(orig_stamp.array-fit_stamp.array))/flux)
            #np.testing.assert_almost_equal(fit_stamp.array/flux, orig_stamp.array/flux, decimal=2)
            weight = s.weight  # These should be 1/var_pix
            resid = fit_stamp - orig_stamp
            chisq = np.sum(resid.array**2 * weight.array)
            print('chisq = ', chisq)
            print('cf. star.chisq, dof = ', s.fit.chisq, s.fit.dof)
            assert abs(chisq - s.fit.chisq) < 1.e-3 * chisq
            if chisq > 2. * s.fit.dof:
                n_bad += 1
            elif chisq > 1.1 * s.fit.dof:
                n_marginal += 1
            else:
                n_good += 1

            # Check the convenience function that an end user would typically use
            offset = s.center_to_offset(s.fit.center)
            image = psf.draw(x=s['x'],
                             y=s['y'],
                             stamp_size=stamp_size,
                             flux=s.fit.flux,
                             offset=offset)
            np.testing.assert_almost_equal(image.array,
                                           fit_stamp.array,
                                           decimal=4)

        print('n_good, marginal, bad = ', n_good, n_marginal, n_bad)
        # The real counts are 10 and 2.  So this says make sure any updates to the code don't make
        # things much worse.
        assert n_marginal <= 12
        assert n_bad <= 3

    # Use piffify function
    if __name__ == '__main__':
        print('start piffify')
        piff.piffify(config)
        print('read stars')
        stars, wcs, pointing = piff.Input.process(config['input'])
        print('read psf')
        psf = piff.read(psf_file)
        stars = [psf.model.initialize(s) for s in stars]
        flux = stars[0].fit.flux
        offset = stars[0].center_to_offset(stars[0].fit.center)
        fit_stamp = psf.draw(x=stars[0]['x'],
                             y=stars[0]['y'],
                             stamp_size=stamp_size,
                             flux=flux,
                             offset=offset)
        orig_stamp = orig_image[stars[0].image.bounds] - stars[0]['sky']
        # The first star happens to be a good one, so go ahead and test the arrays directly.
        np.testing.assert_almost_equal(fit_stamp.array / flux,
                                       orig_stamp.array / flux,
                                       decimal=2)

    # Test using the piffify executable
    config['verbose'] = 0
    with open('pixel_des.yaml', 'w') as f:
        f.write(yaml.dump(config, default_flow_style=False))
    if __name__ == '__main__':
        if os.path.exists(psf_file):
            os.remove(psf_file)
        piffify_exe = get_script_name('piffify')
        print('start piffify executable')
        p = subprocess.Popen([piffify_exe, 'pixel_des.yaml'])
        p.communicate()
        print('read stars')
        stars, wcs, pointing = piff.Input.process(config['input'])
        print('read psf')
        psf = piff.read(psf_file)
        stars = [psf.model.initialize(s) for s in stars]
        flux = stars[0].fit.flux
        offset = stars[0].center_to_offset(stars[0].fit.center)
        fit_stamp = psf.draw(x=stars[0]['x'],
                             y=stars[0]['y'],
                             stamp_size=stamp_size,
                             flux=flux,
                             offset=offset)
        orig_stamp = orig_image[stars[0].image.bounds] - stars[0]['sky']
        np.testing.assert_almost_equal(fit_stamp.array / flux,
                                       orig_stamp.array / flux,
                                       decimal=2)
Example #2
0
def test_single_image():
    """Test the whole process with a single image.

    Note: This test is based heavily on test_single_image in test_simple.py.
    """
    import os
    import fitsio
    np_rng = np.random.RandomState(1234)

    # Make the image
    image = galsim.Image(2048, 2048, scale=0.2)

    # The (x,y) values will be on a grid 5 x 5 stars with a random sub-pixel offset.
    xvals = np.linspace(50., 1950., 5)
    yvals = np.linspace(50., 1950., 5)
    x_list, y_list = np.meshgrid(xvals, yvals)
    x_list = x_list.flatten()
    y_list = y_list.flatten()
    x_list = x_list + (np_rng.rand(len(x_list)) - 0.5)
    y_list = y_list + (np_rng.rand(len(x_list)) - 0.5)
    print('x_list = ', x_list)
    print('y_list = ', y_list)
    # Range of fluxes from 100 to 15000
    flux_list = 100. * np.exp(5. * np_rng.rand(len(x_list)))
    print('fluxes range from ', np.min(flux_list), np.max(flux_list))

    # Draw a Moffat PSF at each location on the image.
    # Have the truth values vary quadratically across the image.
    beta_fn = lambda x, y: 3.5 - 0.1 * (x / 1000) + 0.08 * (y / 1000)**2
    fwhm_fn = lambda x, y: 0.9 + 0.05 * (x / 1000) - 0.03 * (
        y / 1000) + 0.02 * (x / 1000) * (y / 1000)
    e1_fn = lambda x, y: 0.02 - 0.01 * (x / 1000)
    e2_fn = lambda x, y: -0.03 + 0.02 * (x / 1000)**2 - 0.01 * (y / 1000) * 2

    for x, y, flux in zip(x_list, y_list, flux_list):
        beta = beta_fn(x, y)
        fwhm = fwhm_fn(x, y)
        e1 = e1_fn(x, y)
        e2 = e2_fn(x, y)
        print(x, y, beta, fwhm, e1, e2)
        moffat = galsim.Moffat(fwhm=fwhm, beta=beta, flux=flux).shear(e1=e1,
                                                                      e2=e2)
        bounds = galsim.BoundsI(int(x - 31), int(x + 32), int(y - 31),
                                int(y + 32))
        offset = galsim.PositionD(x - int(x) - 0.5, y - int(y) - 0.5)
        moffat.drawImage(image=image[bounds], offset=offset, method='no_pixel')
    print('drew image')

    # Write out the image to a file
    image_file = os.path.join('data', 'pixel_moffat_image.fits')
    image.write(image_file)
    print('wrote image')

    # Write out the catalog to a file
    dtype = [('x', 'f8'), ('y', 'f8')]
    data = np.empty(len(x_list), dtype=dtype)
    data['x'] = x_list
    data['y'] = y_list
    cat_file = os.path.join('data', 'pixel_moffat_cat.fits')
    fitsio.write(cat_file, data, clobber=True)
    print('wrote catalog')

    # Use InputFiles to read these back in
    input = piff.InputFiles(image_file, cat_file, stamp_size=32)
    assert input.image_files == [image_file]
    assert input.cat_files == [cat_file]
    assert input.x_col == 'x'
    assert input.y_col == 'y'

    # Check image
    input.readImages()
    assert len(input.images) == 1
    np.testing.assert_equal(input.images[0].array, image.array)

    # Check catalog
    input.readStarCatalogs()
    assert len(input.cats) == 1
    np.testing.assert_equal(input.cats[0]['x'], x_list)
    np.testing.assert_equal(input.cats[0]['y'], y_list)

    # Make stars
    orig_stars = input.makeStars()
    assert len(orig_stars) == len(x_list)
    assert orig_stars[0].image.array.shape == (32, 32)

    # Make a test star, not at the location of any of the model stars to use for each of the
    # below tests.
    x0 = 1024  # Some random position, not where a star was originally.
    y0 = 133
    beta = beta_fn(x0, y0)
    fwhm = fwhm_fn(x0, y0)
    e1 = e1_fn(x0, y0)
    e2 = e2_fn(x0, y0)
    moffat = galsim.Moffat(fwhm=fwhm, beta=beta).shear(e1=e1, e2=e2)
    target_star = piff.Star.makeTarget(x=x0, y=y0, scale=image.scale)
    test_im = galsim.ImageD(bounds=target_star.image.bounds, scale=image.scale)
    moffat.drawImage(image=test_im, method='no_pixel', use_true_center=False)
    print('made test star')

    # These tests are slow, and it's really just doing the same thing three times, so
    # only do the first one when running via nosetests.
    if True:
        # Process the star data
        model = piff.PixelGrid(0.2, 16, start_sigma=0.9 / 2.355)
        interp = piff.BasisPolynomial(order=2)
        if __name__ == '__main__':
            logger = piff.config.setup_logger(2)
        else:
            logger = None
        pointing = None  # wcs is not Celestial here, so pointing needs to be None.
        psf = piff.SimplePSF(model, interp)
        psf.fit(orig_stars, {0: input.images[0].wcs}, pointing, logger=logger)

        # Check that the interpolation is what it should be
        print('target.flux = ', target_star.fit.flux)
        test_star = psf.drawStar(target_star)
        #print('test_im center = ',test_im[b].array)
        #print('flux = ',test_im.array.sum())
        #print('interp_im center = ',test_star.image[b].array)
        #print('flux = ',test_star.image.array.sum())
        #print('max diff = ',np.max(np.abs(test_star.image.array-test_im.array)))
        np.testing.assert_almost_equal(test_star.image.array,
                                       test_im.array,
                                       decimal=3)

        # Check the convenience function that an end user would typically use
        image = psf.draw(x=x0, y=y0)
        np.testing.assert_almost_equal(image.array, test_im.array, decimal=3)

        # Round trip through a file
        psf_file = os.path.join('output', 'pixel_psf.fits')
        psf.write(psf_file, logger)
        psf = piff.read(psf_file, logger)
        assert type(psf.model) is piff.PixelGrid
        assert type(psf.interp) is piff.BasisPolynomial
        test_star = psf.drawStar(target_star)
        np.testing.assert_almost_equal(test_star.image.array,
                                       test_im.array,
                                       decimal=3)

        # Check the convenience function that an end user would typically use
        image = psf.draw(x=x0, y=y0)
        np.testing.assert_almost_equal(image.array, test_im.array, decimal=3)

    # Do the whole thing with the config parser
    config = {
        'input': {
            'images': image_file,
            'cats': cat_file,
            'x_col': 'x',
            'y_col': 'y',
            'stamp_size': 48  # Bigger than we drew, but should still work.
        },
        'output': {
            'file_name': psf_file
        },
        'psf': {
            'model': {
                'type': 'PixelGrid',
                'scale': 0.2,
                'size':
                16,  # Much smaller than the input stamps, but this is plenty here.
                'start_sigma': 0.9 / 2.355
            },
            'interp': {
                'type': 'BasisPolynomial',
                'order': 2
            },
        },
    }
    if __name__ == '__main__':
        print("Running piffify function")
        piff.piffify(config)
        psf = piff.read(psf_file)
        test_star = psf.drawStar(target_star)
        np.testing.assert_almost_equal(test_star.image.array,
                                       test_im.array,
                                       decimal=3)

    # Test using the piffify executable
    config['verbose'] = 0
    with open('pixel_moffat.yaml', 'w') as f:
        f.write(yaml.dump(config, default_flow_style=False))
    if __name__ == '__main__':
        print("Running piffify executable")
        if os.path.exists(psf_file):
            os.remove(psf_file)
        piffify_exe = get_script_name('piffify')
        p = subprocess.Popen([piffify_exe, 'pixel_moffat.yaml'])
        p.communicate()
        psf = piff.read(psf_file)
        test_star = psf.drawStar(target_star)
        np.testing.assert_almost_equal(test_star.image.array,
                                       test_im.array,
                                       decimal=3)
Example #3
0
def test_undersamp_shift():
    """Next: fit PSF to undersampled, dithered data with variable centroids,
    this time using chisq() and summing alpha,beta instead of fit() per star
    """
    # Pixelized model with Lanczos 3 interpolation, slightly smaller than data
    # than the data
    pixinterp = piff.Lanczos(3)
    influx = 150.
    du = 0.5
    mod = piff.PixelGrid(0.3,
                         25,
                         pixinterp,
                         start_sigma=1.3,
                         force_model_center=True)

    # Make a sample star just so we can pass the initial PSF into interpolator
    # Also store away a noiseless copy of the PSF, origin of focal plane
    s0 = make_gaussian_data(1.0, 0., 0., influx, du=0.5)
    s0 = mod.initialize(s0)

    # Interpolator will be constant
    interp = piff.BasisPolynomial(0)

    # Draw stars on a 2d grid of "focal plane" with 0<=u,v<=1
    positions = np.linspace(0., 1., 8)
    stars = []
    np_rng = np.random.RandomState(1234)
    rng = galsim.BaseDeviate(1234)
    for u in positions:
        for v in positions:
            # Nominal star centers move by +-1/2 pix, real centers another 1/2 pix
            phase1 = (0.5 - np_rng.rand(2)) * du
            phase2 = (0.5 - np_rng.rand(2)) * du
            if u == 0. and v == 0.:
                phase1 = phase2 = (0., 0.)
            s = make_gaussian_data(1.0,
                                   phase2[0],
                                   phase2[1],
                                   influx,
                                   noise=0.1,
                                   du=du,
                                   fpu=u,
                                   fpv=v,
                                   nom_u0=phase1[0],
                                   nom_v0=phase1[1],
                                   rng=rng)
            s = mod.initialize(s)
            ###print("phase:",phase2,'flux',s.fit.flux)###
            stars.append(s)

    # BasisInterp needs to be initialized before solving.
    interp.initialize(stars)

    oldchisq = 0.
    # Iterate solution using mean of chisq
    for iteration in range(10):
        # Refit PSFs star by star:
        stars = [mod.chisq(s) for s in stars]
        # Solve for interpolated PSF function
        interp.solve(stars)
        # Refit and recenter all stars
        stars = [mod.reflux(interp.interpolate(s)) for s in stars]
        chisq = np.sum([s.fit.chisq for s in stars])
        dof = np.sum([s.fit.dof for s in stars])
        print('iteration', iteration, 'chisq=', chisq, 'dof=', dof)
        if oldchisq > 0 and np.abs(oldchisq - chisq) < dof / 10.:
            break
        else:
            oldchisq = chisq

    # Now use the interpolator to produce a noiseless rendering
    s1 = interp.interpolate(s0)
    s1 = mod.reflux(s1)
    print('Flux, ctr after interpolation: ', s1.fit.flux, s1.fit.center,
          s1.fit.chisq)
    # Less than 2 dp of accuracy here!
    np.testing.assert_almost_equal(s1.fit.flux / influx, 1.0, decimal=1)

    s1 = mod.draw(s1)
    print('max image abs diff = ',
          np.max(np.abs(s1.image.array - s0.image.array)))
    print('max image abs value = ', np.max(np.abs(s0.image.array)))
    peak = np.max(np.abs(s0.image.array))
    np.testing.assert_almost_equal(s1.image.array / peak,
                                   s0.image.array / peak,
                                   decimal=1)
Example #4
0
def do_undersamp_drift(fit_centers=False):
    """Draw stars whose size and position vary across FOV.
    Fit to oversampled model with linear dependence across FOV.

    Argument fit_centers decides whether we are letting the PSF model
    center drift, or whether we re-fit the center positions of the stars.
    """
    # Pixelized model with Lanczos 3 interpolation, slightly smaller than data
    # than the data
    pixinterp = piff.Lanczos(3)
    influx = 150.
    du = 0.5
    mod = piff.PixelGrid(0.3,
                         25,
                         pixinterp,
                         start_sigma=1.3,
                         force_model_center=fit_centers)

    # Make a sample star just so we can pass the initial PSF into interpolator
    # Also store away a noiseless copy of the PSF, origin of focal plane
    s0 = make_gaussian_data(1.0, 0., 0., influx, du=0.5)
    s0 = mod.initialize(s0)

    # Interpolator will be linear
    interp = piff.BasisPolynomial(1)

    # Draw stars on a 2d grid of "focal plane" with 0<=u,v<=1
    positions = np.linspace(0., 1., 8)
    stars = []
    np_rng = np.random.RandomState(1234)
    rng = galsim.BaseDeviate(1234)
    for u in positions:
        for v in positions:
            # Nominal star centers move by +-1/2 pix
            phase1 = (0.5 - np_rng.rand(2)) * du
            phase2 = (0.5 - np_rng.rand(2)) * du
            if u == 0. and v == 0.:
                phase1 = phase2 = (0., 0.)
            # PSF center will drift with v; size drifts with u
            s = make_gaussian_data(1.0 + 0.1 * u,
                                   0.,
                                   0.5 * du * v,
                                   influx,
                                   noise=0.1,
                                   du=du,
                                   fpu=u,
                                   fpv=v,
                                   nom_u0=phase1[0],
                                   nom_v0=phase1[1],
                                   rng=rng)
            s = mod.initialize(s)
            ###print("phase:",phase2,'flux',s.fit.flux)###
            stars.append(s)

    # BasisInterp needs to be initialized before solving.
    interp.initialize(stars)

    oldchisq = 0.
    # Iterate solution using mean of chisq
    for iteration in range(20):
        # Refit PSFs star by star:
        stars = [mod.chisq(s) for s in stars]
        # Solve for interpolated PSF function
        interp.solve(stars)
        # Refit and recenter all stars
        stars = [mod.reflux(interp.interpolate(s)) for s in stars]
        chisq = np.sum([s.fit.chisq for s in stars])
        dof = np.sum([s.fit.dof for s in stars])
        print('iteration', iteration, 'chisq=', chisq, 'dof=', dof)
        if oldchisq > 0 and np.abs(oldchisq - chisq) < dof / 10.:
            break
        else:
            oldchisq = chisq

    # Now use the interpolator to produce a noiseless rendering
    s1 = interp.interpolate(s0)
    s1 = mod.reflux(s1)
    print('Flux, ctr after interpolation: ', s1.fit.flux, s1.fit.center,
          s1.fit.chisq)
    # Less than 2 dp of accuracy here!
    np.testing.assert_almost_equal(s1.fit.flux / influx, 1.0, decimal=1)

    s1 = mod.draw(s1)
    print('max image abs diff = ',
          np.max(np.abs(s1.image.array - s0.image.array)))
    print('max image abs value = ', np.max(np.abs(s0.image.array)))
    peak = np.max(np.abs(s0.image.array))
    np.testing.assert_almost_equal(s1.image.array / peak,
                                   s0.image.array / peak,
                                   decimal=1)
Example #5
0
def test_missing():
    """Next: fit mean PSF to multiple images, with missing pixels.
    """
    # Pixelized model with Lanczos 3 interpolation, slightly smaller than data
    # than the data
    pixinterp = piff.Lanczos(3)
    mod = piff.PixelGrid(0.5,
                         25,
                         pixinterp,
                         start_sigma=1.5,
                         force_model_center=False)

    # Draw stars on a 2d grid of "focal plane" with 0<=u,v<=1
    positions = np.linspace(0., 1., 4)
    influx = 150.
    stars = []
    np_rng = np.random.RandomState(1234)
    rng = galsim.BaseDeviate(1234)
    for u in positions:
        for v in positions:
            # Draw stars in focal plane positions around a unit ring
            s = make_gaussian_data(1.0,
                                   0.,
                                   0.,
                                   influx,
                                   noise=0.1,
                                   du=0.5,
                                   fpu=u,
                                   fpv=v,
                                   rng=rng)
            s = mod.initialize(s)
            # Kill 10% of each star's pixels
            bad = np_rng.rand(*s.image.array.shape) < 0.1
            s.weight.array[bad] = 0.
            s.image.array[bad] = -999.
            s = mod.reflux(s, fit_center=False)  # Start with a sensible flux
            stars.append(s)

    # Also store away a noiseless copy of the PSF, origin of focal plane
    s0 = make_gaussian_data(1.0, 0., 0., influx, du=0.5)
    s0 = mod.initialize(s0)

    if __name__ == "__main__":
        interps = [piff.Polynomial(order=0), piff.BasisPolynomial(order=0)]
    else:
        # The Polynomial interpolator works, but it's slow.  For the nosetests runs, skip it.
        interps = [piff.BasisPolynomial(order=0)]

    for interp in interps:
        # Interpolator will be simple mean
        interp = piff.Polynomial(order=0)
        interp.initialize(stars)

        oldchisq = 0.
        # Iterate solution using interpolator
        for iteration in range(40):
            # Refit PSFs star by star:
            for i, s in enumerate(stars):
                stars[i] = mod.fit(s)
            # Run the interpolator
            interp.solve(stars)
            # Install interpolator solution into each
            # star, recalculate flux, report chisq
            chisq = 0.
            dof = 0
            for i, s in enumerate(stars):
                s = interp.interpolate(s)
                s = mod.reflux(s)
                chisq += s.fit.chisq
                dof += s.fit.dof
                stars[i] = s
                ###print('   chisq=',s.fit.chisq, 'dof=',s.fit.dof)
            print('iteration', iteration, 'chisq=', chisq, 'dof=', dof)
            if oldchisq > 0 and chisq < oldchisq and oldchisq - chisq < dof / 10.:
                break
            else:
                oldchisq = chisq

        # Now use the interpolator to produce a noiseless rendering
        s1 = interp.interpolate(s0)
        s1 = mod.reflux(s1)
        print('Flux, ctr after interpolation: ', s1.fit.flux, s1.fit.center,
              s1.fit.chisq)
        # Less than 2 dp of accuracy here!
        np.testing.assert_almost_equal(s1.fit.flux / influx, 1.0, decimal=1)

        s1 = mod.draw(s1)
        print('max image abs diff = ',
              np.max(np.abs(s1.image.array - s0.image.array)))
        print('max image abs value = ', np.max(np.abs(s0.image.array)))
        peak = np.max(np.abs(s0.image.array))
        np.testing.assert_almost_equal(s1.image.array / peak,
                                       s0.image.array / peak,
                                       decimal=1)