class Display(Tools, ObjectHeaderMethods): """ The base display tool for server and consoles. Usually implemented as the "display" tool. """ def __init__(self, tool_id="display", description=None): Tools.__init__(self, tool_id, description) # create the display Header object self.header = Header("Display") self.header.set_header("display", 2) # set default display server self.default_display = 0 # allow initialization of display on server in exposure try: azcam.db.tools_init["display"] = self except AttributeError: pass def initialize(self): """ Initialize display. """ if self.initialized: return if not self.enabled: azcam.AzcamWarning("Display is not enabled") return self.set_display(self.default_display) return def set_display(self, display_number): """ Set the current display by number. :param int display_number: Number for display to be used (0->N) :return None: """ return def display(self, image, extension_number=-1): """ Display a file on the image display. If specified for an MEF file, only extension_number is displayed. :param image: a filename or an image object :param int extension_number: FITS extension number of image, -1 for all :return None: """ raise NotImplementedError
class System(Tools, ObjectHeaderMethods): """ System tool class. Usually implemented as the "system" tool. """ def __init__(self, system_name, template_file=None): Tools.__init__(self, "system", system_name) self.header = Header(system_name, template_file) self.header.set_header("system", 0) return
class Telescope(Tools, ObjectHeaderMethods): """ The base telescope tool. Usually implemented as the "telescope" tool. """ def __init__(self, tool_id="telescope", description=None): Tools.__init__(self, tool_id, description) # focus position self.focus_position = 0 # create the temp control Header object self.header = Header("Telescope") self.header.set_header("telescope", 5) azcam.db.tools_init["telescope"] = self azcam.db.tools_reset["telescope"] = self # *************************************************************************** # exposure # *************************************************************************** def exposure_start(self): """ Optional call before exposure starts. """ return def exposure_finish(self): """ Optional call after exposure finishes. """ return
class TempCon(Tools, ObjectHeaderMethods): """ The base temperature control tool. Usually implemented as the "tempcon" tool. """ def __init__(self, tool_id: str = "tempcon", description: str = None): """ Creates the tool. Args: tool_id: Name of tool description: Description of tool, defaults to tool_id. """ Tools.__init__(self, tool_id, description) # control temperature number (which temp is regulated) self.control_temperature = -999.0 self.control_temperature_number = 0 # system temperatures self.temperature_ids = [0] # True to correct temperature offset and scale for each temperature # Scaling is **new = old * scale + offset** self.temperature_correction = 0 # temperature offset corrections for each temperature self.temperature_offsets = [0.0] # temperature scale corrections self.temperature_scales = [1.0] # calibration flags for each temperature self.temperature_cals = [3] # value returned when temperature read is bad self.bad_temp_value = -999.9 # create the temp control Header object self.header = Header("Temperature") self.header.set_header("tempcon", 4) # add keywords self.define_keywords() azcam.db.tools_init["tempcon"] = self azcam.db.tools_reset["tempcon"] = self return def reset(self) -> None: """ Reset tempcon tool. """ if not self.enabled: return if not self.initialized: self.initialize() self.set_control_temperature() return # *************************************************************************** # exposure # *************************************************************************** def exposure_start(self) -> None: """ Custom commands before exposure starts. """ return def exposure_finish(self) -> None: """ Custom commands after exposure finishes. """ return # *************************************************************************** # temperatures # *************************************************************************** def set_control_temperature(self, temperature: float = None, temperature_id: int = 0) -> None: """ Set the control temperature (set point). Args: temperature: control temperature in Celsius. If not specified, use saved value temperature_id: temperature sensor identifier """ if temperature is None: temperature = self.control_temperature else: self.control_temperature = temperature return def get_control_temperature(self, temperature_id: int = 0) -> float: """ Get the control temperature (set point). Args: temperature_id: temperature sensor identifier Returns: control_temperature: control temperature """ return self.control_temperature def get_temperatures(self) -> List[float]: """ Return all system temperatures. Returns: temperatures: list of temperatures read """ temps = [] for temperature_id in self.temperature_ids: p = self.get_temperature(temperature_id) temps.append(p) return temps def get_temperature(self, temperature_id: int = 0) -> float: """ Returns a system temperature. Args: temperature_id: temperature sensor identifier Returns: temperature: temperature read """ return self.bad_temp_value # *************************************************************************** # calibrations # *************************************************************************** def set_calibrations(self, cals: List[int]) -> None: """ Set calibration curves for temperature sensors. The values of these flags are from the list below which define the calibration curves to use for each sensor's temperature conversion. * 0 => DT670 diode * 1 => AD590 sensor * 2 => 1N4148 diode * 3 => 1N914 diode Args: cals: list of flags defining each temperature sensor type """ self.temperature_cals = [] for i, cal in enumerate(cals): self.temperature_cals.append(cal) return def set_corrections( self, temperature_offsets: List[float] = None, temperature_scales: List[float] = None, ) -> None: """ Set temperature correction values and enable correction. If both parameters are None then correction is disabled. Args: temperature_offsets: list of offsets for each temperature temperature_scales: list of scale factors for each temperature """ # set and save values if temperature_offsets is None and temperature_scales is None: self.temperature_correction = 0 else: self.temperature_correction = 1 if temperature_offsets is not None: self.temperature_offsets = temperature_offsets if temperature_scales is not None: self.temperature_scales = temperature_scales return def apply_corrections(self, temperature: float, temperature_id: int = 0) -> None: """ Correct the temperatures for offset and scale is temperature correction is enabled. Args: temperature: temperatures to be corrected temperature_id: temperature ID number Returns: corrected_temperature: temperature after correction has been appied """ if self.temperature_correction: return (temperature * self.temperature_scales[temperature_id] + self.temperature_offsets[temperature_id]) else: return temperature # *************************************************************************** # keywords # *************************************************************************** def define_keywords(self): """ Defines and resets instrument keywords. """ return def get_keyword(self, keyword: str) -> List: """ Read a temperature keyword value and returns it as [value, comment, type string] Args: keyword: name of keyword Returns: list of [keyword, comment, type] """ reply = self.get_temperatures() if keyword == "CAMTEMP": temp = reply[0] elif keyword == "DEWTEMP": temp = reply[1] elif keyword in self.get_keywords(): value = self.header.values[keyword] temp = value else: raise azcam.AzcamError(f"invalid keyword: {keyword}") # store temperature values in header if keyword == "CAMTEMP": temp = float(f"{temp:.03f}") self.header.set_keyword("CAMTEMP", temp, "Camera temperature in C", "float") self.header.set_keyword("CCDTEMP1", temp, "Camera temperature in C", "float") elif keyword == "DEWTEMP": temp = float(f"{temp:.03f}") self.header.set_keyword("DEWTEMP", temp, "Dewar temperature in C", "float") self.header.set_keyword("CCDTEMP2", temp, "Dewar temperature in C", "float") t = self.header.typestrings[keyword] return [temp, self.header.comments[keyword], t]
class FocalPlane(ObjectHeaderMethods): """ The FocalPlane class describes a focalplane layout. Used by the image tool. """ def __init__(self): # header self.header = Header("Focalplane") # World Coordinate System self.wcs = WCS(self) # only needed for server side self.header.set_header("focalplane") # fixed values # for multiple CCD detectors: # 0 - each CCD has its own physical coordinates # 1 - combine physical coordinates (physical coordinates # are the same as detector coordinates) self.split_physical_coords = 0 # number of detectors along X axis self.numdet_x = 1 # number of detectors along Y axis self.numdet_y = 1 # amplifier readout orientation self.amp_config = "0" # ampliefier readout orientation - new as an array self.amp_cfg = [0] # detector number for each detector self.det_number = [1] # extension number for each amplifier self.ext_number = [1] # extension name for each amplifier self.ext_name = ["1"] # extension number order for each amplifier when creating binary image self.jpg_ext = [1] # detector position in x direction in pixels self.detpos_x = [1] # detector position in y direction in pixels self.detpos_y = [1] # amplifier's positions along X axis self.extpos_x = [1] # amplifier's positions along Y axis self.extpos_y = [1] # amplifier's positions in pixels along X axis self.amppix1 = [1] # amplifier's positions in pixels along Y axis self.amppix2 = [1] # gap between amplifiers in X. value different than 0 # means that there is a gap on the left side of the amplifier. # gapx is the sum of all gaps on the left side of the amplifier. self.gapx = [0] # gap between amplifiers in Y. value different than 0 means # that there is a gap below of the amplifier. # gapy is the sum of all gaps below the amplifier. self.gapy = [0] # number of amplifiers along X axis self.numamps_x = 1 # number of amplifiers along Y axis self.numamps_y = 1 # reference (zeropoint) position X (in pixels) self.refpix1 = 1 # reference (zeropoint) position Y (in pixels) self.refpix2 = 1 # unbinned number of visible pixels per amplifier along X axis self.ampvispix_x = 1 # unbinned number of visible pixels per amplifier along Y axis self.ampvispix_y = 1 # calculated values # number of pixels in image self.numpix_image = 0 # number of bytes per image self.numbytes_image = 0 # number of columns per image self.numcols_image = 0 # number of rows per image self.numrows_image = 0 # number of total amps per image self.numamps_image = 1 # number of detectors self.num_detectors = 1 # number of columns per amplifier self.numcols_amp = 1 # number of row per amplifier self.numrows_amp = 1 # number of serial amps per detector self.num_ser_amps_det = 1 # number of parallel amps per detector self.num_par_amps_det = 1 # number of total amps per detector self.num_amps_det = 1 # first column self.first_col = 1 # first row self.first_row = 1 # last column self.last_col = 1 # last row self.last_row = 1 # column binning self.col_bin = 1 # row binning self.row_bin = 1 # number of visible columns per amplifier self.numviscols_amp = 1 # number of visible rows per amplifier self.numvisrows_amp = 1 # number of visible columns per amplifier self.numviscols_image = 1 # number of visible rows per amplifier self.numvisrows_image = 1 # number of colums in overscan self.numcols_overscan = 0 # number of rows in overscan self.numrows_overscan = 0 # number of colums in underscan self.numcols_underscan = 0 # number of rows in underscan self.numrows_underscan = 0 # underscan skips self.xunderscan = 0 self.yunderscan = 0 # overscan skips self.xoverscan = 0 self.yoverscan = 0 # bias location constants self.BL_COLUNDER = 1 self.BL_COLOVER = 2 self.BL_ROWUNDER = 3 self.BL_ROWOVER = 4 self.bias_location = self.BL_COLOVER # number of pixels per amplifier self.numpix_amp = 1 # number of pixels per detector self.numpix_det = 1 # shifting self.ns_total = 0 self.ns_predark = 0 self.ns_underscan = 0 self.ns_overscan = 0 self.np_total = 0 self.np_predark = 0 self.np_underscan = 0 self.np_overscan = 0 self.np_frametransfer = 0 # set shifting parameters self.coltotal = 0 self.colusct = 0 self.coluscw = 0 self.coluscm = 0 self.coloscw = 0 self.coloscm = 0 self.rowtotal = 0 self.rowusct = 0 self.rowuscw = 0 self.rowuscm = 0 self.rowoscw = 0 self.rowoscm = 0 self.framet = 0 def update_header_keywords(self): """ Update focal plane keywords in header """ self.header.set_keyword("ITL-HEAD", "OK", "ITL Header flag", "str") self.header.set_keyword("NUM-DETX", self.numdet_x, "Number of detectors in X", "int") self.header.set_keyword("NUM-DETY", self.numdet_y, "Number of detectors in Y", "int") self.header.set_keyword("NUM-AMPX", self.numamps_x, "Number of amplifiers in X", "int") self.header.set_keyword("NUM-AMPY", self.numamps_y, "Number of amplifiers in Y", "int") if type(self.refpix1) == list: r1 = self.refpix1[0] r2 = self.refpix2[0] else: r1 = self.refpix1 r2 = self.refpix2 self.header.set_keyword("REF-PIX1", r1, "Reference pixel 1", "int") self.header.set_keyword("REF-PIX2", r2, "Reference pixel 2", "int") return def update_ext_keywords(self): """ Update focal plane keywords for single extension """ self.header.set_keyword("AMP-CFG", self.amp_cfg[0], "Amplifier configuration") self.header.set_keyword("DET-NUM", self.det_number[0], "Detector number") self.header.set_keyword("EXT-NUM", self.ext_number[0], "extension number") self.header.set_keyword("JPG-EXT", self.jpg_ext[0], "Image section") self.header.set_keyword("DET-POSX", self.detpos_x[0], "Detector position in X") self.header.set_keyword("DET-POSY", self.detpos_y[0], "Detector position in Y") self.header.set_keyword("Ext-PosX", self.extpos_x[0], "Amplifier position in X") self.header.set_keyword("Ext-PosY", self.extpos_y[0], "Amplifier position in Y") self.header.set_keyword("AMP-PIX1", self.amppix1[0], "Amplifier pixel position in X") self.header.set_keyword("AMP-PIX2", self.amppix2[0], "Amplifier pixel position in Y") return def update_header(self): """ Update headers, reading current data. """ self.update_header_keywords() return def set_format( self, ns_total=-1, ns_predark=-1, ns_underscan=-1, ns_overscan=-1, np_total=-1, np_predark=-1, np_underscan=-1, np_overscan=-1, np_frametransfer=-1, ): """ Set the detector format. ns_total is the number of visible columns. ns_predark is the number of physical dark underscan columns. ns_underscan is the desired number of desired dark underscan columns. ns_overscan is the number of dark overscan columns. np_total is the number of visible rows. np_predark is the number of physical dark underscan rows. np_underscan is the number of desired dark underscan rows. np_overscan is the number of desired dark overscan rows. np_frametransfer is the rows to frame transfer shift. """ ns_total = int(ns_total) ns_predark = int(ns_predark) ns_underscan = int(ns_underscan) ns_overscan = int(ns_overscan) np_total = int(np_total) np_predark = int(np_predark) np_underscan = int(np_underscan) np_overscan = int(np_overscan) np_frametransfer = int(np_frametransfer) # defaults if ns_total == -1: ns_total = self.ns_total if ns_predark == -1: ns_predark = self.ns_predark if ns_underscan == -1: ns_underscan = self.ns_underscan if ns_overscan == -1: ns_overscan = self.ns_overscan if np_total == -1: np_total = self.np_total if np_predark == -1: np_predark = self.np_predark if np_underscan == -1: np_underscan = self.np_underscan if np_overscan == -1: np_overscan = self.np_overscan if np_frametransfer == -1: np_frametransfer = self.np_frametransfer # sets the format parameters for a detector self.ns_total = int(ns_total) self.ns_predark = int(ns_predark) self.ns_underscan = int(ns_underscan) self.ns_overscan = int(ns_overscan) self.np_total = int(np_total) self.np_predark = int(np_predark) self.np_underscan = int(np_underscan) self.np_overscan = int(np_overscan) self.np_frametransfer = int(np_frametransfer) # set shifting parameters self.coltotal = int(ns_total) self.colusct = int(ns_predark) self.coluscw = int(ns_underscan) self.coluscm = 0 self.coloscw = int(ns_overscan) self.coloscm = 0 self.rowtotal = int(np_total) self.rowusct = int(np_predark) self.rowuscw = int(np_underscan) self.rowuscm = 0 self.rowoscw = int(np_overscan) self.rowoscm = 0 self.framet = int(np_frametransfer) return def get_format(self): """ Return a list of current detector format parameters """ return [ self.ns_total, self.ns_predark, self.ns_underscan, self.ns_overscan, self.np_total, self.np_predark, self.np_underscan, self.np_overscan, self.np_frametransfer, ] def set_focalplane(self, numdet_x=-1, numdet_y=-1, numamps_x=-1, numamps_y=-1, amp_config=""): """ Sets focal plane configuration. Use after set_format() and before set_roi(). This command replaces SetConfiguration. Default focalplane values are set here. numdet_x defines number of detectors in Column direction. numdet_y defines number of detectors in Row direction. numamps_x defines number of amplifiers in Column direction. numamps_y defines number of amplifiers in Row direction. amp_config defines each amplifier's orientation (ex: '1223'). 0 - normal 1 - flip x 2 - flip y 3 - flip x and y """ numdet_x = int(numdet_x) numdet_y = int(numdet_y) numamps_x = int(numamps_x) numamps_y = int(numamps_y) if numdet_x == -1: numdet_x = self.numdet_x if numdet_y == -1: numdet_y = self.numdet_y if numamps_x == -1: numamps_x = self.numamps_x if numamps_y == -1: numamps_y = self.numamps_y if amp_config == "": amp_config = self.amp_config self.numdet_x = numdet_x self.numdet_y = numdet_y self.numamps_x = numamps_x self.numamps_y = numamps_y # special case for amp_config if type(amp_config) == str: self.amp_config = amp_config self.amp_cfg = [] for x in amp_config: self.amp_cfg.append(ord(x) - 48) # convert char to integer elif type(amp_config) == list: self.amp_cfg = amp_config self.amp_config = "" for x in amp_config: self.amp_config += chr(x + 48) # set the keywords in the main header self.header.set_keyword("N-DET-X", self.numdet_x, "Number of detectors in X", "int") self.header.set_keyword("N-DET-Y", self.numdet_y, "Number of detectors in Y", "int") self.header.set_keyword("N-AMPS-X", self.numamps_x, "Number of amplifiers in X", "int") self.header.set_keyword("N-AMPS-Y", self.numamps_y, "Number of amplifiers in Y", "int") # set number of detectors self.num_detectors = self.numdet_x * self.numdet_y # set amps per detector self.num_ser_amps_det = int(self.numamps_x / self.numdet_x) self.num_par_amps_det = int(self.numamps_y / self.numdet_y) self.num_amps_det = self.num_ser_amps_det * self.num_par_amps_det # set amps in focal plane self.numamps_image = self.num_detectors * self.num_amps_det # set unbinned number of visible pixels per amplifier along X and Y axis self.ampvispix_x = int(self.ns_total / self.numamps_x) self.ampvispix_y = int(self.np_total / self.numamps_y) # set default values self.set_default_values() # initialize WCS so array size is correct, values may be manually set later self.wcs.initialize() return def get_focalplane(self): """ Returns the focal plane configuration. """ return [ self.numdet_x, self.numdet_y, self.numamps_x, self.numamps_y, self.amp_config, ] def set_roi( self, first_col=-1, last_col=-1, first_row=-1, last_row=-1, col_bin=-1, row_bin=-1, roi_num=0, ): """ Sets the ROI values for a specified ROI. Currently only one ROI (0) is supported. These values are for the entire focal plane, not just one detector. """ # set current values first_col = int(first_col) last_col = int(last_col) first_row = int(first_row) last_row = int(last_row) col_bin = int(col_bin) row_bin = int(row_bin) roi_num = int(roi_num) if first_col == -1: first_col = self.first_col if last_col == -1: last_col = self.last_col if first_row == -1: first_row = self.first_row if last_row == -1: last_row = self.last_row if col_bin == -1: col_bin = self.col_bin if row_bin == -1: row_bin = self.row_bin self.first_col = int(first_col) self.last_col = int(last_col) self.first_row = int(first_row) self.last_row = int(last_row) self.col_bin = int(col_bin) self.row_bin = int(row_bin) self.roi_num = int(roi_num) # bad fix for ROI now entire focalplane - change later! if self.numdet_x > 1: lc = last_col / self.numdet_x lr = last_row / self.numdet_y else: lc = last_col lr = last_row fc = first_col fr = first_row # calculate variables for shifting a single AMPLIFIER self.xunderscan = int(min(self.colusct / self.col_bin, self.coluscw)) self.xskip = int( min(self.colusct - self.xunderscan * self.col_bin, self.coluscm)) self.xpreskip = self.colusct - self.xskip - self.xunderscan * self.col_bin self.xskip += fc - 1 self.xdata = (lc - (fc - 1)) / self.col_bin self.xdata = max(0, self.xdata) self.xdata = int(self.xdata / self.num_ser_amps_det) self.xpostskip = self.coltotal / self.numamps_x - ( (fc - 1) + self.xdata * self.col_bin) self.xpostskip = int(max(0, self.xpostskip)) self.xpostskip += self.coloscm self.xoverscan = self.coloscw self.numcols_amp = self.xunderscan + self.xdata + self.xoverscan self.numcols_overscan = self.xoverscan self.numviscols_amp = self.xdata # self.numviscols_image=self.numviscols_amp*self.num_ser_amps_det*self.numamps_x # bug? 21may15 self.numviscols_image = self.numviscols_amp * self.num_ser_amps_det * self.numdet_x self.yunderscan = int(min(self.rowusct / self.row_bin, self.rowuscw)) self.yskip = int( min(self.rowusct - self.yunderscan * self.row_bin, self.rowuscm)) self.ypreskip = self.rowusct - self.yskip - self.yunderscan * self.row_bin self.yskip += self.first_row - 1 self.ydata = (lr - (fr - 1)) / self.row_bin self.ydata = max(0, self.ydata) self.ydata = int(self.ydata / self.num_par_amps_det) self.ypostskip = self.rowtotal / self.numamps_y - ( (fr - 1) + self.ydata * self.row_bin) self.ypostskip = int(max(0, self.ypostskip)) self.ypostskip += self.rowoscm self.yoverscan = self.rowoscw self.numrows_amp = self.yunderscan + self.ydata + self.yoverscan self.numrows_overscan = self.yoverscan self.numvisrows_amp = self.ydata # self.numvisrows_image=self.numvisrows_amp*self.num_par_amps_det*self.numamps_y self.numvisrows_image = self.numvisrows_amp * self.num_par_amps_det * self.numdet_y self.numpix_amp = self.numcols_amp * self.numrows_amp # totals for a single DETECTOR self.numcols_det = self.numcols_amp * self.num_ser_amps_det self.numrows_det = self.numrows_amp * self.num_par_amps_det self.numpix_det = self.numcols_det * self.numrows_det # totals for the IMAGE self.numpix_image = self.numpix_det * self.num_detectors self.numcols_image = self.numcols_det * self.numdet_x self.numrows_image = self.numrows_det * self.numdet_y self.numbytes_image = self.numpix_image * 2 # number of UNBINNED pixels to flush during clear self.xflush = ( self.xpreskip + self.xskip + self.xpostskip + (self.xunderscan + self.xdata + self.xoverscan) * self.col_bin) self.yflush = ( self.ypreskip + self.yskip + self.ypostskip + (self.yunderscan + self.ydata + self.yoverscan) * self.row_bin) self.set_amp_positions() return def get_roi(self, roi_num=0): """ Returns a list of the ROI parameters for the roi_num specified. Currently only one ROI (0) is supported. Returned list format is (first_col,last_col,first_row,last_row,col_bin,row_bin). """ return [ self.first_col, self.last_col, self.first_row, self.last_row, self.col_bin, self.row_bin, ] def roi_reset(self): """ Resets detector ROI values to full frame, current binning. """ # get focalplane format reply = self.get_format() # update controller shifting parameters self.set_roi(1, reply[0], 1, reply[4], -1, -1) return reply def set_amp_positions(self): """ Calculates amplifiers positions including gaps between amplifiers and CCDs New: Zareba 23Mar2012 """ self.amppix1 = numpy.empty(shape=[self.numamps_image], dtype="<u2") self.amppix2 = numpy.empty(shape=[self.numamps_image], dtype="<u2") indx = 0 for flip in self.amp_cfg: if flip == 0: flip_x = 0 flip_y = 0 elif flip == 1: flip_x = 1 flip_y = 0 elif flip == 2: flip_x = 0 flip_y = 1 else: flip_x = 1 flip_y = 1 size_x = self.numviscols_amp * self.col_bin size_y = self.numvisrows_amp * self.row_bin self.amppix1[indx] = ((self.extpos_x[indx] - 1) * size_x + flip_x * size_x + self.gapx[indx] + 1 - flip_x) self.amppix2[indx] = ((self.extpos_y[indx] - 1) * size_y + flip_y * size_y + self.gapy[indx] + 1 - flip_y) indx += 1 return def set_default_values(self): """ Sets default values for focalplane variables """ # set default values for the extension names, 'im1' -> 'imN' # use _WCS.py file to set nonstandard values (focalplane.ext_name = [...]) self.ext_name = numpy.empty(shape=[self.numamps_image], dtype="S16") for ext in range(self.numamps_image): ext_name = "im%d" % (ext + 1) self.ext_name[ext] = ext_name self.ext_name = [y.decode() for y in self.ext_name] # new # set default values for the etension numbers, 1 -> N # use _WCS.py file to set nonstandard values ( focalplane.ext_number = [...]) self.ext_number = numpy.empty(shape=[self.numamps_image], dtype="<u2") for ext in range(self.numamps_image): self.ext_number[ext] = ext + 1 # set default values for the jpg extension values, 1 -> N # use _WCS.py file to set nonstandard values (focalplane.jpg_ext = [...]) self.jpg_ext = numpy.empty(shape=[self.numamps_image], dtype="<u2") for ext in range(self.numamps_image): self.jpg_ext[ext] = ext + 1 # set default values for the gaps, 0 self.gapx = numpy.empty(shape=[self.numamps_image], dtype="<u2") for ext in range(self.numamps_image): self.gapx[ext] = 0 self.gapy = numpy.empty(shape=[self.numamps_image], dtype="<u2") for ext in range(self.numamps_image): self.gapy[ext] = 0 # set extension positions self.extpos_x = numpy.empty(shape=[self.numamps_image], dtype="<u2") for ext in range(self.numamps_image): self.extpos_x[ext] = 1 self.extpos_y = numpy.empty(shape=[self.numamps_image], dtype="<u2") for ext in range(self.numamps_image): self.extpos_y[ext] = 1 self.det_number = numpy.empty(shape=[self.numamps_image], dtype="<u2") numpy.ndarray.fill(self.det_number, 1) self.detpos_x = numpy.empty(shape=[self.numamps_image], dtype="<u2") numpy.ndarray.fill(self.detpos_x, 1) self.detpos_y = numpy.empty(shape=[self.numamps_image], dtype="<u2") numpy.ndarray.fill(self.detpos_y, 1) return def set_extension_name(self, ext_name): for i, ext in enumerate(ext_name): self.ext_name[i] = ext return def set_extension_extnum(self, ext_number): for i, ext in enumerate(ext_number): self.ext_number[i] = ext return def set_ref_pixel(self, xy): """ Set the reference pixel. xy is [X,Y] in pixels. """ self.refpix1 = xy[0] self.refpix2 = xy[1] return def set_extension_position(self, xy): """ Set the extension position of each amplifier. xy is [[X,Y]] in index numbers, starting at [1,1]. """ for i, xy_ in enumerate(xy): self.extpos_x[i] = xy_[0] self.extpos_y[i] = xy_[1] return def set_jpg_order(self, indices): """ Set JPG image positions. """ self.jpg_ext = indices return
class Exposure(Tools, Filename, ObjectHeaderMethods): """ The base exposure tool. Usually implemented as the "exposure" tool. Only required attributes and stub methods are defined here. Additional methods and attributes are added as needed in exposure-specific classes which should inherit this class. """ def __init__(self, tool_id="exposure", description=None): Tools.__init__(self, tool_id, description) Filename.__init__(self) self.obstime = ObsTime() self.image = Image() # exposure flags, may be used anywhere self.exposureflags = { "NONE": 0, "EXPOSING": 1, "ABORT": 2, "PAUSE": 3, "RESUME": 4, "READ": 5, "PAUSED": 6, "READOUT": 7, "SETUP": 8, "WRITING": 9, "GUIDEERROR": 10, "ERROR": 11, } azcam.db.exposureflags = self.exposureflags self.exposureflags_rev = {v: k for k, v in self.exposureflags.items()} # exposure flag defining state of current exposure self.exposure_flag = self.exposureflags["NONE"] # current image type, 'zero', 'object', 'dark', 'flat', 'ramp', etc self.image_type = "zero" # default imagetypes self.image_types = ["zero", "object", "flat", "dark"] # dictionary of shutter states for imagetypes {imagetype:ShutterState} self.shutter_dict = { "zero": 0, "object": 1, "flat": 1, "dark": 0, "ramp": 1, } # True to flush detector before exposures self.flush_array = 1 # True to display an image after readout self.display_image = 1 # True to send image to remote image server after readout self.send_image = 0 self.message = "" # exposure status message self.guide_status = 0 self.guide_image_copy = 0 # TdiMode flag, 0=not in TDI mode, 1=TDI mode self.tdi_mode = 0 # TdiDelay mode self.tdi_delay = 5 # ParDelay mode self.par_delay = 5 # guide mode self.guide_mode = 0 # True when exposure type is a comparision, to turn on comp lamps self.comp_exposure = 0 # True when in a comparision exposure sequence so lamps stay on self.comp_sequence = 0 # True when exposure has been aborted self.aborted = 0 # True when exposure is completed, then toggled off self.completed = 0 # requested exposure time in seconds self.exposure_time = 1.0 # remaining exposure time in seconds for an exposure in progress self.exposure_time_remaining = 0.0 # actual elapsed exposure time in seconds of last/current exposure self.exposure_time_actual = 0.0 # exposure time saved for each exposure, used for zeros self.exposure_time_saved = 0.0 # total time in seconds an exposure was paused self.paused_time = 0.0 # starting clock paused time of exposure self.paused_time_start = 0.0 # actual elapsed dark time of last/current exposure self.dark_time = 0.0 # starting clock dark time of exposure self.dark_time_start = 0.0 # True when in an exposure sequence self.is_exposure_sequence = 0 # current exposure sequence number self.exposure_sequence_number = 1 # total number of exposures in sequence self.exposure_sequence_total = 1 # delay between sequence exposures in seconds self.exposure_sequence_delay = 0.0 # sequence flush flag: -1=> use FlushArray, 0==> flush all, 1=> flush only first exposure, 2=> no flush self.exposure_sequence_flush = 0 # remaining number of pixels to read for an exposure in progress self.pixels_remaining = 0 # True to update headers in a thread to save time self.update_headers_in_background = 0 self.updating_header = 0 # True to save image file after exposure self.save_file = 1 # file types self.filetypes = {"FITS": 0, "MEF": 1, "BIN": 2, "ASM": 6} self.filetype = self.filetypes["MEF"] # Exposure title self.title = "" # True to make image title the same as image type self.auto_title = 0 # deinterlace mode; 1 = generic mode; x = ODI mode self.deinterlace_mode = 1 # temporary image files self.temp_image_file = "" # filename of current image self.last_filename = "" # write data asynchronously self.write_async = 0 # create the exposure header self.header = Header("Exposure") # flag indicating ROI has been changed self.new_roi = 0 self.header.set_header("exposure", 1) # data order self.data_order = [] self.imageheaderfile = "" self.pgress = 0 # debug def initialize(self): """ Initialize exposure. """ # call initialize() method on other tools for tool in azcam.db.tools_init: azcam.db.tools[tool].initialize() self.initialized = 1 return def reset(self): """ Reset exposure tool. """ # initialize only once if not self.initialized: self.initialize() # set temporary filenames self.set_temp_files() # setup for exposures self.is_exposure_sequence = 0 self.exposure_sequence_number = 1 self.set_auto_title() azcam.db.abortflag = 0 self.save_file = 1 self.exposure_flag = self.exposureflags["NONE"] # call reset() method on other tools for tool in azcam.db.tools_reset: azcam.db.tools[tool].reset() return # ********************************************************************************************** # Exposure control # ********************************************************************************************** def start(self): """ Allow custom operations at start of exposure. """ try: azcam.db.tools["instrument"].exposure_start() except KeyError: pass try: azcam.db.tools["telescope"].exposure_start() except KeyError: pass return def finish(self): """ Allow custom operations at end of exposure. """ try: azcam.db.tools["instrument"].exposure_finish() except KeyError: pass try: azcam.db.tools["telescope"].exposure_finish() except KeyError: pass return def finished(self): if self.completed: return 1 else: return 0 def get_exposureflag(self): return [self.exposure_flag, self.exposureflags_rev[self.exposure_flag]] def test(self, exposure_time=0.0, shutter=0): """ Make a test exposure. exposure_time is the exposure time in seconds shutter is 0 for closed and 1 for open title is the image title. """ old_testimage = self.test_image old_imagetype = self.image_type old_exposuretime = self.exposure_time self.test_image = 1 if shutter: shutter_state = "object" else: shutter_state = "dark" self.expose(exposure_time, shutter_state, "test image") self.test_image = old_testimage self.image_type = old_imagetype self.exposure_time = old_exposuretime return def expose(self, exposure_time=-1, imagetype="", title=""): """ Make a complete exposure. exposure_time is the exposure time in seconds imagetype is the type of exposure ('zero', 'object', 'flat', ...) title is the image title. """ # allow custom operations self.start() azcam.log("Exposure started") # if last exposure was aborted, warn before clearing flag if self.exposure_flag == self.exposureflags["ABORT"]: azcam.AzcamWarning("Previous exposure was aborted") # begin if self.exposure_flag != self.exposureflags["ABORT"]: self.begin(exposure_time, imagetype, title) # integrate if self.exposure_flag != self.exposureflags["ABORT"]: self.integrate() # readout if (self.exposure_flag != self.exposureflags["ABORT"] and self.exposure_flag == self.exposureflags["READ"]): try: self.readout() except azcam.AzcamError: pass # end if self.exposure_flag != self.exposureflags["ABORT"]: self.end() self.exposure_flag = self.exposureflags["NONE"] self.completed = 1 azcam.log("Exposure finished") # allow custom operations self.finish() return def expose1(self, exposure_time: float = -1, image_type: str = "", image_title: str = ""): """ Make a complete exposure with immediate return to caller. :param exposure_time: the exposure time in seconds :param image_type: type of exposure ('zero', 'object', 'flat', ...) :param image_title: image title, usually surrounded by double quotes """ arglist = [exposure_time, image_type, image_title] thread = threading.Thread(target=self.expose, name="expose1", args=arglist) thread.start() return def guide(self, number_exposures=1): """ Make a complete guider exposure sequence. NumberExposures is the number of exposures to make, -1 loop forever """ AbortFlag = 0 number_exposures = int(number_exposures) # system must be reset once before an exposure can be made if not azcam.db.tools["controller"].is_reset: azcam.db.tools["controller"].reset() # parameters for faster operation flusharray = self.flush_array azcam.log("Guide started") # this loop continues even for errors since data is sent to a seperate client receiving images LoopCount = 0 while True: if 0: self.begin(exposure_time=-1, imagetype="object", title="guide image") # integrate self.integrate() # readout if self.exposure_flag == self.exposureflags["READ"]: try: self.readout() self.guide_status = 1 # image read OK self.guide_image_copy = self.image except Exception: self.guide_status = 2 # image not read OK, but don't stop guide loop self.image = self.guide_image_copy # image writing self.end() self.exposure_flag = self.exposureflags["NONE"] else: self.expose(-1, "object", "guide image") AbortFlag = azcam.db.abortflag if AbortFlag: break if number_exposures == -1: continue else: LoopCount += 1 if LoopCount >= number_exposures: break # finish self.guide_status = 0 self.flush_array = flusharray if AbortFlag: azcam.AzcamWarning("Guide aborted") else: azcam.log("Guide finished") return def guide1(self, number_exposures=1): """ Make a complete guider exposure with an immediate return. NumberExposures is the number of exposures to make, -1 loop forever """ arglist = [number_exposures] thread = threading.Thread(target=self.guide, name="guide1", args=arglist) thread.start() return def begin(self, exposure_time=-1, imagetype="", title=""): """ Initiates the first part of an exposure, through image flushing. exposure_time is in seconds. imagetype is one of zero, object, flat, dark, ramp, ... """ # system must be reset once before an exposure can be made x = self.is_exposure_sequence # save this flag which is lost by reset if not azcam.db.tools["controller"].is_reset: self.reset() self.is_exposure_sequence = x # set exposure flag self.exposure_flag = self.exposureflags["SETUP"] # reset flags as new data coming self.image.valid = 0 self.image.written = 0 self.image.toggle = 0 self.image.assembled = 0 # clear the image header self.image.header.delete_all_items() self.image.header.delete_all_keywords() # update image size try: self.set_roi() # bug? except Exception: pass if self.new_roi: self.image.data = numpy.empty( shape=[ self.image.focalplane.numamps_image, self.image.focalplane.numpix_amp, ], dtype="<u2", ) self.new_roi = 0 # imagetype if imagetype != "": self.image_type = imagetype self.image.image_type = self.image_type imagetype = self.image_type.lower() # exposure times exposure_time = float(exposure_time) self.exposure_time_saved = self.exposure_time if exposure_time >= 0: self.exposure_time = float(exposure_time) if imagetype == "zero": self.exposure_time = 0.0 self.paused_time = 0.0 # reset paused time self.paused_time_start = 0.0 # reset paused start time self.exposure_time_remaining = self.exposure_time # update title and set OBJECT keyword using title self.set_image_title(title) # send exposure time to controller self.set_exposuretime(self.exposure_time) # set total number of pixels to readout self.pixels_remaining = self.image.focalplane.numpix_image # set CompExposure flag for any undefined image types (comp names) if imagetype not in self.image_types: self.comp_exposure = 1 else: self.comp_exposure = 0 if not self.guide_mode: # for speed try: shutterstate = self.shutter_dict[imagetype] except KeyError: shutterstate = 1 # other types are comps, so open shutter azcam.db.tools["controller"].set_shutter_state(shutterstate) self.delete_keyword("COMPLAMP") # set comp lamps, turn on, set keyword if self.comp_exposure and azcam.db.tools["instrument"].enabled: if self.comp_sequence: # lamps already on pass else: azcam.db.tools["instrument"].set_comps(imagetype) if not azcam.db.tools["instrument"].shutter_strobe: azcam.db.tools["instrument"].comps_on() lampnames = " ".join(azcam.db.tools["instrument"].get_comps()) self.set_keyword("COMPLAMP", lampnames, "Comp lamp names", "str") self.set_keyword("IMAGETYP", "comp", "Image type", "str") azcam.db.tools["instrument"].comps_delay() # delay for lamp warmup else: if not self.guide_mode: try: if (azcam.db.tools["instrument"] is not None ) and azcam.db.tools["instrument"].enabled: azcam.db.tools["instrument"].set_comps() # reset except KeyError: pass self.set_keyword("IMAGETYP", imagetype, "Image type", "str") # update all headers with current data if self.update_headers_in_background: headerthread = threading.Thread(target=self.update_headers, name="updateheaders", args=[]) headerthread.start() else: self.update_headers() # flush detector if self.flush_array: self.flush() else: azcam.db.tools["controller"].stop_idle() # record current time and date in header self.record_current_times() return def integrate(self): """ Integration. """ return def readout(self): """ Exposure readout. """ return def end(self): """ Completes an exposure by writing file and displaying image. """ return def start_readout(self): """ Start immediate readout of an exposing image. Returns immediately, not waiting for readout to finish. Really sets a flag which is read in expose(). """ if self.exposure_flag != self.exposureflags["NONE"]: self.exposure_flag = self.exposureflags["READ"] return def flush(self, Cycles=1): """ Flush/clear detector. Cycles is the number of times to flush the detector. """ azcam.log("Flushing") return azcam.db.tools["controller"].flush(int(Cycles)) def sequence(self, number_exposures=1, flush_array_flag=-1, delay=-1): """ Take an exposure sequence. Uses pre-set exposure time, image type and image title. NumberExposures is the number of exposures to make. FlushArrayFlag defines detector flushing: -1 => current value defined by exposure.exposure_sequence_flush [default] 0 => flush for each exposure 1 => flush after first exposure only 2 => no flush Delay => delay between exposures in seconds -1 => no change """ AbortFlag = 0 self.is_exposure_sequence = 1 self.exposure_sequence_number = 1 self.exposure_sequence_total = number_exposures number_exposures = int(number_exposures) flush_array_flag = int(flush_array_flag) if delay != -1 and delay != "-1": self.exposure_sequence_delay = float(delay) # set flushing currentflush = self.flush_array if flush_array_flag == -1: flush_array_flag = self.exposure_sequence_flush if flush_array_flag == 0 or flush_array_flag == 1: FlushArray = True else: FlushArray = False self.flush_array = FlushArray self.comp_sequence = (self.check_comparison_imagetype() and azcam.db.tools["instrument"].enabled) if self.comp_sequence: azcam.log("Starting comparison sequence") azcam.db.tools["instrument"].set_comps(self.image_type) if azcam.db.tools["instrument"].shutter_strobe: pass # these instruments use shutter to turn on comps else: azcam.db.tools["instrument"].comps_on() azcam.db.tools["instrument"].comps_delay( ) # delay for lamp warmup if needed for i in range(number_exposures): if i > 0: time.sleep(self.exposure_sequence_delay) if i > 0 and flush_array_flag == 1: self.flush_array = False self.expose(self.exposure_time, self.image_type, self.title) # check and clear user abort AbortFlag = azcam.db.abortflag if AbortFlag: break # sequence may have been stopped if not self.is_exposure_sequence: break self.exposure_sequence_number += 1 # turn off comps if self.comp_sequence: azcam.db.tools["instrument"].comps_off() self.comp_sequence = 0 self.flush_array = currentflush self.is_exposure_sequence = 0 self.exposure_sequence_number = 1 if AbortFlag: self.aborted = 1 return def sequence1(self, number_exposures=1, flush_array_flag=-1, delay=-1): """ Take an exposure sequence. Uses pre-set image type and image title. number_exposures is the number of exposures to make. flush_array_flag defines detector flushing: -1 => current value defined by exposure.exposure_sequence_flush [default] 0 => flush for each exposure 1 => flush after first exposure only 2 => no flush Delay => delay between exposures in seconds -1 => no change """ arglist = [number_exposures, flush_array_flag, delay] thread = threading.Thread(target=self.sequence, name="sequence1", args=arglist) thread.start() return def pause(self): """ Pause an exposure in progress (integration only). Really sets a flag which is read in expose(). """ self.paused_time_start = time.time() # save paused clock time if self.exposure_flag != self.exposureflags["NONE"]: self.exposure_flag = self.exposureflags["PAUSE"] return def resume(self): """ Resume a paused exposure. Really sets a flag which is read in expose(). """ self.paused_time = ( time.time() - self.paused_time_start ) + self.paused_time # total paused time in seconds if self.exposure_flag != self.exposureflags["NONE"]: self.exposure_flag = self.exposureflags["RESUME"] return def abort(self): """ Abort an exposure in progress. Really sets a flag which is read in expose(). """ if self.exposure_flag != self.exposureflags["NONE"]: self.exposure_flag = self.exposureflags["ABORT"] return def set_shutter(self, state: int = 0, shutter_id: int = 0): """ Open or close a shutter. :param state: :param shutter_id: Shutter ID flag * 0 => controller default shutter. * 1 => instrument default shutter. """ if shutter_id == 0: azcam.db.tools["controller"].set_shutter(state) elif shutter_id == 1: azcam.db.tools["instrument"].set_shutter(state) return def parshift(self, number_rows: int): """ Shift sensor by number_rows. :param number_rows: number of rows to shift (positive is toward readout, negative is away) """ number_rows = int(float(number_rows)) azcam.db.tools["controller"].parshift(number_rows) return # *************************************************************************** # pixel counter # *************************************************************************** def get_pixels_remaining(self): """ Return number of remaining pixels to be read (counts down). """ reply = azcam.db.tools["controller"].get_pixels_remaining() self.pixels_remaining = reply return reply # *************************************************************************** # exposure time # *************************************************************************** def get_exposuretime(self): """ Return current exposure time in seconds. """ if azcam.db.tools["controller"].is_reset: self.exposure_time = azcam.db.tools["controller"].get_exposuretime( ) return self.exposure_time def set_exposuretime(self, exposure_time): """ Set current exposure time in seconds. """ self.exposure_time = float(exposure_time) self.exposure_time_actual = self.exposure_time # may be changed later azcam.db.tools["controller"].set_exposuretime(self.exposure_time) self.header.set_keyword("EXPREQ", exposure_time, "Exposure time requested (seconds)", "float") self.header.set_keyword("EXPTIME", exposure_time, "Exposure time (seconds)", "float") return def get_exposuretime_remaining(self): """ Return remaining exposure time (in seconds). """ if azcam.db.tools["controller"].is_reset: self.exposure_time_remaining = azcam.db.tools[ "controller"].update_exposuretime_remaining() return self.exposure_time_remaining # ********************************************************************************************** # header # ********************************************************************************************** def record_current_times(self): """ Record the current times and data info in the header. """ # get current time and date self.obstime.update(0) # format should be YYYY-MM-DDThh:mm:ss.sss ISO 8601 self.header.set_keyword("DATE-OBS", self.obstime.date[0], "UTC shutter opened", "str") self.header.set_keyword("DATE", self.obstime.date[0], "UTC date and time file writtten", "str") # OLD self.header.set_keyword("TIME-OBS", self.obstime.ut[0], "UTC at start of exposure", "str") self.header.set_keyword("UTC-OBS", self.obstime.ut[0], "UTC at start of exposure", "str") self.header.set_keyword("UT", self.obstime.ut[0], "UTC at start of exposure", "str") self.header.set_keyword("TIMESYS", self.obstime.time_system[0], "Time system", "str") self.header.set_keyword("TIMEZONE", self.obstime.time_zone[0], "Local time zone", "str") self.header.set_keyword( "LOCTIME", self.obstime.local_time[0], "Local time at start of exposure", "str", ) return def update_headers(self): """ Update all headers, reading current data. """ # set flag that update is in progress self.updating_header = 1 # all headers to be updated must be in azcam.db['headers'] for objectname in azcam.db.headers: if (objectname == "controller" or objectname == "system" or objectname == "exposure" or objectname == "focalplane"): continue try: azcam.db.tools[objectname].update_header( ) # dont crash so all headers get updated except Exception as e: azcam.log(f"could not get {objectname} header: {e}") # update focalplane header which is not in db self.image.focalplane.update_header() # try to update system header last if "system" in azcam.db.headers: try: azcam.db.headers["system"].update_header() except Exception: pass # set flag that update is finished self.updating_header = 0 return # *************************************************************************** # misc # *************************************************************************** def set_auto_title(self, flag=-1): """ Set the AutoTitle flag so that image title matches lowercase imagetype. """ if flag != -1: self.auto_title = flag self.set_image_title() if flag != 0: self.set_image_title() return def set_image_title(self, title=""): """ Set the image title. Allows for AutoTitle. """ if self.auto_title: if self.image_type.lower( ) == "object": # don't change object title in AutoTitle mode pass else: if title == "": title = self.image_type.lower() # set OBJECT keyword to title or autotitle value self.set_keyword("OBJECT", title, "", "str") self.title = title self.image.title = title return def get_image_title(self): """ Get the current image title. Allows for AutoTitle. """ return self.title def set_image_type(self, imagetype="zero"): """ Set image type for an exposure. imagetype is system defines, and typically includes: zero, object, dark, flat. """ self.image_type = imagetype return def check_image_ready(self): """ Returns True (1) if exposure image is ready and then toggles the flag off so that image is not detected multiple times. Used only for clients which need to know when an image is ready. An image is ready when written to disk if SaveFile is true, or when valid if SaveFile is false. """ if self.image.toggle: self.image.toggle = 0 return 1 else: return 0 def get_image_type(self): """ Get current image type for an exposure. imagetype is system defines, and typically includes: zero, object, dark, flat. """ return self.image_type def get_image_types(self): """ Return a list of valid imagetypes. Gets imagetypes and comparisons. """ l1 = self.image_types try: l2 = azcam.db.tools["instrument"].get_all_comps() except Exception: return l1 return l1 + l2 if len(l2) > 0 else l1 def check_comparison_imagetype(self, imagetype=""): """ Returns 1 if imagetype is a comparision, 0 if not. If imagetype is not specified, checks the current type. """ if imagetype == "": imagetype = self.image_type # if not specified use previous (global) # any undefined image types are comps if imagetype in self.image_types: return 0 else: return 1 def set_temp_files(self): """ Update TempImageFile and TempDisplayFile file names based on CommandServer port. """ if not os.path.isabs(self.temp_image_file): self.temp_image_file = os.path.join(azcam.db.datafolder, "TempImage") self.temp_image_file = os.path.normpath(self.temp_image_file) self.temp_image_file = "%s%d" % ( self.temp_image_file, azcam.db.tools["cmdserver"].port, ) # make unique for multiple processes return def set_test_image(self, flag): """ Sets TestImage flag is True, clears if False. """ self.test_image = flag return def get_test_image(self): """ Returns TestImage flag. """ flag = self.test_image return ["OK", flag] # ********************************************************************************************** # detector parameters # ********************************************************************************************** def set_detpars(self, sensor_data): """ Set detector parameters from sensor data dictionary. """ detpars = sensor_data if detpars.get("ref_pixel"): self.set_ref_pixel(detpars["ref_pixel"]) if detpars.get("format"): self.set_format(*detpars["format"]) if detpars.get("focalplane"): self.set_focalplane(*detpars["focalplane"]) if detpars.get("roi"): self.set_roi(*detpars["roi"]) if detpars.get("ext_position"): self.set_extension_position(detpars["ext_position"]) if detpars.get("jpg_order"): self.set_jpg_order(detpars["jpg_order"]) if detpars.get("det_number"): self.set_detnum(detpars["det_number"]) if detpars.get("det_position"): self.set_detpos(detpars["det_position"]) if detpars.get("det_gap"): self.set_detgap(detpars["det_gap"]) if detpars.get("ext_name"): self.set_extname(detpars["ext_name"]) if detpars.get("ext_number"): self.set_extnum(detpars["ext_number"]) if detpars.get("ctype"): self.image.focalplane.wcs.ctype1 = detpars["ctype"][0] self.image.focalplane.wcs.ctype2 = detpars["ctype"][1] return def set_format( self, ns_total=-1, ns_predark=-1, ns_underscan=-1, ns_overscan=-1, np_total=-1, np_predark=-1, np_underscan=-1, np_overscan=-1, np_frametransfer=-1, ): """ Set the detector format for subsequent exposures. Must call set_roi() after using this command and before starting exposure. ns_total is the number of visible columns. ns_predark is the number of physical dark underscan columns. ns_underscan is the desired number of desired dark underscan columns. ns_overscan is the number of dark overscan columns. np_total is the number of visible rows. np_predark is the number of physical dark underscan rows. np_underscan is the number of desired dark underscan rows. np_overscan is the number of desired dark overscan rows. np_frametransfer is the rows to frame transfer shift. """ # update image.focalplane self.image.focalplane.set_format( ns_total, ns_predark, ns_underscan, ns_overscan, np_total, np_predark, np_underscan, np_overscan, np_frametransfer, ) # update controller parameters controller = azcam.db.tools["controller"] controller.detpars.ns_total = self.image.focalplane.ns_total controller.detpars.ns_predark = self.image.focalplane.ns_predark controller.detpars.ns_underscan = self.image.focalplane.ns_underscan controller.detpars.ns_overscan = self.image.focalplane.ns_overscan controller.detpars.np_total = self.image.focalplane.np_total controller.detpars.np_predark = self.image.focalplane.np_predark controller.detpars.np_underscan = self.image.focalplane.np_underscan controller.detpars.np_overscan = self.image.focalplane.np_overscan controller.detpars.np_frametransfer = self.image.focalplane.np_frametransfer controller.detpars.coltotal = self.image.focalplane.ns_total controller.detpars.colusct = self.image.focalplane.ns_predark controller.detpars.coluscw = self.image.focalplane.ns_underscan controller.detpars.coluscm = 0 controller.detpars.coloscw = self.image.focalplane.ns_overscan controller.detpars.coloscm = 0 controller.detpars.rowtotal = self.image.focalplane.np_total controller.detpars.rowusct = self.image.focalplane.np_predark controller.detpars.rowuscw = self.image.focalplane.np_underscan controller.detpars.rowuscm = 0 controller.detpars.rowoscw = self.image.focalplane.np_overscan controller.detpars.rowoscm = 0 controller.detpars.framet = self.image.focalplane.np_frametransfer return def get_format(self): """ Return the current detector format parameters. """ return self.image.focalplane.get_format() def set_focalplane(self, numdet_x=-1, numdet_y=-1, numamps_x=-1, numamps_y=-1, amp_config=""): """ Sets focal plane configuration for subsequent exposures. Use after set_format(). Must call set_roi() after using this command and before starting exposure. This command replaces SetConfiguration. Default focalplane values are set here. numdet_x defines number of detectors in Column direction. numdet_y defines number of detectors in Row direction. numamps_x defines number of amplifiers in Column direction. numamps_y defines number of amplifiers in Row direction. amp_config defines each amplifier's orientation (ex: '1223'). 0 - normal 1 - flip x 2 - flip y 3 - flip x and y """ # update image.focalplane reply = self.image.focalplane.set_focalplane(numdet_x, numdet_y, numamps_x, numamps_y, amp_config) # update controller parameters controller = azcam.db.tools["controller"] controller.detpars.numdet_x = self.image.focalplane.numdet_x controller.detpars.numdet_y = self.image.focalplane.numdet_y controller.detpars.numamps_x = self.image.focalplane.numamps_x controller.detpars.numamps_y = self.image.focalplane.numamps_y controller.detpars.amp_config = self.image.focalplane.amp_config controller.detpars.amp_cfg = self.image.focalplane.amp_cfg controller.detpars.num_detectors = self.image.focalplane.num_detectors controller.detpars.num_ser_amps_det = self.image.focalplane.num_ser_amps_det controller.detpars.num_par_amps_det = self.image.focalplane.num_par_amps_det controller.detpars.num_amps_det = self.image.focalplane.num_amps_det controller.detpars.numamps_image = self.image.focalplane.numamps_image controller.detpars.ampvispix_x = self.image.focalplane.ampvispix_x controller.detpars.ampvispix_y = self.image.focalplane.ampvispix_y self.image.set_scaling() return reply def get_focalplane(self): """ Returns the current focal plane configuration. """ return self.image.focalplane.get_focalplane() def set_data_order(self, dataOrder): """ Sets data order """ self.data_order = [] if len(dataOrder) > 0: for item in dataOrder: self.data_order.append(int(item)) return def set_roi( self, first_col=-1, last_col=-1, first_row=-1, last_row=-1, col_bin=-1, row_bin=-1, roi_num=0, ): """ Sets the ROI values for subsequent exposures. Currently only one ROI (0) is supported. These values are for the entire focal plane, not just one detector. """ # update image.focalplane first_col = int(first_col) last_col = int(last_col) first_row = int(first_row) last_row = int(last_row) col_bin = int(col_bin) row_bin = int(row_bin) roi_num = int(roi_num) self.image.focalplane.set_roi(first_col, last_col, first_row, last_row, col_bin, row_bin, roi_num) # update controller parameters controller = azcam.db.tools["controller"] controller.detpars.first_col = self.image.focalplane.first_col controller.detpars.last_col = self.image.focalplane.last_col controller.detpars.first_row = self.image.focalplane.first_row controller.detpars.last_row = self.image.focalplane.last_row controller.detpars.col_bin = self.image.focalplane.col_bin controller.detpars.row_bin = self.image.focalplane.row_bin controller.detpars.xunderscan = self.image.focalplane.xunderscan controller.detpars.xskip = self.image.focalplane.xskip controller.detpars.xpreskip = self.image.focalplane.xpreskip controller.detpars.xdata = self.image.focalplane.xdata controller.detpars.xpostskip = self.image.focalplane.xpostskip controller.detpars.xoverscan = self.image.focalplane.xoverscan controller.detpars.yunderscan = self.image.focalplane.yunderscan controller.detpars.yskip = self.image.focalplane.yskip controller.detpars.ypreskip = self.image.focalplane.ypreskip controller.detpars.ydata = self.image.focalplane.ydata controller.detpars.ypostskip = self.image.focalplane.ypostskip controller.detpars.yoverscan = self.image.focalplane.yoverscan controller.detpars.numcols_amp = self.image.focalplane.numcols_amp controller.detpars.numcols_overscan = self.image.focalplane.numcols_overscan controller.detpars.numviscols_amp = self.image.focalplane.numviscols_amp controller.detpars.numviscols_image = self.image.focalplane.numviscols_image controller.detpars.numrows_amp = self.image.focalplane.numrows_amp controller.detpars.numrows_overscan = self.image.focalplane.numrows_overscan controller.detpars.numvisrows_amp = self.image.focalplane.numvisrows_amp controller.detpars.numvisrows_image = self.image.focalplane.numvisrows_image controller.detpars.numpix_amp = self.image.focalplane.numpix_amp controller.detpars.numcols_det = self.image.focalplane.numcols_det controller.detpars.numrows_det = self.image.focalplane.numrows_det controller.detpars.numpix_det = self.image.focalplane.numpix_det controller.detpars.numpix_image = self.image.focalplane.numpix_image controller.detpars.numcols_image = self.image.focalplane.numcols_image controller.detpars.numrows_image = self.image.focalplane.numrows_image controller.detpars.numbytes_image = self.image.focalplane.numbytes_image controller.detpars.xflush = self.image.focalplane.xflush controller.detpars.yflush = self.image.focalplane.yflush # update controller controller.set_roi() # update image size self.size_x = self.image.focalplane.numcols_image self.size_y = self.image.focalplane.numrows_image self.image.size_x = self.image.focalplane.numcols_image self.image.size_y = self.image.focalplane.numrows_image # indicate that ROI has changed for next exposure self.new_roi = 1 return def get_roi(self, roi_num=0): """ Returns a list of the ROI parameters for the roi_num specified. Currently only one ROI (0) is supported. Returned list format is (first_col,last_col,first_row,last_row,col_bin,row_bin). """ return self.image.focalplane.get_roi(roi_num) def roi_reset(self): """ Resets detector ROI values to full frame, current binning. """ self.image.focalplane.roi_reset() return def set_ref_pixel(self, XY): """ Set the reference pixel. XY is [X,Y] in pixels. """ self.image.focalplane.set_ref_pixel(XY) return def set_extension_position(self, XY): """ Set the extension position of each amplifier. XY is [[X,Y]] in index numbers, starting at [1,1]. """ self.image.focalplane.set_extension_position(XY) return def set_detnum(self, det_number): """ Set the detector numbers. """ self.image.focalplane.det_number = det_number return def set_detgap(self, det_gap): """ Set the detector gaps in pixels. """ for i, gap in enumerate(det_gap): self.image.focalplane.gapx[i] = gap[0] self.image.focalplane.gapy[i] = gap[1] return def set_detpos(self, det_position): """ Set the detector positions. """ for i, pos in enumerate(det_position): self.image.focalplane.detpos_x[i] = pos[0] self.image.focalplane.detpos_y[i] = pos[1] return def set_jpg_order(self, Indices): """ Set JPG image positions. """ self.image.focalplane.set_jpg_order(Indices) return def set_tdi_delay(self, flag): """ Set TDI line delay On or Off. flag==True sets delay to self.tdi_delay. flag==Flase sets line delay to normal value, self.par_delay. """ return def set_extname(self, ext_name): """ Set image extension names. """ self.image.focalplane.set_extension_name(ext_name) return def set_extnum(self, ext_number): """ Set image extension numbers. """ self.image.focalplane.set_extension_extnum(ext_number) return def get_status(self): """ Return a variety of system status data in one dictionary. """ progress = 0 filename = self.get_filename() # filename = os.path.basename(filename) et = self.get_exposuretime() if self.is_exposure_sequence: self.exposure_sequence_number seqcount = self.exposure_sequence_number seqtotal = self.exposure_sequence_total else: seqcount = 0 seqtotal = 0 ef = self.exposure_flag expstate = self.exposureflags_rev.get(ef, "") if azcam.db.tools["tempcon"].enabled: try: camtemp, dewtemp = azcam.db.tools["tempcon"].get_temperatures( )[0:2] camtemp = f"{camtemp:.1f}" dewtemp = f"{dewtemp:.1f}" except Exception as e: azcam.log(e) camtemp = -999.9 # error reading temperature dewtemp = -666.6 else: camtemp = -999.9 dewtemp = -666.6 # camtemp = f"{camtemp:8.3f}" # dewtemp = f"{dewtemp:8.3f}" if ef == 1: expcolor = "green" et = self.get_exposuretime() if et == 0: progress = 0.0 explabel = "" else: etr = self.get_exposuretime_remaining() explabel = f"{etr:.1f} sec remaining" progress = float(100.0 * (etr / et)) elif ef == 7: expcolor = "red" progress = int(100.0 * (self.get_pixels_remaining() / self.image.focalplane.numpix_image)) explabel = f"{progress}% readout" elif ef == 8: expcolor = "cyan" explabel = "exposure setup" else: expcolor = "transparent" progress = 0.0 explabel = "" expstate = "" if self.message == "" and expstate != "": message = expstate if self.is_exposure_sequence: message = ( f"{message} - {self.exposure_sequence_number} of {self.exposure_sequence_total}" ) else: message = self.message # debug if 0: self.pgress += 5.0 if self.pgress > 100: self.pgress = 0 progress = self.pgress response = { "message": message, "exposurelabel": explabel, "exposurecolor": expcolor, "exposurestate": expstate, "progressbar": progress, "camtemp": camtemp, "dewtemp": dewtemp, "filename": filename, "seqcount": seqcount, "seqtotal": seqtotal, "timestamp": self._timestamp(0), "imagetitle": self.get_image_title(), "imagetype": self.get_image_type(), "imagetest": self.test_image, "exposuretime": self.get_exposuretime(), "colbin": self.image.focalplane.col_bin, "rowbin": self.image.focalplane.row_bin, "systemname": azcam.db.systemname, "mode": azcam.db.servermode, } return response def _timestamp(self, precision=0): """ Return a timestamp string. precision is number of fractional seconds. """ dateTimeObj = datetime.datetime.now() timestamp = str(dateTimeObj) if precision >= 6: pass elif precision == 0: timestamp = timestamp[:-7] else: tosecs = timestamp[:-7] frac = str(round(float(timestamp[-7:]), precision)) timestamp = tosecs + frac return timestamp def read_header_file(self, filename): """ Read header template file. """ return azcam.db.headers["system"].read_file(filename)
class Controller(Tools, ObjectHeaderMethods): """ Base class for controller tool. Usually implemented as the "controller" tool. """ def __init__(self, tool_id: str = "controller", description: str or None = None): """ Args: tool_id: tool name description: description of this tool """ Tools.__init__(self, tool_id, description) #: interface type (0 = demo, 4 = PCIe) self.interface_type = 0 # create the controller Header object self.header = Header("Controller") self.header.set_header("controller", 2) #: exposure time (secs) self.exposure_time = 0 self.detpars = DetPars() #: True if the controller has been reset self.reset_flag = 0 azcam.db.tools_init["controller"] = self azcam.db.tools_reset["controller"] = self def set_roi(self): """ Sets ROI parameters values in the controller based on focalplane parameters. """ return def set_shutter_state(self, flag: bool = 0): """ Sets the shutter state during an exposure. Args: flag: True open shutter during exposure, False close shutter during exposure """ return def set_shutter(self, state: bool, shutter_id: int = 0): """ Open or close controller shutter. Args: state: True to open shutter, False to close shutter shutter_id: shutter ID number """ return def flush(self, cycles=1): """ Flush or clear out the sensor. Returns after clearing is finished, which could take many seconds. Args: cycles: number of times to flush sensor """ return def start_idle(self): """ Start idle clocking. """ return def stop_idle(self): """ Stop idle clocking. """ return
class Instrument(Tools, ObjectHeaderMethods): """ The base instrument tool. Usually implemented as the "instrument" tool. """ def __init__(self, tool_id="instrument", description=None): Tools.__init__(self, tool_id, description) # active comparisons self.active_comps = [] # True if shutter controls comps self.shutter_strobe = 0 # focus position self.focus_position = 0 # system pressures self.pressure_ids = [0] # system currents self.current_ids = [0] azcam.db.tools_init["instrument"] = self azcam.db.tools_reset["instrument"] = self # instrument header object self.header = Header("Instrument") self.header.set_header("instrument", 3) return # *************************************************************************** # exposure # *************************************************************************** def exposure_start(self): """ Optional call before exposure starts. """ return def exposure_finish(self): """ Optional tempcon.call after exposure finishes. """ return # *************************************************************************** # comparisons # *************************************************************************** def get_all_comps(self): """ Return all valid comparison names. Useful for clients to determine which type of comparison exposures are supported. """ raise NotImplementedError def get_comps(self): """ Return a list of the active comparison lamps. """ return self.active_comps def set_comps(self, comp_names: List[str] = None): """ Set comparisons to be turned on and off with comps_on() and comps_off(). Args: comp_names: list of string (or a single string) of comparison names """ if type(comp_names) == str: comp_names = comp_names.split(" ") self.active_comps = comp_names return def comps_on(self): """ Turn on active comparisons. """ return def comps_off(self): """ Turn off active comparisons. """ return def comps_delay(self, delay_time=0): """ Delays for delay_time for comparison lamp warmup. May internally set delay based on comp selection. Args: delay_time: delay in seconds """ time.sleep(delay_time) return # *************************************************************************** # filters # *************************************************************************** def get_filters(self, filter_id=0): """ Return a list of all available/loaded filters. Args: filter_id: filter mechanism ID """ raise NotImplementedError def get_filter(self, filter_id=0): """ Return the current/loaded filter, typically the filter in the beam. Args: filter_id: filter mechanism ID """ raise NotImplementedError def set_filter(self, filter_name, filter_id=0): """ Set the current/loaded filter, typically the filter in the beam. Args: filter_name: filter name to set. Could be a number or filter name. filter_id: filter mechanism ID """ raise NotImplementedError # *************************************************************************** # wavelengths # *************************************************************************** def get_wavelengths(self, wavelength_id: int = 0): """ Returns a list of valid wavelengths. Used for filter and LED based systems. Args: wavelength_id: wavelength mechanism ID """ raise NotImplementedError def get_wavelength(self, wavelength_id: int = 0): """ Returns the current wavelength. Args: wavelength_id: wavelength mechanism ID """ raise NotImplementedError def set_wavelength(self, wavelength: Any, wavelength_id: int = 0): """ Set the current wavelength, typically for a filter or grating. Args: wavelength: wavelength value to set. Could be a number or filter name. wavelength_id: wavelength mechanism ID """ raise NotImplementedError # *************************************************************************** # focus # *************************************************************************** def get_focus(self, focus_id=0): """ Return the current instrument focus position. focus_id is the focus mechanism ID. """ raise NotImplementedError("get_focus") def set_focus(self, focus_position, focus_id=0, focus_type="absolute"): """ Move (or step) the instrument focus. focus_position is the focus position or step size. focus_id is the focus mechanism ID. focus_type is "absolute" or "step". """ raise NotImplementedError("set_focus") # *************************************************************************** # pressure # *************************************************************************** def get_pressures(self): """ Return a list of all instrument pressures. """ pressures = [] for pressure_id in self.pressure_ids: p = self.get_pressure(pressure_id) pressures.append(p) return pressures def get_pressure(self, pressure_id=0): """ Read an instrument pressure. """ raise NotImplementedError def set_pressure(self, pressure, pressure_id=0): """ Sets an instrument pressure. """ raise NotImplementedError # *************************************************************************** # shutter # *************************************************************************** def set_shutter(self, state, shutter_id=0): """ Open or close the instrument shutter. state is 1 for open and 0 for close. shutter_id is the shutter mechanism ID. """ raise NotImplementedError # *************************************************************************** # power meter # *************************************************************************** def get_power(self, wavelength: float, power_id: int = 0) -> float: """ Returns power meter reading. Args: wavelength: wavelength for power meter power_id: power ID flag Returns: mean_power: mean power in Watts/cm2 """ raise NotImplementedError # *************************************************************************** # electrometer # *************************************************************************** def get_currents(self): """ Return a list of all instrument currents. """ currents = [] for current_id in self.current_ids: p = self.get_current(current_id) currents.append(p) return currents def get_current(self, shutter_state: int = 1, current_id: int = 0) -> float: """ Read instrument current, usually from an electrometer. Args: current_id: current source ID shutter_state: shutter state during read Returns: current: measured current in amps """ raise NotImplementedError