Esempio n. 1
0
 def plot_spectrum(self):
     """One plot showing the flux of the spectrum for this lenslet.
     
     Plot shows Flux (in counts) vs. Wavelength. ``%(Partials)s/Instrument-%(num)04d-Flux%(ext)s``
     
     **Variables which are used**:
     
     :var tfl: flux of each pixel in counts
     :var twl: wavelength of each pixel in meters
     
     """
     assert self.traced
     self.log.debug(npArrayInfo(self.twl*1e6,"Wavlength"))
     self.log.debug(npArrayInfo(self.tfl,"Flux"))
     plt.clf()
     plt.semilogy(self.twl*1e6,self.tfl,"b.")
     xmin,xmax,ymin,ymax = plt.axis()
     if ymin < 1e-4:
         ymin = 1e-4
     plt.axis((xmin,xmax,ymin,ymax))
     plt.title("Generated, Fluxed Spectra (%d)" % self.num)
     plt.xlabel("Wavelength ($\mu m$)")
     plt.ylabel("Flux (Electrons)")
     plt.savefig("%(Partials)s/Instrument-%(num)04d-Flux%(ext)s" % dict(num=self.num, ext=self.config["Plots"]["format"],**self.config["Dirs"]))
     plt.clf()
Esempio n. 2
0
 def plot_trace(self):
     """Two plots showing the trace data for this lenslet.
     
     1. Delta Lambda per Pixel vs. Wavelenth ``%(Partials)s/Instrument-%(num)04d-DeltaWL%(ext)s``
     
     2. Sampling Resolution vs. Wavelength ``%(Partials)s/Instrument-%(num)04d-Resolution%(ext)s``
     
     **Variables which are used**:
     
     :var tfl: flux of each pixel in counts
     :var twl: wavelength of each pixel in meters
     :var tdw: delta wavelength covered by each pixel in meters
     :var trs: sampling resolution of each pixel
     
     """
     assert self.traced
     plt.clf()
     plt.plot(self.twl*1e6,self.tdw*1e6,"g.")
     plt.title("$\Delta\lambda$ for each pixel (%d)" % self.num)
     plt.xlabel("Wavelength ($\mu m$)")
     plt.ylabel("$\Delta\lambda$ per pixel")
     plt.savefig("%(Partials)s/Instrument-%(num)04d-DeltaWL%(ext)s" % dict(num=self.num, ext=self.config["Plots"]["format"],**self.config["Dirs"]))
     plt.clf()
     self.log.debug(npArrayInfo(self.trs,"Trace RS"))
     plt.semilogy(self.twl*1e6,self.trs,"g.")
     plt.title("$R = \\frac{\lambda}{\Delta\lambda}$ for each pixel (%d)" % self.num)
     plt.xlabel("Wavelength ($\mu m$)")
     plt.ylabel("Resolution $R = \\frac{\lambda}{\Delta\lambda}$ per pixel")
     plt.savefig("%(Partials)s/Instrument-%(num)04d-Resolution%(ext)s" % dict(num=self.num, ext=self.config["Plots"]["format"],**self.config["Dirs"]))
     plt.clf()
Esempio n. 3
0
 def place_trace(self,get_conv):
     """Place the trace on the subimage.
     
     First a blank image is created using the ``subshape`` variable as a template. Then, iterating through each point, we get the convolved PSF and telescope image for that point (called the "convolution"). The convolution can vary by wavelenght, and can have other variables which are sytem dependent. The convolution is multiplied by the flux value for that point. The corner of the convolution is then calculated (the top-left and bottom right corners are actually calculated) so that the image can be insterted as a 'flattened' array. Each image is inserted by addition, adding on to the image already in place.
     
     **Variables which are used**:
     
     :var txs: x-subimage-indicies of each illuminated oversampled pixel in o-px
     :var tys: y-subimage-indicies of each illuminated oversampled pixel in o-px
     :var tfl: flux of each pixel in counts
     :var twl: wavelength of each pixel in meters
     :var subshape: shape of subimage to contain spectrum
     :var subcorner: corner of subimage to contain spectrum in px
     
     **Variables which are set**:
     
     :var frame: A frame labeled "Raw Spectrum" with the oversampled spectrum.
     
     """
     
     img = np.zeros(self.subshape)
     
     for x,y,wl,flux in zip(self.txs,self.tys,self.twl,self.tfl):
         if self.config["Instrument"]["Tel"]["ellipse"]:
             a = self.fa(wl)
             b = self.fb(wl)
             rot = self.falpha(wl)
             conv = get_conv(wl,a,b,rot)
         else:
             conv = get_conv(wl)
         tiny_image = conv * flux
         tl_corner = [ x - tiny_image.shape[0]/2.0, y - tiny_image.shape[0]/2.0 ]
         br_corner = [ x + tiny_image.shape[0]/2.0, y + tiny_image.shape[0]/2.0 ]
         img[tl_corner[0]:br_corner[0],tl_corner[1]:br_corner[1]] += tiny_image
     self.log.debug(npArrayInfo(img,"DenseSubImage"))
     self["Raw Spectrum"] = img
     frame = self.frame()
     frame.lensletNumber = self.num
     frame.corner = self.subcorner
     frame.configHash = hash(str(self.config.extract()))
Esempio n. 4
0
    def get_trace(self,spectrum):
        """Returns a trace of this spectrum. The trace will contain x and y over-dense pixel positions, flux values for each of those illuminated pixels, instantaneous resolution at each pixel, and wavelength of each pixel. The trace also determines the corners of the spectrum, and saves those corner positions with ample padding in the image.
        
        To perform the trace, we first get the oversampled point positions in o-px from the ``dxs`` and ``dys`` variables. These points are saved as both ``x,y`` and ``xorig,yorig``, for later use. ``xorig,yorig`` are stored to save an unmodified copy of the points. The ``xint,yint`` variables are used to store the integer (in px) positions of each ``x,y`` pair, essentially, thier containing camera pixel. ``x,y`` are then zeroed, such that (0,0) is the upper-right corner of the spectrum to be inserted. We then calculate the size of the subimage (in ``xdist,ydist``) and adjust this size so that it is an integer number of camera pixels (px) across. This makes the binning much easier later. The pixels ``x,y`` are then padded to provide space for the PSF to be applied on all sides of the single-pixel spectrum. 
        
        We then find the corner of the of the subimage. First, the corner will generally be extracted as the position with a minimum in the ``y`` direction and a maximum in the ``x`` direction. We first find the corner's position in integer camrea-px space (i.e. from ``xint,yint``, stored as ``corner``), and the corner's position in integer o-px space (i.e. from ``xorig,yorig``, stored as ``realcorner``). Converting the camera's position in integer camera-px space, we take the difference between the two corners as the ``offset``. This is the shift we must insert into ``x,y`` in order to ensure that the corner of our subimage will line up with the corner of a binned pixel. Next we add padding distances into the ``corner`` position. Finally, we use the offset to move the ``x,y`` positions to account for aligning the corner of the subimage with the corner of a full camera pixel. I will make a diagram to explain all of this shortly. Next, we add the padding values into ``xdist,ydist`` to get the full size of the subimage in o-px.
        
        We are now ready to extract flux values from our spectrum. This is done using the ``dwl`` values as the wavelength values to sample at. Using the ``dwl`` values, we also calculate an effective sampling resolution, which is used to resample the spectra. Feeding both of these, we compute the flux of the spectrum at each pixel position. This computation is not described in this function, but in a separate location in the documentation.
        
        After computing the flux at each pixel, we save the ``x,y`` indicies of each pixel, the flux for that pixel, the wavelength of that pixel, and the shape and corner of the subimage for later use.
        
        
        **Variables which are used**:
        
        :var dxs: x-camera-positions of each illuminated oversampled pixel in px
        :var dys: y-camera-positions of each illuminated oversampled pixel in px
        :var dwl: wavelength of each illuminated oversampled pixel in meters
        
        **Variables which are set**:
        
        :var txs: x-subimage-indicies of each illuminated oversampled pixel in o-px
        :var tys: y-subimage-indicies of each illuminated oversampled pixel in o-px
        :var tfl: flux of each pixel in counts
        :var twl: wavelength of each pixel in meters
        :var tdw: delta wavelength covered by each pixel in meters
        :var trs: sampling resolution of each pixel
        :var subshape: shape of subimage to contain spectrum
        :var subcorner: corner of subimage to contain spectrum in px
        :var spectrum: the spectrum object used for flux
        :var traced: bool True
        
        """
        
        if self.traced:
            return self.traced
            
        # Variables taken from the dispersion calculation
        points = np.array([self.dxs,self.dys]).T
        deltawl = np.diff(self.dwl)
        
        
        # Take our points out. Note from the above that we multiply by the density in order to do this
        xorig,yorig = (points * self.config["Instrument"]["density"])[:-1].T.astype(np.int)
        x,y = (points * self.config["Instrument"]["density"])[:-1].T.astype(np.int)
        # Get the way in which those points correspond to actual pixels.
        # As such, this array of points should have duplicates
        xint,yint = points.T.astype(np.int)
        
        # Zero-adjust our x and y points. They will go into a fake subimage anyways, so we don't care
        # for now where they would be on the real image
        x -= np.min(x)
        y -= np.min(y)
        
        # Get the approximate size of our spectra
        xdist = np.max(x)-np.min(x)
        ydist = np.max(y)-np.min(y)
        
        # Convert this size into an integer number of pixels for our subimage. This makes it
        # *much* easier to register our sub-image to the master, larger pixel image
        xdist += (self.config["Instrument"]["density"] - xdist % self.config["Instrument"]["density"])
        ydist += (self.config["Instrument"]["density"] - ydist % self.config["Instrument"]["density"])
        
        # Move our x and y coordinates to the middle of our sub image by applying padding below each one.
        x += self.config["Instrument"]["padding"] * self.config["Instrument"]["density"]
        y += self.config["Instrument"]["padding"] * self.config["Instrument"]["density"]
        
        # Find the first (by the flatten method) corner of the subimage,
        # useful for placing the sub-image into the full image.
        corner = np.array([ xint[np.argmax(x)], yint[np.argmin(y)]])
        self.log.debug("Corner Position in Integer Space: %s" % corner)
        corner *= self.config["Instrument"]["density"]
        realcorner = np.array([ xorig[np.argmax(x)], yorig[np.argmin(y)]])
        offset = corner - realcorner
        corner /= self.config["Instrument"]["density"]
        self.log.debug("Corner Position Offset in Dense Space: %s" % (offset))
        if self.log.getEffectiveLevel() <= logging.DEBUG:
            with open("%(Partials)s/Instrument-Offsets.dat" % self.config["Dirs"],'a') as handle:
                np.savetxt(handle,offset)
        corner -= np.array([-self.config["Instrument"]["padding"],self.config["Instrument"]["padding"]])
        
        x += offset[0]
        y += offset[1]
        
        # Create our sub-image, using the x and y width of the spectrum, plus 2 padding widths.
        # Padding is specified in full-size pixels to ensure that the final image is an integer
        # number of full-size pixels across.
        xsize = xdist+2*self.config["Instrument"]["padding"]*self.config["Instrument"]["density"]
        ysize = ydist+2*self.config["Instrument"]["padding"]*self.config["Instrument"]["density"]
        
        # Calculate the resolution inherent to the pixels asked for
        WLS = self.dwl
        DWL = np.diff(WLS) 
        WLS = WLS[:-1]
        RS = WLS/DWL
        
        
        # Call and evaluate the spectrum
        self.log.debug(npArrayInfo(WLS,"Calling Wavelength"))
        self.log.debug(npArrayInfo(RS,"Calling Resolution"))

        wl,flux = spectrum(wavelengths=WLS,resolution=RS) 
                
        self.log.debug(npArrayInfo(flux,"Final Flux"))
        self.log.debug(npArrayInfo(RS,"Saving Resolution"))
        
        self.txs = x
        self.tys = y
        self.tfl = flux
        self.twl = WLS
        self.tdw = DWL
        self.trs = RS
        self.subshape = (xsize,ysize)
        self.subcorner = corner
        self.spectrum = spectrum
        self.traced = True
        
        return self.traced
Esempio n. 5
0
    def find_dispersion(self):
        """Find the dispersion (dense, pixel aligned wavelength values) for this lenslet.
        
        To calculate dispersion, we first create an interpolation from (wavelength) -> (xpix) and (wavelength) -> (ypix). Then, using a large array of possible wavelengths (100x the number of oversampled pixel positions) we create arrays of possible overdense x and y pixel positions. These arrays are then truncated to contain only integer (overdense) pixel positions. We then calculate the arc-distance to each of these pixel positions from the start (lowest wavelength) of the spectrum. Taking only unique points along the arc-distance, we find a list of all of the unique x and y pixel positions which are illuminated by the spectrum in the over-dense sample space. This array, along with thier corresponding wavelengths and arc-distances, are stored for later use.
        
        **Variables which are used**:
        
        :var xcs: x-camera positions (center of image) in mm
        :var ycs: y-camera positions (center of image) in mm
        :var ls: wavelengths
        :var xpixs: x-camera positions (center of image) in px (integer)
        :var ypixs: y-camera positions (center of image) in px (integer)
        
        
        **Variables which are set**:
        
        :var dxs: x-camera-positions of each illuminated oversampled pixel in px (integer o-px)
        :var dys: y-camera-positions of each illuminated oversampled pixel in px (integer o-px)
        :var dwl: wavelength of each illuminated oversampled pixel in meters
        :var drs: arc-distance along spectrum in mm
        :var dis: array of ``[dxs,dys,dwl,drs]``
        :var dispersion: boolean True
        
        """
        assert self.valid(), "Lenslet must contain valid data."
        if self.dispersion:
            return self.dispersion
        
        if self.config["Instrument"]["Tel"]["ellipse"]:
            # Find ellipse major and minor axis from given data.
            self.a = np.sqrt((self.xcs - self.xas)**2.0 + (self.ycs-self.yas)**2.0) * self.config["Instrument"]["convert"]["mmtopx"] * self.config["Instrument"]["density"]
            self.b = np.sqrt((self.xcs - self.xbs)**2.0 + (self.ycs-self.ybs)**2.0) * self.config["Instrument"]["convert"]["mmtopx"] * self.config["Instrument"]["density"]
            top = self.xcs - self.xas
            bot = self.ycs - self.yas
            bot[np.logical_and(top == 0,bot == 0)] = 1.0
            self.alpha = np.arctan(top/bot)
            self.fa = np.poly1d(np.polyfit(self.ls, self.a, self.config["Instrument"]["Tel"]["dispfitorder"]))
            self.fb = np.poly1d(np.polyfit(self.ls, self.b, self.config["Instrument"]["Tel"]["dispfitorder"]))
            self.falpha = np.poly1d(np.polyfit(self.ls, self.alpha, self.config["Instrument"]["Tel"]["dispfitorder"]))
            

            
        
        # Interpolation to convert from wavelength to pixels.
        #   The accuracy of this interpolation is not important.
        #   Rather, it is used to find the pixels where the light will fall
        #   and is fed an array that is very dense, used on this dense interpolation
        #   and then binned back onto pixels. Thus it will be used to get a list
        #   of all illuminated pixels.
        fx = np.poly1d(np.polyfit(self.ls, self.xpixs, self.config["Instrument"]["dispfitorder"]))
        fy = np.poly1d(np.polyfit(self.ls, self.ypixs, self.config["Instrument"]["dispfitorder"]))
        
        # Find the starting and ending position of the spectra
        startix = np.argmin(self.ls)
        endix = np.argmax(self.ls)
        start = np.array([self.xcs[startix],self.ycs[startix]])
        end = np.array([self.xcs[endix],self.ycs[endix]])
        
        # Get the total length of the spectra
        distance = np.sqrt(np.sum(end-start)**2)
        
        # This should have been checked in the validity function.
        if distance == 0:
            raise SEDLimits
        
        # Find the length in units of (int) pixels
        npix = (distance * self.config["Instrument"]["convert"]["mmtopx"]).astype(np.int) * self.config["Instrument"]["density"]
        
        # Create a data array one hundred times as dense as the number of pixels
        #   This is the super dense array which will use the above interpolation
        superDense_lam = np.linspace(np.min(self.ls),np.max(self.ls),npix*100)
        
        # Interpolate along our really dense set of wavelengths to find all possible
        # illuminated pixel positions in this spectrum
        superDense_pts = np.array([fx(superDense_lam),fy(superDense_lam)])
        
        # Measure the distance along our really dense set of points
        superDense_interval = np.sqrt(np.sum(np.power(np.diff(superDense_pts,axis=1),2),axis=0))
        superDense_distance = np.cumsum(superDense_interval)
        
        # Adjust the density of our points. This rounds all values to only full pixel values.
        superDense_pts = np.round(superDense_pts * self.config["Instrument"]["density"]) / self.config["Instrument"]["density"]
        superDense_int = (superDense_pts * self.config["Instrument"]["density"]).astype(np.int)
        
        # We can identify unique points using the points when the integer position ratchets up or down.
        unique_x,unique_y = np.diff(superDense_int).astype(np.bool)
        
        # We want unique index to include points where either 'y' or 'x' ratchets up or down
        unique_idx = np.logical_or(unique_x,unique_y)
        
        # Remove any duplicate points. This does not do so in order, so we must
        # sort the array of unique points afterwards...
        unique_pts = superDense_pts[:,1:][:,unique_idx]
        
        # An array of distances to the origin of this spectrum, can be used to find wavelength
        # of light at each distance
        distance = superDense_distance[unique_idx] * self.config["Instrument"]["convert"]["pxtomm"]
        
        # Re sort everything by distnace along the trace.
        # Strictly, this shouldn't be necessary if all of the above functions preserved order.
        sorted_idx = np.argsort(distance)
        
        # Pull out sorted valuses
        distance = distance[sorted_idx]
        points = unique_pts[:,sorted_idx].T
        self.log.debug(npArrayInfo(points,"Points"))
            
        
        # Pull out the original wavelengths
        wl_orig = superDense_lam[unique_idx][sorted_idx]
        wl = wl_orig
        self.log.debug(npArrayInfo(wl,"Wavelengths"))
        # We are getting some odd behavior, where the dispersion function seems to not cover the whole
        # arc length and instead covers only part of it. This causes much of our arc to leave the desired
        # and available wavelength boundaries. As such, I'm disabling the more accurate dispersion mode.
        
        # Convert to wavelength space along the dispersion spline.
        # wl = self.spline(distance)
        xs,ys = points.T
        self.dxs = xs
        self.dys = ys
        self.dwl = wl
        self.drs = distance
        self.dis = np.array([xs,ys,wl,distance])
        self.dispersion = True
        
        return self.dispersion
Esempio n. 6
0
    def valid(self,strict=True):
        """Returns true if this is a valid lenslet, false if it fails any of the tests.
        
        :param strict: In not strict mode, validator will let many lenslets which are not well-formed through the system.
        :returns: bool
        
        Checks performed:
        
        - Wavelengths and Points all have the same number of entries.
        - We have at least three entries.
        - No entry's pixel position is exactly 0.
        - The dispersion distance along the x-axis is less than 30 pixels.
        - The distance between the start and end of the spectrum is non-zero.
        - The lenslet positions are not within some configured tolerance of the edge of the spectrum.
        - Warning about the units for wavelengths.
        
        
        
        """
        if self.checked:
            return self.passed
        
        self.checked = True
        self.passed = False
        
        # Data consistency
        if len(self.points) != len(self.ps) or len(self.points) != len(self.ls) or len(self.points) != len(self.pixs):
            self.log.warning("Lenslet %d failed: The data had inconsistent points" % self.num)
            return self.passed
        
        # Data utility
        if len(self.points) < 3:
            self.log.debug("Lenslet %d failed: There were fewer than three data points" % self.num)
            if strict:
                return self.passed
        if np.any(self.pixs.flatten == 0):
            self.log.debug("Lenslet %d failed: Some (x,y) were exactly zero" % self.num)
            if strict:
                return self.passed
        
        # X distance calculation (all spectra should be roughly constant in x, as they are fairly well aligned)
        # NOTE: There really isn't a whole lot to this requriement
        dist = 30
        if np.any(np.abs(np.diff(self.xpixs)) > dist):
            self.log.debug("Lenslet %d failed: x distance was more than %d" % (self.num,dist))
            if strict:
                return self.passed
        
        # The spectrum should span some finite distance
        startix = np.argmin(self.ls)
        endix = np.argmax(self.ls)
        start = np.array([self.xcs[startix],self.ycs[startix]])
        end = np.array([self.xcs[endix],self.ycs[endix]])

        # Get the total length of the spectra
        self.distance = np.sqrt(np.sum(end-start)**2)
        
        if self.distance == 0:
            self.log.debug("Lenslet %d failed: The points have no separating distance" % self.num)
            return self.passed
        
        # Find the xs and ys that are not within 0.1 mm of the edge of the detector...
        padding = self.config["Instrument"]["image"]["pad"]["mm"]
        if not ((self.xcs > 0.1) & (self.xcs < self.config["Instrument"]["image"]["size"]["mm"]-padding) & (self.ycs > padding) & (self.ycs < self.config["Instrument"]["image"]["size"]["mm"]-padding)).any():
            self.log.debug("Lenslet %d failed: The points are too close to the image edge" % self.num)
            if strict:
                return self.passed
        
        self.passed = True
        
        # Warnings about our data go here.
        if np.any(self.ls < 1e-12) or np.any(self.ls > 1e-3):
            self.log.warning("The wavelengths provided for lenslet %d appear as if they aren't SI units." % self.num)
            self.log.debug(npArrayInfo(self.ls,"Lenslet %d Wavelengths" % self.num))
                
        return self.passed