; $Id: starfinder.pro, v 1.8.2b_dev01 Sep 2024 e.d. $
;
;+
; NAME:
;   STARFINDER
;
; PURPOSE:
;   Given a stellar field and an approximation of the Point Spread Function
;   (PSF), detect the stellar sources and estimate their position and flux.
;   The image may be contaminated by a smooth non-uniform background emission.
;   If the imaged field is not isoplanatic, it is possible to partition it
;   into approximately isoplanatic sub-regions, provided a local estimate of
;   the PSF is available. In this case, the field is analyzed as a whole as
;   in the constant PSF case, with the only difference that the local PSF is
;   used to analyze each star. 
;   A spatially variable PSF may also be handled by providing a parametric 
;   PSF model variable across the imaged field.
;
; CATEGORY:
;   Signal processing. Stellar fields astrometry and photometry.
;
; CALLING SEQUENCE:
;   STARFINDER, Image, Psf, Threshold, Min_correlation, $
;               X, Y, Fluxes, Sigma_x, Sigma_y, Sigma_f, Correlation
;
; INPUTS:
;   Image:    2D image of the stellar field
;
;   Psf:  2D array, representing the Point Spread Function of the stellar
;     field. If the PSF is space-variant, Psf is a 3D stack of local
;     PSF measurements, relative to a partition of the imaged field into
;     sub-regions arranged in a regular grid. In this case it is necessary
;     to supply the bounds of the partition (see keyword SV_PAR).
;
;   Threshold:    Vector of lower detection levels (above the local background,
;     which is temporarly removed before detection). These levels are
;     assumed to be "absolute" levels by default. If the keyword
;     REL_THRESHOLD (see below) is set and the noise standard deviation is
;     defined (see the keyword NOISE_STD) below, then the threshold levels
;     are assumed to be "relative", i.e. expressed in units of the noise
;     standard deviation.
;     Example 1: let Threshold be = [10., 3.]
;      If the keyword REL_THRESHOLD is not defined or the keyword
;      parameter NOISE_STD is undefined, the threshold levels 10 and 3
;      are absolute intensity levels, i.e. all the peaks brighter than 10
;      and 3 counts are considered as presumed stars. If REL_THRESHOLD is
;      set and NOISE_STD is defined, the intensity levels for detection
;      are respectively (10 * NOISE_STD) and (3 * NOISE_STD).
;     The number of elements of the vector Threshold establishes the number
;     of iterations of the "basic step" (see PROCEDURE below).
;     The components of the vector Threshold may be all equal or decreasing.
;
;   Min_correlation:  Minimum value of correlation between an acceptable
;     stellar image and the Psf. It must be > 0 and < 1.
;
; KEYWORD PARAMETERS:
;   SV_PAR:   Set this keyword to a structure defining the bounds of the
;     imaged field partition when the PSF is not isoplanatic.
;     Let the imaged field be partitioned into Nx*Ny sub-regions, such
;     that the (j, i)-th region is bounded by
;     Lx[j] < x < Ux[j], Ly[i] < y < Uy[i], where
;     j = 0, ..., Nx - 1; i = 0, ..., Ny - 1.
;     The structure must be defined as follows:
;     SV_PAR = {Lx: Lx, Ux: Ux, Ly: Ly, Uy: Uy}.
;     The (j, i)-th sub-region must correspond to the k-th psf in the 3D
;     stack, where k = i*Nx + j.
;     
;   PSF_TYPE: Set this keyword to a string indicating the name of a PSF 
;     model. Available PSF models are defined in the documentation of the 
;     routine IMAGE_MODEL (see documentation of the input parameter Psf in 
;     the file IMAGE_MODEL.PRO). This keyword has to be used together with 
;     the keyword PSF_DATA.
;     The keywords PSF_TYPE and PSF_DATA are useful to define a parametric 
;     PSF model, that can be made variable across the field of view, 
;     depending on how it is defined in IMAGE_MODEL.PRO.
;     When PSF_TYPE and PSF_DATA are used, the input parameter Psf and the 
;     keyword parameter SV_PAR described above have to be supplied anyway, 
;     as they are used in the preliminary phases of the analysis.
;   
;   PSF_DATA: Use this keyword to provide a pointer to the auxiliary data
;     required by the PSF model defined by the keyword PSF_TYPE, when it is 
;     used. Auxiliary data are defined according to the documentation of the 
;     input parameter Data in the file IMAGE_MODEL.PRO.
;
;   REL_THRESHOLD:    Set this keyword to specify that the detection levels
;     contained in the input parameter Threshold have to be considered as
;     "relative" levels, i.e. they must be multiplied by the noise standard
;     deviation. This keyword is effective only if the NOISE_STD is defined
;     (see below).
;
;   X_BAD, Y_BAD: Coordinates of bad pixels to be excluded.
;     It is important to mask bad pixels, especially in the following phases
;     of the analysis:
;     1) search for presumed stars
;     2) correlation check
;     3) local fitting.
;
;   BACKGROUND:   2D array, containing an initial guess of the image background
;     emission. If the keyword is undefined, the background is estimated
;     automatically, provided the box size BACK_BOX (see below) is defined.
;     The background estimate is subtracted before searching for presumed
;     stars in the image and before computing the correlation coefficient.
;
;   BACK_BOX: Set this keyword to a scalar value specifying the box size
;     to estimate the background emission. For more details see the routine
;     ESTIMATE_BACKGROUND in the file "estimate_background.pro".
;     The value of this keyword may be conveniently defined as a multiple
;     of the PSF Full Width at Half Maximum (e.g. 5 * FWHM).
;     If if is not supplied, the program uses the argument of the keyword
;     BACKGROUND (if defined of course!) as a background estimate. However
;     this will prevent any improvements in the background knowledge in the
;     course of iterations. If the stellar field is supposed to have no
;     background emission, the keywords BACKGROUND and BACK_BOX should be
;     left undefined.
;
;   For other keywords concerning background estimation see the routines
;   IMAGE_BACKGROUND and ESTIMATE_BACKGROUND.
;
;   FOUR: Set this keyword to identify relative maxima referring only to
;     the 4-neighbors of each pixel. The default is to use 8-neighbors.
;
;   PRE_SMOOTH:   Set this keyword to smooth the image with a standard low-pass
;     filter before searching for local maxima. This reduces the probability
;     to pick up noise spikes, which would increase the computation time for
;     further checks and the probability of false detections.
;     For more details see the procedure SEARCH_OBJECTS, in the file
;     "search_objects.pro".
;
;   MINIF:    If PRE_SMOOTH is set, the keyword MINIF may be used to enter the
;     integer minification factor used to down-sample the image before
;     searching for local maxima, as an alternative smoothing strategy to
;     the standard low-pass filter used by default when PRE_SMOOTH is set.
;
;   CORREL_MAG:   Set this keyword to an integer scalar specifying the sub-pixel
;     accuracy with which the template PSF should be superposed to a given
;     object when computing the correlation coefficient.
;     If CORREL_MAG = 1, the PSF is superposed to the
;     object just taking the maximum intensity pixel as a reference.
;     If CORREL_MAG is > 1, all the possible fractional shifts of the PSF,
;     with step size of 1/CORREL_MAG, are considered.
;     The default value is CORREL_MAG = 2 (half pixel positioning).
;
;   DEBLEND:  Set this keyword to a nonzero value to perform de-blending
;     of the objects detected iterating the basic step (see PROCEDURE).
;     This de-blending strategy allows the algorithm to detect secondary
;     components of crowded groups, whose principal component has previously
;     been detected as a single star. This strategy may be referred to as
;     'partial de-blending'.
;
;   DEBLOST:  Set this keyword to a nonzero value to recover all the
;     significant intensity enhancement lost in the course of the analysis,
;     in order to search for possible blends. This strategy allows the
;     algorithm to re-analyze the blended objects whose principal component
;     has not been previously found as a single star (see DEBLEND above).
;     This strategy, applied after the 'partial de-blending' described with
;     the keyword DEBLEND above, may be referred to as 'full de-blending'.
;     More details can be found in the PROCEDURE description below.
;
;   CUT_THRESHOLD:    Set this keyword to a scalar value in the range ]0, 1[
;     to specify the relative threshold to use when "cutting" a given
;     object to measure its area. The default is CUT_THRESHOLD = 0.5,
;     i.e. the object is cut at 50% of the peak height. Notice that the
;     background is subtracted beforehands. Notice also that if the
;     absolute value of the threshold for binary detection is comparable
;     to the gaussian noise level (see also keywords GAUSSIAN_NOISE and
;     BIN_N_STDEV), the estimated area of the object is not reliable: thus
;     deblending is not applied. This check on the threshold value is
;     performed only if the gaussian noise standard deviation is defined
;     (see the keyword GAUSSIAN_NOISE).
;     For more details on the determination of the area, see the routine
;     PEAK_AREA in the file "peak_area.pro".
;
;   BIN_N_STDEV:  Set this keyword to a positive scalar to specify the
;     minimum cutting threshold for binary detection, in units of the
;     gaussian noise standard deviation. The default value is
;     BIN_N_STDEV = 3.
;
;   BIN_THRESHOLD:    When a crowded group (in the simplest case a close binary
;     star) is analyzed, the deblending algorithm searches for residuals
;     representing secondary components by subtracting the known sources:
;     The brightest local maximum in the residual sub-image is taken as a
;     candidate secondary star. In principle the detection threshold used
;     for isolated objects, set by the parameter Threshold described above,
;     should not be applied in this case, because the residual is affected
;     by the poor knowledge about the subtracted components.
;     BIN_THRESHOLD specifies the relative threshold, in units of the
;     parameter Threshold, to use for the detection of candidate secondary
;     sources in crowded groups. The default is BIN_THRESHOLD = 0, i.e. all
;     the positive residuals (remember that the local background level has
;     been removed!) are considered. Their intensity is compared to the
;     detection threshold after fitting: if the fitted intensity happens to;
;     be below the value set by Threshold, the new secondary component is
;     rejected.
;
;   BIN_TOLERANCE:    An object is classified as "blend" if the relative error;
;     between its area and the area of the PSF is greater than the threshold
;     fixed by this keyword. The default is BIN_TOLERANCE = 0.2, i.e. 20%.
;
;   NOISE_STD:    Set this keyword to a 2D array, with the same size as Image,
;     representing the noise standard deviation in each pixel of the input
;     data. This array is used to compute the formal errors on astrometry
;     and photometry.
;
;   NO_SLANT: Set this keyword to avoid fitting the local background with
;     a slanting plane. In this case the background to use for the local
;     fit of the stars is derived from the 2D background array and it is
;     kept fixed in the fitting. This option should be used only if the
;     (input) estimate of the image background is very accurate.
;
;   MIN_DISTANCE: Minimum distance, expressed in units of the PSF FWHM, to
;     consider two sources as separated. This parameter is used to check the
;     outcome of the fitting of a group of neighboring sources: if two sources
;     are closer than this limit, the fit is considered unacceptable and the
;     last detected source is rejected as a false detection.
;     The default value is 1 (which means 1 PSF FWHM).
;
;   N_ITER:   At the end of the analysis, the detected stars are fitted again
;     a number of times equal to the value specified with this keyword.
;     The default is N_ITER = 1. To avoid re-fitting, set N_ITER = 0.
;     Notice that the final number of re-fitting iterations includes also
;     the extra re-fitting which is normally performed after each basic
;     step (see also the keyword NO_INTERMEDIATE_ITER).
;
;   NO_INTERMEDIATE_ITER: The currently detected stars are re-fitted after
;     each basic step. Set NO_INTERMEDIATE_ITER to avoid this re-fitting.
;
;   ASTROMETRIC_TOL, PHOTOMETRIC_TOL: When the last re-fitting is iterated
;     a lot of times, it is possible to stop the iterations when convergence
;     on stellar positions and fluxes is achieved. The default values for
;     convergence are ASTROMETRIC_TOL = 0.01 pixels and
;     PHOTOMETRIC_TOL = 0.01 (i.e. 1%).
;     In practice setting N_ITER = 1 or 2 is generally enough to obtain
;     reliable astrometry and photometry, without having to iterate a lot.
;
;   X_INPUT, Y_INPUT, F_INPUT: vectors with positions and fluxes of the stars,
;     when these are already known. When these parameters are defined, no
;     search is performed: the known sources are just fitted a pre-fixed number
;     of times (see N_ITER) or until convergence (see ASTROMETRIC_TOL and
;     PHOTOMETRIC_TOL). The sources are sorted by decreasing flux. 
;     Usual checks applied in fitting procedure are applied (minimum flux 
;     above threshold, minimum distance between any two stars). 
;     When this option is used, single-component fitting is performed, i.e. 
;     stars closer than the fitting box width are fit one at a time.
;     Input sources for which the fit fails are rejected.
;     *** Recommendation. When this option is used, the following keyword 
;         should be set: 
;         MIN_DISTANCE = 0.0
;     In order to avoid optimizing the input positions set the keyword /FLUXOPT 
;     (see below). In this case, the positions are kept fixed to the input values, 
;     only fluxes are optimized.
;     
;   FLUXOPT: Set this binary keyword to keep the positions of the sources 
;     fixed in the optimization process when an input list of sources is given 
;     (see keywords X_INPUT, Y_INPUT, F_INPUT above).
;
;   C_INPUT: when an input list of sources is given (see keywords X_INPUT, 
;     Y_INPUT, F_INPUT above), the keyword C_INPUT may be used to provide the 
;     correlation coefficients of the sources (detemined in a previous detection 
;     run). These correlation coefficients are just retained on output, associated 
;     to the sources for which the fit process is successful.
;
;   SVDINV: The fitting process is performed by a Newton-Gauss method. 
;     This method requires a matrix inversion at every iteration. 
;     Set this keyword to perform this matrix inversion by SVD (Singular 
;     Value Decomposition). The default is matrix inversion by Moore-Penrose 
;     Generalized Inverse (see routine 'ginv.pro').
;     
;   SILENT: Set this keyword to avoid printing messaged to the output window.
;     If not set, STARFINDER prints messages about the current analysis step and 
;     about the candidate and detected stars. "Candidate stars" are the peaks 
;     found in the image; "Detected stars" are the peaks which are confirmed to be 
;     stars in the analysis. The number of "Candidate stars" does not account for 
;     the additional candidate peaks found with the deblending procedure. 
;
; OUTPUTS:
;   X, Y: Coordinates of detected stars in pixel units.
;     The origin is at (0, 0).
;
;   Fluxes:   Vector of stellar fluxes, referred to the normalization of the Psf.
;
;   Sigma_x, Sigma_y, Sigma_f:    Vector of formal errors on positions and
;     fluxes, estimated by the fitting procedure.
;     These vectors have all components equal to zero if no information is
;     supplied about noise: in this case it has no meaning to estimate the
;     error propagation in the fit.
;
;   Correlation:  Vector of correlation coefficients for accepted stars.
;     Stars belonging to crowded groups detected with the de-blending
;     procedure have a correlation coefficient of -1.
;
; OPTIONAL OUTPUTS:
;   BACKGROUND:   Last estimate of the image background, computed by the
;     program after the iteration corresponding to the lowest
;     detection level.
;
;   STARS:    2D array, containing one shifted scaled replica of the PSF for
;     each detected star.
;
; SIDE EFFECTS:
;   If the keyword NOISE_STD is set to a scalar value it is transformed into
;   a 2D constant array with the same size as the input image.
;
; RESTRICTIONS:
;   1) The algorithm is based on the assumption that the input Image may
;   be considered as a superpositions of stellar sources and a smooth
;   background.
;   2) Saturated stars should have been approximately repaired beforehands.
;   This is especially important for the reliable detection and analysis of
;   fainter sources in their surrounding.
;   3) High accuracy astrometry and photometry are based on sub-pixel
;   positioning of the input PSF. In this version of the algorithm this
;   relies on interpolation techniques, which are not suited to undersampled
;   data. Thus the algorithm, at least in its present form, should not be
;   used with poorly sampled data.
;   4) The keyword /FLUXOPT should be used only when a list of input sources 
;   is given and the input position estimates are very accurate.
;
; PROCEDURE:
;     The standard analysis is accomplished as a sequence of "basic steps".
;   One of these steps consists of 3 phases:
;   1) detection of presumed stars above a given threshold
;   2) check and analysis of detected objects, sorted by decreasing
;      intensity
;   3) re_fitting
;   Pixel (x,y) is the approximate center of a presumed star if
;   a) Image(x,y) is a local maximum
;   b) Image(x,y) > Background(x,y) + Stars(x,y) + Threshold,
;   where Image is the stellar field, Background is an approximation of
;   the background emission, Stars is a sum of shifted scaled replicas
;   of the PSF (one for each detected star) and Threshold is the
;   minimum central intensity of a detectable star. In practice the user
;   provides a set of Threshold levels (generally decreasing!): the
;   number of these levels fixes the number of times the basic step is
;   repeated.
;   At the first iteration of the basic step, the "image model" called
;   Stars is identically zero; in later iterations it contains the detected
;   sources. Subtraction of these stars simplifies the detection of fainter
;   objects and allows a better estimation of the image Background.
;   It should be noticed that the model of detected stars (Stars) is
;   subtracted only in order to detecte new presumed stars and refine the
;   background, whereas the subsequent astrometric and photometric analysis
;   is performed on the original Image.
;     Let us describe in detail how the presumed stars are analyzed (Phase
;   2 of each basic step).
;   First of all each object in the newly formed list is "re-identified",
;   i.e. searched again after subtraction of other brighter sources: this
;   allows to reject many spurious detections associated to PSF features
;   of bright stars. The object is compared with the PSF by a correlation
;   check and accepted only if a sufficient similarity is found. Then it is
;   fitted to estimate its position and flux. The local fitting is performed
;   by FITSTARS (see the file "fitstars.pro" for more details), which takes
;   into consideration all the following contributions:
;   a) other known stars in the fitting box, which are fitted along with
;   the object under examination (multiple fitting)
;   b) known stars lying outside the fitting box: this term is extracted
;   from the image model Stars and considered as a fixed contribution
;   c) the local background, which may be approximated either with a
;   slanting plane (whose coefficients are optimized as well) or with a
;   sub-image extracted from the image Background (kept fixed).
;   If the fit is successful, the parameters of the stars are saved and
;   the image model Stars is updated.
;     The basic step (phases 1 + 2 + 3) may be iterated: a new list of
;   objects is formed by searching in the image after subtraction of the
;   previously detected stars. Then the analysis proceeds on the original
;   frame. This iteration is very useful to detect hidden objects, e.g.
;   close binaries down to separations comparable to 1 PSF FWHM.
;     An optional deblending strategy is available, consisting of two
;   separate phases. The first step (see keyword DEBLEND) consists of a search
;   for secondary sources around the objects detected in the earlier phases of
;   the analysis; in the second step (see keyword DEBLOST) all the significant
;   intensity enhancement previously discarded are recovered and a check is
;   performed in order to find among these some possible blends. Blends are
;   recognized on the basis of their larger extension as compared to the PSF.
;   The area of a given object is measured by a thresholding technique, applied
;   at a specified threshold below the peak. When an object is recognized as a
;   blend, deblending is attempted iteratively by searching for a new residual
;   and subsequent fitting. The iteration is stopped when no more residual is
;   found or the fitting of the last residual is not successful. The deblending
;   procedure is applied at the end of the last iteration of the basic step
;   described above, i.e. when all the "resolved" objects are supposed to have
;   been detected.
;     It should be stressed that step 3 (re-fitting) has the goal to improve 
;   the astrometric and photometric measurement of the detected stars. If the 
;   fit of a given source fails, however, the source (which has already been 
;   identified as a true one) is not rejected: its parameters are simply 
;   set to their previous value, without any update.
;
; MODIFICATION HISTORY:
;   Written by:  Emiliano Diolaiti, August-September 1999.
;   Updates:
;   1) New partial deblending strategy (Emiliano Diolaiti, October 1999).
;   2) Full deblending strategy (Emiliano Diolaiti, February 2000).
;   3) Added PSF stack option; this version might not handle properly
;      very close groups of objects lying on the boundary of two adjacent
;      sub-regions (Emiliano Diolaiti, February 2000).
;   4) Modified keyword parameters in auxiliary routines to avoid trouble
;      under IDL 5.0 (Emiliano Diolaiti, May 2000).
;   5) Modified STARFINDER_FIT module. Now faster.
;      (Emiliano Diolaiti, December 2000).
;   6) Moved auxiliary routines to handle the list of stars in the
;      program module STARLIST. (Emiliano Diolaiti, June 2001).
;   7) Modified STARFINDER_DEBLEND module. Now faster.
;      (Emiliano Diolaiti, June 2001).
;   8) PSF stack option (see modification no. 3): fixed problem with stars
;      lying close to boundary between adjacent sub-regions
;      (E.D., December 2004). DEBLEND option not checked with PSF stack option.
;   9) Increased minimum distance between two stars to 1 FWHM.
;   10) Increased box width for distance check in STARFINDER_CHECK module
;      (after suggestion by Jessica Lu, May 2005).
;   11) Temporarily removed modification no. 10 and restored previous version
;       for trouble at image edge.
;   12) Added keywords X_INPUT, Y_INPUT, F_INPUT for fitting a set of known
;       stars. Not yet tested with space-variant PSF (E. D., February 2006).
;   13) Added keyword MIN_DISTANCE, to set minimum acceptable distance between
;       two stars (E. D., August 2006).
;   14) Fixed parameter value: fitting box size was not properly set with 
;       keywords X_INPUT, Y_INPUT, F_INPUT (E. D., March 2012).
;   15) When an input list of sources is given (keywords X_INPUT, Y_INPUT, 
;       F_INPUT), the sources for which the fit fails are rejected
;       (E. D., March 2012).
;   16) Keyword "re_fitting" turned into a positional parameters in modules 
;       STARFINDER_FIT, STARFINDER_DEBLEND, STARFINDER_ANALYZE (E. D., March 2012).
;   17) Fixed bug about keyword MIN_DIST: 0 value was not accepted before
;       (E. D., March 2012).
;   18) Added keyword SVDINV (E. D., March 2012).
;   19) Changed some output messages in VERBOSE mode (E.D., March 2012).
;   20) Created separate source files for all auxiliary routines described 
;       at point 6) (E. D., March 2012).
;   21) Changed default value of keyword CUT_THRESHOLD = 0.5 (E.D., March 2012).
;   22) Added keyword MAG=3 in call to PEAK_WIDTH in module 
;       STARFINDER_DEB_PAR (E.D., March 2012).
;   23) Added keyword FLUXOPT (E.D., April 2012).
;   24) Removed check MAX_DIST in module STARFINDER_DEBLEND (E.D., April 2012).
;   25) Set default value of keyword CORREL_MAG = 2 (E.D., April 2012).
;   26) Removed bug in STARFINDER_FIT module: sometimes very close sources 
;       were swapped by call to COMPARE_LISTS (E.D., April 2012).
;   27) Updated documentation (E. D., May 2014).
;   28)  Modified data type of variable "list_of_stars":
;        from array of structures to structure of arrays (E.D., September 2024).
;-



;;; Auxiliary procedures/functions to handle list of stars data structure.


;FUNCTION star
;
;PRO update_list, list, SUBSCRIPTS = s, x, y, f, c, $
;                  sigma_x, sigma_y, sigma_f, IS_STAR = is_star
;
;FUNCTION create_element, x, y, f
;
;FUNCTION merge_list, l1, l2
;
;FUNCTION add_subscript, subscripts, s
;
;FUNCTION delete_element, list
;
;PRO star_param, list, SUBSCRIPTS = s, $
;                 n, x, y, f, c, sigma_x, sigma_y, sigma_f
;
;FUNCTION where_stars, list, LX = lx, UX = ux, LY = ly, UY = uy, n
;
;FUNCTION extract_stars, list, n
;
;FUNCTION sort_list, list, SUBSCRIPTS = s
;
;FUNCTION reverse_class, list, SUBSCRIPTS = s
;
;FUNCTION starlist, n, x, y, f, c
;



;;; Auxiliary procedures/functions to define the program's parameters.

; STARFINDER_FWHM: compute PSF FWHM.

FUNCTION starfinder_fwhm, psf, n_psf

    on_error, 2
    fw = fltarr(n_psf)
    for  n = 0L, n_psf - 1  do  fw[n] = fwhm(psf[*,*,n], /CUBIC, MAG = 3)
    return, fw
end

; DEFINE_CORR_BOX: define correlation box size.

FUNCTION define_corr_box, psf_fwhm

    on_error, 2
    box = round(1.5 * psf_fwhm)
    box = box + 1 - box mod 2
    return, box
end

; DEFINE_FIT_BOX: define fitting box size.

FUNCTION define_fit_box, psf_fwhm, WIDER = wider

    on_error, 2
    if  keyword_set(wider)  then  b = 2.  else  b = 1.
    box = round(define_corr_box(psf_fwhm) > 5 + b * psf_fwhm)
    box = box + 1 - box mod 2
    return, box
end

; STARFINDER_ID_PAR: define parameters to re-identify presumed stars.

FUNCTION starfinder_id_par, psf_fwhm, FOUR = four

    on_error, 2
    box = define_fit_box(psf_fwhm)
    neigh4 = keyword_set(four) and 1B
    return, {box: box, neigh4: neigh4}
end

; STARFINDER_CORR_PAR: define parameters for correlation.

FUNCTION starfinder_corr_par, psf, psf_fwhm, n_psf, CORREL_MAG = correl_mag, $
                              _EXTRA = extra

    on_error, 2
    correlation_box = define_corr_box(psf_fwhm)
    search_box = correlation_box / 2
    search_box = search_box + 1 - search_box mod 2
    if  n_elements(correl_mag) eq 0  then  correl_mag = 2
    if  correl_mag gt 1  then  edge = 2  else  edge = 0
    temp_siz = correlation_box + 2 * edge
    template = ptrarr(n_psf, /ALLOCATE)
    if  correl_mag gt 1  then  templates = ptrarr(n_psf, /ALLOCATE) $
    else begin
       templates = 0  &  dx = 0  &  dy = 0
    endelse
    for  n = 0L, n_psf - 1  do begin
       temp = sub_array(psf[*,*,n], temp_siz[n])
       if  correl_mag gt 1  then begin
          shifted_templates, temp, correl_mag, _EXTRA = extra, EDGE = edge, $
                             templs, dx, dy
          temp = sub_array(temp, correlation_box[n])
          *templates[n] = templs
       endif
       *template[n] = temp
    endfor
    return, {correl_mag: correl_mag, $
             correlation_box: correlation_box, $
             search_box: search_box, $
             template: template, $
             templates: templates, dx: dx, dy: dy}
end

; STARFINDER_FIT_PAR: define parameters for fitting.

FUNCTION starfinder_fit_par, psf, psf_fwhm, n_psf, MIN_DISTANCE = min_dist, _EXTRA = extra

    on_error, 2
    fitting_box = define_fit_box(psf_fwhm, _EXTRA = extra)
    psf_size = 2 * max(fitting_box) < (max((size52(psf, /DIM))[0:1]) - 2)
;   edge = round((fitting_box - psf_fwhm) / 2.0) > 1
    edge = 0.5
    if n_elements(min_dist) eq 0 then min_dist = 1.0
    min_distance = min_dist * psf_fwhm
    fitting_psf = fltarr(psf_size, psf_size, n_psf)
    psf_max = fltarr(n_psf)
    for  n = 0L, n_psf - 1  do begin
       fitting_psf[*,*,n] = sub_array(psf[*,*,n], psf_size)
       psf_max[n] = max(psf[*,*,n])
    endfor
    return, {fitting_box: fitting_box, edge: edge, $
             min_distance: min_distance, $
             fitting_psf: fitting_psf, psf: psf, $
             psf_max: psf_max, psf_fwhm: psf_fwhm}
end

; STARFINDER_DEB_PAR: define parameters for deblending.

FUNCTION starfinder_deb_par, psf, psf_fwhm, n_psf, $
                             BIN_THRESHOLD = bin_threshold, $
                             CUT_THRESHOLD = cut, $
                             BIN_N_STDEV = n_std, BIN_TOLERANCE = tol

    on_error, 2
    if  n_elements(bin_threshold) eq 0  then  bin_threshold = 0.
    if  n_elements(cut) eq 0  then  cut = 0.5  &  cut = cut > 0 < 1 ;cut = cut > 0.2 < 1
    if  n_elements(n_std) eq 0  then  n_std = 3
    if  n_elements(tol) eq 0  then  tol = 0.2  &  tol = tol > 0 < 1
    box = lonarr(n_psf)  &  area = fltarr(n_psf)
    for  n = 0L, n_psf - 1  do begin
       box[n] = round(3 * peak_width(psf[*,*,n], REL = cut, MAG = 3))
       area[n] = peak_area(psf[*,*,n], REL = cut, MAG = 3)
    endfor
    return, {bin_threshold: bin_threshold, n_std: n_std, $
             box: box, cut: cut, tol: tol, psf_area: area}
end

; STARFINDER_DEALLOC: de-allocate heap variables.

PRO starfinder_dealloc, corr_par, fit_data, model_data

    on_error, 2
    if  n_elements(corr_par) eq 0  then  return
    if  (ptr_valid(corr_par.template))[0]  then  ptr_free, corr_par.template
    if  (ptr_valid(corr_par.templates))[0]  then  ptr_free, corr_par.templates
    if  ptr_valid(fit_data)  then  ptr_free, fit_data
    if  ptr_valid(model_data)  then  ptr_free, model_data
    return
end



;;; Other auxiliary procedures/functions.

; STARFINDER_BAD: find bad pixels in the sub-image [lx:ux,ly:uy].

PRO starfinder_bad, x_bad, y_bad, lx, ux, ly, uy, x_bad_here, y_bad_here

    on_error, 2
    w = where(x_bad ge lx and x_bad le ux and $
              y_bad ge ly and y_bad le uy, n)
    if  n ne 0  then begin
       x_bad_here = x_bad[w] - lx  &  y_bad_here = y_bad[w] - ly
    endif
    return
end

; STARFINDER_BOXES: extract boxes from image, background and image model.

PRO starfinder_boxes, image, background, stars, x, y, boxsize, $
                      lx, ux, ly, uy, i_box, b_box, s_box, $
                      x_bad, y_bad, x_bad_here, y_bad_here

    on_error, 2
    i_box = sub_array(image, boxsize, REFERENCE = [x, y], $
                      LX = lx, UX = ux, LY = ly, UY = uy)
    s_box = stars[lx:ux,ly:uy]  &  b_box = background[lx:ux,ly:uy]
    if  n_elements(x_bad) ne 0 and n_elements(y_bad) ne 0  then $
       starfinder_bad, x_bad, y_bad, lx, ux, ly, uy, x_bad_here, y_bad_here
    return
end

; STARFINDER_ID: re-identification of a presumed star.

PRO starfinder_id, image, background, stars, sv_par, id_par, $
                   min_intensity, x, y, found

    on_error, 2
    if  n_elements(sv_par) ne 0  then $
       r = pick_region(sv_par.lx, sv_par.ux, sv_par.ly, sv_par.uy, x, y) $
    else  r = 0
    ; Extract boxes
    starfinder_boxes, image, background, stars, x, y, id_par.box[r], $
                      lx, ux, ly, uy, i_box, b_box, s_box
    ; Search
    max_search, i_box - b_box - s_box, min_intensity[lx:ux,ly:uy], $
                X0 = x - lx, Y0 = y - ly, n, x, y, $
                /NEAREST, /MAXIMUM, FOUR = id_par.neigh4
    found = n ne 0
    if  found  then begin
       x = x[0] + lx  &  y = y[0] + ly
    endif
    return
end

; STARFINDER_CORRELATE: correlation check.

PRO starfinder_correlate, image, background, stars, sv_par, x_bad, y_bad, $
                          corr_par, min_correlation, x, y, correl, accepted

    on_error, 2
    if  n_elements(sv_par) ne 0  then $
       r = pick_region(sv_par.lx, sv_par.ux, sv_par.ly, sv_par.uy, x, y) $
    else  r = 0
    ; Extract boxes
    boxsize = corr_par.correlation_box[r] + corr_par.search_box[r] + 1
    starfinder_boxes, image, background, stars, x, y, boxsize, $
                      lx, ux, ly, uy, i_box, b_box, s_box, x_bad, y_bad, xb, yb
    ; Compute correlation
    if  corr_par.correl_mag gt 1  then begin
       templates = *corr_par.templates[r]  &  dx = corr_par.dx  &  dy = corr_par.dy
    endif
    correlate_max, i_box - b_box - s_box, *corr_par.template[r], x - lx, y - ly, $
                   corr_par.search_box[r], X_BAD = xb, Y_BAD = yb, $
                   TEMPLATES = templates, DX = dx, DY = dy, correl, x, y
    x = x + lx  &  y = y + ly
    accepted = correl ge min_correlation and $
               x ge lx and x le ux and y ge ly and y le uy
    return
end

; STARFINDER_CHECK: check outcome of local fitting.

FUNCTION starfinder_check, fit_error, x_fit, y_fit, f_fit, $
                           lx, ux, ly, uy, list, min_intensity, fit_par, r

    on_error, 2
    ; Check convergence of fit, minimum acceptable value of fluxes,
    ; range of positions
;    padd = 2.0   ; added after suggestion by Jessica Lu
    padd = 0.0
    good = fit_error ge 0 and $
           min(x_fit) ge lx-padd and max(x_fit) le ux+padd and $
           min(y_fit) ge ly-padd and max(y_fit) le uy+padd
    if  good  then begin
       threshold = min_intensity[round(x_fit),round(y_fit)] / fit_par.psf_max[r]
       good = min(f_fit - threshold) ge 0
       good = min(f_fit - threshold) ge 0
    endif
    if  good  then begin
       ; Consider the subset of stars in a neighborhood of [lx:ux,ly:uy]
       ; and check their reciprocal distances
       s = where_stars(LX = lx - fit_par.min_distance[r], $
                       UX = ux + fit_par.min_distance[r], $
                       LY = ly - fit_par.min_distance[r], $
                       UY = uy + fit_par.min_distance[r], list, n)                
       star_param, list, SUBSCRIPTS = s, n, x, y, f
       if  n gt 1  then $
          good = min(reciprocal_distance(x, y)) ge fit_par.min_distance[r]
    endif
    return, good
end

; STARFINDER_FIT: local fitting.

PRO starfinder_fit, list, this_max, image, siz, background, stars, noise_std, $
                    sv_par, x_bad, y_bad, fit_par, fit_data, model_data, $
                    min_intensity, NO_SLANT = no_slant, $
                    re_fitting, _EXTRA = extra, star_here, $
                    PSF_TYPE = psf_type, PSF_DATA = psf_data

    on_error, 2
    star_param, list, SUBSCRIPTS = this_max, n, x, y
    if  n_elements(sv_par) ne 0  then $
       r = pick_region(sv_par.lx, sv_par.ux, sv_par.ly, sv_par.uy, x, y) $
    else  r = 0
    ; Extract boxes
    starfinder_boxes, image, background, stars, x, y, fit_par.fitting_box[r], $
                      lx, ux, ly, uy, i_box, b_box, s_box, x_bad, y_bad, xb, yb
    if  n_elements(xb) ne 0 and n_elements(yb) ne 0  then $
       w_bad = coord_to_subs(xb, yb, (size52(i_box, /DIM))[0])
    ; Select known stars into i_box and subtract them from the image model
    s = where_stars(LX = lx + fit_par.edge, UX = ux - fit_par.edge, $
                    LY = ly + fit_par.edge, UY = uy - fit_par.edge, list, n)
    if  re_fitting ne 0  then begin
;       if  n eq 0  then  s = this_max  ; edge star
;       Modified March 2012
;       if  n eq 0  then begin
       if n eq 0 or re_fitting eq 2 then begin
          s = this_max  ; edge star or single-component fitting
          n = 1
       endif
       s_and_this = s
    endif else  s_and_this = add_subscript(s, this_max)
    star_param, list, SUBSCRIPTS = s, n, x_add, y_add, f_add, c
    if  n ne 0  then $
       if keyword_set(psf_type) then $
          stars = image_model(x_add, y_add, -f_add, siz[0], siz[1], psf_type, $
                              psf_data, _EXTRA = extra, MODEL = stars) else $
       if n_elements(sv_par) ne 0 then $
          stars = image_model(x_add, y_add, -f_add, siz[0], siz[1], fit_par.psf, $
                              LX = sv_par.lx, UX = sv_par.ux, LY = sv_par.ly, UY = sv_par.uy, $
                              model_data, _EXTRA = extra, MODEL = stars) else $
          stars = image_model(x_add, y_add, -f_add, siz[0], siz[1], fit_par.psf, $
                              model_data, _EXTRA = extra, MODEL = stars)
    ; Fixed contribution for local fitting
    contrib = stars[lx:ux,ly:uy]   ; stars outside fitting box
    if  keyword_set(no_slant)  then begin
       contrib = contrib + b_box  &  b_box = 0
    endif
    ; Local fitting
    if  re_fitting ne 0  then begin
       x = x_add  &  y = y_add  &  f0 = f_add
    endif else $
; Modified March 2012: n is the number of known sources in the box
       star_param, list, SUBSCRIPTS = s_and_this, n_and_this, x, y, f, c
;       star_param, list, SUBSCRIPTS = s_and_this, n, x, y, f, c
    x0 = x - lx  &  y0 = y - ly
    if n_elements(noise_std) ne 0 then noise = noise_std[lx:ux,ly:uy]
    if n_elements(sv_par) ne 0 then begin
       sv_lx = sv_par.lx - lx
       sv_ux = sv_par.ux - lx
       sv_ly = sv_par.ly - ly
       sv_uy = sv_par.uy - ly
    endif
    if keyword_set(psf_type) then begin
    xori = (*psf_data).xori
    yori = (*psf_data).yori
    (*psf_data).xori = lx
    (*psf_data).yori = ly
    fitstars, i_box, FIXED = contrib, psf_type, $
              PSF_DATA = psf_data, x0, y0, F0 = f0, BACKGROUND = b_box, $
              NO_SLANT = no_slant, NOISE_STD = noise, _EXTRA = extra, $
              BAD_DATA = w_bad, x, y, f, b, fit_error, sigma_x, sigma_y, sigma_f
    (*psf_data).xori = xori
    (*psf_data).yori = yori
    endif else $
    fitstars, i_box, FIXED = contrib, fit_par.fitting_psf, $
              PSF_DATA = fit_data, x0, y0, F0 = f0, BACKGROUND = b_box, $
              NO_SLANT = no_slant, NOISE_STD = noise, _EXTRA = extra, $
              BAD_DATA = w_bad, LX = sv_lx, UX = sv_ux, LY = sv_ly, UY = sv_uy, $
              x, y, f, b, fit_error, sigma_x, sigma_y, sigma_f
;    Modified April 2012
;    x_out = x  &  y_out = y
;    compare_lists, x0, y0, x_out, y_out, x0_out, y0_out, x, y, SUBSCRIPTS_2 = w
    compare_lists, x0, y0, x, y, SUBSCRIPTS_2 = w
    x = x[w] + lx  &  y = y[w] + ly  &  f = f[w]
    if  n_elements(sigma_f) ne 0  then begin
       sigma_x = sigma_x[w]
       sigma_y = sigma_y[w]
       sigma_f = sigma_f[w]
    endif
    ; Is the examined max a star? Assume it is, then perform checks
    temp_list = list
    update_list, temp_list, SUB = s_and_this, x, y, f, c, $
                 sigma_x, sigma_y, sigma_f, /IS_STAR
    star_here = starfinder_check(fit_error, x, y, f, lx, ux, ly, uy, $
                                 temp_list, min_intensity, fit_par, r)
    ; Update list of stars and image model
    if  star_here  then begin
       list = temp_list
       x_add = x  &  y_add = y  &  f_add = f
    endif else if re_fitting eq 2 then begin
       list = reverse_class(list, SUBSCRIPT = this_max)
       n = n - 1
       if n gt 0 then begin
          w = where(s_and_this ne this_max[0])
          star_param, list, SUBSCRIPTS = s_and_this[w], n, x_add, y_add, f_add, c
       endif
    endif
    if n gt 0 or re_fitting eq 0 then $ ; should be equivalent to "n gt 0 or re_fitting lt 2"
    if keyword_set(psf_type) then $
       stars = image_model(x_add, y_add, f_add, siz[0], siz[1], psf_type, $
                           psf_data, _EXTRA = extra, MODEL = stars) else $
    if n_elements(sv_par) ne 0 then $
       stars = image_model(x_add, y_add, f_add, siz[0], siz[1], fit_par.psf, $
                           LX = sv_par.lx, UX = sv_par.ux, LY = sv_par.ly, UY = sv_par.uy, $
                           model_data, _EXTRA = extra, MODEL = stars) else $
       stars = image_model(x_add, y_add, f_add, siz[0], siz[1], fit_par.psf, $
                           model_data, _EXTRA = extra, MODEL = stars)
    return
end

; STARFINDER_BLEND: check the area of a presumed star to identify blends.

FUNCTION starfinder_blend, image, background, stars, noise_std, $
                           deb_par, x, y, r

    on_error, 2
    starfinder_boxes, image, background, stars, x, y, deb_par.box[r], $
                      lx, ux, ly, uy, i_box, b_box, s_box
    ima = i_box - b_box - s_box      ; after this, it should be positive!
    x0 = x - lx  &  y0 = y - ly
    threshold = ima[x0,y0] * deb_par.cut
    check = 1B
    if  n_elements(noise_std) ne 0  then $
       check = threshold gt deb_par.n_std * noise_std[x,y]
    if  check  then begin
       area = peak_area(ima, X = x0, Y = y0, MAG = 3, ABS_THRESHOLD = threshold)
       check = relative_error(deb_par.psf_area[r], area) gt deb_par.tol
    endif
    return, check
end

; STARFINDER_DEBLEND: de-blend a crowded group of stars
; (partial or full de-blending).

PRO starfinder_deblend, list, this_max, image, siz, background, stars, $
                        noise_std, sv_par, x_bad, y_bad, id_par, fit_par, $
                        deb_par, fit_data, model_data, min_intensity, $
                        re_fitting, AROUND = around, _EXTRA = extra, $
                        PSF_TYPE = psf_type, PSF_DATA = psf_data

    on_error, 2
    star_param, list, SUB = this_max, n, x, y, f
    x0 = round(x)  &  y0 = round(y)
    if  n_elements(sv_par) ne 0  then $
       r = pick_region(sv_par.lx, sv_par.ux, sv_par.ly, sv_par.uy, x, y) $
    else  r = 0
    ; Is the present object a blend?
    if  keyword_set(around)  then $
       if keyword_set(psf_type) then $
          stars = image_model(x, y, -f, siz[0], siz[1], psf_type, $
                              psf_data, _EXTRA = extra, MODEL = stars) else $
       if n_elements(sv_par) ne 0 then $
          stars = image_model(x, y, -f, siz[0], siz[1], fit_par.psf, $
                              LX = sv_par.lx, UX = sv_par.ux, LY = sv_par.ly, UY = sv_par.uy, $
                              model_data, _EXTRA = extra, MODEL = stars) else $
          stars = image_model(x, y, -f, siz[0], siz[1], fit_par.psf, $
                              model_data, _EXTRA = extra, MODEL = stars)
;       stars = image_model(x, y, -f, siz[0], siz[1], fit_par.psf[*,*,r], $
;                        model_data, _EXTRA = extra, MODEL = stars)
    blended = starfinder_blend(image, background, stars, noise_std, $
                               deb_par, x0, y0, r)
    if  keyword_set(around)  then $
       if keyword_set(psf_type) then $
          stars = image_model(x, y, f, siz[0], siz[1], psf_type, $
                              psf_data, _EXTRA = extra, MODEL = stars) else $
       if n_elements(sv_par) ne 0 then $
          stars = image_model(x, y, f, siz[0], siz[1], fit_par.psf, $
                              LX = sv_par.lx, UX = sv_par.ux, LY = sv_par.ly, UY = sv_par.uy, $
                              model_data, _EXTRA = extra, MODEL = stars) else $
          stars = image_model(x, y, f, siz[0], siz[1], fit_par.psf, $
                              model_data, _EXTRA = extra, MODEL = stars)
;       stars = image_model(x, y, f, siz[0], siz[1], fit_par.psf[*,*,r], $
;                         model_data, _EXTRA = extra, MODEL = stars)
    if  not blended  then  return  ; not a blend
    ncomp = 0L  &  found = ncomp eq 0  &  star_here = found
    if  keyword_set(around)  then  ncomp = ncomp + 1
;    max_dist = 1.25 * fit_par.psf_fwhm[r]
    while  star_here  do begin
       x = x0  &  y = y0
       if  ncomp ne 0  then begin
          ; Identify another component of the blend
          starfinder_boxes, image, background, stars, x, y, deb_par.box[r], $
                            lx, ux, ly, uy, i_box, b_box, s_box
          max_search, i_box - b_box - s_box, $
                      deb_par.bin_threshold * min_intensity[lx:ux,ly:uy], $
                      X0 = x - lx, Y0 = y - ly, CHECK_DIST = max_dist, $
                      /MAXIMUM, FOUR = id_par.neigh4, n, x, y
          found = n ne 0  &  star_here = found
          if  found  then begin
             x = x[0] + lx  &  y = y[0] + ly
          endif
       endif
       if  found  then begin
          ; Fit the new detected component
          if  ncomp ne 0  then begin
             list = merge_list(list, create_element(x, y, 0))
             index = n_elements(list) - 1
          endif else  index = this_max
          starfinder_fit, list, index, image, siz, background, stars, $
                          noise_std, sv_par, x_bad, y_bad, fit_par, fit_data, $
                          model_data, min_intensity, re_fitting, _EXTRA = extra, $
                          star_here, PSF_TYPE = psf_type, PSF_DATA = psf_data
       endif
       if  star_here  then  ncomp = ncomp + 1
    endwhile
    if  ncomp eq 1 and not keyword_set(around)  then begin
       ; The object was mis-recognized as a blend: it is single
       star_param, list, SUB = this_max, n, x, y, f
       if keyword_set(psf_type) then $
          stars = image_model(x, y, -f, siz[0], siz[1], psf_type, $
                              psf_data, _EXTRA = extra, MODEL = stars) else $
       if n_elements(sv_par) ne 0 then $
          stars = image_model(x, y, -f, siz[0], siz[1], fit_par.psf, $
                              LX = sv_par.lx, UX = sv_par.ux, LY = sv_par.ly, UY = sv_par.uy, $
                              model_data, _EXTRA = extra, MODEL = stars) else $
          stars = image_model(x, y, -f, siz[0], siz[1], fit_par.psf, $
                              model_data, _EXTRA = extra, MODEL = stars)
;       stars = image_model(x, y, -f, siz[0], siz[1], fit_par.psf[*,*,r], $
;                         model_data, _EXTRA = extra)
       list = reverse_class(list, SUBSCRIPT = this_max)
    endif
    return
end

; STARFINDER_ANALYZE: analyze the object list[this_max].

PRO starfinder_analyze, list, this_max, image, siz, background, stars, $
                        noise_std, sv_par, x_bad, y_bad, id_par, corr_par, $
                        fit_par, fit_data, model_data, $
                        min_intensity, min_correlation, re_fitting, _EXTRA = extra

    on_error, 2
    star_param, list, SUB = this_max, n, x, y
    ; Is the maximum list[this_max] a feature of an already detected star?
    starfinder_id, image, background, stars, sv_par, id_par, $
                   min_intensity, x, y, check
    if  not check  then  return           ; yes, it is
    ; Correlation check
    starfinder_correlate, image, background, stars, sv_par, x_bad, y_bad, $
                          corr_par, min_correlation, x, y, c, check
    if  not check  then  return   ; too low correlation
    ; Fit
    update_list, list, SUB = this_max, x, y, 0, c
    starfinder_fit, list, this_max, image, siz, background, stars, noise_std, $
                    sv_par, x_bad, y_bad, fit_par, fit_data, model_data, $
                    min_intensity, re_fitting, _EXTRA = extra
    return
end

; STARFINDER_CONVERG: check convergence of positions and fluxes for Phase 3.

FUNCTION starfinder_converg, list0, list, $
        ASTROMETRIC_TOL = a_tol, PHOTOMETRIC_TOL = ph_tol

    on_error, 2
    if  n_elements(a_tol)  eq 0  then  a_tol  = 1e-2;1e-4;0.01
    if  n_elements(ph_tol) eq 0  then  ph_tol = 1e-2;1e-4;0.01
    s = where_stars(list, n)
    if  n eq 0  then  return, n eq 0
    star_param, list0, SUB = s, n, x0, y0, f0
    star_param, list,  SUB = s, n, x,  y,  f
    check = convergence(x0, x, a_tol, /ABSOLUTE) and $
         convergence(y0, y, a_tol, /ABSOLUTE) and $
         convergence(f0, f, ph_tol)
    return, check
end



;;; The main routine.

PRO starfinder, $
    image, psf, SV_PAR = sv_par, $
    X_BAD = x_bad, Y_BAD = y_bad, $
    BACKGROUND = background, BACK_BOX = back_box, $
    threshold, REL_THRESHOLD = rel_threshold, NOISE_STD = noise_std, $
    min_correlation, DEBLEND = deblend, DEBLOST = deblost, _EXTRA = extra, $
    N_ITER = n_iter, NO_INTERMEDIATE_ITER = no_intermediate, SILENT = silent, $
    x, y, fluxes, sigma_x, sigma_y, sigma_f, correlation, STARS = stars, $
    X_INPUT = x_in, Y_INPUT = y_in, F_INPUT = f_in, C_INPUT = c_in, $
    PSF_TYPE = psf_type, PSF_DATA = psf_data

    catch, error
    if  error ne 0  then begin
       starfinder_dealloc, corr_par, fit_data, model_data
       return
    endif

    ; Define background and image model
    siz = size52(image, /DIM)
    if  n_elements(background) eq 0  then $
       if  n_elements(back_box) eq 0  then $
          background = fltarr(siz[0], siz[1])  else $
          background = estimate_background(image, back_box, _EXTRA = extra)
    if  n_elements(noise_std) ne 0  then $
       if  n_elements(noise_std) ne n_elements(image)  then $
          noise_std = replicate(noise_std[0], siz[0], siz[1])
    stars = fltarr(siz[0], siz[1])

    ; Define some program parameters and default values
    re_fitting = 0 ; re_fitting = 0 --> normal mode
    if  n_elements(x_in) ne 0 and n_elements(y_in) ne 0 and n_elements(f_in) ne 0 then $
       re_fitting = 2 ; re_fitting = 2 --> fit of input list of sources
    if  n_elements(n_iter) eq 0  then  n_iter = 1
    if  size52(psf, /N_DIM) eq 3  then $
       n_psf = (size52(psf, /DIM))[2]  else  n_psf = 1
    psf_fwhm = starfinder_fwhm(psf, n_psf)
    id_par = starfinder_id_par(psf_fwhm, _EXTRA = extra)
    corr_par = starfinder_corr_par(psf, psf_fwhm, n_psf, _EXTRA = extra)
    ; modified March 2012
    if re_fitting eq 2 then $
        fit_par = starfinder_fit_par(psf, psf_fwhm, n_psf, /WIDER, _EXTRA = extra) else $
        fit_par = starfinder_fit_par(psf, psf_fwhm, n_psf, _EXTRA = extra)
;    fit_par = starfinder_fit_par(psf, psf_fwhm, n_psf, _EXTRA = extra)
    if  keyword_set(deblend) or keyword_set(deblost)  then $
       deb_par = starfinder_deb_par(psf, psf_fwhm, n_psf, _EXTRA = extra)
    if  n_elements(sv_par) eq 0  then begin
       fit_data = ptr_new(/ALLOCATE)  &  model_data = ptr_new(/ALLOCATE)
    endif

    ; Iterative search for suspected stars and subsequent analysis
    n_levels = n_elements(threshold)
    n_stars = 0L  &  n_presumed = 0L  &  n_max = 0L
    if re_fitting eq 2 then begin
       n_levels = 1
       n_stars = n_elements(f_in)
       list_of_stars = sort_list(reverse_class(starlist(n_stars, x_in, y_in, f_in)))
       if keyword_set(psf_type) then $
       stars = image_model(x_in, y_in, f_in, siz[0], siz[1], psf_type, $
                           psf_data, _EXTRA = extra) else $
       if n_elements(sv_par) ne 0 then $
       stars = image_model(x_in, y_in, f_in, siz[0], siz[1], psf, $
                           LX = sv_par.lx, UX = sv_par.ux, LY = sv_par.ly, UY = sv_par.uy, $
                           _EXTRA = extra) else $
       stars = image_model(x_in, y_in, f_in, siz[0], siz[1], psf, _EXTRA = extra)
    endif

    for  n_lev = 0L, n_levels - 1  do begin

    ; Update detection threshold
    threshold_n = float(threshold[n_lev])
    if  keyword_set(rel_threshold) and n_elements(noise_std) ne 0  then $
       threshold_n = threshold_n * noise_std
    if  n_elements(threshold_n) eq 1  then $
       threshold_n = replicate(threshold_n[0], siz[0], siz[1])

    ; Phase 1: find local maxima having
    ; central intensity > known stars + background + threshold
    if re_fitting eq 0 then begin
    if  not keyword_set(silent)  then $
       print, "STARFINDER: search for candidate stars"
    search_objects, image - stars, LOW_SURFACE = background, threshold_n, $
                    _EXTRA = extra, n_max, x0, y0, i0
    n_presumed = n_presumed + n_max
    endif

    ; Phase 2: analyze presumed stars with correlation and fitting,
    ;      estimating positions and fluxes.
    if n_max ne 0 and re_fitting eq 0 then begin
       if  not keyword_set(silent)  then $
          print, "STARFINDER: analysis of candidate stars"
       list_of_max = starlist(n_max, x0, y0, i0)
       list_of_stars = merge_list(list_of_stars, sort_list(list_of_max))
       for  n = n_stars, n_stars - 1 + n_max  do $
          starfinder_analyze, list_of_stars, n, image, siz, background, stars, $
                              noise_std, sv_par, x_bad, y_bad, id_par, $
                              corr_par, fit_par, fit_data, model_data, $
                              threshold_n, min_correlation, re_fitting, _EXTRA = extra, $
                              PSF_TYPE = psf_type, PSF_DATA = psf_data
       list_of_stars = sort_list(extract_stars(list_of_stars, n_stars))
       ; Update background estimate
       if  n_elements(back_box) ne 0  then $
          background = estimate_background(image - stars, back_box, _EXTRA = extra)
       ; Modify fitting parameters for subsequent operations
       fit_par = starfinder_fit_par(psf, psf_fwhm, n_psf, /WIDER, _EXTRA = extra)
       if  ptr_valid(fit_data)  then  ptr_free, fit_data
    endif  ; n_max ne 0

    ; Phase 2b: de-blend detected objects.
    if keyword_set(deblend) and n_lev eq n_levels - 1 and n_stars ne 0 and re_fitting eq 0 then begin
       if  not keyword_set(silent)  then $
          print, "STARFINDER: deblending of detected stars"
       ; Check each detected star: is it a blend?
       for  n = 0L, n_stars - 1  do $
          starfinder_deblend, list_of_stars, n, image, siz, background, stars, $
                              noise_std, sv_par, x_bad, y_bad, id_par, $
                              fit_par, deb_par, fit_data, model_data, $
                              threshold_n, re_fitting, _EXTRA = extra, /AROUND, $
                              PSF_TYPE = psf_type, PSF_DATA = psf_data
       list_of_stars = sort_list(extract_stars(list_of_stars, n_stars))
       ; Update background estimate
       if  n_elements(back_box) ne 0  then $
          background = estimate_background(image - stars, back_box, _EXTRA = extra)
    endif

    ; Phase 2c: recover lost objects to search for blends.
;    if keyword_set(deblost) and n_lev eq n_levels - 1 and n_stars ne 0 and re_fitting eq 0 then begin
    if keyword_set(deblost) and n_lev eq n_levels - 1 and re_fitting eq 0 then begin
       if  not keyword_set(silent)  then $
          print, "STARFINDER: search for blends among lost objects"
       search_objects, image - stars, LOW_SURFACE = background, threshold_n, $
                       _EXTRA = extra, n_max, x0, y0, i0
       if  n_max ne 0  then begin
          list_of_max = starlist(n_max, x0, y0, i0)
          list_of_stars = merge_list(list_of_stars, sort_list(list_of_max))
          for  n = n_stars, n_stars - 1 + n_max  do $
             starfinder_deblend, list_of_stars, n, image, siz, background, stars, $
                                 noise_std, sv_par, x_bad, y_bad, id_par, fit_par, $
                                 deb_par, fit_data, model_data, threshold_n, $
                                 re_fitting, _EXTRA = extra, $
                                 PSF_TYPE = psf_type, PSF_DATA = psf_data
          list_of_stars = sort_list(extract_stars(list_of_stars, n_stars))
          ; Update background estimate
          if  n_elements(back_box) ne 0  then $
             background = estimate_background(image - stars, back_box, _EXTRA = extra)
       endif
    endif

    ; Phase 3: re-determination of positions and fluxes by iterative
    ;      fitting of detected stars.
    if  n_lev eq n_levels - 1  then  maxit = n_iter  else $
    if  keyword_set(no_intermediate)  then  maxit = 0  else  maxit = 1
    if  n_stars ne 0  then  list0 = list_of_stars
    if re_fitting eq 0 then re_fitting = 1
    iter = 0L  &  converging = n_stars eq 0
    while  iter lt maxit and not converging  do begin
       if  not keyword_set(silent)  then $
          if  n_lev lt n_levels - 1  then $
             print, "STARFINDER: re-fitting"  else $
             print, "STARFINDER: re-fitting: iteration", iter + 1
;         Modified March 2012
;          if  n_lev lt n_levels - 1  then $
;             print, "STARFINDER: intermediate re-fitting"  else $
;             print, "STARFINDER: final re-fitting: iteration", iter + 1
       for  n = 0L, n_stars - 1  do $
          ; modified March 2012
;         if list_of_stars[n].is_a_star or re_fitting eq 1 then $   ;;; if list_of_stars[n].is_a_star or re_fitting eq 1 or iter eq 0 then $
          ; modified September 2024
          if is_star(list_of_stars, n) or re_fitting eq 1 then $   ;;; if list_of_stars[n].is_a_star or re_fitting eq 1 or iter eq 0 then $
          starfinder_fit, list_of_stars, n, image, siz, background, stars, $
                          noise_std, sv_par, x_bad, y_bad, fit_par, fit_data, $
                          model_data, threshold_n, re_fitting, _EXTRA = extra, $
                          PSF_TYPE = psf_type, PSF_DATA = psf_data
       if  maxit gt 1  then $
          converging = starfinder_converg(list0, list_of_stars, _EXTRA = extra)
       iter = iter + 1  &  list0 = list_of_stars
    endwhile
    if re_fitting eq 1 then re_fitting = 0
    fit_par = starfinder_fit_par(psf, psf_fwhm, n_psf, _EXTRA = extra)
    if  ptr_valid(fit_data)  then  ptr_free, fit_data

    endfor ; end of cycle on threshold levels

    ; De-allocate pointer heap variables
    starfinder_dealloc, corr_par, fit_data, model_data

    ; Save results
    if  n_stars ne 0  then begin
       list_of_stars = sort_list(extract_stars(list_of_stars, n_stars))
       star_param, list_of_stars, n_stars, $
                   x, y, fluxes, correlation, sigma_x, sigma_y, sigma_f
    endif
    if  not keyword_set(silent)  then begin
       if re_fitting ne 2 then $
       print, "STARFINDER", n_presumed,   "   candidate stars"
       print, "STARFINDER", n_stars, "   detected stars"
    endif
    return
end