def configure(self,
                  color=[1, 1, 1, 1],
                  cylinder_radius=0.5,
                  cylinder_height=0.5,
                  n_faces=16,
                  cylinder_locations=[[+5, 0, 0]]):
        """
        Collection of tower objects created with a single shader program
        """
        self.color = color
        self.cylinder_radius = cylinder_radius
        self.cylinder_height = cylinder_height
        self.cylinder_locations = cylinder_locations
        self.n_faces = n_faces

        self.stim_object = GlVertices()

        # This step is slow. Make template once then use .translate() on copies to make cylinders
        cylinder = GlCylinder(cylinder_height=self.cylinder_height,
                              cylinder_radius=self.cylinder_radius,
                              cylinder_location=[0, 0, 0],
                              color=self.color,
                              n_faces=self.n_faces)

        for tree_loc in self.cylinder_locations:
            new_cyl = copy.copy(cylinder).translate(tree_loc)
            self.stim_object.add(new_cyl)
    def configure(self,
                  color=[1, 0, 0, 1],
                  cylinder_radius=0.5,
                  cylinder_height=0.5,
                  cylinder_location=[+5, 0, 0],
                  n_faces=16):
        """
        Cylindrical tower object in arbitrary x, y, z coords
        :param color: [r,g,b,a] color of cylinder. Applied to entire texture, which is monochrome
        :param cylinder_radius: meters
        :param cylinder_height: meters
        :param cylinder_location: [x, y, z] location of the center of the cylinder, meters
        :param n_faces: number of quad faces to make the cylinder out of

        """
        self.color = color
        self.cylinder_radius = cylinder_radius
        self.cylinder_height = cylinder_height
        self.cylinder_location = cylinder_location
        self.n_faces = n_faces

        self.stim_object = GlCylinder(cylinder_height=self.cylinder_height,
                                      cylinder_radius=self.cylinder_radius,
                                      cylinder_location=self.cylinder_location,
                                      color=self.color,
                                      n_faces=self.n_faces)
    def configure(self,
                  period=20,
                  mean=0.5,
                  contrast=1.0,
                  offset=0.0,
                  profile='sine',
                  color=[1, 1, 1, 1],
                  cylinder_radius=1,
                  cylinder_height=10,
                  theta=0,
                  phi=0,
                  angle=0.0):
        """
        Grating texture painted on a cylinder

        :param period: spatial period, degrees
        :param mean: mean intensity of grating texture
        :param contrast: Weber contrast of grating texture
        :param offset: phase offset of grating texture, degrees
        :param profile: 'sine' or 'square'; spatial profile of grating texture

        :params color, cylinder_radius, cylinder_height, theta, phi, angle: see parent class
        *Any of these params except cylinder_radius, cylinder_height and profile can be passed as a trajectory dict to vary as a function of time
        """
        super().configure(color=color,
                          cylinder_radius=cylinder_radius,
                          cylinder_height=cylinder_height,
                          theta=theta,
                          phi=phi,
                          angle=angle)

        self.period = period
        self.mean = mean
        self.contrast = contrast
        self.offset = offset
        self.profile = profile
        self.period = period

        # Only renders part of the cylinder if the period is not a divisor of 360
        n_cycles = np.floor(360 / self.period)
        self.cylinder_angular_extent = n_cycles * self.period

        self.stim_object = GlCylinder(
            cylinder_height=self.cylinder_height,
            cylinder_radius=self.cylinder_radius,
            cylinder_angular_extent=self.cylinder_angular_extent,
            color=[1, 1, 1, 1],
            texture=True).rotate(np.radians(self.theta), np.radians(self.phi),
                                 np.radians(self.angle))

        if np.any([type(x) == dict for x in [mean, contrast, offset]]):
            pass
        else:
            self.updateTexture(self.mean, self.contrast, self.offset)
    def configure(self,
                  rate=10,
                  period=20,
                  mean=0.5,
                  contrast=1.0,
                  offset=0.0,
                  profile='square',
                  color=[1, 1, 1, 1],
                  alpha_by_face=None,
                  cylinder_radius=1,
                  cylinder_height=10,
                  theta=0,
                  phi=0,
                  angle=0):
        """
        Subclass of CylindricalGrating that rotates the grating along the varying axis of the grating
        Note that the rotation effect is achieved by translating the texture on a semi-cylinder. This
        allows for arbitrary spatial periods to be achieved with no discontinuities in the grating

        :param rate: rotation rate, degrees/sec
        :other params: see CylindricalGrating, TexturedCylinder
        """
        super().configure(period=period,
                          mean=mean,
                          contrast=contrast,
                          offset=offset,
                          profile=profile,
                          color=color,
                          cylinder_radius=cylinder_radius,
                          cylinder_height=cylinder_height,
                          theta=theta,
                          phi=phi,
                          angle=angle)
        self.rate = rate
        self.alpha_by_face = alpha_by_face
        if self.alpha_by_face is None:
            self.n_faces = 32
        else:
            self.n_faces = len(self.alpha_by_face)
        self.updateTexture(mean=mean, contrast=contrast, offset=offset)

        self.stim_object_template = GlCylinder(
            cylinder_height=self.cylinder_height,
            cylinder_radius=self.cylinder_radius,
            cylinder_angular_extent=self.cylinder_angular_extent,
            color=self.color,
            alpha_by_face=self.alpha_by_face,
            n_faces=self.n_faces,
            texture=True)
    def configure(self,
                  color=[1, 1, 1, 1],
                  cylinder_radius=5,
                  cylinder_height=5,
                  image_path=None):
        super().configure(color=color,
                          cylinder_radius=cylinder_radius,
                          cylinder_height=cylinder_height,
                          theta=0,
                          phi=0,
                          angle=0.0)

        if image_path is None:
            load_image = False
        elif os.path.isfile(image_path):
            load_image = True
        else:
            load_image = False

        if load_image:
            with open(image_path, 'rb') as handle:
                s = handle.read()
            arr = array.array('H', s)
            arr.byteswap()
            img = np.array(arr, dtype='uint16').reshape(1024, 1536)
            img = np.uint8(255 * (img / np.max(img)))
            img = img[:, :1024]

        else:
            # use a dummy texture
            np.random.seed(0)
            face_colors = np.random.uniform(size=(128, 128))
            img = (255 * face_colors).astype(np.uint8)

        self.texture_interpolation = 'LINEAR'
        self.texture_image = img

        self.stim_template = GlCylinder(cylinder_height=self.cylinder_height,
                                        cylinder_radius=self.cylinder_radius,
                                        cylinder_location=(0, 0, 0),
                                        color=self.color,
                                        texture=True).rotz(np.radians(180))
    def configure(self,
                  patch_width=4,
                  patch_height=4,
                  cylinder_vertical_extent=160,
                  cylinder_angular_extent=360,
                  color=[1, 1, 1, 1],
                  cylinder_radius=1,
                  theta=0,
                  phi=0,
                  angle=0.0):
        """
        Periodic checkerboard pattern painted on the inside of a cylinder

        :param patch width: Azimuth extent (degrees) of each patch
        :param patch height: Elevation extent (degrees) of each patch
        :param cylinder_vertical_extent: Elevation extent of the entire cylinder (degrees)
        :param cylinder_angular_extent: Azimuth extent of the cylinder texture (degrees)

        :other params: see TexturedCylinder
        """

        # Only renders part of the cylinder if the period is not a divisor of cylinder_angular_extent
        self.n_patches_width = int(
            np.floor(cylinder_angular_extent / patch_width))
        self.cylinder_angular_extent = self.n_patches_width * patch_width

        # assuming fly is at (0,0,0), calculate cylinder height required to achieve (approx.) vert_extent (degrees)
        # actual vert. extent is based on floor-nearest integer number of patch heights
        assert cylinder_vertical_extent < 180
        self.n_patches_height = int(
            np.floor(cylinder_vertical_extent / patch_height))
        patch_height_m = cylinder_radius * np.tan(
            np.radians(patch_height))  # in meters
        cylinder_height = self.n_patches_height * patch_height_m

        super().configure(color=color,
                          angle=angle,
                          cylinder_radius=cylinder_radius,
                          cylinder_height=cylinder_height)

        self.patch_width = patch_width
        self.patch_height = patch_height

        # Only renders part of the cylinder if the period is not a divisor of 360
        self.n_patches_width = int(np.floor(360 / self.patch_width))
        self.cylinder_angular_extent = self.n_patches_width * self.patch_width
        self.patch_height_m = self.cylinder_radius * np.tan(
            np.radians(self.patch_height))  # in meters
        self.n_patches_height = int(
            np.floor(self.cylinder_height / self.patch_height_m))

        # create the texture
        face_colors = np.zeros((self.n_patches_height, self.n_patches_width))
        face_colors[0::2, 0::2] = 1
        face_colors[1::2, 1::2] = 1

        # make and apply the texture
        img = (255 * face_colors).astype(np.uint8)
        self.texture_interpolation = 'NEAREST'
        self.texture_image = img

        self.stim_object = GlCylinder(
            cylinder_height=self.cylinder_height,
            cylinder_radius=self.cylinder_radius,
            cylinder_angular_extent=self.cylinder_angular_extent,
            color=self.color,
            texture=True).rotate(np.radians(self.theta), np.radians(self.phi),
                                 np.radians(self.angle))
    def configure(self,
                  patch_width=10,
                  patch_height=10,
                  cylinder_vertical_extent=160,
                  cylinder_angular_extent=360,
                  distribution_data=None,
                  update_rate=60.0,
                  start_seed=0,
                  color=[1, 1, 1, 1],
                  cylinder_radius=1,
                  theta=0,
                  phi=0,
                  angle=0.0):
        """
        Random square grid pattern painted on the inside of a cylinder

        :param patch width: Azimuth extent (degrees) of each patch
        :param patch height: Elevation extent (degrees) of each patch
        :param cylinder_vertical_extent: Elevation extent of the entire cylinder (degrees)
        :param cylinder_angular_extent: Azimuth extent of the cylinder texture (degrees)
        :param distribution_data: dict. containing name and args/kwargs for random distribution (see flystim.distribution)
        :param update_rate: Hz, update rate of bar intensity
        :param start_seed: seed with which to start rng at the beginning of the stimulus presentation

        :other params: see TexturedCylinder
        """

        # Only renders part of the cylinder if the period is not a divisor of cylinder_angular_extent
        self.n_patches_width = int(
            np.floor(cylinder_angular_extent / patch_width))
        self.cylinder_angular_extent = self.n_patches_width * patch_width

        # assuming fly is at (0,0,0), calculate cylinder height required to achieve (approx.) vert_extent (degrees)
        # actual vert. extent is based on floor-nearest integer number of patch heights
        assert cylinder_vertical_extent < 180
        self.n_patches_height = int(
            np.floor(cylinder_vertical_extent / patch_height))
        patch_height_m = cylinder_radius * np.tan(
            np.radians(patch_height))  # in meters
        cylinder_height = self.n_patches_height * patch_height_m

        super().configure(color=color,
                          angle=angle,
                          cylinder_radius=cylinder_radius,
                          cylinder_height=cylinder_height,
                          theta=theta,
                          phi=phi)

        # get the noise distribution
        if distribution_data is None:
            distribution_data = {
                'name': 'Uniform',
                'args': [0, 1],
                'kwargs': {}
            }
        self.noise_distribution = getattr(
            distribution,
            distribution_data['name'])(*distribution_data.get('args', []),
                                       **distribution_data.get('kwargs', {}))

        self.patch_width = patch_width
        self.patch_height = patch_height
        self.start_seed = start_seed
        self.update_rate = update_rate

        self.stim_object = GlCylinder(
            cylinder_height=self.cylinder_height,
            cylinder_radius=self.cylinder_radius,
            cylinder_angular_extent=self.cylinder_angular_extent,
            color=self.color,
            texture=True).rotate(np.radians(self.theta), np.radians(self.phi),
                                 np.radians(self.angle))
    def configure(self,
                  period=20,
                  width=5,
                  vert_extent=80,
                  theta_offset=0,
                  background=0.5,
                  distribution_data=None,
                  update_rate=60.0,
                  start_seed=0,
                  color=[1, 1, 1, 1],
                  cylinder_radius=1,
                  theta=0,
                  phi=0,
                  angle=0.0,
                  cylinder_location=(0, 0, 0)):
        """
        Periodic bars of randomized intensity painted on the inside of a cylinder
        :param period: spatial period (degrees) of bar locations
        :param width: width (degrees) of each bar
        :param vert_extent: vertical extent (degrees) of bars
        :param theta_offset: offset of periodic bar pattern (degrees)
        :param background: intensity (mono) of texture background, where no bars appear
        :param distribution_data: dict. containing name and args/kwargs for random distribution (see flystim.distribution)
        :param update_rate: Hz, update rate of bar intensity
        :param start_seed: seed with which to start rng at the beginning of the stimulus presentation

        :other params: see TexturedCylinder
        """
        # assuming fly is at (0,0,0), calculate cylinder height required to achieve vert_extent (degrees)
        # tan(vert_extent/2) = (cylinder_height/2) / cylinder_radius
        assert vert_extent < 180
        cylinder_height = 2 * cylinder_radius * np.tan(
            np.radians(vert_extent / 2))
        super().configure(color=color,
                          cylinder_radius=cylinder_radius,
                          cylinder_height=cylinder_height,
                          theta=theta,
                          phi=phi,
                          angle=angle)

        # get the noise distribution
        if distribution_data is None:
            distribution_data = {
                'name': 'Uniform',
                'args': [0, 1],
                'kwargs': {}
            }
        self.noise_distribution = getattr(
            distribution,
            distribution_data['name'])(*distribution_data.get('args', []),
                                       **distribution_data.get('kwargs', {}))

        self.period = period
        self.width = width
        self.vert_extent = vert_extent
        self.theta_offset = theta_offset
        self.background = background
        self.update_rate = update_rate
        self.start_seed = start_seed
        self.cylinder_location = cylinder_location

        # Only renders part of the cylinder if the period is not a divisor of 360
        self.n_bars = int(np.floor(360 / self.period))
        self.cylinder_angular_extent = self.n_bars * self.period  # degrees

        self.stim_object_template = GlCylinder(
            cylinder_height=self.cylinder_height,
            cylinder_radius=self.cylinder_radius,
            cylinder_angular_extent=self.cylinder_angular_extent,
            color=self.color,
            cylinder_location=self.cylinder_location,
            texture=True)