Source code for romanimpreprocess.utils.maskhandling

"""
Tools for building a mask by growing the input bitmask by different amounts.

Classes
-------
CombinedMask
    Class to generate a boolean mask from multiple flags.

"""


import asdf
import numpy as np
from astropy.io import fits
from roman_datamodels.dqflags import pixel
from scipy.signal import convolve


[docs] class CombinedMask: """ Class to generate a boolean mask from multiple flags. Parameters ---------- maskdict : dict A dictionary of how much to grow each flag (see notes for description). Attributes ---------- array : dict Dictionary of how much to grow each flag. Methods ------- __init__ Constructor. build Make boolean mask from a data quality array. convert_file Stand-alone function to make a mask from a file. Notes ----- To take a dq array, and flag any pixel with ``'gw_affected_data'`` and the cardinal-nearest neigbors if ``'jump_det'`` is set:: myMaskFunc = CombinedMask({'jump_det': 5, 'gw_affected_data': 1}) mymask = myMaskFunc.build(dq) The mask names are as described in ``roman_datamodels.dqflags.pixel``. Note that capitalization is automatic so this function is not case-sensitive. Options for growing are specified by the number of pixels affected: * 1 = that pixel * 5 = cardinal nearest neighbors * 9 = 3x3 block * 25 = 5x5 block """ # Kernel dictionary is a class variable
[docs] kerneldict = { 5: np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]]).astype(np.int16), 9: np.ones((3, 3), dtype=np.int16), 25: np.ones((5, 5), dtype=np.int16), }
[docs] def __init__(self, maskdict):
[docs] self.array = np.zeros(32, dtype=np.uint8)
for d in maskdict: if isinstance(d, int): whichbit = d if isinstance(d, str): e = getattr(pixel, d.upper()) whichbit = 0 for x in range(32): if e >> x == 1: whichbit = x self.array[whichbit] = int(maskdict[d])
[docs] def build(self, dq): """ Make boolean mask from a data quality array. Parameters ---------- dq : np.array of uint32 2D data quality array. Returns ------- np.array of bool Grown mask; True indicates a masked pixel, False a normal pixel. """ (ny, nx) = np.shape(dq) mask = np.zeros((ny, nx), dtype=bool) # loop over each bit for whichbit in range(32): if self.array[whichbit] > 0: layer = np.where(np.uint32(2**whichbit) & dq != 0, 1, 0).astype(np.int16) # now different types of growing masks. 1 is a simple copy, >1 leads to a convolution if self.array[whichbit] == 1: mask |= layer >= 1 else: mask |= ( convolve( 2 * layer, self.kerneldict[self.array[whichbit]], mode="same", method="direct" ) >= 1 ) return mask
[docs] def convert_file(self, file_in, file_mask): """ Stand-alone function to make a mask from a file. The type of output depends on the `file_mask` extension: * If .asdf is requested, simply writes the boolean array. * If .fits is requested, makes a masked image (HDU0, for display purposes) and an int8 version (HDU1). Parameters ---------- file_in : str The input ASDF file, in L2 format. file_mask : str The output file. Returns ------- None """ with asdf.open(file_in) as f_in: locmask = self.build(f_in["roman"]["dq"]) if file_mask[-5:] == ".asdf": asdf.AsdfFile({"mask": locmask}).write_to(file_mask) elif file_mask[-5:] == ".fits": h1 = fits.PrimaryHDU(np.where(locmask, -1000.0, f_in["roman"]["data"]).astype(np.float32)) h2 = fits.ImageHDU(np.where(locmask, 1, 0).astype(np.int8)) h2.header["EXTNAME"] = "MASK" fits.HDUList([h1, h2]).writeto(file_mask, overwrite=True)
# Some specific choices you may want
[docs] PixelMask1 = CombinedMask( { "DO_NOT_USE": 1, "JUMP_DET": 5, "DROPOUT": 25, "GW_AFFECTED_DATA": 1, "PERSISTENCE": 1, "AD_FLOOR": 5, "UNRELIABLE_ERROR": 1, "NON_SCIENCE": 1, "DEAD": 9, "HOT": 9, "WARM": 1, "LOW_QE": 9, "TELEGRAPH": 1, "NO_FLAT_FIELD": 9, "NO_GAIN_VALUE": 9, "NO_LIN_CORR": 9, "NO_SAT_CHECK": 9, "UNRELIABLE_BIAS": 1, "UNRELIABLE_DARK": 9, "UNRELIABLE_SLOPE": 9, "UNRELIABLE_FLAT": 9, "UNRELIABLE_RESET": 9, "OTHER_BAD_PIXEL": 9, } )