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
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
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
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