Exemplo n.º 1
0
    def mouse_press_optimize(self, event):
        self.event = event
        p = self._pt_xyz(event)

        if p is not None:
            print "Optimizing particle near", p
            n = self.state.obj_closest_particle(p)
            old_err = self.state.error
            _ = opt.do_levmarq_particles(self.state, np.array([n]), max_iter=2)
            new_err = self.state.error
            print '{}->{}'.format(old_err, new_err)

        self.state.set_tile(self.state.oshape)
        self.set_field()
        self.draw()
Exemplo n.º 2
0
def check_add_particles(st,
                        guess,
                        rad='calc',
                        do_opt=True,
                        im_change_frac=0.2,
                        min_derr='3sig',
                        **kwargs):
    """
    Checks whether to add particles at a given position by seeing if adding
    the particle improves the fit of the state.

    Parameters
    ----------
    st : :class:`peri.states.State`
        The state to check adding particles to.
    guess : [N,3] list-like
        The positions of particles to check to add.
    rad : {Float, ``'calc'``}, optional.
        The radius of the newly-added particles. Default is ``'calc'``,
        which uses the states current radii's median.
    do_opt : Bool, optional
        Whether to optimize the particle position before checking if it
        should be kept. Default is True (optimizes position).
    im_change_frac : Float
        How good the change in error needs to be relative to the change in
        the difference image. Default is 0.2; i.e. if the error does not
        decrease by 20% of the change in the difference image, do not add
        the particle.
    min_derr : Float or '3sig'
        The minimal improvement in error to add a particle. Default
        is ``'3sig' = 3*st.sigma``.

    Returns
    -------
    accepts : Int
        The number of added particles
    new_poses : [N,3] list
        List of the positions of the added particles. If ``do_opt==True``,
        then these positions will differ from the input 'guess'.
    """
    # FIXME does not use the **kwargs, but needs b/c called with wrong kwargs
    if min_derr == '3sig':
        min_derr = 3 * st.sigma
    accepts = 0
    new_poses = []
    if rad == 'calc':
        rad = guess_add_radii(st)
    message = ('-' * 30 + 'ADDING' + '-' * 30 +
               '\n  Z\t  Y\t  X\t  R\t|\t ERR0\t\t ERR1')
    with log.noformat():
        CLOG.info(message)
    for a in range(guess.shape[0]):
        p0 = guess[a]
        absent_err = st.error
        absent_d = st.residuals.copy()
        ind = st.obj_add_particle(p0, rad)
        if do_opt:
            # the slowest part of this
            opt.do_levmarq_particles(st,
                                     ind,
                                     damping=1.0,
                                     max_iter=1,
                                     run_length=3,
                                     eig_update=False,
                                     include_rad=False)
        present_err = st.error
        present_d = st.residuals.copy()
        dont_kill = should_particle_exist(absent_err,
                                          present_err,
                                          absent_d,
                                          present_d,
                                          im_change_frac=im_change_frac,
                                          min_derr=min_derr)
        if dont_kill:
            accepts += 1
            p = tuple(st.obj_get_positions()[ind].ravel())
            r = tuple(st.obj_get_radii()[ind].ravel())
            new_poses.append(p)
            part_msg = '%2.2f\t%3.2f\t%3.2f\t%3.2f\t|\t%4.3f  \t%4.3f' % (
                p + r + (absent_err, st.error))
            with log.noformat():
                CLOG.info(part_msg)
        else:
            st.obj_remove_particle(ind)
            if np.abs(absent_err - st.error) > 1e-4:
                raise RuntimeError('updates not exact?')
    return accepts, new_poses
Exemplo n.º 3
0
def add_subtract_misfeatured_tile(st,
                                  tile,
                                  rad='calc',
                                  max_iter=3,
                                  invert='guess',
                                  max_allowed_remove=20,
                                  minmass=None,
                                  use_tp=False,
                                  **kwargs):
    """
    Automatically adds and subtracts missing & extra particles in a region
    of poor fit.

    Parameters
    ----------
    st: :class:`peri.states.State`
        The state to add and subtract particles to.
    tile : :class:`peri.util.Tile`
        The poorly-fit region to examine.
    rad : Float or 'calc', optional
        The initial radius for added particles; added particles radii are
        not fit until the end of add_subtract. Default is ``'calc'``, which
        uses the median radii of active particles.
    max_iter : Int, optional
        The maximum number of loops for attempted adds at one tile location.
        Default is 3.
    invert : {'guess', True, False}, optional
        Whether to invert the image for feature_guess -- True for dark
        particles on a bright background, False for bright particles. The
        default is to guess from the state's current particles.
    max_allowed_remove : Int, optional
        The maximum number of particles to remove. If the misfeatured tile
        contains more than this many particles, raises an error. If it
        contains more than half as many particles, logs a warning. If more
        than this many particles are added, they are optimized in blocks of
        ``max_allowed_remove``. Default is 20.

    Other Parameters
    ----------------
    im_change_frac : Float on [0, 1], optional.
        If adding or removing a particle decreases the error less than
        ``im_change_frac``*the change in the image, the particle is deleted.
        Default is 0.2.

    min_derr : {Float, ``'3sig'``}, optional
        The minimum change in the state's error to keep a particle in the
        image. Default is ``'3sig'`` which uses ``3*st.sigma``.

    do_opt : Bool, optional
        Set to False to avoid optimizing particle positions after adding
        them. Default is True.

    minmass : Float, optional
        The minimum mass for a particle to be identified as a feature, as
        used by trackpy. Defaults to a decent guess.

    use_tp : Bool, optional
        Set to True to use trackpy to find missing particles inside the
        image. Not recommended since trackpy deliberately cuts out particles
        at the edge of the image. Default is False.

    Outputs
    -------
    n_added : Int
        The change in the number of particles, i.e. ``n_added-n_subtracted``
    ainds: List of ints
        The indices of the added particles.

    Notes
    --------
    The added/removed positions returned are whether or not the
    position has been added or removed ever. It's possible/probably that
    a position is added, then removed during a later iteration.

    Algorithm is:
    1.  Remove all particles within the tile.
    2.  Feature and add particles to the tile.
    3.  Optimize the added particles positions only.
    4.  Run 2-3 until no particles have been added.
    5.  Optimize added particle radii
    Because all the particles are removed within a tile, it is important
    to set max_allowed_remove to a reasonable value. Otherwise, if the
    tile is the size of the image it can take a long time to remove all
    the particles and re-add them.
    """
    if rad == 'calc':
        rad = guess_add_radii(st)
    if invert == 'guess':
        invert = guess_invert(st)
    # 1. Remove all possibly bad particles within the tile.
    initial_error = np.copy(st.error)
    rinds = np.nonzero(tile.contains(st.obj_get_positions()))[0]
    if rinds.size >= max_allowed_remove:
        CLOG.fatal('Misfeatured region too large!')
        raise RuntimeError
    elif rinds.size >= max_allowed_remove / 2:
        CLOG.warn('Large misfeatured regions.')
    elif rinds.size > 0:
        rpos, rrad = st.obj_remove_particle(rinds)

    # 2-4. Feature & add particles to the tile, optimize, run until none added
    n_added = -rinds.size
    added_poses = []
    for _ in range(max_iter):
        if invert:
            im = 1 - st.residuals[tile.slicer]
        else:
            im = st.residuals[tile.slicer]
        guess, _ = _feature_guess(im, rad, minmass=minmass, use_tp=use_tp)
        accepts, poses = check_add_particles(st,
                                             guess + tile.l,
                                             rad=rad,
                                             do_opt=True,
                                             **kwargs)
        added_poses.extend(poses)
        n_added += accepts
        if accepts == 0:
            break
    else:  # for-break-else
        CLOG.warn('Runaway adds or insufficient max_iter')

    # 5. Optimize added pos + rad:
    ainds = []
    for p in added_poses:
        ainds.append(st.obj_closest_particle(p))
    if len(ainds) > max_allowed_remove:
        for i in range(0, len(ainds), max_allowed_remove):
            opt.do_levmarq_particles(st,
                                     np.array(ainds[i:i + max_allowed_remove]),
                                     include_rad=True,
                                     max_iter=3)
    elif len(ainds) > 0:
        opt.do_levmarq_particles(st, ainds, include_rad=True, max_iter=3)

    # 6. Ensure that current error after add-subtracting is lower than initial
    did_something = (rinds.size > 0) or (len(ainds) > 0)
    if did_something & (st.error > initial_error):
        CLOG.info('Failed addsub, Tile {} -> {}'.format(
            tile.l.tolist(), tile.r.tolist()))
        if len(ainds) > 0:
            _ = st.obj_remove_particle(ainds)
        if rinds.size > 0:
            for p, r in zip(rpos.reshape(-1, 3), rrad.reshape(-1)):
                _ = st.obj_add_particle(p, r)
        n_added = 0
        ainds = []
    return n_added, ainds
Exemplo n.º 4
0
def add_subtract(st,
                 max_iter=7,
                 max_npart='calc',
                 max_mem=2e8,
                 always_check_remove=False,
                 **kwargs):
    """
    Automatically adds and subtracts missing & extra particles.

    Operates by removing bad particles then adding missing particles on
    repeat, until either no particles are added/removed or after `max_iter`
    attempts.

    Parameters
    ----------
    st: :class:`peri.states.State`
        The state to add and subtract particles to.
    max_iter : Int, optional
        The maximum number of add-subtract loops to use. Default is 7.
        Terminates after either max_iter loops or when nothing has changed.
    max_npart : Int or 'calc', optional
        The maximum number of particles to add before optimizing the non-psf
        globals. Default is ``'calc'``, which uses 5% of the initial number
        of particles.
    max_mem : Int, optional
        The maximum memory to use for optimization after adding max_npart
        particles. Default is 2e8.
    always_check_remove : Bool, optional
        Set to True to always check whether to remove particles. If ``False``,
        only checks for removal while particles were removed on the previous
        attempt. Default is False.

    Other Parameters
    ----------------
    invert : Bool, optional
        ``True`` if the particles are dark on a bright background, ``False``
        if they are bright on a dark background. Default is ``True``.
    min_rad : Float, optional
        Particles with radius below ``min_rad`` are automatically deleted.
        Default is ``'calc'`` = median rad - 25* radius std.
    max_rad : Float, optional
        Particles with radius above ``max_rad`` are automatically deleted.
        Default is ``'calc'`` = median rad + 15* radius std, but you should
        change this for your particle sizes.

    min_edge_dist : Float, optional
        Particles closer to the edge of the padded image than this are
        automatically deleted. Default is 2.0.
    check_rad_cutoff : 2-element float list.
        Particles with ``radii < check_rad_cutoff[0]`` or ``> check...[1]``
        are checked if they should be deleted (not automatic). Default is
        ``[3.5, 15]``.
    check_outside_im : Bool, optional
        Set to True to check whether to delete particles whose positions are
        outside the un-padded image.

    rad : Float, optional
        The initial radius for added particles; added particles radii are
        not fit until the end of ``add_subtract``. Default is ``'calc'``,
        which uses the median radii of active particles.

    tries : Int, optional
        The number of particles to attempt to remove or add, per iteration.
        Default is 50.

    im_change_frac : Float, optional
        How good the change in error needs to be relative to the change in
        the difference image. Default is 0.2; i.e. if the error does not
        decrease by 20% of the change in the difference image, do not add
        the particle.

    min_derr : Float, optional
        The minimum change in the state's error to keep a particle in the
        image. Default is ``'3sig'`` which uses ``3*st.sigma``.

    do_opt : Bool, optional
        Set to False to avoid optimizing particle positions after adding.
    minmass : Float, optional
        The minimum mass for a particle to be identified as a feature,
        as used by trackpy. Defaults to a decent guess.

    use_tp : Bool, optional
        Set to True to use trackpy to find missing particles inside the
        image. Not recommended since trackpy deliberately cuts out particles
        at the edge of the image. Default is ``False``.

    Returns
    -------
    total_changed : Int
        The total number of adds and subtracts done on the data. Not the
        same as ``changed_inds.size`` since the same particle or particle
        index can be added/subtracted multiple times.
    added_positions : [N_added,3] numpy.ndarray
        The positions of particles that have been added at any point in the
        add-subtract cycle.
    removed_positions : [N_added,3] numpy.ndarray
        The positions of particles that have been removed at any point in
        the add-subtract cycle.

    Notes
    ------
    Occasionally after the intial featuring a cluster of particles is
    featured as 1 big particle. To fix these mistakes, it helps to set
    max_rad to a physical value. This removes the big particle and allows
    it to be re-featured by (several passes of) the adds.

    The added/removed positions returned are whether or not the position
    has been added or removed ever. It's possible that a position is
    added, then removed during a later iteration.
    """
    if max_npart == 'calc':
        max_npart = 0.05 * st.obj_get_positions().shape[0]

    total_changed = 0
    _change_since_opt = 0
    removed_poses = []
    added_poses0 = []
    added_poses = []

    nr = 1  # Check removal on the first loop
    for _ in range(max_iter):
        if (nr != 0) or (always_check_remove):
            nr, rposes = remove_bad_particles(st, **kwargs)
        na, aposes = add_missing_particles(st, **kwargs)
        current_changed = na + nr
        removed_poses.extend(rposes)
        added_poses0.extend(aposes)
        total_changed += current_changed
        _change_since_opt += current_changed
        if current_changed == 0:
            break
        elif _change_since_opt > max_npart:
            _change_since_opt *= 0
            CLOG.info('Start add_subtract optimization.')
            opt.do_levmarq(st,
                           opt.name_globals(
                               st, remove_params=st.get('psf').params),
                           max_iter=1,
                           run_length=4,
                           num_eig_dirs=3,
                           max_mem=max_mem,
                           eig_update_frequency=2,
                           rz_order=0,
                           use_accel=True)
            CLOG.info('After optimization:\t{:.6}'.format(st.error))

    # Optimize the added particles' radii:
    for p in added_poses0:
        i = st.obj_closest_particle(p)
        opt.do_levmarq_particles(st, np.array([i]), max_iter=2, damping=0.3)
        added_poses.append(st.obj_get_positions()[i])
    return total_changed, np.array(removed_poses), np.array(added_poses)