Source code for ctapipe.instrument.guess

"""
Methods for guessing details about a telescope from some metadata, so that
we can create TelescopeDescriptions from Monte-Carlo where some
parameters like the names of the camera and optics structure are not
stored in the file.
"""
from collections import namedtuple

import astropy.units as u
import numpy as np

from .optics import ReflectorShape

__all__ = [
    "GuessingKey",
    "GuessingResult",
    "guess_telescope",
    "unknown_telescope",
    "type_from_mirror_area",
]


GuessingKey = namedtuple(
    "GuessingKey",
    ["n_pixels", "focal_length", "n_mirror_tiles"],
    defaults=(None,),  # Mirror Tiles are optional
)

GuessingResult = namedtuple(
    "GuessingResult", ["type", "name", "camera_name", "n_mirrors", "reflector_shape"]
)


def _build_lookup_tree(telescopes):
    tree = {}

    for k, v in telescopes:
        if k.n_pixels not in tree:
            tree[k.n_pixels] = {}

        if k.focal_length not in tree[k.n_pixels]:
            tree[k.n_pixels][k.focal_length] = {}

        d = tree[k.n_pixels][k.focal_length]

        if k.n_mirror_tiles in d:
            other = d[k.n_mirror_tiles]
            raise ValueError(f"GuessingKeys are not unique: {k}: {v}, {other}")

        d[k.n_mirror_tiles] = v

    return tree


# focal length must have at most two digits after period
# as we round the lookup to two digits
_sc = ReflectorShape.SCHWARZSCHILD_COUDER
_dc = ReflectorShape.DAVIES_COTTON
_h = ReflectorShape.HYBRID
_p = ReflectorShape.PARABOLIC

# This is a list of tuples instead of a dict to be able to check for duplicates
TELESCOPE_NAMES = [
    (GuessingKey(2048, 2.28), GuessingResult("SST", "GCT", "CHEC", 2, _sc)),
    (GuessingKey(2368, 2.15), GuessingResult("SST", "ASTRI", "ASTRICam", 2, _sc)),
    (GuessingKey(2048, 2.15), GuessingResult("SST", "ASTRI", "CHEC", 2, _sc)),
    (GuessingKey(1296, 5.60), GuessingResult("SST", "1M", "DigiCam", 1, _dc)),
    (GuessingKey(1764, 16.0), GuessingResult("MST", "MST", "FlashCam", 1, _h)),
    (GuessingKey(1855, 16.0), GuessingResult("MST", "MST", "NectarCam", 1, _h)),
    (GuessingKey(1855, 28.0), GuessingResult("LST", "LST", "LSTCam", 1, _p)),
    (GuessingKey(11328, 5.59), GuessingResult("MST", "SCT", "SCTCam", 1, _sc)),
    # Non-CTA Telescopes
    (
        GuessingKey(1039, 16.97, 964),
        GuessingResult("LST", "MAGIC-1", "MAGICCam", 1, _p),
    ),
    (
        GuessingKey(1039, 16.97, 247),
        GuessingResult("LST", "MAGIC-2", "MAGICCam", 1, _p),
    ),
    (GuessingKey(1039, 17.0, 964), GuessingResult("LST", "MAGIC-1", "MAGICCam", 1, _p)),
    (GuessingKey(1039, 17.0, 247), GuessingResult("LST", "MAGIC-2", "MAGICCam", 1, _p)),
    (GuessingKey(960, 15.0), GuessingResult("MST", "HESS-I", "HESS-I", 1, _dc)),
    (GuessingKey(2048, 36.0), GuessingResult("LST", "HESS-II", "HESS-II", 1, _p)),
    (GuessingKey(1440, 4.89), GuessingResult("SST", "FACT", "FACT", 1, _h)),
]
LOOKUP_TREE = _build_lookup_tree(TELESCOPE_NAMES)


[docs]def guess_telescope(n_pixels, focal_length, n_mirror_tiles=None): """ From n_pixels of the camera and the focal_length, guess which telescope we are dealing with. This is mainly needed to add human readable names to telescopes read from simtel array. Parameters ---------- n_pixels: int number of pixels of the telescope's camera focal_length: float or u.Quantity[length] Focal length, either in m or as astropy quantity Returns ------- result: GuessingResult A namedtuple having type, telescope_name, camera_name and n_mirrors fields """ focal_length = round(u.Quantity(focal_length, u.m).to_value(u.m), 2) try: d = LOOKUP_TREE[n_pixels][focal_length] # first check with n_mirror_tiles tel = d.get(n_mirror_tiles) if tel is not None: return tel # fallback to "not needed mirror tiles" return d[None] except KeyError: raise ValueError(f"Unknown telescope: n_pixel={n_pixels}, f={focal_length}")
@u.quantity_input(mirror_area=u.m**2) def type_from_mirror_area(mirror_area): mirror_diameter = (2 * np.sqrt(mirror_area / np.pi)).to_value(u.m) if mirror_diameter < 8: return "SST" if mirror_diameter < 16: return "MST" return "LST" def unknown_telescope(mirror_area, n_pixels, n_mirrors=-1): """Create a GuessingResult for an unknown_telescope""" # this allows passing a plain number in square meter and any quantity # with an area unit mirror_area = u.Quantity(mirror_area, u.m**2) return GuessingResult( type=type_from_mirror_area(mirror_area), name=f"UNKNOWN-{mirror_area.to_value(u.m**2):.0f}M2", camera_name=f"UNKNOWN-{n_pixels}PX", n_mirrors=n_mirrors, reflector_shape="UNKNOWN", )