Example #1
0
    def __init__(self,
                 naxis1=None,
                 naxis2=None,
                 naxis3=None,
                 n_out=None,
                 dt=None,
                 nroh=None,
                 nfoh=None,
                 nfoh_pix=None,
                 dark_file=None,
                 bias_file=None,
                 verbose=False,
                 reverse_scan_direction=False,
                 reference_pixel_border_width=None,
                 wind_mode='FULL',
                 x0=0,
                 y0=0,
                 det_size=None,
                 use_fftw=False,
                 ncores=None):

        # pyFFTW usage
        self.use_fftw = True if (use_fftw and pyfftw_available) else False
        # By default, use 50% of available cores for FFTW parallelization
        self.ncores = mp.cpu_count() // 2 if ncores is None else int(ncores)

        # ======================================================================
        #
        # DEFAULT CLOCKING PARAMETERS
        #
        # The following parameters define the default HxRG clocking pattern. The
        # parameters that define the default noise model are defined in the
        # mknoise() method.
        #
        # ======================================================================

        # Subarray?
        if wind_mode is None:
            wind_mode = 'FULL'
        if det_size is None:
            det_size = 2048
        wind_mode = wind_mode.upper()
        modes = ['FULL', 'STRIPE', 'WINDOW']
        if wind_mode not in modes:
            _log.warning('%s not a valid window readout mode! Returning...' %
                         inst_params['wind_mode'])
            os.sys.exit()
        if wind_mode == 'WINDOW':
            n_out = 1
        if wind_mode == 'FULL':
            x0 = 0
            y0 = 0
        if wind_mode == 'STRIPE':
            x0 = 0

        # Default clocking pattern is JWST NIRSpec
        self.naxis1 = 2048 if naxis1 is None else int(naxis1)
        self.naxis2 = 2048 if naxis2 is None else int(naxis2)
        self.naxis3 = 1 if naxis3 is None else int(naxis3)
        self.n_out = 4 if n_out is None else int(n_out)
        self.dt = 10e-6 if dt is None else dt
        self.nroh = 12 if nroh is None else int(nroh)
        self.nfoh = 1 if nfoh is None else int(nfoh)
        self.nfoh_pix = 0  #if nfoh_pix is None else int(nfoh_pix)
        self.reference_pixel_border_width = 4 if reference_pixel_border_width is None \
                                              else int(reference_pixel_border_width)

        # Check that det_size is greater than self.naxis1 and self.naxis2 in WINDOW mode (JML)
        if wind_mode == 'WINDOW':
            if (self.naxis1 > det_size):
                _log.warning(
                    'NAXIS1 %s greater than det_size %s! Returning...' %
                    (self.naxis1, det_size))
                os.sys.exit()
            if (self.naxis2 > det_size):
                _log.warning(
                    'NAXIS2 %s greater than det_size %s! Returning...' %
                    (self.naxis1, det_size))
                os.sys.exit()

        # Initialize PCA-zero file and make sure that it exists and is a file
        #self.bias_file = os.getenv('NGHXRG_HOME')+'/sca_images/nirspec_pca0.fits' if \
        #				 bias_file is None else bias_file
        #self.bias_file = 'nirspec_pca0.fits' if bias_file is None else bias_file
        self.bias_file = bias_file
        if bias_file is not None:
            if os.path.isfile(self.bias_file) is False:
                raise ValueError(
                    'There was an error finding bias_file {}'.format(
                        bias_file))
                print('There was an error finding bias_file!')
                print(bias_file)
                #os.sys.exit()

#             print('There was an error finding bias_file! Check to be')
#             print('sure that the NGHXRG_HOME shell environment')
#             print('variable is set correctly and that the')
#             print('$NGHXRG_HOME/ directory contains the desired PCA0')
#             print('file. The default is nirspec_pca0.fits.')
#             os.sys.exit()

# Add in dark current file (JML)
        self.dark_file = dark_file
        if dark_file is not None:
            if os.path.isfile(self.dark_file) is False:
                raise ValueError(
                    'There was an error finding dark_file {}'.format(
                        dark_file))
                #print('There was an error finding dark_file!')
                #print(dark_file)
                #os.sys.exit()

        # ======================================================================

        # Configure Subarray
        self.wind_mode = wind_mode
        self.det_size = det_size
        self.x0 = x0
        self.y0 = y0

        # Configure status reporting
        self.verbose = verbose

        # Configure readout direction
        self.reverse_scan_direction = reverse_scan_direction

        # Compute the number of pixels in the fast-scan direction per output
        self.xsize = self.naxis1 // self.n_out

        # Compute the number of time steps per integration, per output
        self.nstep_frame = (self.xsize + self.nroh) * (
            self.naxis2 + self.nfoh) + self.nfoh_pix
        self.nstep = self.nstep_frame * self.naxis3
        # Pad nsteps to a power of 2, which is much faster
        self.nstep2 = int(2**np.ceil(np.log2(self.nstep)))

        # Compute frame time and ramp time
        self.tframe = self.nstep_frame * self.dt
        self.inttime = self.tframe * self.naxis3

        # For adding in ACN, it is handy to have masks of the even
        # and odd pixels on one output neglecting any gaps
        self.m_even = np.zeros((self.naxis3, self.naxis2, self.xsize))
        self.m_odd = np.zeros_like(self.m_even)
        for x in np.arange(0, self.xsize, 2):
            self.m_even[:, :self.naxis2, x] = 1
            self.m_odd[:, :self.naxis2, x + 1] = 1
        self.m_even = np.reshape(self.m_even, np.size(self.m_even))
        self.m_odd = np.reshape(self.m_odd, np.size(self.m_odd))

        # Also for adding in ACN, we need a mask that point to just
        # the real pixels in ordered vectors of just the even or odd
        # pixels
        self.m_short = np.zeros((self.naxis3, self.naxis2+self.nfoh, \
                                      (self.xsize+self.nroh)//2))
        self.m_short[:, :self.naxis2, :self.xsize // 2] = 1
        self.m_short = np.reshape(self.m_short, np.size(self.m_short))

        # Define frequency arrays
        self.f1 = np.fft.rfftfreq(
            self.nstep2)  # Frequencies for nstep elements
        self.f2 = np.fft.rfftfreq(2 * self.nstep2)  # ... for 2*nstep elements
        self.f3 = np.fft.rfftfreq(2 * self.naxis3)

        # Define pinkening filters. F1 and p_filter1 are used to
        # generate ACN. F2 and p_filter2 are used to generate 1/f noise.
        self.alpha = -1  # Hard code for 1/f noise until proven otherwise
        self.p_filter1 = np.sqrt(self.f1**self.alpha)
        self.p_filter2 = np.sqrt(self.f2**self.alpha)
        self.p_filter3 = np.sqrt(self.f3**self.alpha)
        self.p_filter1[0] = 0.
        self.p_filter2[0] = 0.
        self.p_filter3[0] = 0.

        # Initialize pca0. This includes scaling to the correct size,
        # zero offsetting, and renormalization. We use robust statistics
        # because pca0 is real data
        if self.bias_file is None:
            h = fits.PrimaryHDU(np.zeros([det_size, det_size]))
            hdu = fits.HDUList([h])
        else:
            hdu = fits.open(self.bias_file)
        nx_pca0 = hdu[0].header['naxis1']
        ny_pca0 = hdu[0].header['naxis2']
        data = hdu[0].data

        # Make sure the real PCA image is correctly scaled to size of fake data (JML)
        # Depends if we're FULL, STRIPE, or WINDOW
        if wind_mode == 'FULL':
            scale1 = self.naxis1 / nx_pca0
            scale2 = self.naxis2 / ny_pca0
            zoom_factor = np.max([scale1, scale2])
        if wind_mode == 'STRIPE':
            zoom_factor = self.naxis1 / nx_pca0
        if wind_mode == 'WINDOW':
            # Scale based on det_size
            scale1 = self.det_size / nx_pca0
            scale2 = self.det_size / ny_pca0
            zoom_factor = np.max([scale1, scale2])

        # Resize PCA0 data
        #print(zoom_factor)
        if zoom_factor != 1:
            data = zoom(data, zoom_factor, order=1, mode='wrap')

        # Copy data to save as bias pattern
        bias_image = data.copy()

        # Renormalize for PCA0 noise stuff
        data -= np.median(data)  # Zero offset
        data /= (1.4826 * mad(data))  # Renormalize

        # Select region of pca0 associated with window position
        if self.wind_mode == 'WINDOW':
            x1 = self.x0
            y1 = self.y0
        elif self.wind_mode == 'STRIPE':
            x1 = 0
            y1 = self.y0
        else:
            x1 = 0
            y1 = 0

        x2 = x1 + self.naxis1
        y2 = y1 + self.naxis2
        # Make sure x2 and y2 are valid
        if (x2 > data.shape[0] or y2 > data.shape[1]):
            _log.warning(
                'Specified window size does not fit within detector array!')
            _log.warning(
                'X indices: [%s,%s]; Y indices: [%s,%s]; XY Size: [%s, %s]' %
                (x1, x2, y1, y2, data.shape[0], data.shape[1]))
            os.sys.exit()

        # Save as properties
        self.pca0 = data[y1:y2, x1:x2]
        self.bias_image = bias_image[y1:y2, x1:x2]

        # Open dark current file (ADU/sec/pixel)
        if self.dark_file is not None:
            dark_hdu = fits.open(self.dark_file)
            dark_image = dark_hdu[0].data
            self.dark_image = dark_image[y1:y2, x1:x2]
            # Dark current distributions are very wide because of uncertainties
            # This causes certain pixels to fall below 0.
            # We can assume all pixels within 5-sigma have the same dark current
            #   as well as those with negative values.
            # Those with large dark currents are likely real.
            sig = 1.4826 * mad(dark_image)
            med = np.median(dark_image)
            l1 = med - 5 * sig
            l2 = med + 5 * sig
            self.dark_image[(self.dark_image > l1)
                            & (self.dark_image < l2)] = med
            # Set negative values to median
            self.dark_image[self.dark_image < 0] = med

            # Set negative values to median
            #self.dark_image[self.dark_image<0] = np.median(self.dark_image)
            #self.dark_image[self.dark_image<0.005] = 0.001
        else:
            self.dark_image = None

        # How many reference pixels on each border?
        w = self.reference_pixel_border_width  # Easier to work with
        lower = w - y1
        upper = w - (det_size - y2)
        left = w - x1
        right = w - (det_size - x2)
        ref_all = np.array([lower, upper, left, right])
        ref_all[ref_all < 0] = 0
        self.ref_all = ref_all
Example #2
0
    def __init__(self,
                 naxis1=None,
                 naxis2=None,
                 naxis3=None,
                 n_out=None,
                 dt=None,
                 nroh=None,
                 nfoh=None,
                 pca0_file=None,
                 verbose=False,
                 reverse_scan_direction=False,
                 reference_pixel_border_width=None):
        """
        Simulate Teledyne HxRG+SIDECAR ASIC system noise.

        Parameters
        ----------
        naxis1 : int
            X-dimension of the FITS cube
        naxis2 : int
            Y-dimension of the FITS cube
        naxis3 : int
            Z-dimension of the FITS cube (number of up-the-ramp samples)
        n_out : int
            Number of detector outputs
        nfoh : int
            New frame overhead in rows. This allows for a short wait at the end 
            of a frame before starting the next one.
        nroh : int
            New row overhead in pixels. This allows for a short
            wait at the end of a row before starting the next one.
        dt : int
            Pixel dwell time in seconds
        pca0_file : str
            Name of a FITS file that contains PCA-zero
        verbose : bool
            Enable this to provide status reporting
        reference_pixel_border_width : int 
            Width of reference pixel border around image area
        reverse_scan_direction : bool
            Enable this to reverse the fast scanner readout directions. This
            capability was added to support Teledyne's programmable fast scan
            readout directions. The default setting =False corresponds to
            what HxRG detectors default to upon power up.
        """

        # ======================================================================
        #
        # DEFAULT CLOCKING PARAMETERS
        #
        # The following parameters define the default HxRG clocking pattern. The
        # parameters that define the default noise model are defined in the
        # mknoise() method.
        #
        # ======================================================================

        # Default clocking pattern is JWST NIRSpec
        self.naxis1 = 2048 if naxis1 is None else int(naxis1)
        self.naxis2 = 2048 if naxis2 is None else int(naxis2)
        self.naxis3 = 1 if naxis3 is None else int(naxis3)
        self.n_out = 4 if n_out is None else int(n_out)
        self.dt = 1.e-5 if dt is None else dt
        self.nroh = 12 if nroh is None else int(nroh)
        self.nfoh = 1 if nfoh is None else int(nfoh)
        self.reference_pixel_border_width = 4 \
                                            if reference_pixel_border_width is \
                                            None else reference_pixel_border_width

        # Initialize PCA-zero file and make sure that it exists and is a file
        self.pca0_file = os.getenv('NGHXRG_HOME')+'/nirspec_pca0.fits' if \
                         pca0_file is None else pca0_file
        if os.path.isfile(self.pca0_file) is False:
            print('There was an error finding pca0_file! Check to be')
            print('sure that the NGHXRG_HOME shell environment')
            print('variable is set correctly and that the')
            print('$NGHXRG_HOME/ directory contains the desired PCA0')
            print('file. The default is nirspec_pca0.fits.')
            os.sys.exit()

        # ======================================================================

        # Configure status reporting
        self.verbose = verbose

        # Configure readout direction
        self.reverse_scan_direction = reverse_scan_direction

        # Compute the number of pixels in the fast-scan direction per
        # output
        self.xsize = self.naxis1 // self.n_out

        # Compute the number of time steps per integration, per
        # output
        self.nstep = (self.xsize+self.nroh) * (self.naxis2+self.nfoh)\
                     * self.naxis3

        # For adding in ACN, it is handy to have masks of the even
        # and odd pixels on one output neglecting any gaps
        self.m_even = np.zeros((self.naxis3, self.naxis2, self.xsize))
        self.m_odd = np.zeros_like(self.m_even)
        for x in np.arange(0, self.xsize, 2):
            self.m_even[:, :self.naxis2, x] = 1
            self.m_odd[:, :self.naxis2, x + 1] = 1
        self.m_even = np.reshape(self.m_even, np.size(self.m_even))
        self.m_odd = np.reshape(self.m_odd, np.size(self.m_odd))

        # Also for adding in ACN, we need a mask that point to just
        # the real pixels in ordered vectors of just the even or odd
        # pixels
        self.m_short = np.zeros((self.naxis3, self.naxis2+self.nfoh, \
                                      (self.xsize+self.nroh)//2))
        self.m_short[:, :self.naxis2, :self.xsize // 2] = 1
        self.m_short = np.reshape(self.m_short, np.size(self.m_short))

        # Define frequency arrays
        self.f1 = np.fft.rfftfreq(self.nstep)  # Frequencies for nstep elements
        self.f2 = np.fft.rfftfreq(2 * self.nstep)  # ... for 2*nstep elements

        # Define pinkening filters. F1 and p_filter1 are used to
        # generate ACN. F2 and p_filter2 are used to generate 1/f noise.
        self.alpha = -1  # Hard code for 1/f noise until proven otherwise
        self.p_filter1 = np.sqrt(self.f1**self.alpha)
        self.p_filter2 = np.sqrt(self.f2**self.alpha)
        self.p_filter1[0] = 0.
        self.p_filter2[0] = 0.

        # Initialize pca0. This includes scaling to the correct size,
        # zero offsetting, and renormalization. We use robust statistics
        # because pca0 is real data
        hdu = fits.open(self.pca0_file)
        naxis1 = hdu[0].header['naxis1']
        naxis2 = hdu[0].header['naxis2']
        if (naxis1 != self.naxis1 or naxis2 != self.naxis2):
            zoom_factor = self.naxis1 / naxis1
            self.pca0 = zoom(hdu[0].data, zoom_factor, order=1, mode='wrap')
        else:
            self.pca0 = hdu[0].data
        self.pca0 -= np.median(self.pca0)  # Zero offset
        self.pca0 /= (1.4826 * mad(self.pca0))  # Renormalize
Example #3
0
    def __init__(self,
                 naxis1=None,
                 naxis2=None,
                 naxis3=None,
                 n_out=None,
                 dt=None,
                 nroh=None,
                 nfoh=None,
                 pca0_file=None,
                 verbose=False,
                 reverse_scan_direction=False,
                 reference_pixel_border_width=None,
                 wind_mode='FULL',
                 x0=0,
                 y0=0,
                 det_size=None):
        """
        Simulate Teledyne HxRG+SIDECAR ASIC system noise.
        Parameters:
            naxis1      - X-dimension of the FITS cube
            naxis2      - Y-dimension of the FITS cube
            naxis3      - Z-dimension of the FITS cube
                          (number of up-the-ramp samples)
            n_out       - Number of detector outputs
            nfoh        - New frame overhead in rows. This allows for a short
                          wait at the end of a frame before starting the next
                          one.
            nroh        - New row overhead in pixels. This allows for a short
                          wait at the end of a row before starting the next one.
            dt          - Pixel dwell time in seconds
            pca0_file   - Name of a FITS file that contains PCA-zero
            verbose     - Enable this to provide status reporting
            wind_mode   - 'FULL', 'STRIPE', or 'WINDOW' (JML)
            x0/y0       - Pixel positions of subarray mode (JML)
            det_size    - Pixel dimension of full detector (square), used only
                          for WINDOW mode (JML)
            reference_pixel_border_width - Width of reference pixel border
                                           around image area
            reverse_scan_direction - Enable this to reverse the fast scanner
                                     readout directions. This
                                     capability was added to support
                                     Teledyne's programmable fast scan
                                     readout directions. The default
                                     setting =False corresponds to
                                     what HxRG detectors default to
                                     upon power up.
        """

        # ======================================================================
        #
        # DEFAULT CLOCKING PARAMETERS
        #
        # The following parameters define the default HxRG clocking pattern. The
        # parameters that define the default noise model are defined in the
        # mknoise() method.
        #
        # ======================================================================

        # Subarray Mode? (JML)
        if wind_mode is None:
            wind_mode = 'FULL'
        if det_size is None:
            det_size = 2048
        wind_mode = wind_mode.upper()
        modes = ['FULL', 'STRIPE', 'WINDOW']
        if wind_mode not in modes:
            _log.warn('%s not a valid window readout mode! Returning...' %
                      inst_params['wind_mode'])
            os.sys.exit()
        if wind_mode == 'WINDOW':
            n_out = 1
        if wind_mode == 'FULL':
            x0 = 0
            y0 = 0
        if wind_mode == 'STRIPE':
            x0 = 0

        # Default clocking pattern is JWST NIRSpec
        self.naxis1 = 2048 if naxis1 is None else naxis1
        self.naxis2 = 2048 if naxis2 is None else naxis2
        self.naxis3 = 1 if naxis3 is None else naxis3
        self.n_out = 4 if n_out is None else n_out
        self.dt = 1.e-5 if dt is None else dt
        self.nroh = 12 if nroh is None else nroh
        self.nfoh = 1 if nfoh is None else nfoh
        self.reference_pixel_border_width = 4 if reference_pixel_border_width is None \
            else reference_pixel_border_width

        # Check that det_size is greater than self.naxis1 and self.naxis2 in WINDOW mode (JML)
        if wind_mode == 'WINDOW':
            if (self.naxis1 > det_size):
                _log.warn('NAXIS1 %s greater than det_size %s! Returning...' %
                          (self.naxis1, det_size))
                os.sys.exit()
            if (self.naxis2 > det_size):
                _log.warn('NAXIS2 %s greater than det_size %s! Returning...' %
                          (self.naxis1, det_size))
                os.sys.exit()

        # Initialize PCA-zero file and make sure that it exists and is a file
        path = os.getenv('NIRISS_NOISE_HOME')
        if path is None:
            path = '.'
        self.pca0_file = path + '/niriss_pca0.fits' if \
            pca0_file is None else pca0_file
        if os.path.isfile(self.pca0_file) is False:
            print('There was an error finding pca0_file! Check to be')
            print('sure that the NIRISS_NOISE_HOME shell environment')
            print('variable is set correctly and that the')
            print('$NIRISS_NOISE_HOME/ directory contains the desired PCA0')
            print('file. The default is niriss_pca0.fits.')
            os.sys.exit()

        # ======================================================================

        # Configure Subarray (JML)
        self.wind_mode = wind_mode
        self.det_size = det_size
        self.x0 = x0
        self.y0 = y0

        # Configure status reporting
        self.verbose = verbose

        # Configure readout direction
        self.reverse_scan_direction = reverse_scan_direction

        # Compute the number of pixels in the fast-scan direction per
        # output
        self.xsize = self.naxis1 // self.n_out

        # Compute the number of time steps per integration, per
        # output
        self.nstep = (self.xsize + self.nroh) * (self.naxis2 +
                                                 self.nfoh) * self.naxis3
        # Pad nsteps to a power of 2, which is much faster (JML)
        self.nstep2 = int(2**np.ceil(np.log2(self.nstep)))

        # For adding in ACN, it is handy to have masks of the even
        # and odd pixels on one output neglecting any gaps
        self.m_even = np.zeros((self.naxis3, self.naxis2, self.xsize))
        self.m_odd = np.zeros_like(self.m_even)
        for x in np.arange(0, self.xsize, 2):
            self.m_even[:, :self.naxis2, x] = 1
            self.m_odd[:, :self.naxis2, x + 1] = 1
        self.m_even = np.reshape(self.m_even, np.size(self.m_even))
        self.m_odd = np.reshape(self.m_odd, np.size(self.m_odd))

        # Also for adding in ACN, we need a mask that point to just
        # the real pixels in ordered vectors of just the even or odd
        # pixels
        self.m_short = np.zeros((self.naxis3, self.naxis2 + self.nfoh, \
                                 (self.xsize + self.nroh) // 2))
        self.m_short[:, :self.naxis2, :self.xsize // 2] = 1
        self.m_short = np.reshape(self.m_short, np.size(self.m_short))

        # Define frequency arrays
        self.f1 = np.fft.rfftfreq(
            self.nstep2)  # Frequencies for nstep elements
        self.f2 = np.fft.rfftfreq(2 * self.nstep2)  # ... for 2*nstep elements

        # Define pinkening filters. F1 and p_filter1 are used to
        # generate ACN. F2 and p_filter2 are used to generate 1/f noise.
        self.alpha = -1  # Hard code for 1/f noise until proven otherwise
        self.p_filter1 = np.sqrt(self.f1**self.alpha)
        self.p_filter2 = np.sqrt(self.f2**self.alpha)
        self.p_filter1[0] = 0.
        self.p_filter2[0] = 0.

        # Initialize pca0. This includes scaling to the correct size,
        # zero offsetting, and renormalization. We use robust statistics
        # because pca0 is real data
        hdu = fits.open(self.pca0_file)
        nx_pca0 = hdu[0].header['naxis1']
        ny_pca0 = hdu[0].header['naxis2']

        # Do this slightly differently, taking into account the
        # different types of readout modes (JML)
        # if (nx_pca0 != self.naxis1 or naxis2 != self.naxis2):
        #    zoom_factor = self.naxis1 / nx_pca0
        #    self.pca0 = zoom(hdu[0].data, zoom_factor, order=1, mode='wrap')
        # else:
        #    self.pca0 = hdu[0].data
        # self.pca0 -= np.median(self.pca0) # Zero offset
        # self.pca0 /= (1.4826*mad(self.pca0)) # Renormalize

        data = hdu[0].data
        # Make sure the real PCA image is correctly scaled to size of fake data (JML)
        # Depends if we're FULL, STRIPE, or WINDOW
        if wind_mode == 'FULL':
            scale1 = self.naxis1 / nx_pca0
            scale2 = self.naxis2 / ny_pca0
            zoom_factor = np.max([scale1, scale2])
        if wind_mode == 'STRIPE':
            zoom_factor = self.naxis1 / nx_pca0
        if wind_mode == 'WINDOW':
            # Scale based on det_size
            scale1 = self.det_size / nx_pca0
            scale2 = self.det_size / ny_pca0
            zoom_factor = np.max([scale1, scale2])

        # Resize PCA0 data
        if zoom_factor != 1:
            data = zoom(data, zoom_factor, order=1, mode='wrap')

        data -= np.median(data)  # Zero offset
        data /= (1.4826 * mad(data))  # Renormalize

        # Select region of pca0 associated with window position
        if self.wind_mode == 'WINDOW':
            x1 = self.x0
            y1 = self.y0
        elif self.wind_mode == 'STRIPE':
            x1 = 0
            y1 = self.y0
        else:
            x1 = 0
            y1 = 0

        # print(y1, self.naxis2) This appears to be a stub
        x2 = x1 + self.naxis1
        y2 = y1 + self.naxis2
        # Make sure x2 and y2 are valid
        if (x2 > data.shape[0] or y2 > data.shape[1]):
            _log.warn(
                'Specified window size does not fit within detector array!')
            _log.warn(
                'X indices: [%s,%s]; Y indices: [%s,%s]; XY Size: [%s, %s]' %
                (x1, x2, y1, y2, data.shape[0], data.shape[1]))
            os.sys.exit()
        self.pca0 = data[y1:y2, x1:x2]

        # How many reference pixels on each border?
        w = self.reference_pixel_border_width  # Easier to work with
        lower = w - y1
        upper = w - (det_size - y2)
        left = w - x1
        right = w - (det_size - x2)
        ref_all = np.array([lower, upper, left, right])
        ref_all[ref_all < 0] = 0
        self.ref_all = ref_all
Example #4
0
	def __init__(self, naxis1=None, naxis2=None, naxis3=None, n_out=None,
				 dt=None, nroh=None, nfoh=None, pca0_file=None, verbose=False,
				 reverse_scan_direction=False, reference_pixel_border_width=None,
				 wind_mode='FULL', x0=0, y0=0, det_size=None):
		"""
		Simulate Teledyne HxRG+SIDECAR ASIC system noise.

		Parameters:
			naxis1      - X-dimension of the FITS cube
			naxis2      - Y-dimension of the FITS cube
			naxis3      - Z-dimension of the FITS cube
						  (number of up-the-ramp samples)
			n_out       - Number of detector outputs
			nfoh        - New frame overhead in rows. This allows for a short
						  wait at the end of a frame before starting the next
						  one.
			nroh        - New row overhead in pixels. This allows for a short
						  wait at the end of a row before starting the next one.
			dt          - Pixel dwell time in seconds
			pca0_file   - Name of a FITS file that contains PCA-zero
			verbose     - Enable this to provide status reporting
			wind_mode   - 'FULL', 'STRIPE', or 'WINDOW' (JML)
			x0/y0       - Pixel positions of subarray mode (JML)
			det_size    - Pixel dimension of full detector (square), used only
						  for WINDOW mode (JML)
			reference_pixel_border_width - Width of reference pixel border
										   around image area
			reverse_scan_direction - Enable this to reverse the fast scanner
									 readout directions. This
									 capability was added to support
									 Teledyne's programmable fast scan
									 readout directions. The default
									 setting =False corresponds to
									 what HxRG detectors default to
									 upon power up.
		"""

		# ======================================================================
		#
		# DEFAULT CLOCKING PARAMETERS
		#
		# The following parameters define the default HxRG clocking pattern. The
		# parameters that define the default noise model are defined in the
		# mknoise() method.
		#
		# ======================================================================

		# Subarray Mode? (JML)
		if wind_mode is None:
			wind_mode = 'FULL'
		if det_size is None:
			det_size = 2048
		wind_mode = wind_mode.upper()
		modes = ['FULL', 'STRIPE', 'WINDOW']
		if wind_mode not in modes:
			_log.warn('%s not a valid window readout mode! Returning...' % inst_params['wind_mode'])
			os.sys.exit()
		if wind_mode == 'WINDOW':
			n_out = 1
		if wind_mode == 'FULL':
			x0 = 0; y0 = 0
		if wind_mode == 'STRIPE':
			x0 = 0

		# Default clocking pattern is JWST NIRSpec
		self.naxis1    = 2048  if naxis1   is None else naxis1
		self.naxis2    = 2048  if naxis2   is None else naxis2
		self.naxis3    = 1     if naxis3   is None else naxis3
		self.n_out     = 4     if n_out    is None else n_out
		self.dt        = 1.e-5 if dt       is None else dt
		self.nroh      = 12    if nroh     is None else nroh
		self.nfoh      = 1     if nfoh     is None else nfoh
		self.reference_pixel_border_width = 4 if reference_pixel_border_width is None \
											  else reference_pixel_border_width
											  
		# Check that det_size is greater than self.naxis1 and self.naxis2 in WINDOW mode (JML)
		if wind_mode == 'WINDOW':
			if (self.naxis1 > det_size):
				_log.warn('NAXIS1 %s greater than det_size %s! Returning...' % (self.naxis1,det_size))
				os.sys.exit()
			if (self.naxis2 > det_size):
				_log.warn('NAXIS2 %s greater than det_size %s! Returning...' % (self.naxis1,det_size))
				os.sys.exit()
				

		# Initialize PCA-zero file and make sure that it exists and is a file
		self.pca0_file = os.getenv('NGHXRG_HOME')+'/nirspec_pca0.fits' if \
			pca0_file is None else pca0_file
		if os.path.isfile(self.pca0_file) is False:
			print('There was an error finding pca0_file! Check to be')
			print('sure that the NGHXRG_HOME shell environment')
			print('variable is set correctly and that the')
			print('$NGHXRG_HOME/ directory contains the desired PCA0')
			print('file. The default is nirspec_pca0.fits.')
			os.sys.exit()


		# ======================================================================

		# Configure Subarray (JML)
		self.wind_mode = wind_mode
		self.det_size  = det_size
		self.x0 = x0
		self.y0 = y0

		# Configure status reporting
		self.verbose = verbose

		# Configure readout direction
		self.reverse_scan_direction = reverse_scan_direction
	
		# Compute the number of pixels in the fast-scan direction per
		# output
		self.xsize = self.naxis1 // self.n_out
			
		# Compute the number of time steps per integration, per
		# output
		self.nstep = (self.xsize+self.nroh) * (self.naxis2+self.nfoh) * self.naxis3
		# Pad nsteps to a power of 2, which is much faster (JML)
		self.nstep2 = int(2**np.ceil(np.log2(self.nstep)))

		# For adding in ACN, it is handy to have masks of the even
		# and odd pixels on one output neglecting any gaps
		self.m_even = np.zeros((self.naxis3,self.naxis2,self.xsize))
		self.m_odd = np.zeros_like(self.m_even)
		for x in np.arange(0,self.xsize,2):
			self.m_even[:,:self.naxis2,x] = 1
			self.m_odd[:,:self.naxis2,x+1] = 1
		self.m_even = np.reshape(self.m_even, np.size(self.m_even))
		self.m_odd = np.reshape(self.m_odd, np.size(self.m_odd))

		# Also for adding in ACN, we need a mask that point to just
		# the real pixels in ordered vectors of just the even or odd
		# pixels
		self.m_short = np.zeros((self.naxis3, self.naxis2+self.nfoh, \
									  (self.xsize+self.nroh)//2))
		self.m_short[:,:self.naxis2,:self.xsize//2] = 1
		self.m_short = np.reshape(self.m_short, np.size(self.m_short))

		# Define frequency arrays
		self.f1 = np.fft.rfftfreq(self.nstep2) # Frequencies for nstep elements
		self.f2 = np.fft.rfftfreq(2*self.nstep2) # ... for 2*nstep elements

		# Define pinkening filters. F1 and p_filter1 are used to
		# generate ACN. F2 and p_filter2 are used to generate 1/f noise.
		self.alpha = -1 # Hard code for 1/f noise until proven otherwise
		self.p_filter1 = np.sqrt(self.f1**self.alpha)
		self.p_filter2 = np.sqrt(self.f2**self.alpha)
		self.p_filter1[0] = 0.
		self.p_filter2[0] = 0.


		# Initialize pca0. This includes scaling to the correct size,
		# zero offsetting, and renormalization. We use robust statistics
		# because pca0 is real data
		hdu = fits.open(self.pca0_file)
		nx_pca0 = hdu[0].header['naxis1']
		ny_pca0 = hdu[0].header['naxis2']

		# Do this slightly differently, taking into account the 
		# different types of readout modes (JML)
		#if (nx_pca0 != self.naxis1 or naxis2 != self.naxis2):
		#    zoom_factor = self.naxis1 / nx_pca0
		#    self.pca0 = zoom(hdu[0].data, zoom_factor, order=1, mode='wrap')
		#else:
		#    self.pca0 = hdu[0].data
		#self.pca0 -= np.median(self.pca0) # Zero offset
		#self.pca0 /= (1.4826*mad(self.pca0)) # Renormalize

		data = hdu[0].data        
		# Make sure the real PCA image is correctly scaled to size of fake data (JML)
		# Depends if we're FULL, STRIPE, or WINDOW
		if wind_mode == 'FULL':
			scale1 = self.naxis1 / nx_pca0
			scale2 = self.naxis2 / ny_pca0
			zoom_factor = np.max([scale1, scale2])
		if wind_mode == 'STRIPE':
			zoom_factor = self.naxis1 / nx_pca0
		if wind_mode == 'WINDOW':
			# Scale based on det_size
			scale1 = self.det_size / nx_pca0
			scale2 = self.det_size / ny_pca0
			zoom_factor = np.max([scale1, scale2])
		
		# Resize PCA0 data
		if zoom_factor != 1:
			data = zoom(data, zoom_factor, order=1, mode='wrap')

		data -= np.median(data) # Zero offset
		data /= (1.4826*mad(data)) # Renormalize
	
		# Select region of pca0 associated with window position
		if self.wind_mode == 'WINDOW':
			x1 = self.x0; y1 = self.y0
		elif self.wind_mode == 'STRIPE':
			x1 = 0; y1 = self.y0
		else:
			x1 = 0; y1 = 0
	
		# print(y1, self.naxis2) This appears to be a stub
		x2 = x1 + self.naxis1
		y2 = y1 + self.naxis2
		# Make sure x2 and y2 are valid
		if (x2 > data.shape[0] or y2 > data.shape[1]):
			_log.warn('Specified window size does not fit within detector array!')
			_log.warn('X indices: [%s,%s]; Y indices: [%s,%s]; XY Size: [%s, %s]' % 
						(x1,x2,y1,y2,data.shape[0],data.shape[1]))
			os.sys.exit()
		self.pca0 = data[y1:y2,x1:x2]
		
		# How many reference pixels on each border?
		w = self.reference_pixel_border_width # Easier to work with
		lower = w-y1; upper = w-(det_size-y2)
		left = w-x1; right = w-(det_size-x2)
		ref_all = np.array([lower,upper,left,right])
		ref_all[ref_all<0] = 0
		self.ref_all = ref_all