Repository: astropy/photutils

Issue:
SourceCatalog: Cannot cast ufunc 'subtract' output from dtype('float64') to dtype('uint16') with casting rule 'same_kind'
Hello,

I'm trying to extract some source properties using the photutils.segmentation.SourceCatalog, however I'm getting the following error for properties corresponding to ADU counts:
`---------------------------------------------------------------------------
UFuncTypeError                            Traceback (most recent call last)
/var/folders/t2/pdxc8twj5q7c1mfhc61y5ts40000gw/T/ipykernel_28748/4271812969.py in <module>
    182         print(cat_subset.orientation)
    183         source_angle.append(cat_subset.orientation)
--> 184         print(cat_subset.max_value)
    185         source_peak.append(cat_subset.max_value)
    186         print(cat_subset.fwhm)

~/anaconda3/lib/python3.7/site-packages/astropy/utils/decorators.py in __get__(self, obj, owner)
    756                 return val
    757             else:
--> 758                 val = self.fget(obj)
    759                 obj.__dict__[self._key] = val
    760                 return val

~/anaconda3/lib/python3.7/site-packages/photutils/segmentation/catalog.py in _decorator(*args, **kwargs)
     44     @functools.wraps(method)
     45     def _decorator(*args, **kwargs):
---> 46         result = method(*args, **kwargs)
     47         try:
     48             return (result[0] if args[0].isscalar and len(result) == 1

~/anaconda3/lib/python3.7/site-packages/photutils/segmentation/catalog.py in max_value(self)
   1338         """
   1339         values = np.array([np.max(array) for array in self._data_values])
-> 1340         values -= self._local_background
   1341         if self._data_unit is not None:
   1342             values <<= self._data_unit

UFuncTypeError: Cannot cast ufunc 'subtract' output from dtype('float64') to dtype('uint16') with casting rule 'same_kind'`

It looks like the image data from my FITS file has type dtype('float64'), but the SourceCatalog expects the data as dtype('uint16')?

Thanks,
Andre

Discussion:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index f1fe142f..85fb110c 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -58,6 +58,11 @@ Bug Fixes
       ``xpeak``, or ``ypeak`` to ``None`` would result in an error.
       [#1297]
 
+- ``photutils.segmentation``
+
+    - Fixed a bug in ``SourceCatalog`` where a ``UFuncTypeError`` would
+      be raised if the input ``data`` had an integer ``dtype`` [#1312].
+
 API Changes
 ^^^^^^^^^^^
 
diff --git a/photutils/segmentation/catalog.py b/photutils/segmentation/catalog.py
index f6cd54c3..0a02b025 100644
--- a/photutils/segmentation/catalog.py
+++ b/photutils/segmentation/catalog.py
@@ -223,13 +223,15 @@ class SourceCatalog:
 
         (data, error, background), unit = process_quantities(
             (data, error, background), ('data', 'error', 'background'))
-        self._data = self._validate_array(data, 'data', shape=False)
+        self._data = self._validate_array(data, 'data', shape=False,
+                                          dtype=float)
         self._data_unit = unit
         self._segment_img = self._validate_segment_img(segment_img)
-        self._error = self._validate_array(error, 'error')
-        self._mask = self._validate_array(mask, 'mask')
+        self._error = self._validate_array(error, 'error', dtype=float)
+        self._mask = self._validate_array(mask, 'mask', dtype=None)
         self._kernel = kernel
-        self._background = self._validate_array(background, 'background')
+        self._background = self._validate_array(background, 'background',
+                                                dtype=float)
         self._wcs = wcs
 
         self._convolved_data = self._convolve_data()
@@ -263,11 +265,13 @@ class SourceCatalog:
             raise ValueError('segment_img and data must have the same shape.')
         return segment_img
 
-    def _validate_array(self, array, name, shape=True):
+    def _validate_array(self, array, name, shape=True, dtype=None):
         if name == 'mask' and array is np.ma.nomask:
             array = None
         if array is not None:
-            array = np.asanyarray(array)
+            # UFuncTypeError is raised when subtracting float
+            # local_background from int data; convert to float
+            array = np.asanyarray(array, dtype=dtype)
             if array.ndim != 2:
                 raise ValueError(f'{name} must be a 2D array.')
             if shape and array.shape != self._data.shape:


Test patch:
diff --git a/photutils/segmentation/tests/test_catalog.py b/photutils/segmentation/tests/test_catalog.py
index ab1ba768..88e6f4a0 100644
--- a/photutils/segmentation/tests/test_catalog.py
+++ b/photutils/segmentation/tests/test_catalog.py
@@ -673,3 +673,18 @@ class TestSourceCatalog:
         assert 'kron_flux' not in cat2.__dict__
         tbl = cat2.to_table()
         assert len(tbl) == 7
+
+    def test_data_dtype(self):
+        """
+        Regression test that input ``data`` with int dtype does not
+        raise UFuncTypeError due to subtraction of float array from int
+        array.
+        """
+        data = np.zeros((25, 25), dtype=np.uint16)
+        data[8:16, 8:16] = 10
+        segmdata = np.zeros((25, 25))
+        segmdata[8:16, 8:16] = 1
+        segm = SegmentationImage(segmdata)
+        cat = SourceCatalog(data, segm, localbkg_width=3)
+        assert cat.min_value == 10
+        assert cat.max_value == 10