예제 #1
0
def adc_shift(cmds):
    """Generates a list of x and y shifts from a tests_commands object"""

    para_angle = cmds["OBS_PARALLACTIC_ANGLE"]
    effectiveness = cmds["INST_ADC_PERFORMANCE"] / 100.

    ## get the angle shift for each slice
    zenith_distance = utils.airmass2zendist(cmds["ATMO_AIRMASS"])
    angle_shift = [
        scopesim.effects.shifts.atmospheric_refraction(
            lam, zenith_distance, cmds["ATMO_TEMPERATURE"],
            cmds["ATMO_REL_HUMIDITY"], cmds["ATMO_PRESSURE"],
            cmds["SCOPE_LATITUDE"], cmds["SCOPE_ALTITUDE"])
        for lam in cmds.lam_bin_centers
    ]

    ## convert angle shift into number of pixels
    ## pixel shifts are defined with respect to last slice
    rel_shift = (angle_shift - angle_shift[-1])
    if np.max(np.abs(rel_shift)) > 1000:
        raise ValueError("Pixel shifts too great (>1000), check units")

    ## Rotate by the paralytic angle
    x = -rel_shift * np.sin(np.deg2rad(para_angle)) * (1. - effectiveness)
    y = -rel_shift * np.cos(np.deg2rad(para_angle)) * (1. - effectiveness)

    ## return values are in [arcsec]
    return x, y
예제 #2
0
 def test_airmass2zendist_undoes_exactly_what_zendist2airmass_does(self):
     zendist = 12.31334
     assert np.allclose(airmass2zendist(zendist2airmass(zendist)),
                        zendist)
예제 #3
0
 def test_zendist2airmass_undoes_exactly_what_airmass2zendist_does(self):
     airmass = 1.78974234
     assert np.allclose(zendist2airmass(airmass2zendist(airmass)),
                        airmass)
예제 #4
0
 def test_pass_for_known_quanities_AM_2_equals_ZD_sqrt2(self):
     assert np.allclose(airmass2zendist(np.sqrt(2)), 45)
예제 #5
0
 def test_airmass2zendist_pass_for_known_quanities_AM_1_equals_ZD_0(self):
     assert np.allclose(airmass2zendist(1.0), 0)
예제 #6
0
    def _get_lam_bin_edges(self, lam_min, lam_max):
        """
        Generates an array with the bin edges of the layers in spectral space

        Parameters
        ----------
        lam_min, lam_max : float
            [um] the minimum and maximum wavelengths of the filter range

        Notes
        -------
        Atmospheric diffraction causes blurring in an image. To model this
        effect the spectra from a ``Source`` object are cut into bins based on
        how far the photons at different wavelength are diffracted from the
        image center. The threshold for defining a new layer based on the how
        far a certain bin will move is given by ``SIM_ADC_SHIFT_THRESHOLD``. The
        default value is 1 pixel.

        The PSF also causes blurring as it spreads out over a bandpass. This
        also needed to be taken into account
        """

        if self.cmds["SIM_VERBOSE"] == "yes":
            print("Determining lam_bin_edges")

        effectiveness = self.cmds["INST_ADC_PERFORMANCE"] / 100.

        # This is redundant because also need to look at the PSF width
        # if effectiveness == 1.:
        #    lam_bin_edges = np.array([lam_min, lam_max])
        #    return lam_bin_edges

        shift_threshold = self.cmds["SIM_ADC_SHIFT_THRESHOLD"]

        # get the angle shift for each slice
        lam = np.arange(lam_min, lam_max + 1E-7, 0.001)
        zenith_distance = airmass2zendist(self.cmds["ATMO_AIRMASS"])
        angle_shift = atmospheric_refraction(lam, zenith_distance,
                                             self.cmds["ATMO_TEMPERATURE"],
                                             self.cmds["ATMO_REL_HUMIDITY"],
                                             self.cmds["ATMO_PRESSURE"],
                                             self.cmds["SCOPE_LATITUDE"],
                                             self.cmds["SCOPE_ALTITUDE"])

        # convert angle shift into number of pixels
        # pixel shifts are defined with respect to last slice
        rel_shift = (angle_shift - angle_shift[-1]) / self.pix_res
        rel_shift *= (1. - effectiveness)
        if np.max(np.abs(rel_shift)) > 1000:
            raise ValueError("Pixel shifts too great (>1000), check units")

        # Rotate by the paralytic angle
        int_shift = np.array(rel_shift / shift_threshold, dtype=np.int)
        idx = [
            np.where(int_shift == i)[0][0] for i in np.unique(int_shift)[::-1]
        ]
        lam_bin_edges_adc = np.array(lam[idx + [len(lam) - 1]])

        # Now check to see if the PSF blurring is the controlling factor. If so,
        # take the lam_bin_edges for the PSF blurring

        diam = self.diameter
        d_ang = self.pix_res * shift_threshold

        # .. todo:: get rid of hard coded diameter of MICADO FOV
        diam_arcsec = 1.22 * 53 * 3600
        lam_bin_edges_psf = [lam_min]
        ang0 = (lam_min * 1E-6) / diam * diam_arcsec

        i = 1
        while lam_bin_edges_psf[-1] < lam_max:
            lam_bin_edges_psf += [(ang0 + d_ang * i) * diam / diam_arcsec * 1E6
                                  ]
            i += 1
            if i > 1000:
                raise ValueError("lam_bin_edges needs >1000 values")
        lam_bin_edges_psf[-1] = lam_max

        lam_bin_edges = np.unique(
            np.concatenate((np.round(lam_bin_edges_psf,
                                     3), np.round(lam_bin_edges_adc, 3))))

        if self.cmds["SIM_VERBOSE"] == "yes":
            print("PSF edges were", np.round(lam_bin_edges_psf, 3))
            print("ADC edges were", np.round(lam_bin_edges_adc, 3))
            print("All edges were", np.round(lam_bin_edges, 3))

        return lam_bin_edges
예제 #7
0
    def read_out(self, filename=None, to_disk=False, chips=None,
                 read_out_type="superfast", **kwargs):
        """
        Simulate the read-out process of the detector array

        Based on the parameters set in the ``UserCommands`` object, the detector
        will read out the images stored on the ``Chips`` according to the
        specified read-out scheme, i.e. Fowler, up-the-ramp, single read, etc.

        Parameters
        ----------
        filename : str
            where the file is to be saved. If ``None`` and ``to_disk`` is true,
            the output file is called "output.fits". Default is ``None``

        to_disk : bool
            a flag for where the output should go.
            If ``filename`` is given or if ``to_disk=True``,
            the ``Chip`` images will be written to a  `.fits`` file on disk.
            If no `filename`` is specified, the output will be called "output.fits".

        chips : int, array-like, optional
            The chip or chips to be read out, based on the detector_layout.dat
            file. Default is the first ``Chip`` specified in the list, i.e. [0].

        read_out_type : str, optional
            The name of the algorithm used to read out the chips:
            - "superfast"
            - "non_destructive"
            - "up_the_ramp"

        Returns
        -------
        astropy.io.fits.HDUList

        Keyword Arguments (**kwargs)
        ----------------------------
        **kwargs are used to update the ``UserCommands`` object that controls
        the ``Detector``. Therefore any dictionary keywords can be passed in the
        form of a dictionary, i.e. {"OBS_EXPTIME" : 60, "OBS_OUTPUT_DIR" : "./"}

        """

        #removed kwargs
        self.cmds.update(kwargs)

        if filename is not None:
            to_disk = True

        if filename is None and to_disk is True:
            if self.cmds["OBS_OUTPUT_DIR"] is None:
                self.cmds["OBS_OUTPUT_DIR"] = "./output.fits"
            filename = self.cmds["OBS_OUTPUT_DIR"]

        if chips is not None:
            if np.isscalar(chips):
                ro_chips = [chips]
            else:
                ro_chips = chips
        elif chips is None:
            ro_chips = np.arange(len(self.chips))
        else:
            raise ValueError("Something wrong with ``chips``")

        # Time stamp for FITS header
        #creation_date = datetime.now().isoformat(timespec='seconds')
        # timespec="seconds" throws an error on some python versions
        creation_date = datetime.now().strftime("%Y-%m-%dT%H-%M-%S")

        hdulist = fits.HDUList()

        # Create primary header unit for multi-extension files
        if len(ro_chips) > 1:
            primary_hdu = fits.PrimaryHDU()

            primary_hdu.header['DATE'] = creation_date


            for key in self.cmds.cmds:
                val = self.cmds.cmds[key]

                if isinstance(val, (sc.TransmissionCurve, sc.EmissionCurve,
                                    sc.UnityCurve, sc.BlackbodyCurve)):
                    val = val.params["filename"]

                if isinstance(val, str) and len(val) > 35:
                    val = "... " + val[-35:]

                try:
                    primary_hdu.header["HIERARCH "+key] = val
                except NameError:   # any other exceptions possible?
                    pass
            hdulist.append(primary_hdu)

        # Save the detector image(s)
        for i in ro_chips:
            ######
            # Put in a catch here so that only the chips specified in "chips"
            # are read out
            ######
            print("Reading out chip", self.chips[i].id, "using",
                  read_out_type)

            array = self.chips[i].read_out(self.cmds,
                                           read_out_type=read_out_type)

            ## TODO: transpose is just a hack - need to make sure
            ##       x and y are handled correctly throughout ScopeSim
            thishdu = fits.ImageHDU(array.T)

            thishdu.header["EXTNAME"] = ("CHIP_{:02d}".format(self.chips[i].id),
                                         "Chip ID")

            thishdu.header["CHIP_ID"] = (self.chips[i].id, "Chip ID")
            thishdu.header['DATE'] = creation_date

            # Primary WCS for sky coordinates
            thishdu.header.extend(self.chips[i].wcs.to_header())

            # Secondary WCS for focal plane coordinates
            try:
                thishdu.header.extend(self.chips[i].wcs_fp.to_header(key='A'))
            except AttributeError:
                print("No WCS_FP!")
                pass

            thishdu.header["BUNIT"] = ("ADU", "")
            thishdu.header["EXPTIME"] = (self.exptime, "[s] Exposure time")
            thishdu.header["NDIT"] = (self.ndit, "Number of exposures")
            #thishdu.header["TRO"] = (self.tro,
            #                         "[s] Time between non-destructive readouts")
            thishdu.header["GAIN"] = (self.chips[i].gain, "[e-/ADU]")
            thishdu.header["AIRMASS"] = (self.cmds["ATMO_AIRMASS"], "")
            thishdu.header["ZD"] = \
                (airmass2zendist(self.cmds["ATMO_AIRMASS"]), "[deg]")


            for key in self.cmds.cmds:
                val = self.cmds.cmds[key]
                if isinstance(val, str):
                    if len(val) > 35:
                        val = "... " + val[-35:]
                try:
                    thishdu.header["HIERARCH "+key] = val
                except NameError:   # any other exceptions possible?
                    pass
                except ValueError:
                    logging.warning("ValueError - Couldn't add keyword: "+key)
            hdulist.append(thishdu)

        if to_disk:
            hdulist.writeto(filename, clobber=True, checksum=True)

        return hdulist