Esempio n. 1
0
    def __init__(self, height, num_lenslet, pixels_lenslet, atmosphere, telescope):
        """
        :param height: Conjugated h/eight of the WFS [meters]
        :param num_lenslet: Number of lenslet spanning one side of pupil [int]
        :param pixels_lenslet: Number of detector pixels per lenslet [int]
        :param delta: Size of detector pixel [meters]
        :param atmosphere: Atmosphere object
        """

        # WFS attributes
        ### by default, WFS spans size of metapupil
        self.conjugated_height = height  # [meters]
        self.num_lenslet = num_lenslet  # [int]
        self.pixels_lenslet = pixels_lenslet  # [int]
        self.conjugated_size = telescope.pupil_diameter + telescope.field_of_view * height  # [meters]

        # Lenslet attributes (Conjugated)
        self.conjugated_lenslet_size = self.conjugated_size / float(num_lenslet)  # [meters]

        # Lenslet attributes (Physical)
        ### For physical sanity check only. Not needed for simulation
        self.lenslet_size = self.conjugated_lenslet_size / float(telescope.Mfactor)  # [meters]

        # Detector attributes
        ### Angular resolution valid for small angles only
        self.angular_res = telescope.field_of_view / pixels_lenslet  # [rad/pixel]
        self.conjugated_delta = self.conjugated_size / float(num_lenslet) / float(pixels_lenslet)  # [meters]

        # Relevant objects
        self.atmos = atmosphere
        self.tel = telescope

        # Experimental algorithms
        ### Strategy pattern for custom ImageSimulation / ImageInterpretor algorithm
        self.ImgSimulator = ImageSimulator(atmosphere, telescope, self)
        self.ImgInterpreter = ImageInterpreter(self.ImgSimulator)
Esempio n. 2
0
class WideFieldSHWFS(object):
    def __init__(self, height, num_lenslet, pixels_lenslet, atmosphere, telescope):
        """
        :param height: Conjugated h/eight of the WFS [meters]
        :param num_lenslet: Number of lenslet spanning one side of pupil [int]
        :param pixels_lenslet: Number of detector pixels per lenslet [int]
        :param delta: Size of detector pixel [meters]
        :param atmosphere: Atmosphere object
        """

        # WFS attributes
        ### by default, WFS spans size of metapupil
        self.conjugated_height = height  # [meters]
        self.num_lenslet = num_lenslet  # [int]
        self.pixels_lenslet = pixels_lenslet  # [int]
        self.conjugated_size = telescope.pupil_diameter + telescope.field_of_view * height  # [meters]

        # Lenslet attributes (Conjugated)
        self.conjugated_lenslet_size = self.conjugated_size / float(num_lenslet)  # [meters]

        # Lenslet attributes (Physical)
        ### For physical sanity check only. Not needed for simulation
        self.lenslet_size = self.conjugated_lenslet_size / float(telescope.Mfactor)  # [meters]

        # Detector attributes
        ### Angular resolution valid for small angles only
        self.angular_res = telescope.field_of_view / pixels_lenslet  # [rad/pixel]
        self.conjugated_delta = self.conjugated_size / float(num_lenslet) / float(pixels_lenslet)  # [meters]

        # Relevant objects
        self.atmos = atmosphere
        self.tel = telescope

        # Experimental algorithms
        ### Strategy pattern for custom ImageSimulation / ImageInterpretor algorithm
        self.ImgSimulator = ImageSimulator(atmosphere, telescope, self)
        self.ImgInterpreter = ImageInterpreter(self.ImgSimulator)

    def print_attributes(self):
        print "WFS ATTRIBUTES"
        print "Conjugated height:\t" + str(self.conjugated_height)
        print "Number of lenslets:\t" + str(self.num_lenslet)
        print "Number of pixels per lenslet:\t" + str(self.pixels_lenslet)
        print "Size of conjugated WFS:\t" + str(self.conjugated_size)

        print ""

        print "LENSLET ATTRIBUTES"
        print "Size of conjugate lenslet:\t" + str(self.conjugated_lenslet_size)
        print "Size of physical lenslet:\t" + str(self.lenslet_size)

        print ""

        print "DETECTOR ATTRIBUTES"
        print "Angular resolution of pixel:\t" + str(self.angular_res)
        print "Size of conjugate pixel:\t" + str(self.conjugated_delta)

    def _reconstruct_WF(self, all_shifts):
        # TODO: should this function accept shifts or tilts?
        # ans: if it accept tilts, this will be a static function
        """
        Reconstruct the WF surface from the shifts sensed by WFS.

        Usage: Many reconstruction algorithm exists. They have been packaged in a class.
         Change the choice of algorithm under the wrapper of this function
        :param all_shifts: (xShift, yShift) ndarray # [pixel]
        :return: # [radian ndarray]
        """
        tilts = self._shifts_to_tilts(all_shifts)
        slopes = self._tilts_to_gradient(tilts)

        # Extract xSlope and ySlope from nested ndarray
        Vtake = np.vectorize(np.take)
        xSlopes = Vtake(slopes, [0], axis=0)  # [radians/meter]
        ySlopes = Vtake(slopes, [1], axis=0)  # [radians/meter]
        surface = ReconMethods.LeastSquare(xSlopes, ySlopes)

        return surface

    def _shifts_to_tilts(self, shifts):
        # Linear approximation
        return shifts * self.angular_res

    def _tilts_to_gradient(self, tilts):
        """
        Converts tilts [radians] to gradient [radians/meter]
        Function is numpy-aware
        :param tilts: [radians]
        :return: gradient [radians/meter]
        """
        # numpy awareness
        if tilts.dtype == np.ndarray:
            Vtan = np.vectorize(np.tan, otypes=[np.ndarray])
        else:
            Vtan = np.tan

        return Vtan(tilts)

    def _get_metascreen(self, scrn):
        """
        Returns the portion of phase screen that WFS senses.

        Differs from metapupil when WFS not the same size as metapupil

        Refer to diagram to understand this implementation
        :param scrn: the Screen object whose metascreen we are finding
        :return: portion of screen # [radian ndarray]
        """

        # basis parameters
        FoV = self.tel.field_of_view
        theta = FoV / 2.0
        radius = self.conjugated_size / 2.0

        # find meta radius
        # meta radius: half length of metascreen

        if scrn.height > self.conjugated_height:
            meta_radius = radius + (scrn.height - self.conjugated_height) * np.tan(theta)
        else:
            threshold_height = (
                                   (radius - self.tel.pupil_diameter / 2.0) / np.tan(
                                       theta) + self.tel.pupil_diameter) / 2.0

            if scrn.height > threshold_height:
                meta_radius = radius + abs(self.conjugated_height - scrn.height) * np.tan(theta)
            else:
                meta_radius = radius + scrn.height * np.tan(theta)

        # convert to array indices
        x_mid = scrn.phase_screen.shape[0] / 2.0  # [index]
        y_mid = scrn.phase_screen.shape[1] / 2.0  # [index]

        # convert lenslets size to phase screen indices; constant for all directions
        sizeX = int(meta_radius / scrn.delta)  # [index]
        sizeY = int(meta_radius / scrn.delta)  # [index]

        # frame to capture
        x1 = int(x_mid) - sizeX
        x2 = int(x_mid) + sizeX
        y1 = int(y_mid) - sizeY
        y2 = int(y_mid) + sizeY

        return scrn.phase_screen[y1:y2, x1:x2]

    def _get_meta_pupil(self, scrn):
        """
        Returns the portion of phase screen in field of view of telescope.

        :param scrn: Screen object
        :return: sub phase screen [ndarray] [radian]
        """
        size_of_WFS = self.num_lenslet * self.pixels_lenslet * self.conjugated_delta
        num_scrn_pixels = size_of_WFS / scrn.delta
        shapeX, shapeY = scrn.phase_screen.shape
        x1 = int(shapeX / 2.0 - num_scrn_pixels / 2.0)

        return scrn.phase_screen[x1:x1 + num_scrn_pixels, x1:x1 + num_scrn_pixels]

    def set_size(self, c_size):
        """
        Sets size of WFS.
        WFS is centered in metapupil

        Context:
            By default the WFS size is set to that of the metapupil
        :param c_size: conjugated size of WFS
        """
        self.conjugated_size = c_size  # [meters]
        self.conjugated_lenslet_size = self.conjugated_size / float(self.num_lenslet)  # [meters]
        self.lenslet_size = self.conjugated_lenslet_size / float(self.tel.Mfactor)  # [meters]
        self.conjugated_delta = self.conjugated_size / float(self.num_lenslet) / float(self.pixels_lenslet)  # [meters]

    def runWFS(self):
        """
        Observes the atmosphere and sense the image shifts in all lenslets

        Design:
            The simulation of lenslet images and the deduction of image shift values from those images
            are delegated to two classes --- ImageSimulator and Image Interpreter. The only information
            passed between them should only be the lenslet images.

            The use of template and strategy patterns
            is intended to allow easy modifications to algorithms without disruption AO simulation setup.

            This function is intended to be the main function from which other AO components read WFS outputs
        """

        all_dimg = self.ImgSimulator.all_dimg()
        all_shifts = self.ImgInterpreter.all_dimg_to_shifts(all_dimg)

        return all_shifts