Repository: cta-observatory/ctapipe
Pull request #2482 (2023-12-13 10:31:23)

Move `TableLoader` traits to `read_...`-methods
As discussed in #2165 and related to #2012. Thanks to @ccossou!

I've also added a new example/tutorial related to TableLoader and EventSources based on the hands-on presented in October 2023 at the DPPS Meeting at CEA Paris-Saclay.




Patch:
diff --git a/ctapipe/io/tableloader.py b/ctapipe/io/tableloader.py
index f7077969..267c2d18 100644
--- a/ctapipe/io/tableloader.py
+++ b/ctapipe/io/tableloader.py
@@ -82,7 +82,6 @@ class ChunkIterator:
         return self.n_chunks
 
     def __getitem__(self, chunk):
-
         if chunk < 0:
             chunk = self.n_chunks - chunk
 
@@ -161,7 +160,7 @@ class TableLoader(Component):
 
     * `TableLoader.read_subarray_events`
     * `TableLoader.read_telescope_events`
-    * `TableLoader.read_telescope_events_by_type` retuns a dict with a table per
+    * `TableLoader.read_telescope_events_by_type` returns a dict with a table per
       telescope type, which is needed for e.g. DL1 image data that might have
       different shapes for each of the telescope types as tables do not support
       variable length columns.
@@ -175,37 +174,33 @@ class TableLoader(Component):
     input_url = traits.Path(
         directory_ok=False, exists=True, allow_none=True, default_value=None
     ).tag(config=True)
-
-    load_dl1_images = traits.Bool(False, help="load extracted images").tag(config=True)
-    load_dl1_parameters = traits.Bool(
-        True, help="load reconstructed image parameters"
-    ).tag(config=True)
-    load_dl1_muons = traits.Bool(False, help="load muon ring parameters").tag(
+    dl1_images = traits.Bool(False, help="load extracted images").tag(config=True)
+    dl1_parameters = traits.Bool(True, help="load reconstructed image parameters").tag(
         config=True
     )
+    dl1_muons = traits.Bool(False, help="load muon ring parameters").tag(config=True)
 
-    load_dl2 = traits.Bool(False, help="load available dl2 stereo parameters").tag(
+    dl2 = traits.Bool(True, help="load available dl2 stereo parameters").tag(
         config=True
     )
 
-    load_simulated = traits.Bool(False, help="load simulated shower information").tag(
+    simulated = traits.Bool(True, help="load simulated shower information").tag(
         config=True
     )
-    load_true_images = traits.Bool(False, help="load simulated shower images").tag(
+    true_images = traits.Bool(False, help="load simulated shower images").tag(
         config=True
     )
-    load_true_parameters = traits.Bool(
-        False, help="load image parameters obtained from true images"
+    true_parameters = traits.Bool(
+        True, help="load image parameters obtained from true images"
     ).tag(config=True)
 
-    load_instrument = traits.Bool(
+    instrument = traits.Bool(
         False, help="join subarray instrument information to each event"
     ).tag(config=True)
 
-    load_observation_info = traits.Bool(
+    observation_info = traits.Bool(
         False, help="join observation information to each event"
     ).tag(config=True)
-
     focal_length_choice = traits.UseEnum(
         FocalLengthKind,
         default_value=FocalLengthKind.EFFECTIVE,
@@ -247,23 +242,6 @@ class TableLoader(Component):
 
         Provenance().add_input_file(self.input_url, role="Event data")
 
-        self.instrument_table = None
-        if self.load_instrument:
-            self.instrument_table = self.subarray.to_table("joined")
-
-        groups = {
-            "load_dl1_parameters": PARAMETERS_GROUP,
-            "load_dl1_images": IMAGES_GROUP,
-            "load_true_parameters": TRUE_PARAMETERS_GROUP,
-            "load_true_images": TRUE_IMAGES_GROUP,
-        }
-        for attr, group in groups.items():
-            if getattr(self, attr) and group not in self.h5file.root:
-                self.log.info(
-                    "Setting %s to False, input file does not contain such data", attr
-                )
-                setattr(self, attr, False)
-
     def close(self):
         """Close the underlying hdf5 file"""
         if self._should_close:
@@ -282,6 +260,32 @@ class TableLoader(Component):
         """Number of subarray events in input file"""
         return self.h5file.root[TRIGGER_TABLE].shape[0]
 
+    def _check_args(self, **kwargs):
+        """Checking args:
+        1) If None, set to default (trait) value.
+        2) If True but correlated group is not included in input file - set to False.
+        returns a dict with new args"""
+        groups = {
+            "dl1_parameters": PARAMETERS_GROUP,
+            "dl1_images": IMAGES_GROUP,
+            "dl1_muons": MUON_GROUP,
+            "true_parameters": TRUE_PARAMETERS_GROUP,
+            "true_images": TRUE_IMAGES_GROUP,
+            "observation_info": OBSERVATION_TABLE,
+        }
+        updated_attributes = {}
+        for key, value in kwargs.items():
+            updated_value = value
+            if updated_value is None:
+                updated_value = getattr(self, key)
+            if updated_value and key in groups and groups[key] not in self.h5file.root:
+                self.log.info(
+                    "Setting %s to False, input file does not contain such data", key
+                )
+                updated_value = False
+            updated_attributes[key] = updated_value
+        return updated_attributes
+
     def _read_telescope_table(self, group, tel_id, start=None, stop=None):
         key = f"{group}/tel_{tel_id:03d}"
 
@@ -369,23 +373,53 @@ class TableLoader(Component):
             table.remove_column("__index__")
         return joint
 
-    def read_subarray_events(self, start=None, stop=None, keep_order=True):
+    def read_subarray_events(
+        self,
+        start=None,
+        stop=None,
+        dl2=None,
+        simulated=None,
+        observation_info=None,
+        keep_order=True,
+    ):
         """Read subarray-based event information.
 
+        Parameters
+        ----------
+
+        dl2: bool
+            load available dl2 stereo parameters
+        simulated: bool
+            load simulated shower information
+        observation_info: bool
+            join observation information to each event
+        start: int
+            First *subarray* event to read
+        stop: int
+            Last *subarray* event (non-inclusive)
+
         Returns
         -------
         table: astropy.io.Table
             Table with primary index columns "obs_id" and "event_id".
         """
+        updated_attributes = self._check_args(
+            dl2=dl2, simulated=simulated, observation_info=observation_info
+        )
+
+        dl2 = updated_attributes["dl2"]
+        simulated = updated_attributes["simulated"]
+        observation_info = updated_attributes["observation_info"]
+
         table = read_table(self.h5file, TRIGGER_TABLE, start=start, stop=stop)
         if keep_order:
             self._add_index_if_needed(table)
 
-        if self.load_simulated and SHOWER_TABLE in self.h5file:
+        if simulated and SHOWER_TABLE in self.h5file:
             showers = read_table(self.h5file, SHOWER_TABLE, start=start, stop=stop)
             table = _merge_subarray_tables(table, showers)
 
-        if self.load_dl2:
+        if dl2:
             if DL2_SUBARRAY_GROUP in self.h5file:
                 for group_name in self.h5file.root[DL2_SUBARRAY_GROUP]._v_children:
                     group_path = f"{DL2_SUBARRAY_GROUP}/{group_name}"
@@ -400,7 +434,7 @@ class TableLoader(Component):
                         )
                         table = _merge_subarray_tables(table, dl2)
 
-        if self.load_observation_info:
+        if observation_info:
             table = self._join_observation_info(table)
 
         if keep_order:
@@ -424,7 +458,20 @@ class TableLoader(Component):
             kwargs=kwargs,
         )
 
-    def _read_telescope_events_for_id(self, tel_id, start=None, stop=None):
+    def _read_telescope_events_for_id(
+        self,
+        tel_id,
+        dl1_images,
+        dl1_parameters,
+        dl1_muons,
+        dl2,
+        simulated,
+        true_images,
+        true_parameters,
+        instrument,
+        start=None,
+        stop=None,
+    ):
         """Read telescope-based event information for a single telescope.
 
         This is the most low-level function doing the actual reading.
@@ -434,9 +481,25 @@ class TableLoader(Component):
         tel_id: int
             Telescope identification number.
         start: int
-            First subarray event index to read
+            First *subarray* event to read
         stop: int
-            Last subarray event index to read
+            Last *subarray* event (non-inclusive)
+        dl1_images: bool
+            load extracted images
+        dl1_parameters: bool
+            load reconstructed image parameters
+        dl1_muons: bool
+            load muon ring parameters
+        dl2: bool
+            load available dl2 stereo parameters
+        simulated: bool
+            load simulated shower information
+        true_images: bool
+            load simulated shower images
+        true_parameters: bool
+            load image parameters obtained from true images
+        instrument: bool
+            join subarray instrument information to each event
 
         Returns
         -------
@@ -468,25 +531,25 @@ class TableLoader(Component):
             stop=trigger_stop,
         )
 
-        if self.load_dl1_parameters:
+        if dl1_parameters:
             parameters = self._read_telescope_table(
                 PARAMETERS_GROUP, tel_id, start=tel_start, stop=tel_stop
             )
             table = _merge_telescope_tables(table, parameters)
 
-        if self.load_dl1_muons:
+        if dl1_muons:
             muon_parameters = self._read_telescope_table(
                 MUON_GROUP, tel_id, start=tel_start, stop=tel_stop
             )
             table = _merge_telescope_tables(table, muon_parameters)
 
-        if self.load_dl1_images:
+        if dl1_images:
             images = self._read_telescope_table(
                 IMAGES_GROUP, tel_id, start=tel_start, stop=tel_stop
             )
             table = _merge_telescope_tables(table, images)
 
-        if self.load_dl2:
+        if dl2:
             if DL2_TELESCOPE_GROUP in self.h5file:
                 dl2_tel_group = self.h5file.root[DL2_TELESCOPE_GROUP]
                 for group_name in dl2_tel_group._v_children:
@@ -503,24 +566,25 @@ class TableLoader(Component):
 
                         table = _merge_telescope_tables(table, dl2)
 
-        if self.load_true_images:
+        if true_images:
             true_images = self._read_telescope_table(
                 TRUE_IMAGES_GROUP, tel_id, start=tel_start, stop=tel_stop
             )
             table = _merge_telescope_tables(table, true_images)
 
-        if self.load_true_parameters:
+        if true_parameters:
             true_parameters = self._read_telescope_table(
                 TRUE_PARAMETERS_GROUP, tel_id, start=tel_start, stop=tel_stop
             )
             table = _join_telescope_events(table, true_parameters)
 
-        if self.load_instrument:
+        if instrument:
+            instrument_table = self.subarray.to_table("joined")
             table = join_allow_empty(
-                table, self.instrument_table, keys=["tel_id"], join_type="left"
+                table, instrument_table, keys=["tel_id"], join_type="left"
             )
 
-        if self.load_simulated and TRUE_IMPACT_GROUP in self.h5file.root:
+        if simulated and TRUE_IMPACT_GROUP in self.h5file.root:
             impacts = self._read_telescope_table(
                 TRUE_IMPACT_GROUP,
                 tel_id,
@@ -531,18 +595,55 @@ class TableLoader(Component):
 
         return table
 
-    def _read_telescope_events_for_ids(self, tel_ids, start=None, stop=None):
+    def _read_telescope_events_for_ids(
+        self,
+        tel_ids,
+        dl1_images,
+        dl1_parameters,
+        dl1_muons,
+        dl2,
+        simulated,
+        true_images,
+        true_parameters,
+        instrument,
+        start=None,
+        stop=None,
+    ):
         tables = [
-            self._read_telescope_events_for_id(tel_id, start=start, stop=stop)
+            self._read_telescope_events_for_id(
+                tel_id,
+                dl1_images=dl1_images,
+                dl1_parameters=dl1_parameters,
+                dl1_muons=dl1_muons,
+                dl2=dl2,
+                simulated=simulated,
+                true_images=true_images,
+                true_parameters=true_parameters,
+                instrument=instrument,
+                start=start,
+                stop=stop,
+            )
             for tel_id in tel_ids
         ]
         return vstack(tables)
 
-    def _join_subarray_info(self, table, start=None, stop=None, subarray_events=None):
+    def _join_subarray_info(
+        self,
+        table,
+        dl2,
+        simulated,
+        observation_info,
+        start=None,
+        stop=None,
+        subarray_events=None,
+    ):
         if subarray_events is None:
             subarray_events = self.read_subarray_events(
                 start=start,
                 stop=stop,
+                dl2=dl2,
+                simulated=simulated,
+                observation_info=observation_info,
                 keep_order=False,
             )
         table = join_allow_empty(
@@ -572,7 +673,21 @@ class TableLoader(Component):
 
         return tel_start, tel_stop
 
-    def read_telescope_events(self, telescopes=None, start=None, stop=None):
+    def read_telescope_events(
+        self,
+        telescopes=None,
+        start=None,
+        stop=None,
+        dl1_images=None,
+        dl1_parameters=None,
+        dl1_muons=None,
+        dl2=None,
+        simulated=None,
+        true_images=None,
+        true_parameters=None,
+        instrument=None,
+        observation_info=None,
+    ):
         """
         Read telescope-based event information.
 
@@ -594,20 +709,79 @@ class TableLoader(Component):
             First *subarray* event to read
         stop: int
             Last *subarray* event (non-inclusive)
+        dl1_images: bool
+            load extracted images
+        dl1_parameters: bool
+            load reconstructed image parameters
+        dl1_muons: bool
+            load muon ring parameters
+        dl2: bool
+            load available dl2 stereo parameters
+        simulated: bool
+            load simulated shower information
+        true_images: bool
+            load simulated shower images
+        true_parameters: bool
+            load image parameters obtained from true images
+        instrument: bool
+            join subarray instrument information to each event
+        observation_info: bool
+            join observation information to each event
 
         Returns
         -------
         events: astropy.io.Table
             Table with primary index columns "obs_id", "event_id" and "tel_id".
         """
+        updated_attributes = self._check_args(
+            dl1_images=dl1_images,
+            dl1_parameters=dl1_parameters,
+            dl1_muons=dl1_muons,
+            dl2=dl2,
+            simulated=simulated,
+            true_images=true_images,
+            true_parameters=true_parameters,
+            instrument=instrument,
+            observation_info=observation_info,
+        )
+
+        dl1_images = updated_attributes["dl1_images"]
+        dl1_parameters = updated_attributes["dl1_parameters"]
+        dl1_muons = updated_attributes["dl1_muons"]
+        dl2 = updated_attributes["dl2"]
+        simulated = updated_attributes["simulated"]
+        true_images = updated_attributes["true_images"]
+        true_parameters = updated_attributes["true_parameters"]
+        instrument = updated_attributes["instrument"]
+        observation_info = updated_attributes["observation_info"]
 
         if telescopes is None:
             tel_ids = tuple(self.subarray.tel.keys())
         else:
             tel_ids = self.subarray.get_tel_ids(telescopes)
 
-        table = self._read_telescope_events_for_ids(tel_ids, start, stop)
-        table = self._join_subarray_info(table, start=start, stop=stop)
+        table = self._read_telescope_events_for_ids(
+            tel_ids,
+            dl1_images=dl1_images,
+            dl1_parameters=dl1_parameters,
+            dl1_muons=dl1_muons,
+            dl2=dl2,
+            simulated=simulated,
+            true_images=true_images,
+            true_parameters=true_parameters,
+            instrument=instrument,
+            start=start,
+            stop=stop,
+        )
+
+        table = self._join_subarray_info(
+            table,
+            dl2=dl2,
+            simulated=simulated,
+            observation_info=observation_info,
+            start=start,
+            stop=stop,
+        )
 
         # sort back to order in the file
         table = _join_subarray_events(
@@ -666,14 +840,44 @@ class TableLoader(Component):
         return self._n_telescope_events.sum(axis=1)
 
     def read_telescope_events_by_type(
-        self, telescopes=None, start=None, stop=None
+        self,
+        telescopes=None,
+        start=None,
+        stop=None,
+        dl1_images=None,
+        dl1_parameters=None,
+        dl1_muons=None,
+        dl2=None,
+        simulated=None,
+        true_images=None,
+        true_parameters=None,
+        instrument=None,
+        observation_info=None,
     ) -> Dict[str, Table]:
-        """Read telescope-based event information.
+        """Read subarray-based event information.
 
         Parameters
         ----------
         telescopes: List[Union[int, str, TelescopeDescription]]
             Any list containing a combination of telescope IDs or telescope_descriptions.
+        dl1_images: bool
+            load extracted images
+        dl1_parameters: bool
+            load reconstructed image parameters
+        dl1_muons: bool
+            load muon ring parameters
+        dl2: bool
+            load available dl2 stereo parameters
+        simulated: bool
+            load simulated shower information
+        true_images: bool
+            load simulated shower images
+        true_parameters: bool
+            load image parameters obtained from true images
+        instrument: bool
+            join subarray instrument information to each event
+        observation_info: bool
+            join observation information to each event
 
         Returns
         -------
@@ -681,6 +885,27 @@ class TableLoader(Component):
             Dictionary of tables organized by telescope types
             Table with primary index columns "obs_id", "event_id" and "tel_id".
         """
+        updated_attributes = self._check_args(
+            dl1_images=dl1_images,
+            dl1_parameters=dl1_parameters,
+            dl1_muons=dl1_muons,
+            dl2=dl2,
+            simulated=simulated,
+            true_images=true_images,
+            true_parameters=true_parameters,
+            instrument=instrument,
+            observation_info=observation_info,
+        )
+
+        dl1_images = updated_attributes["dl1_images"]
+        dl1_parameters = updated_attributes["dl1_parameters"]
+        dl1_muons = updated_attributes["dl1_muons"]
+        dl2 = updated_attributes["dl2"]
+        simulated = updated_attributes["simulated"]
+        true_images = updated_attributes["true_images"]
+        true_parameters = updated_attributes["true_parameters"]
+        instrument = updated_attributes["instrument"]
+        observation_info = updated_attributes["observation_info"]
 
         if telescopes is None:
             tel_ids = tuple(self.subarray.tel.keys())
@@ -688,21 +913,43 @@ class TableLoader(Component):
             tel_ids = self.subarray.get_tel_ids(telescopes)
 
         subarray_events = self.read_subarray_events(
-            start=start, stop=stop, keep_order=False
+            start=start,
+            stop=stop,
+            dl2=dl2,
+            simulated=simulated,
+            observation_info=observation_info,
+            keep_order=False,
         )
         self._add_index_if_needed(subarray_events)
 
         by_type = defaultdict(list)
         for tel_id in tel_ids:
             key = str(self.subarray.tel[tel_id])
-            table = self._read_telescope_events_for_id(tel_id, start=start, stop=stop)
+            table = self._read_telescope_events_for_id(
+                tel_id,
+                start=start,
+                stop=stop,
+                dl1_images=dl1_images,
+                dl1_parameters=dl1_parameters,
+                dl1_muons=dl1_muons,
+                dl2=dl2,
+                simulated=simulated,
+                true_images=true_images,
+                true_parameters=true_parameters,
+                instrument=instrument,
+            )
+
             if len(table) > 0:
                 by_type[key].append(table)
 
         by_type = {k: vstack(ts) for k, ts in by_type.items()}
         for key in by_type.keys():
             by_type[key] = self._join_subarray_info(
-                by_type[key], subarray_events=subarray_events
+                by_type[key],
+                dl2=dl2,
+                simulated=simulated,
+                observation_info=observation_info,
+                subarray_events=subarray_events,
             )
             self._sort_to_original_order(by_type[key], include_tel_id=True)
 
@@ -730,14 +977,44 @@ class TableLoader(Component):
         )
 
     def read_telescope_events_by_id(
-        self, telescopes=None, start=None, stop=None
+        self,
+        telescopes=None,
+        start=None,
+        stop=None,
+        dl1_images=None,
+        dl1_parameters=None,
+        dl1_muons=None,
+        dl2=None,
+        simulated=None,
+        true_images=None,
+        true_parameters=None,
+        instrument=None,
+        observation_info=None,
     ) -> Dict[int, Table]:
-        """Read telescope-based event information.
+        """Read subarray-based event information.
 
         Parameters
         ----------
         telescopes: List[Union[int, str, TelescopeDescription]]
             Any list containing a combination of telescope IDs or telescope_descriptions.
+        dl1_images: bool
+            load extracted images
+        dl1_parameters: bool
+            load reconstructed image parameters
+        dl1_muons: bool
+            load muon ring parameters
+        dl2: bool
+            load available dl2 stereo parameters
+        simulated: bool
+            load simulated shower information
+        true_images: bool
+            load simulated shower images
+        true_parameters: bool
+            load image parameters obtained from true images
+        instrument: bool
+            join subarray instrument information to each event
+        observation_info: bool
+            join observation information to each event
 
         Returns
         -------
@@ -745,6 +1022,27 @@ class TableLoader(Component):
             Dictionary of tables organized by telescope ids
             Table with primary index columns "obs_id", "event_id" and "tel_id".
         """
+        updated_attributes = self._check_args(
+            dl1_images=dl1_images,
+            dl1_parameters=dl1_parameters,
+            dl1_muons=dl1_muons,
+            dl2=dl2,
+            simulated=simulated,
+            true_images=true_images,
+            true_parameters=true_parameters,
+            instrument=instrument,
+            observation_info=observation_info,
+        )
+
+        dl1_images = updated_attributes["dl1_images"]
+        dl1_parameters = updated_attributes["dl1_parameters"]
+        dl1_muons = updated_attributes["dl1_muons"]
+        dl2 = updated_attributes["dl2"]
+        simulated = updated_attributes["simulated"]
+        true_images = updated_attributes["true_images"]
+        true_parameters = updated_attributes["true_parameters"]
+        instrument = updated_attributes["instrument"]
+        observation_info = updated_attributes["observation_info"]
 
         if telescopes is None:
             tel_ids = tuple(self.subarray.tel.keys())
@@ -752,20 +1050,41 @@ class TableLoader(Component):
             tel_ids = self.subarray.get_tel_ids(telescopes)
 
         subarray_events = self.read_subarray_events(
-            start=start, stop=stop, keep_order=False
+            start=start,
+            stop=stop,
+            dl2=dl2,
+            simulated=simulated,
+            observation_info=observation_info,
+            keep_order=False,
         )
         self._add_index_if_needed(subarray_events)
 
         by_id = {}
         for tel_id in tel_ids:
             # no events for this telescope in range start/stop
-            table = self._read_telescope_events_for_id(tel_id, start=start, stop=stop)
+            table = self._read_telescope_events_for_id(
+                tel_id,
+                start=start,
+                stop=stop,
+                dl1_images=dl1_images,
+                dl1_parameters=dl1_parameters,
+                dl1_muons=dl1_muons,
+                dl2=dl2,
+                simulated=simulated,
+                true_images=true_images,
+                true_parameters=true_parameters,
+                instrument=instrument,
+            )
             if len(table) > 0:
                 by_id[tel_id] = table
 
         for tel_id in by_id.keys():
             by_id[tel_id] = self._join_subarray_info(
-                by_id[tel_id], subarray_events=subarray_events
+                by_id[tel_id],
+                dl2=dl2,
+                simulated=simulated,
+                observation_info=observation_info,
+                subarray_events=subarray_events,
             )
             self._sort_to_original_order(by_id[tel_id], include_tel_id=False)
 
diff --git a/ctapipe/tools/apply_models.py b/ctapipe/tools/apply_models.py
index dee234f6..6ec23700 100644
--- a/ctapipe/tools/apply_models.py
+++ b/ctapipe/tools/apply_models.py
@@ -144,12 +144,6 @@ class ApplyModels(Tool):
             TableLoader(
                 self.input_url,
                 parent=self,
-                load_dl1_parameters=True,
-                load_dl2=True,
-                load_instrument=True,
-                load_dl1_images=False,
-                load_simulated=False,
-                load_observation_info=True,
             )
         )
 
@@ -163,7 +157,11 @@ class ApplyModels(Tool):
     def start(self):
         """Apply models to input tables"""
         chunk_iterator = self.loader.read_telescope_events_by_id_chunked(
-            self.chunk_size
+            self.chunk_size,
+            simulated=False,
+            true_parameters=False,
+            observation_info=True,
+            instrument=True,
         )
         bar = tqdm(
             chunk_iterator,
diff --git a/ctapipe/tools/train_disp_reconstructor.py b/ctapipe/tools/train_disp_reconstructor.py
index 405eb9f9..1dd4103f 100644
--- a/ctapipe/tools/train_disp_reconstructor.py
+++ b/ctapipe/tools/train_disp_reconstructor.py
@@ -99,12 +99,6 @@ class TrainDispReconstructor(Tool):
         self.loader = self.enter_context(
             TableLoader(
                 parent=self,
-                load_dl1_images=False,
-                load_dl1_parameters=True,
-                load_dl2=True,
-                load_simulated=True,
-                load_instrument=True,
-                load_observation_info=True,
             )
         )
         self.n_events.attach_subarray(self.loader.subarray)
diff --git a/ctapipe/tools/train_energy_regressor.py b/ctapipe/tools/train_energy_regressor.py
index faf7c12d..61c149b2 100644
--- a/ctapipe/tools/train_energy_regressor.py
+++ b/ctapipe/tools/train_energy_regressor.py
@@ -91,11 +91,6 @@ class TrainEnergyRegressor(Tool):
         self.loader = self.enter_context(
             TableLoader(
                 parent=self,
-                load_dl1_images=False,
-                load_dl1_parameters=True,
-                load_dl2=True,
-                load_simulated=True,
-                load_instrument=True,
             )
         )
         self.n_events.attach_subarray(self.loader.subarray)
diff --git a/ctapipe/tools/train_particle_classifier.py b/ctapipe/tools/train_particle_classifier.py
index 14ca3183..794bc7e9 100644
--- a/ctapipe/tools/train_particle_classifier.py
+++ b/ctapipe/tools/train_particle_classifier.py
@@ -122,11 +122,6 @@ class TrainParticleClassifier(Tool):
             TableLoader(
                 parent=self,
                 input_url=self.input_url_signal,
-                load_dl1_images=False,
-                load_dl1_parameters=True,
-                load_dl2=True,
-                load_simulated=True,
-                load_instrument=True,
             )
         )
 
@@ -134,11 +129,6 @@ class TrainParticleClassifier(Tool):
             TableLoader(
                 parent=self,
                 input_url=self.input_url_background,
-                load_dl1_images=False,
-                load_dl1_parameters=True,
-                load_dl2=True,
-                load_simulated=True,
-                load_instrument=True,
             )
         )
 
diff --git a/ctapipe/tools/utils.py b/ctapipe/tools/utils.py
index 9e7c76eb..2c6c3ff5 100644
--- a/ctapipe/tools/utils.py
+++ b/ctapipe/tools/utils.py
@@ -68,7 +68,6 @@ def get_installed_tools():
 
 
 def get_all_descriptions():
-
     tools = get_installed_tools()
 
     descriptions = OrderedDict()
@@ -102,6 +101,9 @@ def read_training_events(
     chunk_iterator = loader.read_telescope_events_chunked(
         chunk_size,
         telescopes=[telescope_type],
+        true_parameters=False,
+        instrument=True,
+        observation_info=True,
     )
     table = []
     n_events_in_file = 0
diff --git a/docs/changes/2482.api.rst b/docs/changes/2482.api.rst
new file mode 100644
index 00000000..00400681
--- /dev/null
+++ b/docs/changes/2482.api.rst
@@ -0,0 +1,5 @@
+Move the ``TableLoader`` options from being traitlets to
+each ``read_...`` method allowing to load different data with the 
+same TableLoader-Instance.
+
+In addition the default values for the options have changed.
diff --git a/examples/tutorials/ctapipe_overview.py b/examples/tutorials/ctapipe_overview.py
index 8d18c735..767e125e 100644
--- a/examples/tutorials/ctapipe_overview.py
+++ b/examples/tutorials/ctapipe_overview.py
@@ -372,7 +372,6 @@ f = tempfile.NamedTemporaryFile(suffix=".hdf5")
 with DataWriter(
     source, output_path=f.name, overwrite=True, write_showers=True
 ) as writer:
-
     for event in source:
         energy = event.simulation.shower.energy
         n_telescopes_r1 = len(event.r1.tel)
@@ -400,7 +399,7 @@ with DataWriter(
 
 
 ######################################################################
-loader = TableLoader(f.name, load_dl2=True, load_simulated=True)
+loader = TableLoader(f.name)
 
 events = loader.read_subarray_events()
 
@@ -465,9 +464,13 @@ plt.legend()
 # ~~~~~~~~~~~~~~~~~~~~~~~~
 #
 
-loader = TableLoader(f.name, load_simulated=True, load_dl1_parameters=True)
+loader = TableLoader(f.name)
 
-dl1_table = loader.read_telescope_events(["LST_LST_LSTCam"])
+dl1_table = loader.read_telescope_events(
+    ["LST_LST_LSTCam"],
+    dl2=False,
+    true_parameters=False,
+)
 
 ######################################################################
 plt.scatter(
@@ -525,7 +528,6 @@ image = np.zeros(geometry.n_pixels)
 
 
 for i in range(9):
-
     model = toymodel.Gaussian(
         x=np.random.uniform(-0.8, 0.8) * u.m,
         y=np.random.uniform(-0.8, 0.8) * u.m,
diff --git a/examples/tutorials/tableloader_and_eventsources.py b/examples/tutorials/tableloader_and_eventsources.py
new file mode 100644
index 00000000..36ea8982
--- /dev/null
+++ b/examples/tutorials/tableloader_and_eventsources.py
@@ -0,0 +1,283 @@
+"""
+IO: TableLoader and EventSources
+================================
+
+This hands-on was presented in October 2023 at the DPPS Meeting
+at CEA Paris-Saclay (J. Hackfeld).
+"""
+
+# %%
+# Introduction
+# ------------
+# ``ctapipe`` provides basically two different ways of accessing its data products:
+#
+# - event-wise (**EventSource**)
+# - column-wise (**TableLoader**)
+#
+# **EventSource(s):**
+#
+# EventSources read input files and generate ``ctapipe.containers.ArrayEventContainer``
+# instances when iterated over.
+#
+# A new EventSource should be created for each type of event file read
+# into ctapipe, e.g. simtel files are read by the ``ctapipe.io.SimTelEventSource`` .
+#
+# EventSource provides a common high-level interface for accessing event
+# information from different data sources. Creating an EventSource for a new
+# file format or other event source ensures that data can be accessed in a common way,
+# irregardless of the file format or data origin.
+#
+# EventSource itself is an abstract class, but will create an
+# appropriate subclass if a compatible source is found for the given
+# ``input_url`` .
+#
+# **TableLoader**:
+#
+# Loads telescope-event or subarray-event data from ctapipe HDF5 files and returns
+# ``astropy.table`` .
+# See `Astropy docs <https://docs.astropy.org/en/stable/table>`__
+# or `this video from A.Donath at the ESCAPE Summer School
+# 2021 <https://www.youtube.com/watch?v=uzhQ6RIGHQA>`__.
+#
+# This class provides high-level access to data stored in ctapipe HDF5 files,
+# such as created by the ctapipe-process tool ( ``ctapipe.tools.process.ProcessorTool`` ).
+#
+# There are multiple ``TableLoader`` methods loading data from all relevant tables
+# (depending on the options) and **joins** them into single tables:
+#
+# -  ``TableLoader.read_subarray_events``
+# -  ``TableLoader.read_telescope_events``
+# -  ``TableLoader.read_telescope_events_by_id``
+# -  ``TableLoader.read_telescope_events_by_type``
+#
+# The last one returns a dict with a table per telescope type, which is needed for
+# e.g. DL1 image data that might have different shapes for each of the telescope
+# types as tables do not support variable length columns.
+#
+# **It is recommended to use the** ``TableLoader`` **when loading data into Tables,
+# because its much faster than EventSources!**
+
+# %%
+# Code Examples
+# -------------
+# First import some classes/modules and get the example dataset path:
+
+from traitlets.config import Config
+
+from ctapipe import utils
+from ctapipe.calib import CameraCalibrator
+from ctapipe.io import DataWriter, EventSource, TableLoader
+
+simtel_path = utils.get_dataset_path("gamma_prod5.simtel.zst")
+
+# %%
+# EventSource(s)
+# --------------
+# The already implemented EventSources are:
+
+sources = EventSource.non_abstract_subclasses()
+maxlen = max(len(source) for source in sources)
+for source in sources.values():
+    print(f"{source.__name__:>{maxlen}s} -- {source.__module__}.{source.__qualname__}")
+
+# %%
+# ``EventSource`` will create an appropriate subclass if a compatible source is found for
+# the given ``input_url`` :
+
+source = EventSource(input_url=simtel_path, max_events=5)
+print(source)
+
+# %%
+# You can now loop over the ``ctapipe.containers.ArrayEventContainer`` generated by the
+# source.
+
+for event in source:
+    print(event.count)
+
+# %%
+
+print(repr(event))
+
+# %%
+# Every time a new loop is started through the source,
+# it tries to restart from the first event, which might not be supported
+# by the event source. It is encouraged to use ``EventSource`` in a **context manager**
+# to ensure the correct cleanups are performed when you are finished with the source:
+
+with EventSource(input_url=simtel_path) as source:
+    for event in source:
+        print(
+            f"Event Count: {event.count},"
+            f"Tels with trigger: {event.trigger.tels_with_trigger}"
+        )
+
+# %%
+# You can hand in the ID's of the telescopes to be included in the data
+# with the ``allowed_tels`` attribute. If given, only this subset of telescopes
+# will be present in the generated events. If None, all available telescopes are used.
+
+eventsource_config = Config(
+    {"EventSource": {"max_events": 5, "allowed_tels": [3, 4, 9]}}
+)
+
+with EventSource(input_url=simtel_path, config=eventsource_config) as source:
+    for event in source:
+        print(
+            f"Event Count: {event.count},"
+            f"Tels with trigger: {event.trigger.tels_with_trigger}"
+        )
+
+# %%
+# If you want to calibrate your data in the event loop and write it to an .h5 file with
+# the ``DataWriter`` :
+#
+
+source = EventSource(input_url=simtel_path, max_events=50)
+calibrate = CameraCalibrator(subarray=source.subarray)
+
+with DataWriter(
+    event_source=source,
+    output_path="events.dl1.h5",
+    write_parameters=False,
+    overwrite=True,
+    write_images=True,
+) as write_data:
+    for event in source:
+        calibrate(event)
+        write_data(event)
+
+# %%
+# Alternatively doing it with ``ctapipe-process`` would look like this:
+#
+# .. code-block:: bash
+#
+#   ! ctapipe-process -i {simtel_path} -o events.dl1.h5 --overwrite --progress
+#
+
+# %%
+# TableLoader
+# -----------
+#
+# Create a TableLoader instance with the above created dl1 file:
+
+loader = TableLoader(input_url="events.dl1.h5")
+
+# %%
+# Alternatively using a config file:
+
+tableloader_config = Config(
+    {
+        "TableLoader": {
+            "input_url": "events.dl1.h5",
+        }
+    }
+)
+
+loader = TableLoader(config=tableloader_config)
+
+# %%
+# Reading subarray-based event information:
+
+subarray_events = loader.read_subarray_events(
+    start=None,
+    stop=None,
+    dl2=False,
+    simulated=True,
+    observation_info=False,
+)
+
+subarray_events
+
+# %%
+# Reading subarray-based event information in chunks:
+
+subarray_events_chunked = loader.read_subarray_events_chunked(
+    chunk_size=3,
+    dl2=False,
+    simulated=True,
+    observation_info=False,
+)
+
+for chunk in subarray_events_chunked:
+    print(" \n", chunk)
+
+# %%
+# Reading just LST events:
+
+lst_events = loader.read_telescope_events(
+    telescopes=[1, 2, 3, 4],
+    start=None,
+    stop=None,
+    dl1_images=True,
+    dl1_parameters=False,
+    dl1_muons=False,
+    dl2=False,
+    simulated=False,
+    true_images=True,
+    true_parameters=False,
+    instrument=True,
+    observation_info=False,
+)
+
+lst_events
+
+# %%
+# Loading telescope events by type returns a dict with the different telescope types:
+
+telescope_events_by_type = loader.read_telescope_events_by_type(
+    telescopes=["LST_LST_LSTCam", "MST_MST_FlashCam"],
+    start=None,
+    stop=None,
+    dl1_images=True,
+    dl1_parameters=False,
+    dl1_muons=False,
+    dl2=False,
+    simulated=False,
+    true_images=True,
+    true_parameters=False,
+    instrument=True,
+    observation_info=False,
+)
+
+for tel_type, table in telescope_events_by_type.items():
+    print(f"Telescope Type: {tel_type} \n", table, "\n")
+
+# %%
+# Loading telescope events by ID returns a dict with the different telescope IDs:
+
+telescope_events_by_id = loader.read_telescope_events_by_id(
+    telescopes=[3, 14],
+    start=None,
+    stop=None,
+    dl1_images=True,
+    dl1_parameters=False,
+    dl1_muons=False,
+    dl2=False,
+    simulated=False,
+    true_images=True,
+    true_parameters=False,
+    instrument=True,
+    observation_info=False,
+)
+for tel_id, table in telescope_events_by_id.items():
+    print(f"Telescope ID: {tel_id} \n", table, "\n")
+
+# %%
+# - ``read_telescope_events_chunked``
+# - ``read_telescope_events_by_type_chunked``
+# - ``read_telescope_events_by_id_chunked``
+#
+# are also available.
+
+# %%
+# Reading e.g. simulation- or observation-information:
+
+simulation_configuration = loader.read_simulation_configuration()
+observation_information = loader.read_observation_information()
+
+simulation_configuration
+
+# %%
+# Now you have ``astropy.table`` s including all the relevant data you need
+# for your analyses.
+#


Test patch:
diff --git a/ctapipe/instrument/tests/test_trigger.py b/ctapipe/instrument/tests/test_trigger.py
index fec25900..40cabb99 100644
--- a/ctapipe/instrument/tests/test_trigger.py
+++ b/ctapipe/instrument/tests/test_trigger.py
@@ -105,7 +105,6 @@ def test_software_trigger_simtel(allowed_tels):
     with EventSource(
         path, focal_length_choice="EQUIVALENT", allowed_tels=allowed_tels
     ) as source:
-
         trigger = SoftwareTrigger(
             subarray=source.subarray,
             min_telescopes=2,
@@ -152,7 +151,6 @@ def test_software_trigger_simtel_single_lsts():
     with EventSource(
         path, focal_length_choice="EQUIVALENT", allowed_tels=allowed_tels
     ) as source:
-
         trigger = SoftwareTrigger(
             subarray=source.subarray,
             min_telescopes=2,
@@ -218,18 +216,22 @@ def test_software_trigger_simtel_process(tmp_path):
 
     with TableLoader(
         output_path,
-        load_simulated=True,
-        load_dl1_parameters=True,
         focal_length_choice="EQUIVALENT",
     ) as loader:
-        events_trigger = loader.read_telescope_events("LST_LST_LSTCam")
+        events_trigger = loader.read_telescope_events(
+            "LST_LST_LSTCam",
+            dl2=False,
+            true_parameters=False,
+        )
 
     with TableLoader(
         output_path_no_software_trigger,
-        load_simulated=True,
-        load_dl1_parameters=True,
         focal_length_choice="EQUIVALENT",
     ) as loader:
-        events_no_trigger = loader.read_telescope_events("LST_LST_LSTCam")
+        events_no_trigger = loader.read_telescope_events(
+            "LST_LST_LSTCam",
+            dl2=False,
+            true_parameters=False,
+        )
 
     assert len(events_no_trigger) > len(events_trigger)
diff --git a/ctapipe/io/tests/test_table_loader.py b/ctapipe/io/tests/test_table_loader.py
index 864a53ba..2262dd4d 100644
--- a/ctapipe/io/tests/test_table_loader.py
+++ b/ctapipe/io/tests/test_table_loader.py
@@ -70,7 +70,7 @@ def test_telescope_events_for_tel_id(dl1_file):
     """Test loading data for a single telescope"""
     from ctapipe.io.tableloader import TableLoader
 
-    loader = TableLoader(dl1_file, load_dl1_parameters=True)
+    loader = TableLoader(dl1_file)
 
     with loader as table_loader:
         table = table_loader.read_telescope_events([8])
@@ -79,8 +79,8 @@ def test_telescope_events_for_tel_id(dl1_file):
         assert "event_type" in table.colnames
         assert np.all(table["tel_id"] == 8)
 
-    with TableLoader(dl1_file, load_dl1_images=True) as table_loader:
-        table = table_loader.read_telescope_events([8])
+    with TableLoader(dl1_file) as table_loader:
+        table = table_loader.read_telescope_events([8], dl1_images=True)
         assert "image" in table.colnames
         assert np.all(table["tel_id"] == 8)
         assert table["obs_id"].dtype == np.int32
@@ -94,11 +94,11 @@ def test_telescope_muon_events_for_tel_id(dl1_muon_output_file):
 
     with TableLoader(
         dl1_muon_output_file,
-        load_dl1_muons=True,
-        load_dl1_parameters=False,
         focal_length_choice="EQUIVALENT",
     ) as table_loader:
-        table = table_loader.read_telescope_events([1])
+        table = table_loader.read_telescope_events(
+            [1], dl1_muons=True, dl1_parameters=False
+        )
         assert "muonring_radius" in table.colnames
         assert "muonparameters_containment" in table.colnames
         assert "muonefficiency_optical_efficiency" in table.colnames
@@ -107,22 +107,22 @@ def test_telescope_muon_events_for_tel_id(dl1_muon_output_file):
     assert not table_loader.h5file.isopen
 
 
-def test_load_instrument(dl1_file):
+def test_instrument(dl1_file):
     """Test joining instrument data onto telescope events"""
     from ctapipe.io.tableloader import TableLoader
 
-    with TableLoader(dl1_file, load_instrument=True) as table_loader:
+    with TableLoader(dl1_file) as table_loader:
         expected = table_loader.subarray.tel[8].optics.equivalent_focal_length
-        table = table_loader.read_telescope_events([8])
+        table = table_loader.read_telescope_events([8], instrument=True)
         assert "equivalent_focal_length" in table.colnames
         assert np.all(table["equivalent_focal_length"] == expected)
 
 
-def test_load_simulated(dl1_file):
+def test_simulated(dl1_file):
     """Test joining simulation info onto telescope events"""
     from ctapipe.io.tableloader import TableLoader
 
-    with TableLoader(dl1_file, load_simulated=True) as table_loader:
+    with TableLoader(dl1_file) as table_loader:
         table = table_loader.read_subarray_events()
         assert "true_energy" in table.colnames
         assert table["obs_id"].dtype == np.int32
@@ -136,10 +136,10 @@ def test_true_images(dl1_file):
     """Test joining true images onto telescope events"""
     from ctapipe.io.tableloader import TableLoader
 
-    with TableLoader(
-        dl1_file, load_dl1_parameters=False, load_true_images=True
-    ) as table_loader:
-        table = table_loader.read_telescope_events(["MST_MST_NectarCam"])
+    with TableLoader(dl1_file) as table_loader:
+        table = table_loader.read_telescope_events(
+            ["MST_MST_NectarCam"], dl1_parameters=False, true_images=True
+        )
         assert "true_image" in table.colnames
 
 
@@ -147,10 +147,8 @@ def test_true_parameters(dl1_file):
     """Test joining true parameters onto telescope events"""
     from ctapipe.io.tableloader import TableLoader
 
-    with TableLoader(
-        dl1_file, load_dl1_parameters=False, load_true_parameters=True
-    ) as table_loader:
-        table = table_loader.read_telescope_events()
+    with TableLoader(dl1_file) as table_loader:
+        table = table_loader.read_telescope_events(dl1_parameters=False)
         assert "true_hillas_intensity" in table.colnames
 
 
@@ -158,8 +156,8 @@ def test_observation_info(dl1_file):
     """Test joining observation info onto telescope events"""
     from ctapipe.io.tableloader import TableLoader
 
-    with TableLoader(dl1_file, load_observation_info=True) as table_loader:
-        table = table_loader.read_telescope_events()
+    with TableLoader(dl1_file) as table_loader:
+        table = table_loader.read_telescope_events(observation_info=True)
         assert "subarray_pointing_lat" in table.colnames
 
 
@@ -167,11 +165,7 @@ def test_read_subarray_events(dl2_shower_geometry_file):
     """Test reading subarray events"""
     from ctapipe.io.tableloader import TableLoader
 
-    with TableLoader(
-        dl2_shower_geometry_file,
-        load_dl2=True,
-        load_simulated=True,
-    ) as table_loader:
+    with TableLoader(dl2_shower_geometry_file) as table_loader:
         table = table_loader.read_subarray_events()
         assert "HillasReconstructor_alt" in table.colnames
         assert "true_energy" in table.colnames
@@ -187,9 +181,7 @@ def test_table_loader_keeps_original_order(dl2_merged_file):
     # check we actually have unsorted input
     assert not np.all(np.diff(trigger["obs_id"]) >= 0)
 
-    with TableLoader(
-        dl2_merged_file, load_dl2=True, load_simulated=True
-    ) as table_loader:
+    with TableLoader(dl2_merged_file) as table_loader:
         events = table_loader.read_subarray_events()
         tel_events = table_loader.read_telescope_events()
 
@@ -204,17 +196,13 @@ def test_read_telescope_events_type(dl2_shower_geometry_file):
 
     subarray = SubarrayDescription.from_hdf(dl2_shower_geometry_file)
 
-    with TableLoader(
-        dl2_shower_geometry_file,
-        load_dl1_images=False,
-        load_dl1_parameters=False,
-        load_dl2=True,
-        load_simulated=True,
-        load_true_images=True,
-        load_instrument=True,
-    ) as table_loader:
-
-        table = table_loader.read_telescope_events(["MST_MST_FlashCam"])
+    with TableLoader(dl2_shower_geometry_file) as table_loader:
+        table = table_loader.read_telescope_events(
+            ["MST_MST_FlashCam"],
+            dl1_parameters=False,
+            true_images=True,
+            instrument=True,
+        )
 
         assert "HillasReconstructor_alt" in table.colnames
         assert "true_energy" in table.colnames
@@ -233,20 +221,16 @@ def test_read_telescope_events_by_type(dl2_shower_geometry_file):
 
     subarray = SubarrayDescription.from_hdf(dl2_shower_geometry_file)
 
-    with TableLoader(
-        dl2_shower_geometry_file,
-        load_dl1_images=False,
-        load_dl1_parameters=False,
-        load_dl2=True,
-        load_simulated=True,
-        load_true_images=True,
-        load_instrument=True,
-    ) as table_loader:
-
-        tables = table_loader.read_telescope_events_by_type([25, 130])
+    with TableLoader(dl2_shower_geometry_file) as table_loader:
+        tables = table_loader.read_telescope_events_by_type(
+            [25, 130],
+            dl1_images=False,
+            dl1_parameters=False,
+            true_images=True,
+            instrument=True,
+        )
 
         for tel_type in ["MST_MST_NectarCam", "MST_MST_FlashCam"]:
-
             table = tables[tel_type]
 
             assert "HillasReconstructor_alt" in table.colnames
@@ -287,24 +271,26 @@ def test_chunked(dl2_shower_geometry_file):
     start = 0
     stop = chunk_size
 
-    with TableLoader(
-        dl2_shower_geometry_file,
-        load_dl1_images=False,
-        load_true_images=False,
-        load_dl1_parameters=True,
-        load_dl2=True,
-        load_simulated=True,
-    ) as table_loader:
-
-        tel_event_it = table_loader.read_telescope_events_chunked(chunk_size)
-        event_it = table_loader.read_subarray_events_chunked(chunk_size)
-        by_type_it = table_loader.read_telescope_events_by_type_chunked(chunk_size)
-        by_id_it = table_loader.read_telescope_events_by_id_chunked(chunk_size)
+    with TableLoader(dl2_shower_geometry_file) as table_loader:
+        tel_event_it = table_loader.read_telescope_events_chunked(
+            chunk_size,
+            true_parameters=False,
+        )
+        event_it = table_loader.read_subarray_events_chunked(
+            chunk_size,
+        )
+        by_type_it = table_loader.read_telescope_events_by_type_chunked(
+            chunk_size,
+            true_parameters=False,
+        )
+        by_id_it = table_loader.read_telescope_events_by_id_chunked(
+            chunk_size,
+            true_parameters=False,
+        )
 
         iters = (event_it, tel_event_it, by_type_it, by_id_it)
 
         for chunk, (events, tel_events, by_type, by_id) in enumerate(zip(*iters)):
-
             expected_start = chunk * chunk_size
             expected_stop = min(n_events, (chunk + 1) * chunk_size)
 
@@ -386,26 +372,24 @@ def test_read_unavailable_telescope(dl2_shower_geometry_file):
     """Reading a telescope that is not part of the subarray of the file should fail."""
     from ctapipe.io import TableLoader
 
-    with TableLoader(
-        dl2_shower_geometry_file,
-        load_dl1_parameters=False,
-        load_dl2=True,
-    ) as loader:
+    with TableLoader(dl2_shower_geometry_file) as loader:
         tel_id = max(loader.subarray.tel.keys()) + 1
         with pytest.raises(ValueError):
-            loader.read_telescope_events([tel_id])
+            loader.read_telescope_events(
+                [tel_id],
+                dl1_parameters=False,
+            )
 
 
 def test_read_empty_table(dl2_shower_geometry_file):
     """Reading an empty table should return an empty table."""
     from ctapipe.io import TableLoader
 
-    with TableLoader(
-        dl2_shower_geometry_file,
-        load_dl1_parameters=False,
-        load_dl2=True,
-    ) as loader:
-        table = loader.read_telescope_events([6])
+    with TableLoader(dl2_shower_geometry_file) as loader:
+        table = loader.read_telescope_events(
+            [6],
+            dl1_parameters=False,
+        )
         assert len(table) == 0
 
 
@@ -417,13 +401,8 @@ def test_order_merged():
 
     trigger = read_table(path, "/dl1/event/subarray/trigger")
     tel_trigger = read_table(path, "/dl1/event/telescope/trigger")
-    with TableLoader(
-        path,
-        load_dl1_parameters=True,
-        load_dl2=True,
-        load_observation_info=True,
-    ) as loader:
-        events = loader.read_subarray_events()
+    with TableLoader(path) as loader:
+        events = loader.read_subarray_events(dl2=True, observation_info=True)
         check_equal_array_event_order(events, trigger)
 
         tables = loader.read_telescope_events_by_id()
diff --git a/ctapipe/tools/tests/test_apply_models.py b/ctapipe/tools/tests/test_apply_models.py
index 1075cb13..0aa61cef 100644
--- a/ctapipe/tools/tests/test_apply_models.py
+++ b/ctapipe/tools/tests/test_apply_models.py
@@ -48,9 +48,10 @@ def test_apply_energy_regressor(
         assert colname in table.colnames
         assert table[colname].description == field.description
 
-    with TableLoader(output_path, load_dl2=True) as loader:
-
-        events = loader.read_subarray_events()
+    with TableLoader(output_path) as loader:
+        events = loader.read_subarray_events(
+            simulated=False,
+        )
         assert f"{prefix}_energy" in events.colnames
         assert f"{prefix}_energy_uncert" in events.colnames
         assert f"{prefix}_is_valid" in events.colnames
@@ -60,7 +61,9 @@ def test_apply_energy_regressor(
             np.isfinite(events[f"{prefix}_energy"][events[f"{prefix}_is_valid"]])
         )
 
-        tel_events = loader.read_telescope_events()
+        tel_events = loader.read_telescope_events(
+            simulated=False,
+        )
         assert f"{prefix}_energy" in tel_events.colnames
         assert f"{prefix}_energy_uncert" in tel_events.colnames
         assert f"{prefix}_is_valid" in tel_events.colnames
@@ -180,8 +183,10 @@ def test_apply_all(
             table = read_table(output_path, key)
             check_equal_array_event_order(tel_trigger[tel_mask], table)
 
-    with TableLoader(output_path, load_dl2=True) as loader:
-        events = loader.read_subarray_events()
+    with TableLoader(output_path) as loader:
+        events = loader.read_subarray_events(
+            simulated=False,
+        )
         assert f"{prefix_clf}_prediction" in events.colnames
         assert f"{prefix_clf}_telescopes" in events.colnames
         assert f"{prefix_clf}_is_valid" in events.colnames
@@ -192,7 +197,10 @@ def test_apply_all(
         assert f"{prefix_disp}_is_valid" in events.colnames
         assert f"{prefix_disp}_goodness_of_fit" in events.colnames
 
-        tel_events = loader.read_telescope_events()
+        tel_events = loader.read_telescope_events(
+            simulated=False,
+            true_parameters=False,
+        )
         assert f"{prefix_clf}_prediction" in tel_events.colnames
         assert f"{prefix_clf}_telescopes" in tel_events.colnames
         assert f"{prefix_clf}_is_valid" in tel_events.colnames
diff --git a/ctapipe/tools/tests/test_merge.py b/ctapipe/tools/tests/test_merge.py
index 1e09ad04..09b3bca2 100644
--- a/ctapipe/tools/tests/test_merge.py
+++ b/ctapipe/tools/tests/test_merge.py
@@ -157,8 +157,11 @@ def test_dl2(tmp_path, dl2_shower_geometry_file, dl2_proton_geometry_file):
     assert len(sbs) == 2, "should have two SB entries"
 
     # regression test for #2048
-    loader = TableLoader(output, load_dl2=True, load_simulated=True)
-    tel_events = loader.read_telescope_events()
+    loader = TableLoader(output)
+    tel_events = loader.read_telescope_events(
+        dl1_parameters=False,
+        true_parameters=False,
+    )
     assert "true_impact_distance" in tel_events.colnames
     # regression test for #2051
     assert "HillasReconstructor_tel_impact_distance" in tel_events.colnames
diff --git a/ctapipe/tools/tests/test_process.py b/ctapipe/tools/tests/test_process.py
index 38628fd3..95237160 100644
--- a/ctapipe/tools/tests/test_process.py
+++ b/ctapipe/tools/tests/test_process.py
@@ -393,10 +393,9 @@ def test_read_from_simtel_and_dl1(prod5_proton_simtel_path, tmp_path):
     ] + few_tels
     assert run_tool(ProcessorTool(), argv=argv, cwd=tmp_path) == 0
 
-    args = dict(load_dl2=True, load_simulated=True)
-    with TableLoader(dl2_from_simtel, **args) as loader:
+    with TableLoader(dl2_from_simtel) as loader:
         events_from_simtel = loader.read_subarray_events()
-    with TableLoader(dl2_from_dl1, **args) as loader:
+    with TableLoader(dl2_from_dl1) as loader:
         events_from_dl1 = loader.read_subarray_events()
 
     # both files should contain identical data
@@ -487,8 +486,8 @@ def test_only_trigger_and_simulation(tmp_path):
         raises=True,
     )
 
-    with TableLoader(output, load_simulated=True) as loader:
-        events = loader.read_subarray_events()
+    with TableLoader(output) as loader:
+        events = loader.read_subarray_events(dl2=False)
         assert len(events) == 7
         assert "tels_with_trigger" in events.colnames
         assert "true_energy" in events.colnames