Repository: spacetelescope/jwql

Issue:
Preview image generator crash
Another crash of the preview image generator today:

07/27/2022 16:14:02 PM INFO: Working on /ifs/archive/ops/jwst/distribution/public/jw01092/jw01092010001/jw01092010001_02101_00004_nis_uncal.fits
07/27/2022 16:14:02 PM INFO: 	JPG already exists for /ifs/archive/ops/jwst/distribution/public/jw01092/jw01092010001/jw01092010001_02101_00004_nis_uncal.fits, skipping.
07/27/2022 16:14:02 PM INFO: Working on /ifs/archive/ops/jwst/distribution/public/jw01092/jw01092002001/jw01092002001_02101_00001_nis_trapsfilled.fits
07/27/2022 16:14:02 PM INFO: 	JPG already exists for /ifs/archive/ops/jwst/distribution/public/jw01092/jw01092002001/jw01092002001_02101_00001_nis_trapsfilled.fits, skipping.
07/27/2022 16:14:03 PM CRITICAL: multiprocessing.pool.RemoteTraceback: 
"""
Traceback (most recent call last):
  File "/home/svc_jwqladm_ops/jwql/miniconda3/envs/jwql-3.8/lib/python3.8/multiprocessing/pool.py", line 125, in worker
    result = (True, func(*args, **kwds))
  File "/home/svc_jwqladm_ops/jwql/miniconda3/envs/jwql-3.8/lib/python3.8/multiprocessing/pool.py", line 48, in mapstar
    return list(map(*args))
  File "jwql_monitors/generate_preview_images.py", line 736, in process_program
    im.make_image(max_img_size=8, create_thumbnail=False)
  File "/home/svc_jwqladm_ops/jwql/jwql/jwql/utils/preview_image.py", line 428, in make_image
    minval, maxval = self.find_limits(frame, self.dq,
  File "/home/svc_jwqladm_ops/jwql/jwql/jwql/utils/preview_image.py", line 183, in find_limits
    minval = sorted[numclip]
IndexError: index 41943 is out of bounds for axis 0 with size 1

This implies that the code is trying to clip 41,943 pixels from the image (sorted by brightness), but the image itself has only a single good pixel in it. Again, because we are using multiprocessing, the log isn't really helpful about specifying what file caused the problem. 

generate_preview_images_2022-07-27-11-12.log


Discussion:

Resolving patch:
diff --git a/jwql/jwql_monitors/generate_preview_images.py b/jwql/jwql_monitors/generate_preview_images.py
--- a/jwql/jwql_monitors/generate_preview_images.py
+++ b/jwql/jwql_monitors/generate_preview_images.py
@@ -673,8 +673,8 @@ def process_program(program):
     logging.info('Processing {}'.format(program))
 
     # Gather files to process
-    filenames = glob.glob(os.path.join(SETTINGS['filesystem'], 'public', program, '*/*.fits'))
-    filenames.extend(glob.glob(os.path.join(SETTINGS['filesystem'], 'proprietary', program, '*/*.fits')))
+    filenames = glob.glob(os.path.join(SETTINGS['filesystem'], 'public', program, 'jw*/*.fits'))
+    filenames.extend(glob.glob(os.path.join(SETTINGS['filesystem'], 'proprietary', program, 'jw*/*.fits')))
     filenames = list(set(filenames))
 
     # remove specific "ignored" suffix files (currently "original" and "stream")
diff --git a/jwql/utils/preview_image.py b/jwql/utils/preview_image.py
--- a/jwql/utils/preview_image.py
+++ b/jwql/utils/preview_image.py
@@ -143,7 +143,7 @@ def difference_image(self, data):
         """
         return data[:, -1, :, :] - data[:, 0, :, :]
 
-    def find_limits(self, data, pixmap, clipperc):
+    def find_limits(self, data):
         """
         Find the minimum and maximum signal levels after clipping the
         top and bottom ``clipperc`` of the pixels.
@@ -152,36 +152,33 @@ def find_limits(self, data, pixmap, clipperc):
         ----------
         data : obj
             2D numpy ndarray of floats
-        pixmap : obj
-            2D numpy ndarray boolean array of science pixel locations
-            (``True`` for science pixels, ``False`` for non-science
-            pixels)
-        clipperc : float
-            Fraction of top and bottom signal levels to clip (e.g. 0.01
-            means to clip brightest and dimmest 1% of pixels)
 
         Returns
         -------
         results : tuple
             Tuple of floats, minimum and maximum signal levels
         """
-        nelem = np.sum(pixmap)
-        numclip = np.int32(clipperc * nelem)
-
         # Ignore any pixels that are NaN
         finite = np.isfinite(data)
 
+        # Combine maps of science pixels and finite pixels
+        pixmap = self.dq & finite
+
         # If all non-science pixels are NaN then we're sunk. Scale
         # from 0 to 1.
-        if not np.any(finite):
+        if not np.any(pixmap):
             logging.info('No pixels with finite signal. Scaling from 0 to 1')
             return (0., 1.)
 
-        pixmap = pixmap & finite
+        sorted_pix = np.sort(data[pixmap], axis=None)
+
+        # Determine how many pixels to clip off of the high and low ends
+        nelem = np.sum(pixmap)
+        numclip = np.int32(self.clip_percent * nelem)
 
-        sorted = np.sort(data[pixmap], axis=None)
-        minval = sorted[numclip]
-        maxval = sorted[-numclip - 1]
+        # Determine min and max scaling levels
+        minval = sorted_pix[numclip]
+        maxval = sorted_pix[-numclip - 1]
         return (minval, maxval)
 
     def get_data(self, filename, ext):
@@ -425,8 +422,7 @@ def make_image(self, max_img_size=8.0, create_thumbnail=False):
             frame = diff_img[i, :, :]
 
             # Find signal limits for the display
-            minval, maxval = self.find_limits(frame, self.dq,
-                                              self.clip_percent)
+            minval, maxval = self.find_limits(frame)
 
             # Create preview image matplotlib object
             indir, infile = os.path.split(self.file)


Test patch: