Repository: cta-observatory/ctapipe
Pull request #1977 (2022-06-30 14:50:21)

Include number of mirror tiles in guessing keys to be able to distinguish between MAGIC 1 and MAGIC 2

kosack: You really need to add some descriptions to these though - took me a bit to figure out what M1 and M2 are
maxnoe: Sorry, I wanted to open this as draft...

Patch:
diff --git a/ctapipe/instrument/guess.py b/ctapipe/instrument/guess.py
index f2d04653..838a72e4 100644
--- a/ctapipe/instrument/guess.py
+++ b/ctapipe/instrument/guess.py
@@ -1,5 +1,5 @@
 """
-Methods for guessing details about a telescope from some metadata (so that
+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.
@@ -9,32 +9,63 @@ from collections import namedtuple
 import astropy.units as u
 import numpy as np
 
-GuessingKey = namedtuple("GuessingKey", ["n_pixels", "focal_length"])
+
+GuessingKey = namedtuple(
+    "GuessingKey",
+    ["n_pixels", "focal_length", "num_mirror_tiles"],
+    defaults=(None,),  # Mirror Tiles are optional
+)
+
 GuessingResult = namedtuple(
     "GuessingResult", ["type", "name", "camera_name", "n_mirrors"]
 )
 
 
+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.num_mirror_tiles in d:
+            other = d[k.num_mirror_tiles]
+            raise ValueError(f"GuessingKeys are not unique: {k}: {v}, {other}")
+
+        d[k.num_mirror_tiles] = v
+
+    return tree
+
+
 # focal length must have at most two digits after period
 # as we round the lookup to two digits
-TELESCOPE_NAMES = {
-    GuessingKey(2048, 2.28): GuessingResult("SST", "GCT", "CHEC", 2),
-    GuessingKey(2368, 2.15): GuessingResult("SST", "ASTRI", "ASTRICam", 2),
-    GuessingKey(2048, 2.15): GuessingResult("SST", "ASTRI", "CHEC", 2),
-    GuessingKey(1296, 5.60): GuessingResult("SST", "1M", "DigiCam", 1),
-    GuessingKey(1764, 16.0): GuessingResult("MST", "MST", "FlashCam", 1),
-    GuessingKey(1855, 16.0): GuessingResult("MST", "MST", "NectarCam", 1),
-    GuessingKey(1855, 28.0): GuessingResult("LST", "LST", "LSTCam", 1),
-    GuessingKey(11328, 5.59): GuessingResult("MST", "SCT", "SCTCam", 1),
+TELESCOPE_NAMES = [
+    (GuessingKey(2048, 2.28), GuessingResult("SST", "GCT", "CHEC", 2)),
+    (GuessingKey(2368, 2.15), GuessingResult("SST", "ASTRI", "ASTRICam", 2)),
+    (GuessingKey(2048, 2.15), GuessingResult("SST", "ASTRI", "CHEC", 2)),
+    (GuessingKey(1296, 5.60), GuessingResult("SST", "1M", "DigiCam", 1)),
+    (GuessingKey(1764, 16.0), GuessingResult("MST", "MST", "FlashCam", 1)),
+    (GuessingKey(1855, 16.0), GuessingResult("MST", "MST", "NectarCam", 1)),
+    (GuessingKey(1855, 28.0), GuessingResult("LST", "LST", "LSTCam", 1)),
+    (GuessingKey(11328, 5.59), GuessingResult("MST", "SCT", "SCTCam", 1)),
     # Non-CTA Telescopes
-    GuessingKey(1039, 16.97): GuessingResult("LST", "MAGIC", "MAGICCam", 1),
-    GuessingKey(960, 15.0): GuessingResult("MST", "HESS-I", "HESS-I", 1),
-    GuessingKey(2048, 36.0): GuessingResult("LST", "HESS-II", "HESS-II", 1),
-    GuessingKey(1440, 4.89): GuessingResult("SST", "FACT", "FACT", 1),
-}
+    (GuessingKey(1039, 16.97, 964), GuessingResult("LST", "MAGIC-1", "MAGICCam", 1)),
+    (GuessingKey(1039, 16.97, 247), GuessingResult("LST", "MAGIC-2", "MAGICCam", 1)),
+    (GuessingKey(1039, 17.0, 964), GuessingResult("LST", "MAGIC-1", "MAGICCam", 1)),
+    (GuessingKey(1039, 17.0, 247), GuessingResult("LST", "MAGIC-2", "MAGICCam", 1)),
+    (GuessingKey(960, 15.0), GuessingResult("MST", "HESS-I", "HESS-I", 1)),
+    (GuessingKey(2048, 36.0), GuessingResult("LST", "HESS-II", "HESS-II", 1)),
+    (GuessingKey(1440, 4.89), GuessingResult("SST", "FACT", "FACT", 1)),
+]
+LOOKUP_TREE = _build_lookup_tree(TELESCOPE_NAMES)
 
 
-def guess_telescope(n_pixels, focal_length):
+def guess_telescope(n_pixels, focal_length, num_mirror_tiles=None):
     """
     From n_pixels of the camera and the focal_length,
     guess which telescope we are dealing with.
@@ -53,17 +84,24 @@ def guess_telescope(n_pixels, focal_length):
     result: GuessingResult
         A namedtuple having type, telescope_name, camera_name and n_mirrors fields
     """
-    focal_length = u.Quantity(focal_length, u.m).to_value(u.m)
+    focal_length = round(u.Quantity(focal_length, u.m).to_value(u.m), 2)
 
     try:
-        return TELESCOPE_NAMES[(n_pixels, round(focal_length, 2))]
+        d = LOOKUP_TREE[n_pixels][focal_length]
+        # first check with num_mirror_tiles
+        tel = d.get(num_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}")
 
 
 def unknown_telescope(mirror_area, n_pixels, n_mirrors=-1):
     """Create a GuessingResult for an unknown_telescope"""
-    mirror_area = u.Quantity(mirror_area, u.m ** 2).to_value(u.m ** 2)
+    mirror_area = u.Quantity(mirror_area, u.m**2).to_value(u.m**2)
 
     mirror_diameter = 2 * np.sqrt(mirror_area / np.pi)
     if mirror_diameter < 8:
diff --git a/ctapipe/io/simteleventsource.py b/ctapipe/io/simteleventsource.py
index a4cd6917..e19abf1a 100644
--- a/ctapipe/io/simteleventsource.py
+++ b/ctapipe/io/simteleventsource.py
@@ -318,7 +318,11 @@ class SimTelEventSource(EventSource):
             )
 
             try:
-                telescope = guess_telescope(n_pixels, equivalent_focal_length)
+                telescope = guess_telescope(
+                    n_pixels,
+                    equivalent_focal_length,
+                    cam_settings["n_mirrors"],
+                )
             except ValueError:
                 telescope = unknown_telescope(mirror_area, n_pixels)
 


Test patch:
diff --git a/ctapipe/instrument/tests/test_guessing.py b/ctapipe/instrument/tests/test_guessing.py
index b71bc970..d1c66d74 100644
--- a/ctapipe/instrument/tests/test_guessing.py
+++ b/ctapipe/instrument/tests/test_guessing.py
@@ -5,11 +5,12 @@ from pytest import raises
 def test_guessing():
     from ctapipe.instrument import guess_telescope
 
-    guess = guess_telescope(2048, 2.28)
+    # n_tiles should not be used for GCT since pixels + focal length is unique
+    guess = guess_telescope(2048, 2.28, None)
     assert guess.type == "SST"
     assert guess.name == "GCT"
 
-    guess = guess_telescope(2048, 2.28 * u.m)
+    guess = guess_telescope(2048, 2.28 * u.m, 32)
     assert guess.type == "SST"
     assert guess.name == "GCT"
 
@@ -23,6 +24,9 @@ def test_guessing():
     assert guess.camera_name == "FlashCam"
     assert guess.type == "MST"
 
+    assert guess_telescope(1039, 16.97, 964).name == "MAGIC-1"
+    assert guess_telescope(1039, 16.97, 247).name == "MAGIC-2"
+
 
 def test_unknown_telescope():
     from ctapipe.instrument.guess import unknown_telescope
@@ -37,7 +41,7 @@ def test_unknown_telescope():
     assert tel.name == "UNKNOWN-10M2"
     assert tel.camera_name == "UNKNOWN-2048PX"
 
-    tel = unknown_telescope(100 * u.m ** 2, 2048)
+    tel = unknown_telescope(100 * u.m**2, 2048)
     assert tel.type == "MST"
     assert tel.name == "UNKNOWN-100M2"
     assert tel.camera_name == "UNKNOWN-2048PX"