Repository: astropy/photutils

Issue:
DAOStarFinder throws an uncaught exception if no sources are detected
Photutils == 1.6.0 or main on github both produce this behavior.


```
from photutils import DAOStarFinder
import numpy as np
from scipy.ndimage import gaussian_filter
sigma = 1.0
data = np.zeros((100, 100))
err = np.ones_like(data)
xcoords = [10, 30, 50, 70]  # 4 features
ycoords = np.array(xcoords) + 5  # 4 features
data[ycoords, xcoords] = 1
data = gaussian_filter(data, sigma=sigma)
data /= np.max(data)
nsigma=2.
fwhm=6
daofind = DAOStarFinder(fwhm=fwhm, threshold=nsigma, exclude_border=True)
features = daofind(data / err, mask=np.zeros(data.shape, dtype=int))
```

produces the following error

```
File site-packages/photutils/detection/core.py:111, in StarFinderBase._find_stars(convolved_data, kernel, threshold, min_separation, mask, exclude_border)
    109     xmax = convolved_data.shape[1] - xpad
    110     ymax = convolved_data.shape[0] - ypad
--> 111     mask = ((tbl['x_peak'] > xpad) & (tbl['y_peak'] > ypad)
    112             & (tbl['x_peak'] < xmax) & (tbl['y_peak'] < ymax))
    113     tbl = tbl[mask]
    115 if tbl is None:

TypeError: 'NoneType' object is not subscriptable
```

Discussion:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 46113e73..c7d07224 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -51,6 +51,13 @@ Bug Fixes
   - Fixed an issue where ``centroid_quadratic`` would sometimes fail if
     the input data contained NaNs. [#1495]
 
+- ``photutils.detection``
+
+  - Fixed an issue with the starfinders (``DAOStarFinder``,
+    ``IRAFStarFinder``, and ``StarFinder``) where an exception was
+    raised if ``exclude_border=True`` and there were no detections.
+    [#1512].
+
 - ``photutils.isophote``
 
   - Fixed a bug where the upper harmonics (a3, a4, b3, and b4) had the
diff --git a/photutils/detection/core.py b/photutils/detection/core.py
index b2286436..b871c4f4 100644
--- a/photutils/detection/core.py
+++ b/photutils/detection/core.py
@@ -105,6 +105,9 @@ class StarFinderBase(metaclass=abc.ABCMeta):
             tbl = find_peaks(convolved_data, threshold, footprint=footprint,
                              mask=mask)
 
+        if tbl is None:
+            return None
+
         if exclude_border:
             xmax = convolved_data.shape[1] - xpad
             ymax = convolved_data.shape[0] - ypad
@@ -112,9 +115,6 @@ class StarFinderBase(metaclass=abc.ABCMeta):
                     & (tbl['x_peak'] < xmax) & (tbl['y_peak'] < ymax))
             tbl = tbl[mask]
 
-        if tbl is None:
-            return None
-
         xpos, ypos = tbl['x_peak'], tbl['y_peak']
         if not exclude_border:
             xpos -= xpad


Test patch:
diff --git a/photutils/detection/tests/test_daofinder.py b/photutils/detection/tests/test_daofinder.py
index 1b5638d0..f4766cc7 100644
--- a/photutils/detection/tests/test_daofinder.py
+++ b/photutils/detection/tests/test_daofinder.py
@@ -49,12 +49,26 @@ class TestDAOStarFinder:
         tbl = starfinder(DATA)
         assert len(tbl) == 20
 
+        # test when no detections
+        starfinder = DAOStarFinder(threshold=100, fwhm=2, sigma_radius=1.5,
+                                   exclude_border=False)
+        with pytest.warns(NoDetectionsWarning, match='No sources were found'):
+            tbl = starfinder(DATA)
+            assert tbl is None
+
     def test_daofind_exclude_border(self):
         starfinder = DAOStarFinder(threshold=10, fwhm=2, sigma_radius=1.5,
                                    exclude_border=True)
         tbl = starfinder(DATA)
         assert len(tbl) == 19
 
+        # test when no detections
+        starfinder = DAOStarFinder(threshold=100, fwhm=2, sigma_radius=1.5,
+                                   exclude_border=True)
+        with pytest.warns(NoDetectionsWarning, match='No sources were found'):
+            tbl = starfinder(DATA)
+            assert tbl is None
+
     def test_daofind_nosources(self):
         data = np.ones((3, 3))
         with pytest.warns(NoDetectionsWarning, match='No sources were found'):