Example #1
0
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
Example #2
0
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
Example #3
0
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
Example #4
0
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]
Example #5
0
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
Example #6
0
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)
Example #7
0
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
Example #8
0
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