Module pyaurorax.tools

Data analysis toolkit for working with all-sky imager data available within the AuroraX platform.

This portion of the PyAuroraX library allows you to easily generate basic plots for ASI data, and common manipulations. These include things like displaying single images, making keograms, projecting ASI data onto maps, and extracting metrics for a given lat/lon bounding box.

Example

For shorter function calls, you can initialize the tools submodule using like so:

import pyaurorax
aurorax = pyaurorax.PyAuroraX()
at = aurorax.tools

Sub-modules

pyaurorax.tools.bounding_box

Methods for working with data in a specific bounding box.

pyaurorax.tools.calibration

Perform various calibration procedures on image data.

pyaurorax.tools.ccd_contour

Obtain contours in pixel coordinates from a skymap for plotting over CCD images.

pyaurorax.tools.classes

Class definitions for data analysis objects.

pyaurorax.tools.fov

Obtain FoVs of ASIs and create plots.

pyaurorax.tools.grid_files

Prepare grid data for plotting.

pyaurorax.tools.keogram

Generate keograms.

pyaurorax.tools.montage

Create montages.

pyaurorax.tools.mosaic

Prepare data and create mosaics.

pyaurorax.tools.spectra

Work with spectrograph data.

Classes

class FOV (cartopy_projection: cartopy.crs.Projection,
fov_data: List[FOVData] | None = None,
contour_data: Dict[str, List[Any]] | None = None)
Expand source code
@dataclass
class FOV:
    """
    Class representation for a FoV map.

    Attributes:

        cartopy_projection (cartopy.crs.Projection): 
            Cartopy projection to utilize.

        fov_data (FOVData or list of FOVData objects): 
            FoV contours included in FOV object.

        contour_data (Dict[str, List[Any]]): 
            Other contour data included in FOV object.

    """
    cartopy_projection: Projection
    fov_data: Optional[List[FOVData]] = None
    contour_data: Optional[Dict[str, List[Any]]] = None

    def __str__(self) -> str:
        return self.__repr__()

    def __repr__(self) -> str:

        if self.fov_data is not None:

            n_fovs = 0
            for fov in self.fov_data:
                n_fovs += len(fov.fovs.keys())

            if self.contour_data is not None:
                return "FOV(cartopy_projection=Projection(%s), %d FOVData Object(s), %d Contour(s))" % (
                    self.cartopy_projection.to_string(),
                    n_fovs,
                    len(self.contour_data.get("x", [])),
                )
            else:
                return "FOV(cartopy_projection=Projection(%s), %d FOVData Object(s))" % (
                    self.cartopy_projection.to_string(),
                    n_fovs,
                )
        else:

            if self.contour_data is not None:
                return "FOV(cartopy_projection=Projection(%s), %d Contour(s))" % (
                    self.cartopy_projection.to_string(),
                    len(self.contour_data.get("x", [])),
                )
            else:
                return "FOV(cartopy_projection=Projection(%s))" % (self.cartopy_projection.to_string())

    def pretty_print(self):
        """
        A special print output for this class.
        """
        # set special strings
        cartopy_projection_str = "Projection(%s)" % (self.cartopy_projection.to_string())

        if self.contour_data is not None:
            contour_data_str = "%d Contour(s)" % (len(self.contour_data.get("x", [])), )
        else:
            contour_data_str = "None"

        if self.fov_data is not None:
            n_fovs = 0

            for fov in self.fov_data:
                n_fovs += len(fov.fovs.keys())

            fov_data_str = "%d FoVData Object(s)" % (n_fovs)
        else:
            fov_data_str = "None"

        # print
        print("FOV:")
        print("  %-23s: %s" % ("cartopy_projection", cartopy_projection_str))
        print("  %-23s: %s" % ("fov_data", fov_data_str))
        print("  %-23s: %s" % ("contour_data", contour_data_str))

    def plot(self,
             map_extent: Sequence[Union[float, int]],
             label: bool = True,
             enforce_data_availability: bool = False,
             figsize: Optional[Tuple[int, int]] = None,
             title: Optional[str] = None,
             ocean_color: Optional[str] = None,
             land_color: str = "gray",
             land_edgecolor: str = "#8A8A8A",
             borders_color: str = "#AEAEAE",
             borders_disable: bool = False,
             returnfig: bool = False,
             savefig: bool = False,
             savefig_filename: Optional[str] = None,
             savefig_quality: Optional[int] = None) -> Any:
        """
        Generate a plot of the FoV data. 
        
        Either display it (default behaviour), save it to disk (using the `savefig` parameter), or 
        return the matplotlib plot object for further usage (using the `returnfig` parameter).

        Args:
            map_extent (List[int]): 
                Latitude/longitude range to be visible on the rendered map. This is a list of 4 integers 
                and/or floats, in the order of [min_lon, max_lon, min_lat, max_lat].

            label (bool): 
                Specifies wether individual FoVs will be labelled with their site_uid.
                
            enforce_data_availability (bool): 
                Specifies whether or not data availability information associated with the FOV object
                should be used to omit sites with no available data. Note that data availability information
                is typically created using the FOVData.add_availability method.

            figsize (tuple): 
                The matplotlib figure size to use when plotting. For example `figsize=(14,4)`.

            title (str): 
                The title to display above the plotted Fov Map. Default is no title.

            ocean_color (str): 
                Colour of the ocean. Default is cartopy's default shade of blue. Colours can be supplied
                as a word, or hexcode prefixed with a '#' character (ie. `#55AADD`).
            
            land_color (str): 
                Colour of the land. Default is `gray`. Colours can be supplied as a word, or hexcode 
                prefixed with a '#' character (ie. `#41BB87`).

            land_edgecolor (str): 
                Color of the land edges. Default is `#8A8A8A`. Colours can be supplied as a word, or
                hexcode prefixed with a '#' character.

            borders_color (str): 
                Color of the country borders. Default is `AEAEAE`. Colours can be supplied as a word, or
                hexcode prefixed with a '#' character.
            
            borders_disable (bool): 
                Disbale rendering of the borders. Default is `False`.

            returnfig (bool): 
                Instead of displaying the image, return the matplotlib figure object. This allows for further plot 
                manipulation, for example, adding labels or a title in a different location than the default. 
                
                Remember - if this parameter is supplied, be sure that you close your plot after finishing work 
                with it. This can be achieved by doing `plt.close(fig)`. 
                
                Note that this method cannot be used in combination with `savefig`.

            savefig (bool): 
                Save the displayed image to disk instead of displaying it. The parameter savefig_filename is required if 
                this parameter is set to True. Defaults to `False`.

            savefig_filename (str): 
                Filename to save the image to. Must be specified if the savefig parameter is set to True.

            savefig_quality (int): 
                Quality level of the saved image. This can be specified if the savefig_filename is a JPG image. If it
                is a PNG, quality is ignored. Default quality level for JPGs is matplotlib/Pillow's default of 75%.

        Returns:
            The displayed fov map, by default. If `savefig` is set to True, nothing will be returned. If `returnfig` is 
            set to True, the plotting variables `(fig, ax)` will be returned.

        Raises:
        """

        # check return mode
        if (returnfig is True and savefig is True):
            raise ValueError("Only one of returnfig or savefig can be set to True")
        if returnfig is True and (savefig_filename is not None or savefig_quality is not None):
            show_warning("The figure will be returned, but a savefig option parameter was supplied. Consider " +
                         "removing the savefig option parameter(s) as they will be ignored.",
                         stacklevel=1)
        elif (savefig is False and (savefig_filename is not None or savefig_quality is not None)):
            show_warning("A savefig option parameter was supplied, but the savefig parameter is False. The " +
                         "savefig option parameters will be ignored.",
                         stacklevel=1)

        # Initialize figure
        fig = plt.figure(figsize=figsize)
        ax = fig.add_axes((0, 0, 1, 1), projection=self.cartopy_projection)
        ax.set_extent(map_extent, crs=cartopy.crs.Geodetic())  # type: ignore

        # Add ocean
        #
        # NOTE: we use the default ocean color
        if (ocean_color is not None):
            ax.add_feature(  # type: ignore
                cartopy.feature.OCEAN, facecolor=ocean_color, zorder=0)
        else:
            ax.add_feature(cartopy.feature.OCEAN, zorder=0)  # type: ignore

        # add land
        ax.add_feature(  # type: ignore
            cartopy.feature.LAND, facecolor=land_color, edgecolor=land_edgecolor, zorder=0)

        # add borders
        if (borders_disable is False):
            ax.add_feature(  # type: ignore
                cartopy.feature.BORDERS, edgecolor=borders_color, zorder=0)

        # Go through and plot all of the FoVs within each of the Object's FOVData objects
        if self.fov_data is not None:

            # If requesting enforcement of data availability, check that data availability exists in
            # the object first
            if (enforce_data_availability is True):
                for fov_data in self.fov_data:
                    if (fov_data.data_availability is None):
                        raise ValueError("Before plotting FOV object with enforce_data_availability=True, FOVData.add_availability(...) " +
                                         "must be called for all included FOVData objects.")

            for fov_data in self.fov_data:
                latlon_dict = fov_data.fovs

                for site in fov_data.site_uid_list:

                    # If data_availability has been enforced and is False for this site, don't plot it
                    if (enforce_data_availability is True) and (fov_data.data_availability is not None) and (site
                                                                                                             in fov_data.data_availability.keys()):
                        if (fov_data.data_availability[site] is False):
                            continue

                    latlon = latlon_dict[site]

                    ax.plot(np.squeeze(latlon[1, :]),
                            np.squeeze(latlon[0, :]),
                            color=fov_data.color,
                            linestyle=fov_data.linestyle,
                            zorder=1,
                            linewidth=fov_data.linewidth,
                            transform=cartopy.crs.Geodetic())

                    # Add site_uid labels to the FoV plot, in the center of each ASI FoV
                    if label:
                        if (fov_data.instrument_array != "trex_spectrograph"):
                            center_lat = (latlon[0, 0] + latlon[0, 179]) / 2.0
                            center_lon = (latlon[1, 89] + latlon[1, 269]) / 2.0
                        else:
                            center_lat = (latlon[0, 0] + latlon[0, -1]) / 2.0
                            center_lon = (latlon[1, 0] + latlon[1, -1]) / 2.0

                        ax.text(center_lon,
                                center_lat,
                                site,
                                ha="center",
                                va="center",
                                color=fov_data.color,
                                transform=cartopy.crs.Geodetic(),
                                clip_on=True)

        # Go through and plot all of the contour data included in the Object
        if self.contour_data is not None:
            for i in range(len(self.contour_data["x"])):
                ax.plot(self.contour_data["x"][i],
                        self.contour_data["y"][i],
                        color=self.contour_data["color"][i],
                        linewidth=self.contour_data["linewidth"][i],
                        linestyle=self.contour_data["linestyle"][i],
                        marker=self.contour_data["marker"][i],
                        zorder=self.contour_data["zorder"][i])

        # set title
        if (title is not None):
            ax.set_title(title)

        # save figure or show it
        if (savefig is True):
            # check that filename has been set
            if (savefig_filename is None):
                raise ValueError("The savefig_filename parameter is missing, but required since savefig was set to True.")

            # save the figure
            f_extension = os.path.splitext(savefig_filename)[-1].lower()
            if (".jpg" == f_extension or ".jpeg" == f_extension):
                # check quality setting
                if (savefig_quality is not None):
                    plt.savefig(savefig_filename, pil_kwargs={"quality": savefig_quality}, bbox_inches="tight")
                else:
                    plt.savefig(savefig_filename, bbox_inches="tight")
            else:
                if (savefig_quality is not None):
                    # quality specified, but output filename is not a JPG, so show a warning
                    show_warning("The savefig_quality parameter was specified, but is only used for saving JPG files. The " +
                                 "savefig_filename parameter was determined to not be a JPG file, so the quality will be ignored",
                                 stacklevel=1)
                plt.savefig(savefig_filename, bbox_inches="tight")

            # clean up by closing the figure
            plt.close(fig)
        elif (returnfig is True):
            # return the figure and axis objects
            return (fig, ax)
        else:
            # show the figure
            plt.show(fig)

            # cleanup by closing the figure
            plt.close(fig)

        # return
        return None

    def add_fov(self, fov_data: Union[FOVData, List[FOVData]]):
        """
        Add one or more FOVData objects to the FOV object.

        Args:

            fov_data (pyaurorax.tools.FOVData or list): 
                A single or list of FOVData objects, that will be added to the
                FOV object map upon initialization.

        Returns:
            The object's fov_data parameter is updated appropriately.

        Raises:
            ValueError: issues encountered with supplied parameters.
        """

        if isinstance(fov_data, list):
            fovs_to_add = fov_data
        else:
            fovs_to_add = [fov_data]

        if self.fov_data is None:
            self.fov_data = fovs_to_add
        else:
            for data in fovs_to_add:
                self.fov_data.append(data)

    def add_geo_contours(self,
                         lats: Optional[Union[ndarray, list]] = None,
                         lons: Optional[Union[ndarray, list]] = None,
                         constant_lats: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                         constant_lons: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                         color: str = "black",
                         linewidth: Union[float, int] = 1,
                         linestyle: str = "solid",
                         marker: str = "",
                         bring_to_front: bool = False):
        """
        Add geographic contours to a FoV map.

        Args:
            lats (ndarray or list): 
                Sequence of geographic latitudes defining a contour.
            
            lons (ndarray or list): 
                Sequence of geographic longitudes defining a contour.

            constant_lats (float, int, or Sequence): 
                Geographic Latitude(s) at which to add line(s) of constant latitude.
            
            constant_lons (float, int, or Sequence): 
                Geographic Longitude(s) at which to add line(s) of constant longitude.

            color (str): 
                The matplotlib color used for the contour(s).

            linewidth (float or int): 
                The contour thickness.
            
            linestyle (str): 
                The matplotlib linestyle used for the contour(s).

            marker (str): 
                The matplotlib marker used for the contour(s).

            bring_to_front (bool): 
                Plots the contour on top of all other currently plotted objects.

        Returns:
            The object's contour_data parameter is populated appropriately.

        Raises:
            ValueError: issues encountered with supplied parameters.
        """
        # Make sure some form of lat/lon is provided
        if (constant_lats is None) and (constant_lons is None) and (lats is None) and (lons is None):
            raise ValueError("No latitudes or longitudes provided.")

        # If manually passing in lats & lons, make sure both are provided
        if (lats is not None or lons is not None) and (lats is None or lons is None):
            raise (ValueError("Manually supplying contour requires both lats and lons."))

        # Check that color exists in matplotlib
        if color not in matplotlib.colors.CSS4_COLORS:
            raise ValueError(f"Color '{color}' not recognized by matplotlib.")

        # Check that linestyle is valid
        if linestyle not in ["-", "--", "-.", ":", "solid", "dashed", "dashdot", "dotted"]:
            raise ValueError(f"Linestyle '{linestyle}' not recognized by matplotlib.")

        # Check that linewidth is valid
        if linewidth <= 0:
            raise ValueError("Linewidth must be greater than zero.")

        # Check that marker is valid
        if marker not in ["", "o", ".", "p", "*", "x", "+", "X"]:
            raise ValueError(f"Marker '{marker}' is not currently supported.")

        # Convert numerics to lists if necessary
        if constant_lats is not None:
            if isinstance(constant_lats, (float, int)):
                constant_lats = [constant_lats]
        if constant_lons is not None:
            if isinstance(constant_lons, (float, int)):
                constant_lons = [constant_lons]

        # Initialize contour data dict if it doesn't exist yet
        if self.contour_data is None:
            self.contour_data = {"x": [], "y": [], "color": [], "linewidth": [], "linestyle": [], "marker": [], "zorder": []}

        # Obtain the Fov map's projection
        source_proj = pyproj.CRS.from_user_input(cartopy.crs.Geodetic())
        mosaic_proj = pyproj.CRS.from_user_input(self.cartopy_projection)
        transformer = pyproj.Transformer.from_crs(source_proj, mosaic_proj, always_xy=True)

        # First handling manually supplied lat/lon arrays
        if (lats is not None) and (lons is not None):
            # Convert lists to ndarrays if necessary
            if isinstance(lats, list):
                lats = np.array(lats)
            if isinstance(lons, list):
                lons = np.array(lons)

            if len(lats) != len(lons):
                raise ValueError("Lat/Lon data must be of the same size.")

            # Create specified contour from geographic coords
            x, y = transformer.transform(lons, lats)
            # Add contour to dict, along with color and linewidth
            self.contour_data["x"].append(x)
            self.contour_data["y"].append(y)
            self.contour_data["color"].append(color)
            self.contour_data["linewidth"].append(linewidth)
            self.contour_data["linestyle"].append(linestyle)
            self.contour_data["marker"].append(marker)
            self.contour_data["zorder"].append(int(bring_to_front))

        # Next handling lines of constant latitude
        if constant_lats is not None:
            # Generate longitudinal domain of the lat line (full globe)
            lon_domain = np.arange(-180, 180 + 0.2, 0.2)

            # Iterate through all lines of constant lat requested
            for lat in constant_lats:
                # Create line of constant lat
                const_lat_x, const_lat_y = (lon_domain, lon_domain * 0 + lat)
                sort_idx = np.argsort(const_lat_x)
                const_lat_y = const_lat_y[sort_idx]
                const_lat_x = const_lat_x[sort_idx]
                const_lat_x, const_lat_y = transformer.transform(const_lat_x, const_lat_y)

                # Add contour to dict, along with color and linewidth
                self.contour_data["x"].append(const_lat_x)
                self.contour_data["y"].append(const_lat_y)
                self.contour_data["color"].append(color)
                self.contour_data["linewidth"].append(linewidth)
                self.contour_data["linestyle"].append(linestyle)
                self.contour_data["marker"].append(marker)
                self.contour_data["zorder"].append(int(bring_to_front))

        # Now handling lines of constant longitude
        if constant_lons is not None:
            # Generate latitudinal domain of the lon line (full globe)
            lat_domain = np.arange(-90, 90 + 0.1, 0.1)

            # Iterate through all lines of constant lon requested
            for lon in constant_lons:
                # Create line of constant lon and add to dict
                const_lon_x, const_lon_y = (lat_domain * 0 + lon, lat_domain)
                sort_idx = np.argsort(const_lon_y)
                const_lon_x = const_lon_x[sort_idx]
                const_lon_y = const_lon_y[sort_idx]
                const_lon_x, const_lon_y = transformer.transform(const_lon_x, const_lon_y)

                # Add contour to dict, along with color and linewidth
                self.contour_data["x"].append(const_lon_x)
                self.contour_data["y"].append(const_lon_y)
                self.contour_data["color"].append(color)
                self.contour_data["linewidth"].append(linewidth)
                self.contour_data["linestyle"].append(linestyle)
                self.contour_data["marker"].append(marker)
                self.contour_data["zorder"].append(int(bring_to_front))

    def add_mag_contours(self,
                         timestamp: datetime.datetime,
                         constant_lats: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                         constant_lons: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                         lats: Optional[Union[ndarray, list]] = None,
                         lons: Optional[Union[ndarray, list]] = None,
                         color: str = "black",
                         linewidth: Union[float, int] = 1,
                         linestyle: str = "solid",
                         marker: str = "",
                         bring_to_front: bool = False):
        """
        Add geomagnetic contours to a FoV map.

        Args:
            timestamp (datetime.datetime): 
                The timestamp used in computing AACGM coordinates.

            lats (ndarray or list): 
                Sequence of geomagnetic latitudes defining a contour.
            
            lons (ndarray or list): 
                Sequence of geomagnetic longitudes defining a contour.

            constant_lats (float, int, Sequence): 
                Geomagnetic latitude(s) at which to add contour(s) of constant latitude.
            
            constant_lons (float, int, Sequence): 
                Geomagnetic longitude(s) at which to add contours(s) of constant longitude.

            color (str): 
                The matplotlib color used for the contour(s).

            linewidth (float or int): 
                The contour thickness.

            linestyle (str): 
                The matplotlib linestyle used for the contour(s).

            marker (str): 
                The matplotlib marker used for the contour(s).

            bring_to_front (bool): 
                Plots the contour on top of all other currently plotted objects.

        Returns:
            The object's contour_data parameter is populated appropriately.

        Raises:
            ValueError: issues encountered with supplied parameters
        """
        # Make sure some form of lat/lon is provided
        if (constant_lats is None) and (constant_lons is None) and (lats is None) and (lons is None):
            raise ValueError("No latitudes or longitudes provided.")

        # If manually passing in lats & lons, make sure both are provided
        if (lats is not None or lons is not None) and (lats is None or lons is None):
            raise ValueError("Manually supplying contour requires both lats and lons.")

        # Check that color exists in matplotlib
        if color not in matplotlib.colors.CSS4_COLORS:
            raise ValueError(f"Color '{color}' not recognized by matplotlib.")

        # Check that linestyle is valid
        if linestyle not in ["-", "--", "-.", ":", "solid", "dashed", "dashdot", "dotted"]:
            raise ValueError(f"Linestyle '{linestyle}' not recognized by matplotlib.")

        # Check that linewidth is valid
        if linewidth <= 0:
            raise ValueError("Linewidth must be greater than zero.")

        # Check that marker is valid
        if marker not in ["", "o", ".", "p", "*", "x", "+", "X"]:
            raise ValueError(f"Marker '{marker}' is not currently supported.")

        # Convert numerics to lists if necessary
        if constant_lats is not None:
            if isinstance(constant_lats, (float, int)):
                constant_lats = [constant_lats]
        if constant_lons is not None:
            if isinstance(constant_lons, (float, int)):
                constant_lons = [constant_lons]

        # Initialize contour data dict if it doesn't exist yet
        if self.contour_data is None:
            self.contour_data = {"x": [], "y": [], "color": [], "linewidth": [], "linestyle": [], "marker": [], "zorder": []}

        # Obtain the FoV map's projection
        source_proj = pyproj.CRS.from_user_input(cartopy.crs.Geodetic())
        mosaic_proj = pyproj.CRS.from_user_input(self.cartopy_projection)
        transformer = pyproj.Transformer.from_crs(source_proj, mosaic_proj, always_xy=True)

        # First handling manually supplied lat/lon arrays
        if (lats is not None) and (lons is not None):
            # Convert lists to ndarrays if necessary
            if isinstance(lats, list):
                lats = np.array(lats)
            if isinstance(lons, list):
                lons = np.array(lons)

            if len(lats) != len(lons):
                raise ValueError("Lat/Lon data must be of the same size.")

            # Create specified contour from magnetic coords
            y, x, _ = aacgmv2.convert_latlon_arr(lats, lons, lats * 0.0, timestamp, method_code="A2G")
            x, y = transformer.transform(x, y)

            # Add contour to dict, along with color and linewidth
            self.contour_data["x"].append(x)
            self.contour_data["y"].append(y)
            self.contour_data["color"].append(color)
            self.contour_data["linewidth"].append(linewidth)
            self.contour_data["linestyle"].append(linestyle)
            self.contour_data["marker"].append(marker)
            self.contour_data["zorder"].append(int(bring_to_front))

        # Next handling lines of constant latitude
        if constant_lats is not None:
            # Generate longitudinal domain of the lat line (full globe)
            lon_domain = np.arange(-180, 180 + 0.2, 0.2)

            # iterate through all lines of constant lat requested
            for lat in constant_lats:
                # Create line of constant lat from magnetic coords
                const_lat_x, const_lat_y = (lon_domain, lon_domain * 0 + lat)
                const_lat_y, const_lat_x, _ = aacgmv2.convert_latlon_arr(const_lat_y, const_lat_x, const_lat_x * 0.0, timestamp, method_code="A2G")
                sort_idx = np.argsort(const_lat_x)
                const_lat_y = const_lat_y[sort_idx]
                const_lat_x = const_lat_x[sort_idx]
                const_lat_x, const_lat_y = transformer.transform(const_lat_x, const_lat_y)

                # Add contour to dict, along with color and linewidth
                self.contour_data["x"].append(const_lat_x)
                self.contour_data["y"].append(const_lat_y)
                self.contour_data["color"].append(color)
                self.contour_data["linewidth"].append(linewidth)
                self.contour_data["linestyle"].append(linestyle)
                self.contour_data["marker"].append(marker)
                self.contour_data["zorder"].append(int(bring_to_front))

        # Now handling lines of constant longitude
        if constant_lons is not None:
            # Generate latitudinal domain of the lon line (full globe)
            lat_domain = np.arange(-90, 90 + 0.1, 0.1)

            # iterate through all lines of constant lon requested
            for lon in constant_lons:
                # Create line of constant lon from magnetic coords
                const_lon_x, const_lon_y = (lat_domain * 0 + lon, lat_domain)
                const_lon_y, const_lon_x, _ = aacgmv2.convert_latlon_arr(const_lon_y, const_lon_x, const_lon_x * 0.0, timestamp, method_code="A2G")
                sort_idx = np.argsort(const_lon_y)
                const_lon_x = const_lon_x[sort_idx]
                const_lon_y = const_lon_y[sort_idx]
                const_lon_x, const_lon_y = transformer.transform(const_lon_x, const_lon_y)

                # Add contour to dict, along with color and linewidth
                self.contour_data["x"].append(const_lon_x)
                self.contour_data["y"].append(const_lon_y)
                self.contour_data["color"].append(color)
                self.contour_data["linewidth"].append(linewidth)
                self.contour_data["linestyle"].append(linestyle)
                self.contour_data["marker"].append(marker)
                self.contour_data["zorder"].append(int(bring_to_front))

Class representation for a FoV map.

Attributes

cartopy_projection : cartopy.crs.Projection
Cartopy projection to utilize.
fov_data : FOVData or list of FOVData objects
FoV contours included in FOV object.
contour_data : Dict[str, List[Any]]
Other contour data included in FOV object.

Instance variables

var cartopy_projection : cartopy.crs.Projection
var contour_data : Dict[str, List[Any]] | None
var fov_data : List[FOVData] | None

Methods

def add_fov(self,
fov_data: FOVData | List[FOVData])
Expand source code
def add_fov(self, fov_data: Union[FOVData, List[FOVData]]):
    """
    Add one or more FOVData objects to the FOV object.

    Args:

        fov_data (pyaurorax.tools.FOVData or list): 
            A single or list of FOVData objects, that will be added to the
            FOV object map upon initialization.

    Returns:
        The object's fov_data parameter is updated appropriately.

    Raises:
        ValueError: issues encountered with supplied parameters.
    """

    if isinstance(fov_data, list):
        fovs_to_add = fov_data
    else:
        fovs_to_add = [fov_data]

    if self.fov_data is None:
        self.fov_data = fovs_to_add
    else:
        for data in fovs_to_add:
            self.fov_data.append(data)

Add one or more FOVData objects to the FOV object.

Args

fov_data : FOVData or list
A single or list of FOVData objects, that will be added to the FOV object map upon initialization.

Returns

The object's fov_data parameter is updated appropriately.

Raises

ValueError
issues encountered with supplied parameters.
def add_geo_contours(self,
lats: numpy.ndarray | list | None = None,
lons: numpy.ndarray | list | None = None,
constant_lats: float | int | Sequence[float | int] | numpy.ndarray | None = None,
constant_lons: float | int | Sequence[float | int] | numpy.ndarray | None = None,
color: str = 'black',
linewidth: float | int = 1,
linestyle: str = 'solid',
marker: str = '',
bring_to_front: bool = False)
Expand source code
def add_geo_contours(self,
                     lats: Optional[Union[ndarray, list]] = None,
                     lons: Optional[Union[ndarray, list]] = None,
                     constant_lats: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                     constant_lons: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                     color: str = "black",
                     linewidth: Union[float, int] = 1,
                     linestyle: str = "solid",
                     marker: str = "",
                     bring_to_front: bool = False):
    """
    Add geographic contours to a FoV map.

    Args:
        lats (ndarray or list): 
            Sequence of geographic latitudes defining a contour.
        
        lons (ndarray or list): 
            Sequence of geographic longitudes defining a contour.

        constant_lats (float, int, or Sequence): 
            Geographic Latitude(s) at which to add line(s) of constant latitude.
        
        constant_lons (float, int, or Sequence): 
            Geographic Longitude(s) at which to add line(s) of constant longitude.

        color (str): 
            The matplotlib color used for the contour(s).

        linewidth (float or int): 
            The contour thickness.
        
        linestyle (str): 
            The matplotlib linestyle used for the contour(s).

        marker (str): 
            The matplotlib marker used for the contour(s).

        bring_to_front (bool): 
            Plots the contour on top of all other currently plotted objects.

    Returns:
        The object's contour_data parameter is populated appropriately.

    Raises:
        ValueError: issues encountered with supplied parameters.
    """
    # Make sure some form of lat/lon is provided
    if (constant_lats is None) and (constant_lons is None) and (lats is None) and (lons is None):
        raise ValueError("No latitudes or longitudes provided.")

    # If manually passing in lats & lons, make sure both are provided
    if (lats is not None or lons is not None) and (lats is None or lons is None):
        raise (ValueError("Manually supplying contour requires both lats and lons."))

    # Check that color exists in matplotlib
    if color not in matplotlib.colors.CSS4_COLORS:
        raise ValueError(f"Color '{color}' not recognized by matplotlib.")

    # Check that linestyle is valid
    if linestyle not in ["-", "--", "-.", ":", "solid", "dashed", "dashdot", "dotted"]:
        raise ValueError(f"Linestyle '{linestyle}' not recognized by matplotlib.")

    # Check that linewidth is valid
    if linewidth <= 0:
        raise ValueError("Linewidth must be greater than zero.")

    # Check that marker is valid
    if marker not in ["", "o", ".", "p", "*", "x", "+", "X"]:
        raise ValueError(f"Marker '{marker}' is not currently supported.")

    # Convert numerics to lists if necessary
    if constant_lats is not None:
        if isinstance(constant_lats, (float, int)):
            constant_lats = [constant_lats]
    if constant_lons is not None:
        if isinstance(constant_lons, (float, int)):
            constant_lons = [constant_lons]

    # Initialize contour data dict if it doesn't exist yet
    if self.contour_data is None:
        self.contour_data = {"x": [], "y": [], "color": [], "linewidth": [], "linestyle": [], "marker": [], "zorder": []}

    # Obtain the Fov map's projection
    source_proj = pyproj.CRS.from_user_input(cartopy.crs.Geodetic())
    mosaic_proj = pyproj.CRS.from_user_input(self.cartopy_projection)
    transformer = pyproj.Transformer.from_crs(source_proj, mosaic_proj, always_xy=True)

    # First handling manually supplied lat/lon arrays
    if (lats is not None) and (lons is not None):
        # Convert lists to ndarrays if necessary
        if isinstance(lats, list):
            lats = np.array(lats)
        if isinstance(lons, list):
            lons = np.array(lons)

        if len(lats) != len(lons):
            raise ValueError("Lat/Lon data must be of the same size.")

        # Create specified contour from geographic coords
        x, y = transformer.transform(lons, lats)
        # Add contour to dict, along with color and linewidth
        self.contour_data["x"].append(x)
        self.contour_data["y"].append(y)
        self.contour_data["color"].append(color)
        self.contour_data["linewidth"].append(linewidth)
        self.contour_data["linestyle"].append(linestyle)
        self.contour_data["marker"].append(marker)
        self.contour_data["zorder"].append(int(bring_to_front))

    # Next handling lines of constant latitude
    if constant_lats is not None:
        # Generate longitudinal domain of the lat line (full globe)
        lon_domain = np.arange(-180, 180 + 0.2, 0.2)

        # Iterate through all lines of constant lat requested
        for lat in constant_lats:
            # Create line of constant lat
            const_lat_x, const_lat_y = (lon_domain, lon_domain * 0 + lat)
            sort_idx = np.argsort(const_lat_x)
            const_lat_y = const_lat_y[sort_idx]
            const_lat_x = const_lat_x[sort_idx]
            const_lat_x, const_lat_y = transformer.transform(const_lat_x, const_lat_y)

            # Add contour to dict, along with color and linewidth
            self.contour_data["x"].append(const_lat_x)
            self.contour_data["y"].append(const_lat_y)
            self.contour_data["color"].append(color)
            self.contour_data["linewidth"].append(linewidth)
            self.contour_data["linestyle"].append(linestyle)
            self.contour_data["marker"].append(marker)
            self.contour_data["zorder"].append(int(bring_to_front))

    # Now handling lines of constant longitude
    if constant_lons is not None:
        # Generate latitudinal domain of the lon line (full globe)
        lat_domain = np.arange(-90, 90 + 0.1, 0.1)

        # Iterate through all lines of constant lon requested
        for lon in constant_lons:
            # Create line of constant lon and add to dict
            const_lon_x, const_lon_y = (lat_domain * 0 + lon, lat_domain)
            sort_idx = np.argsort(const_lon_y)
            const_lon_x = const_lon_x[sort_idx]
            const_lon_y = const_lon_y[sort_idx]
            const_lon_x, const_lon_y = transformer.transform(const_lon_x, const_lon_y)

            # Add contour to dict, along with color and linewidth
            self.contour_data["x"].append(const_lon_x)
            self.contour_data["y"].append(const_lon_y)
            self.contour_data["color"].append(color)
            self.contour_data["linewidth"].append(linewidth)
            self.contour_data["linestyle"].append(linestyle)
            self.contour_data["marker"].append(marker)
            self.contour_data["zorder"].append(int(bring_to_front))

Add geographic contours to a FoV map.

Args

lats : ndarray or list
Sequence of geographic latitudes defining a contour.
lons : ndarray or list
Sequence of geographic longitudes defining a contour.
constant_lats : float, int, or Sequence
Geographic Latitude(s) at which to add line(s) of constant latitude.
constant_lons : float, int, or Sequence
Geographic Longitude(s) at which to add line(s) of constant longitude.
color : str
The matplotlib color used for the contour(s).
linewidth : float or int
The contour thickness.
linestyle : str
The matplotlib linestyle used for the contour(s).
marker : str
The matplotlib marker used for the contour(s).
bring_to_front : bool
Plots the contour on top of all other currently plotted objects.

Returns

The object's contour_data parameter is populated appropriately.

Raises

ValueError
issues encountered with supplied parameters.
def add_mag_contours(self,
timestamp: datetime.datetime,
constant_lats: float | int | Sequence[float | int] | numpy.ndarray | None = None,
constant_lons: float | int | Sequence[float | int] | numpy.ndarray | None = None,
lats: numpy.ndarray | list | None = None,
lons: numpy.ndarray | list | None = None,
color: str = 'black',
linewidth: float | int = 1,
linestyle: str = 'solid',
marker: str = '',
bring_to_front: bool = False)
Expand source code
def add_mag_contours(self,
                     timestamp: datetime.datetime,
                     constant_lats: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                     constant_lons: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                     lats: Optional[Union[ndarray, list]] = None,
                     lons: Optional[Union[ndarray, list]] = None,
                     color: str = "black",
                     linewidth: Union[float, int] = 1,
                     linestyle: str = "solid",
                     marker: str = "",
                     bring_to_front: bool = False):
    """
    Add geomagnetic contours to a FoV map.

    Args:
        timestamp (datetime.datetime): 
            The timestamp used in computing AACGM coordinates.

        lats (ndarray or list): 
            Sequence of geomagnetic latitudes defining a contour.
        
        lons (ndarray or list): 
            Sequence of geomagnetic longitudes defining a contour.

        constant_lats (float, int, Sequence): 
            Geomagnetic latitude(s) at which to add contour(s) of constant latitude.
        
        constant_lons (float, int, Sequence): 
            Geomagnetic longitude(s) at which to add contours(s) of constant longitude.

        color (str): 
            The matplotlib color used for the contour(s).

        linewidth (float or int): 
            The contour thickness.

        linestyle (str): 
            The matplotlib linestyle used for the contour(s).

        marker (str): 
            The matplotlib marker used for the contour(s).

        bring_to_front (bool): 
            Plots the contour on top of all other currently plotted objects.

    Returns:
        The object's contour_data parameter is populated appropriately.

    Raises:
        ValueError: issues encountered with supplied parameters
    """
    # Make sure some form of lat/lon is provided
    if (constant_lats is None) and (constant_lons is None) and (lats is None) and (lons is None):
        raise ValueError("No latitudes or longitudes provided.")

    # If manually passing in lats & lons, make sure both are provided
    if (lats is not None or lons is not None) and (lats is None or lons is None):
        raise ValueError("Manually supplying contour requires both lats and lons.")

    # Check that color exists in matplotlib
    if color not in matplotlib.colors.CSS4_COLORS:
        raise ValueError(f"Color '{color}' not recognized by matplotlib.")

    # Check that linestyle is valid
    if linestyle not in ["-", "--", "-.", ":", "solid", "dashed", "dashdot", "dotted"]:
        raise ValueError(f"Linestyle '{linestyle}' not recognized by matplotlib.")

    # Check that linewidth is valid
    if linewidth <= 0:
        raise ValueError("Linewidth must be greater than zero.")

    # Check that marker is valid
    if marker not in ["", "o", ".", "p", "*", "x", "+", "X"]:
        raise ValueError(f"Marker '{marker}' is not currently supported.")

    # Convert numerics to lists if necessary
    if constant_lats is not None:
        if isinstance(constant_lats, (float, int)):
            constant_lats = [constant_lats]
    if constant_lons is not None:
        if isinstance(constant_lons, (float, int)):
            constant_lons = [constant_lons]

    # Initialize contour data dict if it doesn't exist yet
    if self.contour_data is None:
        self.contour_data = {"x": [], "y": [], "color": [], "linewidth": [], "linestyle": [], "marker": [], "zorder": []}

    # Obtain the FoV map's projection
    source_proj = pyproj.CRS.from_user_input(cartopy.crs.Geodetic())
    mosaic_proj = pyproj.CRS.from_user_input(self.cartopy_projection)
    transformer = pyproj.Transformer.from_crs(source_proj, mosaic_proj, always_xy=True)

    # First handling manually supplied lat/lon arrays
    if (lats is not None) and (lons is not None):
        # Convert lists to ndarrays if necessary
        if isinstance(lats, list):
            lats = np.array(lats)
        if isinstance(lons, list):
            lons = np.array(lons)

        if len(lats) != len(lons):
            raise ValueError("Lat/Lon data must be of the same size.")

        # Create specified contour from magnetic coords
        y, x, _ = aacgmv2.convert_latlon_arr(lats, lons, lats * 0.0, timestamp, method_code="A2G")
        x, y = transformer.transform(x, y)

        # Add contour to dict, along with color and linewidth
        self.contour_data["x"].append(x)
        self.contour_data["y"].append(y)
        self.contour_data["color"].append(color)
        self.contour_data["linewidth"].append(linewidth)
        self.contour_data["linestyle"].append(linestyle)
        self.contour_data["marker"].append(marker)
        self.contour_data["zorder"].append(int(bring_to_front))

    # Next handling lines of constant latitude
    if constant_lats is not None:
        # Generate longitudinal domain of the lat line (full globe)
        lon_domain = np.arange(-180, 180 + 0.2, 0.2)

        # iterate through all lines of constant lat requested
        for lat in constant_lats:
            # Create line of constant lat from magnetic coords
            const_lat_x, const_lat_y = (lon_domain, lon_domain * 0 + lat)
            const_lat_y, const_lat_x, _ = aacgmv2.convert_latlon_arr(const_lat_y, const_lat_x, const_lat_x * 0.0, timestamp, method_code="A2G")
            sort_idx = np.argsort(const_lat_x)
            const_lat_y = const_lat_y[sort_idx]
            const_lat_x = const_lat_x[sort_idx]
            const_lat_x, const_lat_y = transformer.transform(const_lat_x, const_lat_y)

            # Add contour to dict, along with color and linewidth
            self.contour_data["x"].append(const_lat_x)
            self.contour_data["y"].append(const_lat_y)
            self.contour_data["color"].append(color)
            self.contour_data["linewidth"].append(linewidth)
            self.contour_data["linestyle"].append(linestyle)
            self.contour_data["marker"].append(marker)
            self.contour_data["zorder"].append(int(bring_to_front))

    # Now handling lines of constant longitude
    if constant_lons is not None:
        # Generate latitudinal domain of the lon line (full globe)
        lat_domain = np.arange(-90, 90 + 0.1, 0.1)

        # iterate through all lines of constant lon requested
        for lon in constant_lons:
            # Create line of constant lon from magnetic coords
            const_lon_x, const_lon_y = (lat_domain * 0 + lon, lat_domain)
            const_lon_y, const_lon_x, _ = aacgmv2.convert_latlon_arr(const_lon_y, const_lon_x, const_lon_x * 0.0, timestamp, method_code="A2G")
            sort_idx = np.argsort(const_lon_y)
            const_lon_x = const_lon_x[sort_idx]
            const_lon_y = const_lon_y[sort_idx]
            const_lon_x, const_lon_y = transformer.transform(const_lon_x, const_lon_y)

            # Add contour to dict, along with color and linewidth
            self.contour_data["x"].append(const_lon_x)
            self.contour_data["y"].append(const_lon_y)
            self.contour_data["color"].append(color)
            self.contour_data["linewidth"].append(linewidth)
            self.contour_data["linestyle"].append(linestyle)
            self.contour_data["marker"].append(marker)
            self.contour_data["zorder"].append(int(bring_to_front))

Add geomagnetic contours to a FoV map.

Args

timestamp : datetime.datetime
The timestamp used in computing AACGM coordinates.
lats : ndarray or list
Sequence of geomagnetic latitudes defining a contour.
lons : ndarray or list
Sequence of geomagnetic longitudes defining a contour.
constant_lats : float, int, Sequence
Geomagnetic latitude(s) at which to add contour(s) of constant latitude.
constant_lons : float, int, Sequence
Geomagnetic longitude(s) at which to add contours(s) of constant longitude.
color : str
The matplotlib color used for the contour(s).
linewidth : float or int
The contour thickness.
linestyle : str
The matplotlib linestyle used for the contour(s).
marker : str
The matplotlib marker used for the contour(s).
bring_to_front : bool
Plots the contour on top of all other currently plotted objects.

Returns

The object's contour_data parameter is populated appropriately.

Raises

ValueError
issues encountered with supplied parameters
def plot(self,
map_extent: Sequence[float | int],
label: bool = True,
enforce_data_availability: bool = False,
figsize: Tuple[int, int] | None = None,
title: str | None = None,
ocean_color: str | None = None,
land_color: str = 'gray',
land_edgecolor: str = '#8A8A8A',
borders_color: str = '#AEAEAE',
borders_disable: bool = False,
returnfig: bool = False,
savefig: bool = False,
savefig_filename: str | None = None,
savefig_quality: int | None = None) ‑> Any
Expand source code
def plot(self,
         map_extent: Sequence[Union[float, int]],
         label: bool = True,
         enforce_data_availability: bool = False,
         figsize: Optional[Tuple[int, int]] = None,
         title: Optional[str] = None,
         ocean_color: Optional[str] = None,
         land_color: str = "gray",
         land_edgecolor: str = "#8A8A8A",
         borders_color: str = "#AEAEAE",
         borders_disable: bool = False,
         returnfig: bool = False,
         savefig: bool = False,
         savefig_filename: Optional[str] = None,
         savefig_quality: Optional[int] = None) -> Any:
    """
    Generate a plot of the FoV data. 
    
    Either display it (default behaviour), save it to disk (using the `savefig` parameter), or 
    return the matplotlib plot object for further usage (using the `returnfig` parameter).

    Args:
        map_extent (List[int]): 
            Latitude/longitude range to be visible on the rendered map. This is a list of 4 integers 
            and/or floats, in the order of [min_lon, max_lon, min_lat, max_lat].

        label (bool): 
            Specifies wether individual FoVs will be labelled with their site_uid.
            
        enforce_data_availability (bool): 
            Specifies whether or not data availability information associated with the FOV object
            should be used to omit sites with no available data. Note that data availability information
            is typically created using the FOVData.add_availability method.

        figsize (tuple): 
            The matplotlib figure size to use when plotting. For example `figsize=(14,4)`.

        title (str): 
            The title to display above the plotted Fov Map. Default is no title.

        ocean_color (str): 
            Colour of the ocean. Default is cartopy's default shade of blue. Colours can be supplied
            as a word, or hexcode prefixed with a '#' character (ie. `#55AADD`).
        
        land_color (str): 
            Colour of the land. Default is `gray`. Colours can be supplied as a word, or hexcode 
            prefixed with a '#' character (ie. `#41BB87`).

        land_edgecolor (str): 
            Color of the land edges. Default is `#8A8A8A`. Colours can be supplied as a word, or
            hexcode prefixed with a '#' character.

        borders_color (str): 
            Color of the country borders. Default is `AEAEAE`. Colours can be supplied as a word, or
            hexcode prefixed with a '#' character.
        
        borders_disable (bool): 
            Disbale rendering of the borders. Default is `False`.

        returnfig (bool): 
            Instead of displaying the image, return the matplotlib figure object. This allows for further plot 
            manipulation, for example, adding labels or a title in a different location than the default. 
            
            Remember - if this parameter is supplied, be sure that you close your plot after finishing work 
            with it. This can be achieved by doing `plt.close(fig)`. 
            
            Note that this method cannot be used in combination with `savefig`.

        savefig (bool): 
            Save the displayed image to disk instead of displaying it. The parameter savefig_filename is required if 
            this parameter is set to True. Defaults to `False`.

        savefig_filename (str): 
            Filename to save the image to. Must be specified if the savefig parameter is set to True.

        savefig_quality (int): 
            Quality level of the saved image. This can be specified if the savefig_filename is a JPG image. If it
            is a PNG, quality is ignored. Default quality level for JPGs is matplotlib/Pillow's default of 75%.

    Returns:
        The displayed fov map, by default. If `savefig` is set to True, nothing will be returned. If `returnfig` is 
        set to True, the plotting variables `(fig, ax)` will be returned.

    Raises:
    """

    # check return mode
    if (returnfig is True and savefig is True):
        raise ValueError("Only one of returnfig or savefig can be set to True")
    if returnfig is True and (savefig_filename is not None or savefig_quality is not None):
        show_warning("The figure will be returned, but a savefig option parameter was supplied. Consider " +
                     "removing the savefig option parameter(s) as they will be ignored.",
                     stacklevel=1)
    elif (savefig is False and (savefig_filename is not None or savefig_quality is not None)):
        show_warning("A savefig option parameter was supplied, but the savefig parameter is False. The " +
                     "savefig option parameters will be ignored.",
                     stacklevel=1)

    # Initialize figure
    fig = plt.figure(figsize=figsize)
    ax = fig.add_axes((0, 0, 1, 1), projection=self.cartopy_projection)
    ax.set_extent(map_extent, crs=cartopy.crs.Geodetic())  # type: ignore

    # Add ocean
    #
    # NOTE: we use the default ocean color
    if (ocean_color is not None):
        ax.add_feature(  # type: ignore
            cartopy.feature.OCEAN, facecolor=ocean_color, zorder=0)
    else:
        ax.add_feature(cartopy.feature.OCEAN, zorder=0)  # type: ignore

    # add land
    ax.add_feature(  # type: ignore
        cartopy.feature.LAND, facecolor=land_color, edgecolor=land_edgecolor, zorder=0)

    # add borders
    if (borders_disable is False):
        ax.add_feature(  # type: ignore
            cartopy.feature.BORDERS, edgecolor=borders_color, zorder=0)

    # Go through and plot all of the FoVs within each of the Object's FOVData objects
    if self.fov_data is not None:

        # If requesting enforcement of data availability, check that data availability exists in
        # the object first
        if (enforce_data_availability is True):
            for fov_data in self.fov_data:
                if (fov_data.data_availability is None):
                    raise ValueError("Before plotting FOV object with enforce_data_availability=True, FOVData.add_availability(...) " +
                                     "must be called for all included FOVData objects.")

        for fov_data in self.fov_data:
            latlon_dict = fov_data.fovs

            for site in fov_data.site_uid_list:

                # If data_availability has been enforced and is False for this site, don't plot it
                if (enforce_data_availability is True) and (fov_data.data_availability is not None) and (site
                                                                                                         in fov_data.data_availability.keys()):
                    if (fov_data.data_availability[site] is False):
                        continue

                latlon = latlon_dict[site]

                ax.plot(np.squeeze(latlon[1, :]),
                        np.squeeze(latlon[0, :]),
                        color=fov_data.color,
                        linestyle=fov_data.linestyle,
                        zorder=1,
                        linewidth=fov_data.linewidth,
                        transform=cartopy.crs.Geodetic())

                # Add site_uid labels to the FoV plot, in the center of each ASI FoV
                if label:
                    if (fov_data.instrument_array != "trex_spectrograph"):
                        center_lat = (latlon[0, 0] + latlon[0, 179]) / 2.0
                        center_lon = (latlon[1, 89] + latlon[1, 269]) / 2.0
                    else:
                        center_lat = (latlon[0, 0] + latlon[0, -1]) / 2.0
                        center_lon = (latlon[1, 0] + latlon[1, -1]) / 2.0

                    ax.text(center_lon,
                            center_lat,
                            site,
                            ha="center",
                            va="center",
                            color=fov_data.color,
                            transform=cartopy.crs.Geodetic(),
                            clip_on=True)

    # Go through and plot all of the contour data included in the Object
    if self.contour_data is not None:
        for i in range(len(self.contour_data["x"])):
            ax.plot(self.contour_data["x"][i],
                    self.contour_data["y"][i],
                    color=self.contour_data["color"][i],
                    linewidth=self.contour_data["linewidth"][i],
                    linestyle=self.contour_data["linestyle"][i],
                    marker=self.contour_data["marker"][i],
                    zorder=self.contour_data["zorder"][i])

    # set title
    if (title is not None):
        ax.set_title(title)

    # save figure or show it
    if (savefig is True):
        # check that filename has been set
        if (savefig_filename is None):
            raise ValueError("The savefig_filename parameter is missing, but required since savefig was set to True.")

        # save the figure
        f_extension = os.path.splitext(savefig_filename)[-1].lower()
        if (".jpg" == f_extension or ".jpeg" == f_extension):
            # check quality setting
            if (savefig_quality is not None):
                plt.savefig(savefig_filename, pil_kwargs={"quality": savefig_quality}, bbox_inches="tight")
            else:
                plt.savefig(savefig_filename, bbox_inches="tight")
        else:
            if (savefig_quality is not None):
                # quality specified, but output filename is not a JPG, so show a warning
                show_warning("The savefig_quality parameter was specified, but is only used for saving JPG files. The " +
                             "savefig_filename parameter was determined to not be a JPG file, so the quality will be ignored",
                             stacklevel=1)
            plt.savefig(savefig_filename, bbox_inches="tight")

        # clean up by closing the figure
        plt.close(fig)
    elif (returnfig is True):
        # return the figure and axis objects
        return (fig, ax)
    else:
        # show the figure
        plt.show(fig)

        # cleanup by closing the figure
        plt.close(fig)

    # return
    return None

Generate a plot of the FoV data.

Either display it (default behaviour), save it to disk (using the savefig parameter), or return the matplotlib plot object for further usage (using the returnfig parameter).

Args

map_extent : List[int]
Latitude/longitude range to be visible on the rendered map. This is a list of 4 integers and/or floats, in the order of [min_lon, max_lon, min_lat, max_lat].
label : bool
Specifies wether individual FoVs will be labelled with their site_uid.
enforce_data_availability : bool
Specifies whether or not data availability information associated with the FOV object should be used to omit sites with no available data. Note that data availability information is typically created using the FOVData.add_availability method.
figsize : tuple
The matplotlib figure size to use when plotting. For example figsize=(14,4).
title : str
The title to display above the plotted Fov Map. Default is no title.
ocean_color : str
Colour of the ocean. Default is cartopy's default shade of blue. Colours can be supplied as a word, or hexcode prefixed with a '#' character (ie. #55AADD).
land_color : str
Colour of the land. Default is gray. Colours can be supplied as a word, or hexcode prefixed with a '#' character (ie. #41BB87).
land_edgecolor : str
Color of the land edges. Default is #8A8A8A. Colours can be supplied as a word, or hexcode prefixed with a '#' character.
borders_color : str
Color of the country borders. Default is AEAEAE. Colours can be supplied as a word, or hexcode prefixed with a '#' character.
borders_disable : bool
Disbale rendering of the borders. Default is False.
returnfig : bool

Instead of displaying the image, return the matplotlib figure object. This allows for further plot manipulation, for example, adding labels or a title in a different location than the default.

Remember - if this parameter is supplied, be sure that you close your plot after finishing work with it. This can be achieved by doing plt.close(fig).

Note that this method cannot be used in combination with savefig.

savefig : bool
Save the displayed image to disk instead of displaying it. The parameter savefig_filename is required if this parameter is set to True. Defaults to False.
savefig_filename : str
Filename to save the image to. Must be specified if the savefig parameter is set to True.
savefig_quality : int
Quality level of the saved image. This can be specified if the savefig_filename is a JPG image. If it is a PNG, quality is ignored. Default quality level for JPGs is matplotlib/Pillow's default of 75%.

Returns

The displayed fov map, by default. If savefig is set to True, nothing will be returned. If returnfig is set to True, the plotting variables (fig, ax) will be returned. Raises:

def pretty_print(self)
Expand source code
def pretty_print(self):
    """
    A special print output for this class.
    """
    # set special strings
    cartopy_projection_str = "Projection(%s)" % (self.cartopy_projection.to_string())

    if self.contour_data is not None:
        contour_data_str = "%d Contour(s)" % (len(self.contour_data.get("x", [])), )
    else:
        contour_data_str = "None"

    if self.fov_data is not None:
        n_fovs = 0

        for fov in self.fov_data:
            n_fovs += len(fov.fovs.keys())

        fov_data_str = "%d FoVData Object(s)" % (n_fovs)
    else:
        fov_data_str = "None"

    # print
    print("FOV:")
    print("  %-23s: %s" % ("cartopy_projection", cartopy_projection_str))
    print("  %-23s: %s" % ("fov_data", fov_data_str))
    print("  %-23s: %s" % ("contour_data", contour_data_str))

A special print output for this class.

class FOVData (site_uid_list: List[str],
fovs: Dict[str, numpy.ndarray],
fovs_dimensions: Dict[str, Tuple],
instrument_array: str,
data_availability: Dict[str, bool] | None,
color: str,
linewidth: int,
linestyle: str,
aurorax_obj)
Expand source code
class FOVData:
    """
    Prepared ASI FoV data for use by FOV routines or manual plotting.

    Attributes:
        site_uid_list (List[str]): 
            List of site unique identifiers contained within this object.

        fovs (Dict[str, numpy.ndarray]): 
            Dictionary that holds the lat/lon data in a 2xN numpy array giving the FoV, for each site UID.
            
        fovs_dimensions (Dict[str, numpy.ndarray]): 
            Dictionary that holds the shape of each set of lat/lon data.

        instrument_array (str): 
            String giving the name of the instrument array this FOVData object corresponds to (optional).
            
        data_availability (dict): 
            An optional dictionary containing information about which sites included in the FOVData actually
            have available data for a given time range. Usually created using the FOVData.add_Availability()
            method.

        color (str): 
            String specifying the color to use when plotting this FOVData.
        
        linewidth (str): 
            Integer specifying the linewidth to use when plotting this FOVData.
        
        linestyle (str): 
            String (matplotlib.pyplot format code) specifying the linestyle to use when plotting this FOVData.
    """

    def __init__(self, site_uid_list: List[str], fovs: Dict[str, ndarray], fovs_dimensions: Dict[str, Tuple], instrument_array: str,
                 data_availability: Optional[Dict[str, bool]], color: str, linewidth: int, linestyle: str, aurorax_obj):

        # Public vars
        self.site_uid_list = site_uid_list
        self.fovs = fovs
        self.fovs_dimensions = fovs_dimensions
        self.instrument_array = instrument_array
        self.data_availability = data_availability
        self.color = color
        self.linewidth = linewidth
        self.linestyle = linestyle

        # Private vars
        self.__aurorax_obj = aurorax_obj

    def __str__(self) -> str:
        return self.__repr__()

    def __repr__(self) -> str:
        # set special strings
        unique_dimensions_str = str(list(dict.fromkeys(self.fovs_dimensions.values()))).replace("[", "").replace("]", "").replace("), (", "),(")
        fovs_str = "Dict[%d site(s) of array(dims=%s)]" % (len(self.fovs.keys()), unique_dimensions_str)

        # return
        return "FOVData(fovs=%s, site_uid_list=%s, instrument_array=%s)" % (fovs_str, self.site_uid_list.__repr__(), self.instrument_array)

    def pretty_print(self):
        """
        A special print output for this class.
        """
        # set special strings
        unique_dimensions_str = str(list(dict.fromkeys(self.fovs_dimensions.values()))).replace("[", "").replace("]", "").replace("), (", "),(")
        fovs_str = "Dict[%d site(s) of array(dims=%s)]" % (len(self.fovs.keys()), unique_dimensions_str)

        # print
        print("FovData:")
        print("  %-19s: %s" % ("instrument_array", self.instrument_array))
        print("  %-19s: %s" % ("site_uid_list", self.site_uid_list))
        print("  %-19s: %s" % ("fovs", fovs_str))

    def add_availability(self, dataset_name: str, start: datetime.datetime, end: datetime.datetime):
        """
        Add data availability information to an FOVData object. Given a start and end time, information
        will be added to the object regarding whether or not each site included in the FOVData object
        took data in that time range. This is useful for plotting FOVs for only those sites that took
        data in a given time interval.

        Args:
            dataset_name (str): 
                The name of the dataset to check for data availability (e.g. "REGO_RAW")

            start (datetime.datetime): 
                Defines the start time of the interval to check for data availability.
                
            end (datetime.datetime): 
                Defines the end time of the interval to check for data availability.

        Returns:
            The FOVData object is updated to hold data availability information, that can be used when
            plotting to omit sites that did not take data during the time interval defined by start and end.

        Raises:
        """

        if (self.instrument_array is None):
            raise ValueError("Cannot add data availability to an FOVData object with no associated instrument_array. Please specify " +
                             "instrument_array upon creation of FOVData object to enforce data availability.")

        # Check if the requested dataset makes sense for the FOVData
        fov_instrument = self.instrument_array.upper()
        if (fov_instrument == "TREX_SPECTROGRAPH"):
            fov_instrument = "TREX_SPECT"

        if (fov_instrument not in dataset_name):
            raise ValueError("Requested dataset_name does not match the instrument_array contained in this FOVData object.")

        # Create a dictionary corresponding to the FoV data, that will hold
        # booleans specifying whether or not there is data for each site
        availability_dict = {}
        for site in self.fovs.keys():

            # Request list of all files of requested dataset at requested site
            result = self.__aurorax_obj.data.ucalgary.get_urls(dataset_name, start, end, site_uid=site)

            # If there are any files within the desired time-range then update availability dict with True, otherwise update False
            if (result.count > 0):
                availability_dict[site] = True
            else:
                availability_dict[site] = False

        self.data_availability = availability_dict

Prepared ASI FoV data for use by FOV routines or manual plotting.

Attributes

site_uid_list : List[str]
List of site unique identifiers contained within this object.
fovs : Dict[str, numpy.ndarray]
Dictionary that holds the lat/lon data in a 2xN numpy array giving the FoV, for each site UID.
fovs_dimensions : Dict[str, numpy.ndarray]
Dictionary that holds the shape of each set of lat/lon data.
instrument_array : str
String giving the name of the instrument array this FOVData object corresponds to (optional).
data_availability : dict
An optional dictionary containing information about which sites included in the FOVData actually have available data for a given time range. Usually created using the FOVData.add_Availability() method.
color : str
String specifying the color to use when plotting this FOVData.
linewidth : str
Integer specifying the linewidth to use when plotting this FOVData.
linestyle : str
String (matplotlib.pyplot format code) specifying the linestyle to use when plotting this FOVData.

Methods

def add_availability(self, dataset_name: str, start: datetime.datetime, end: datetime.datetime)
Expand source code
def add_availability(self, dataset_name: str, start: datetime.datetime, end: datetime.datetime):
    """
    Add data availability information to an FOVData object. Given a start and end time, information
    will be added to the object regarding whether or not each site included in the FOVData object
    took data in that time range. This is useful for plotting FOVs for only those sites that took
    data in a given time interval.

    Args:
        dataset_name (str): 
            The name of the dataset to check for data availability (e.g. "REGO_RAW")

        start (datetime.datetime): 
            Defines the start time of the interval to check for data availability.
            
        end (datetime.datetime): 
            Defines the end time of the interval to check for data availability.

    Returns:
        The FOVData object is updated to hold data availability information, that can be used when
        plotting to omit sites that did not take data during the time interval defined by start and end.

    Raises:
    """

    if (self.instrument_array is None):
        raise ValueError("Cannot add data availability to an FOVData object with no associated instrument_array. Please specify " +
                         "instrument_array upon creation of FOVData object to enforce data availability.")

    # Check if the requested dataset makes sense for the FOVData
    fov_instrument = self.instrument_array.upper()
    if (fov_instrument == "TREX_SPECTROGRAPH"):
        fov_instrument = "TREX_SPECT"

    if (fov_instrument not in dataset_name):
        raise ValueError("Requested dataset_name does not match the instrument_array contained in this FOVData object.")

    # Create a dictionary corresponding to the FoV data, that will hold
    # booleans specifying whether or not there is data for each site
    availability_dict = {}
    for site in self.fovs.keys():

        # Request list of all files of requested dataset at requested site
        result = self.__aurorax_obj.data.ucalgary.get_urls(dataset_name, start, end, site_uid=site)

        # If there are any files within the desired time-range then update availability dict with True, otherwise update False
        if (result.count > 0):
            availability_dict[site] = True
        else:
            availability_dict[site] = False

    self.data_availability = availability_dict

Add data availability information to an FOVData object. Given a start and end time, information will be added to the object regarding whether or not each site included in the FOVData object took data in that time range. This is useful for plotting FOVs for only those sites that took data in a given time interval.

Args

dataset_name : str
The name of the dataset to check for data availability (e.g. "REGO_RAW")
start : datetime.datetime
Defines the start time of the interval to check for data availability.
end : datetime.datetime
Defines the end time of the interval to check for data availability.

Returns

The FOVData object is updated to hold data availability information, that can be used when plotting to omit sites that did not take data during the time interval defined by start and end. Raises:

def pretty_print(self)
Expand source code
def pretty_print(self):
    """
    A special print output for this class.
    """
    # set special strings
    unique_dimensions_str = str(list(dict.fromkeys(self.fovs_dimensions.values()))).replace("[", "").replace("]", "").replace("), (", "),(")
    fovs_str = "Dict[%d site(s) of array(dims=%s)]" % (len(self.fovs.keys()), unique_dimensions_str)

    # print
    print("FovData:")
    print("  %-19s: %s" % ("instrument_array", self.instrument_array))
    print("  %-19s: %s" % ("site_uid_list", self.site_uid_list))
    print("  %-19s: %s" % ("fovs", fovs_str))

A special print output for this class.

class Keogram (data: numpy.ndarray,
timestamp: List[datetime.datetime],
instrument_type: str,
slice_idx: int | None = None,
ccd_y: numpy.ndarray | None = None,
mag_y: numpy.ndarray | None = None,
geo_y: numpy.ndarray | None = None)
Expand source code
@dataclass
class Keogram:
    """
    Class representation for a keogram

    Attributes:
        data (numpy.ndarray): 
            The derived keogram data.

        timestamp (List[datetime.datetime]): 
            Timestamps corresponding to each keogram slice.

        instrument_type (str): 
            String giving instrument type, either 'asi' or 'spectrograph'.

        ccd_y (numpy.ndarray): 
            The y-axis representing CCD Y coordinates for the keogram.

        mag_y (numpy.ndarray): 
            The y-axis representing magnetic latitude for the keogram.

        geo_y (numpy.ndarray): 
            The y-axis representing geographic latitude for the keogram.
    """

    def __init__(self,
                 data: np.ndarray,
                 timestamp: List[datetime.datetime],
                 instrument_type: str,
                 slice_idx: Optional[int] = None,
                 ccd_y: Optional[np.ndarray] = None,
                 mag_y: Optional[np.ndarray] = None,
                 geo_y: Optional[np.ndarray] = None):
        # public vars
        self.data = data
        self.timestamp = timestamp
        self.instrument_type = instrument_type
        self.ccd_y = ccd_y
        self.mag_y = mag_y
        self.geo_y = geo_y

        # private vars
        self.__slice_idx = slice_idx

    def __str__(self) -> str:
        return self.__repr__()

    def __repr__(self) -> str:
        data_str = "array(dims=%s, dtype=%s)" % (self.data.shape, self.data.dtype)
        timestamp_str = "[%d datetime objects]" % (len(self.timestamp))
        ccd_y_str = "None" if self.ccd_y is None else "array(%d values)" % (self.ccd_y.shape[0])
        mag_y_str = "None" if self.mag_y is None else "array(%d values)" % (self.mag_y.shape[0])
        geo_y_str = "None" if self.geo_y is None else "array(%d values)" % (self.geo_y.shape[0])

        return "Keogram(data=%s, timestamp=%s, ccd_y=%s, mag_y=%s, geo_y=%s)" % (data_str, timestamp_str, ccd_y_str, mag_y_str, geo_y_str)

    def pretty_print(self):
        """
        A special print output for this class.
        """
        # set special strings
        data_str = "array(dims=%s, dtype=%s)" % (self.data.shape, self.data.dtype)
        timestamp_str = "[%d datetime objects]" % (len(self.timestamp))
        ccd_y_str = "None" if self.ccd_y is None else "array(%d values)" % (self.ccd_y.shape[0])
        mag_y_str = "None" if self.mag_y is None else "array(%d values)" % (self.mag_y.shape[0])
        geo_y_str = "None" if self.geo_y is None else "array(%d values)" % (self.geo_y.shape[0])

        # print
        print("Keogram:")
        print("  %-17s: %s" % ("data", data_str))
        print("  %-17s: %s" % ("timestamp", timestamp_str))
        print("  %-17s: %s" % ("instrument_type", self.instrument_type))
        print("  %-17s: %s" % ("ccd_y", ccd_y_str))
        print("  %-17s: %s" % ("geo_y", geo_y_str))
        print("  %-17s: %s" % ("mag_y", mag_y_str))

    def set_geographic_latitudes(self, skymap: Skymap, altitude_km: Optional[Union[int, float]] = None) -> None:
        """
        Set the geographic latitude values for this keogram, using the specified skymap 
        data. The data will be set to the geo_y attribute of this Keogram object, which
        can then be used for plotting and/or further analysis.

        Args:
            skymap (pyaurorax.data.ucalgary.Skymap): 
                The skymap object to use. This parameter is required.

            altitude_km (int): 
                The altitude to use, in kilometers. If not specified, it will use the default in the 
                skymap object. If the specified altitude is not valid, a ValueError will be raised.
        
        Returns:
            None. The Keogram object's `geo_y` attribute will be updated.

        Raises:
            ValueError: Issues with specified altitude.
        """
        # check for slice idx
        if (self.__slice_idx is None):

            raise ValueError("Unable to set the geographic latitudes since the private slice_idx is None. If this keogram " +
                             "object was created as part of the custom_keogram routines or is a spectrogaph keogram, " +
                             "this is expected and performing this action is not supported at this time.")

        # Check the dimensions of the skymap lat/lon arrays
        # If they are 2-dimensional [altitude_idx, y] instead of [altitude_idx, y, x] then we know it is a spectrograph
        # skymap. In this case, we will simply reform to add an additional dimension, so that self.__slice_idx (which is
        # always zero for spectrograph data as there is only one longitudinal bin) can be used to index into the array
        # the same as it would be for ASI data
        if len(skymap.full_map_latitude.shape) == 2:
            # Reform all spectrograph skymap arrays to have an extra dimension, for indexing purposes
            skymap.full_map_latitude = skymap.full_map_latitude[:, :, np.newaxis]
            skymap.full_map_longitude = skymap.full_map_longitude[:, :, np.newaxis]
            skymap.full_elevation = skymap.full_elevation[:, np.newaxis]

        # determine altitude index to use
        if (altitude_km is not None):
            # Obtain lat/lon arrays from skymap
            if (altitude_km * 1000.0 in skymap.full_map_altitude):
                altitude_idx = np.where(altitude_km * 1000.0 == skymap.full_map_altitude)

                if skymap.full_map_latitude.shape[-1] == 1:
                    self.geo_y = np.squeeze(skymap.full_map_latitude[altitude_idx, :, 0]).copy()
                else:
                    self.geo_y = np.squeeze(skymap.full_map_latitude[altitude_idx, :, self.__slice_idx]).copy()
            else:
                # Make sure altitude is in range that can be interpolated
                if (altitude_km * 1000.0 < skymap.full_map_altitude[0]) or (altitude_km * 1000.0 > skymap.full_map_altitude[2]):
                    raise ValueError("Altitude " + str(altitude_km) + " outside valid range of " +
                                     str((skymap.full_map_altitude[0] / 1000.0, skymap.full_map_altitude[2] / 1000.0)))

                # Initialze empty lat/lon arrays
                lats = np.full(np.squeeze(skymap.full_map_latitude[0, :, :]).shape, np.nan, dtype=skymap.full_map_latitude[0, :, :].dtype)

                # Interpolate lats and lons at desired altitude
                for i in range(skymap.full_map_latitude.shape[1]):
                    for j in range(skymap.full_map_latitude.shape[2]):
                        lats[i, j] = np.interp(altitude_km * 1000.0, skymap.full_map_altitude, skymap.full_map_latitude[:, i, j])

                self.geo_y = lats[:, self.__slice_idx].copy()
        else:
            # use default middle altitude
            if skymap.full_map_latitude.shape[-1] == 1:
                self.geo_y = np.squeeze(skymap.full_map_latitude[1, :, 0]).copy()
            else:
                self.geo_y = np.squeeze(skymap.full_map_latitude[1, :, self.__slice_idx]).copy()

    def set_magnetic_latitudes(self, skymap: Skymap, timestamp: datetime.datetime, altitude_km: Optional[Union[int, float]] = None) -> None:
        """
        Set the magnetic latitude values for this keogram, using the specified skymap 
        data. AACGMv2 will be utilized to perform the calculations. The resulting data
        will be set to the mag_y attribute of this Keogram object, which can then be
        used for plotting and/or further analysis.

        Args:
            skymap (pyaurorax.data.ucalgary.Skymap): 
                The skymap object to use. This parameter is required.

            timestamp (datetime.datetime): 
                The timestamp to use when converting skymap data to magnetic coordinates. Utilizes
                AACGMv2 to do the conversion.

            altitude_km (int): 
                The altitude to use. If not specified, it will use the default in the skymap
                object. If the specified altitude is not valid, a ValueError will be raised.
        
        Returns:
            None. The Keogram object's `mag_y` attribute will be updated.

        Raises:
            ValueError: Issues with specified altitude.
        """

        # check for slice idx
        if (self.__slice_idx is None):
            raise ValueError("Unable to set the magnetic latitudes since the slice_idx is None. If this keogram " +
                             "object was created as part of the custom_keogram routines or is a spectrogaph keogram, " +
                             "this is expected and performing this action is not supported at this time.")

        # Check the dimensions of the skymap lat/lon arrays
        # If they are 2-dimensional [altitude_idx, y] instead of [altitude_idx, y, x] then we know it is a spectrograph
        # skymap. In this case, we will simply reform to add an additional dimension, so that self.__slice_idx (which is
        # always zero for spectrograph data as there is only one longitudinal bin) can be used to index into the array
        # the same as it would be for ASI data
        if len(skymap.full_map_latitude.shape) == 2:
            # Reform all spectrograph skymap arrays to have an extra dimension, for indexing purposes
            skymap.full_map_latitude = skymap.full_map_latitude[:, :, np.newaxis]
            skymap.full_map_longitude = skymap.full_map_longitude[:, :, np.newaxis]
            skymap.full_elevation = skymap.full_elevation[:, np.newaxis]

        # determine altitude index to use
        if (altitude_km is not None):
            # Obtain lat/lon arrays from skymap
            if (altitude_km * 1000.0 in skymap.full_map_altitude):
                altitude_idx = np.where(altitude_km * 1000.0 == skymap.full_map_altitude)

                lats = np.squeeze(skymap.full_map_latitude[altitude_idx, :, :])
                lons = np.squeeze(skymap.full_map_longitude[altitude_idx, :, :])
                lons[np.where(lons > 180)] -= 360.0

            else:
                # Make sure altitude is in range that can be interpolated
                if (altitude_km * 1000.0 < skymap.full_map_altitude[0]) or (altitude_km * 1000.0 > skymap.full_map_altitude[2]):
                    raise ValueError("Altitude " + str(altitude_km) + " outside valid range of " +
                                     str((skymap.full_map_altitude[0] / 1000.0, skymap.full_map_altitude[2] / 1000.0)))

                # Initialze empty lat/lon arrays
                lats = np.full(np.squeeze(skymap.full_map_latitude[0, :, :]).shape, np.nan, dtype=skymap.full_map_latitude[0, :, :].dtype)
                lons = lats.copy()

                # Interpolate lats and lons at desired altitude
                for i in range(skymap.full_map_latitude.shape[1]):
                    for j in range(skymap.full_map_latitude.shape[2]):
                        lats[i, j] = np.interp(altitude_km * 1000.0, skymap.full_map_altitude, skymap.full_map_latitude[:, i, j])
                        lons[i, j] = np.interp(altitude_km * 1000.0, skymap.full_map_altitude, skymap.full_map_longitude[:, i, j])

                lons[np.where(lons > 180)] -= 360.0

            # Convert lats and lons to geomagnetic coordinates
            mag_lats, mag_lons, _ = aacgmv2.convert_latlon_arr(lats.flatten(), lons.flatten(), (lons * 0.0).flatten(), timestamp, method_code='G2A')
            mag_lats = np.reshape(mag_lats, lats.shape)
            mag_lons = np.reshape(mag_lons, lons.shape)

            # If lat/lon arrays are 1-dimensional then we know it is a spectrograph skymap. In this case, we will simply
            # reform to add an additional dimension, so that self.__slice_idx (which is always zero for spectrograph data
            # as there is only one longitudinal bin) can be used to index into the array the same as it would be for ASI data
            if len(mag_lats.shape) == 1:
                mag_lats = mag_lats[:, np.newaxis]
                mag_lons = mag_lons[:, np.newaxis]

            # Set the y axis to the desired slice index of the magnetic latitudes
            self.mag_y = mag_lats[:, self.__slice_idx].copy()
        else:
            # Convert middle altitude lats and lons to geomagnetic coordinates
            mag_lats, mag_lons, _ = aacgmv2.convert_latlon_arr(np.squeeze(skymap.full_map_latitude[1, :, :]).flatten(),
                                                               np.squeeze(skymap.full_map_longitude[1, :, :]).flatten(),
                                                               (skymap.full_map_longitude[1, :, :] * 0.0).flatten(),
                                                               timestamp,
                                                               method_code='G2A')
            mag_lats = np.reshape(mag_lats, np.squeeze(skymap.full_map_latitude[1, :, :]).shape)
            mag_lons = np.reshape(mag_lons, np.squeeze(skymap.full_map_longitude[1, :, :]).shape)

            # If lat/lon arrays are 1-dimensional then we know it is a spectrograph skymap. In this case, we will simply
            # reform to add an additional dimension, so that self.__slice_idx (which is always zero for spectrograph data
            # as there is only one longitudinal bin) can be used to index into the array the same as it would be for ASI data
            if len(mag_lats.shape) == 1:
                mag_lats = mag_lats[:, np.newaxis]
                mag_lons = mag_lons[:, np.newaxis]

            # Set the y axis to the desired slice index of the magnetic latitudes
            self.mag_y = mag_lats[:, self.__slice_idx].copy()

    def plot(self,
             y_type: Literal["ccd", "mag", "geo"] = "ccd",
             title: Optional[str] = None,
             figsize: Optional[Tuple[int, int]] = None,
             cmap: Optional[str] = None,
             aspect: Optional[Union[Literal["equal", "auto"], float]] = None,
             axes_visible: bool = True,
             xlabel: str = "Time (UTC)",
             ylabel: Optional[str] = None,
             xtick_increment: Optional[int] = None,
             ytick_increment: Optional[int] = None,
             returnfig: bool = False,
             savefig: bool = False,
             savefig_filename: Optional[str] = None,
             savefig_quality: Optional[int] = None) -> Any:
        """
        Generate a plot of the keogram data. 
        
        Either display it (default behaviour), save it to disk (using the `savefig` parameter), or 
        return the matplotlib plot object for further usage (using the `returnfig` parameter).

        Args:
            y_type (str): 
                Type of y-axis to use when plotting. Options are `ccd`, `mag`, or `geo`. The
                default is `ccd`. This parameter is required.

            title (str): 
                The title to display above the plotted keogram. Default is no title.

            figsize (tuple): 
                The matplotlib figure size to use when plotting. For example `figsize=(14,4)`.

            cmap (str): 
                The matplotlib colormap to use.

                Commonly used colormaps are:

                - REGO: `gist_heat`
                - THEMIS ASI: `gray`
                - TREx Blue: `Blues_r`
                - TREx NIR: `gray`
                - TREx RGB: `None`

                A list of all available colormaps can be found on the 
                [matplotlib documentation](https://matplotlib.org/stable/gallery/color/colormap_reference.html).
            
            aspect (str or float): 
                The matplotlib imshow aspect ration to use. A common value for this is `auto`. All valid values 
                can be found on the [matplotlib documentation](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.imshow.html).

            axes_visible (bool): 
                Display the axes. Default is `True`.

            xlabel (str): 
                The x-axis label to use. Default is `Time (UTC)`.

            ylabel (str): 
                The y-axis label to use. Default is based on y_type.

            xtick_increment (int): 
                The x-axis tick increment to use. Default is 100.

            ytick_increment (int): 
                The y-axis tick increment to use. Default is 50.

            returnfig (bool): 
                Instead of displaying the image, return the matplotlib figure object. This allows for further plot 
                manipulation, for example, adding labels or a title in a different location than the default. 
                
                Remember - if this parameter is supplied, be sure that you close your plot after finishing work 
                with it. This can be achieved by doing `plt.close(fig)`. 
                
                Note that this method cannot be used in combination with `savefig`.

            savefig (bool): 
                Save the displayed image to disk instead of displaying it. The parameter savefig_filename is required if 
                this parameter is set to True. Defaults to `False`.

            savefig_filename (str): 
                Filename to save the image to. Must be specified if the savefig parameter is set to True.

            savefig_quality (int): 
                Quality level of the saved image. This can be specified if the savefig_filename is a JPG image. If it
                is a PNG, quality is ignored. Default quality level for JPGs is matplotlib/Pillow's default of 75%.

        Returns:
            The displayed keogram, by default. If `savefig` is set to True, nothing will be returned. If `returnfig` is 
            set to True, the plotting variables `(fig, ax)` will be returned.

        Raises:
            ValueError: issues encountered with the y-axis choice
        """
        # check return mode
        if (returnfig is True and savefig is True):
            raise ValueError("Only one of returnfig or savefig can be set to True")
        if returnfig is True and (savefig_filename is not None or savefig_quality is not None):
            show_warning("The figure will be returned, but a savefig option parameter was supplied. Consider " +
                         "removing the savefig option parameter(s) as they will be ignored.",
                         stacklevel=1)
        elif savefig is False and (savefig_filename is not None or savefig_quality is not None):
            show_warning("A savefig option parameter was supplied, but the savefig parameter is False. The " +
                         "savefig option parameters will be ignored.",
                         stacklevel=1)

        # init figure and plot data
        fig = plt.figure(figsize=figsize)
        ax = fig.add_axes((0, 0, 1, 1))

        # If RGB, we need to normalize for matplotlib
        if len(self.data.shape) == 3:
            img_arr = self.data / 255.0
        else:
            img_arr = self.data

        ax.imshow(img_arr, origin="lower", cmap=cmap, aspect=aspect)

        # set title
        if (title is not None):
            ax.set_title(title)

        # set axes
        if (axes_visible is True):
            # do checks for y-axis that was chosen
            if (y_type == "geo" and self.geo_y is None):
                raise ValueError("Unable to plot using geo_y data. The geo_y attribute is currently None, so either populate "
                                 "it with data using the set_geographic_latitudes() function, or choose a different y_type")
            elif (y_type == "mag" and self.mag_y is None):
                raise ValueError("Unable to plot using mag_y data. The mag_y attribute is currently None, so either populate "
                                 "it with data using the set_magnetic_latitudes() function, or choose a different y_type")

            # set y axis data, and y label
            y_axis_data = self.ccd_y
            if (y_type == "mag"):
                y_axis_data = self.mag_y
                if (ylabel is None):
                    ylabel = "Magnetic latitude"
            elif (y_type == "geo"):
                y_axis_data = self.geo_y
                if (ylabel is None):
                    ylabel = "Geographic latitude"
            else:
                if (ylabel is None):
                    ylabel = "CCD Y"

            # print labels
            ax.set_xlabel(xlabel, fontsize=14)
            ax.set_ylabel(ylabel, fontsize=14)

            # generate x ticks and labels
            #
            # TODO: make this more dynamic
            if (xtick_increment is None):
                xtick_increment = 100  # assume data is 3 second cadence; good enough for now
            x_ticks = np.arange(0, self.data.shape[1], xtick_increment)
            x_labels = self.timestamp[::xtick_increment]
            for i in range(0, len(x_labels)):
                x_labels[i] = x_labels[i].strftime("%H:%M")  # type: ignore
            ax.set_xticks(x_ticks, x_labels)  # type: ignore

            # do check for ccd_y
            if (self.ccd_y is None):
                show_warning(
                    "Unable to plot CCD y-axis. If this keogram object was create as part of the custom_keogram " +
                    "routines, this is expected and plotting a custom keogram with axes is not supported at this time.",
                    stacklevel=1,
                )
                ylabel = "Keogram Y"
            else:
                # generate y ticks and labels
                if (y_type == "ccd"):
                    # TODO: make this more dynamic
                    if (ytick_increment is None):
                        ytick_increment = 50

                    # generate y ticks and labels
                    y_ticks = y_axis_data[::ytick_increment]  # type: ignore
                    y_labels = y_axis_data[::ytick_increment]  # type: ignore

                    # apply yticks
                    ax.set_yticks(y_ticks, y_labels)  # type: ignore
                elif (y_type == "geo" and self.geo_y is not None) or (y_type == "mag" and self.mag_y is not None):
                    # set tick increments
                    if (ytick_increment is None):
                        ytick_increment = 50

                    # generate y ticks and labels
                    y_ticks = self.ccd_y[25::ytick_increment]
                    y_labels = np.round(
                        y_axis_data,  # type: ignore
                        1).astype(str)[25::ytick_increment]
                    y_labels[np.where(y_labels == 'nan')] = ''

                    # apply yticks
                    ax.set_yticks(y_ticks, y_labels)
        else:
            # disable axes
            ax.set_axis_off()

        # save figure or show it
        if (savefig is True):
            # check that filename has been set
            if (savefig_filename is None):
                raise ValueError("The savefig_filename parameter is missing, but required since savefig was set to True.")

            # save the figure
            f_extension = os.path.splitext(savefig_filename)[-1].lower()
            if (".jpg" == f_extension or ".jpeg" == f_extension):
                # check quality setting
                if (savefig_quality is not None):
                    plt.savefig(savefig_filename, pil_kwargs={"quality": savefig_quality}, bbox_inches="tight")
                else:
                    plt.savefig(savefig_filename, bbox_inches="tight")
            else:
                if (savefig_quality is not None):
                    # quality specified, but output filename is not a JPG, so show a warning
                    show_warning("The savefig_quality parameter was specified, but is only used for saving JPG files. The " +
                                 "savefig_filename parameter was determined to not be a JPG file, so the quality will be ignored",
                                 stacklevel=1)
                plt.savefig(savefig_filename, bbox_inches="tight")

            # clean up by closing the figure
            plt.close(fig)
        elif (returnfig is True):
            # return the figure and axis objects
            return (fig, ax)
        else:
            # show the figure
            plt.show(fig)

            # cleanup by closing the figure
            plt.close(fig)

        # return
        return None

    def inject_nans(
        self,
        cadence: Optional[Union[int, float]] = None,
    ) -> None:
        """
        Fill keogram columns that do not have data with NaNs.

        Args:
            cadence (int or float): 
                The cadence, in seconds, of the data for the keogram. Default is to automatically
                determine the cadence based on the keogram's timestamp data
        
        Returns:
            None. If there is missing data, the Keogram object's data and timestamp attributes
            will be updated accordingly.

        # Raises:
            ValueError if called on a keogram with improper / corrupted data format / shape.
        """

        # First, regardless of whether or not the user supplies a cadence, determine the cadence
        # based on the keograms timestamp attribute. If the user provided cadence is different
        # then that calculated, we will raise a Warning
        apparent_cadence = _determine_cadence(self.timestamp)
        if not isinstance(apparent_cadence, (float, int)):
            raise ValueError("Could not determine cadence from object Keogram.timestamp.")  # pragma: nocover

        if (cadence is not None) and (apparent_cadence != cadence):
            warning_str = ("Based on the keogram's timestamp attribute, the apparent cadence is %.2f s, but %.2f s was "
                           "passed in as an argument. Ensure that the selected cadence of %.2f s is correct for the dataset being used.")
            show_warning(warning_str % (apparent_cadence, cadence, cadence), stacklevel=1)

        # If a cadence was not supplied by the user, then use the apparent cadence calculated based on timestamps
        if cadence is None:
            cadence = apparent_cadence

        # The first step is checking if there actually is any missing data in this keogram. To do this we
        # find the total number of frames that there should be, based on the first and last timestamp, and
        # check if there are indeed that many frames in the keogram
        start_dt = self.timestamp[0]
        end_dt = self.timestamp[-1]

        # If cadence is supplied as or calculated to be (burst) a float, it needs to be
        # handled on the order of milliseconds
        if isinstance(cadence, float):

            n_desired_frames = round(((end_dt - start_dt).seconds + (end_dt - start_dt).microseconds * 10**(-6)) / cadence + 1)
            n_keogram_frames = (self.data.shape)[1]

            if cadence < 1.0:
                is_burst = True
            else:
                is_burst = False

        # Otherwise, handle timestamps on the order of seconds
        else:
            n_desired_frames = round(((end_dt - start_dt).seconds) / cadence + 1)
            n_keogram_frames = (self.data.shape)[1]
            is_burst = False

        # If the keogram is not missing any data, nothing is changed
        if n_desired_frames == n_keogram_frames:
            return

        # Otherwise, we need to find which desired timestamps are missing

        # First, create a new keogram array and new timestamp list with the correct size for the desired number of frames
        if len(self.data.shape) == 2:
            desired_keogram_shape = (self.data.shape[0], n_desired_frames)
        elif len(self.data.shape) == 3:
            desired_keogram_shape = (self.data.shape[0], n_desired_frames, self.data.shape[2])
        else:
            raise ValueError(f"Could not inject NaNs into keogram with data shape {self.data.shape}")  # pragma: nocover

        desired_keogram = np.empty(shape=desired_keogram_shape)
        desired_timestamp = []
        desired_timestamp_indices = []

        if is_burst:
            tol = datetime.timedelta(seconds=(1.0 / 6.0))
        else:
            tol = datetime.timedelta(seconds=1.0)

        # Fill the list of desired timestamps based on the cadence

        # For each *desired* timestamp, we use a binary search to determine whether
        # or not that timestamp already exists in the data, within tolerance
        target_dt = start_dt
        for _ in range(n_desired_frames):

            low = 0
            high = len(self.timestamp) - 1
            match_idx = None

            # binary search
            while low <= high:
                mid = (low + high) // 2
                mid_ts = self.timestamp[mid]

                if mid_ts < target_dt - tol:
                    low = mid + 1
                elif mid_ts > target_dt + tol:
                    high = mid - 1
                else:
                    # Match, within tolerance, has been found
                    match_idx = mid

                    high = mid - 1

            # If we've found a matching timestamp, insert it into the new timestamp array, and
            # otherwise insert the desired timestamp
            if match_idx is not None:
                desired_timestamp.append(self.timestamp[match_idx])
            else:
                desired_timestamp.append(target_dt)

            # Add the index into the original keogram corresponding to this timestamp if it exists
            # and otherwise add None to the index tracking list, which we will use to insert the
            # data and NaN columns
            desired_timestamp_indices.append(match_idx)

            # Update the desired datetime according to the cadence
            target_dt += datetime.timedelta(seconds=cadence)

        # Now that we have our desired timestamps, we can go through and fill the new keogram array
        for i in range(len(desired_timestamp)):

            keo_idx = desired_timestamp_indices[i]

            # If this desired timestamp had no data, fill the keogram column with nans
            if keo_idx is None:
                desired_keogram[:, i] = np.nan
            # Otherwise, keep the data intact for this column
            else:
                desired_keogram[:, i] = self.data[:, keo_idx]

        # Update the keogram object with the new data and timestamp arrays
        self.data = desired_keogram
        self.timestamp = desired_timestamp

Class representation for a keogram

Attributes

data : numpy.ndarray
The derived keogram data.
timestamp : List[datetime.datetime]
Timestamps corresponding to each keogram slice.
instrument_type : str
String giving instrument type, either 'asi' or 'spectrograph'.
ccd_y : numpy.ndarray
The y-axis representing CCD Y coordinates for the keogram.
mag_y : numpy.ndarray
The y-axis representing magnetic latitude for the keogram.
geo_y : numpy.ndarray
The y-axis representing geographic latitude for the keogram.

Methods

def inject_nans(self, cadence: int | float | None = None) ‑> None
Expand source code
def inject_nans(
    self,
    cadence: Optional[Union[int, float]] = None,
) -> None:
    """
    Fill keogram columns that do not have data with NaNs.

    Args:
        cadence (int or float): 
            The cadence, in seconds, of the data for the keogram. Default is to automatically
            determine the cadence based on the keogram's timestamp data
    
    Returns:
        None. If there is missing data, the Keogram object's data and timestamp attributes
        will be updated accordingly.

    # Raises:
        ValueError if called on a keogram with improper / corrupted data format / shape.
    """

    # First, regardless of whether or not the user supplies a cadence, determine the cadence
    # based on the keograms timestamp attribute. If the user provided cadence is different
    # then that calculated, we will raise a Warning
    apparent_cadence = _determine_cadence(self.timestamp)
    if not isinstance(apparent_cadence, (float, int)):
        raise ValueError("Could not determine cadence from object Keogram.timestamp.")  # pragma: nocover

    if (cadence is not None) and (apparent_cadence != cadence):
        warning_str = ("Based on the keogram's timestamp attribute, the apparent cadence is %.2f s, but %.2f s was "
                       "passed in as an argument. Ensure that the selected cadence of %.2f s is correct for the dataset being used.")
        show_warning(warning_str % (apparent_cadence, cadence, cadence), stacklevel=1)

    # If a cadence was not supplied by the user, then use the apparent cadence calculated based on timestamps
    if cadence is None:
        cadence = apparent_cadence

    # The first step is checking if there actually is any missing data in this keogram. To do this we
    # find the total number of frames that there should be, based on the first and last timestamp, and
    # check if there are indeed that many frames in the keogram
    start_dt = self.timestamp[0]
    end_dt = self.timestamp[-1]

    # If cadence is supplied as or calculated to be (burst) a float, it needs to be
    # handled on the order of milliseconds
    if isinstance(cadence, float):

        n_desired_frames = round(((end_dt - start_dt).seconds + (end_dt - start_dt).microseconds * 10**(-6)) / cadence + 1)
        n_keogram_frames = (self.data.shape)[1]

        if cadence < 1.0:
            is_burst = True
        else:
            is_burst = False

    # Otherwise, handle timestamps on the order of seconds
    else:
        n_desired_frames = round(((end_dt - start_dt).seconds) / cadence + 1)
        n_keogram_frames = (self.data.shape)[1]
        is_burst = False

    # If the keogram is not missing any data, nothing is changed
    if n_desired_frames == n_keogram_frames:
        return

    # Otherwise, we need to find which desired timestamps are missing

    # First, create a new keogram array and new timestamp list with the correct size for the desired number of frames
    if len(self.data.shape) == 2:
        desired_keogram_shape = (self.data.shape[0], n_desired_frames)
    elif len(self.data.shape) == 3:
        desired_keogram_shape = (self.data.shape[0], n_desired_frames, self.data.shape[2])
    else:
        raise ValueError(f"Could not inject NaNs into keogram with data shape {self.data.shape}")  # pragma: nocover

    desired_keogram = np.empty(shape=desired_keogram_shape)
    desired_timestamp = []
    desired_timestamp_indices = []

    if is_burst:
        tol = datetime.timedelta(seconds=(1.0 / 6.0))
    else:
        tol = datetime.timedelta(seconds=1.0)

    # Fill the list of desired timestamps based on the cadence

    # For each *desired* timestamp, we use a binary search to determine whether
    # or not that timestamp already exists in the data, within tolerance
    target_dt = start_dt
    for _ in range(n_desired_frames):

        low = 0
        high = len(self.timestamp) - 1
        match_idx = None

        # binary search
        while low <= high:
            mid = (low + high) // 2
            mid_ts = self.timestamp[mid]

            if mid_ts < target_dt - tol:
                low = mid + 1
            elif mid_ts > target_dt + tol:
                high = mid - 1
            else:
                # Match, within tolerance, has been found
                match_idx = mid

                high = mid - 1

        # If we've found a matching timestamp, insert it into the new timestamp array, and
        # otherwise insert the desired timestamp
        if match_idx is not None:
            desired_timestamp.append(self.timestamp[match_idx])
        else:
            desired_timestamp.append(target_dt)

        # Add the index into the original keogram corresponding to this timestamp if it exists
        # and otherwise add None to the index tracking list, which we will use to insert the
        # data and NaN columns
        desired_timestamp_indices.append(match_idx)

        # Update the desired datetime according to the cadence
        target_dt += datetime.timedelta(seconds=cadence)

    # Now that we have our desired timestamps, we can go through and fill the new keogram array
    for i in range(len(desired_timestamp)):

        keo_idx = desired_timestamp_indices[i]

        # If this desired timestamp had no data, fill the keogram column with nans
        if keo_idx is None:
            desired_keogram[:, i] = np.nan
        # Otherwise, keep the data intact for this column
        else:
            desired_keogram[:, i] = self.data[:, keo_idx]

    # Update the keogram object with the new data and timestamp arrays
    self.data = desired_keogram
    self.timestamp = desired_timestamp

Fill keogram columns that do not have data with NaNs.

Args

cadence : int or float
The cadence, in seconds, of the data for the keogram. Default is to automatically determine the cadence based on the keogram's timestamp data

Returns

None. If there is missing data, the Keogram object's data and timestamp attributes will be updated accordingly.

Raises:

ValueError if called on a keogram with improper / corrupted data format / shape.
def plot(self,
y_type: Literal['ccd', 'mag', 'geo'] = 'ccd',
title: str | None = None,
figsize: Tuple[int, int] | None = None,
cmap: str | None = None,
aspect: Literal['equal', 'auto'] | float | None = None,
axes_visible: bool = True,
xlabel: str = 'Time (UTC)',
ylabel: str | None = None,
xtick_increment: int | None = None,
ytick_increment: int | None = None,
returnfig: bool = False,
savefig: bool = False,
savefig_filename: str | None = None,
savefig_quality: int | None = None) ‑> Any
Expand source code
def plot(self,
         y_type: Literal["ccd", "mag", "geo"] = "ccd",
         title: Optional[str] = None,
         figsize: Optional[Tuple[int, int]] = None,
         cmap: Optional[str] = None,
         aspect: Optional[Union[Literal["equal", "auto"], float]] = None,
         axes_visible: bool = True,
         xlabel: str = "Time (UTC)",
         ylabel: Optional[str] = None,
         xtick_increment: Optional[int] = None,
         ytick_increment: Optional[int] = None,
         returnfig: bool = False,
         savefig: bool = False,
         savefig_filename: Optional[str] = None,
         savefig_quality: Optional[int] = None) -> Any:
    """
    Generate a plot of the keogram data. 
    
    Either display it (default behaviour), save it to disk (using the `savefig` parameter), or 
    return the matplotlib plot object for further usage (using the `returnfig` parameter).

    Args:
        y_type (str): 
            Type of y-axis to use when plotting. Options are `ccd`, `mag`, or `geo`. The
            default is `ccd`. This parameter is required.

        title (str): 
            The title to display above the plotted keogram. Default is no title.

        figsize (tuple): 
            The matplotlib figure size to use when plotting. For example `figsize=(14,4)`.

        cmap (str): 
            The matplotlib colormap to use.

            Commonly used colormaps are:

            - REGO: `gist_heat`
            - THEMIS ASI: `gray`
            - TREx Blue: `Blues_r`
            - TREx NIR: `gray`
            - TREx RGB: `None`

            A list of all available colormaps can be found on the 
            [matplotlib documentation](https://matplotlib.org/stable/gallery/color/colormap_reference.html).
        
        aspect (str or float): 
            The matplotlib imshow aspect ration to use. A common value for this is `auto`. All valid values 
            can be found on the [matplotlib documentation](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.imshow.html).

        axes_visible (bool): 
            Display the axes. Default is `True`.

        xlabel (str): 
            The x-axis label to use. Default is `Time (UTC)`.

        ylabel (str): 
            The y-axis label to use. Default is based on y_type.

        xtick_increment (int): 
            The x-axis tick increment to use. Default is 100.

        ytick_increment (int): 
            The y-axis tick increment to use. Default is 50.

        returnfig (bool): 
            Instead of displaying the image, return the matplotlib figure object. This allows for further plot 
            manipulation, for example, adding labels or a title in a different location than the default. 
            
            Remember - if this parameter is supplied, be sure that you close your plot after finishing work 
            with it. This can be achieved by doing `plt.close(fig)`. 
            
            Note that this method cannot be used in combination with `savefig`.

        savefig (bool): 
            Save the displayed image to disk instead of displaying it. The parameter savefig_filename is required if 
            this parameter is set to True. Defaults to `False`.

        savefig_filename (str): 
            Filename to save the image to. Must be specified if the savefig parameter is set to True.

        savefig_quality (int): 
            Quality level of the saved image. This can be specified if the savefig_filename is a JPG image. If it
            is a PNG, quality is ignored. Default quality level for JPGs is matplotlib/Pillow's default of 75%.

    Returns:
        The displayed keogram, by default. If `savefig` is set to True, nothing will be returned. If `returnfig` is 
        set to True, the plotting variables `(fig, ax)` will be returned.

    Raises:
        ValueError: issues encountered with the y-axis choice
    """
    # check return mode
    if (returnfig is True and savefig is True):
        raise ValueError("Only one of returnfig or savefig can be set to True")
    if returnfig is True and (savefig_filename is not None or savefig_quality is not None):
        show_warning("The figure will be returned, but a savefig option parameter was supplied. Consider " +
                     "removing the savefig option parameter(s) as they will be ignored.",
                     stacklevel=1)
    elif savefig is False and (savefig_filename is not None or savefig_quality is not None):
        show_warning("A savefig option parameter was supplied, but the savefig parameter is False. The " +
                     "savefig option parameters will be ignored.",
                     stacklevel=1)

    # init figure and plot data
    fig = plt.figure(figsize=figsize)
    ax = fig.add_axes((0, 0, 1, 1))

    # If RGB, we need to normalize for matplotlib
    if len(self.data.shape) == 3:
        img_arr = self.data / 255.0
    else:
        img_arr = self.data

    ax.imshow(img_arr, origin="lower", cmap=cmap, aspect=aspect)

    # set title
    if (title is not None):
        ax.set_title(title)

    # set axes
    if (axes_visible is True):
        # do checks for y-axis that was chosen
        if (y_type == "geo" and self.geo_y is None):
            raise ValueError("Unable to plot using geo_y data. The geo_y attribute is currently None, so either populate "
                             "it with data using the set_geographic_latitudes() function, or choose a different y_type")
        elif (y_type == "mag" and self.mag_y is None):
            raise ValueError("Unable to plot using mag_y data. The mag_y attribute is currently None, so either populate "
                             "it with data using the set_magnetic_latitudes() function, or choose a different y_type")

        # set y axis data, and y label
        y_axis_data = self.ccd_y
        if (y_type == "mag"):
            y_axis_data = self.mag_y
            if (ylabel is None):
                ylabel = "Magnetic latitude"
        elif (y_type == "geo"):
            y_axis_data = self.geo_y
            if (ylabel is None):
                ylabel = "Geographic latitude"
        else:
            if (ylabel is None):
                ylabel = "CCD Y"

        # print labels
        ax.set_xlabel(xlabel, fontsize=14)
        ax.set_ylabel(ylabel, fontsize=14)

        # generate x ticks and labels
        #
        # TODO: make this more dynamic
        if (xtick_increment is None):
            xtick_increment = 100  # assume data is 3 second cadence; good enough for now
        x_ticks = np.arange(0, self.data.shape[1], xtick_increment)
        x_labels = self.timestamp[::xtick_increment]
        for i in range(0, len(x_labels)):
            x_labels[i] = x_labels[i].strftime("%H:%M")  # type: ignore
        ax.set_xticks(x_ticks, x_labels)  # type: ignore

        # do check for ccd_y
        if (self.ccd_y is None):
            show_warning(
                "Unable to plot CCD y-axis. If this keogram object was create as part of the custom_keogram " +
                "routines, this is expected and plotting a custom keogram with axes is not supported at this time.",
                stacklevel=1,
            )
            ylabel = "Keogram Y"
        else:
            # generate y ticks and labels
            if (y_type == "ccd"):
                # TODO: make this more dynamic
                if (ytick_increment is None):
                    ytick_increment = 50

                # generate y ticks and labels
                y_ticks = y_axis_data[::ytick_increment]  # type: ignore
                y_labels = y_axis_data[::ytick_increment]  # type: ignore

                # apply yticks
                ax.set_yticks(y_ticks, y_labels)  # type: ignore
            elif (y_type == "geo" and self.geo_y is not None) or (y_type == "mag" and self.mag_y is not None):
                # set tick increments
                if (ytick_increment is None):
                    ytick_increment = 50

                # generate y ticks and labels
                y_ticks = self.ccd_y[25::ytick_increment]
                y_labels = np.round(
                    y_axis_data,  # type: ignore
                    1).astype(str)[25::ytick_increment]
                y_labels[np.where(y_labels == 'nan')] = ''

                # apply yticks
                ax.set_yticks(y_ticks, y_labels)
    else:
        # disable axes
        ax.set_axis_off()

    # save figure or show it
    if (savefig is True):
        # check that filename has been set
        if (savefig_filename is None):
            raise ValueError("The savefig_filename parameter is missing, but required since savefig was set to True.")

        # save the figure
        f_extension = os.path.splitext(savefig_filename)[-1].lower()
        if (".jpg" == f_extension or ".jpeg" == f_extension):
            # check quality setting
            if (savefig_quality is not None):
                plt.savefig(savefig_filename, pil_kwargs={"quality": savefig_quality}, bbox_inches="tight")
            else:
                plt.savefig(savefig_filename, bbox_inches="tight")
        else:
            if (savefig_quality is not None):
                # quality specified, but output filename is not a JPG, so show a warning
                show_warning("The savefig_quality parameter was specified, but is only used for saving JPG files. The " +
                             "savefig_filename parameter was determined to not be a JPG file, so the quality will be ignored",
                             stacklevel=1)
            plt.savefig(savefig_filename, bbox_inches="tight")

        # clean up by closing the figure
        plt.close(fig)
    elif (returnfig is True):
        # return the figure and axis objects
        return (fig, ax)
    else:
        # show the figure
        plt.show(fig)

        # cleanup by closing the figure
        plt.close(fig)

    # return
    return None

Generate a plot of the keogram data.

Either display it (default behaviour), save it to disk (using the savefig parameter), or return the matplotlib plot object for further usage (using the returnfig parameter).

Args

y_type : str
Type of y-axis to use when plotting. Options are ccd, mag, or geo. The default is ccd. This parameter is required.
title : str
The title to display above the plotted keogram. Default is no title.
figsize : tuple
The matplotlib figure size to use when plotting. For example figsize=(14,4).
cmap : str

The matplotlib colormap to use.

Commonly used colormaps are:

  • REGO: gist_heat
  • THEMIS ASI: gray
  • TREx Blue: Blues_r
  • TREx NIR: gray
  • TREx RGB: None

A list of all available colormaps can be found on the matplotlib documentation.

aspect : str or float
The matplotlib imshow aspect ration to use. A common value for this is auto. All valid values can be found on the matplotlib documentation.
axes_visible : bool
Display the axes. Default is True.
xlabel : str
The x-axis label to use. Default is Time (UTC).
ylabel : str
The y-axis label to use. Default is based on y_type.
xtick_increment : int
The x-axis tick increment to use. Default is 100.
ytick_increment : int
The y-axis tick increment to use. Default is 50.
returnfig : bool

Instead of displaying the image, return the matplotlib figure object. This allows for further plot manipulation, for example, adding labels or a title in a different location than the default.

Remember - if this parameter is supplied, be sure that you close your plot after finishing work with it. This can be achieved by doing plt.close(fig).

Note that this method cannot be used in combination with savefig.

savefig : bool
Save the displayed image to disk instead of displaying it. The parameter savefig_filename is required if this parameter is set to True. Defaults to False.
savefig_filename : str
Filename to save the image to. Must be specified if the savefig parameter is set to True.
savefig_quality : int
Quality level of the saved image. This can be specified if the savefig_filename is a JPG image. If it is a PNG, quality is ignored. Default quality level for JPGs is matplotlib/Pillow's default of 75%.

Returns

The displayed keogram, by default. If savefig is set to True, nothing will be returned. If returnfig is set to True, the plotting variables (fig, ax) will be returned.

Raises

ValueError
issues encountered with the y-axis choice
def pretty_print(self)
Expand source code
def pretty_print(self):
    """
    A special print output for this class.
    """
    # set special strings
    data_str = "array(dims=%s, dtype=%s)" % (self.data.shape, self.data.dtype)
    timestamp_str = "[%d datetime objects]" % (len(self.timestamp))
    ccd_y_str = "None" if self.ccd_y is None else "array(%d values)" % (self.ccd_y.shape[0])
    mag_y_str = "None" if self.mag_y is None else "array(%d values)" % (self.mag_y.shape[0])
    geo_y_str = "None" if self.geo_y is None else "array(%d values)" % (self.geo_y.shape[0])

    # print
    print("Keogram:")
    print("  %-17s: %s" % ("data", data_str))
    print("  %-17s: %s" % ("timestamp", timestamp_str))
    print("  %-17s: %s" % ("instrument_type", self.instrument_type))
    print("  %-17s: %s" % ("ccd_y", ccd_y_str))
    print("  %-17s: %s" % ("geo_y", geo_y_str))
    print("  %-17s: %s" % ("mag_y", mag_y_str))

A special print output for this class.

def set_geographic_latitudes(self,
skymap: pyucalgarysrs.data.classes.Skymap,
altitude_km: int | float | None = None) ‑> None
Expand source code
def set_geographic_latitudes(self, skymap: Skymap, altitude_km: Optional[Union[int, float]] = None) -> None:
    """
    Set the geographic latitude values for this keogram, using the specified skymap 
    data. The data will be set to the geo_y attribute of this Keogram object, which
    can then be used for plotting and/or further analysis.

    Args:
        skymap (pyaurorax.data.ucalgary.Skymap): 
            The skymap object to use. This parameter is required.

        altitude_km (int): 
            The altitude to use, in kilometers. If not specified, it will use the default in the 
            skymap object. If the specified altitude is not valid, a ValueError will be raised.
    
    Returns:
        None. The Keogram object's `geo_y` attribute will be updated.

    Raises:
        ValueError: Issues with specified altitude.
    """
    # check for slice idx
    if (self.__slice_idx is None):

        raise ValueError("Unable to set the geographic latitudes since the private slice_idx is None. If this keogram " +
                         "object was created as part of the custom_keogram routines or is a spectrogaph keogram, " +
                         "this is expected and performing this action is not supported at this time.")

    # Check the dimensions of the skymap lat/lon arrays
    # If they are 2-dimensional [altitude_idx, y] instead of [altitude_idx, y, x] then we know it is a spectrograph
    # skymap. In this case, we will simply reform to add an additional dimension, so that self.__slice_idx (which is
    # always zero for spectrograph data as there is only one longitudinal bin) can be used to index into the array
    # the same as it would be for ASI data
    if len(skymap.full_map_latitude.shape) == 2:
        # Reform all spectrograph skymap arrays to have an extra dimension, for indexing purposes
        skymap.full_map_latitude = skymap.full_map_latitude[:, :, np.newaxis]
        skymap.full_map_longitude = skymap.full_map_longitude[:, :, np.newaxis]
        skymap.full_elevation = skymap.full_elevation[:, np.newaxis]

    # determine altitude index to use
    if (altitude_km is not None):
        # Obtain lat/lon arrays from skymap
        if (altitude_km * 1000.0 in skymap.full_map_altitude):
            altitude_idx = np.where(altitude_km * 1000.0 == skymap.full_map_altitude)

            if skymap.full_map_latitude.shape[-1] == 1:
                self.geo_y = np.squeeze(skymap.full_map_latitude[altitude_idx, :, 0]).copy()
            else:
                self.geo_y = np.squeeze(skymap.full_map_latitude[altitude_idx, :, self.__slice_idx]).copy()
        else:
            # Make sure altitude is in range that can be interpolated
            if (altitude_km * 1000.0 < skymap.full_map_altitude[0]) or (altitude_km * 1000.0 > skymap.full_map_altitude[2]):
                raise ValueError("Altitude " + str(altitude_km) + " outside valid range of " +
                                 str((skymap.full_map_altitude[0] / 1000.0, skymap.full_map_altitude[2] / 1000.0)))

            # Initialze empty lat/lon arrays
            lats = np.full(np.squeeze(skymap.full_map_latitude[0, :, :]).shape, np.nan, dtype=skymap.full_map_latitude[0, :, :].dtype)

            # Interpolate lats and lons at desired altitude
            for i in range(skymap.full_map_latitude.shape[1]):
                for j in range(skymap.full_map_latitude.shape[2]):
                    lats[i, j] = np.interp(altitude_km * 1000.0, skymap.full_map_altitude, skymap.full_map_latitude[:, i, j])

            self.geo_y = lats[:, self.__slice_idx].copy()
    else:
        # use default middle altitude
        if skymap.full_map_latitude.shape[-1] == 1:
            self.geo_y = np.squeeze(skymap.full_map_latitude[1, :, 0]).copy()
        else:
            self.geo_y = np.squeeze(skymap.full_map_latitude[1, :, self.__slice_idx]).copy()

Set the geographic latitude values for this keogram, using the specified skymap data. The data will be set to the geo_y attribute of this Keogram object, which can then be used for plotting and/or further analysis.

Args

skymap : Skymap
The skymap object to use. This parameter is required.
altitude_km : int
The altitude to use, in kilometers. If not specified, it will use the default in the skymap object. If the specified altitude is not valid, a ValueError will be raised.

Returns

None. The Keogram object's geo_y attribute will be updated.

Raises

ValueError
Issues with specified altitude.
def set_magnetic_latitudes(self,
skymap: pyucalgarysrs.data.classes.Skymap,
timestamp: datetime.datetime,
altitude_km: int | float | None = None) ‑> None
Expand source code
def set_magnetic_latitudes(self, skymap: Skymap, timestamp: datetime.datetime, altitude_km: Optional[Union[int, float]] = None) -> None:
    """
    Set the magnetic latitude values for this keogram, using the specified skymap 
    data. AACGMv2 will be utilized to perform the calculations. The resulting data
    will be set to the mag_y attribute of this Keogram object, which can then be
    used for plotting and/or further analysis.

    Args:
        skymap (pyaurorax.data.ucalgary.Skymap): 
            The skymap object to use. This parameter is required.

        timestamp (datetime.datetime): 
            The timestamp to use when converting skymap data to magnetic coordinates. Utilizes
            AACGMv2 to do the conversion.

        altitude_km (int): 
            The altitude to use. If not specified, it will use the default in the skymap
            object. If the specified altitude is not valid, a ValueError will be raised.
    
    Returns:
        None. The Keogram object's `mag_y` attribute will be updated.

    Raises:
        ValueError: Issues with specified altitude.
    """

    # check for slice idx
    if (self.__slice_idx is None):
        raise ValueError("Unable to set the magnetic latitudes since the slice_idx is None. If this keogram " +
                         "object was created as part of the custom_keogram routines or is a spectrogaph keogram, " +
                         "this is expected and performing this action is not supported at this time.")

    # Check the dimensions of the skymap lat/lon arrays
    # If they are 2-dimensional [altitude_idx, y] instead of [altitude_idx, y, x] then we know it is a spectrograph
    # skymap. In this case, we will simply reform to add an additional dimension, so that self.__slice_idx (which is
    # always zero for spectrograph data as there is only one longitudinal bin) can be used to index into the array
    # the same as it would be for ASI data
    if len(skymap.full_map_latitude.shape) == 2:
        # Reform all spectrograph skymap arrays to have an extra dimension, for indexing purposes
        skymap.full_map_latitude = skymap.full_map_latitude[:, :, np.newaxis]
        skymap.full_map_longitude = skymap.full_map_longitude[:, :, np.newaxis]
        skymap.full_elevation = skymap.full_elevation[:, np.newaxis]

    # determine altitude index to use
    if (altitude_km is not None):
        # Obtain lat/lon arrays from skymap
        if (altitude_km * 1000.0 in skymap.full_map_altitude):
            altitude_idx = np.where(altitude_km * 1000.0 == skymap.full_map_altitude)

            lats = np.squeeze(skymap.full_map_latitude[altitude_idx, :, :])
            lons = np.squeeze(skymap.full_map_longitude[altitude_idx, :, :])
            lons[np.where(lons > 180)] -= 360.0

        else:
            # Make sure altitude is in range that can be interpolated
            if (altitude_km * 1000.0 < skymap.full_map_altitude[0]) or (altitude_km * 1000.0 > skymap.full_map_altitude[2]):
                raise ValueError("Altitude " + str(altitude_km) + " outside valid range of " +
                                 str((skymap.full_map_altitude[0] / 1000.0, skymap.full_map_altitude[2] / 1000.0)))

            # Initialze empty lat/lon arrays
            lats = np.full(np.squeeze(skymap.full_map_latitude[0, :, :]).shape, np.nan, dtype=skymap.full_map_latitude[0, :, :].dtype)
            lons = lats.copy()

            # Interpolate lats and lons at desired altitude
            for i in range(skymap.full_map_latitude.shape[1]):
                for j in range(skymap.full_map_latitude.shape[2]):
                    lats[i, j] = np.interp(altitude_km * 1000.0, skymap.full_map_altitude, skymap.full_map_latitude[:, i, j])
                    lons[i, j] = np.interp(altitude_km * 1000.0, skymap.full_map_altitude, skymap.full_map_longitude[:, i, j])

            lons[np.where(lons > 180)] -= 360.0

        # Convert lats and lons to geomagnetic coordinates
        mag_lats, mag_lons, _ = aacgmv2.convert_latlon_arr(lats.flatten(), lons.flatten(), (lons * 0.0).flatten(), timestamp, method_code='G2A')
        mag_lats = np.reshape(mag_lats, lats.shape)
        mag_lons = np.reshape(mag_lons, lons.shape)

        # If lat/lon arrays are 1-dimensional then we know it is a spectrograph skymap. In this case, we will simply
        # reform to add an additional dimension, so that self.__slice_idx (which is always zero for spectrograph data
        # as there is only one longitudinal bin) can be used to index into the array the same as it would be for ASI data
        if len(mag_lats.shape) == 1:
            mag_lats = mag_lats[:, np.newaxis]
            mag_lons = mag_lons[:, np.newaxis]

        # Set the y axis to the desired slice index of the magnetic latitudes
        self.mag_y = mag_lats[:, self.__slice_idx].copy()
    else:
        # Convert middle altitude lats and lons to geomagnetic coordinates
        mag_lats, mag_lons, _ = aacgmv2.convert_latlon_arr(np.squeeze(skymap.full_map_latitude[1, :, :]).flatten(),
                                                           np.squeeze(skymap.full_map_longitude[1, :, :]).flatten(),
                                                           (skymap.full_map_longitude[1, :, :] * 0.0).flatten(),
                                                           timestamp,
                                                           method_code='G2A')
        mag_lats = np.reshape(mag_lats, np.squeeze(skymap.full_map_latitude[1, :, :]).shape)
        mag_lons = np.reshape(mag_lons, np.squeeze(skymap.full_map_longitude[1, :, :]).shape)

        # If lat/lon arrays are 1-dimensional then we know it is a spectrograph skymap. In this case, we will simply
        # reform to add an additional dimension, so that self.__slice_idx (which is always zero for spectrograph data
        # as there is only one longitudinal bin) can be used to index into the array the same as it would be for ASI data
        if len(mag_lats.shape) == 1:
            mag_lats = mag_lats[:, np.newaxis]
            mag_lons = mag_lons[:, np.newaxis]

        # Set the y axis to the desired slice index of the magnetic latitudes
        self.mag_y = mag_lats[:, self.__slice_idx].copy()

Set the magnetic latitude values for this keogram, using the specified skymap data. AACGMv2 will be utilized to perform the calculations. The resulting data will be set to the mag_y attribute of this Keogram object, which can then be used for plotting and/or further analysis.

Args

skymap : Skymap
The skymap object to use. This parameter is required.
timestamp : datetime.datetime
The timestamp to use when converting skymap data to magnetic coordinates. Utilizes AACGMv2 to do the conversion.
altitude_km : int
The altitude to use. If not specified, it will use the default in the skymap object. If the specified altitude is not valid, a ValueError will be raised.

Returns

None. The Keogram object's mag_y attribute will be updated.

Raises

ValueError
Issues with specified altitude.
class Montage (data: numpy.ndarray, timestamp: List[datetime.datetime], n_channels: int)
Expand source code
@dataclass
class Montage:
    """
    Class representation for a montage

    Attributes:
        data (numpy.ndarray): 
            The derived montage data.

        timestamp (List[datetime.datetime]): 
            Timestamps corresponding to each montage image.
    """

    def __init__(self, data: np.ndarray, timestamp: List[datetime.datetime], n_channels: int):
        # public vars
        self.data = data
        self.timestamp = timestamp

        # private vars
        self.__n_channels = n_channels

    def __str__(self) -> str:
        return self.__repr__()

    def __repr__(self) -> str:
        data_str = "array(dims=%s, dtype=%s)" % (self.data.shape, self.data.dtype)
        timestamp_str = "[%d datetime objects]" % (len(self.timestamp))

        return "Montage(data=%s, timestamp=%s)" % (data_str, timestamp_str)

    def pretty_print(self):
        """
        A special print output for this class.
        """
        # set special strings
        data_str = "array(dims=%s, dtype=%s)" % (self.data.shape, self.data.dtype)
        timestamp_str = "[%d datetime objects]" % (len(self.timestamp))

        # print
        print("Montage:")
        print("  %-11s: %s" % ("data", data_str))
        print("  %-11s: %s" % ("timestamp", timestamp_str))

    def plot(self,
             rows: int,
             cols: int,
             timestamps_display: bool = True,
             timestamps_format: str = "%Y-%m-%d %H:%M:%S",
             timestamps_fontsize: int = 11,
             figsize: Optional[Tuple[int, int]] = None,
             title: Optional[str] = None,
             cmap: Optional[str] = None,
             returnfig: bool = False,
             savefig: bool = False,
             savefig_filename: Optional[str] = None,
             savefig_quality: Optional[int] = None) -> Any:
        """
        Generate a plot of the montage data. 
        
        Either display it (default behaviour), save it to disk (using the `savefig` parameter), or 
        return the matplotlib plot object for further usage (using the `returnfig` parameter).

        Args:
            rows (int): 
                The number of rows to use when displaying the images as a montage. The product of 
                the `rows` and `cols` parameters must match the number of images supplied when creating 
                the `Montage` object. If not, a ValueError will be raised. This parameter is required.

            cols (int): 
                The number of columns to use when displaying the images as a montage. The product of 
                the `rows` and `cols` parameters must match the number of images supplied when creating 
                the `Montage` object. If not, a ValueError will be raised. This parameter is required.

            timestamps_display (bool): 
                Display the timestamps on each montage image. Defaults to True. This parameter is
                optional.

            timestamps_format (str): 
                The format of the timestamps when being displayed. This is the same format string
                as when using the `strftime()` function for a `datetime` object. Default format 
                string is `%Y-%m-%d %H:%M:%S`. Refer to the 
                [Python documentation](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes)
                for further information. This parameter is optional.
            
            timestamps_fontsize (int): 
                The font size for the displayed timestamps. Default is size 11. This parameter is 
                optional.

            figsize (tuple): 
                The matplotlib figure size to use when plotting. For example `figsize=(14,4)`.

            title (str): 
                The title to display above the plotted montage. Default is no title.

            cmap (str): 
                The matplotlib colormap to use.

                Commonly used colormaps are:

                - REGO: `gist_heat`
                - THEMIS ASI: `gray`
                - TREx Blue: `Blues_r`
                - TREx NIR: `gray`
                - TREx RGB: `None`

                A list of all available colormaps can be found on the 
                [matplotlib documentation](https://matplotlib.org/stable/gallery/color/colormap_reference.html).
            
            returnfig (bool): 
                Instead of displaying the image, return the matplotlib figure object. This allows for further plot 
                manipulation, for example, adding labels or a title in a different location than the default. 
                
                Remember - if this parameter is supplied, be sure that you close your plot after finishing work 
                with it. This can be achieved by doing `plt.close(fig)`. 
                
                Note that this method cannot be used in combination with `savefig`.

            savefig (bool): 
                Save the displayed image to disk instead of displaying it. The parameter savefig_filename is required if 
                this parameter is set to True. Defaults to `False`.

            savefig_filename (str): 
                Filename to save the image to. Must be specified if the savefig parameter is set to True.

            savefig_quality (int): 
                Quality level of the saved image. This can be specified if the savefig_filename is a JPG image. If it
                is a PNG, quality is ignored. Default quality level for JPGs is matplotlib/Pillow's default of 75%.

        Returns:
            The displayed montage, by default. If `savefig` is set to True, nothing will be returned. If `returnfig` is 
            set to True, the plotting variables `(fig, ax)` will be returned.

        Raises:
            ValueError: issues with the y-axis choice, or rows/cols choice.
        """
        # check rows and cols
        if (rows * cols != self.data.shape[-1]):
            raise ValueError(("Invalid choice of rows and columns. The %d images in this Montage object " +
                              "cannot be organized into a %dx%d grid (%d * %d != %d)") % (
                                  self.data.shape[-1],
                                  rows,
                                  cols,
                                  rows,
                                  cols,
                                  self.data.shape[-1],
                              ))

        # check return mode
        if (returnfig is True and savefig is True):
            raise ValueError("Only one of returnfig or savefig can be set to True")
        if (returnfig is True and (savefig_filename is not None or savefig_quality is not None)):
            show_warning("The figure will be returned, but a savefig option parameter was supplied. Consider " +
                         "removing the savefig option parameter(s) as they will be ignored.",
                         stacklevel=1)
        elif (savefig is False and (savefig_filename is not None or savefig_quality is not None)):
            show_warning("A savefig option parameter was supplied, but the savefig parameter is False. The " +
                         "savefig option parameters will be ignored.",
                         stacklevel=1)

        # init figure
        fig, axs = plt.subplots(nrows=rows, ncols=cols, figsize=figsize)

        # set title
        if (title is not None):
            fig.suptitle(title)

        # for each image
        for ax, i in zip(axs.flat, range(0, len(self.timestamp))):  # type: ignore
            if (self.__n_channels == 1):
                # single channel
                ax.imshow(self.data[:, :, i], cmap=cmap, origin="lower", interpolation="nearest")
            elif (self.__n_channels == 3):
                # single channel
                ax.imshow(self.data[:, :, :, i], cmap=cmap, origin="lower", interpolation="nearest")
            else:  # pragma: nocover
                raise ValueError("Can only plot 3 or 4 dimensional data (series of single-channel or RGB mages), but found data of shape %s" %
                                 (self.data.shape))

            ax.set_axis_off()

            # show timestamp
            if (timestamps_display is True):
                ax.text(
                    int(np.floor(self.data.shape[1] / 2.)),
                    5,
                    self.timestamp[i].strftime(timestamps_format),
                    ha="center",
                    fontsize=timestamps_fontsize,
                )
        plt.tight_layout(h_pad=0, w_pad=0)

        # save figure or show it
        if (savefig is True):
            # check that filename has been set
            if (savefig_filename is None):
                raise ValueError("The savefig_filename parameter is missing, but required since savefig was set to True.")

            # save the figure
            f_extension = os.path.splitext(savefig_filename)[-1].lower()
            if (".jpg" == f_extension or ".jpeg" == f_extension):
                # check quality setting
                if (savefig_quality is not None):
                    plt.savefig(savefig_filename, pil_kwargs={"quality": savefig_quality}, bbox_inches="tight")
                else:
                    plt.savefig(savefig_filename, bbox_inches="tight")
            else:
                if (savefig_quality is not None):
                    # quality specified, but output filename is not a JPG, so show a warning
                    show_warning("The savefig_quality parameter was specified, but is only used for saving JPG files. The " +
                                 "savefig_filename parameter was determined to not be a JPG file, so the quality will be ignored",
                                 stacklevel=1)
                plt.savefig(savefig_filename, bbox_inches="tight")

            # clean up by closing the figure
            plt.close(fig)
        elif (returnfig is True):
            # return the figure and axis objects
            return (fig, axs)
        else:
            # show the figure
            plt.show(fig)

            # cleanup by closing the figure
            plt.close(fig)

        # return
        return None

Class representation for a montage

Attributes

data : numpy.ndarray
The derived montage data.
timestamp : List[datetime.datetime]
Timestamps corresponding to each montage image.

Methods

def plot(self,
rows: int,
cols: int,
timestamps_display: bool = True,
timestamps_format: str = '%Y-%m-%d %H:%M:%S',
timestamps_fontsize: int = 11,
figsize: Tuple[int, int] | None = None,
title: str | None = None,
cmap: str | None = None,
returnfig: bool = False,
savefig: bool = False,
savefig_filename: str | None = None,
savefig_quality: int | None = None) ‑> Any
Expand source code
def plot(self,
         rows: int,
         cols: int,
         timestamps_display: bool = True,
         timestamps_format: str = "%Y-%m-%d %H:%M:%S",
         timestamps_fontsize: int = 11,
         figsize: Optional[Tuple[int, int]] = None,
         title: Optional[str] = None,
         cmap: Optional[str] = None,
         returnfig: bool = False,
         savefig: bool = False,
         savefig_filename: Optional[str] = None,
         savefig_quality: Optional[int] = None) -> Any:
    """
    Generate a plot of the montage data. 
    
    Either display it (default behaviour), save it to disk (using the `savefig` parameter), or 
    return the matplotlib plot object for further usage (using the `returnfig` parameter).

    Args:
        rows (int): 
            The number of rows to use when displaying the images as a montage. The product of 
            the `rows` and `cols` parameters must match the number of images supplied when creating 
            the `Montage` object. If not, a ValueError will be raised. This parameter is required.

        cols (int): 
            The number of columns to use when displaying the images as a montage. The product of 
            the `rows` and `cols` parameters must match the number of images supplied when creating 
            the `Montage` object. If not, a ValueError will be raised. This parameter is required.

        timestamps_display (bool): 
            Display the timestamps on each montage image. Defaults to True. This parameter is
            optional.

        timestamps_format (str): 
            The format of the timestamps when being displayed. This is the same format string
            as when using the `strftime()` function for a `datetime` object. Default format 
            string is `%Y-%m-%d %H:%M:%S`. Refer to the 
            [Python documentation](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes)
            for further information. This parameter is optional.
        
        timestamps_fontsize (int): 
            The font size for the displayed timestamps. Default is size 11. This parameter is 
            optional.

        figsize (tuple): 
            The matplotlib figure size to use when plotting. For example `figsize=(14,4)`.

        title (str): 
            The title to display above the plotted montage. Default is no title.

        cmap (str): 
            The matplotlib colormap to use.

            Commonly used colormaps are:

            - REGO: `gist_heat`
            - THEMIS ASI: `gray`
            - TREx Blue: `Blues_r`
            - TREx NIR: `gray`
            - TREx RGB: `None`

            A list of all available colormaps can be found on the 
            [matplotlib documentation](https://matplotlib.org/stable/gallery/color/colormap_reference.html).
        
        returnfig (bool): 
            Instead of displaying the image, return the matplotlib figure object. This allows for further plot 
            manipulation, for example, adding labels or a title in a different location than the default. 
            
            Remember - if this parameter is supplied, be sure that you close your plot after finishing work 
            with it. This can be achieved by doing `plt.close(fig)`. 
            
            Note that this method cannot be used in combination with `savefig`.

        savefig (bool): 
            Save the displayed image to disk instead of displaying it. The parameter savefig_filename is required if 
            this parameter is set to True. Defaults to `False`.

        savefig_filename (str): 
            Filename to save the image to. Must be specified if the savefig parameter is set to True.

        savefig_quality (int): 
            Quality level of the saved image. This can be specified if the savefig_filename is a JPG image. If it
            is a PNG, quality is ignored. Default quality level for JPGs is matplotlib/Pillow's default of 75%.

    Returns:
        The displayed montage, by default. If `savefig` is set to True, nothing will be returned. If `returnfig` is 
        set to True, the plotting variables `(fig, ax)` will be returned.

    Raises:
        ValueError: issues with the y-axis choice, or rows/cols choice.
    """
    # check rows and cols
    if (rows * cols != self.data.shape[-1]):
        raise ValueError(("Invalid choice of rows and columns. The %d images in this Montage object " +
                          "cannot be organized into a %dx%d grid (%d * %d != %d)") % (
                              self.data.shape[-1],
                              rows,
                              cols,
                              rows,
                              cols,
                              self.data.shape[-1],
                          ))

    # check return mode
    if (returnfig is True and savefig is True):
        raise ValueError("Only one of returnfig or savefig can be set to True")
    if (returnfig is True and (savefig_filename is not None or savefig_quality is not None)):
        show_warning("The figure will be returned, but a savefig option parameter was supplied. Consider " +
                     "removing the savefig option parameter(s) as they will be ignored.",
                     stacklevel=1)
    elif (savefig is False and (savefig_filename is not None or savefig_quality is not None)):
        show_warning("A savefig option parameter was supplied, but the savefig parameter is False. The " +
                     "savefig option parameters will be ignored.",
                     stacklevel=1)

    # init figure
    fig, axs = plt.subplots(nrows=rows, ncols=cols, figsize=figsize)

    # set title
    if (title is not None):
        fig.suptitle(title)

    # for each image
    for ax, i in zip(axs.flat, range(0, len(self.timestamp))):  # type: ignore
        if (self.__n_channels == 1):
            # single channel
            ax.imshow(self.data[:, :, i], cmap=cmap, origin="lower", interpolation="nearest")
        elif (self.__n_channels == 3):
            # single channel
            ax.imshow(self.data[:, :, :, i], cmap=cmap, origin="lower", interpolation="nearest")
        else:  # pragma: nocover
            raise ValueError("Can only plot 3 or 4 dimensional data (series of single-channel or RGB mages), but found data of shape %s" %
                             (self.data.shape))

        ax.set_axis_off()

        # show timestamp
        if (timestamps_display is True):
            ax.text(
                int(np.floor(self.data.shape[1] / 2.)),
                5,
                self.timestamp[i].strftime(timestamps_format),
                ha="center",
                fontsize=timestamps_fontsize,
            )
    plt.tight_layout(h_pad=0, w_pad=0)

    # save figure or show it
    if (savefig is True):
        # check that filename has been set
        if (savefig_filename is None):
            raise ValueError("The savefig_filename parameter is missing, but required since savefig was set to True.")

        # save the figure
        f_extension = os.path.splitext(savefig_filename)[-1].lower()
        if (".jpg" == f_extension or ".jpeg" == f_extension):
            # check quality setting
            if (savefig_quality is not None):
                plt.savefig(savefig_filename, pil_kwargs={"quality": savefig_quality}, bbox_inches="tight")
            else:
                plt.savefig(savefig_filename, bbox_inches="tight")
        else:
            if (savefig_quality is not None):
                # quality specified, but output filename is not a JPG, so show a warning
                show_warning("The savefig_quality parameter was specified, but is only used for saving JPG files. The " +
                             "savefig_filename parameter was determined to not be a JPG file, so the quality will be ignored",
                             stacklevel=1)
            plt.savefig(savefig_filename, bbox_inches="tight")

        # clean up by closing the figure
        plt.close(fig)
    elif (returnfig is True):
        # return the figure and axis objects
        return (fig, axs)
    else:
        # show the figure
        plt.show(fig)

        # cleanup by closing the figure
        plt.close(fig)

    # return
    return None

Generate a plot of the montage data.

Either display it (default behaviour), save it to disk (using the savefig parameter), or return the matplotlib plot object for further usage (using the returnfig parameter).

Args

rows : int
The number of rows to use when displaying the images as a montage. The product of the rows and cols parameters must match the number of images supplied when creating the Montage object. If not, a ValueError will be raised. This parameter is required.
cols : int
The number of columns to use when displaying the images as a montage. The product of the rows and cols parameters must match the number of images supplied when creating the Montage object. If not, a ValueError will be raised. This parameter is required.
timestamps_display : bool
Display the timestamps on each montage image. Defaults to True. This parameter is optional.
timestamps_format : str
The format of the timestamps when being displayed. This is the same format string as when using the strftime() function for a datetime object. Default format string is %Y-%m-%d %H:%M:%S. Refer to the Python documentation for further information. This parameter is optional.
timestamps_fontsize : int
The font size for the displayed timestamps. Default is size 11. This parameter is optional.
figsize : tuple
The matplotlib figure size to use when plotting. For example figsize=(14,4).
title : str
The title to display above the plotted montage. Default is no title.
cmap : str

The matplotlib colormap to use.

Commonly used colormaps are:

  • REGO: gist_heat
  • THEMIS ASI: gray
  • TREx Blue: Blues_r
  • TREx NIR: gray
  • TREx RGB: None

A list of all available colormaps can be found on the matplotlib documentation.

returnfig : bool

Instead of displaying the image, return the matplotlib figure object. This allows for further plot manipulation, for example, adding labels or a title in a different location than the default.

Remember - if this parameter is supplied, be sure that you close your plot after finishing work with it. This can be achieved by doing plt.close(fig).

Note that this method cannot be used in combination with savefig.

savefig : bool
Save the displayed image to disk instead of displaying it. The parameter savefig_filename is required if this parameter is set to True. Defaults to False.
savefig_filename : str
Filename to save the image to. Must be specified if the savefig parameter is set to True.
savefig_quality : int
Quality level of the saved image. This can be specified if the savefig_filename is a JPG image. If it is a PNG, quality is ignored. Default quality level for JPGs is matplotlib/Pillow's default of 75%.

Returns

The displayed montage, by default. If savefig is set to True, nothing will be returned. If returnfig is set to True, the plotting variables (fig, ax) will be returned.

Raises

ValueError
issues with the y-axis choice, or rows/cols choice.
def pretty_print(self)
Expand source code
def pretty_print(self):
    """
    A special print output for this class.
    """
    # set special strings
    data_str = "array(dims=%s, dtype=%s)" % (self.data.shape, self.data.dtype)
    timestamp_str = "[%d datetime objects]" % (len(self.timestamp))

    # print
    print("Montage:")
    print("  %-11s: %s" % ("data", data_str))
    print("  %-11s: %s" % ("timestamp", timestamp_str))

A special print output for this class.

class Mosaic (polygon_data: matplotlib.collections.PolyCollection | List[matplotlib.collections.PolyCollection],
cartopy_projection: cartopy.crs.Projection,
contour_data: Dict[str, List[Any]] | None = None,
spect_cmap: str | None = None,
spect_intensity_scale: Tuple[int, int] | None = None)
Expand source code
@dataclass
class Mosaic:
    """
    Class representation for a generated mosaic.

    Attributes:
        polygon_data (matplotlib.collections.PolyCollection): 
            Generated polygons containing rendered data.

        cartopy_projection (cartopy.crs.Projection): 
            Cartopy projection to utilize.

        contour_data (Dict[str, List[Any]]): 
            Generated contour data.

        spect_cmap (str): 
            String giving the cmap to use for spect legend.
            
        spect_intensity_scale (Tuple[int]): 
            The min and max values that spectrograph data
            is scaled to in the mosaic, if any is present.
    """
    polygon_data: Union[PolyCollection, List[PolyCollection]]
    cartopy_projection: Projection
    contour_data: Optional[Dict[str, List[Any]]] = None
    spect_cmap: Optional[str] = None
    spect_intensity_scale: Optional[Tuple[int, int]] = None

    def __str__(self) -> str:
        return self.__repr__()

    def __repr__(self) -> str:
        if isinstance(self.polygon_data, list):
            polycollection_str = "[PolyCollection(...), ...]"  # pragma: nocover
        else:
            polycollection_str = "PolyCollection(...)"

        if self.contour_data is not None:
            return "Mosaic(polygon_data=PolyCollection(...), cartopy_projection=Projection(%s), %d Contours)" % (  # pragma: nocover
                self.cartopy_projection.to_string(),
                len(self.contour_data.get("x", [])),
            )
        else:
            return "Mosaic(polygon_data=" + polycollection_str + ", cartopy_projection=Projection(%s))" % (self.cartopy_projection.to_string())

    def pretty_print(self):
        """
        A special print output for this class.
        """
        # set special strings
        if isinstance(self.polygon_data, list):
            polycollection_str = "[PolyCollection(...), ...]"  # pragma: nocover
        else:
            polycollection_str = "PolyCollection(...)"
        cartopy_projection_str = "Projection(%s)" % (self.cartopy_projection.to_string())
        if self.contour_data is not None:
            contour_data_str = "%d Contours" % (len(self.contour_data.get("x", [])), )  # pragma: nocover
        else:
            contour_data_str = "None"

        # print
        print("Mosaic:")
        print("  %-23s: %s" % ("polygon_data", polycollection_str))
        print("  %-23s: %s" % ("cartopy_projection", cartopy_projection_str))
        print("  %-23s: %s" % ("contour_data", contour_data_str))
        print("  %-23s: %s" % ("spect_cmap", self.spect_cmap))
        print("  %-23s: %s" % ("spect_intensity_scale", self.spect_intensity_scale))

    def plot(
            self,
            map_extent: Sequence[Union[float, int]],
            figsize: Optional[Tuple[int, int]] = None,
            rayleighs: bool = False,
            max_rayleighs: int = 20000,
            title: Optional[str] = None,
            ocean_color: Optional[str] = None,
            land_color: str = "gray",
            land_edgecolor: str = "#8A8A8A",
            borders_color: str = "#AEAEAE",
            borders_disable: bool = False,
            colorbar_title: Optional[str] = None,  # deprecated in v1.11.0, use cbar_title instead
            cbar_title: Optional[str] = None,
            cbar_colormap: str = "",
            returnfig: bool = False,
            savefig: bool = False,
            savefig_filename: Optional[str] = None,
            savefig_quality: Optional[int] = None) -> Any:
        """
        Generate a plot of the mosaic data. 
        
        Either display it (default behaviour), save it to disk (using the `savefig` parameter), or 
        return the matplotlib plot object for further usage (using the `returnfig` parameter).

        Args:
            map_extent (List[int]): 
                Latitude/longitude range to be visible on the rendered map. This is a list of 4 integers 
                and/or floats, in the order of [min_lon, max_lon, min_lat, max_lat].

            figsize (tuple): 
                The matplotlib figure size to use when plotting. For example `figsize=(14,4)`.

            rayleighs (bool): 
                Set to `True` if the data being plotted is in Rayleighs. Defaults to `False`.

            max_rayleighs (int): 
                Max intensity scale for Rayleighs. Defaults to `20000`.

            title (str): 
                The title to display above the plotted mosaic. Default is no title.

            ocean_color (str): 
                Colour of the ocean. Default is cartopy's default shade of blue. Colours can be supplied
                as a word, or hexcode prefixed with a '#' character (ie. `#55AADD`).
            
            land_color (str): 
                Colour of the land. Default is `gray`. Colours can be supplied as a word, or hexcode 
                prefixed with a '#' character (ie. `#41BB87`).

            land_edgecolor (str): 
                Color of the land edges. Default is `#8A8A8A`. Colours can be supplied as a word, or
                hexcode prefixed with a '#' character.

            borders_color (str): 
                Color of the country borders. Default is `AEAEAE`. Colours can be supplied as a word, or
                hexcode prefixed with a '#' character.
            
            borders_disable (bool): 
                Disbale rendering of the borders. Default is `False`.

            cbar_title (str): 
                Title for the colorbar. Default is no title.

            colorbar_title (str): 
                Deprecated as of v1.10.0. Use 'cbar_title' instead in the exact same way.

            cbar_colormap (str): 
                The matplotlib colormap to use for the plotted color bar. Default is `gray`, unless
                mosaic was created with spectrograph data, in which case defaults to the colormap
                used for spectrograph data..

                Commonly used colormaps are:

                - REGO: `gist_heat`
                - THEMIS ASI: `gray`
                - TREx Blue: `Blues_r`
                - TREx NIR: `gray`
                - TREx RGB: `None`

                A list of all available colormaps can be found on the 
                [matplotlib documentation](https://matplotlib.org/stable/gallery/color/colormap_reference.html).

            returnfig (bool): 
                Instead of displaying the image, return the matplotlib figure object. This allows for further plot 
                manipulation, for example, adding labels or a title in a different location than the default. 
                
                Remember - if this parameter is supplied, be sure that you close your plot after finishing work 
                with it. This can be achieved by doing `plt.close(fig)`. 
                
                Note that this method cannot be used in combination with `savefig`.

            savefig (bool): 
                Save the displayed image to disk instead of displaying it. The parameter savefig_filename is required if 
                this parameter is set to True. Defaults to `False`.

            savefig_filename (str): 
                Filename to save the image to. Must be specified if the savefig parameter is set to True.

            savefig_quality (int): 
                Quality level of the saved image. This can be specified if the savefig_filename is a JPG image. If it
                is a PNG, quality is ignored. Default quality level for JPGs is matplotlib/Pillow's default of 75%.

        Returns:
            The displayed montage, by default. If `savefig` is set to True, nothing will be returned. If `returnfig` is 
            set to True, the plotting variables `(fig, ax)` will be returned.

        Raises:
        """
        # handle deprecation warnings
        if (colorbar_title is not None):
            show_warning("The parameter 'colorbar_title' was deprecated in v1.11.0. Please use 'cbar_title' instead (usage is identical).",
                         stacklevel=1)
            cbar_title = colorbar_title

        # check return mode
        if (returnfig is True and savefig is True):
            raise ValueError("Only one of returnfig or savefig can be set to True")
        if returnfig is True and (savefig_filename is not None or savefig_quality is not None):
            show_warning("The figure will be returned, but a savefig option parameter was supplied. Consider " +
                         "removing the savefig option parameter(s) as they will be ignored.",
                         stacklevel=1)
        elif (savefig is False and (savefig_filename is not None or savefig_quality is not None)):
            show_warning("A savefig option parameter was supplied, but the savefig parameter is False. The " +
                         "savefig option parameters will be ignored.",
                         stacklevel=1)

        # get colormap if there is spectrograph data
        if self.spect_cmap is not None:
            cbar_colormap = self.spect_cmap

        # initialize figure
        fig = plt.figure(figsize=figsize)
        ax = fig.add_axes((0, 0, 1, 1), projection=self.cartopy_projection)
        ax.set_extent(map_extent, crs=cartopy.crs.Geodetic())  # type: ignore

        # add ocean
        #
        # NOTE: we use the default ocean color
        if (ocean_color is not None):
            ax.add_feature(  # type: ignore
                cartopy.feature.OCEAN, facecolor=ocean_color, zorder=0)
        else:
            ax.add_feature(cartopy.feature.OCEAN, zorder=0)  # type: ignore

        # add land
        ax.add_feature(  # type: ignore
            cartopy.feature.LAND, facecolor=land_color, edgecolor=land_edgecolor, zorder=0)

        # add borders
        if (borders_disable is False):
            ax.add_feature(  # type: ignore
                cartopy.feature.BORDERS, edgecolor=borders_color, zorder=0)

        # add polygon data
        #
        # NOTE: it seems that when running this function a second time, the polygon
        # data is not too happy. So to handle this, we plot a copy of the polygon data
        if isinstance(self.polygon_data, list):
            for polygon_data in self.polygon_data:  # pragma: nocover
                ax.add_collection(copy(polygon_data))
        else:
            ax.add_collection(copy(self.polygon_data))

        if self.contour_data is not None:
            for i in range(len(self.contour_data["x"])):
                ax.plot(self.contour_data["x"][i],
                        self.contour_data["y"][i],
                        color=self.contour_data["color"][i],
                        linewidth=self.contour_data["linewidth"][i],
                        linestyle=self.contour_data["linestyle"][i],
                        marker=self.contour_data["marker"][i],
                        zorder=self.contour_data["zorder"][i])

        # set title
        if (title is not None):
            ax.set_title(title)

        # add text
        if (rayleighs is True):
            if isinstance(self.polygon_data, list):  # pragma: nocover
                raise ValueError("Rayleighs Keyword is currently not available for mosaics with multiple sets of data.")

            # Create a colorbar, in Rayleighs, that accounts for the scaling limit we applied
            cbar_ticks = [float(j) / 5. for j in range(0, 6)]
            cbar_ticknames = [str(int(max_rayleighs / 5) * j) for j in range(0, 6)]

            # Any pixels with the max Rayleigh value could be greater than it, so we include the plus sign
            cbar_ticknames[-1] += "+"
            self.polygon_data.set_cmap(cbar_colormap)
            cbar = plt.colorbar(self.polygon_data, shrink=0.5, ticks=cbar_ticks, ax=ax)
            cbar.ax.set_yticklabels(cbar_ticknames)
            plt.text(1.025,
                     0.5,
                     "Intensity (Rayleighs)",
                     fontsize=14,
                     transform=ax.transAxes,
                     va="center",
                     rotation="vertical",
                     weight="bold",
                     style="oblique")

        if (self.spect_cmap) is not None:

            if (self.spect_intensity_scale) is None:  # pragma: nocover
                intensity_max = np.nan
                intensity_min = np.nan
            else:
                intensity_max = self.spect_intensity_scale[1]
                intensity_min = self.spect_intensity_scale[0]

            # Create a colorbar, in Rayleighs, that accounts for the scaling limit we applied
            cbar_ticks = [float(j) / 5. for j in range(0, 6)]
            cbar_ticknames = [str(int((intensity_max / 5) + intensity_min) * j) for j in range(0, 6)]

            # Any specrograph bins with the max intensity value could be greater than it, so we include the plus sign
            cbar_ticknames[-1] += "+"
            if isinstance(self.polygon_data, list):  # pragma: nocover
                self.polygon_data[0].set_cmap(cbar_colormap)
            else:
                self.polygon_data.set_cmap(cbar_colormap)
            if isinstance(self.polygon_data, list):  # pragma: nocover
                cbar = plt.colorbar(self.polygon_data[0], shrink=0.5, ticks=cbar_ticks, ax=ax)
            else:
                cbar = plt.colorbar(self.polygon_data, shrink=0.5, ticks=cbar_ticks, ax=ax)
            cbar.ax.set_yticklabels(cbar_ticknames)
            if (cbar_title is None):
                plt.text(1.025,
                         0.5,
                         "Spectrograph Intensity (Rayleighs)",
                         fontsize=10,
                         transform=ax.transAxes,
                         va="center",
                         rotation="vertical",
                         weight="bold",
                         style="oblique")
            else:
                plt.text(1.025,
                         0.5,
                         cbar_title,
                         fontsize=10,
                         transform=ax.transAxes,
                         va="center",
                         rotation="vertical",
                         weight="bold",
                         style="oblique")

        # save figure or show it
        if (savefig is True):
            # check that filename has been set
            if (savefig_filename is None):
                raise ValueError("The savefig_filename parameter is missing, but required since savefig was set to True.")

            # save the figure
            f_extension = os.path.splitext(savefig_filename)[-1].lower()
            if (".jpg" == f_extension or ".jpeg" == f_extension):
                # check quality setting
                if (savefig_quality is not None):
                    plt.savefig(savefig_filename, pil_kwargs={"quality": savefig_quality}, bbox_inches="tight")
                else:
                    plt.savefig(savefig_filename, bbox_inches="tight")
            else:
                if (savefig_quality is not None):
                    # quality specified, but output filename is not a JPG, so show a warning
                    show_warning("The savefig_quality parameter was specified, but is only used for saving JPG files. The " +
                                 "savefig_filename parameter was determined to not be a JPG file, so the quality will be ignored",
                                 stacklevel=1)
                plt.savefig(savefig_filename, bbox_inches="tight")

            # clean up by closing the figure
            plt.close(fig)
        elif (returnfig is True):
            # return the figure and axis objects
            return (fig, ax)
        else:
            # show the figure
            plt.show(fig)

            # cleanup by closing the figure
            plt.close(fig)

        # return
        return None

    def add_geo_contours(self,
                         lats: Optional[Union[ndarray, list]] = None,
                         lons: Optional[Union[ndarray, list]] = None,
                         constant_lats: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                         constant_lons: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                         color: str = "black",
                         linewidth: Union[float, int] = 1,
                         linestyle: str = "solid",
                         marker: str = "",
                         bring_to_front: bool = False):
        """
        Add geographic contours to a mosaic.

        Args:
            lats (ndarray or list): 
                Sequence of geographic latitudes defining a contour.
            
            lons (ndarray or list): 
                Sequence of geographic longitudes defining a contour.

            constant_lats (float, int, or Sequence): 
                Geographic Latitude(s) at which to add line(s) of constant latitude.
            
            constant_lons (float, int, or Sequence): 
                Geographic Longitude(s) at which to add line(s) of constant longitude.

            color (str): 
                The matplotlib color used for the contour(s).

            linewidth (float or int): 
                The contour thickness.
            
            linestyle (str): 
                The matplotlib linestyle used for the contour(s).

            marker (str): 
                The matplotlib marker used for the contour(s).

            bring_to_front (bool): 
                Plots the contour on top of all other currently plotted objects.

        Returns:
            The object's contour_data parameter is populated appropriately.

        Raises:
            ValueError: issues encountered with supplied parameters.
        """
        # Make sure some form of lat/lon is provided
        if (constant_lats is None) and (constant_lons is None) and (lats is None) and (lons is None):
            raise ValueError("No latitudes or longitudes provided.")

        # If manually passing in lats & lons, make sure both are provided
        if (lats is not None or lons is not None) and (lats is None or lons is None):
            raise (ValueError("Manually supplying contour requires both lats and lons."))

        # Check that color exists in matplotlib
        if color not in matplotlib.colors.CSS4_COLORS:
            raise ValueError(f"Color '{color}' not recognized by matplotlib.")

        # Check that linestyle is valid
        if linestyle not in ["-", "--", "-.", ":", "solid", "dashed", "dashdot", "dotted"]:
            raise ValueError(f"Linestyle '{linestyle}' not recognized by matplotlib.")

        # Check that linewidth is valid
        if linewidth <= 0:
            raise ValueError("Linewidth must be greater than zero.")

        # Check that marker is valid
        if marker not in ["", "o", ".", "p", "*", "x", "+", "X"]:
            raise ValueError(f"Marker '{marker}' is not currently supported.")

        # Convert numerics to lists if necessary
        if constant_lats is not None:
            if isinstance(constant_lats, (float, int)):
                constant_lats = [constant_lats]
        if constant_lons is not None:
            if isinstance(constant_lons, (float, int)):
                constant_lons = [constant_lons]

        # Initialize contour data dict if it doesn't exist yet
        if self.contour_data is None:
            self.contour_data = {"x": [], "y": [], "color": [], "linewidth": [], "linestyle": [], "marker": [], "zorder": []}  # pragma: nocover

        # Obtain the mosaic's projection
        source_proj = pyproj.CRS.from_user_input(cartopy.crs.Geodetic())
        mosaic_proj = pyproj.CRS.from_user_input(self.cartopy_projection)
        transformer = pyproj.Transformer.from_crs(source_proj, mosaic_proj, always_xy=True)

        # First handling manually supplied lat/lon arrays
        if (lats is not None) and (lons is not None):
            # Convert lists to ndarrays if necessary
            if isinstance(lats, list):
                lats = np.array(lats)
            if isinstance(lons, list):
                lons = np.array(lons)

            if len(lats) != len(lons):
                raise ValueError("Lat/Lon data must be of the same size.")

            # Create specified contour from geographic coords
            x, y = transformer.transform(lons, lats)
            # Add contour to dict, along with color and linewidth
            self.contour_data["x"].append(x)
            self.contour_data["y"].append(y)
            self.contour_data["color"].append(color)
            self.contour_data["linewidth"].append(linewidth)
            self.contour_data["linestyle"].append(linestyle)
            self.contour_data["marker"].append(marker)
            self.contour_data["zorder"].append(int(bring_to_front))

        # Next handling lines of constant latitude
        if constant_lats is not None:
            # Generate longitudinal domain of the lat line (full globe)
            lon_domain = np.arange(-180, 180 + 0.2, 0.2)

            # Iterate through all lines of constant lat requested
            for lat in constant_lats:
                # Create line of constant lat
                const_lat_x, const_lat_y = (lon_domain, lon_domain * 0 + lat)
                sort_idx = np.argsort(const_lat_x)
                const_lat_y = const_lat_y[sort_idx]
                const_lat_x = const_lat_x[sort_idx]
                const_lat_x, const_lat_y = transformer.transform(const_lat_x, const_lat_y)

                # Add contour to dict, along with color and linewidth
                self.contour_data["x"].append(const_lat_x)
                self.contour_data["y"].append(const_lat_y)
                self.contour_data["color"].append(color)
                self.contour_data["linewidth"].append(linewidth)
                self.contour_data["linestyle"].append(linestyle)
                self.contour_data["marker"].append(marker)
                self.contour_data["zorder"].append(int(bring_to_front))

        # Now handling lines of constant longitude
        if constant_lons is not None:
            # Generate latitudinal domain of the lon line (full globe)
            lat_domain = np.arange(-90, 90 + 0.1, 0.1)

            # Iterate through all lines of constant lon requested
            for lon in constant_lons:
                # Create line of constant lon and add to dict
                const_lon_x, const_lon_y = (lat_domain * 0 + lon, lat_domain)
                sort_idx = np.argsort(const_lon_y)
                const_lon_x = const_lon_x[sort_idx]
                const_lon_y = const_lon_y[sort_idx]
                const_lon_x, const_lon_y = transformer.transform(const_lon_x, const_lon_y)

                # Add contour to dict, along with color and linewidth
                self.contour_data["x"].append(const_lon_x)
                self.contour_data["y"].append(const_lon_y)
                self.contour_data["color"].append(color)
                self.contour_data["linewidth"].append(linewidth)
                self.contour_data["linestyle"].append(linestyle)
                self.contour_data["marker"].append(marker)
                self.contour_data["zorder"].append(int(bring_to_front))

    def add_mag_contours(self,
                         timestamp: datetime.datetime,
                         constant_lats: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                         constant_lons: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                         lats: Optional[Union[ndarray, list]] = None,
                         lons: Optional[Union[ndarray, list]] = None,
                         color: str = "black",
                         linewidth: Union[float, int] = 1,
                         linestyle: str = "solid",
                         marker: str = "",
                         bring_to_front: bool = False):
        """
        Add geomagnetic contours to a mosaic.

        Args:
            timestamp (datetime.datetime): 
                The timestamp used in computing AACGM coordinates.

            lats (ndarray or list): 
                Sequence of geomagnetic latitudes defining a contour.
            
            lons (ndarray or list): 
                Sequence of geomagnetic longitudes defining a contour.

            constant_lats (float, int, Sequence): 
                Geomagnetic latitude(s) at which to add contour(s) of constant latitude.
            
            constant_lons (float, int, Sequence): 
                Geomagnetic longitude(s) at which to add contours(s) of constant longitude.

            color (str): 
                The matplotlib color used for the contour(s).

            linewidth (float or int): 
                The contour thickness.

            linestyle (str): 
                The matplotlib linestyle used for the contour(s).

            marker (str): 
                The matplotlib marker used for the contour(s).

            bring_to_front (bool): 
                Plots the contour on top of all other currently plotted objects.

        Returns:
            The object's contour_data parameter is populated appropriately.

        Raises:
            ValueError: issues encountered with supplied parameters
        """
        # Make sure some form of lat/lon is provided
        if (constant_lats is None) and (constant_lons is None) and (lats is None) and (lons is None):
            raise ValueError("No latitudes or longitudes provided.")

        # If manually passing in lats & lons, make sure both are provided
        if (lats is not None or lons is not None) and (lats is None or lons is None):
            raise ValueError("Manually supplying contour requires both lats and lons.")

        # Check that color exists in matplotlib
        if color not in matplotlib.colors.CSS4_COLORS:
            raise ValueError(f"Color '{color}' not recognized by matplotlib.")

        # Check that linestyle is valid
        if linestyle not in ["-", "--", "-.", ":", "solid", "dashed", "dashdot", "dotted"]:
            raise ValueError(f"Linestyle '{linestyle}' not recognized by matplotlib.")

        # Check that linewidth is valid
        if linewidth <= 0:
            raise ValueError("Linewidth must be greater than zero.")

        # Check that marker is valid
        if marker not in ["", "o", ".", "p", "*", "x", "+", "X"]:
            raise ValueError(f"Marker '{marker}' is not currently supported.")

        # Convert numerics to lists if necessary
        if constant_lats is not None:
            if isinstance(constant_lats, (float, int)):
                constant_lats = [constant_lats]
        if constant_lons is not None:
            if isinstance(constant_lons, (float, int)):
                constant_lons = [constant_lons]

        # Initialize contour data dict if it doesn't exist yet
        if self.contour_data is None:
            self.contour_data = {"x": [], "y": [], "color": [], "linewidth": [], "linestyle": [], "marker": [], "zorder": []}

        # Obtain the mosaic's projection
        source_proj = pyproj.CRS.from_user_input(cartopy.crs.Geodetic())
        mosaic_proj = pyproj.CRS.from_user_input(self.cartopy_projection)
        transformer = pyproj.Transformer.from_crs(source_proj, mosaic_proj, always_xy=True)

        # First handling manually supplied lat/lon arrays
        if (lats is not None) and (lons is not None):
            # Convert lists to ndarrays if necessary
            if isinstance(lats, list):
                lats = np.array(lats)
            if isinstance(lons, list):
                lons = np.array(lons)

            if len(lats) != len(lons):
                raise ValueError("Lat/Lon data must be of the same size.")

            # Create specified contour from magnetic coords
            y, x, alt = aacgmv2.convert_latlon_arr(lats, lons, lats * 0.0, timestamp, method_code="A2G")
            x, y = transformer.transform(x, y)

            # Add contour to dict, along with color and linewidth
            self.contour_data["x"].append(x)
            self.contour_data["y"].append(y)
            self.contour_data["color"].append(color)
            self.contour_data["linewidth"].append(linewidth)
            self.contour_data["linestyle"].append(linestyle)
            self.contour_data["marker"].append(marker)
            self.contour_data["zorder"].append(int(bring_to_front))

        # Next handling lines of constant latitude
        if constant_lats is not None:
            # Generate longitudinal domain of the lat line (full globe)
            lon_domain = np.arange(-180, 180 + 0.2, 0.2)

            # iterate through all lines of constant lat requested
            for lat in constant_lats:
                # Create line of constant lat from magnetic coords
                const_lat_x, const_lat_y = (lon_domain, lon_domain * 0 + lat)
                const_lat_y, const_lat_x, alt = aacgmv2.convert_latlon_arr(const_lat_y, const_lat_x, const_lat_x * 0.0, timestamp, method_code="A2G")
                sort_idx = np.argsort(const_lat_x)
                const_lat_y = const_lat_y[sort_idx]
                const_lat_x = const_lat_x[sort_idx]
                const_lat_x, const_lat_y = transformer.transform(const_lat_x, const_lat_y)

                # Add contour to dict, along with color and linewidth
                self.contour_data["x"].append(const_lat_x)
                self.contour_data["y"].append(const_lat_y)
                self.contour_data["color"].append(color)
                self.contour_data["linewidth"].append(linewidth)
                self.contour_data["linestyle"].append(linestyle)
                self.contour_data["marker"].append(marker)
                self.contour_data["zorder"].append(int(bring_to_front))

        # Now handling lines of constant longitude
        if constant_lons is not None:
            # Generate latitudinal domain of the lon line (full globe)
            lat_domain = np.arange(-90, 90 + 0.1, 0.1)

            # iterate through all lines of constant lon requested
            for lon in constant_lons:
                # Create line of constant lon from magnetic coords
                const_lon_x, const_lon_y = (lat_domain * 0 + lon, lat_domain)
                const_lon_y, const_lon_x, alt = aacgmv2.convert_latlon_arr(const_lon_y, const_lon_x, const_lon_x * 0.0, timestamp, method_code="A2G")
                sort_idx = np.argsort(const_lon_y)
                const_lon_x = const_lon_x[sort_idx]
                const_lon_y = const_lon_y[sort_idx]
                const_lon_x, const_lon_y = transformer.transform(const_lon_x, const_lon_y)

                # Add contour to dict, along with color and linewidth
                self.contour_data["x"].append(const_lon_x)
                self.contour_data["y"].append(const_lon_y)
                self.contour_data["color"].append(color)
                self.contour_data["linewidth"].append(linewidth)
                self.contour_data["linestyle"].append(linestyle)
                self.contour_data["marker"].append(marker)
                self.contour_data["zorder"].append(int(bring_to_front))

Class representation for a generated mosaic.

Attributes

polygon_data : matplotlib.collections.PolyCollection
Generated polygons containing rendered data.
cartopy_projection : cartopy.crs.Projection
Cartopy projection to utilize.
contour_data : Dict[str, List[Any]]
Generated contour data.
spect_cmap : str
String giving the cmap to use for spect legend.
spect_intensity_scale : Tuple[int]
The min and max values that spectrograph data is scaled to in the mosaic, if any is present.

Instance variables

var cartopy_projection : cartopy.crs.Projection
var contour_data : Dict[str, List[Any]] | None
var polygon_data : matplotlib.collections.PolyCollection | List[matplotlib.collections.PolyCollection]
var spect_cmap : str | None
var spect_intensity_scale : Tuple[int, int] | None

Methods

def add_geo_contours(self,
lats: numpy.ndarray | list | None = None,
lons: numpy.ndarray | list | None = None,
constant_lats: float | int | Sequence[float | int] | numpy.ndarray | None = None,
constant_lons: float | int | Sequence[float | int] | numpy.ndarray | None = None,
color: str = 'black',
linewidth: float | int = 1,
linestyle: str = 'solid',
marker: str = '',
bring_to_front: bool = False)
Expand source code
def add_geo_contours(self,
                     lats: Optional[Union[ndarray, list]] = None,
                     lons: Optional[Union[ndarray, list]] = None,
                     constant_lats: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                     constant_lons: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                     color: str = "black",
                     linewidth: Union[float, int] = 1,
                     linestyle: str = "solid",
                     marker: str = "",
                     bring_to_front: bool = False):
    """
    Add geographic contours to a mosaic.

    Args:
        lats (ndarray or list): 
            Sequence of geographic latitudes defining a contour.
        
        lons (ndarray or list): 
            Sequence of geographic longitudes defining a contour.

        constant_lats (float, int, or Sequence): 
            Geographic Latitude(s) at which to add line(s) of constant latitude.
        
        constant_lons (float, int, or Sequence): 
            Geographic Longitude(s) at which to add line(s) of constant longitude.

        color (str): 
            The matplotlib color used for the contour(s).

        linewidth (float or int): 
            The contour thickness.
        
        linestyle (str): 
            The matplotlib linestyle used for the contour(s).

        marker (str): 
            The matplotlib marker used for the contour(s).

        bring_to_front (bool): 
            Plots the contour on top of all other currently plotted objects.

    Returns:
        The object's contour_data parameter is populated appropriately.

    Raises:
        ValueError: issues encountered with supplied parameters.
    """
    # Make sure some form of lat/lon is provided
    if (constant_lats is None) and (constant_lons is None) and (lats is None) and (lons is None):
        raise ValueError("No latitudes or longitudes provided.")

    # If manually passing in lats & lons, make sure both are provided
    if (lats is not None or lons is not None) and (lats is None or lons is None):
        raise (ValueError("Manually supplying contour requires both lats and lons."))

    # Check that color exists in matplotlib
    if color not in matplotlib.colors.CSS4_COLORS:
        raise ValueError(f"Color '{color}' not recognized by matplotlib.")

    # Check that linestyle is valid
    if linestyle not in ["-", "--", "-.", ":", "solid", "dashed", "dashdot", "dotted"]:
        raise ValueError(f"Linestyle '{linestyle}' not recognized by matplotlib.")

    # Check that linewidth is valid
    if linewidth <= 0:
        raise ValueError("Linewidth must be greater than zero.")

    # Check that marker is valid
    if marker not in ["", "o", ".", "p", "*", "x", "+", "X"]:
        raise ValueError(f"Marker '{marker}' is not currently supported.")

    # Convert numerics to lists if necessary
    if constant_lats is not None:
        if isinstance(constant_lats, (float, int)):
            constant_lats = [constant_lats]
    if constant_lons is not None:
        if isinstance(constant_lons, (float, int)):
            constant_lons = [constant_lons]

    # Initialize contour data dict if it doesn't exist yet
    if self.contour_data is None:
        self.contour_data = {"x": [], "y": [], "color": [], "linewidth": [], "linestyle": [], "marker": [], "zorder": []}  # pragma: nocover

    # Obtain the mosaic's projection
    source_proj = pyproj.CRS.from_user_input(cartopy.crs.Geodetic())
    mosaic_proj = pyproj.CRS.from_user_input(self.cartopy_projection)
    transformer = pyproj.Transformer.from_crs(source_proj, mosaic_proj, always_xy=True)

    # First handling manually supplied lat/lon arrays
    if (lats is not None) and (lons is not None):
        # Convert lists to ndarrays if necessary
        if isinstance(lats, list):
            lats = np.array(lats)
        if isinstance(lons, list):
            lons = np.array(lons)

        if len(lats) != len(lons):
            raise ValueError("Lat/Lon data must be of the same size.")

        # Create specified contour from geographic coords
        x, y = transformer.transform(lons, lats)
        # Add contour to dict, along with color and linewidth
        self.contour_data["x"].append(x)
        self.contour_data["y"].append(y)
        self.contour_data["color"].append(color)
        self.contour_data["linewidth"].append(linewidth)
        self.contour_data["linestyle"].append(linestyle)
        self.contour_data["marker"].append(marker)
        self.contour_data["zorder"].append(int(bring_to_front))

    # Next handling lines of constant latitude
    if constant_lats is not None:
        # Generate longitudinal domain of the lat line (full globe)
        lon_domain = np.arange(-180, 180 + 0.2, 0.2)

        # Iterate through all lines of constant lat requested
        for lat in constant_lats:
            # Create line of constant lat
            const_lat_x, const_lat_y = (lon_domain, lon_domain * 0 + lat)
            sort_idx = np.argsort(const_lat_x)
            const_lat_y = const_lat_y[sort_idx]
            const_lat_x = const_lat_x[sort_idx]
            const_lat_x, const_lat_y = transformer.transform(const_lat_x, const_lat_y)

            # Add contour to dict, along with color and linewidth
            self.contour_data["x"].append(const_lat_x)
            self.contour_data["y"].append(const_lat_y)
            self.contour_data["color"].append(color)
            self.contour_data["linewidth"].append(linewidth)
            self.contour_data["linestyle"].append(linestyle)
            self.contour_data["marker"].append(marker)
            self.contour_data["zorder"].append(int(bring_to_front))

    # Now handling lines of constant longitude
    if constant_lons is not None:
        # Generate latitudinal domain of the lon line (full globe)
        lat_domain = np.arange(-90, 90 + 0.1, 0.1)

        # Iterate through all lines of constant lon requested
        for lon in constant_lons:
            # Create line of constant lon and add to dict
            const_lon_x, const_lon_y = (lat_domain * 0 + lon, lat_domain)
            sort_idx = np.argsort(const_lon_y)
            const_lon_x = const_lon_x[sort_idx]
            const_lon_y = const_lon_y[sort_idx]
            const_lon_x, const_lon_y = transformer.transform(const_lon_x, const_lon_y)

            # Add contour to dict, along with color and linewidth
            self.contour_data["x"].append(const_lon_x)
            self.contour_data["y"].append(const_lon_y)
            self.contour_data["color"].append(color)
            self.contour_data["linewidth"].append(linewidth)
            self.contour_data["linestyle"].append(linestyle)
            self.contour_data["marker"].append(marker)
            self.contour_data["zorder"].append(int(bring_to_front))

Add geographic contours to a mosaic.

Args

lats : ndarray or list
Sequence of geographic latitudes defining a contour.
lons : ndarray or list
Sequence of geographic longitudes defining a contour.
constant_lats : float, int, or Sequence
Geographic Latitude(s) at which to add line(s) of constant latitude.
constant_lons : float, int, or Sequence
Geographic Longitude(s) at which to add line(s) of constant longitude.
color : str
The matplotlib color used for the contour(s).
linewidth : float or int
The contour thickness.
linestyle : str
The matplotlib linestyle used for the contour(s).
marker : str
The matplotlib marker used for the contour(s).
bring_to_front : bool
Plots the contour on top of all other currently plotted objects.

Returns

The object's contour_data parameter is populated appropriately.

Raises

ValueError
issues encountered with supplied parameters.
def add_mag_contours(self,
timestamp: datetime.datetime,
constant_lats: float | int | Sequence[float | int] | numpy.ndarray | None = None,
constant_lons: float | int | Sequence[float | int] | numpy.ndarray | None = None,
lats: numpy.ndarray | list | None = None,
lons: numpy.ndarray | list | None = None,
color: str = 'black',
linewidth: float | int = 1,
linestyle: str = 'solid',
marker: str = '',
bring_to_front: bool = False)
Expand source code
def add_mag_contours(self,
                     timestamp: datetime.datetime,
                     constant_lats: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                     constant_lons: Optional[Union[float, int, Sequence[Union[float, int]], ndarray]] = None,
                     lats: Optional[Union[ndarray, list]] = None,
                     lons: Optional[Union[ndarray, list]] = None,
                     color: str = "black",
                     linewidth: Union[float, int] = 1,
                     linestyle: str = "solid",
                     marker: str = "",
                     bring_to_front: bool = False):
    """
    Add geomagnetic contours to a mosaic.

    Args:
        timestamp (datetime.datetime): 
            The timestamp used in computing AACGM coordinates.

        lats (ndarray or list): 
            Sequence of geomagnetic latitudes defining a contour.
        
        lons (ndarray or list): 
            Sequence of geomagnetic longitudes defining a contour.

        constant_lats (float, int, Sequence): 
            Geomagnetic latitude(s) at which to add contour(s) of constant latitude.
        
        constant_lons (float, int, Sequence): 
            Geomagnetic longitude(s) at which to add contours(s) of constant longitude.

        color (str): 
            The matplotlib color used for the contour(s).

        linewidth (float or int): 
            The contour thickness.

        linestyle (str): 
            The matplotlib linestyle used for the contour(s).

        marker (str): 
            The matplotlib marker used for the contour(s).

        bring_to_front (bool): 
            Plots the contour on top of all other currently plotted objects.

    Returns:
        The object's contour_data parameter is populated appropriately.

    Raises:
        ValueError: issues encountered with supplied parameters
    """
    # Make sure some form of lat/lon is provided
    if (constant_lats is None) and (constant_lons is None) and (lats is None) and (lons is None):
        raise ValueError("No latitudes or longitudes provided.")

    # If manually passing in lats & lons, make sure both are provided
    if (lats is not None or lons is not None) and (lats is None or lons is None):
        raise ValueError("Manually supplying contour requires both lats and lons.")

    # Check that color exists in matplotlib
    if color not in matplotlib.colors.CSS4_COLORS:
        raise ValueError(f"Color '{color}' not recognized by matplotlib.")

    # Check that linestyle is valid
    if linestyle not in ["-", "--", "-.", ":", "solid", "dashed", "dashdot", "dotted"]:
        raise ValueError(f"Linestyle '{linestyle}' not recognized by matplotlib.")

    # Check that linewidth is valid
    if linewidth <= 0:
        raise ValueError("Linewidth must be greater than zero.")

    # Check that marker is valid
    if marker not in ["", "o", ".", "p", "*", "x", "+", "X"]:
        raise ValueError(f"Marker '{marker}' is not currently supported.")

    # Convert numerics to lists if necessary
    if constant_lats is not None:
        if isinstance(constant_lats, (float, int)):
            constant_lats = [constant_lats]
    if constant_lons is not None:
        if isinstance(constant_lons, (float, int)):
            constant_lons = [constant_lons]

    # Initialize contour data dict if it doesn't exist yet
    if self.contour_data is None:
        self.contour_data = {"x": [], "y": [], "color": [], "linewidth": [], "linestyle": [], "marker": [], "zorder": []}

    # Obtain the mosaic's projection
    source_proj = pyproj.CRS.from_user_input(cartopy.crs.Geodetic())
    mosaic_proj = pyproj.CRS.from_user_input(self.cartopy_projection)
    transformer = pyproj.Transformer.from_crs(source_proj, mosaic_proj, always_xy=True)

    # First handling manually supplied lat/lon arrays
    if (lats is not None) and (lons is not None):
        # Convert lists to ndarrays if necessary
        if isinstance(lats, list):
            lats = np.array(lats)
        if isinstance(lons, list):
            lons = np.array(lons)

        if len(lats) != len(lons):
            raise ValueError("Lat/Lon data must be of the same size.")

        # Create specified contour from magnetic coords
        y, x, alt = aacgmv2.convert_latlon_arr(lats, lons, lats * 0.0, timestamp, method_code="A2G")
        x, y = transformer.transform(x, y)

        # Add contour to dict, along with color and linewidth
        self.contour_data["x"].append(x)
        self.contour_data["y"].append(y)
        self.contour_data["color"].append(color)
        self.contour_data["linewidth"].append(linewidth)
        self.contour_data["linestyle"].append(linestyle)
        self.contour_data["marker"].append(marker)
        self.contour_data["zorder"].append(int(bring_to_front))

    # Next handling lines of constant latitude
    if constant_lats is not None:
        # Generate longitudinal domain of the lat line (full globe)
        lon_domain = np.arange(-180, 180 + 0.2, 0.2)

        # iterate through all lines of constant lat requested
        for lat in constant_lats:
            # Create line of constant lat from magnetic coords
            const_lat_x, const_lat_y = (lon_domain, lon_domain * 0 + lat)
            const_lat_y, const_lat_x, alt = aacgmv2.convert_latlon_arr(const_lat_y, const_lat_x, const_lat_x * 0.0, timestamp, method_code="A2G")
            sort_idx = np.argsort(const_lat_x)
            const_lat_y = const_lat_y[sort_idx]
            const_lat_x = const_lat_x[sort_idx]
            const_lat_x, const_lat_y = transformer.transform(const_lat_x, const_lat_y)

            # Add contour to dict, along with color and linewidth
            self.contour_data["x"].append(const_lat_x)
            self.contour_data["y"].append(const_lat_y)
            self.contour_data["color"].append(color)
            self.contour_data["linewidth"].append(linewidth)
            self.contour_data["linestyle"].append(linestyle)
            self.contour_data["marker"].append(marker)
            self.contour_data["zorder"].append(int(bring_to_front))

    # Now handling lines of constant longitude
    if constant_lons is not None:
        # Generate latitudinal domain of the lon line (full globe)
        lat_domain = np.arange(-90, 90 + 0.1, 0.1)

        # iterate through all lines of constant lon requested
        for lon in constant_lons:
            # Create line of constant lon from magnetic coords
            const_lon_x, const_lon_y = (lat_domain * 0 + lon, lat_domain)
            const_lon_y, const_lon_x, alt = aacgmv2.convert_latlon_arr(const_lon_y, const_lon_x, const_lon_x * 0.0, timestamp, method_code="A2G")
            sort_idx = np.argsort(const_lon_y)
            const_lon_x = const_lon_x[sort_idx]
            const_lon_y = const_lon_y[sort_idx]
            const_lon_x, const_lon_y = transformer.transform(const_lon_x, const_lon_y)

            # Add contour to dict, along with color and linewidth
            self.contour_data["x"].append(const_lon_x)
            self.contour_data["y"].append(const_lon_y)
            self.contour_data["color"].append(color)
            self.contour_data["linewidth"].append(linewidth)
            self.contour_data["linestyle"].append(linestyle)
            self.contour_data["marker"].append(marker)
            self.contour_data["zorder"].append(int(bring_to_front))

Add geomagnetic contours to a mosaic.

Args

timestamp : datetime.datetime
The timestamp used in computing AACGM coordinates.
lats : ndarray or list
Sequence of geomagnetic latitudes defining a contour.
lons : ndarray or list
Sequence of geomagnetic longitudes defining a contour.
constant_lats : float, int, Sequence
Geomagnetic latitude(s) at which to add contour(s) of constant latitude.
constant_lons : float, int, Sequence
Geomagnetic longitude(s) at which to add contours(s) of constant longitude.
color : str
The matplotlib color used for the contour(s).
linewidth : float or int
The contour thickness.
linestyle : str
The matplotlib linestyle used for the contour(s).
marker : str
The matplotlib marker used for the contour(s).
bring_to_front : bool
Plots the contour on top of all other currently plotted objects.

Returns

The object's contour_data parameter is populated appropriately.

Raises

ValueError
issues encountered with supplied parameters
def plot(self,
map_extent: Sequence[float | int],
figsize: Tuple[int, int] | None = None,
rayleighs: bool = False,
max_rayleighs: int = 20000,
title: str | None = None,
ocean_color: str | None = None,
land_color: str = 'gray',
land_edgecolor: str = '#8A8A8A',
borders_color: str = '#AEAEAE',
borders_disable: bool = False,
colorbar_title: str | None = None,
cbar_title: str | None = None,
cbar_colormap: str = '',
returnfig: bool = False,
savefig: bool = False,
savefig_filename: str | None = None,
savefig_quality: int | None = None) ‑> Any
Expand source code
def plot(
        self,
        map_extent: Sequence[Union[float, int]],
        figsize: Optional[Tuple[int, int]] = None,
        rayleighs: bool = False,
        max_rayleighs: int = 20000,
        title: Optional[str] = None,
        ocean_color: Optional[str] = None,
        land_color: str = "gray",
        land_edgecolor: str = "#8A8A8A",
        borders_color: str = "#AEAEAE",
        borders_disable: bool = False,
        colorbar_title: Optional[str] = None,  # deprecated in v1.11.0, use cbar_title instead
        cbar_title: Optional[str] = None,
        cbar_colormap: str = "",
        returnfig: bool = False,
        savefig: bool = False,
        savefig_filename: Optional[str] = None,
        savefig_quality: Optional[int] = None) -> Any:
    """
    Generate a plot of the mosaic data. 
    
    Either display it (default behaviour), save it to disk (using the `savefig` parameter), or 
    return the matplotlib plot object for further usage (using the `returnfig` parameter).

    Args:
        map_extent (List[int]): 
            Latitude/longitude range to be visible on the rendered map. This is a list of 4 integers 
            and/or floats, in the order of [min_lon, max_lon, min_lat, max_lat].

        figsize (tuple): 
            The matplotlib figure size to use when plotting. For example `figsize=(14,4)`.

        rayleighs (bool): 
            Set to `True` if the data being plotted is in Rayleighs. Defaults to `False`.

        max_rayleighs (int): 
            Max intensity scale for Rayleighs. Defaults to `20000`.

        title (str): 
            The title to display above the plotted mosaic. Default is no title.

        ocean_color (str): 
            Colour of the ocean. Default is cartopy's default shade of blue. Colours can be supplied
            as a word, or hexcode prefixed with a '#' character (ie. `#55AADD`).
        
        land_color (str): 
            Colour of the land. Default is `gray`. Colours can be supplied as a word, or hexcode 
            prefixed with a '#' character (ie. `#41BB87`).

        land_edgecolor (str): 
            Color of the land edges. Default is `#8A8A8A`. Colours can be supplied as a word, or
            hexcode prefixed with a '#' character.

        borders_color (str): 
            Color of the country borders. Default is `AEAEAE`. Colours can be supplied as a word, or
            hexcode prefixed with a '#' character.
        
        borders_disable (bool): 
            Disbale rendering of the borders. Default is `False`.

        cbar_title (str): 
            Title for the colorbar. Default is no title.

        colorbar_title (str): 
            Deprecated as of v1.10.0. Use 'cbar_title' instead in the exact same way.

        cbar_colormap (str): 
            The matplotlib colormap to use for the plotted color bar. Default is `gray`, unless
            mosaic was created with spectrograph data, in which case defaults to the colormap
            used for spectrograph data..

            Commonly used colormaps are:

            - REGO: `gist_heat`
            - THEMIS ASI: `gray`
            - TREx Blue: `Blues_r`
            - TREx NIR: `gray`
            - TREx RGB: `None`

            A list of all available colormaps can be found on the 
            [matplotlib documentation](https://matplotlib.org/stable/gallery/color/colormap_reference.html).

        returnfig (bool): 
            Instead of displaying the image, return the matplotlib figure object. This allows for further plot 
            manipulation, for example, adding labels or a title in a different location than the default. 
            
            Remember - if this parameter is supplied, be sure that you close your plot after finishing work 
            with it. This can be achieved by doing `plt.close(fig)`. 
            
            Note that this method cannot be used in combination with `savefig`.

        savefig (bool): 
            Save the displayed image to disk instead of displaying it. The parameter savefig_filename is required if 
            this parameter is set to True. Defaults to `False`.

        savefig_filename (str): 
            Filename to save the image to. Must be specified if the savefig parameter is set to True.

        savefig_quality (int): 
            Quality level of the saved image. This can be specified if the savefig_filename is a JPG image. If it
            is a PNG, quality is ignored. Default quality level for JPGs is matplotlib/Pillow's default of 75%.

    Returns:
        The displayed montage, by default. If `savefig` is set to True, nothing will be returned. If `returnfig` is 
        set to True, the plotting variables `(fig, ax)` will be returned.

    Raises:
    """
    # handle deprecation warnings
    if (colorbar_title is not None):
        show_warning("The parameter 'colorbar_title' was deprecated in v1.11.0. Please use 'cbar_title' instead (usage is identical).",
                     stacklevel=1)
        cbar_title = colorbar_title

    # check return mode
    if (returnfig is True and savefig is True):
        raise ValueError("Only one of returnfig or savefig can be set to True")
    if returnfig is True and (savefig_filename is not None or savefig_quality is not None):
        show_warning("The figure will be returned, but a savefig option parameter was supplied. Consider " +
                     "removing the savefig option parameter(s) as they will be ignored.",
                     stacklevel=1)
    elif (savefig is False and (savefig_filename is not None or savefig_quality is not None)):
        show_warning("A savefig option parameter was supplied, but the savefig parameter is False. The " +
                     "savefig option parameters will be ignored.",
                     stacklevel=1)

    # get colormap if there is spectrograph data
    if self.spect_cmap is not None:
        cbar_colormap = self.spect_cmap

    # initialize figure
    fig = plt.figure(figsize=figsize)
    ax = fig.add_axes((0, 0, 1, 1), projection=self.cartopy_projection)
    ax.set_extent(map_extent, crs=cartopy.crs.Geodetic())  # type: ignore

    # add ocean
    #
    # NOTE: we use the default ocean color
    if (ocean_color is not None):
        ax.add_feature(  # type: ignore
            cartopy.feature.OCEAN, facecolor=ocean_color, zorder=0)
    else:
        ax.add_feature(cartopy.feature.OCEAN, zorder=0)  # type: ignore

    # add land
    ax.add_feature(  # type: ignore
        cartopy.feature.LAND, facecolor=land_color, edgecolor=land_edgecolor, zorder=0)

    # add borders
    if (borders_disable is False):
        ax.add_feature(  # type: ignore
            cartopy.feature.BORDERS, edgecolor=borders_color, zorder=0)

    # add polygon data
    #
    # NOTE: it seems that when running this function a second time, the polygon
    # data is not too happy. So to handle this, we plot a copy of the polygon data
    if isinstance(self.polygon_data, list):
        for polygon_data in self.polygon_data:  # pragma: nocover
            ax.add_collection(copy(polygon_data))
    else:
        ax.add_collection(copy(self.polygon_data))

    if self.contour_data is not None:
        for i in range(len(self.contour_data["x"])):
            ax.plot(self.contour_data["x"][i],
                    self.contour_data["y"][i],
                    color=self.contour_data["color"][i],
                    linewidth=self.contour_data["linewidth"][i],
                    linestyle=self.contour_data["linestyle"][i],
                    marker=self.contour_data["marker"][i],
                    zorder=self.contour_data["zorder"][i])

    # set title
    if (title is not None):
        ax.set_title(title)

    # add text
    if (rayleighs is True):
        if isinstance(self.polygon_data, list):  # pragma: nocover
            raise ValueError("Rayleighs Keyword is currently not available for mosaics with multiple sets of data.")

        # Create a colorbar, in Rayleighs, that accounts for the scaling limit we applied
        cbar_ticks = [float(j) / 5. for j in range(0, 6)]
        cbar_ticknames = [str(int(max_rayleighs / 5) * j) for j in range(0, 6)]

        # Any pixels with the max Rayleigh value could be greater than it, so we include the plus sign
        cbar_ticknames[-1] += "+"
        self.polygon_data.set_cmap(cbar_colormap)
        cbar = plt.colorbar(self.polygon_data, shrink=0.5, ticks=cbar_ticks, ax=ax)
        cbar.ax.set_yticklabels(cbar_ticknames)
        plt.text(1.025,
                 0.5,
                 "Intensity (Rayleighs)",
                 fontsize=14,
                 transform=ax.transAxes,
                 va="center",
                 rotation="vertical",
                 weight="bold",
                 style="oblique")

    if (self.spect_cmap) is not None:

        if (self.spect_intensity_scale) is None:  # pragma: nocover
            intensity_max = np.nan
            intensity_min = np.nan
        else:
            intensity_max = self.spect_intensity_scale[1]
            intensity_min = self.spect_intensity_scale[0]

        # Create a colorbar, in Rayleighs, that accounts for the scaling limit we applied
        cbar_ticks = [float(j) / 5. for j in range(0, 6)]
        cbar_ticknames = [str(int((intensity_max / 5) + intensity_min) * j) for j in range(0, 6)]

        # Any specrograph bins with the max intensity value could be greater than it, so we include the plus sign
        cbar_ticknames[-1] += "+"
        if isinstance(self.polygon_data, list):  # pragma: nocover
            self.polygon_data[0].set_cmap(cbar_colormap)
        else:
            self.polygon_data.set_cmap(cbar_colormap)
        if isinstance(self.polygon_data, list):  # pragma: nocover
            cbar = plt.colorbar(self.polygon_data[0], shrink=0.5, ticks=cbar_ticks, ax=ax)
        else:
            cbar = plt.colorbar(self.polygon_data, shrink=0.5, ticks=cbar_ticks, ax=ax)
        cbar.ax.set_yticklabels(cbar_ticknames)
        if (cbar_title is None):
            plt.text(1.025,
                     0.5,
                     "Spectrograph Intensity (Rayleighs)",
                     fontsize=10,
                     transform=ax.transAxes,
                     va="center",
                     rotation="vertical",
                     weight="bold",
                     style="oblique")
        else:
            plt.text(1.025,
                     0.5,
                     cbar_title,
                     fontsize=10,
                     transform=ax.transAxes,
                     va="center",
                     rotation="vertical",
                     weight="bold",
                     style="oblique")

    # save figure or show it
    if (savefig is True):
        # check that filename has been set
        if (savefig_filename is None):
            raise ValueError("The savefig_filename parameter is missing, but required since savefig was set to True.")

        # save the figure
        f_extension = os.path.splitext(savefig_filename)[-1].lower()
        if (".jpg" == f_extension or ".jpeg" == f_extension):
            # check quality setting
            if (savefig_quality is not None):
                plt.savefig(savefig_filename, pil_kwargs={"quality": savefig_quality}, bbox_inches="tight")
            else:
                plt.savefig(savefig_filename, bbox_inches="tight")
        else:
            if (savefig_quality is not None):
                # quality specified, but output filename is not a JPG, so show a warning
                show_warning("The savefig_quality parameter was specified, but is only used for saving JPG files. The " +
                             "savefig_filename parameter was determined to not be a JPG file, so the quality will be ignored",
                             stacklevel=1)
            plt.savefig(savefig_filename, bbox_inches="tight")

        # clean up by closing the figure
        plt.close(fig)
    elif (returnfig is True):
        # return the figure and axis objects
        return (fig, ax)
    else:
        # show the figure
        plt.show(fig)

        # cleanup by closing the figure
        plt.close(fig)

    # return
    return None

Generate a plot of the mosaic data.

Either display it (default behaviour), save it to disk (using the savefig parameter), or return the matplotlib plot object for further usage (using the returnfig parameter).

Args

map_extent : List[int]
Latitude/longitude range to be visible on the rendered map. This is a list of 4 integers and/or floats, in the order of [min_lon, max_lon, min_lat, max_lat].
figsize : tuple
The matplotlib figure size to use when plotting. For example figsize=(14,4).
rayleighs : bool
Set to True if the data being plotted is in Rayleighs. Defaults to False.
max_rayleighs : int
Max intensity scale for Rayleighs. Defaults to 20000.
title : str
The title to display above the plotted mosaic. Default is no title.
ocean_color : str
Colour of the ocean. Default is cartopy's default shade of blue. Colours can be supplied as a word, or hexcode prefixed with a '#' character (ie. #55AADD).
land_color : str
Colour of the land. Default is gray. Colours can be supplied as a word, or hexcode prefixed with a '#' character (ie. #41BB87).
land_edgecolor : str
Color of the land edges. Default is #8A8A8A. Colours can be supplied as a word, or hexcode prefixed with a '#' character.
borders_color : str
Color of the country borders. Default is AEAEAE. Colours can be supplied as a word, or hexcode prefixed with a '#' character.
borders_disable : bool
Disbale rendering of the borders. Default is False.
cbar_title : str
Title for the colorbar. Default is no title.
colorbar_title : str
Deprecated as of v1.10.0. Use 'cbar_title' instead in the exact same way.
cbar_colormap : str

The matplotlib colormap to use for the plotted color bar. Default is gray, unless mosaic was created with spectrograph data, in which case defaults to the colormap used for spectrograph data..

Commonly used colormaps are:

  • REGO: gist_heat
  • THEMIS ASI: gray
  • TREx Blue: Blues_r
  • TREx NIR: gray
  • TREx RGB: None

A list of all available colormaps can be found on the matplotlib documentation.

returnfig : bool

Instead of displaying the image, return the matplotlib figure object. This allows for further plot manipulation, for example, adding labels or a title in a different location than the default.

Remember - if this parameter is supplied, be sure that you close your plot after finishing work with it. This can be achieved by doing plt.close(fig).

Note that this method cannot be used in combination with savefig.

savefig : bool
Save the displayed image to disk instead of displaying it. The parameter savefig_filename is required if this parameter is set to True. Defaults to False.
savefig_filename : str
Filename to save the image to. Must be specified if the savefig parameter is set to True.
savefig_quality : int
Quality level of the saved image. This can be specified if the savefig_filename is a JPG image. If it is a PNG, quality is ignored. Default quality level for JPGs is matplotlib/Pillow's default of 75%.

Returns

The displayed montage, by default. If savefig is set to True, nothing will be returned. If returnfig is set to True, the plotting variables (fig, ax) will be returned. Raises:

def pretty_print(self)
Expand source code
def pretty_print(self):
    """
    A special print output for this class.
    """
    # set special strings
    if isinstance(self.polygon_data, list):
        polycollection_str = "[PolyCollection(...), ...]"  # pragma: nocover
    else:
        polycollection_str = "PolyCollection(...)"
    cartopy_projection_str = "Projection(%s)" % (self.cartopy_projection.to_string())
    if self.contour_data is not None:
        contour_data_str = "%d Contours" % (len(self.contour_data.get("x", [])), )  # pragma: nocover
    else:
        contour_data_str = "None"

    # print
    print("Mosaic:")
    print("  %-23s: %s" % ("polygon_data", polycollection_str))
    print("  %-23s: %s" % ("cartopy_projection", cartopy_projection_str))
    print("  %-23s: %s" % ("contour_data", contour_data_str))
    print("  %-23s: %s" % ("spect_cmap", self.spect_cmap))
    print("  %-23s: %s" % ("spect_intensity_scale", self.spect_intensity_scale))

A special print output for this class.

class MosaicData (site_uid_list: List[str],
timestamps: List[datetime.datetime],
images: Dict[str, numpy.ndarray],
images_dimensions: Dict[str, Tuple],
data_types: List[str])
Expand source code
@dataclass
class MosaicData:
    """
    Prepared image data for use by mosaic routines.

    Attributes:
        site_uid_list (List[str]): 
            List of site unique identifiers contained within this object.

        timestamps (List[datetime.datetime]): 
            Timestamps of corresponding images.

        images (Dict[str, numpy.ndarray]): 
            Image data prepared into the necessary format; a dictionary. Keys are the site UID, 
            ndarray is the prepared data.

        images_dimensions (Dict[str, Tuple]): 
            The image dimensions.

        data_types (List[str]): 
            The data types for each data object.    
    """

    site_uid_list: List[str]
    timestamps: List[datetime.datetime]
    images: Dict[str, ndarray]
    images_dimensions: Dict[str, Tuple]
    data_types: List[str]

    def __str__(self) -> str:
        return self.__repr__()

    def __repr__(self) -> str:
        # set special strings
        unique_dimensions_str = str(list(dict.fromkeys(self.images_dimensions.values()))).replace("[", "").replace("]", "").replace("), (", "),(")
        images_str = "Dict[%d sites of array(dims=%s)]" % (len(self.images.keys()), unique_dimensions_str)
        timestamps_str = "[%d timestamps]" % (len(self.timestamps))

        # return
        return "MosaicData(images=%s, timestamps=%s, site_uid_list=%s)" % (images_str, timestamps_str, self.site_uid_list.__repr__())

    def pretty_print(self):
        """
        A special print output for this class.
        """
        # set special strings
        unique_dimensions_str = str(list(dict.fromkeys(self.images_dimensions.values()))).replace("[", "").replace("]", "").replace("), (", "),(")
        images_str = "Dict[%d sites of array(dims=%s)]" % (len(self.images.keys()), unique_dimensions_str)
        timestamps_str = "[%d timestamps]" % (len(self.timestamps))

        # print
        print("MosaicData:")
        print("  %-19s: %s" % ("site_uid_list", self.site_uid_list))
        print("  %-19s: %s" % ("timestamps", timestamps_str))
        print("  %-19s: %s" % ("images", images_str))
        print("  %-19s: %s" % ("images_dimensions", self.images_dimensions))
        print("  %-19s: %s" % ("data_types", self.data_types))

Prepared image data for use by mosaic routines.

Attributes

site_uid_list : List[str]
List of site unique identifiers contained within this object.
timestamps : List[datetime.datetime]
Timestamps of corresponding images.
images : Dict[str, numpy.ndarray]
Image data prepared into the necessary format; a dictionary. Keys are the site UID, ndarray is the prepared data.
images_dimensions : Dict[str, Tuple]
The image dimensions.
data_types : List[str]
The data types for each data object.

Instance variables

var data_types : List[str]
var images : Dict[str, numpy.ndarray]
var images_dimensions : Dict[str, Tuple]
var site_uid_list : List[str]
var timestamps : List[datetime.datetime]

Methods

def pretty_print(self)
Expand source code
def pretty_print(self):
    """
    A special print output for this class.
    """
    # set special strings
    unique_dimensions_str = str(list(dict.fromkeys(self.images_dimensions.values()))).replace("[", "").replace("]", "").replace("), (", "),(")
    images_str = "Dict[%d sites of array(dims=%s)]" % (len(self.images.keys()), unique_dimensions_str)
    timestamps_str = "[%d timestamps]" % (len(self.timestamps))

    # print
    print("MosaicData:")
    print("  %-19s: %s" % ("site_uid_list", self.site_uid_list))
    print("  %-19s: %s" % ("timestamps", timestamps_str))
    print("  %-19s: %s" % ("images", images_str))
    print("  %-19s: %s" % ("images_dimensions", self.images_dimensions))
    print("  %-19s: %s" % ("data_types", self.data_types))

A special print output for this class.

class MosaicSkymap (site_uid_list: List[str],
elevation: List[numpy.ndarray],
polyfill_lat: List[numpy.ndarray],
polyfill_lon: List[numpy.ndarray])
Expand source code
@dataclass
class MosaicSkymap:
    """
    Prepared skymap data for use by mosaic routines.

    Attributes:
        site_uid_list (List[str]): 
            List of site unique identifiers contained within this object.

        elevation (List[numpy.ndarray]): 
            List of elevation data, with each element corresponding to each site. Order 
            matches that of the `site_uid_list` attribute.

        polyfill_lat (List[numpy.ndarray]): 
            List of latitude polygon data, with each element corresponding to each site. 
            Order matches that of the `site_uid_list` attribute. 

        polyfill_lon (List[numpy.ndarray]): 
            List of longitude polygon data, with each element corresponding to each site. 
            Order matches that of the `site_uid_list` attribute. 

    """

    site_uid_list: List[str]
    elevation: List[ndarray]
    polyfill_lat: List[ndarray]
    polyfill_lon: List[ndarray]

    def __str__(self) -> str:
        return self.__repr__()

    def __repr__(self) -> str:
        # set special strings
        unique_polyfill_lat_dims = str(list(dict.fromkeys(fill_arr.shape
                                                          for fill_arr in self.polyfill_lat))).replace("[", "").replace("]",
                                                                                                                        "").replace("), (", "),(")
        unique_polyfill_lon_dims = str(list(dict.fromkeys(fill_arr.shape
                                                          for fill_arr in self.polyfill_lon))).replace("[", "").replace("]",
                                                                                                                        "").replace("), (", "),(")
        unique_elevation_dims = str(list(dict.fromkeys(el.shape for el in self.elevation))).replace("[", "").replace("]", "").replace("), (", "),(")

        polyfill_lat_str = "array(dims=%s, dtype=%s)" % (unique_polyfill_lat_dims, self.polyfill_lat[0].dtype)
        polyfill_lon_str = "array(dims=%s, dtype=%s)" % (unique_polyfill_lon_dims, self.polyfill_lon[0].dtype)
        elevation_str = "array(dims=%s, dtype=%s)" % (unique_elevation_dims, self.elevation[0].dtype)

        # return
        return "MosaicSkymap(polyfill_lat=%s, polyfill_lon=%s, elevation=%s, site_uid_list=%s)" % (
            polyfill_lat_str,
            polyfill_lon_str,
            elevation_str,
            self.site_uid_list.__repr__(),
        )

    def pretty_print(self):
        """
        A special print output for this class.
        """
        unique_polyfill_lat_dims = str(list(dict.fromkeys(fill_arr.shape
                                                          for fill_arr in self.polyfill_lat))).replace("[", "").replace("]",
                                                                                                                        "").replace("), (", "),(")
        unique_polyfill_lon_dims = str(list(dict.fromkeys(fill_arr.shape
                                                          for fill_arr in self.polyfill_lon))).replace("[", "").replace("]",
                                                                                                                        "").replace("), (", "),(")
        unique_elevation_dims = str(list(dict.fromkeys(el.shape for el in self.elevation))).replace("[", "").replace("]", "").replace("), (", "),(")

        polyfill_lat_str = "array(dims=%s, dtype=%s)" % (unique_polyfill_lat_dims, self.polyfill_lat[0].dtype)
        polyfill_lon_str = "array(dims=%s, dtype=%s)" % (unique_polyfill_lon_dims, self.polyfill_lon[0].dtype)
        elevation_str = "array(dims=%s, dtype=%s)" % (unique_elevation_dims, self.elevation[0].dtype)

        # print
        print("MosaicSkymap:")
        print("  %-15s: %s" % ("polyfill_lat", polyfill_lat_str))
        print("  %-15s: %s" % ("polyfill_lon", polyfill_lon_str))
        print("  %-15s: %s" % ("elevation", elevation_str))
        print("  %-15s: %s" % ("site_uid_list", self.site_uid_list))

Prepared skymap data for use by mosaic routines.

Attributes

site_uid_list : List[str]
List of site unique identifiers contained within this object.
elevation : List[numpy.ndarray]
List of elevation data, with each element corresponding to each site. Order matches that of the site_uid_list attribute.
polyfill_lat : List[numpy.ndarray]
List of latitude polygon data, with each element corresponding to each site. Order matches that of the site_uid_list attribute.
polyfill_lon : List[numpy.ndarray]
List of longitude polygon data, with each element corresponding to each site. Order matches that of the site_uid_list attribute.

Instance variables

var elevation : List[numpy.ndarray]
var polyfill_lat : List[numpy.ndarray]
var polyfill_lon : List[numpy.ndarray]
var site_uid_list : List[str]

Methods

def pretty_print(self)
Expand source code
def pretty_print(self):
    """
    A special print output for this class.
    """
    unique_polyfill_lat_dims = str(list(dict.fromkeys(fill_arr.shape
                                                      for fill_arr in self.polyfill_lat))).replace("[", "").replace("]",
                                                                                                                    "").replace("), (", "),(")
    unique_polyfill_lon_dims = str(list(dict.fromkeys(fill_arr.shape
                                                      for fill_arr in self.polyfill_lon))).replace("[", "").replace("]",
                                                                                                                    "").replace("), (", "),(")
    unique_elevation_dims = str(list(dict.fromkeys(el.shape for el in self.elevation))).replace("[", "").replace("]", "").replace("), (", "),(")

    polyfill_lat_str = "array(dims=%s, dtype=%s)" % (unique_polyfill_lat_dims, self.polyfill_lat[0].dtype)
    polyfill_lon_str = "array(dims=%s, dtype=%s)" % (unique_polyfill_lon_dims, self.polyfill_lon[0].dtype)
    elevation_str = "array(dims=%s, dtype=%s)" % (unique_elevation_dims, self.elevation[0].dtype)

    # print
    print("MosaicSkymap:")
    print("  %-15s: %s" % ("polyfill_lat", polyfill_lat_str))
    print("  %-15s: %s" % ("polyfill_lon", polyfill_lon_str))
    print("  %-15s: %s" % ("elevation", elevation_str))
    print("  %-15s: %s" % ("site_uid_list", self.site_uid_list))

A special print output for this class.

class ToolsManager (aurorax_obj)
Expand source code
class ToolsManager:
    """
    The ToolsManager object is initialized within every PyAuroraX object. It acts as a way to access 
    the submodules and carry over configuration information in the super class.
    """

    def __init__(self, aurorax_obj):
        self.__aurorax_obj = aurorax_obj

        # initialize sub-modules
        self.__bounding_box = BoundingBoxManager(self.__aurorax_obj)
        self.__calibration = CalibrationManager()
        self.__ccd_contour = CCDContourManager()
        self.__grid_files = GridFilesManager(self.__aurorax_obj)
        self.__keogram = KeogramManager()
        self.__montage = MontageManager()
        self.__mosaic = MosaicManager(self.__aurorax_obj)
        self.__spectra = SpectraManager()
        self.__fov = FOVManager(self.__aurorax_obj)

    # ------------------------------------------
    # properties for submodule managers
    # ------------------------------------------
    @property
    def bounding_box(self):
        """
        Access to the `bounding_box` submodule from within a PyAuroraX object.
        """
        return self.__bounding_box

    @property
    def calibration(self):
        """
        Access to the `calibration` submodule from within a PyAuroraX object.
        """
        return self.__calibration

    @property
    def ccd_contour(self):
        """
        Access to the `ccd_contour` submodule from within a PyAuroraX object.
        """
        return self.__ccd_contour

    @property
    def grid_files(self):
        """
        Access to the `grid_files` submodule from within a PyAuroraX object.
        """
        return self.__grid_files

    @property
    def keogram(self):
        """
        Access to the `keogram` submodule from within a PyAuroraX object.
        """
        return self.__keogram

    @property
    def montage(self):
        """
        Access to the `montage` submodule from within a PyAuroraX object.
        """
        return self.__montage

    @property
    def mosaic(self):
        """
        Access to the `mosaic` submodule from within a PyAuroraX object.
        """
        return self.__mosaic

    @property
    def spectra(self):
        """
        Access to the `spectra` submodule from within a PyAuroraX object.
        """
        return self.__spectra

    @property
    def fov(self):
        """
        Access to the `fov` submodule from within a PyAuroraX object.
        """
        return self.__fov

    # ------------------------------------------
    # functions available at this manager level
    # ------------------------------------------
    def display(self,
                image: np.ndarray,
                cmap: Optional[str] = None,
                figsize: Optional[Tuple[int, int]] = None,
                aspect: Optional[Union[Literal["equal", "auto"], float]] = None,
                colorbar: bool = False,
                title: Optional[str] = None,
                returnfig: bool = False,
                savefig: bool = False,
                savefig_filename: Optional[str] = None,
                savefig_quality: Optional[int] = None) -> Any:
        """
        Render a visualization of a single image.

        Either display it (default behaviour), save it to disk (using the `savefig` parameter), or 
        return the matplotlib plot object for further usage (using the `returnfig` parameter).

        Args:
            image (numpy.ndarray): 
                The image to display, represented as a numpy array.

            cmap (str): 
                The matplotlib colormap to use.

                Commonly used colormaps are:

                - REGO: `gist_heat`
                - THEMIS ASI: `gray`
                - TREx Blue: `Blues_r`
                - TREx NIR: `gray`
                - TREx RGB: `None`

                A list of all available colormaps can be found on the 
                [matplotlib documentation](https://matplotlib.org/stable/gallery/color/colormap_reference.html).

            figsize (tuple): 
                The matplotlib figure size to use when displaying, tuple of two integers (ie. `figsize=(14, 4)`)
        
            aspect (str or float): 
                The matplotlib imshow aspect ration to use. A common value for this is `auto`. All valid values 
                can be found on the [matplotlib documentation](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.imshow.html).

            colorbar (bool): 
                Display a colorbar. Default is `False`.

            title (str): 
                A title to display above the rendered image. Defaults to no title.

            returnfig (bool): 
                Instead of displaying the image, return the matplotlib figure object. This allows for further plot 
                manipulation, for example, adding labels or a title in a different location than the default. 
                
                Remember - if this parameter is supplied, be sure that you close your plot after finishing work 
                with it. This can be achieved by doing `plt.close(fig)`. 
                
                Note that this method cannot be used in combination with `savefig`.

            savefig (bool): 
                Save the displayed image to disk instead of displaying it. The parameter savefig_filename is required if 
                this parameter is set to True. Defaults to `False`.

            savefig_filename (str): 
                Filename to save the image to. Must be specified if the savefig parameter is set to True.

            savefig_quality (int): 
                Quality level of the saved image. This can be specified if the savefig_filename is a JPG image. If it
                is a PNG, quality is ignored. Default quality level for JPGs is matplotlib/Pillow's default of 75%.

        Returns:
            The displayed image, by default. If `savefig` is set to True, nothing will be returned. If `returnfig` is 
            set to True, the plotting variables `(fig, ax)` will be returned.

        Raises:
            ValueError: issues encountered with supplied parameters.
        """
        return func_display(image, cmap, figsize, aspect, colorbar, title, returnfig, savefig, savefig_filename, savefig_quality)

    def movie(
        self,
        input_filenames: List[str],
        output_filename: str,
        n_parallel: int = 1,
        fps: int = 25,
        progress_bar_disable: bool = False,
    ) -> None:
        """
        Generate a movie file from a list of filenames. Note that the codec used is "mp4v".

        Args:
            input_filenames (List[str]): 
                Filenames of frames to use for movie generation. No sorting is applied, so it is 
                assumed the list is in the desired order. This parameter is required.
            
            output_filename (str): 
                Filename for the created movie file. This parameter is required.

            n_parallel (int): 
                Number of multiprocessing workers to use. Default is `1`, which does not use
                multiprocessing.

            fps (int): 
                Frames per second (FPS) for the movie file. Default is `25`.

            progress_bar_disable (bool): 
                Toggle the progress bars off. Default is `False`.        

        Raises:
            IOError: I/O related issue while generating movie
        """
        return func_movie(self.__aurorax_obj, input_filenames, output_filename, n_parallel, fps, progress_bar_disable)

    def scale_intensity(
        self,
        data: np.ndarray,
        min: Optional[float] = None,
        max: Optional[float] = None,
        top: Optional[float] = None,
        memory_saver: bool = True,
    ) -> np.ndarray:
        """
        Scale all values of an array that lie in the range min<=x<=max in to 
        the range 0<=x<=top.
        
        Args:
            data (numpy.ndarray): 
                Data array, can be 2, 3, or 4-dimensional. Assumed to be an image, or array of 
                images. Also assumed that the first 2 dimensions are the image's x and y 
                coordinates, and the following dimensions are some combination of the number of 
                images, and/or the colour channel.

            min (float): 
                Minimum value of array to be considered

            max (float): 
                Maximum value of array to be considered

            top (float): 
                Maximum value of the scaled result. If not supplied, the max value
                of the data array's dtype is used.

            memory_saver (bool): 
                Utilize less RAM when scaling a set of images. Defaults to `True`. If set to `False` then
                the scaling routine will be faster, but will utilize significantly more RAM.

        Returns:
            A new `numpy.ndarray` that is the same dimensions as the inputted data array, 
            with the scaling applied.

        Raises:
            ValueError: issues encountered with supplied min, max, or top value(s)
        """
        return func_scale_intensity(data, min, max, top, memory_saver)

    def set_theme(self, theme: str) -> None:
        """
        A handy wrapper for setting the matplotlib global theme. Common choices are `light`, 
        `dark`, or `default`.

        Args:
            theme (str): 
                Theme name. Common choices are `light`, `dark`, or `default`. If default, then
                matplotlib theme settings will be fully reset to their defaults.

                Additional themes can be found on the 
                [matplotlib documentation](https://matplotlib.org/stable/gallery/style_sheets/style_sheets_reference.html)
        """
        return func_set_theme(theme)

The ToolsManager object is initialized within every PyAuroraX object. It acts as a way to access the submodules and carry over configuration information in the super class.

Instance variables

prop bounding_box
Expand source code
@property
def bounding_box(self):
    """
    Access to the `bounding_box` submodule from within a PyAuroraX object.
    """
    return self.__bounding_box

Access to the pyaurorax.tools.bounding_box submodule from within a PyAuroraX object.

prop calibration
Expand source code
@property
def calibration(self):
    """
    Access to the `calibration` submodule from within a PyAuroraX object.
    """
    return self.__calibration

Access to the pyaurorax.tools.calibration submodule from within a PyAuroraX object.

prop ccd_contour
Expand source code
@property
def ccd_contour(self):
    """
    Access to the `ccd_contour` submodule from within a PyAuroraX object.
    """
    return self.__ccd_contour

Access to the pyaurorax.tools.ccd_contour submodule from within a PyAuroraX object.

prop fov
Expand source code
@property
def fov(self):
    """
    Access to the `fov` submodule from within a PyAuroraX object.
    """
    return self.__fov

Access to the pyaurorax.tools.fov submodule from within a PyAuroraX object.

prop grid_files
Expand source code
@property
def grid_files(self):
    """
    Access to the `grid_files` submodule from within a PyAuroraX object.
    """
    return self.__grid_files

Access to the pyaurorax.tools.grid_files submodule from within a PyAuroraX object.

prop keogram
Expand source code
@property
def keogram(self):
    """
    Access to the `keogram` submodule from within a PyAuroraX object.
    """
    return self.__keogram

Access to the pyaurorax.tools.keogram submodule from within a PyAuroraX object.

prop montage
Expand source code
@property
def montage(self):
    """
    Access to the `montage` submodule from within a PyAuroraX object.
    """
    return self.__montage

Access to the pyaurorax.tools.montage submodule from within a PyAuroraX object.

prop mosaic
Expand source code
@property
def mosaic(self):
    """
    Access to the `mosaic` submodule from within a PyAuroraX object.
    """
    return self.__mosaic

Access to the pyaurorax.tools.mosaic submodule from within a PyAuroraX object.

prop spectra
Expand source code
@property
def spectra(self):
    """
    Access to the `spectra` submodule from within a PyAuroraX object.
    """
    return self.__spectra

Access to the pyaurorax.tools.spectra submodule from within a PyAuroraX object.

Methods

def display(self,
image: numpy.ndarray,
cmap: str | None = None,
figsize: Tuple[int, int] | None = None,
aspect: Literal['equal', 'auto'] | float | None = None,
colorbar: bool = False,
title: str | None = None,
returnfig: bool = False,
savefig: bool = False,
savefig_filename: str | None = None,
savefig_quality: int | None = None) ‑> Any
Expand source code
def display(self,
            image: np.ndarray,
            cmap: Optional[str] = None,
            figsize: Optional[Tuple[int, int]] = None,
            aspect: Optional[Union[Literal["equal", "auto"], float]] = None,
            colorbar: bool = False,
            title: Optional[str] = None,
            returnfig: bool = False,
            savefig: bool = False,
            savefig_filename: Optional[str] = None,
            savefig_quality: Optional[int] = None) -> Any:
    """
    Render a visualization of a single image.

    Either display it (default behaviour), save it to disk (using the `savefig` parameter), or 
    return the matplotlib plot object for further usage (using the `returnfig` parameter).

    Args:
        image (numpy.ndarray): 
            The image to display, represented as a numpy array.

        cmap (str): 
            The matplotlib colormap to use.

            Commonly used colormaps are:

            - REGO: `gist_heat`
            - THEMIS ASI: `gray`
            - TREx Blue: `Blues_r`
            - TREx NIR: `gray`
            - TREx RGB: `None`

            A list of all available colormaps can be found on the 
            [matplotlib documentation](https://matplotlib.org/stable/gallery/color/colormap_reference.html).

        figsize (tuple): 
            The matplotlib figure size to use when displaying, tuple of two integers (ie. `figsize=(14, 4)`)
    
        aspect (str or float): 
            The matplotlib imshow aspect ration to use. A common value for this is `auto`. All valid values 
            can be found on the [matplotlib documentation](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.imshow.html).

        colorbar (bool): 
            Display a colorbar. Default is `False`.

        title (str): 
            A title to display above the rendered image. Defaults to no title.

        returnfig (bool): 
            Instead of displaying the image, return the matplotlib figure object. This allows for further plot 
            manipulation, for example, adding labels or a title in a different location than the default. 
            
            Remember - if this parameter is supplied, be sure that you close your plot after finishing work 
            with it. This can be achieved by doing `plt.close(fig)`. 
            
            Note that this method cannot be used in combination with `savefig`.

        savefig (bool): 
            Save the displayed image to disk instead of displaying it. The parameter savefig_filename is required if 
            this parameter is set to True. Defaults to `False`.

        savefig_filename (str): 
            Filename to save the image to. Must be specified if the savefig parameter is set to True.

        savefig_quality (int): 
            Quality level of the saved image. This can be specified if the savefig_filename is a JPG image. If it
            is a PNG, quality is ignored. Default quality level for JPGs is matplotlib/Pillow's default of 75%.

    Returns:
        The displayed image, by default. If `savefig` is set to True, nothing will be returned. If `returnfig` is 
        set to True, the plotting variables `(fig, ax)` will be returned.

    Raises:
        ValueError: issues encountered with supplied parameters.
    """
    return func_display(image, cmap, figsize, aspect, colorbar, title, returnfig, savefig, savefig_filename, savefig_quality)

Render a visualization of a single image.

Either display it (default behaviour), save it to disk (using the savefig parameter), or return the matplotlib plot object for further usage (using the returnfig parameter).

Args

image : numpy.ndarray
The image to display, represented as a numpy array.
cmap : str

The matplotlib colormap to use.

Commonly used colormaps are:

  • REGO: gist_heat
  • THEMIS ASI: gray
  • TREx Blue: Blues_r
  • TREx NIR: gray
  • TREx RGB: None

A list of all available colormaps can be found on the matplotlib documentation.

figsize : tuple
The matplotlib figure size to use when displaying, tuple of two integers (ie. figsize=(14, 4))
aspect : str or float
The matplotlib imshow aspect ration to use. A common value for this is auto. All valid values can be found on the matplotlib documentation.
colorbar : bool
Display a colorbar. Default is False.
title : str
A title to display above the rendered image. Defaults to no title.
returnfig : bool

Instead of displaying the image, return the matplotlib figure object. This allows for further plot manipulation, for example, adding labels or a title in a different location than the default.

Remember - if this parameter is supplied, be sure that you close your plot after finishing work with it. This can be achieved by doing plt.close(fig).

Note that this method cannot be used in combination with savefig.

savefig : bool
Save the displayed image to disk instead of displaying it. The parameter savefig_filename is required if this parameter is set to True. Defaults to False.
savefig_filename : str
Filename to save the image to. Must be specified if the savefig parameter is set to True.
savefig_quality : int
Quality level of the saved image. This can be specified if the savefig_filename is a JPG image. If it is a PNG, quality is ignored. Default quality level for JPGs is matplotlib/Pillow's default of 75%.

Returns

The displayed image, by default. If savefig is set to True, nothing will be returned. If returnfig is set to True, the plotting variables (fig, ax) will be returned.

Raises

ValueError
issues encountered with supplied parameters.
def movie(self,
input_filenames: List[str],
output_filename: str,
n_parallel: int = 1,
fps: int = 25,
progress_bar_disable: bool = False) ‑> None
Expand source code
def movie(
    self,
    input_filenames: List[str],
    output_filename: str,
    n_parallel: int = 1,
    fps: int = 25,
    progress_bar_disable: bool = False,
) -> None:
    """
    Generate a movie file from a list of filenames. Note that the codec used is "mp4v".

    Args:
        input_filenames (List[str]): 
            Filenames of frames to use for movie generation. No sorting is applied, so it is 
            assumed the list is in the desired order. This parameter is required.
        
        output_filename (str): 
            Filename for the created movie file. This parameter is required.

        n_parallel (int): 
            Number of multiprocessing workers to use. Default is `1`, which does not use
            multiprocessing.

        fps (int): 
            Frames per second (FPS) for the movie file. Default is `25`.

        progress_bar_disable (bool): 
            Toggle the progress bars off. Default is `False`.        

    Raises:
        IOError: I/O related issue while generating movie
    """
    return func_movie(self.__aurorax_obj, input_filenames, output_filename, n_parallel, fps, progress_bar_disable)

Generate a movie file from a list of filenames. Note that the codec used is "mp4v".

Args

input_filenames : List[str]
Filenames of frames to use for movie generation. No sorting is applied, so it is assumed the list is in the desired order. This parameter is required.
output_filename : str
Filename for the created movie file. This parameter is required.
n_parallel : int
Number of multiprocessing workers to use. Default is 1, which does not use multiprocessing.
fps : int
Frames per second (FPS) for the movie file. Default is 25.
progress_bar_disable : bool
Toggle the progress bars off. Default is False.

Raises

IOError
I/O related issue while generating movie
def scale_intensity(self,
data: numpy.ndarray,
min: float | None = None,
max: float | None = None,
top: float | None = None,
memory_saver: bool = True) ‑> numpy.ndarray
Expand source code
def scale_intensity(
    self,
    data: np.ndarray,
    min: Optional[float] = None,
    max: Optional[float] = None,
    top: Optional[float] = None,
    memory_saver: bool = True,
) -> np.ndarray:
    """
    Scale all values of an array that lie in the range min<=x<=max in to 
    the range 0<=x<=top.
    
    Args:
        data (numpy.ndarray): 
            Data array, can be 2, 3, or 4-dimensional. Assumed to be an image, or array of 
            images. Also assumed that the first 2 dimensions are the image's x and y 
            coordinates, and the following dimensions are some combination of the number of 
            images, and/or the colour channel.

        min (float): 
            Minimum value of array to be considered

        max (float): 
            Maximum value of array to be considered

        top (float): 
            Maximum value of the scaled result. If not supplied, the max value
            of the data array's dtype is used.

        memory_saver (bool): 
            Utilize less RAM when scaling a set of images. Defaults to `True`. If set to `False` then
            the scaling routine will be faster, but will utilize significantly more RAM.

    Returns:
        A new `numpy.ndarray` that is the same dimensions as the inputted data array, 
        with the scaling applied.

    Raises:
        ValueError: issues encountered with supplied min, max, or top value(s)
    """
    return func_scale_intensity(data, min, max, top, memory_saver)

Scale all values of an array that lie in the range min<=x<=max in to the range 0<=x<=top.

Args

data : numpy.ndarray
Data array, can be 2, 3, or 4-dimensional. Assumed to be an image, or array of images. Also assumed that the first 2 dimensions are the image's x and y coordinates, and the following dimensions are some combination of the number of images, and/or the colour channel.
min : float
Minimum value of array to be considered
max : float
Maximum value of array to be considered
top : float
Maximum value of the scaled result. If not supplied, the max value of the data array's dtype is used.
memory_saver : bool
Utilize less RAM when scaling a set of images. Defaults to True. If set to False then the scaling routine will be faster, but will utilize significantly more RAM.

Returns

A new numpy.ndarray that is the same dimensions as the inputted data array, with the scaling applied.

Raises

ValueError
issues encountered with supplied min, max, or top value(s)
def set_theme(self, theme: str) ‑> None
Expand source code
def set_theme(self, theme: str) -> None:
    """
    A handy wrapper for setting the matplotlib global theme. Common choices are `light`, 
    `dark`, or `default`.

    Args:
        theme (str): 
            Theme name. Common choices are `light`, `dark`, or `default`. If default, then
            matplotlib theme settings will be fully reset to their defaults.

            Additional themes can be found on the 
            [matplotlib documentation](https://matplotlib.org/stable/gallery/style_sheets/style_sheets_reference.html)
    """
    return func_set_theme(theme)

A handy wrapper for setting the matplotlib global theme. Common choices are light, dark, or default.

Args

theme : str

Theme name. Common choices are light, dark, or default. If default, then matplotlib theme settings will be fully reset to their defaults.

Additional themes can be found on the matplotlib documentation