From cf7400b5f46073085cdec956d304886f34120b77 Mon Sep 17 00:00:00 2001 From: Ned Molter Date: Tue, 23 Jan 2024 11:28:06 -0500 Subject: [PATCH] JP-2259: Tweakreg source finding (#8203) Co-authored-by: Ned Molter Co-authored-by: Howard Bushouse --- CHANGES.rst | 3 + docs/jwst/tweakreg/README.rst | 145 ++++++++++++--- jwst/regtest/test_nircam_align_to_gaia.py | 3 +- jwst/regtest/test_niriss_sourcefind.py | 32 ++++ jwst/tweakreg/tests/test_tweakreg.py | 8 + jwst/tweakreg/tweakreg_catalog.py | 204 +++++++++++++++++----- jwst/tweakreg/tweakreg_step.py | 48 ++++- 7 files changed, 364 insertions(+), 79 deletions(-) create mode 100644 jwst/regtest/test_niriss_sourcefind.py diff --git a/CHANGES.rst b/CHANGES.rst index bb24116a56..30ce8aeb7a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -68,6 +68,9 @@ tweakreg - Update ``sregion`` after WCS corrections are applied. [#8158] +- Added option to choose IRAFStarFinder and segmentation.SourceFinder + instead of DAOStarFinder and exposed star finder parameters. [#8203] + 1.13.3 (01-05-2024) =================== diff --git a/docs/jwst/tweakreg/README.rst b/docs/jwst/tweakreg/README.rst index 03f4e0334e..ac0c0588fb 100644 --- a/docs/jwst/tweakreg/README.rst +++ b/docs/jwst/tweakreg/README.rst @@ -13,29 +13,61 @@ image catalogs using the corrected WCS will align on the sky. Source Detection ---------------- -If ``meta.tweakreg_catalog`` attribute of input data models is a non-empty -string and ``use_custom_catalogs`` is `True`, then it will be interpretted +If the ``meta.tweakreg_catalog`` attribute of input data models is a non-empty +string and ``use_custom_catalogs`` is `True`, then it will be interpreted as a file name of a user-provided source catalog. The catalog must be in a format automatically recognized by :py:meth:`~astropy.table.Table.read`. -When ``meta.tweakreg_catalog`` attribute of input data models is `None` or -an empty string, then ``tweakreg`` step will attempt to detect sources in the -input images. Stars are detected in the image using the Photutils "daofind" -function. Photutils.daofind is an implementation of the `DAOFIND`_ algorithm +When the ``meta.tweakreg_catalog`` attribute of input data models is `None` or +an empty string, then the ``tweakreg`` step will attempt to detect sources in the +input images. Stars are detected in the image with one of the following source +detection algorithms: ``photutils.detection.DAOStarFinder`` (default), +``photutils.detection.IRAFStarFinder``, or ``photutils.segmentation.SourceFinder`` +in conjunction with ``photutils.segmentation.SourceCatalog``. + +DAOStarFinder is an implementation of the `DAOFIND`_ algorithm (`Stetson 1987, PASP 99, 191 `_). It searches images for local density maxima that have a peak amplitude greater than a specified threshold (the threshold is applied to a convolved image) and have a size and shape similar to a defined 2D Gaussian -kernel. ``photutils.daofind`` also provides an estimate of the objects +kernel. DAOFind also provides an estimate of the object's roundness and sharpness, whose lower and upper bounds can be specified. +IRAFStarFinder is a Python implementation of the IRAF star finding algorithm, +which also calculates the objects' centroids, roundness, and sharpness. +However, IRAFStarFinder uses image moments +instead of 1-D Gaussian fits to projected light distributions like +DAOStarFinder. + +SourceFinder implements a segmentation algorithm that identifies +sources in an image based on a number of connected pixels above a +specified threshold value. The sources are deblended using a +combination of multi-thresholding and watershed segmentation. +SourceCatalog finds the centroids of these sources, which are used +as the retrieved star positions. + +.. warning:: + It has been shown (`STScI Technical Report JWST-STScI-008116, SM-12 + `_) + that for undersampled PSFs, e.g. for short-wavelength NIRISS + imaging data, ``DAOStarFinder`` gives bad results no matter the input parameters + due to its use of 1-D Gaussian fits. + ``IRAFStarFinder`` or ``SourceFinder`` should be used instead. + +.. note:: + ``SourceFinder`` is likely to detect non-stellar sources + such as galaxies because sources are not assumed to be + point-source-like. This may lead to mismatches between the + derived source catalog and the reference catalog during the + alignment step. + .. _DAOFIND: http://stsdas.stsci.edu/cgi-bin/gethelp.cgi?daofind Custom Source Catalogs ---------------------- -Source detection built-in into the ``tweakreg`` step can be disabled by +Source detection built into the ``tweakreg`` step can be disabled by providing a file name to a custom source catalog in the ``meta.tweakreg_catalog`` attribute of input data models. The catalog must be in a format automatically recognized by @@ -63,7 +95,7 @@ other ways of supplying custom source catalogs to the step: Catalog file names are relative to ``catfile`` file path. Specifying custom source catalogs via either the input ASN file or -``catfile``, will update input data models' ``meta.tweakreg_catalog`` +``catfile`` will update input data models' ``meta.tweakreg_catalog`` attributes to the catalog file names provided in either in the ASN file or ``catfile``. @@ -76,12 +108,12 @@ attributes to the catalog file names provided in either in the ASN file or 1. Providing a data model file name in the ``catfile`` and leaving the corresponding source catalog file name empty -- same as setting ``'tweakreg_catalog'`` in the ASN file to an empty string ``""`` -- - would set corresponding input data model's ``meta.tweakreg_catalog`` + would set the corresponding input data model's ``meta.tweakreg_catalog`` attribute to `None`. In this case, ``tweakreg_step`` will automatically generate a source catalog for that data model. 2. If an input data model is not listed in the ``catfile`` or does not - have ``'tweakreg_catalog'`` attribute provided in the ASN file, + have the ``'tweakreg_catalog'`` attribute provided in the ASN file, then the catalog file name in that model's ``meta.tweakreg_catalog`` attribute will be used. If ``model.meta.tweakreg_catalog`` is `None`, ``tweakreg_step`` will automatically generate a source catalog for @@ -96,7 +128,7 @@ are aligned relative to each other. This step produces a combined source catalog for the entire set of input images as if they were combined into a single mosaic. -If the step parameter ``abs_refcat`` is set to 'GAIADR3', 'GAIADR2' or 'GAIADR1', +If the step parameter ``abs_refcat`` is set to 'GAIADR3', 'GAIADR2', or 'GAIADR1', an astrometric reference catalog then gets generated by querying a GAIA-based astrometric catalog web service for all astrometrically measured sources in the combined field-of-view of the set of input @@ -117,8 +149,8 @@ maintaining the relative alignment between the images. For this part of alignment, instead of 'GAIADR1', 'GAIADR2', or 'GAIADR3', users can supply an external reference catalog by providing a path to an existing -file. User-supplied catalog must contain ``'RA'`` and ``'DEC'`` columns which -indicate reference source world coordinates (in degrees). An optional column +file. A user-supplied catalog must contain ``'RA'`` and ``'DEC'`` columns +indicating reference source world coordinates (in degrees). An optional column in the catalog is the ``'weight'`` column, which when present, will be used in fitting. The catalog must be in a format automatically recognized by :py:meth:`~astropy.table.Table.read`. @@ -127,7 +159,7 @@ Grouping -------- Images taken at the same time (e.g., NIRCam images from all short-wave -detectors) can be aligned together, that is, a single correction +detectors) can be aligned together; that is, a single correction can be computed and applied to all these images because any error in telescope pointing will be identical in all these images and it is assumed that the relative positions of (e.g., NIRCam) detectors do not change. @@ -169,24 +201,38 @@ The ``tweakreg`` step has the following optional arguments: * ``save_catalogs``: A boolean indicating whether or not the catalogs should be written out. This parameter is ignored for input data models whose ``meta.tweakreg_catalog`` is a non-empty string pointing to a user-supplied - source catalog. (Default=`False`) + source catalog. (Default=False) * ``use_custom_catalogs``: A boolean that indicates whether to ignore source catalog in the input data model's ``meta.tweakreg_catalog`` attribute. If `False`, new catalogs will be generated by the ``tweakreg`` - step. (Default=`False`) + step. (Default=False) * ``catalog_format``: A `str` indicating catalog output file format. - (Default='ecsv') + (Default= `'ecsv'`) * ``catfile``: Name of the file with a list of custom user-provided catalogs. - (Default='') + (Default= `''`) + +* ``bkg_boxsize``: A positive `int` indicating the background mesh box size + in pixels. (Default=400) + +* ``starfinder``: A `str` indicating the source detection algorithm to use. + Allowed values: `'iraf'`, `'dao'`, `'segmentation'`. (Default= `'dao'`) + +* ``snr_threshold``: A `float` value indicating SNR threshold above the + background. Required for all star finders. (Default=10.0) + +**Additional source finding parameters for DAO and IRAF:** * ``kernel_fwhm``: A `float` value indicating the Gaussian kernel FWHM in pixels. (Default=2.5) -* ``snr_threshold``: A `float` value indicating SNR threshold above the - background. (Default=5.0) +* ``minsep_fwhm``: A `float` value indicating the minimum separation between + detected objects in units of number of FWHMs. (Default=0.0) + +* ``sigma_radius``: A `float` value indicating the truncation radius of the + Gaussian kernel in units of number of FWHMs. (Default=2.5) * ``sharplo``: A `float` value indicating The lower bound on sharpness for object detection. (Default=0.2) @@ -197,24 +243,52 @@ The ``tweakreg`` step has the following optional arguments: * ``roundlo``: A `float` value indicating the lower bound on roundness for object detection. (Default=-1.0) -* ``roundhi``: `float` value indicating the upper bound on roundness +* ``roundhi``: A `float` value indicating the upper bound on roundness for object detection. (Default=1.0) * ``brightest``: A positive `int` value indicating the number of brightest - objects to keep. (Default=200) + objects to keep. If None, keep all objects above the threshold. (Default=200) * ``peakmax``: A `float` value used to filter out objects with pixel values >= ``peakmax``. (Default=None) -* ``bkg_boxsize``: A positive `int` indicating the background mesh box size - in pixels. (Default=400) +**Additional source finding parameters for segmentation:** + +* ``npixels``: An `int` value indicating the minimum number of + connected pixels that comprises a segment (Default=10) + +* ``connectivity``: An `int` value indicating the connectivity defining the + neighborhood of a pixel. Options are `4`, i.e., connected pixels touch along edges, + or `8`, i.e, connected pixels touch along edges or corners (Default=8) + +* ``nlevels``: An `int` value indicating the number of multi-thresholding + levels for deblending (Default=32) + +* ``contrast``: A `float` value indicating the fraction of total source flux + an object must have to be deblended (Default=0.001) + +* ``multithresh_mode``: A `str` indicating the multi-thresholding mode. + Allowed values: `'exponential'`, `'linear'`, `'sinh'`. + (Default= `'exponential'`) + +* ``localbkg_width``: An `int` value indicating the width of rectangular + annulus used to compute local background around each source. If set to 0, + then local background will not be subtracted. (Default=0) + +* ``apermask_method``: A `str` indicating the method used to handle + neighboring sources when performing aperture photometry. + Allowed values: `'correct'`, `'mask'`, `'none'`. (Default= `'correct'`) + +* ``kron_params``: A tuple of `float` values indicating the + parameters defining Kron aperture. If None, + the parameters `(2.5, 1.4, 0.0)` are used. (Default=None) **Optimize alignment order:** * ``enforce_user_order``: a boolean value indicating whether or not take the first image as a reference image and then align the rest of the images to that reference image in the order in which input images have been provided - or to optimize order in which images are aligned. (Default=`False`) + or to optimize order in which images are aligned. (Default=False) **Reference Catalog parameters:** @@ -269,7 +343,7 @@ The ``tweakreg`` step has the following optional arguments: spread across/covering the entire image). * ``nclip``: A non-negative integer number of clipping iterations - to use in the fit. (Default = 3) + to use in the fit. (Default=3) * ``sigma``: A positive `float` indicating the clipping limit, in sigma units, used when performing fit. (Default=3.0) @@ -280,13 +354,13 @@ Parameters used for absolute astrometry to a reference catalog. * ``abs_refcat``: String indicating what astrometric catalog should be used. Currently supported options: 'GAIADR1', 'GAIADR2', 'GAIADR3', a path to an existing - reference catalog, `None`, or ''. See + reference catalog, `None`, or `''`. See :py:data:`jwst.tweakreg.tweakreg_step.SINGLE_GROUP_REFCAT` for an up-to-date list of supported built-in reference catalogs. When ``abs_refcat`` is `None` or an empty string, alignment to the absolute astrometry catalog will be turned off. - (Default='') + (Default= `''`) * ``abs_minobj``: A positive `int` indicating minimum number of objects acceptable for matching. (Default=15) @@ -335,6 +409,19 @@ in more detail at https://tweakwcs.readthedocs.io/en/latest/ +Further description of the input parameters and algorithms for star finding +can be found at the following links: + +* `DAOStarFinder`_ +* `IRAFStarFinder`_ +* `SourceFinder`_ +* `SourceCatalog`_ + +.. _DAOStarFinder: https://photutils.readthedocs.io/en/stable/api/photutils.detection.DAOStarFinder.html +.. _IRAFStarFinder: https://photutils.readthedocs.io/en/stable/api/photutils.detection.IRAFStarFinder.html +.. _SourceFinder: https://photutils.readthedocs.io/en/stable/api/photutils.segmentation.SourceFinder.html +.. _SourceCatalog: https://photutils.readthedocs.io/en/stable/api/photutils.segmentation.SourceCatalog.html + Reference Files =============== diff --git a/jwst/regtest/test_nircam_align_to_gaia.py b/jwst/regtest/test_nircam_align_to_gaia.py index d7fed42c6d..6eac063cb1 100644 --- a/jwst/regtest/test_nircam_align_to_gaia.py +++ b/jwst/regtest/test_nircam_align_to_gaia.py @@ -3,7 +3,6 @@ from numpy.testing import assert_allclose from stdatamodels.jwst import datamodels - from jwst.stpipe import Step @@ -16,7 +15,7 @@ def run_image3pipeline(rtdata_module, jail): args = ["calwebb_image3", rtdata.input, "--steps.tweakreg.abs_refcat=GAIADR2", "--steps.tweakreg.save_results=True", - "--steps.tweakreg.output_use_model=True" + "--steps.tweakreg.output_use_model=True", ] Step.from_cmdline(args) diff --git a/jwst/regtest/test_niriss_sourcefind.py b/jwst/regtest/test_niriss_sourcefind.py new file mode 100644 index 0000000000..e0c7c9b496 --- /dev/null +++ b/jwst/regtest/test_niriss_sourcefind.py @@ -0,0 +1,32 @@ +import pytest +from astropy.io import ascii +from numpy.testing import assert_allclose + +from stdatamodels.jwst import datamodels +from jwst.tweakreg import tweakreg_catalog + + +@pytest.mark.bigdata +@pytest.mark.parametrize("starfinder", ["iraf", "segmentation"], ) +def test_tweakreg_catalog_starfinder_alternatives(rtdata, starfinder): + ''' + Test that the IRAF and segmentation star finders give expected results for undersampled NIRISS data + It is well known that DAOStarFinder gives bad results so is not included in this test + ''' + + stem = "jw01088003001_01101_00005" + rtdata.get_data(f"niriss/imaging/{stem}_nis_cal.fits") + model = datamodels.ImageModel(rtdata.input) + catalog = tweakreg_catalog.make_tweakreg_catalog( + model, 2.5, 10.0, starfinder=starfinder, starfinder_kwargs={ + 'brightest': None, + 'sharphi': 3.0, + 'minsep_fwhm': 2.5, + 'sigma_radius': 2.5, + }) + rtdata.get_truth(f"truth/test_niriss_sourcefind/{stem}_{starfinder}_cat.ecsv") + catalog_truth = ascii.read(rtdata.truth) + + # rtol is larger than default because of numerical differences on Linux vs MacOS + assert_allclose(catalog['xcentroid'], catalog_truth['xcentroid'], rtol=1e-3) + assert_allclose(catalog['ycentroid'], catalog_truth['ycentroid'], rtol=1e-3) diff --git a/jwst/tweakreg/tests/test_tweakreg.py b/jwst/tweakreg/tests/test_tweakreg.py index ad45bf523d..cac427d952 100644 --- a/jwst/tweakreg/tests/test_tweakreg.py +++ b/jwst/tweakreg/tests/test_tweakreg.py @@ -6,6 +6,7 @@ import pytest from jwst.tweakreg import tweakreg_step +from jwst.tweakreg import tweakreg_catalog from stdatamodels.jwst.datamodels import ImageModel @@ -55,3 +56,10 @@ def test_common_name(groups, all_group_names, common_name): cn = tweakreg_step._common_name(group, all_group_names) assert cn == cn_truth + + +def test_expected_failure_bad_starfinder(): + + model = ImageModel() + with pytest.raises(ValueError): + tweakreg_catalog.make_tweakreg_catalog(model, 5.0, bkg_boxsize=400, starfinder='bad_value') diff --git a/jwst/tweakreg/tweakreg_catalog.py b/jwst/tweakreg/tweakreg_catalog.py index 80a2d26111..7b7a6543b2 100644 --- a/jwst/tweakreg/tweakreg_catalog.py +++ b/jwst/tweakreg/tweakreg_catalog.py @@ -1,8 +1,10 @@ import logging +import inspect from astropy.table import Table import numpy as np -from photutils.detection import DAOStarFinder +from photutils.detection import DAOStarFinder, IRAFStarFinder +from photutils.segmentation import SourceFinder, SourceCatalog from stdatamodels.jwst.datamodels import dqflags, ImageModel @@ -12,9 +14,139 @@ log.setLevel(logging.DEBUG) -def make_tweakreg_catalog(model, kernel_fwhm, snr_threshold, sharplo=0.2, - sharphi=1.0, roundlo=-1.0, roundhi=1.0, - brightest=None, peakmax=None, bkg_boxsize=400): +def _SourceFinderWrapper(data, threshold, mask=None, **kwargs): + """ + Wrapper function for photutils.source_finder.SourceFinder to make input + and output consistent with DAOStarFinder and IRAFStarFinder. + see `photutils segmentation tutorial `_. + + Parameters + ---------- + data : array_like + The 2D array of the image. + threshold : float + The absolute image value above which to select sources. + mask : array_like (bool), optional + The image mask + **kwargs : Additional keyword arguments are passed to `photutils.segmentation.SourceFinder` + and/or `photutils.segmentation.SourceCatalog`. + + Returns + ------- + sources : `~astropy.table.QTable` + A table containing the found sources. + """ + + default_kwargs = {'npixels': 10, + 'progress_bar': False, + } + kwargs = {**default_kwargs, **kwargs} + + # handle passing kwargs into SourceFinder and SourceCatalog + # note that this suppresses TypeError: unexpected keyword arguments + # so user must be careful to know which kwargs are passed in here + finder_args = list(inspect.signature(SourceFinder).parameters) + catalog_args = list(inspect.signature(SourceCatalog).parameters) + finder_dict = {k: kwargs.pop(k) for k in dict(kwargs) if k in finder_args} + catalog_dict = {k: kwargs.pop(k) for k in dict(kwargs) if k in catalog_args} + if ('kron_params' in catalog_dict.keys()) and (catalog_dict['kron_params'] is None): + catalog_dict['kron_params'] = (2.5, 1.4, 0.0) # necessary because cannot specify default in Step spec string + + finder = SourceFinder(**finder_dict) + segment_map = finder(data, threshold, mask=mask) + sources = SourceCatalog(data, segment_map, mask=mask, **catalog_dict).to_table() + sources.rename_column('label', 'id') + sources.rename_column('segment_flux', 'flux') + + return sources + + +def _IRAFStarFinderWrapper(data, threshold, mask=None, **kwargs): + """ + Wrapper function for `photutils.detection.IRAFStarFinder` to make inputs + and outputs consistent across the three allowed detection methods + + Parameters + ---------- + data : array_like + The 2D array of the image. + threshold : float + The absolute image value above which to select sources. + mask : array_like (bool), optional + The image mask + **kwargs : Additional keyword arguments are passed to + `photutils.detection.IRAFStarFinder`. + + Returns + ------- + sources : `~astropy.table.QTable` + A table containing the found sources. + """ + + # defaults are not necessary to repeat here when running full pipeline step + # but direct call to make_tweakreg_catalog will fail without 'fwhm' specified + default_kwargs = {'fwhm': 2.5,} + kwargs = {**default_kwargs, **kwargs} + + # note that this suppresses TypeError: unexpected keyword arguments + # so user must be careful to know which kwargs are passed in here + finder_args = list(inspect.signature(IRAFStarFinder).parameters) + finder_dict = {k: kwargs.pop(k) for k in dict(kwargs) if k in finder_args} + fwhm = finder_dict.pop('fwhm') + + starfind = IRAFStarFinder(threshold, fwhm, **finder_dict) + sources = starfind(data, mask=mask) + + return sources + + +def _DaoStarFinderWrapper(data, threshold, mask=None, **kwargs): + """ + Wrapper function for `photutils.detection.DAOStarFinder` to make inputs + and outputs consistent across the three allowed detection methods + + Parameters + ---------- + data : array_like + The 2D array of the image. + threshold : float + The absolute image value above which to select sources. + mask : array_like (bool), optional + The image mask + **kwargs : Additional keyword arguments are passed to + a`photutils.detection.DAOStarFinder`. + + Returns + ------- + sources : `~astropy.table.QTable` + A table containing the found sources. + """ + + # defaults are not necessary to repeat here when running full pipeline step + # but direct call to make_tweakreg_catalog will fail without 'fwhm' specified + default_kwargs = {'fwhm': 2.5,} + kwargs = {**default_kwargs, **kwargs} + + # for consistency with IRAFStarFinder, allow minsep_fwhm to be passed in + # and convert to pixels in the same way that IRAFStarFinder does + # see IRAFStarFinder readthedocs page and also + # https://github.com/astropy/photutils/issues/1561 + if "minsep_fwhm" in kwargs: + min_sep_pix = max(2, int(kwargs["minsep_fwhm"] * kwargs["fwhm"] + 0.5)) + kwargs["min_separation"] = min_sep_pix + + # note that this suppresses TypeError: unexpected keyword arguments + # so user must be careful to know which kwargs are passed in here + finder_args = list(inspect.signature(DAOStarFinder).parameters) + finder_dict = {k: kwargs.pop(k) for k in dict(kwargs) if k in finder_args} + fwhm = finder_dict.pop('fwhm') + starfind = DAOStarFinder(threshold, fwhm, **finder_dict) + sources = starfind(data, mask=mask) + + return sources + + +def make_tweakreg_catalog(model, snr_threshold, bkg_boxsize=400, starfinder='dao', starfinder_kwargs={}): """ Create a catalog of point-line sources to be used for image alignment in tweakreg. @@ -25,45 +157,31 @@ def make_tweakreg_catalog(model, kernel_fwhm, snr_threshold, sharplo=0.2, The input `ImageModel` of a single image. The input image is assumed to be background subtracted. - kernel_fwhm : float - The full-width at half-maximum (FWHM) of the 2D Gaussian kernel - used to filter the image before thresholding. Filtering the - image will smooth the noise and maximize detectability of - objects with a shape similar to the kernel. - snr_threshold : float The signal-to-noise ratio per pixel above the ``background`` for which to consider a pixel as possibly being part of a source. - sharplo : float, optional - The lower bound on sharpness for object detection. - - sharphi : float, optional - The upper bound on sharpness for object detection. - - roundlo : float, optional - The lower bound on roundness for object detection. - - roundhi : float, optional - The upper bound on roundness for object detection. + bkg_boxsize : float, optional + The background mesh box size in pixels. - brightest : int, None, optional - Number of brightest objects to keep after sorting the full object list. - If ``brightest`` is set to `None`, all objects will be selected. + starfinder : str, optional + The `photutils` star finder to use. Options are 'dao', 'iraf', or 'segmentation'. - peakmax : float, None, optional - Maximum peak pixel value in an object. Only objects whose peak pixel - values are *strictly smaller* than ``peakmax`` will be selected. - This may be used to exclude saturated sources. By default, when - ``peakmax`` is set to `None`, all objects will be selected. + - 'dao': `photutils.detection.DAOStarFinder` + - 'iraf': `photutils.detection.IRAFStarFinder` + - 'segmentation': `photutils.segmentation.SourceFinder` and `photutils.segmentation.SourceCatalog` - .. warning:: - `DAOStarFinder` automatically excludes objects whose peak - pixel values are negative. Therefore, setting ``peakmax`` to a - non-positive value would result in exclusion of all objects. + starfinder_kwargs : dict, optional + additional keyword arguments to be passed to the star finder. + for 'segmentation', these can be kwargs to `photutils.segmentation.SourceFinder` + and/or `photutils.segmentation.SourceCatalog`. + for 'dao' or 'iraf', these are kwargs to `photutils.detection.DAOStarFinder` + or `photutils.detection.IRAFStarFinder`, respectively. + Defaults are as stated in the docstrings of those functions unless noted here: - bkg_boxsize : float, optional - The background mesh box size in pixels. + - 'dao': fwhm=2.5 + - 'iraf': fwhm=2.5 + - 'segmentation': npixels=10, progress_bar=False Returns ------- @@ -73,6 +191,15 @@ def make_tweakreg_catalog(model, kernel_fwhm, snr_threshold, sharplo=0.2, if not isinstance(model, ImageModel): raise TypeError('The input model must be an ImageModel.') + if starfinder.lower() in ['dao', 'daostarfinder']: + StarFinder = _DaoStarFinderWrapper + elif starfinder.lower() in ['iraf', 'irafstarfinder']: + StarFinder = _IRAFStarFinderWrapper + elif starfinder.lower() in ['segmentation', 'sourcefinder']: + StarFinder = _SourceFinderWrapper + else: + raise ValueError(f"Unknown starfinder type: {starfinder}") + # Mask the non-imaging area (e.g. MIRI) coverage_mask = ((dqflags.pixel['NON_SCIENCE'] + dqflags.pixel['DO_NOT_USE']) & @@ -90,12 +217,7 @@ def make_tweakreg_catalog(model, kernel_fwhm, snr_threshold, sharplo=0.2, return catalog threshold = np.median(threshold_img) # DAOStarFinder requires float - - daofind = DAOStarFinder(fwhm=kernel_fwhm, threshold=threshold, - sharplo=sharplo, sharphi=sharphi, roundlo=roundlo, - roundhi=roundhi, brightest=brightest, - peakmax=peakmax) - sources = daofind(model.data, mask=coverage_mask) + sources = StarFinder(model.data, threshold, mask=coverage_mask, **starfinder_kwargs) if sources: catalog = sources[columns] diff --git a/jwst/tweakreg/tweakreg_step.py b/jwst/tweakreg/tweakreg_step.py index 4913cb73f0..85851cc103 100644 --- a/jwst/tweakreg/tweakreg_step.py +++ b/jwst/tweakreg/tweakreg_step.py @@ -56,21 +56,35 @@ class TweakRegStep(Step): use_custom_catalogs = boolean(default=False) # Use custom user-provided catalogs? catalog_format = string(default='ecsv') # Catalog output file format catfile = string(default='') # Name of the file with a list of custom user-provided catalogs + starfinder = option('dao', 'iraf', 'segmentation', default='dao') # Star finder to use. + snr_threshold = float(default=10.0) # SNR threshold above the bkg for star finder + # kwargs for DAOStarFinder and IRAFStarFinder, only used if starfinder is 'dao' or 'iraf' kernel_fwhm = float(default=2.5) # Gaussian kernel FWHM in pixels - snr_threshold = float(default=10.0) # SNR threshold above the bkg + minsep_fwhm = float(default=0.0) # Minimum separation between detected objects in FWHM + sigma_radius = float(default=1.5) # Truncation radius of the Gaussian kernel in units of sigma sharplo = float(default=0.2) # The lower bound on sharpness for object detection. sharphi = float(default=1.0) # The upper bound on sharpness for object detection. roundlo = float(default=-1.0) # The lower bound on roundness for object detection. roundhi = float(default=1.0) # The upper bound on roundness for object detection. brightest = integer(default=200) # Keep top ``brightest`` objects peakmax = float(default=None) # Filter out objects with pixel values >= ``peakmax`` + # kwargs for SourceCatalog and SourceFinder, only used if starfinder is 'segmentation' + npixels = integer(default=10) # Minimum number of connected pixels + connectivity = option(4, 8, default=8) # The connectivity defining the neighborhood of a pixel + nlevels = integer(default=32) # Number of multi-thresholding levels for deblending + contrast = float(default=0.001) # Fraction of total source flux an object must have to be deblended + multithresh_mode = option('exponential', 'linear', 'sinh', default='exponential') # Multi-thresholding mode + localbkg_width = integer(default=0) # Width of rectangular annulus used to compute local background around each source + apermask_method = option('correct', 'mask', 'none', default='correct') # How to handle neighboring sources + kron_params = float_list(min=2, max=3, default=None) # Parameters defining Kron aperture + # continue args for rest of step bkg_boxsize = integer(default=400) # The background mesh box size in pixels. enforce_user_order = boolean(default=False) # Align images in user specified order? expand_refcat = boolean(default=False) # Expand reference catalog with new sources? minobj = integer(default=15) # Minimum number of objects acceptable for matching searchrad = float(default=2.0) # The search radius in arcsec for a match use2dhist = boolean(default=True) # Use 2d histogram to find initial offset? - separation = float(default=1.0) # Minimum object separation in arcsec + separation = float(default=1.0) # Minimum object separation for xyxymatch in arcsec tolerance = float(default=0.7) # Matching tolerance for xyxymatch in arcsec xoffset = float(default=0.0), # Initial guess for X offset in arcsec yoffset = float(default=0.0) # Initial guess for Y offset in arcsec @@ -170,12 +184,32 @@ def process(self, input): else: # source finding + starfinder_kwargs = { + 'fwhm': self.kernel_fwhm, + 'sigma_radius': self.sigma_radius, + 'minsep_fwhm': self.minsep_fwhm, + 'sharplo': self.sharplo, + 'sharphi': self.sharphi, + 'roundlo': self.roundlo, + 'roundhi': self.roundhi, + 'peakmax': self.peakmax, + 'brightest': self.brightest, + 'npixels': self.npixels, + 'connectivity': self.connectivity, + 'nlevels': self.nlevels, + 'contrast': self.contrast, + 'mode': self.multithresh_mode, + 'error': image_model.err, + 'localbkg_width': self.localbkg_width, + 'apermask_method': self.apermask_method, + 'kron_params': self.kron_params, + } + catalog = make_tweakreg_catalog( - image_model, self.kernel_fwhm, self.snr_threshold, - sharplo=self.sharplo, sharphi=self.sharphi, - roundlo=self.roundlo, roundhi=self.roundhi, - brightest=self.brightest, peakmax=self.peakmax, - bkg_boxsize=self.bkg_boxsize + image_model, self.snr_threshold, + starfinder=self.starfinder, + bkg_boxsize=self.bkg_boxsize, + starfinder_kwargs=starfinder_kwargs, ) new_cat = True