Source code for arguslib.camera.camera_array

from matplotlib import pyplot as plt
import numpy as np
from pathlib import Path

from .camera import Camera
from ..instruments.instruments import PlottableInstrument, Position


[docs] class CameraArray(PlottableInstrument): def __init__(self, cameras: list[Camera], layout_shape: tuple[int, int]): self.cameras = cameras self.layout_shape = layout_shape if self.layout_shape[0] * self.layout_shape[1] < len(self.cameras): raise ValueError( "The specified layout doesn't have enough spaces to have a full layout." ) attrs = {"camstr": [c.attrs["camstr"] for c in cameras]} self.positions = self.infer_positions() super().__init__(**attrs)
[docs] @classmethod def from_config( cls, array_name, camera_class=Camera ): # TODO: make from_config a method of Instrument...? from arguslib.config import load_config camera_arrays = load_config("camera_arrays.yml") array_config = camera_arrays[array_name] cameras = [ camera_class.from_config(array_config["campaign"], c) for c in array_config["cameras"] ] # will ignore config if kwargs contains any of the keys in camera_config return cls(cameras, array_config["layout_shape"])
[docs] def infer_positions(self) -> list[tuple[int, int]]: """ Infer the positions of the cameras in the layout, based on the lat/lon under the camera Camera.Position properties. """ latitudes = [c.position.lat for c in self.cameras] longitudes = [c.position.lon for c in self.cameras] # Get the min and max lat/lon min_lat, max_lat = min(latitudes), max(latitudes) min_lon, max_lon = min(longitudes), max(longitudes) # get positions for the layout positions = [] for i in range(self.layout_shape[0]): for j in range(self.layout_shape[1]): positions.append( Position( ( np.mean(longitudes) if self.layout_shape[0] == 1 else min_lon + (max_lon - min_lon) * i / (self.layout_shape[0] - 1) ), ( np.mean(latitudes) if self.layout_shape[1] == 1 else min_lat + (max_lat - min_lat) * j / (self.layout_shape[1] - 1) ), 0.1, ) ) # get a list of distances between each camera and each position distances_to_each_pos_for_each_camera = [] for camera in self.cameras: distances_to_each_pos_for_each_camera.append( [ (np.array(camera.position.target_xyz(pos)) ** 2).sum() for pos in positions ] ) distances_to_each_pos_for_each_camera = np.array( distances_to_each_pos_for_each_camera ) closest_pos_index = np.argmin(distances_to_each_pos_for_each_camera, axis=1) # get the multiply assigned positions multiply_assigned = np.unique( closest_pos_index[np.unique(closest_pos_index, return_counts=True)[1] > 1] ) if multiply_assigned.size != 0: raise ValueError( "There are multiple cameras assigned to the same position. Disambiguation not yet implemented." # TODO: ) # for each camera, return the position within the layout camera_positions = [] for i in range(len(self.cameras)): camera_positions.append( ( (closest_pos_index[i] // self.layout_shape[1]).item(), (closest_pos_index[i] % self.layout_shape[1]).item(), ) ) return camera_positions
[docs] def show(self, dt, ax=None, replace_ax=None, label_cameras=True, **kwargs): """ Show the camera array on a map. """ if replace_ax is not None: # we need to get the positon of the axis in the figure, and replace it with the new array of subplots... fig = replace_ax.figure pos = replace_ax.get_subplotspec() replace_ax.remove() subfig = fig.add_subfigure( pos, ) axes = subfig.subplots( self.layout_shape[0], self.layout_shape[1], subplot_kw={"projection": "polar"}, ) elif ax is None: fig, axes = plt.subplots( self.layout_shape[0], self.layout_shape[1], subplot_kw={"projection": "polar"}, figsize=(8, 8), constrained_layout=True, ) else: if isinstance(ax, np.ndarray): axes = ax else: return self.show(dt, replace_ax=ax, label_cameras=label_cameras) fail_counts = 0 for i in range(self.layout_shape[0]): for j in range(self.layout_shape[1]): ax = axes[self.layout_shape[1] - j - 1, i] camera = [ c for i_cam, c in enumerate(self.cameras) if self.positions[i_cam] == (i, j) ] if len(camera) == 0: try: ax.remove() except KeyError: # if the axis is already removed, we can just ignore it pass continue camera = camera[0] ax = camera.show( dt, pos=f"{self.layout_shape[0]}{self.layout_shape[1]}{3*i+j}", ax=ax, fail_if_no_data=False, **kwargs, ) # check if it failed if ax.get_images() == []: fail_counts += 1 ax.set_thetagrids(np.arange(0, 360, 45), labels=[]) if label_cameras: ax.text( 0.15, 0.85, f"{camera.attrs['camstr']}".replace("-", "--"), va="bottom", ha="right", transform=ax.transAxes, fontsize="small", ) if fail_counts == len(self.cameras): raise FileNotFoundError("No camera data found for this time.") return axes
[docs] def annotate_positions(self, positions, dt, ax, *args, **kwargs): """ Annotate the positions of the cameras on the map. """ for i in range(self.layout_shape[0]): for j in range(self.layout_shape[1]): ax_cam = ax[self.layout_shape[1] - j - 1, i] camera = [ c for i_cam, c in enumerate(self.cameras) if self.positions[i_cam] == (i, j) ] if len(camera) == 0: continue camera = camera[0] camera.annotate_positions(positions, dt, ax_cam, *args, **kwargs)