From cae8dd33d4431d97b67ce576775b6085ea2a09a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Fr=C3=B6se?= Date: Thu, 6 Jul 2023 09:07:06 +0200 Subject: [PATCH 01/17] convert .ipynb to py and move --- .github/workflows/ci.yml | 2 +- docs/conf.py | 19 + .../examples/InstrumentDescription.ipynb | 374 ------ docs/user-guide/examples/Tools.ipynb | 618 --------- docs/user-guide/examples/array_display.ipynb | 377 ------ docs/user-guide/examples/camera_display.ipynb | 560 -------- docs/user-guide/examples/containers.ipynb | 396 ------ .../examples/convert_images_to_2d.ipynb | 208 --- docs/user-guide/examples/dilate_image.ipynb | 128 -- docs/user-guide/examples/index.rst | 36 - .../examples/nd_interpolation.ipynb | 207 --- docs/user-guide/examples/provenance.ipynb | 199 --- .../examples/table_writer_reader.ipynb | 456 ------- .../calibrated_data_exploration.ipynb | 398 ------ .../tutorials/coordinates_example.ipynb | 649 --------- .../tutorials/ctapipe_handson.ipynb | 678 ---------- .../tutorials/ctapipe_overview.ipynb | 1180 ----------------- docs/user-guide/tutorials/index.rst | 14 - .../tutorials/raw_data_exploration.ipynb | 522 -------- docs/user-guide/tutorials/theta_square.ipynb | 212 --- environment.yml | 1 + examples/examples/algorithms/README.rst | 4 + .../algorithms/convert_images_to_2d.py | 89 ++ examples/examples/algorithms/dilate_image.py | 68 + .../examples/algorithms/nd_interpolation.py | 146 ++ .../examples/core/InstrumentDescription.py | 161 +++ examples/examples/core/README.rst | 4 + examples/examples/core/Tools.py | 357 +++++ examples/examples/core/containers.py | 202 +++ examples/examples/core/provenance.py | 115 ++ examples/examples/core/table_writer_reader.py | 281 ++++ examples/examples/index.rst | 9 + examples/examples/visualization/README.rst | 4 + .../examples/visualization/array_display.py | 225 ++++ .../examples/visualization/camera_display.py | 416 ++++++ examples/tutorials/README.txt | 6 + .../tutorials/calibrated_data_exploration.py | 206 +++ examples/tutorials/coordinates_example.py | 414 ++++++ examples/tutorials/ctapipe_handson.py | 254 ++++ examples/tutorials/ctapipe_overview.py | 675 ++++++++++ examples/tutorials/ground_frame.png | Bin 0 -> 100340 bytes examples/tutorials/raw_data_exploration.py | 303 +++++ examples/tutorials/theta_square.py | 77 ++ examples/tutorials/tilted_ground_frame.png | Bin 0 -> 129772 bytes 44 files changed, 4037 insertions(+), 7213 deletions(-) delete mode 100644 docs/user-guide/examples/InstrumentDescription.ipynb delete mode 100644 docs/user-guide/examples/Tools.ipynb delete mode 100644 docs/user-guide/examples/array_display.ipynb delete mode 100644 docs/user-guide/examples/camera_display.ipynb delete mode 100644 docs/user-guide/examples/containers.ipynb delete mode 100644 docs/user-guide/examples/convert_images_to_2d.ipynb delete mode 100644 docs/user-guide/examples/dilate_image.ipynb delete mode 100644 docs/user-guide/examples/index.rst delete mode 100644 docs/user-guide/examples/nd_interpolation.ipynb delete mode 100644 docs/user-guide/examples/provenance.ipynb delete mode 100644 docs/user-guide/examples/table_writer_reader.ipynb delete mode 100644 docs/user-guide/tutorials/calibrated_data_exploration.ipynb delete mode 100644 docs/user-guide/tutorials/coordinates_example.ipynb delete mode 100644 docs/user-guide/tutorials/ctapipe_handson.ipynb delete mode 100644 docs/user-guide/tutorials/ctapipe_overview.ipynb delete mode 100644 docs/user-guide/tutorials/index.rst delete mode 100644 docs/user-guide/tutorials/raw_data_exploration.ipynb delete mode 100644 docs/user-guide/tutorials/theta_square.ipynb create mode 100644 examples/examples/algorithms/README.rst create mode 100644 examples/examples/algorithms/convert_images_to_2d.py create mode 100644 examples/examples/algorithms/dilate_image.py create mode 100644 examples/examples/algorithms/nd_interpolation.py create mode 100644 examples/examples/core/InstrumentDescription.py create mode 100644 examples/examples/core/README.rst create mode 100644 examples/examples/core/Tools.py create mode 100644 examples/examples/core/containers.py create mode 100644 examples/examples/core/provenance.py create mode 100644 examples/examples/core/table_writer_reader.py create mode 100644 examples/examples/index.rst create mode 100644 examples/examples/visualization/README.rst create mode 100644 examples/examples/visualization/array_display.py create mode 100644 examples/examples/visualization/camera_display.py create mode 100644 examples/tutorials/README.txt create mode 100644 examples/tutorials/calibrated_data_exploration.py create mode 100644 examples/tutorials/coordinates_example.py create mode 100644 examples/tutorials/ctapipe_handson.py create mode 100644 examples/tutorials/ctapipe_overview.py create mode 100644 examples/tutorials/ground_frame.png create mode 100644 examples/tutorials/raw_data_exploration.py create mode 100644 examples/tutorials/theta_square.py create mode 100644 examples/tutorials/tilted_ground_frame.png diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 248d26a8fac..8c7d0abe22c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -151,7 +151,7 @@ jobs: - name: Install doc dependencies run: | sudo apt update --yes && sudo apt install --yes git build-essential pandoc graphviz ffmpeg - pip install -U pip towncrier + pip install -U pip towncrier sphinx-gallery pip install -e .[docs] pip install ./test_plugin git describe --tags diff --git a/docs/conf.py b/docs/conf.py index 013d575931e..854b46773d6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -55,6 +55,7 @@ "numpydoc", "sphinx_design", "IPython.sphinxext.ipython_console_highlighting", + "sphinx_gallery.gen_gallery", ] @@ -142,6 +143,20 @@ def setup(app): ("py:class", "ctapipe.compat.StrEnum"), ] +# Sphinx gallery config +sphinx_gallery_conf = { + "examples_dirs": [ + "../examples/examples", + "../examples/tutorials", + ], # path to your example scripts + "gallery_dirs": [ + "examples", + "tutorials", + ], # path to where to save gallery generated output + "nested_sections": True, + "copyfile_regex": r"index.rst", +} + # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: @@ -195,6 +210,10 @@ def setup(app): ".DS_Store", "**.ipynb_checkpoints", "changes", + "examples/*/*.ipynb", + "examples/*/*.py", + "tutorials/*.ipynb", + "tutorials/*.py", ] # The name of the Pygments (syntax highlighting) style to use. diff --git a/docs/user-guide/examples/InstrumentDescription.ipynb b/docs/user-guide/examples/InstrumentDescription.ipynb deleted file mode 100644 index 2c3b6bd7ea0..00000000000 --- a/docs/user-guide/examples/InstrumentDescription.ipynb +++ /dev/null @@ -1,374 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Working with Instrumental Descriptions\n", - "\n", - "the instrumental description is loaded by the event source, and consists of a hierarchy of classes in the ctapipe.instrument module, the base of which is the `SubarrayDescription`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.utils.datasets import get_dataset_path\n", - "from ctapipe.io import EventSource\n", - "import numpy as np\n", - "\n", - "filename = get_dataset_path(\"gamma_prod5.simtel.zst\")\n", - "\n", - "with EventSource(filename, max_events=1) as source:\n", - " subarray = source.subarray" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## the SubarrayDescription:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.info()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.to_table()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also get a table of just the `OpticsDescriptions` (`CameraGeometry` is more complex and can't be stored on a single table row, so each one can be converted to a table separately)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.to_table(kind=\"optics\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Make a sub-array with only SC-type telescopes:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sc_tels = [tel_id for tel_id, tel in subarray.tel.items() if tel.optics.n_mirrors == 2]\n", - "newsub = subarray.select_subarray(sc_tels, name=\"SCTels\")\n", - "newsub.info()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "can also do this by using `Table.group_by`" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Explore some of the details of the telescopes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tel = subarray.tel[1]\n", - "tel" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tel.optics.mirror_area" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tel.optics.n_mirror_tiles" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tel.optics.equivalent_focal_length" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tel.camera" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tel.camera.geometry.pix_x" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "from ctapipe.visualization import CameraDisplay\n", - "\n", - "CameraDisplay(tel.camera.geometry)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "CameraDisplay(subarray.tel[98].camera.geometry)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Plot the subarray\n", - "\n", - "We'll make a subarray by telescope type and plot each separately, so they appear in different colors. We also calculate the radius using the mirror area (and exagerate it a bit).\n", - "\n", - "This is just for debugging and info, for any \"real\" use, a `visualization.ArrayDisplay` should be used" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.peek()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.footprint" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Get info about the subarray in general" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.telescope_types" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.camera_types" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.optics_types" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from astropy.coordinates import SkyCoord\n", - "from ctapipe.coordinates import GroundFrame\n", - "\n", - "center = SkyCoord(\"10.0 m\", \"2.0 m\", \"0.0 m\", frame=\"groundframe\")\n", - "coords = subarray.tel_coords # a flat list of coordinates by tel_index\n", - "coords.separation(center)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Telescope IDs vs Indices\n", - "\n", - "Note that `subarray.tel` is a dict mapped by `tel_id` (the indentifying number of a telescope). It is possible to have telescope IDs that do not start at 0, are not contiguouous (e.g. if a subarray is selected). Some functions and properties like `tel_coords` are numpy arrays (not dicts) so they are not mapped to the telescope ID, but rather the *index* within this SubarrayDescription. To convert between the two concepts you can do:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.tel_ids_to_indices([1, 5, 23])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "or you can get the indexing array directly in numpy or dict form:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.tel_index_array" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.tel_index_array[[1, 5, 23]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.tel_indices[\n", - " 1\n", - "] # this is a dict of tel_id -> tel_index, so we can only do one at once" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ids = subarray.get_tel_ids_for_type(subarray.telescope_types[0])\n", - "ids" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "idx = subarray.tel_ids_to_indices(ids)\n", - "idx" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.tel_coords[idx]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "so, with that method you can quickly get many telescope positions at once (the alternative is to use the dict `positions` which maps `tel_id` to a position on the ground" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.positions[1]" - ] - } - ], - "metadata": { - "language_info": { - "codemirror_mode": { - "name": "ipython" - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/user-guide/examples/Tools.ipynb b/docs/user-guide/examples/Tools.ipynb deleted file mode 100644 index 02eb2b8f3c1..00000000000 --- a/docs/user-guide/examples/Tools.ipynb +++ /dev/null @@ -1,618 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Creating command-line Tools" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.core import Tool, Component, TelescopeComponent\n", - "from ctapipe.core.traits import (\n", - " Integer,\n", - " Float,\n", - " List,\n", - " Dict,\n", - " Unicode,\n", - " TraitError,\n", - " observe,\n", - " FloatTelescopeParameter,\n", - " Path,\n", - ")\n", - "import logging\n", - "from time import sleep\n", - "from astropy import units as u\n", - "from ctapipe.utils import get_dataset_path" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "GAMMA_FILE = get_dataset_path(\"gamma_prod5.simtel.zst\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "see https://github.com/ipython/traitlets/blob/master/examples/myapp.py" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup:\n", - "\n", - "Create a few `Component`s that we will use later in a `Tool`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class MyComponent(Component):\n", - " \"\"\"A Component that does stuff\"\"\"\n", - "\n", - " value = Integer(default_value=-1, help=\"Value to use\").tag(config=True)\n", - "\n", - " def do_thing(self):\n", - " self.log.debug(\"Did thing\")\n", - "\n", - "\n", - "# in order to have 2 of the same components at once\n", - "class SecondaryMyComponent(MyComponent):\n", - " \"\"\"A second component\"\"\"\n", - "\n", - " pass\n", - "\n", - "\n", - "class AdvancedComponent(Component):\n", - " \"\"\"An advanced technique\"\"\"\n", - "\n", - " value1 = Integer(default_value=-1, help=\"Value to use\").tag(config=True)\n", - " infile = Path(\n", - " help=\"input file name\",\n", - " exists=None, # set to True to require existing, False for requiring non-existing\n", - " directory_ok=False,\n", - " ).tag(config=True)\n", - " outfile = Path(help=\"output file name\", exists=False, directory_ok=False).tag(\n", - " config=True\n", - " )\n", - "\n", - " def __init__(self, config=None, parent=None, **kwargs):\n", - " super().__init__(config=config, parent=parent, **kwargs)\n", - " # components can have sub components, but these must have\n", - " # then parent=self as argument and be assigned as member\n", - " # so the full config can be received later\n", - " self.subcompent = MyComponent(parent=self)\n", - "\n", - " @observe(\"outfile\")\n", - " def on_outfile_changed(self, change):\n", - " self.log.warning(\"Outfile was changed to '{}'\".format(change))\n", - "\n", - "\n", - "class TelescopeWiseComponent(TelescopeComponent):\n", - " \"\"\"a component that contains parameters that are per-telescope configurable\"\"\"\n", - "\n", - " param = FloatTelescopeParameter(\n", - " help=\"Something configurable with telescope patterns\", default_value=5.0\n", - " ).tag(config=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "MyComponent()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "AdvancedComponent(infile=\"test.foo\", outfile=\"out.foo\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`TelescopeComponents` need to have a subarray given to them in order to work (since they need one to turn a `TelescopeParameter` into a concrete list of values for each telescope. Here we will give a dummy one:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.instrument import SubarrayDescription, TelescopeDescription\n", - "\n", - "subarray = SubarrayDescription.read(GAMMA_FILE)\n", - "subarray.info()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "TelescopeWiseComponent(subarray=subarray)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This TelescopeParameters can then be set using a list of patterns like:\n", - "```python\n", - "component.param = [ \n", - " (\"type\", \"LST*\",3.0), \n", - " (\"type\", \"MST*\", 2.0), \n", - " (id, 25, 4.0) \n", - "]\n", - "```\n", - "\n", - "These get translated into per-telescope-id values once the subarray is registered. After that one acccess the per-telescope id values via:\n", - "```python\n", - "component.param.tel[tel_id]\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Now create an executable Tool that contains the Components\n", - "Note that all the components we wish to be configured via the tool must be added to the `classes` attribute." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class MyTool(Tool):\n", - " name = \"mytool\"\n", - " description = \"do some things and stuff\"\n", - " aliases = dict(\n", - " infile=\"AdvancedComponent.infile\",\n", - " outfile=\"AdvancedComponent.outfile\",\n", - " iterations=\"MyTool.iterations\",\n", - " )\n", - "\n", - " # Which classes are registered for configuration\n", - " classes = [\n", - " MyComponent,\n", - " AdvancedComponent,\n", - " SecondaryMyComponent,\n", - " TelescopeWiseComponent,\n", - " ]\n", - "\n", - " # local configuration parameters\n", - " iterations = Integer(5, help=\"Number of times to run\", allow_none=False).tag(\n", - " config=True\n", - " )\n", - "\n", - " def setup(self):\n", - " self.comp = MyComponent(parent=self)\n", - " self.comp2 = SecondaryMyComponent(parent=self)\n", - " self.comp3 = TelescopeWiseComponent(parent=self, subarray=subarray)\n", - " self.advanced = AdvancedComponent(parent=self)\n", - "\n", - " def start(self):\n", - " self.log.info(\"Performing {} iterations...\".format(self.iterations))\n", - " for ii in range(self.iterations):\n", - " self.log.info(\"ITERATION {}\".format(ii))\n", - " self.comp.do_thing()\n", - " self.comp2.do_thing()\n", - " sleep(0.1)\n", - "\n", - " def finish(self):\n", - " self.log.warning(\"Shutting down.\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Get Help info\n", - "\n", - "The following allows you to print the help info within a Jupyter notebook, but this same inforamtion would be displayed if the user types:\n", - "```\n", - " mytool --help\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool = MyTool()\n", - "tool" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool.print_help()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The following is equivalant to the user typing `mytool --help-all`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool.print_help(classes=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Run the tool\n", - "\n", - "here we pass in argv since it is a Notebook, but if argv is not specified it's read from `sys.argv`, so the following is the same as running:\n", - "\n", - "```sh\n", - "mytool --log_level=INFO --infile gamma_test.simtel.gz --iterations=3\n", - "```\n", - "\n", - "As Tools are intended to be exectutables, they are raising `SystemExit` on exit.\n", - "Here, we use them to demonstrate how it would work, so we catch the `SystemExit`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "try:\n", - " tool.run(argv=[\"--infile\", str(GAMMA_FILE), \"--outfile\", \"out.csv\"])\n", - "except SystemExit as e:\n", - " assert e.code == 0, f\"Tool returned with error status {e}\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool.log_format = \"%(asctime)s : %(levelname)s [%(name)s %(funcName)s] %(message)s\"\n", - "\n", - "\n", - "try:\n", - " tool.run(\n", - " argv=[\n", - " \"--log-level\",\n", - " \"INFO\",\n", - " \"--infile\",\n", - " str(GAMMA_FILE),\n", - " \"--outfile\",\n", - " \"out.csv\",\n", - " \"--iterations\",\n", - " \"3\",\n", - " ]\n", - " )\n", - "except SystemExit as e:\n", - " assert e.code == 0, f\"Tool returned with error status {e}\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "here we change the log-level to DEBUG:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "try:\n", - " tool.run(\n", - " argv=[\n", - " \"--log-level\",\n", - " \"DEBUG\",\n", - " \"--infile\",\n", - " str(GAMMA_FILE),\n", - " \"--outfile\",\n", - " \"out.csv\",\n", - " ]\n", - " )\n", - "except SystemExit as e:\n", - " assert e.code == 0, f\"Tool returned with error status {e}\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "you can also set parameters directly in the class, rather than using the argument/configfile parser. This is useful if you are calling the Tool from a script rather than the command-line" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool.iterations = 1\n", - "tool.log_level = 0\n", - "\n", - "try:\n", - " tool.run([\"--infile\", str(GAMMA_FILE), \"--outfile\", \"out.csv\"])\n", - "except SystemExit as e:\n", - " assert e.code == 0, f\"Tool returned with error status {e}\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "see what happens when a value is set that is not of the correct type:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "try:\n", - " tool.iterations = \"badval\"\n", - "except TraitError as E:\n", - " print(\"bad value:\", E)\n", - "except SystemExit as e:\n", - " assert e.code == 0, f\"Tool returned with error status {e}\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Example of what happens when you change a parameter that is being \"observed\" in a class. It's handler is called:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool.advanced.outfile = \"Another.txt\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "we see that the handler for `outfile` was called, and it receive a change dict that shows the old and new values." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "create a tool using a config file:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool2 = MyTool()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "try:\n", - " tool2.run(argv=[\"--config\", \"Tools.json\"])\n", - "except SystemExit as e:\n", - " assert e.code == 0, f\"Tool returned with error status {e}\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(tool2.advanced.infile)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(tool2.config)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool2.is_setup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool3 = MyTool()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool3.is_setup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool3.initialize(argv=[])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool3.is_setup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool3" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool.setup()\n", - "tool" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool.comp2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Getting the configuration of an instance" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool.get_current_config()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tool.iterations = 12\n", - "tool.get_current_config()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Writing a Sample Config File" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(tool.generate_config_file())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.16" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/user-guide/examples/array_display.ipynb b/docs/user-guide/examples/array_display.ipynb deleted file mode 100644 index 6e7ca08f78e..00000000000 --- a/docs/user-guide/examples/array_display.ipynb +++ /dev/null @@ -1,377 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "fb302dc9-65c4-41b0-b8d3-865c0f7845a7", - "metadata": {}, - "source": [ - "# Array Displays\n", - "\n", - "Like `CameraDisplays`, ctapipe provides a way to display information related to the array on the ground: `ArrayDisplay`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1217a94e-f541-4dfe-8f4f-ec9dd70296fd", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "from ctapipe.visualization import ArrayDisplay\n", - "from ctapipe.instrument import SubarrayDescription\n", - "from ctapipe.coordinates import GroundFrame, EastingNorthingFrame\n", - "from ctapipe.containers import HillasParametersContainer\n", - "\n", - "from astropy.coordinates import SkyCoord\n", - "from astropy import units as u\n", - "\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "\n", - "plt.rcParams[\"figure.figsize\"] = (8, 6)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c7c6775d-ce49-4d83-a3c6-e7f5ed55abfd", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "tel_ids = list(range(1, 5)) + list(range(5, 20)) # just LSTs + one set of MSTs\n", - "\n", - "subarray = SubarrayDescription.read(\n", - " \"dataset://gamma_20deg_0deg_run1___cta-prod5-lapalma_desert-2158m-LaPalma-dark_100evts.simtel.zst\"\n", - ").select_subarray(tel_ids)" - ] - }, - { - "cell_type": "markdown", - "id": "d3b103fc-6b8c-4233-939a-09c5f5d0e60d", - "metadata": {}, - "source": [ - "An array display is created for example in `subarray.peek()`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a32c4646-9c74-4665-bfaa-81aa1de88dc8", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "subarray.peek()" - ] - }, - { - "cell_type": "markdown", - "id": "a27ed92a-268c-404c-8b96-039a739ea26a", - "metadata": {}, - "source": [ - "However, you can make one manually with a bit more flexibility:" - ] - }, - { - "cell_type": "markdown", - "id": "503dea3f-fff3-4cd2-b938-c0d524a6069c", - "metadata": {}, - "source": [ - "## Constructing an ArrayDisplay" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e27d5c28-dfc7-424b-b511-bcb0a1f35729", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "disp = ArrayDisplay(subarray)" - ] - }, - { - "cell_type": "markdown", - "id": "ea297d1c-4777-4945-81d0-e7807605b742", - "metadata": {}, - "source": [ - "You can specify the Frame you want as long as it is compatible with `GroundFrame`. `EastingNorthingFrame` is probably the most useful. You can also add telescope labels" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "46acf3bd-6cf3-42bb-8a76-163b44d3cc23", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "disp = ArrayDisplay(subarray, frame=EastingNorthingFrame())\n", - "disp.add_labels()" - ] - }, - { - "cell_type": "markdown", - "id": "2925e1f6-eb29-43a7-bda1-c647424e7cf7", - "metadata": {}, - "source": [ - "## Using color to show information \n", - "\n", - "By default the color of the telescope circles correlates to telescope type. However, you can use color to convey other information by setting the `values` attribute, like a trigger pattern" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "141ef1eb-82ca-4e45-a3ae-2b11828c601c", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "plt.set_cmap(\"rainbow\") # the array display will use the current colormap for values\n", - "\n", - "ad = ArrayDisplay(subarray)\n", - "ad.telescopes.set_linewidth(0) # to turn off the telescope borders\n", - "\n", - "trigger_pattern = np.zeros(subarray.n_tels)\n", - "trigger_pattern[\n", - " [\n", - " 1,\n", - " 4,\n", - " 5,\n", - " 6,\n", - " ]\n", - "] = 1\n", - "ad.values = trigger_pattern # display certain telescopes in a color\n", - "ad.add_labels()" - ] - }, - { - "cell_type": "markdown", - "id": "a2c0cec8-46b8-4ea4-b4a5-70d943ed474c", - "metadata": {}, - "source": [ - "or for example, you could use color to represent the telescope distance to the impact point" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8422c9da-df65-44c4-b8bf-da49d95b16fb", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "shower_impact = SkyCoord(200 * u.m, -200 * u.m, 0 * u.m, frame=EastingNorthingFrame())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e9e87e72-d000-4c14-99eb-665849d83e56", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "plt.set_cmap(\"rainbow\") # the array display will use the current colormap for values\n", - "ad = ArrayDisplay(subarray)\n", - "ad.telescopes.set_linewidth(0) # to turn off the telescope borders\n", - "plt.scatter(shower_impact.easting, shower_impact.northing, marker=\"+\", s=200)\n", - "\n", - "distances = np.hypot(\n", - " subarray.tel_coords.cartesian.x - shower_impact.cartesian.x,\n", - " subarray.tel_coords.cartesian.y - shower_impact.cartesian.y,\n", - ")\n", - "ad.values = distances\n", - "plt.colorbar(ad.telescopes, label=\"Distance (m)\")" - ] - }, - { - "cell_type": "markdown", - "id": "359935df-f551-40cf-8432-b191986c8213", - "metadata": {}, - "source": [ - "## Overlaying vectors\n", - "\n", - "For plotting reconstruction quantities, it's useful to overlay vectors on the telescope positions. `ArrayDisplay` provides functions:\n", - "* `set_vector_uv` to set by cartesian coordinates from the center of each telescope\n", - "* `set_vector_rho_phi` to set by polar coorinates from the center of each telescope\n", - "* `set_vector_hillas` to set vectors from a `dict[int,HillasParameters]` mapping tel_id (not index!) to a set of parameters. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "040a0f4c-ffee-4a25-a419-7d90f5f16a6f", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "np.random.seed(0)\n", - "phis = np.random.uniform(0, 180.0, size=subarray.n_tels) * u.deg\n", - "rhos = np.ones(subarray.n_tels) * 200 * u.m\n", - "\n", - "\n", - "ad = ArrayDisplay(subarray, frame=EastingNorthingFrame(), tel_scale=2)\n", - "ad.set_vector_rho_phi(rho=rhos, phi=phis)" - ] - }, - { - "cell_type": "markdown", - "id": "980be561-28f6-41e6-94cd-ec19d260fe3b", - "metadata": { - "tags": [] - }, - "source": [ - "## Overlaying Image Axes\n", - "\n", - "For the common use case of plotting image axis on an `ArrayDisplay`, the `set_line_hillas()` method is provided for convenience. The following example shows its use: " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cb0b242c-4fc4-48ff-b8f2-2d57b852deea", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "from astropy.coordinates import SkyCoord\n", - "from ctapipe.calib import CameraCalibrator\n", - "from ctapipe.image import ImageProcessor\n", - "from ctapipe.io import EventSource\n", - "from ctapipe.reco import ShowerProcessor\n", - "from ctapipe.utils import get_dataset_path\n", - "from ctapipe.visualization import ArrayDisplay\n", - "from IPython import display\n", - "from matplotlib.animation import FuncAnimation\n", - "\n", - "input_url = \"dataset://gamma_LaPalma_baseline_20Zd_180Az_prod3b_test.simtel.gz\"" - ] - }, - { - "cell_type": "markdown", - "id": "f073a3a5-5db3-43fa-8734-deea0de7b7b4", - "metadata": { - "tags": [] - }, - "source": [ - "First, we define a function to plot the array with overlaid lines for the image axes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b24b2c6b-60fb-4a5c-b433-9be76ab26cee", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "def plot_event(event, subarray, ax):\n", - " \"\"\"\n", - " Draw an ArrayDisplay with image axes and the\n", - " true and reconstructed impact position overlaid\n", - " \"\"\"\n", - "\n", - " array_pointing = SkyCoord(\n", - " az=event.pointing.array_azimuth,\n", - " alt=event.pointing.array_altitude,\n", - " frame=\"altaz\",\n", - " )\n", - "\n", - " angle_offset = event.pointing.array_azimuth\n", - " disp = ArrayDisplay(subarray, axes=ax)\n", - "\n", - " hillas_dict = {tid: tel.parameters.hillas for tid, tel in event.dl1.tel.items()}\n", - " core_dict = {tid: tel.parameters.core.psi for tid, tel in event.dl1.tel.items()}\n", - "\n", - " disp.set_line_hillas(\n", - " hillas_dict,\n", - " core_dict,\n", - " 500,\n", - " )\n", - "\n", - " reco_shower = event.dl2.stereo.geometry[\"HillasReconstructor\"]\n", - "\n", - " ax.scatter(\n", - " event.simulation.shower.core_x,\n", - " event.simulation.shower.core_y,\n", - " s=200,\n", - " c=\"k\",\n", - " marker=\"x\",\n", - " label=\"True Impact\",\n", - " )\n", - " ax.scatter(\n", - " reco_shower.core_x,\n", - " reco_shower.core_y,\n", - " s=200,\n", - " c=\"r\",\n", - " marker=\"x\",\n", - " label=\"Estimated Impact\",\n", - " )\n", - "\n", - " ax.legend()" - ] - }, - { - "cell_type": "markdown", - "id": "47c2e70a-f1f3-4922-92e6-12fffe0566a4", - "metadata": {}, - "source": [ - "Now, we can loop through some events and plot them. Here we apply default calibration, image processing, and reconstruction, however it is better to use `ctapipe-process` with a well-defined configuration to do this in reality. Note that some events will not have images bright enough to do parameterization or reconstruction, so they will have no image axis lines or no estimated impact position." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "502fe577-3e8a-4e6d-90f3-8db8fbb57b90", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(5, 3, figsize=(20, 40), constrained_layout=True)\n", - "ax = ax.ravel()\n", - "\n", - "with EventSource(input_url, max_events=15, focal_length_choice=\"EQUIVALENT\") as source:\n", - " calib = CameraCalibrator(subarray=source.subarray)\n", - " process_images = ImageProcessor(subarray=source.subarray)\n", - " process_shower = ShowerProcessor(subarray=source.subarray)\n", - "\n", - " for i, event in enumerate(source):\n", - " calib(event)\n", - " process_images(event)\n", - " process_shower(event)\n", - " plot_event(event, source.subarray, ax=ax[i])" - ] - } - ], - "metadata": { - "language_info": { - "codemirror_mode": { - "name": "ipython" - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/user-guide/examples/camera_display.ipynb b/docs/user-guide/examples/camera_display.ipynb deleted file mode 100644 index 3d1274ee515..00000000000 --- a/docs/user-guide/examples/camera_display.ipynb +++ /dev/null @@ -1,560 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Displaying Camera Images" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import astropy.coordinates as c\n", - "import astropy.units as u\n", - "import matplotlib.pylab as plt\n", - "import numpy as np\n", - "from ctapipe.coordinates import CameraFrame, EngineeringCameraFrame, TelescopeFrame\n", - "from ctapipe.image import hillas_parameters, tailcuts_clean, toymodel\n", - "from ctapipe.instrument import SubarrayDescription\n", - "from ctapipe.visualization import CameraDisplay" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, let's create a fake Cherenkov image from a given `CameraGeometry` and fill it with some data that we can draw later." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# load an example camera geometry from a simulation file\n", - "subarray = SubarrayDescription.read(\"dataset://gamma_prod5.simtel.zst\")\n", - "geom = subarray.tel[100].camera.geometry\n", - "\n", - "# create a fake camera image to display:\n", - "model = toymodel.Gaussian(\n", - " x=0.2 * u.m,\n", - " y=0.0 * u.m,\n", - " width=0.05 * u.m,\n", - " length=0.15 * u.m,\n", - " psi=\"35d\",\n", - ")\n", - "\n", - "image, sig, bg = model.generate_image(geom, intensity=1500, nsb_level_pe=10)\n", - "mask = tailcuts_clean(geom, image, picture_thresh=15, boundary_thresh=5)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "geom" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Displaying Images\n", - "\n", - "The simplest plot is just to generate a CameraDisplay with an image in its constructor. A figure and axis will be created automatically" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "CameraDisplay(geom)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also specify the initial `image`, `cmap` and `norm` (colomap and normalization, see below), `title` to use. You can specify `ax` if you want to draw the camera on an existing *matplotlib* `Axes` object (otherwise one is created).\n", - "\n", - "To change other options, or to change options dynamically, you can call the relevant functions of the `CameraDisplay` object that is returned. For example to add a color bar, call `add_colorbar()`, or to change the color scale, modify the `cmap` or `norm` properties directly. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Choosing a coordinate frame\n", - "\n", - "The `CameraGeometry` object contains a `ctapipe.coordinates.Frame` used by `CameraDisplay` to draw the camera in the correct orientation and distance units. The default frame is the `CameraFrame`, which will display the camera in units of *meters* and with an orientation that the top of the camera (when parked) is aligned to the X-axis. To show the camera in another orientation, it's useful to apply a coordinate transform to the `CameraGeometry` before passing it to the `CameraDisplay`. The following `Frames` are supported:\n", - "* `EngineeringCameraFrame` : similar to CameraFrame, but with the top of the camera aligned to the Y axis\n", - "* `TelescopeFrame`: In *degrees* (on the sky) coordinates relative to the telescope Alt/Az pointing position, with the Alt axis pointing upward. \n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(1, 3, figsize=(15, 4))\n", - "CameraDisplay(geom, image=image, ax=ax[0])\n", - "CameraDisplay(geom.transform_to(EngineeringCameraFrame()), image=image, ax=ax[1])\n", - "CameraDisplay(geom.transform_to(TelescopeFrame()), image=image, ax=ax[2])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note the the name of the Frame appears in the lower-right corner" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For the rest of this demo, let's use the `TelescopeFrame`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "geom_camframe = geom\n", - "geom = geom_camframe.transform_to(EngineeringCameraFrame())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Changing the color map and scale" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "CameraDisplay supports any [matplotlib color map](https://matplotlib.org/stable/tutorials/colors/colormaps.html)\n", - "It is **highly recommended** to use a *perceptually uniform* map, unless you have a good reason not to." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(1, 3, figsize=(15, 4))\n", - "for ii, cmap in enumerate([\"PuOr_r\", \"rainbow\", \"twilight\"]):\n", - " disp = CameraDisplay(geom, image=image, ax=ax[ii], title=cmap)\n", - " disp.add_colorbar()\n", - " disp.cmap = cmap" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "By default the minimum and maximum of the color bar are set automatically by the data in the image. To choose fixed limits, use:`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(1, 3, figsize=(15, 4))\n", - "for ii, minmax in enumerate([(10, 50), (-10, 10), (1, 100)]):\n", - " disp = CameraDisplay(geom, image=image, ax=ax[ii], title=minmax)\n", - " disp.add_colorbar()\n", - " disp.set_limits_minmax(minmax[0], minmax[1])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Or you can set the maximum limit by percentile of the charge distribution:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(1, 3, figsize=(15, 4))\n", - "for ii, pct in enumerate([30, 50, 90]):\n", - " disp = CameraDisplay(geom, image=image, ax=ax[ii], title=f\"{pct} %\")\n", - " disp.add_colorbar()\n", - " disp.set_limits_percent(pct)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Using different normalizations" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can choose from several preset normalizations (lin, log, symlog) and also provide a custom normalization, for example a `PowerNorm`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from matplotlib.colors import PowerNorm\n", - "\n", - "fig, axes = plt.subplots(2, 2, figsize=(14, 10))\n", - "norms = [\"lin\", \"log\", \"symlog\", PowerNorm(0.5)]\n", - "\n", - "for norm, ax in zip(norms, axes.flatten()):\n", - " disp = CameraDisplay(geom, image=image, ax=ax)\n", - " disp.norm = norm\n", - " disp.add_colorbar()\n", - " ax.set_title(str(norm))\n", - "\n", - "axes[1, 1].set_title(\"PowerNorm(0.5)\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Overlays" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Marking pixels\n", - "\n", - "here we will mark pixels in the image mask. That will change their outline color" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(1, 2, figsize=(10, 4))\n", - "disp = CameraDisplay(\n", - " geom, image=image, cmap=\"gray\", ax=ax[0], title=\"Image mask in green\"\n", - ")\n", - "disp.highlight_pixels(mask, alpha=0.8, linewidth=2, color=\"green\")\n", - "\n", - "disp = CameraDisplay(\n", - " geom, image=image, cmap=\"gray\", ax=ax[1], title=\"Image mask in green (zoom)\"\n", - ")\n", - "disp.highlight_pixels(mask, alpha=1, linewidth=3, color=\"green\")\n", - "\n", - "ax[1].set_ylim(-0.5, 0.5)\n", - "ax[1].set_xlim(-0.5, 0.5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Drawing a Hillas-parameter ellipse" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this, we will first compute some Hillas Parameters in the current frame:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "clean_image = image.copy()\n", - "clean_image[~mask] = 0\n", - "hillas = hillas_parameters(geom, clean_image)\n", - "\n", - "plt.figure(figsize=(6, 6))\n", - "disp = CameraDisplay(geom, image=image, cmap=\"gray_r\")\n", - "disp.highlight_pixels(mask, alpha=0.5, color=\"dodgerblue\")\n", - "disp.overlay_moments(hillas, color=\"red\", linewidth=3, with_label=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Drawing a marker at a coordinate\n", - "\n", - "This depends on the coordinate frame of the `CameraGeometry`. Here we will sepcify the coordinate the `EngineerngCameraFrame`, but if you have enough information to do the coordinate transform, you could use `ICRS` coordinates and overlay star positions. `CameraDisplay` will convert the coordinate you pass in to the `Frame` of the display automatically (if sufficient frame attributes are set). \n", - "\n", - "Note that the parameter `keep_old` is False by default, meaning adding a new point will clear the previous ones (useful for animations, but perhaps unexpected for a static plot). Set it to `True` to plot multiple markers." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.figure(figsize=(6, 6))\n", - "disp = CameraDisplay(geom, image=image, cmap=\"gray_r\")\n", - "\n", - "coord = c.SkyCoord(x=0.5 * u.m, y=0.7 * u.m, frame=geom.frame)\n", - "coord_in_another_frame = c.SkyCoord(x=0.5 * u.m, y=0.7 * u.m, frame=CameraFrame())\n", - "disp.overlay_coordinate(coord, markersize=20, marker=\"*\")\n", - "disp.overlay_coordinate(\n", - " coord_in_another_frame, markersize=20, marker=\"*\", keep_old=True\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Generating an animation\n", - "\n", - "Here we will make an animation of fake events by re-using a single display (much faster than generating a new one each time) " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython import display\n", - "from matplotlib.animation import FuncAnimation\n", - "\n", - "subarray = SubarrayDescription.read(\"dataset://gamma_prod5.simtel.zst\")\n", - "geom = subarray.tel[1].camera.geometry\n", - "\n", - "fov = 1.0\n", - "maxwid = 0.05\n", - "maxlen = 0.1\n", - "\n", - "fig, ax = plt.subplots(1, 1, figsize=(8, 6))\n", - "disp = CameraDisplay(geom, ax=ax) # we only need one display (it can be re-used)\n", - "disp.cmap = \"inferno\"\n", - "disp.add_colorbar(ax=ax)\n", - "\n", - "\n", - "def update(frame):\n", - " \"\"\"this function will be called for each frame of the animation\"\"\"\n", - " x, y = np.random.uniform(-fov, fov, size=2)\n", - " width = np.random.uniform(0.01, maxwid)\n", - " length = np.random.uniform(width, maxlen)\n", - " angle = np.random.uniform(0, 180)\n", - " intens = width * length * (5e4 + 1e5 * np.random.exponential(2))\n", - "\n", - " model = toymodel.Gaussian(\n", - " x=x * u.m,\n", - " y=y * u.m,\n", - " width=width * u.m,\n", - " length=length * u.m,\n", - " psi=angle * u.deg,\n", - " )\n", - " image, _, _ = model.generate_image(\n", - " geom,\n", - " intensity=intens,\n", - " nsb_level_pe=5,\n", - " )\n", - " disp.image = image\n", - "\n", - "\n", - "# Create the animation and convert to a displayable video:\n", - "anim = FuncAnimation(fig, func=update, frames=10, interval=200)\n", - "plt.close(fig) # so it doesn't display here\n", - "video = anim.to_html5_video()\n", - "display.display(display.HTML(video))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Using CameraDisplays interactively\n", - "\n", - "`CameraDisplays` can be used interactivly whe displayed in a window, and also when using Jupyter notebooks/lab with appropriate backends. \n", - "\n", - "When this is the case, the same `CameraDisplay` object can be re-used. We can't show this here in the documentation, but creating an animation when in a matplotlib window is quite easy! Try this in an interactive ipython session:\n", - "\n", - "### Running interactive displays in a matplotlib window\n", - "\n", - "```sh\n", - "ipython -i --maplotlib=auto\n", - "```\n", - "\n", - "That will open an ipython session with matplotlib graphics in a separate thread, meaning that you can type code and interact with plots simultaneneously. \n", - "\n", - "In the ipython session try running the following code and you will see an animation (here in the documentation, it will of course be static)\n", - "\n", - "First we load some real data so we have a nice image to view:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "from ctapipe.io import EventSource\n", - "from ctapipe.visualization import CameraDisplay\n", - "import numpy as np\n", - "\n", - "DATA = \"dataset://gamma_20deg_0deg_run1___cta-prod5-lapalma_desert-2158m-LaPalma-dark_100evts.simtel.zst\"\n", - "\n", - "with EventSource(\n", - " DATA,\n", - " max_events=1,\n", - " focal_length_choice=\"EQUIVALENT\",\n", - ") as source:\n", - " event = next(iter(source))\n", - "\n", - "tel_id = list(event.r0.tel.keys())[0]\n", - "geom = source.subarray.tel[tel_id].camera.geometry\n", - "waveform = event.r0.tel[tel_id].waveform\n", - "n_chan, n_pix, n_samp = waveform.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Running the following the will bring up a window and animate the shower image as a function of time. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "disp = CameraDisplay(geom)\n", - "\n", - "for ii in range(n_samp):\n", - " disp.image = waveform[0, :, ii]\n", - " plt.pause(0.1) # this lets matplotlib re-draw the scene" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The output will be similar to the static animation created as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(1, 1)\n", - "disp = CameraDisplay(geom, ax=ax)\n", - "disp.add_colorbar()\n", - "disp.autoscale = False\n", - "\n", - "\n", - "def draw_sample(frame):\n", - " ax.set_title(f\"sample: {frame}\")\n", - " disp.set_limits_minmax(200, 400)\n", - " disp.image = waveform[0, :, frame]\n", - "\n", - "\n", - "anim = FuncAnimation(fig, func=draw_sample, frames=n_samp, interval=100)\n", - "plt.close(fig) # so it doesn't display here\n", - "video = anim.to_html5_video()\n", - "display.display(display.HTML(video))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Making it clickable \n", - "\n", - "Also when running in a window, you can enable the `disp.enable_pixel_picker()` option. This will then allow the user to click a pixel and a function will run. By default the function simply prints the pixel and value to stdout, however you can override the function `on_pixel_clicked(pix_id)` to do anything you want by making a subclass" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class MyCameraDisplay(CameraDisplay):\n", - " def on_pixel_clicked(self, pix_id):\n", - " print(f\"{pix_id=} has value {self.image[pix_id]:.2f}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "disp = MyCameraDisplay(geom, image=image)\n", - "disp.enable_pixel_picker()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "then, when a user clicks a pixel it would print:\n", - "```\n", - "pixel 5 has value 2.44\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "anaconda-cloud": {}, - "language_info": { - "codemirror_mode": { - "name": "ipython" - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/user-guide/examples/containers.ipynb b/docs/user-guide/examples/containers.ipynb deleted file mode 100644 index 603139f6f41..00000000000 --- a/docs/user-guide/examples/containers.ipynb +++ /dev/null @@ -1,396 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Using Container classes\n", - "\n", - "`ctapipe.core.Container` is the base class for all event-wise data classes in ctapipe. It works like a object-relational mapper, in that it defines a set of `Fields` along with their metadata (description, unit, default), which can be later translated automatially into an output table using a `ctapipe.io.TableWriter`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.core import Container, Field, Map\n", - "import numpy as np\n", - "from astropy import units as u\n", - "from functools import partial" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's define a few example containers with some dummy fields in them:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class SubContainer(Container):\n", - " junk = Field(-1, \"Some junk\")\n", - " value = Field(0.0, \"some value\", unit=u.deg)\n", - "\n", - "\n", - "class TelContainer(Container):\n", - " # defaults should match the other requirements, e.g. the defaults\n", - " # should have the correct unit. It most often also makes sense to use\n", - " # an invalid value marker like nan for floats or -1 for positive integers\n", - " # as default\n", - " tel_id = Field(-1, \"telescope ID number\")\n", - "\n", - " # For mutable structures like lists, arrays or containers, use a `default_factory` function or class\n", - " # not an instance to assure each container gets a fresh instance and there is no hidden\n", - " # shared state between containers.\n", - " image = Field(default_factory=lambda: np.zeros(10), description=\"camera pixel data\")\n", - "\n", - "\n", - "class EventContainer(Container):\n", - " event_id = Field(-1, \"event id number\")\n", - "\n", - " tels_with_data = Field(\n", - " default_factory=list, description=\"list of telescopes with data\"\n", - " )\n", - " sub = Field(\n", - " default_factory=SubContainer, description=\"stuff\"\n", - " ) # a sub-container in the hierarchy\n", - "\n", - " # A Map is like a defaultdictionary with a specific container type as default.\n", - " # This can be used to e.g. store a container per telescope\n", - " # we use partial here to automatically get a function that creates a map with the correct container type\n", - " # as default\n", - " tel = Field(default_factory=partial(Map, TelContainer), description=\"telescopes\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Basic features" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ev = EventContainer()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Check that default values are automatically filled in" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(ev.event_id)\n", - "print(ev.sub)\n", - "print(ev.tel)\n", - "print(ev.tel.keys())\n", - "\n", - "# default dict access will create container:\n", - "print(ev.tel[1])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "print the dict representation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(ev)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We also get docstrings \"for free\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "?EventContainer" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "?SubContainer" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "values can be set as normal for a class:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ev.event_id = 100\n", - "ev.event_id" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ev.as_dict() # by default only shows the bare items, not sub-containers (See later)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ev.as_dict(recursive=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "and we can add a few of these to the parent container inside the tel dict:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ev.tel[10] = TelContainer()\n", - "ev.tel[5] = TelContainer()\n", - "ev.tel[42] = TelContainer()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# because we are using a default_factory to handle mutable defaults, the images are actually different:\n", - "ev.tel[42].image is ev.tel[32]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Be careful to use the `default_factory` mechanism for mutable fields, see this **negative** example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class DangerousContainer(Container):\n", - " image = Field(\n", - " np.zeros(10),\n", - " description=\"Attention!!!! Globally mutable shared state. Use default_factory instead\",\n", - " )\n", - "\n", - "\n", - "c1 = DangerousContainer()\n", - "c2 = DangerousContainer()\n", - "\n", - "c1.image[5] = 9999\n", - "\n", - "print(c1.image)\n", - "print(c2.image)\n", - "print(c1.image is c2.image)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ev.tel" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Converion to dictionaries" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ev.as_dict()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ev.as_dict(recursive=True, flatten=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "for serialization to a table, we can even flatten the output into a single set of columns" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ev.as_dict(recursive=True, flatten=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setting and clearing values" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ev.tel[5].image[:] = 9\n", - "print(ev)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ev.reset()\n", - "ev.as_dict(recursive=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## look at a pre-defined Container" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.containers import SimulatedShowerContainer" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "?SimulatedShowerContainer" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "shower = SimulatedShowerContainer()\n", - "shower" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Container prefixes\n", - "\n", - "To store the same container in the same table in a file or give more information, containers support setting\n", - "a custom prefix:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "c1 = SubContainer(junk=5, value=3, prefix=\"foo\")\n", - "c2 = SubContainer(junk=10, value=9001, prefix=\"bar\")\n", - "\n", - "# create a common dict with data from both containers:\n", - "d = c1.as_dict(add_prefix=True)\n", - "d.update(c2.as_dict(add_prefix=True))\n", - "d" - ] - } - ], - "metadata": { - "anaconda-cloud": {}, - "language_info": { - "codemirror_mode": { - "name": "ipython" - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/user-guide/examples/convert_images_to_2d.ipynb b/docs/user-guide/examples/convert_images_to_2d.ipynb deleted file mode 100644 index a38e135ab2d..00000000000 --- a/docs/user-guide/examples/convert_images_to_2d.ipynb +++ /dev/null @@ -1,208 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Convert camera images to pixels on a s square grid" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.utils import get_dataset_path\n", - "from ctapipe.visualization import CameraDisplay\n", - "from ctapipe.instrument import SubarrayDescription\n", - "from ctapipe.io import EventSource\n", - "from ctapipe.image.toymodel import Gaussian\n", - "import matplotlib.pyplot as plt\n", - "import astropy.units as u" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# get the subarray from an example file\n", - "subarray = SubarrayDescription.read(\"dataset://gamma_prod5.simtel.zst\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Geometries with square pixels\n", - "\n", - "Define a camera geometry and generate a dummy image:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "geom = subarray.tel[40].camera.geometry\n", - "model = Gaussian(\n", - " x=0.05 * u.m,\n", - " y=0.05 * u.m,\n", - " width=0.01 * u.m,\n", - " length=0.05 * u.m,\n", - " psi=\"30d\",\n", - ")\n", - "_, image, _ = model.generate_image(geom, intensity=500, nsb_level_pe=3)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "CameraDisplay(geom, image)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `CameraGeometry` has functions to convert the 1d image arrays to 2d arrays and back to the 1d array:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "image_square = geom.image_to_cartesian_representation(image)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.imshow(image_square)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "image_1d = geom.image_from_cartesian_representation(image_square)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "CameraDisplay(geom, image_1d)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Geometries with hexagonal pixels\n", - "\n", - "Define a camera geometry and generate a dummy image:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "geom = subarray.tel[1].camera.geometry\n", - "model = Gaussian(\n", - " x=0.5 * u.m,\n", - " y=0.5 * u.m,\n", - " width=0.1 * u.m,\n", - " length=0.2 * u.m,\n", - " psi=\"30d\",\n", - ")\n", - "_, image, _ = model.generate_image(geom, intensity=5000)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "CameraDisplay(geom, image)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "image_square = geom.image_to_cartesian_representation(image)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Conversion into square geometry\n", - "\n", - "Since the resulting array has square pixels, the pixel grid has to be rotated and distorted.\n", - "This is reversible (The `image_from_cartesian_representation` method takes care of this):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.imshow(image_square)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "image_1d = geom.image_from_cartesian_representation(image_square)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "disp = CameraDisplay(geom, image_1d)" - ] - } - ], - "metadata": { - "language_info": { - "codemirror_mode": { - "name": "ipython" - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/user-guide/examples/dilate_image.ipynb b/docs/user-guide/examples/dilate_image.ipynb deleted file mode 100644 index e833a07f142..00000000000 --- a/docs/user-guide/examples/dilate_image.ipynb +++ /dev/null @@ -1,128 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Basic Image Cleaning and Dilation\n", - "\n", - "Here we create an example shower image, do a tail-cuts (picture/boundary) cleaning, and then dilate the resulting cleaning mask by several neighbor pixels" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "from matplotlib import pyplot as plt\n", - "import astropy.units as u" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.instrument import SubarrayDescription\n", - "from ctapipe.visualization import CameraDisplay\n", - "from ctapipe.image import toymodel, tailcuts_clean, dilate\n", - "\n", - "# Load a camera from an example file\n", - "subarray = SubarrayDescription.read(\"dataset://gamma_prod5.simtel.zst\")\n", - "geom = subarray.tel[100].camera.geometry\n", - "\n", - "# Create a fake camera image to display:\n", - "model = toymodel.Gaussian(\n", - " x=0.2 * u.m,\n", - " y=0.0 * u.m,\n", - " width=0.05 * u.m,\n", - " length=0.15 * u.m,\n", - " psi=\"35d\",\n", - ")\n", - "\n", - "image, sig, bg = model.generate_image(geom, intensity=1500, nsb_level_pe=5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Apply the image cleaning:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cleanmask = tailcuts_clean(geom, image, picture_thresh=10, boundary_thresh=5)\n", - "clean = image.copy()\n", - "clean[~cleanmask] = 0.0\n", - "\n", - "disp = CameraDisplay(geom, image=image)\n", - "disp.highlight_pixels(cleanmask, color=\"red\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now dialte the mask a few times:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.image.cleaning import dilate" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def show_dilate(mask, times=1):\n", - " m = mask.copy()\n", - " for ii in range(times):\n", - " m = dilate(geom, m)\n", - " CameraDisplay(\n", - " geom, image=(m.astype(int) + mask.astype(int)), title=\"dilate{}\".format(times)\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.figure(figsize=(18, 3))\n", - "\n", - "for ii in range(0, 6):\n", - " plt.subplot(1, 7, ii + 1)\n", - " show_dilate(cleanmask.copy(), times=ii)" - ] - } - ], - "metadata": { - "language_info": { - "codemirror_mode": { - "name": "ipython" - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/user-guide/examples/index.rst b/docs/user-guide/examples/index.rst deleted file mode 100644 index 71eb0c33078..00000000000 --- a/docs/user-guide/examples/index.rst +++ /dev/null @@ -1,36 +0,0 @@ -.. _examples: - -======== -Examples -======== - -Some lower-level examples of features of the ctapipe API (see -the Tutorials section for more complete examples) - - -.. toctree:: - :maxdepth: 1 - :caption: Visualization - - camera_display - array_display - - -.. toctree:: - :maxdepth: 1 - :caption: Algorithms - - dilate_image - nd_interpolation - convert_images_to_2d - - -.. toctree:: - :maxdepth: 1 - :caption: Core functionality - - InstrumentDescription - containers - Tools - provenance - table_writer_reader diff --git a/docs/user-guide/examples/nd_interpolation.ipynb b/docs/user-guide/examples/nd_interpolation.ipynb deleted file mode 100644 index f98d71c8cef..00000000000 --- a/docs/user-guide/examples/nd_interpolation.ipynb +++ /dev/null @@ -1,207 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Use N-dimensional Histogram functionality and Interpolation\n", - " \n", - "* could be used for example to read and interpolate an lookup table or IRF.\n", - "* In this example, we load a sample energy reconstruction lookup-table from a FITS file\n", - "* In this case it is only in 2D cube (to keep the file size small): `SIZE` vs `IMPACT-DISTANCE`, however the same method will work for any dimensionality" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pylab as plt\n", - "import numpy as np\n", - "from astropy.io import fits\n", - "from scipy.interpolate import RegularGridInterpolator\n", - "\n", - "from ctapipe.utils.datasets import get_dataset_path\n", - "from ctapipe.utils import Histogram\n", - "\n", - "%matplotlib inline" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### load an example datacube \n", - "(an energy table generated for a\n", - "small subset of HESS simulations) to use as a lookup table. Here\n", - "we will use the `Histogram` class, which automatically loads both\n", - "the data cube and creates arrays for the coordinates of each\n", - "axis." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "testfile = get_dataset_path(\"hess_ct001_energylut.fits.gz\")\n", - "energy_hdu = fits.open(testfile)[\"MEAN\"]\n", - "energy_table = Histogram.from_fits(energy_hdu)\n", - "print(energy_table)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### construct an interpolator that we can use to get values at any point:\n", - "\n", - "Here we will use a `RegularGridInterpolator`, since it is the most appropriate for this type of data, but others are available (see the SciPy documentation)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "centers = [energy_table.bin_centers(ii) for ii in range(energy_table.ndims)]\n", - "energy_lookup = RegularGridInterpolator(\n", - " centers, energy_table.hist, bounds_error=False, fill_value=-100\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`energy_lookup` is now just a continuous function of `log(SIZE)`,\n", - "`DISTANCE` in m. \n", - "\n", - "### Now plot some curves from the interpolator. \n", - "\n", - "Note that the LUT we used is does not have very high statistics,\n", - "so the interpolation starts to be affected by noise at the high\n", - "end. In a real case, we would want to use a table that has been\n", - "sanitized (smoothed and extrapolated)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "lsize = np.linspace(1.5, 5.0, 100)\n", - "dists = np.linspace(50, 100, 5)\n", - "\n", - "plt.figure()\n", - "plt.title(\"Variation of energy with size and impact distance\")\n", - "plt.xlabel(\"SIZE (P.E.)\")\n", - "plt.ylabel(\"ENERGY (TeV)\")\n", - "\n", - "for dist in dists:\n", - " plt.plot(\n", - " 10**lsize,\n", - " 10 ** energy_lookup((lsize, dist)),\n", - " \"+-\",\n", - " label=\"DIST={:.1f} m\".format(dist),\n", - " )\n", - "\n", - "plt.legend(loc=\"best\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Using the interpolator, reinterpolate the lookup table onto an $N \\times N$\n", - "grid (regardless of its original dimensions):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "N = 300\n", - "xmin, xmax = energy_table.bin_centers(0)[0], energy_table.bin_centers(0)[-1]\n", - "ymin, ymax = energy_table.bin_centers(1)[0], energy_table.bin_centers(1)[-1]\n", - "xx, yy = np.linspace(xmin, xmax, N), np.linspace(ymin, ymax, N)\n", - "X, Y = np.meshgrid(xx, yy)\n", - "points = list(zip(X.ravel(), Y.ravel()))\n", - "E = energy_lookup(points).reshape((N, N))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, let's plot the original table and the new one (E). The color bar shows $\\log_{10}(E)$ in TeV" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.figure(figsize=(12, 5))\n", - "plt.nipy_spectral()\n", - "\n", - "# the uninterpolated table\n", - "plt.subplot(1, 2, 1)\n", - "plt.xlim(1.5, 5)\n", - "plt.ylim(0, 500)\n", - "plt.xlabel(\"log10(SIZE)\")\n", - "plt.ylabel(\"Impact Dist (m)\")\n", - "plt.pcolormesh(\n", - " energy_table.bin_centers(0), energy_table.bin_centers(1), energy_table.hist.T\n", - ")\n", - "plt.title(\"Raw table, uninterpolated {0}\".format(energy_table.hist.T.shape))\n", - "cb = plt.colorbar()\n", - "cb.set_label(\"$\\log_{10}(E/\\mathrm{TeV})$\")\n", - "\n", - "# the interpolated table\n", - "plt.subplot(1, 2, 2)\n", - "plt.pcolormesh(np.linspace(xmin, xmax, N), np.linspace(ymin, ymax, N), E)\n", - "plt.xlim(1.5, 5)\n", - "plt.ylim(0, 500)\n", - "plt.xlabel(\"log10(SIZE)\")\n", - "plt.ylabel(\"Impact Dist(m)\")\n", - "plt.title(\"Interpolated to a ({0}, {0}) grid\".format(N))\n", - "cb = plt.colorbar()\n", - "cb.set_label(\"$\\log_{10}(E/\\mathrm{TeV})$\")\n", - "\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the high-stats central region, we get a nice smooth interpolation function. Of course we can see that there are a few more steps to take before using this table:\n", - "* need to deal with cases where the table had low stats near the edges (smooth or extrapolate, or set bounds)\n", - "* may need to smooth the table even where there are sufficient stats, to avoid wiggles" - ] - } - ], - "metadata": { - "anaconda-cloud": {}, - "language_info": { - "codemirror_mode": { - "name": "ipython" - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/user-guide/examples/provenance.ipynb b/docs/user-guide/examples/provenance.ipynb deleted file mode 100644 index 2743eed9261..00000000000 --- a/docs/user-guide/examples/provenance.ipynb +++ /dev/null @@ -1,199 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Using the ctapipe Provenance service\n", - "\n", - "The provenance functionality is used automatically when you use most of ctapipe functionality (particularly `ctapipe.core.Tool` and functions in `ctapipe.io` and `ctapipe.utils`), so normally you don't have to work with it directly. It tracks both input and output files, as well as details of the machine and software environment on which a Tool executed. \n", - "\n", - "Here we show some very low-level functions of this system:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.core import Provenance\n", - "from pprint import pprint" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Activities\n", - "\n", - "The basis of Provenance is an *activity*, which is generally an executable or step in a script. Activities can be nested (e.g. with sub-activities), as shown below, but normally this is not required:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "p = Provenance() # note this is a singleton, so only ever one global provenence object\n", - "p.clear()\n", - "p.start_activity()\n", - "p.add_input_file(\"test.txt\")\n", - "\n", - "p.start_activity(\"sub\")\n", - "p.add_input_file(\"subinput.txt\")\n", - "p.add_input_file(\"anothersubinput.txt\")\n", - "p.add_output_file(\"suboutput.txt\")\n", - "p.finish_activity(\"sub\")\n", - "\n", - "p.start_activity(\"sub2\")\n", - "p.add_input_file(\"sub2input.txt\")\n", - "p.finish_activity(\"sub2\")\n", - "\n", - "p.finish_activity()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "p.finished_activity_names" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Activities have associated input and output *entities* (files or other objects)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "[(x[\"activity_name\"], x[\"input\"]) for x in p.provenance]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Activities track when they were started and finished:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "[(x[\"activity_name\"], x[\"duration_min\"]) for x in p.provenance]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Full provenance\n", - "\n", - "The provence object is a list of activitites, and for each lots of details are collected:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "p.provenance[0]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This can be better represented in JSON:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(p.as_json(indent=2))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Storing provenance info in output files\n", - "\n", - "* already this can be stored in something like an HDF5 file header, which allows hierarchies.\n", - "* Try to flatted the data so it can be stored in a key=value header in a **FITS file** (using the FITS extended keyword convention to allow >8 character keywords), or as a table " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def flatten_dict(y):\n", - " out = {}\n", - "\n", - " def flatten(x, name=\"\"):\n", - " if type(x) is dict:\n", - " for a in x:\n", - " flatten(x[a], name + a + \".\")\n", - " elif type(x) is list:\n", - " i = 0\n", - " for a in x:\n", - " flatten(a, name + str(i) + \".\")\n", - " i += 1\n", - " else:\n", - " out[name[:-1]] = x\n", - "\n", - " flatten(y)\n", - " return out" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "d = dict(activity=p.provenance)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pprint(flatten_dict(d))" - ] - } - ], - "metadata": { - "language_info": { - "codemirror_mode": { - "name": "ipython" - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/user-guide/examples/table_writer_reader.ipynb b/docs/user-guide/examples/table_writer_reader.ipynb deleted file mode 100644 index aa27756d0c4..00000000000 --- a/docs/user-guide/examples/table_writer_reader.ipynb +++ /dev/null @@ -1,456 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Writing Containers to a tabular format\n", - "\n", - "The `TableWriter`/`TableReader` sub-classes allow you to write a `ctapipe.core.Container` class and its meta-data to an output table. They treat the `Field`s in the `Container` as columns in the output, and automatically generate a schema. Here we will go through an example of writing out data and reading it back with *Pandas*, *PyTables*, and a `ctapipe.io.TableReader`:\n", - "\n", - "In this example, we will use the `HDF5TableWriter`, which writes to HDF5 datasets using *PyTables*. Currently this is the only implemented TableWriter." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Caveats to think about:\n", - "* vector columns in Containers *can* be written, but some lilbraries like Pandas can not read those (so you must use pytables or astropy to read outputs that have vector columns)\n", - "* units are stored in the table metadata, but some libraries like Pandas ignore them and all other metadata" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create some example Containers" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.io import HDF5TableWriter\n", - "from ctapipe.core import Container, Field\n", - "from astropy import units as u\n", - "import numpy as np" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class VariousTypesContainer(Container):\n", - "\n", - " a_int = Field(int, \"some int value\")\n", - " a_float = Field(float, \"some float value with a unit\", unit=u.m)\n", - " a_bool = Field(bool, \"some bool value\")\n", - " a_np_int = Field(np.int64, \"a numpy int\")\n", - " a_np_float = Field(np.float64, \"a numpy float\")\n", - " a_np_bool = Field(np.bool_, \"np.bool\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "let's also make a dummy stream (generator) that will create a series of these containers" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def create_stream(n_event):\n", - "\n", - " data = VariousTypesContainer()\n", - " for i in range(n_event):\n", - "\n", - " data.a_int = int(i)\n", - " data.a_float = float(i) * u.cm # note unit conversion will happen\n", - " data.a_bool = (i % 2) == 0\n", - " data.a_np_int = np.int64(i)\n", - " data.a_np_float = np.float64(i)\n", - " data.a_np_bool = np.bool_((i % 2) == 0)\n", - "\n", - " yield data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for data in create_stream(2):\n", - "\n", - " for key, val in data.items():\n", - "\n", - " print(\"{}: {}, type : {}\".format(key, val, type(val)))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Writing the Data (and good practices)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Always use context managers with IO classes, as they will make sure the underlying resources are properly closed in case of errors:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "try:\n", - " with HDF5TableWriter(\"container.h5\", group_name=\"data\") as h5_table:\n", - "\n", - " for data in create_stream(10):\n", - "\n", - " h5_table.write(\"table\", data)\n", - " 0 / 0\n", - "except Exception as err:\n", - " print(\"FAILED:\", err)\n", - "print(\"Done\")\n", - "\n", - "h5_table.h5file.isopen == False" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!ls container.h5" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Appending new Containers" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To append some new containers we need to set the writing in append mode by using: 'mode=a'. But let's now first look at what happens if we don't." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for i in range(2):\n", - "\n", - " with HDF5TableWriter(\n", - " \"container.h5\", mode=\"w\", group_name=\"data_{}\".format(i)\n", - " ) as h5_table:\n", - "\n", - " for data in create_stream(10):\n", - "\n", - " h5_table.write(\"table\", data)\n", - "\n", - " print(h5_table.h5file)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!rm -f container.h5" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Ok so the writer destroyed the content of the file each time it opens the file. Now let's try to append some data group to it! (using mode='a')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for i in range(2):\n", - "\n", - " with HDF5TableWriter(\n", - " \"container.h5\", mode=\"a\", group_name=\"data_{}\".format(i)\n", - " ) as h5_table:\n", - "\n", - " for data in create_stream(10):\n", - "\n", - " h5_table.write(\"table\", data)\n", - "\n", - " print(h5_table.h5file)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "So we can append some data groups. As long as the data group_name does not already exists. Let's try to overwrite the data group : data_1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "try:\n", - " with HDF5TableWriter(\"container.h5\", mode=\"a\", group_name=\"data_1\") as h5_table:\n", - " for data in create_stream(10):\n", - " h5_table.write(\"table\", data)\n", - "except Exception as err:\n", - " print(\"Failed as expected:\", err)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Good ! I cannot overwrite my data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(bool(h5_table.h5file.isopen))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Reading the Data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Reading the whole table at once:\n", - "\n", - "For this, you have several choices. Since we used the HDF5TableWriter in this example, we have at least these options avilable:\n", - "\n", - "* Pandas\n", - "* PyTables\n", - "* Astropy Table\n", - "\n", - "For other TableWriter implementations, others may be possible (depending on format)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Reading using `ctapipe.io.read_table`\n", - "\n", - "This is the preferred method, it returns an astropy `Table` and supports keeping track of\n", - "units, metadata and transformations." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.io import read_table\n", - "\n", - "\n", - "table = read_table(\"container.h5\", \"/data_0/table\")\n", - "table[:5]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "table.meta" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Reading with Pandas:\n", - "\n", - "Pandas is a convenient way to read the output. **HOWEVER BE WARNED** that so far Pandas does not support reading the table *meta-data* or *units* for colums, so that information is lost! " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "\n", - "data = pd.read_hdf(\"container.h5\", key=\"/data_0/table\")\n", - "data.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Reading with PyTables" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import tables\n", - "\n", - "h5 = tables.open_file(\"container.h5\")\n", - "table = h5.root[\"data_0\"][\"table\"]\n", - "table" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "note that here we can still access the metadata" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "table.attrs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Reading one-row-at-a-time:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Rather than using the full-table methods, if you want to read it row-by-row (e.g. to maintain compatibility with an existing event loop), you can use a `TableReader` instance.\n", - "\n", - "The advantage here is that units and other metadata are retained and re-applied" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.io import HDF5TableReader\n", - "\n", - "\n", - "def read(mode):\n", - "\n", - " print(\"reading mode {}\".format(mode))\n", - "\n", - " with HDF5TableReader(\"container.h5\", mode=mode) as h5_table:\n", - "\n", - " for group_name in [\"data_0/\", \"data_1/\"]:\n", - "\n", - " group_name = \"/{}table\".format(group_name)\n", - " print(group_name)\n", - "\n", - " for data in h5_table.read(group_name, VariousTypesContainer):\n", - "\n", - " print(data.as_dict())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "read(\"r\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "read(\"r+\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "read(\"a\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "read(\"w\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "language_info": { - "codemirror_mode": { - "name": "ipython" - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/user-guide/tutorials/calibrated_data_exploration.ipynb b/docs/user-guide/tutorials/calibrated_data_exploration.ipynb deleted file mode 100644 index 2966094d860..00000000000 --- a/docs/user-guide/tutorials/calibrated_data_exploration.ipynb +++ /dev/null @@ -1,398 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Explore Calibrated Data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import ctapipe\n", - "from ctapipe.utils.datasets import get_dataset_path\n", - "from ctapipe.io import EventSource, EventSeeker\n", - "from ctapipe.visualization import CameraDisplay\n", - "from ctapipe.instrument import CameraGeometry\n", - "from matplotlib import pyplot as plt\n", - "from astropy import units as u\n", - "import numpy as np\n", - "\n", - "%matplotlib inline\n", - "plt.style.use(\"ggplot\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(ctapipe.__version__)\n", - "print(ctapipe.__file__)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's first open a raw event file and get an event out of it:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "filename = get_dataset_path(\"gamma_prod5.simtel.zst\")\n", - "source = EventSource(filename, max_events=2)\n", - "\n", - "for event in source:\n", - " print(event.index.event_id)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "filename" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "source" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "event" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "print(event.r1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Perform basic calibration:\n", - "\n", - "Here we will use a `CameraCalibrator` which is just a simple wrapper that runs the three calibraraton and trace-integration phases of the pipeline, taking the data from levels:\n", - "\n", - " **R0** → **R1** → **DL0** → **DL1**\n", - "\n", - "You could of course do these each separately, by using the classes `R1Calibrator`, `DL0Reducer`, and `DL1Calibrator`.\n", - "Note that we have not specified any configuration to the `CameraCalibrator`, so it will be using the default algorithms and thresholds, other than specifying that the product is a \"HESSIOR1Calibrator\" (hopefully in the near future that will be automatic)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.calib import CameraCalibrator\n", - "\n", - "calib = CameraCalibrator(subarray=source.subarray)\n", - "calib(event)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now the *r1*, *dl0* and *dl1* containers are filled in the event\n", - "\n", - "* **r1.tel[x]**: contains the \"r1-calibrated\" waveforms, after gain-selection, pedestal subtraciton, and gain-correction\n", - "* **dl0.tel[x]**: is the same but with optional data volume reduction (some pixels not filled), in this case this is not performed by default, so it is the same as r1\n", - "* **dl1.tel[x]**: contains the (possibly re-calibrated) waveforms as dl0, but also the time-integrated *image* that has been calculated using a `ImageExtractor` (a `NeighborPeakWindowSum` by default)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for tel_id in event.dl1.tel:\n", - " print(\"TEL{:03}: {}\".format(tel_id, source.subarray.tel[tel_id]))\n", - " print(\" - r0 wave shape : {}\".format(event.r0.tel[tel_id].waveform.shape))\n", - " print(\" - r1 wave shape : {}\".format(event.r1.tel[tel_id].waveform.shape))\n", - " print(\" - dl1 image shape : {}\".format(event.dl1.tel[tel_id].image.shape))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Some image processing:\n", - "\n", - "Let's look at the image" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.visualization import CameraDisplay\n", - "\n", - "tel_id = sorted(event.r1.tel.keys())[1]\n", - "sub = source.subarray\n", - "geometry = sub.tel[tel_id].camera.geometry\n", - "image = event.dl1.tel[tel_id].image" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "disp = CameraDisplay(geometry, image=image)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.image import tailcuts_clean, hillas_parameters" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "mask = tailcuts_clean(\n", - " geometry,\n", - " image,\n", - " picture_thresh=10,\n", - " boundary_thresh=5,\n", - " min_number_picture_neighbors=2,\n", - ")\n", - "cleaned = image.copy()\n", - "cleaned[~mask] = 0\n", - "disp = CameraDisplay(geometry, image=cleaned)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "params = hillas_parameters(geometry, cleaned)\n", - "print(params)\n", - "params" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "params = hillas_parameters(geometry, cleaned)\n", - "\n", - "plt.figure(figsize=(10, 10))\n", - "disp = CameraDisplay(geometry, image=image)\n", - "disp.add_colorbar()\n", - "disp.overlay_moments(params, color=\"red\", lw=3)\n", - "disp.highlight_pixels(mask, color=\"white\", alpha=0.3, linewidth=2)\n", - "\n", - "plt.xlim(params.x.to_value(u.m) - 0.5, params.x.to_value(u.m) + 0.5)\n", - "plt.ylim(params.y.to_value(u.m) - 0.5, params.y.to_value(u.m) + 0.5)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "source.metadata" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## More complex image processing:\n", - "\n", - "Let's now explore how stereo reconstruction works. \n", - "\n", - "### first, look at a summed image from multiple telescopes\n", - "\n", - "For this, we want to use a `CameraDisplay` again, but since we can't sum and display images with different cameras, we'll just sub-select images from a particular camera type\n", - "\n", - "These are the telescopes that are in this event:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tels_in_event = set(\n", - " event.dl1.tel.keys()\n", - ") # use a set here, so we can intersect it later\n", - "tels_in_event" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cam_ids = set(sub.get_tel_ids_for_type(\"MST_MST_NectarCam\"))\n", - "cam_ids" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cams_in_event = tels_in_event.intersection(cam_ids)\n", - "first_tel_id = list(cams_in_event)[0]\n", - "tel = sub.tel[first_tel_id]\n", - "print(\"{}s in event: {}\".format(tel, cams_in_event))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, let's sum those images:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "image_sum = np.zeros_like(\n", - " tel.camera.geometry.pix_x.value\n", - ") # just make an array of 0's in the same shape as the camera\n", - "\n", - "for tel_id in cams_in_event:\n", - " image_sum += event.dl1.tel[tel_id].image" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And finally display the sum of those images" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.figure(figsize=(8, 8))\n", - "\n", - "disp = CameraDisplay(tel.camera.geometry, image=image_sum)\n", - "disp.overlay_moments(params, with_label=False)\n", - "plt.title(\"Sum of {}x {}\".format(len(cams_in_event), tel))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "let's also show which telescopes those were. Note that currently ArrayDisplay's value field is a vector by `tel_index`, not `tel_id`, so we have to convert to a tel_index. (this may change in a future version to be more user-friendly)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.visualization import ArrayDisplay" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nectarcam_subarray = sub.select_subarray(cam_ids, name=\"NectarCam\")\n", - "\n", - "hit_pattern = np.zeros(shape=nectarcam_subarray.n_tels)\n", - "hit_pattern[[nectarcam_subarray.tel_indices[x] for x in cams_in_event]] = 100\n", - "\n", - "plt.set_cmap(plt.cm.Accent)\n", - "plt.figure(figsize=(8, 8))\n", - "\n", - "ad = ArrayDisplay(nectarcam_subarray)\n", - "ad.values = hit_pattern\n", - "ad.add_labels()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.13" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/user-guide/tutorials/coordinates_example.ipynb b/docs/user-guide/tutorials/coordinates_example.ipynb deleted file mode 100644 index 5fa46d0850d..00000000000 --- a/docs/user-guide/tutorials/coordinates_example.ipynb +++ /dev/null @@ -1,649 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "# Coordinates usage in ctapipe" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "import astropy.units as u\n", - "import copy\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "\n", - "\n", - "from ctapipe.io import EventSource\n", - "from ctapipe.calib import CameraCalibrator\n", - "from ctapipe.utils import get_dataset_path\n", - "\n", - "from ctapipe.visualization import ArrayDisplay\n", - "\n", - "\n", - "%matplotlib inline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "from astropy.coordinates import SkyCoord, AltAz\n", - "\n", - "from ctapipe.coordinates import (\n", - " GroundFrame,\n", - " TiltedGroundFrame,\n", - " NominalFrame,\n", - " TelescopeFrame,\n", - " CameraFrame,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# make plots and fonts larger\n", - "plt.rcParams[\"figure.figsize\"] = (12, 8)\n", - "plt.rcParams[\"font.size\"] = 16" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Open test dataset" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "filename = get_dataset_path(\"gamma_prod5.simtel.zst\")\n", - "source = EventSource(filename)\n", - "\n", - "events = [copy.deepcopy(event) for event in source]\n", - "event = events[4]\n", - "\n", - "layout = set(source.subarray.tel_ids)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Choose event with LST" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This ensures that the telescope is not \"parked\" (as it would be in an event where it is not triggered) but is actually pointing to a source." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [], - "source": [ - "print(f\"Telescope with data: {event.r1.tel.keys()}\")\n", - "tel_id = 3" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "## AltAz\n", - "\n", - "See [Astropy Docs on AltAz](https://docs.astropy.org/en/stable/api/astropy.coordinates.AltAz.html). \n", - "\n", - "Pointing direction of telescopes or the origin of a simulated shower are described in the `AltAz` frame.\n", - "This is a local, angular coordinate frame, with angles `altitude` and `azimuth`.\n", - "Altitude is the measured from the Horizon (0°) to the Zenith (90°).\n", - "For the azimuth, there are different conventions. In Astropy und thus ctapipe, Azimuth is oriented East of North (i.e., N=0°, E=90°)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from astropy.time import Time\n", - "from astropy.coordinates import EarthLocation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "obstime = Time(\"2013-11-01T03:00\")\n", - "location = EarthLocation.of_site(\"Roque de los Muchachos\")\n", - "\n", - "altaz = AltAz(location=location, obstime=obstime)\n", - "\n", - "array_pointing = SkyCoord(\n", - " alt=event.pointing.array_azimuth,\n", - " az=event.pointing.array_altitude,\n", - " frame=altaz,\n", - ")\n", - "\n", - "print(array_pointing)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## CameraFrame\n", - "\n", - "Camera coordinate frame.\n", - "\n", - "The camera frame is a 2d cartesian frame, describing position of objects in the focal plane of the telescope.\n", - "\n", - "The frame is defined as in H.E.S.S., starting at the horizon, the telescope is pointed to magnetic north in azimuth and then up to zenith.\n", - "\n", - "Now, x points north and y points west, so in this orientation, the camera coordinates line up with the CORSIKA ground coordinate system.\n", - "\n", - "MAGIC and FACT use a different camera coordinate system: Standing at the dish, looking at the camera, x points right, y points up.\n", - "To transform MAGIC/FACT to ctapipe, do x' = -y, y' = -x.\n", - "\n", - "**Typical usage**: Position of pixels in the focal plane." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "geometry = source.subarray.tel[tel_id].camera.geometry\n", - "pix_x = geometry.pix_x\n", - "pix_y = geometry.pix_y\n", - "focal_length = source.subarray.tel[tel_id].optics.equivalent_focal_length" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "telescope_pointing = SkyCoord(\n", - " alt=event.pointing.tel[tel_id].altitude,\n", - " az=event.pointing.tel[tel_id].azimuth,\n", - " frame=altaz,\n", - ")\n", - "\n", - "camera_frame = CameraFrame(\n", - " focal_length=focal_length,\n", - " rotation=0 * u.deg,\n", - " telescope_pointing=telescope_pointing,\n", - ")\n", - "\n", - "cam_coords = SkyCoord(x=pix_x, y=pix_y, frame=camera_frame)\n", - "\n", - "print(cam_coords)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "plt.scatter(cam_coords.x, cam_coords.y)\n", - "plt.title(f\"Camera type: {geometry.name}\")\n", - "plt.xlabel(f\"x / {cam_coords.x.unit}\")\n", - "plt.ylabel(f\"y / {cam_coords.y.unit}\")\n", - "plt.axis(\"square\");" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The implementation of the coordinate system with astropy makes it easier to use time of the observation and location of the observing site, to understand, for example which stars are visible during a certain night and how they might be visible in the camera.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.visualization import CameraDisplay\n", - "from ctapipe.instrument import SubarrayDescription\n", - "\n", - "location = EarthLocation.of_site(\"Roque de los Muchachos\")\n", - "obstime = Time(\"2018-11-01T04:00\")\n", - "\n", - "crab = SkyCoord.from_name(\"crab nebula\")\n", - "\n", - "altaz = AltAz(location=location, obstime=obstime)\n", - "\n", - "pointing = crab.transform_to(altaz)\n", - "\n", - "camera_frame = CameraFrame(\n", - " telescope_pointing=pointing,\n", - " focal_length=focal_length,\n", - " obstime=obstime,\n", - " location=location,\n", - ")\n", - "\n", - "\n", - "subarray = SubarrayDescription.read(\"dataset://gamma_prod5.simtel.zst\")\n", - "cam = subarray.tel[1].camera.geometry\n", - "fig, ax = plt.subplots()\n", - "display = CameraDisplay(cam, ax=ax)\n", - "\n", - "ax.set_title(\n", - " f\"La Palma, {obstime}, az={pointing.az.deg:.1f}°, zenith={pointing.zen.deg:.1f}°, camera={geometry.name}\"\n", - ")\n", - "\n", - "for i, name in enumerate([\"crab nebula\", \"o tau\", \"zet tau\"]):\n", - " star = SkyCoord.from_name(name)\n", - " star_cam = star.transform_to(camera_frame)\n", - "\n", - " x = star_cam.x.to_value(u.m)\n", - " y = star_cam.y.to_value(u.m)\n", - "\n", - " ax.plot(x, y, marker=\"*\", color=f\"C{i}\")\n", - " ax.annotate(\n", - " name,\n", - " xy=(x, y),\n", - " xytext=(5, 5),\n", - " textcoords=\"offset points\",\n", - " color=f\"C{i}\",\n", - " )\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## TelescopeFrame\n", - "\n", - "Telescope coordinate frame.\n", - "A `Frame` using a `UnitSphericalRepresentation`.\n", - "\n", - "This is basically the same as a `HorizonCoordinate`, but the origin is at the telescope's pointing direction.\n", - "This is what astropy calls a `SkyOffsetFrame`.\n", - "\n", - "The axis of the telescope frame, `fov_lon` and `fov_lat`, are aligned with the horizontal system's azimuth and altitude respectively.\n", - " \n", - "Pointing corrections should applied to the transformation between this frame and the camera frame." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true, - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "telescope_frame = TelescopeFrame(\n", - " telescope_pointing=pointing,\n", - " obstime=pointing.obstime,\n", - " location=pointing.location,\n", - ")\n", - "telescope_coords = cam_coords.transform_to(telescope_frame)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "wrap_angle = telescope_pointing.az + 180 * u.deg\n", - "\n", - "plt.axis(\"equal\")\n", - "plt.scatter(\n", - " telescope_coords.fov_lon.deg, telescope_coords.fov_lat.deg, alpha=0.2, color=\"gray\"\n", - ")\n", - "\n", - "\n", - "for i, name in enumerate([\"crab nebula\", \"o tau\", \"zet tau\"]):\n", - " star = SkyCoord.from_name(name)\n", - " star_tel = star.transform_to(telescope_frame)\n", - "\n", - " plt.plot(star_tel.fov_lon.deg, star_tel.fov_lat.deg, \"*\", ms=10)\n", - " plt.annotate(\n", - " name,\n", - " xy=(star_tel.fov_lon.deg, star_tel.fov_lat.deg),\n", - " xytext=(5, 5),\n", - " textcoords=\"offset points\",\n", - " color=f\"C{i}\",\n", - " )\n", - "\n", - "plt.xlabel(\"fov_lon / {}\".format(telescope_coords.altaz.az.unit))\n", - "plt.ylabel(\"fov_lat / {}\".format(telescope_coords.altaz.alt.unit))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## NominalFrame\n", - "\n", - "Nominal coordinate frame.\n", - "A Frame using a `UnitSphericalRepresentation`.\n", - "This is basically the same as a `HorizonCoordinate`, but the\n", - "origin is at an arbitray position in the sky.\n", - "This is what astropy calls a `SkyOffsetFrame`\n", - "If the telescopes are in divergent pointing, this `Frame` can be\n", - "used to transform to a common system.\n", - "- 2D reconstruction (`HillasIntersector`) is performed in this frame \n", - "- 3D reconstruction (`HillasReconstructor`) doesn't need this frame" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "source": [ - "Let's play a bit with 3 LSTs with divergent pointing" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "location = EarthLocation.of_site(\"Roque de los Muchachos\")\n", - "obstime = Time(\"2018-11-01T02:00\")\n", - "altaz = AltAz(location=location, obstime=obstime)\n", - "\n", - "crab = SkyCoord.from_name(\"crab nebula\")\n", - "\n", - "# let's observe crab\n", - "array_pointing = crab.transform_to(altaz)\n", - "\n", - "\n", - "# let the telescopes point to different positions\n", - "alt_offsets = u.Quantity([1, -1, -1], u.deg)\n", - "az_offsets = u.Quantity([0, -2, +2], u.deg)\n", - "\n", - "\n", - "tel_pointings = SkyCoord(\n", - " alt=array_pointing.alt + alt_offsets,\n", - " az=array_pointing.az + az_offsets,\n", - " frame=altaz,\n", - ")\n", - "\n", - "camera_frames = CameraFrame(\n", - " telescope_pointing=tel_pointings, # multiple pointings, so we get multiple frames\n", - " focal_length=focal_length,\n", - " obstime=obstime,\n", - " location=location,\n", - ")\n", - "\n", - "nom_frame = NominalFrame(origin=array_pointing, obstime=obstime, location=location)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(figsize=(15, 10))\n", - "ax.set_aspect(1)\n", - "\n", - "for i in range(3):\n", - " cam_coord = SkyCoord(x=pix_x, y=pix_y, frame=camera_frames[i])\n", - " nom_coord = cam_coord.transform_to(nom_frame)\n", - "\n", - " ax.scatter(\n", - " x=nom_coord.fov_lon.deg,\n", - " y=nom_coord.fov_lat.deg,\n", - " label=f\"Telescope {i + 1}\",\n", - " s=30,\n", - " alpha=0.15,\n", - " )\n", - "\n", - "\n", - "for i, name in enumerate([\"Crab\", \"o tau\", \"zet tau\"]):\n", - " s = SkyCoord.from_name(name)\n", - " s_nom = s.transform_to(nom_frame)\n", - " ax.plot(\n", - " s_nom.fov_lon.deg,\n", - " s_nom.fov_lat.deg,\n", - " \"*\",\n", - " ms=10,\n", - " )\n", - " ax.annotate(\n", - " name,\n", - " xy=(s_nom.fov_lon.deg, s_nom.fov_lat.deg),\n", - " xytext=(5, 5),\n", - " textcoords=\"offset points\",\n", - " color=f\"C{i}\",\n", - " )\n", - "\n", - "\n", - "ax.set_xlabel(f\"fov_lon / deg\")\n", - "ax.set_ylabel(f\"fov_lat / deg\")\n", - "\n", - "ax.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## GroundFrame\n", - "\n", - "\n", - "Ground coordinate frame. The ground coordinate frame is a simple\n", - " cartesian frame describing the 3 dimensional position of objects\n", - " compared to the array ground level in relation to the nomial\n", - " centre of the array. Typically this frame will be used for\n", - " describing the position on telescopes and equipment\n", - " \n", - "**Typical usage**: positions of telescopes on the ground (x, y, z)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "source.subarray.peek()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "source": [ - "In case a layout is selected, the following line will produce a different output from the picture above." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "source.subarray.select_subarray(layout, name=\"Prod3b layout\").peek()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Ground Frame](ground_frame.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this image all the telescope from the `gamma_test.simtel.gz` file are plotted as spheres in the GroundFrame." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## TiltedGroundFrame" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Tilted ground coordinate frame. \n", - "\n", - "The tilted ground coordinate frame is a cartesian system describing the 2 dimensional projected positions of objects in a tilted plane described by pointing_direction. The plane is rotated along the z_axis by the azimuth of the `pointing_direction` and then it is inclined with an angle equal to the zenith angle of the `pointing_direction`.\n", - "\n", - "This frame is used for the reconstruction of the shower core position." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Tilted Ground Frame](tilted_ground_frame.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This image picture both the telescopes in the GroundFrame (red) and in the TiltedGroundFrame (green) are displayed: in this case since the azimuth of the `pointing_direction` is 0 degrees, then the plane is just tilted according to the zenith angle. \n", - "\n", - "For playing with these and with more 3D models of the telescopes themselves, have a look at the [CREED_VTK](https://github.com/thomasgas/CREED_VTK) library. " - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.13" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/user-guide/tutorials/ctapipe_handson.ipynb b/docs/user-guide/tutorials/ctapipe_handson.ipynb deleted file mode 100644 index f3c27873309..00000000000 --- a/docs/user-guide/tutorials/ctapipe_handson.ipynb +++ /dev/null @@ -1,678 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Getting Started with ctapipe\n", - "\n", - "This hands-on was presented at the Paris CTA Consoritum meeting (K. Kosack)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Part 1: load and loop over data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.io import EventSource\n", - "from ctapipe import utils\n", - "from matplotlib import pyplot as plt\n", - "import numpy as np\n", - "\n", - "%matplotlib inline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "path = utils.get_dataset_path(\"gamma_prod5.simtel.zst\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "source = EventSource(path, max_events=5)\n", - "\n", - "for event in source:\n", - " print(event.count, event.index.event_id, event.simulation.shower.energy)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "event" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "event.r1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for event in EventSource(path, max_events=5):\n", - " print(event.count, event.r1.tel.keys())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "event.r0.tel[3]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "r0tel = event.r0.tel[3]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "r0tel.waveform" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "r0tel.waveform.shape" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "note that this is ($N_{channels}$, $N_{pixels}$, $N_{samples}$)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.pcolormesh(r0tel.waveform[0])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "brightest_pixel = np.argmax(r0tel.waveform[0].sum(axis=1))\n", - "print(f\"pixel {brightest_pixel} has sum {r0tel.waveform[0,1535].sum()}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.plot(r0tel.waveform[0, brightest_pixel], label=\"channel 0 (high-gain)\")\n", - "plt.plot(r0tel.waveform[1, brightest_pixel], label=\"channel 1 (low-gain)\")\n", - "plt.legend()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ipywidgets import interact\n", - "\n", - "\n", - "@interact\n", - "def view_waveform(chan=0, pix_id=brightest_pixel):\n", - " plt.plot(r0tel.waveform[chan, pix_id])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "try making this compare 2 waveforms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Part 2: Explore the instrument description\n", - "This is all well and good, but we don't really know what camera or telescope this is... how do we get instrumental description info?\n", - "\n", - "Currently this is returned *inside* the event (it will soon change to be separate in next version or so)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray = source.subarray" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.peek()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.to_table()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.tel[2]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.tel[2].camera" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.tel[2].optics" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tel = subarray.tel[2]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tel.camera" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "tel.optics" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tel.camera.geometry.pix_x" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tel.camera.geometry.to_table()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tel.optics.mirror_area" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.visualization import CameraDisplay" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "disp = CameraDisplay(tel.camera.geometry)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "disp = CameraDisplay(tel.camera.geometry)\n", - "disp.image = r0tel.waveform[\n", - " 0, :, 10\n", - "] # display channel 0, sample 0 (try others like 10)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - " ** aside: ** show demo using a CameraDisplay in interactive mode in ipython rather than notebook" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Part 3: Apply some calibration and trace integration" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.calib import CameraCalibrator" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "calib = CameraCalibrator(subarray=subarray)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for event in EventSource(path, max_events=5):\n", - " calib(event) # fills in r1, dl0, and dl1\n", - " print(event.dl1.tel.keys())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "event.dl1.tel[3]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dl1tel = event.dl1.tel[3]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dl1tel.image.shape # note this will be gain-selected in next version, so will be just 1D array of 1855" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dl1tel.peak_time" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "CameraDisplay(tel.camera.geometry, image=dl1tel.image)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "CameraDisplay(tel.camera.geometry, image=dl1tel.peak_time)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now for Hillas Parameters" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.image import hillas_parameters, tailcuts_clean" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "image = dl1tel.image\n", - "mask = tailcuts_clean(tel.camera.geometry, image, picture_thresh=10, boundary_thresh=5)\n", - "mask" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "CameraDisplay(tel.camera.geometry, image=mask)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cleaned = image.copy()\n", - "cleaned[~mask] = 0" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "disp = CameraDisplay(tel.camera.geometry, image=cleaned)\n", - "disp.cmap = plt.cm.coolwarm\n", - "disp.add_colorbar()\n", - "plt.xlim(0.5, 1.0)\n", - "plt.ylim(-1.0, 0.0)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "params = hillas_parameters(tel.camera.geometry, cleaned)\n", - "print(params)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "disp = CameraDisplay(tel.camera.geometry, image=cleaned)\n", - "disp.cmap = plt.cm.coolwarm\n", - "disp.add_colorbar()\n", - "plt.xlim(0.5, 1.0)\n", - "plt.ylim(-1.0, 0.0)\n", - "disp.overlay_moments(params, color=\"white\", lw=2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Part 4: Let's put it all together: \n", - "- loop over events, selecting only telescopes of the same type (e.g. LST:LSTCam)\n", - "- for each event, apply calibration/trace integration\n", - "- calculate Hillas parameters \n", - "- write out all hillas paremeters to a file that can be loaded with Pandas" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "first let's select only those telescopes with LST:LSTCam" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.telescope_types" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "subarray.get_tel_ids_for_type(\"LST_LST_LSTCam\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's write out program" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data = utils.get_dataset_path(\"gamma_prod5.simtel.zst\")\n", - "source = EventSource(data) # remove the max_events limit to get more stats" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for event in source:\n", - " calib(event)\n", - "\n", - " for tel_id, tel_data in event.dl1.tel.items():\n", - " tel = source.subarray.tel[tel_id]\n", - " mask = tailcuts_clean(tel.camera.geometry, tel_data.image)\n", - " if np.count_nonzero(mask) > 0:\n", - " params = hillas_parameters(tel.camera.geometry[mask], tel_data.image[mask])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.io import HDF5TableWriter" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "with HDF5TableWriter(filename=\"hillas.h5\", group_name=\"dl1\", overwrite=True) as writer:\n", - "\n", - " source = EventSource(data, allowed_tels=[1, 2, 3, 4], max_events=10)\n", - " for event in source:\n", - " calib(event)\n", - "\n", - " for tel_id, tel_data in event.dl1.tel.items():\n", - " tel = source.subarray.tel[tel_id]\n", - " mask = tailcuts_clean(tel.camera.geometry, tel_data.image)\n", - " params = hillas_parameters(tel.camera.geometry[mask], tel_data.image[mask])\n", - " writer.write(\"hillas\", params)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### We can now load in the file we created and plot it" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!ls *.h5" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "\n", - "hillas = pd.read_hdf(\"hillas.h5\", key=\"/dl1/hillas\")\n", - "hillas" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "_ = hillas.hist(figsize=(8, 8))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you do this yourself, chose a larger file to loop over more events to get better statistics" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.13" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/user-guide/tutorials/ctapipe_overview.ipynb b/docs/user-guide/tutorials/ctapipe_overview.ipynb deleted file mode 100644 index 6ee1bac2b63..00000000000 --- a/docs/user-guide/tutorials/ctapipe_overview.ipynb +++ /dev/null @@ -1,1180 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "pycharm": {} - }, - "source": [ - "# Analyzing Events Using ctapipe" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {}, - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "
\n", - "\n", - "\"ctapipe\"/\n", - "\n", - "\n", - "

Initially presented @ LST Analysis Bootcamp

\n", - "\n", - "

Padova, 26.11.2018

\n", - "\n", - "

Maximilian Nöthe (@maxnoe) & Kai A. Brügge (@mackaiver)

\n", - "\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {}, - "slideshow": { - "slide_type": "skip" - } - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "\n", - "%matplotlib inline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {}, - "slideshow": { - "slide_type": "skip" - } - }, - "outputs": [], - "source": [ - "plt.rcParams[\"figure.figsize\"] = (12, 8)\n", - "plt.rcParams[\"font.size\"] = 14\n", - "plt.rcParams[\"figure.figsize\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {}, - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "

Table of Contents

\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {}, - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "## General Information" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {}, - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### Design\n", - "\n", - "* DL0 → DL3 analysis\n", - "\n", - "* Currently some R0 → DL2 code to be able to analyze simtel files\n", - "\n", - "* ctapipe is built upon the Scientific Python Stack, core dependencies are\n", - " * numpy\n", - " * scipy\n", - " * astropy\n", - " * numba" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {}, - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### Developement\n", - "\n", - "* ctapipe is developed as Open Source Software (BSD 3-Clause License) at \n", - "\n", - "* We use the \"Github-Workflow\": \n", - " * Few people (e.g. @kosack, @maxnoe) have write access to the main repository\n", - " * Contributors fork the main repository and work on branches\n", - " * Pull Requests are merged after Code Review and automatic execution of the test suite\n", - "\n", - "* Early developement stage ⇒ backwards-incompatible API changes might and will happen \n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {}, - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### What's there?\n", - "\n", - "* Reading simtel simulation files\n", - "* Simple calibration, cleaning and feature extraction functions\n", - "* Camera and Array plotting\n", - "* Coordinate frames and transformations \n", - "* Stereo-reconstruction using line intersections\n", - " \n", - " " - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {}, - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### What's still missing?\n", - "\n", - "* Good integration with machine learning techniques\n", - "* IRF calculation \n", - "* Documentation, e.g. formal definitions of coordinate frames " - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {}, - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### What can you do?\n", - "\n", - "* Report issues\n", - " * Hard to get started? Tell us where you are stuck\n", - " * Tell user stories\n", - " * Missing features\n", - "\n", - "* Start contributing\n", - " * ctapipe needs more workpower\n", - " * Implement new reconstruction features" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {}, - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "## A simple hillas analysis" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {} - }, - "source": [ - "### Reading in simtel files" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "from ctapipe.io import EventSource\n", - "from ctapipe.utils.datasets import get_dataset_path\n", - "\n", - "input_url = get_dataset_path(\"gamma_prod5.simtel.zst\")\n", - "\n", - "# EventSource() automatically detects what kind of file we are giving it,\n", - "# if already supported by ctapipe\n", - "source = EventSource(input_url, max_events=5)\n", - "\n", - "print(type(source))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "for event in source:\n", - " print(\n", - " \"Id: {}, E = {:1.3f}, Telescopes: {}\".format(\n", - " event.count, event.simulation.shower.energy, len(event.r0.tel)\n", - " )\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {} - }, - "source": [ - "Each event is a `DataContainer` holding several `Field`s of data, which can be containers or just numbers.\n", - "Let's look a one event:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "event" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "source.subarray.camera_types" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "len(event.r0.tel), len(event.r1.tel)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {} - }, - "source": [ - "### Data calibration\n", - "\n", - "The `CameraCalibrator` calibrates the event (obtaining the `dl1` images)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "from ctapipe.calib import CameraCalibrator\n", - "\n", - "calibrator = CameraCalibrator(subarray=source.subarray)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "calibrator(event)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {} - }, - "source": [ - "### Event displays\n", - "\n", - "Let's use ctapipe's plotting facilities to plot the telescope images" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "event.dl1.tel.keys()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "tel_id = 130" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "geometry = source.subarray.tel[tel_id].camera.geometry\n", - "dl1 = event.dl1.tel[tel_id]\n", - "\n", - "geometry, dl1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "dl1.image" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "from ctapipe.visualization import CameraDisplay\n", - "\n", - "display = CameraDisplay(geometry)\n", - "\n", - "# right now, there might be one image per gain channel.\n", - "# This will change as soon as\n", - "display.image = dl1.image\n", - "display.add_colorbar()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {} - }, - "source": [ - "### Image Cleaning" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "from ctapipe.image.cleaning import tailcuts_clean" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "# unoptimized cleaning levels\n", - "cleaning_level = {\n", - " \"CHEC\": (2, 4, 2),\n", - " \"LSTCam\": (3.5, 7, 2),\n", - " \"FlashCam\": (3.5, 7, 2),\n", - " \"NectarCam\": (4, 8, 2),\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "boundary, picture, min_neighbors = cleaning_level[geometry.name]\n", - "\n", - "clean = tailcuts_clean(\n", - " geometry,\n", - " dl1.image,\n", - " boundary_thresh=boundary,\n", - " picture_thresh=picture,\n", - " min_number_picture_neighbors=min_neighbors,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\n", - "\n", - "d1 = CameraDisplay(geometry, ax=ax1)\n", - "d2 = CameraDisplay(geometry, ax=ax2)\n", - "\n", - "ax1.set_title(\"Image\")\n", - "d1.image = dl1.image\n", - "d1.add_colorbar(ax=ax1)\n", - "\n", - "ax2.set_title(\"Pulse Time\")\n", - "d2.image = dl1.peak_time - np.average(dl1.peak_time, weights=dl1.image)\n", - "d2.cmap = \"RdBu_r\"\n", - "d2.add_colorbar(ax=ax2)\n", - "d2.set_limits_minmax(-20, 20)\n", - "\n", - "d1.highlight_pixels(clean, color=\"red\", linewidth=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {} - }, - "source": [ - "### Image Parameters" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "from ctapipe.image import (\n", - " hillas_parameters,\n", - " leakage_parameters,\n", - " concentration_parameters,\n", - ")\n", - "from ctapipe.image import timing_parameters\n", - "from ctapipe.image import number_of_islands\n", - "from ctapipe.image import camera_to_shower_coordinates" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "hillas = hillas_parameters(geometry[clean], dl1.image[clean])\n", - "\n", - "print(hillas)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "display = CameraDisplay(geometry)\n", - "\n", - "# set \"unclean\" pixels to 0\n", - "cleaned = dl1.image.copy()\n", - "cleaned[~clean] = 0.0\n", - "\n", - "display.image = cleaned\n", - "display.add_colorbar()\n", - "\n", - "display.overlay_moments(hillas, color=\"xkcd:red\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "timing = timing_parameters(geometry, dl1.image, dl1.peak_time, hillas, clean)\n", - "\n", - "print(timing)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "long, trans = camera_to_shower_coordinates(\n", - " geometry.pix_x, geometry.pix_y, hillas.x, hillas.y, hillas.psi\n", - ")\n", - "\n", - "plt.plot(long[clean], dl1.peak_time[clean], \"o\")\n", - "plt.plot(long[clean], timing.slope * long[clean] + timing.intercept)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "l = leakage_parameters(geometry, dl1.image, clean)\n", - "print(l)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "disp = CameraDisplay(geometry)\n", - "disp.image = dl1.image\n", - "disp.highlight_pixels(geometry.get_border_pixel_mask(1), linewidth=2, color=\"xkcd:red\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "n_islands, island_id = number_of_islands(geometry, clean)\n", - "\n", - "print(n_islands)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "conc = concentration_parameters(geometry, dl1.image, hillas)\n", - "print(conc)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {} - }, - "source": [ - "### Putting it all together / Stereo reconstruction\n", - "\n", - "\n", - "All these steps are now unified in several components configurable through the config system, mainly:\n", - "\n", - "* CameraCalibrator for DL0 → DL1 (Images)\n", - "* ImageProcessor for DL1 (Images) → DL1 (Parameters)\n", - "* ShowerProcessor for stereo reconstruction of the shower geometry\n", - "* DataWriter for writing data into HDF5\n", - "\n", - "A command line tool doing these steps and writing out data in HDF5 format is available as `ctapipe-process`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {}, - "scrolled": false - }, - "outputs": [], - "source": [ - "import astropy.units as u\n", - "from astropy.coordinates import SkyCoord, AltAz\n", - "\n", - "from ctapipe.containers import ImageParametersContainer\n", - "from ctapipe.io import EventSource\n", - "from ctapipe.utils.datasets import get_dataset_path\n", - "\n", - "from ctapipe.calib import CameraCalibrator\n", - "\n", - "from ctapipe.image import ImageProcessor\n", - "from ctapipe.reco import ShowerProcessor\n", - "\n", - "from ctapipe.io import DataWriter\n", - "\n", - "from copy import deepcopy\n", - "import tempfile\n", - "\n", - "from traitlets.config import Config\n", - "\n", - "\n", - "image_processor_config = Config(\n", - " {\n", - " \"ImageProcessor\": {\n", - " \"image_cleaner_type\": \"TailcutsImageCleaner\",\n", - " \"TailcutsImageCleaner\": {\n", - " \"picture_threshold_pe\": [\n", - " (\"type\", \"LST_LST_LSTCam\", 7.5),\n", - " (\"type\", \"MST_MST_FlashCam\", 8),\n", - " (\"type\", \"MST_MST_NectarCam\", 8),\n", - " (\"type\", \"SST_ASTRI_CHEC\", 7),\n", - " ],\n", - " \"boundary_threshold_pe\": [\n", - " (\"type\", \"LST_LST_LSTCam\", 5),\n", - " (\"type\", \"MST_MST_FlashCam\", 4),\n", - " (\"type\", \"MST_MST_NectarCam\", 4),\n", - " (\"type\", \"SST_ASTRI_CHEC\", 4),\n", - " ],\n", - " },\n", - " }\n", - " }\n", - ")\n", - "\n", - "input_url = get_dataset_path(\"gamma_prod5.simtel.zst\")\n", - "source = EventSource(input_url)\n", - "\n", - "calibrator = CameraCalibrator(subarray=source.subarray)\n", - "image_processor = ImageProcessor(\n", - " subarray=source.subarray, config=image_processor_config\n", - ")\n", - "shower_processor = ShowerProcessor(subarray=source.subarray)\n", - "horizon_frame = AltAz()\n", - "\n", - "f = tempfile.NamedTemporaryFile(suffix=\".hdf5\")\n", - "\n", - "with DataWriter(\n", - " source, output_path=f.name, overwrite=True, write_showers=True\n", - ") as writer:\n", - "\n", - " for event in source:\n", - " energy = event.simulation.shower.energy\n", - " n_telescopes_r1 = len(event.r1.tel)\n", - " event_id = event.index.event_id\n", - " print(f\"Id: {event_id}, E = {energy:1.3f}, Telescopes (R1): {n_telescopes_r1}\")\n", - "\n", - " calibrator(event)\n", - " image_processor(event)\n", - " shower_processor(event)\n", - "\n", - " stereo = event.dl2.stereo.geometry[\"HillasReconstructor\"]\n", - " if stereo.is_valid:\n", - " print(\" Alt: {:.2f}°\".format(stereo.alt.deg))\n", - " print(\" Az: {:.2f}°\".format(stereo.az.deg))\n", - " print(\" Hmax: {:.0f}\".format(stereo.h_max))\n", - " print(\" CoreX: {:.1f}\".format(stereo.core_x))\n", - " print(\" CoreY: {:.1f}\".format(stereo.core_y))\n", - " print(\" Multiplicity: {:d}\".format(len(stereo.telescopes)))\n", - "\n", - " # save a nice event for plotting later\n", - " if event.count == 3:\n", - " plotting_event = deepcopy(event)\n", - "\n", - " writer(event)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "from astropy.coordinates.angle_utilities import angular_separation\n", - "import pandas as pd\n", - "\n", - "from ctapipe.io import TableLoader\n", - "\n", - "loader = TableLoader(f.name, load_dl2=True, load_simulated=True)\n", - "\n", - "events = loader.read_subarray_events()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "theta = angular_separation(\n", - " events[\"HillasReconstructor_az\"].quantity,\n", - " events[\"HillasReconstructor_alt\"].quantity,\n", - " events[\"true_az\"].quantity,\n", - " events[\"true_alt\"].quantity,\n", - ")\n", - "\n", - "plt.hist(theta.to_value(u.deg) ** 2, bins=25, range=[0, 0.3])\n", - "plt.xlabel(r\"$\\theta² / deg²$\")\n", - "None" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {} - }, - "source": [ - "## ArrayDisplay\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "from ctapipe.visualization import ArrayDisplay\n", - "\n", - "\n", - "angle_offset = plotting_event.pointing.array_azimuth\n", - "\n", - "plotting_hillas = {\n", - " tel_id: dl1.parameters.hillas for tel_id, dl1 in plotting_event.dl1.tel.items()\n", - "}\n", - "\n", - "plotting_core = {\n", - " tel_id: dl1.parameters.core.psi for tel_id, dl1 in plotting_event.dl1.tel.items()\n", - "}\n", - "\n", - "\n", - "disp = ArrayDisplay(source.subarray)\n", - "\n", - "disp.set_line_hillas(plotting_hillas, plotting_core, 500)\n", - "\n", - "plt.scatter(\n", - " plotting_event.simulation.shower.core_x,\n", - " plotting_event.simulation.shower.core_y,\n", - " s=200,\n", - " c=\"k\",\n", - " marker=\"x\",\n", - " label=\"True Impact\",\n", - ")\n", - "plt.scatter(\n", - " plotting_event.dl2.stereo.geometry[\"HillasReconstructor\"].core_x,\n", - " plotting_event.dl2.stereo.geometry[\"HillasReconstructor\"].core_y,\n", - " s=200,\n", - " c=\"r\",\n", - " marker=\"x\",\n", - " label=\"Estimated Impact\",\n", - ")\n", - "\n", - "plt.legend()\n", - "# plt.xlim(-400, 400)\n", - "# plt.ylim(-400, 400)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {} - }, - "source": [ - "### Reading the LST dl1 data\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "loader = TableLoader(f.name, load_simulated=True, load_dl1_parameters=True)\n", - "\n", - "dl1_table = loader.read_telescope_events([\"LST_LST_LSTCam\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "plt.scatter(\n", - " np.log10(dl1_table[\"true_energy\"].quantity / u.TeV),\n", - " np.log10(dl1_table[\"hillas_intensity\"]),\n", - ")\n", - "plt.xlabel(\"log10(E / TeV)\")\n", - "plt.ylabel(\"log10(intensity)\")\n", - "None" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {} - }, - "source": [ - "## Isn't python slow?\n", - "\n", - "* Many of you might have heard: \"Python is slow\".\n", - "* That's trueish.\n", - "* All python objects are classes living on the heap, even integers.\n", - "* Looping over lots of \"primitives\" is quite slow compared to other languages.\n", - "\n", - "⇒ Vectorize as much as possible using numpy \n", - "⇒ Use existing interfaces to fast C / C++ / Fortran code \n", - "⇒ Optimize using numba \n", - "\n", - "**But: \"Premature Optimization is the root of all evil\" — Donald Knuth**\n", - "\n", - "So profile to find exactly what is slow.\n", - "\n", - "### Why use python then?\n", - "\n", - "* Python works very well as *glue* for libraries of all kinds of languages\n", - "* Python has a rich ecosystem for data science, physics, algorithms, astronomy\n", - "\n", - "### Example: Number of Islands\n", - "\n", - "Find all groups of pixels, that survived the cleaning" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "from ctapipe.image import toymodel\n", - "from ctapipe.instrument import SubarrayDescription\n", - "\n", - "\n", - "geometry = loader.subarray.tel[1].camera.geometry" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {} - }, - "source": [ - "Let's create a toy images with several islands;" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "np.random.seed(42)\n", - "\n", - "image = np.zeros(geometry.n_pixels)\n", - "\n", - "\n", - "for i in range(9):\n", - "\n", - " model = toymodel.Gaussian(\n", - " x=np.random.uniform(-0.8, 0.8) * u.m,\n", - " y=np.random.uniform(-0.8, 0.8) * u.m,\n", - " width=np.random.uniform(0.05, 0.075) * u.m,\n", - " length=np.random.uniform(0.1, 0.15) * u.m,\n", - " psi=np.random.uniform(0, 2 * np.pi) * u.rad,\n", - " )\n", - "\n", - " new_image, sig, bg = model.generate_image(\n", - " geometry, intensity=np.random.uniform(1000, 3000), nsb_level_pe=5\n", - " )\n", - " image += new_image" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "clean = tailcuts_clean(\n", - " geometry,\n", - " image,\n", - " picture_thresh=10,\n", - " boundary_thresh=5,\n", - " min_number_picture_neighbors=2,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "disp = CameraDisplay(geometry)\n", - "disp.image = image\n", - "disp.highlight_pixels(clean, color=\"xkcd:red\", linewidth=1.5)\n", - "disp.add_colorbar()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "def num_islands_python(camera, clean):\n", - " \"\"\"A breadth first search to find connected islands of neighboring pixels in the cleaning set\"\"\"\n", - "\n", - " # the camera geometry has a [n_pixel, n_pixel] boolean array\n", - " # that is True where two pixels are neighbors\n", - " neighbors = camera.neighbor_matrix\n", - "\n", - " island_ids = np.zeros(camera.n_pixels)\n", - " current_island = 0\n", - "\n", - " # a set to remember which pixels we already visited\n", - " visited = set()\n", - "\n", - " # go only through the pixels, that survived cleaning\n", - " for pix_id in np.where(clean)[0]:\n", - " if pix_id not in visited:\n", - " # remember that we already checked this pixel\n", - " visited.add(pix_id)\n", - "\n", - " # if we land in the outer loop again, we found a new island\n", - " current_island += 1\n", - " island_ids[pix_id] = current_island\n", - "\n", - " # now check all neighbors of the current pixel recursively\n", - " to_check = set(np.where(neighbors[pix_id] & clean)[0])\n", - " while to_check:\n", - " pix_id = to_check.pop()\n", - "\n", - " if pix_id not in visited:\n", - " visited.add(pix_id)\n", - " island_ids[pix_id] = current_island\n", - "\n", - " to_check.update(np.where(neighbors[pix_id] & clean)[0])\n", - "\n", - " n_islands = current_island\n", - " return n_islands, island_ids" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "n_islands, island_ids = num_islands_python(geometry, clean)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "from matplotlib.colors import ListedColormap\n", - "\n", - "cmap = plt.get_cmap(\"Paired\")\n", - "cmap = ListedColormap(cmap.colors[:n_islands])\n", - "cmap.set_under(\"k\")\n", - "\n", - "disp = CameraDisplay(geometry)\n", - "disp.image = island_ids\n", - "disp.cmap = cmap\n", - "disp.set_limits_minmax(0.5, n_islands + 0.5)\n", - "disp.add_colorbar()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "%timeit num_islands_python(geometry, clean)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "from scipy.sparse.csgraph import connected_components\n", - "\n", - "\n", - "def num_islands_scipy(geometry, clean):\n", - " neighbors = geometry.neighbor_matrix_sparse\n", - "\n", - " clean_neighbors = neighbors[clean][:, clean]\n", - " num_islands, labels = connected_components(clean_neighbors, directed=False)\n", - "\n", - " island_ids = np.zeros(geometry.n_pixels)\n", - " island_ids[clean] = labels + 1\n", - "\n", - " return num_islands, island_ids" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "n_islands_s, island_ids_s = num_islands_scipy(geometry, clean)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "disp = CameraDisplay(geometry)\n", - "disp.image = island_ids_s\n", - "disp.cmap = cmap\n", - "disp.set_limits_minmax(0.5, n_islands_s + 0.5)\n", - "disp.add_colorbar()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "pycharm": {} - }, - "outputs": [], - "source": [ - "%timeit num_islands_scipy(geometry, clean)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": {} - }, - "source": [ - "**A lot less code, and a factor 3 speed improvement**" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, current ctapipe implementation is using numba:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%timeit number_of_islands(geometry, clean)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.13" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/user-guide/tutorials/index.rst b/docs/user-guide/tutorials/index.rst deleted file mode 100644 index bfa4bb78594..00000000000 --- a/docs/user-guide/tutorials/index.rst +++ /dev/null @@ -1,14 +0,0 @@ -.. _tutorials: - -========= -Tutorials -========= - -.. toctree:: - - ctapipe_handson - ctapipe_overview - coordinates_example - raw_data_exploration - calibrated_data_exploration - theta_square diff --git a/docs/user-guide/tutorials/raw_data_exploration.ipynb b/docs/user-guide/tutorials/raw_data_exploration.ipynb deleted file mode 100644 index 1f2a23c978b..00000000000 --- a/docs/user-guide/tutorials/raw_data_exploration.ipynb +++ /dev/null @@ -1,522 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Exploring Raw Data\n", - "\n", - "Here are just some very simple examples of going through and inspecting the raw data, and making some plots using `ctapipe`.\n", - "The data explored here are *raw Monte Carlo* data, which is Data Level \"R0\" in CTA terminology (e.g. it is before any processing that would happen inside a Camera or off-line)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Setup:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from ctapipe.utils import get_dataset_path\n", - "from ctapipe.io import EventSource\n", - "from ctapipe.visualization import CameraDisplay\n", - "from ctapipe.instrument import CameraGeometry\n", - "from matplotlib import pyplot as plt\n", - "from astropy import units as u\n", - "\n", - "%matplotlib inline" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To read SimTelArray format data, ctapipe uses the `pyeventio` library (which is installed automatically along with ctapipe). The following lines however will load any data known to ctapipe (multiple `EventSources` are implemented, and chosen automatically based on the type of the input file. \n", - "\n", - "All data access first starts with an `EventSource`, and here we use a helper function `event_source` that constructs one. The resulting `source` object can be iterated over like a list of events. We also here use an `EventSeeker` which provides random-access to the source (by seeking to the given event ID or number)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "source = EventSource(get_dataset_path(\"gamma_prod5.simtel.zst\"), max_events=5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Explore the contents of an event\n", - "\n", - "note that the R0 level is the raw data that comes out of a camera, and also the lowest level of monte-carlo data. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# so we can advance through events one-by-one\n", - "event_iterator = iter(source)\n", - "\n", - "event = next(event_iterator)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "the event is just a class with a bunch of data items in it. You can see a more compact represntation via:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "event.r0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "printing the event structure, will currently print the value all items under it (so you get a lot of output if you print a high-level container):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(event.simulation.shower)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(event.r0.tel.keys())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "note that the event has 3 telescopes in it: Let's try the next one:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "event = next(event_iterator)\n", - "print(event.r0.tel.keys())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "now, we have a larger event with many telescopes... Let's look at one of them:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "teldata = event.r0.tel[26]\n", - "print(teldata)\n", - "teldata" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that some values are unit quantities (`astropy.units.Quantity`) or angular quantities (`astropy.coordinates.Angle`), and you can easily maniuplate them:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "event.simulation.shower.energy" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "event.simulation.shower.energy.to(\"GeV\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "event.simulation.shower.energy.to(\"J\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "event.simulation.shower.alt" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"Altitude in degrees:\", event.simulation.shower.alt.deg)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Look for signal pixels in a camera\n", - "again, `event.r0.tel[x]` contains a data structure for the telescope data, with some fields like `waveform`.\n", - "\n", - "Let's make a 2D plot of the sample data (sample vs pixel), so we can see if we see which pixels contain Cherenkov light signals:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.pcolormesh(teldata.waveform[0]) # note the [0] is for channel 0\n", - "plt.colorbar()\n", - "plt.xlabel(\"sample number\")\n", - "plt.ylabel(\"Pixel_id\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's zoom in to see if we can identify the pixels that have the Cherenkov signal in them" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.pcolormesh(teldata.waveform[0])\n", - "plt.colorbar()\n", - "plt.ylim(700, 750)\n", - "plt.xlabel(\"sample number\")\n", - "plt.ylabel(\"pixel_id\")\n", - "print(\"waveform[0] is an array of shape (N_pix,N_slice) =\", teldata.waveform[0].shape)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can really see that some pixels have a signal in them!\n", - "\n", - "Lets look at a 1D plot of pixel 270 in channel 0 and see the signal:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "trace = teldata.waveform[0][719]\n", - "plt.plot(trace, drawstyle=\"steps\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Great! It looks like a *standard Cherenkov signal*!\n", - "\n", - "Let's take a look at several traces to see if the peaks area aligned:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for pix_id in range(718, 723):\n", - " plt.plot(\n", - " teldata.waveform[0][pix_id], label=\"pix {}\".format(pix_id), drawstyle=\"steps\"\n", - " )\n", - "plt.legend()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Look at the time trace from a Camera Pixel\n", - "\n", - "`ctapipe.calib.camera` includes classes for doing automatic trace integration with many methods, but before using that, let's just try to do something simple!\n", - "\n", - "Let's define the integration windows first:\n", - "By eye, they seem to be reaonsable from sample 8 to 13 for signal, and 20 to 29 for pedestal (which we define as the sum of all noise: NSB + electronic)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for pix_id in range(718, 723):\n", - " plt.plot(teldata.waveform[0][pix_id], \"+-\")\n", - "plt.fill_betweenx([0, 1600], 19, 24, color=\"red\", alpha=0.3, label=\"Ped window\")\n", - "plt.fill_betweenx([0, 1600], 5, 9, color=\"green\", alpha=0.3, label=\"Signal window\")\n", - "plt.legend()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Do a very simplisitic trace analysis \n", - "Now, let's for example calculate a signal and background in a the fixed windows we defined for this single event. Note we are ignoring the fact that cameras have 2 gains, and just using a single gain (channel 0, which is the high-gain channel):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data = teldata.waveform[0]\n", - "peds = data[:, 19:24].mean(axis=1) # mean of samples 20 to 29 for all pixels\n", - "sums = data[:, 5:9].sum(axis=1) / (13 - 8) # simple sum integration" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "phist = plt.hist(peds, bins=50, range=[0, 150])\n", - "plt.title(\"Pedestal Distribution of all pixels for a single event\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "let's now take a look at the pedestal-subtracted sums and a pedestal-subtracted signal:\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.plot(sums - peds)\n", - "plt.xlabel(\"pixel id\")\n", - "plt.ylabel(\"Pedestal-subtracted Signal\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we can clearly see that the signal is centered at 0 where there is no Cherenkov light, and we can also clearly see the shower around pixel 250." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# we can also subtract the pedestals from the traces themselves, which would be needed to compare peaks properly\n", - "for ii in range(270, 280):\n", - " plt.plot(data[ii] - peds[ii], drawstyle=\"steps\", label=\"pix{}\".format(ii))\n", - "plt.legend()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Camera Displays\n", - "\n", - "It's of course much easier to see the signal if we plot it in 2D with correct pixel positions! \n", - "\n", - ">note: the instrument data model is not fully implemented, so there is not a good way to load all the camera information (right now it is hacked into the `inst` sub-container that is read from the Monte-Carlo file)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "camgeom = source.subarray.tel[24].camera.geometry" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "title = \"CT24, run {} event {} ped-sub\".format(event.index.obs_id, event.index.event_id)\n", - "disp = CameraDisplay(camgeom, title=title)\n", - "disp.image = sums - peds\n", - "disp.cmap = plt.cm.RdBu_r\n", - "disp.add_colorbar()\n", - "disp.set_limits_percent(95) # autoscale" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It looks like a nice signal! We have plotted our pedestal-subtracted trace integral, and see the shower clearly!\n", - "\n", - "Let's look at all telescopes:\n", - "\n", - "> note we plot here the raw signal, since we have not calculated the pedestals for each)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "for tel in event.r0.tel.keys():\n", - " plt.figure()\n", - " camgeom = source.subarray.tel[tel].camera.geometry\n", - " title = \"CT{}, run {} event {}\".format(\n", - " tel, event.index.obs_id, event.index.event_id\n", - " )\n", - " disp = CameraDisplay(camgeom, title=title)\n", - " disp.image = event.r0.tel[tel].waveform[0].sum(axis=1)\n", - " disp.cmap = plt.cm.RdBu_r\n", - " disp.add_colorbar()\n", - " disp.set_limits_percent(95)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## some signal processing...\n", - "\n", - "Let's try to detect the peak using the scipy.signal package:\n", - "https://docs.scipy.org/doc/scipy/reference/signal.html" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from scipy import signal\n", - "import numpy as np" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pix_ids = np.arange(len(data))\n", - "has_signal = sums > 300\n", - "\n", - "widths = np.array(\n", - " [\n", - " 8,\n", - " ]\n", - ") # peak widths to search for (let's fix it at 8 samples, about the width of the peak)\n", - "peaks = [signal.find_peaks_cwt(trace, widths) for trace in data[has_signal]]\n", - "\n", - "for p, s in zip(pix_ids[has_signal], peaks):\n", - " print(\"pix{} has peaks at sample {}\".format(p, s))\n", - " plt.plot(data[p], drawstyle=\"steps-mid\")\n", - " plt.scatter(np.array(s), data[p, s])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "clearly the signal needs to be filtered first, or an appropriate wavelet used, but the idea is nice" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.13" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/user-guide/tutorials/theta_square.ipynb b/docs/user-guide/tutorials/theta_square.ipynb deleted file mode 100644 index 6e2c7652f33..00000000000 --- a/docs/user-guide/tutorials/theta_square.ipynb +++ /dev/null @@ -1,212 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Make a theta-square plot\n", - "\n", - "This is a basic example to analyze some events and make a $\\Theta^2$ plot" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ExecuteTime": { - "end_time": "2018-06-15T12:49:35.515499Z", - "start_time": "2018-06-15T12:49:34.968051Z" - } - }, - "outputs": [], - "source": [ - "%matplotlib inline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ExecuteTime": { - "end_time": "2018-06-15T12:49:37.807612Z", - "start_time": "2018-06-15T12:49:35.520552Z" - } - }, - "outputs": [], - "source": [ - "from astropy import units as u\n", - "from astropy.coordinates.angle_utilities import angular_separation\n", - "from astropy.coordinates import SkyCoord, AltAz\n", - "\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "\n", - "from ctapipe.io import EventSource\n", - "\n", - "from ctapipe.calib import CameraCalibrator\n", - "from ctapipe.image import ImageProcessor\n", - "from ctapipe.reco import ShowerProcessor\n", - "\n", - "from tqdm.auto import tqdm" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "ExecuteTime": { - "end_time": "2018-06-15T12:49:37.887391Z", - "start_time": "2018-06-15T12:49:37.818824Z" - } - }, - "source": [ - "Get source events in MC dataset. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ExecuteTime": { - "end_time": "2018-06-15T12:49:37.887391Z", - "start_time": "2018-06-15T12:49:37.818824Z" - } - }, - "outputs": [], - "source": [ - "source = EventSource(\n", - " \"dataset://gamma_prod5.simtel.zst\",\n", - " # allowed_tels={1, 2, 3, 4},\n", - ")\n", - "\n", - "subarray = source.subarray\n", - "\n", - "calib = CameraCalibrator(subarray=subarray)\n", - "image_processor = ImageProcessor(subarray=subarray)\n", - "shower_processor = ShowerProcessor(subarray=subarray)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ExecuteTime": { - "end_time": "2018-06-15T12:49:47.500199Z", - "start_time": "2018-06-15T12:49:37.893169Z" - } - }, - "outputs": [], - "source": [ - "off_angles = []\n", - "\n", - "for event in tqdm(source):\n", - "\n", - " # calibrating the event\n", - " calib(event)\n", - " image_processor(event)\n", - " shower_processor(event)\n", - "\n", - " reco_result = event.dl2.stereo.geometry[\"HillasReconstructor\"]\n", - "\n", - " # get angular offset between reconstructed shower direction and MC\n", - " # generated shower direction\n", - " true_shower = event.simulation.shower\n", - " off_angle = angular_separation(\n", - " true_shower.az, true_shower.alt, reco_result.az, reco_result.alt\n", - " )\n", - "\n", - " # Appending all estimated off angles\n", - " off_angles.append(off_angle.to(u.deg).value)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "ExecuteTime": { - "end_time": "2018-06-15T12:49:47.507369Z", - "start_time": "2018-06-15T12:49:47.502642Z" - } - }, - "source": [ - "calculate theta square for angles which are not nan" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ExecuteTime": { - "end_time": "2018-06-15T12:49:47.507369Z", - "start_time": "2018-06-15T12:49:47.502642Z" - } - }, - "outputs": [], - "source": [ - "off_angles = np.array(off_angles)\n", - "thetasquare = off_angles[np.isfinite(off_angles)] ** 2" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "ExecuteTime": { - "end_time": "2018-06-15T12:49:48.264122Z", - "start_time": "2018-06-15T12:49:47.511172Z" - } - }, - "source": [ - "## Plot the results" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "ExecuteTime": { - "end_time": "2018-06-15T12:49:48.264122Z", - "start_time": "2018-06-15T12:49:47.511172Z" - } - }, - "outputs": [], - "source": [ - "plt.hist(thetasquare, bins=10, range=[0, 0.4])\n", - "plt.xlabel(r\"$\\theta^2$ (deg)\")\n", - "plt.ylabel(\"# of events\")\n", - "plt.show()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.13" - }, - "toc": { - "nav_menu": { - "height": "13px", - "width": "253px" - }, - "number_sections": false, - "sideBar": true, - "skip_h1_title": false, - "toc_cell": false, - "toc_position": {}, - "toc_section_display": "block", - "toc_window_display": false - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/environment.yml b/environment.yml index e0951f7fec1..11268bb1543 100644 --- a/environment.yml +++ b/environment.yml @@ -34,6 +34,7 @@ dependencies: - scipy - setuptools - sphinx + - sphinx-gallery - sphinx-automodapi - pydata-sphinx-theme - sphinx-design diff --git a/examples/examples/algorithms/README.rst b/examples/examples/algorithms/README.rst new file mode 100644 index 00000000000..4ddc214f0d3 --- /dev/null +++ b/examples/examples/algorithms/README.rst @@ -0,0 +1,4 @@ +Algorithms +========== + +.. _algorithms-examples-gallery: diff --git a/examples/examples/algorithms/convert_images_to_2d.py b/examples/examples/algorithms/convert_images_to_2d.py new file mode 100644 index 00000000000..4817580d1b1 --- /dev/null +++ b/examples/examples/algorithms/convert_images_to_2d.py @@ -0,0 +1,89 @@ +""" +Convert camera images to pixels on a s square grid +================================================== + +""" + +import astropy.units as u +import matplotlib.pyplot as plt + +from ctapipe.image.toymodel import Gaussian +from ctapipe.instrument import SubarrayDescription +from ctapipe.io import EventSource +from ctapipe.utils import get_dataset_path +from ctapipe.visualization import CameraDisplay + +# get the subarray from an example file +subarray = SubarrayDescription.read("dataset://gamma_prod5.simtel.zst") + + +###################################################################### +# Geometries with square pixels +# ----------------------------- +# +# Define a camera geometry and generate a dummy image: +# + +geom = subarray.tel[40].camera.geometry +model = Gaussian( + x=0.05 * u.m, + y=0.05 * u.m, + width=0.01 * u.m, + length=0.05 * u.m, + psi="30d", +) +_, image, _ = model.generate_image(geom, intensity=500, nsb_level_pe=3) + +CameraDisplay(geom, image) + + +###################################################################### +# The ``CameraGeometry`` has functions to convert the 1d image arrays to +# 2d arrays and back to the 1d array: +# + +image_square = geom.image_to_cartesian_representation(image) + +plt.imshow(image_square) + +image_1d = geom.image_from_cartesian_representation(image_square) + +CameraDisplay(geom, image_1d) + + +###################################################################### +# Geometries with hexagonal pixels +# -------------------------------- +# +# Define a camera geometry and generate a dummy image: +# + +geom = subarray.tel[1].camera.geometry +model = Gaussian( + x=0.5 * u.m, + y=0.5 * u.m, + width=0.1 * u.m, + length=0.2 * u.m, + psi="30d", +) +_, image, _ = model.generate_image(geom, intensity=5000) + +CameraDisplay(geom, image) + +image_square = geom.image_to_cartesian_representation(image) + + +###################################################################### +# Conversion into square geometry +# ------------------------------- +# +# Since the resulting array has square pixels, the pixel grid has to be +# rotated and distorted. This is reversible (The +# ``image_from_cartesian_representation`` method takes care of this): +# + +plt.imshow(image_square) + +image_1d = geom.image_from_cartesian_representation(image_square) + +disp = CameraDisplay(geom, image_1d) diff --git a/examples/examples/algorithms/dilate_image.py b/examples/examples/algorithms/dilate_image.py new file mode 100644 index 00000000000..9b6de53e237 --- /dev/null +++ b/examples/examples/algorithms/dilate_image.py @@ -0,0 +1,68 @@ +""" +Basic Image Cleaning and Dilation +================================= + +Here we create an example shower image, do a tail-cuts +(picture/boundary) cleaning, and then dilate the resulting cleaning mask +by several neighbor pixels + +""" + +import astropy.units as u + +# %matplotlib inline +from matplotlib import pyplot as plt + +from ctapipe.image import dilate, tailcuts_clean, toymodel +from ctapipe.instrument import SubarrayDescription +from ctapipe.visualization import CameraDisplay + +# Load a camera from an example file +subarray = SubarrayDescription.read("dataset://gamma_prod5.simtel.zst") +geom = subarray.tel[100].camera.geometry + +# Create a fake camera image to display: +model = toymodel.Gaussian( + x=0.2 * u.m, + y=0.0 * u.m, + width=0.05 * u.m, + length=0.15 * u.m, + psi="35d", +) + +image, sig, bg = model.generate_image(geom, intensity=1500, nsb_level_pe=5) + + +###################################################################### +# Apply the image cleaning: +# + +cleanmask = tailcuts_clean(geom, image, picture_thresh=10, boundary_thresh=5) +clean = image.copy() +clean[~cleanmask] = 0.0 + +disp = CameraDisplay(geom, image=image) +disp.highlight_pixels(cleanmask, color="red") + + +###################################################################### +# Now dialte the mask a few times: +# + +from ctapipe.image.cleaning import dilate + + +def show_dilate(mask, times=1): + m = mask.copy() + for ii in range(times): + m = dilate(geom, m) + CameraDisplay( + geom, image=(m.astype(int) + mask.astype(int)), title="dilate{}".format(times) + ) + + +plt.figure(figsize=(18, 3)) + +for ii in range(0, 6): + plt.subplot(1, 7, ii + 1) + show_dilate(cleanmask.copy(), times=ii) diff --git a/examples/examples/algorithms/nd_interpolation.py b/examples/examples/algorithms/nd_interpolation.py new file mode 100644 index 00000000000..19b0b06e386 --- /dev/null +++ b/examples/examples/algorithms/nd_interpolation.py @@ -0,0 +1,146 @@ +""" +Use N-dimensional Histogram functionality and Interpolation +=========================================================== + +- could be used for example to read and interpolate an lookup table or + IRF. +- In this example, we load a sample energy reconstruction lookup-table + from a FITS file +- In this case it is only in 2D cube (to keep the file size small): + ``SIZE`` vs ``IMPACT-DISTANCE``, however the same method will work + for any dimensionality + +""" + +import matplotlib.pylab as plt +import numpy as np +from astropy.io import fits +from scipy.interpolate import RegularGridInterpolator + +from ctapipe.utils import Histogram +from ctapipe.utils.datasets import get_dataset_path + +# %matplotlib inline + + +###################################################################### +# load an example datacube +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# +# (an energy table generated for a small subset of HESS simulations) to +# use as a lookup table. Here we will use the ``Histogram`` class, which +# automatically loads both the data cube and creates arrays for the +# coordinates of each axis. +# + +testfile = get_dataset_path("hess_ct001_energylut.fits.gz") +energy_hdu = fits.open(testfile)["MEAN"] +energy_table = Histogram.from_fits(energy_hdu) +print(energy_table) + + +###################################################################### +# construct an interpolator that we can use to get values at any point: +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Here we will use a ``RegularGridInterpolator``, since it is the most +# appropriate for this type of data, but others are available (see the +# SciPy documentation) +# + +centers = [energy_table.bin_centers(ii) for ii in range(energy_table.ndims)] +energy_lookup = RegularGridInterpolator( + centers, energy_table.hist, bounds_error=False, fill_value=-100 +) + + +###################################################################### +# ``energy_lookup`` is now just a continuous function of ``log(SIZE)``, +# ``DISTANCE`` in m. +# +# Now plot some curves from the interpolator. +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Note that the LUT we used is does not have very high statistics, so the +# interpolation starts to be affected by noise at the high end. In a real +# case, we would want to use a table that has been sanitized (smoothed and +# extrapolated) +# + +lsize = np.linspace(1.5, 5.0, 100) +dists = np.linspace(50, 100, 5) + +plt.figure() +plt.title("Variation of energy with size and impact distance") +plt.xlabel("SIZE (P.E.)") +plt.ylabel("ENERGY (TeV)") + +for dist in dists: + plt.plot( + 10**lsize, + 10 ** energy_lookup((lsize, dist)), + "+-", + label="DIST={:.1f} m".format(dist), + ) + +plt.legend(loc="best") + + +###################################################################### +# Using the interpolator, reinterpolate the lookup table onto an +# :math:`N \times N` grid (regardless of its original dimensions): +# + +N = 300 +xmin, xmax = energy_table.bin_centers(0)[0], energy_table.bin_centers(0)[-1] +ymin, ymax = energy_table.bin_centers(1)[0], energy_table.bin_centers(1)[-1] +xx, yy = np.linspace(xmin, xmax, N), np.linspace(ymin, ymax, N) +X, Y = np.meshgrid(xx, yy) +points = list(zip(X.ravel(), Y.ravel())) +E = energy_lookup(points).reshape((N, N)) + + +###################################################################### +# Now, let’s plot the original table and the new one (E). The color bar +# shows :math:`\log_{10}(E)` in TeV +# + +plt.figure(figsize=(12, 5)) +plt.nipy_spectral() + +# the uninterpolated table +plt.subplot(1, 2, 1) +plt.xlim(1.5, 5) +plt.ylim(0, 500) +plt.xlabel("log10(SIZE)") +plt.ylabel("Impact Dist (m)") +plt.pcolormesh( + energy_table.bin_centers(0), energy_table.bin_centers(1), energy_table.hist.T +) +plt.title("Raw table, uninterpolated {0}".format(energy_table.hist.T.shape)) +cb = plt.colorbar() +cb.set_label("$\log_{10}(E/\mathrm{TeV})$") + +# the interpolated table +plt.subplot(1, 2, 2) +plt.pcolormesh(np.linspace(xmin, xmax, N), np.linspace(ymin, ymax, N), E) +plt.xlim(1.5, 5) +plt.ylim(0, 500) +plt.xlabel("log10(SIZE)") +plt.ylabel("Impact Dist(m)") +plt.title("Interpolated to a ({0}, {0}) grid".format(N)) +cb = plt.colorbar() +cb.set_label("$\log_{10}(E/\mathrm{TeV})$") + +plt.tight_layout() +plt.show() + + +###################################################################### +# In the high-stats central region, we get a nice smooth interpolation +# function. Of course we can see that there are a few more steps to take +# before using this table: \* need to deal with cases where the table had +# low stats near the edges (smooth or extrapolate, or set bounds) \* may +# need to smooth the table even where there are sufficient stats, to avoid +# wiggles +# diff --git a/examples/examples/core/InstrumentDescription.py b/examples/examples/core/InstrumentDescription.py new file mode 100644 index 00000000000..ac4a5ccc1bd --- /dev/null +++ b/examples/examples/core/InstrumentDescription.py @@ -0,0 +1,161 @@ +""" +Working with Instrumental Descriptions +====================================== + +the instrumental description is loaded by the event source, and consists +of a hierarchy of classes in the ctapipe.instrument module, the base of +which is the ``SubarrayDescription`` + +""" + +import numpy as np + +from ctapipe.io import EventSource +from ctapipe.utils.datasets import get_dataset_path + +filename = get_dataset_path("gamma_prod5.simtel.zst") + +with EventSource(filename, max_events=1) as source: + subarray = source.subarray + + +###################################################################### +# the SubarrayDescription: +# ------------------------ +# + +subarray.info() + +subarray.to_table() + + +###################################################################### +# You can also get a table of just the ``OpticsDescriptions`` +# (``CameraGeometry`` is more complex and can’t be stored on a single +# table row, so each one can be converted to a table separately) +# + +subarray.to_table(kind="optics") + + +###################################################################### +# Make a sub-array with only SC-type telescopes: +# + +sc_tels = [tel_id for tel_id, tel in subarray.tel.items() if tel.optics.n_mirrors == 2] +newsub = subarray.select_subarray(sc_tels, name="SCTels") +newsub.info() + + +###################################################################### +# can also do this by using ``Table.group_by`` +# + + +###################################################################### +# Explore some of the details of the telescopes +# --------------------------------------------- +# + +tel = subarray.tel[1] +tel + +tel.optics.mirror_area + +tel.optics.n_mirror_tiles + +tel.optics.equivalent_focal_length + +tel.camera + +tel.camera.geometry.pix_x + +# %matplotlib inline +from ctapipe.visualization import CameraDisplay + +CameraDisplay(tel.camera.geometry) + +CameraDisplay(subarray.tel[98].camera.geometry) + + +###################################################################### +# Plot the subarray +# ----------------- +# +# We’ll make a subarray by telescope type and plot each separately, so +# they appear in different colors. We also calculate the radius using the +# mirror area (and exagerate it a bit). +# +# This is just for debugging and info, for any “real” use, a +# ``visualization.ArrayDisplay`` should be used +# + +subarray.peek() + +subarray.footprint + + +###################################################################### +# Get info about the subarray in general +# -------------------------------------- +# + +subarray.telescope_types + +subarray.camera_types + +subarray.optics_types + +from astropy.coordinates import SkyCoord + +from ctapipe.coordinates import GroundFrame + +center = SkyCoord("10.0 m", "2.0 m", "0.0 m", frame="groundframe") +coords = subarray.tel_coords # a flat list of coordinates by tel_index +coords.separation(center) + + +###################################################################### +# Telescope IDs vs Indices +# ------------------------ +# +# Note that ``subarray.tel`` is a dict mapped by ``tel_id`` (the +# indentifying number of a telescope). It is possible to have telescope +# IDs that do not start at 0, are not contiguouous (e.g. if a subarray is +# selected). Some functions and properties like ``tel_coords`` are numpy +# arrays (not dicts) so they are not mapped to the telescope ID, but +# rather the *index* within this SubarrayDescription. To convert between +# the two concepts you can do: +# + +subarray.tel_ids_to_indices([1, 5, 23]) + + +###################################################################### +# or you can get the indexing array directly in numpy or dict form: +# + +subarray.tel_index_array + +subarray.tel_index_array[[1, 5, 23]] + +subarray.tel_indices[ + 1 +] # this is a dict of tel_id -> tel_index, so we can only do one at once + +ids = subarray.get_tel_ids_for_type(subarray.telescope_types[0]) +ids + +idx = subarray.tel_ids_to_indices(ids) +idx + +subarray.tel_coords[idx] + + +###################################################################### +# so, with that method you can quickly get many telescope positions at +# once (the alternative is to use the dict ``positions`` which maps +# ``tel_id`` to a position on the ground +# + +subarray.positions[1] diff --git a/examples/examples/core/README.rst b/examples/examples/core/README.rst new file mode 100644 index 00000000000..5bcae85324a --- /dev/null +++ b/examples/examples/core/README.rst @@ -0,0 +1,4 @@ +Core Functionality +================== + +.. _core-examples-gallery: diff --git a/examples/examples/core/Tools.py b/examples/examples/core/Tools.py new file mode 100644 index 00000000000..212b860bcff --- /dev/null +++ b/examples/examples/core/Tools.py @@ -0,0 +1,357 @@ +""" +Creating command-line Tools +=========================== + +""" + +import logging +from time import sleep + +from astropy import units as u + +from ctapipe.core import Component, TelescopeComponent, Tool +from ctapipe.core.traits import ( + Dict, + Float, + FloatTelescopeParameter, + Integer, + List, + Path, + TraitError, + Unicode, + observe, +) +from ctapipe.utils import get_dataset_path + +GAMMA_FILE = get_dataset_path("gamma_prod5.simtel.zst") + + +###################################################################### +# see https://github.com/ipython/traitlets/blob/master/examples/myapp.py +# + + +###################################################################### +# Setup: +# ------ +# +# Create a few ``Component``\ s that we will use later in a ``Tool``: +# + + +class MyComponent(Component): + """A Component that does stuff""" + + value = Integer(default_value=-1, help="Value to use").tag(config=True) + + def do_thing(self): + self.log.debug("Did thing") + + +# in order to have 2 of the same components at once +class SecondaryMyComponent(MyComponent): + """A second component""" + + pass + + +class AdvancedComponent(Component): + """An advanced technique""" + + value1 = Integer(default_value=-1, help="Value to use").tag(config=True) + infile = Path( + help="input file name", + exists=None, # set to True to require existing, False for requiring non-existing + directory_ok=False, + ).tag(config=True) + outfile = Path(help="output file name", exists=False, directory_ok=False).tag( + config=True + ) + + def __init__(self, config=None, parent=None, **kwargs): + super().__init__(config=config, parent=parent, **kwargs) + # components can have sub components, but these must have + # then parent=self as argument and be assigned as member + # so the full config can be received later + self.subcompent = MyComponent(parent=self) + + @observe("outfile") + def on_outfile_changed(self, change): + self.log.warning("Outfile was changed to '{}'".format(change)) + + +class TelescopeWiseComponent(TelescopeComponent): + """a component that contains parameters that are per-telescope configurable""" + + param = FloatTelescopeParameter( + help="Something configurable with telescope patterns", default_value=5.0 + ).tag(config=True) + + +MyComponent() + +AdvancedComponent(infile="test.foo", outfile="out.foo") + + +###################################################################### +# ``TelescopeComponents`` need to have a subarray given to them in order +# to work (since they need one to turn a ``TelescopeParameter`` into a +# concrete list of values for each telescope. Here we will give a dummy +# one: +# + +from ctapipe.instrument import SubarrayDescription, TelescopeDescription + +subarray = SubarrayDescription.read(GAMMA_FILE) +subarray.info() + +TelescopeWiseComponent(subarray=subarray) + + +###################################################################### +# This TelescopeParameters can then be set using a list of patterns like: +# +# .. code:: python +# +# component.param = [ +# ("type", "LST*",3.0), +# ("type", "MST*", 2.0), +# (id, 25, 4.0) +# ] +# +# These get translated into per-telescope-id values once the subarray is +# registered. After that one acccess the per-telescope id values via: +# +# .. code:: python +# +# component.param.tel[tel_id] +# + + +###################################################################### +# Now create an executable Tool that contains the Components +# ---------------------------------------------------------- +# +# Note that all the components we wish to be configured via the tool must +# be added to the ``classes`` attribute. +# + + +class MyTool(Tool): + name = "mytool" + description = "do some things and stuff" + aliases = dict( + infile="AdvancedComponent.infile", + outfile="AdvancedComponent.outfile", + iterations="MyTool.iterations", + ) + + # Which classes are registered for configuration + classes = [ + MyComponent, + AdvancedComponent, + SecondaryMyComponent, + TelescopeWiseComponent, + ] + + # local configuration parameters + iterations = Integer(5, help="Number of times to run", allow_none=False).tag( + config=True + ) + + def setup(self): + self.comp = MyComponent(parent=self) + self.comp2 = SecondaryMyComponent(parent=self) + self.comp3 = TelescopeWiseComponent(parent=self, subarray=subarray) + self.advanced = AdvancedComponent(parent=self) + + def start(self): + self.log.info("Performing {} iterations...".format(self.iterations)) + for ii in range(self.iterations): + self.log.info("ITERATION {}".format(ii)) + self.comp.do_thing() + self.comp2.do_thing() + sleep(0.1) + + def finish(self): + self.log.warning("Shutting down.") + + +###################################################################### +# Get Help info +# ------------- +# +# The following allows you to print the help info within a Jupyter +# notebook, but this same inforamtion would be displayed if the user +# types: +# +# :: +# +# mytool --help +# + +tool = MyTool() +tool + +tool.print_help() + + +###################################################################### +# The following is equivalant to the user typing ``mytool --help-all`` +# + +tool.print_help(classes=True) + + +###################################################################### +# Run the tool +# ------------ +# +# here we pass in argv since it is a Notebook, but if argv is not +# specified it’s read from ``sys.argv``, so the following is the same as +# running: +# +# .. code:: sh +# +# mytool --log_level=INFO --infile gamma_test.simtel.gz --iterations=3 +# +# As Tools are intended to be exectutables, they are raising +# ``SystemExit`` on exit. Here, we use them to demonstrate how it would +# work, so we catch the ``SystemExit``. +# + +try: + tool.run(argv=["--infile", str(GAMMA_FILE), "--outfile", "out.csv"]) +except SystemExit as e: + assert e.code == 0, f"Tool returned with error status {e}" + +tool.log_format = "%(asctime)s : %(levelname)s [%(name)s %(funcName)s] %(message)s" + + +try: + tool.run( + argv=[ + "--log-level", + "INFO", + "--infile", + str(GAMMA_FILE), + "--outfile", + "out.csv", + "--iterations", + "3", + ] + ) +except SystemExit as e: + assert e.code == 0, f"Tool returned with error status {e}" + + +###################################################################### +# here we change the log-level to DEBUG: +# + +try: + tool.run( + argv=[ + "--log-level", + "DEBUG", + "--infile", + str(GAMMA_FILE), + "--outfile", + "out.csv", + ] + ) +except SystemExit as e: + assert e.code == 0, f"Tool returned with error status {e}" + + +###################################################################### +# you can also set parameters directly in the class, rather than using the +# argument/configfile parser. This is useful if you are calling the Tool +# from a script rather than the command-line +# + +tool.iterations = 1 +tool.log_level = 0 + +try: + tool.run(["--infile", str(GAMMA_FILE), "--outfile", "out.csv"]) +except SystemExit as e: + assert e.code == 0, f"Tool returned with error status {e}" + + +###################################################################### +# see what happens when a value is set that is not of the correct type: +# + +try: + tool.iterations = "badval" +except TraitError as E: + print("bad value:", E) +except SystemExit as e: + assert e.code == 0, f"Tool returned with error status {e}" + + +###################################################################### +# Example of what happens when you change a parameter that is being +# “observed” in a class. It’s handler is called: +# + +tool.advanced.outfile = "Another.txt" + + +###################################################################### +# we see that the handler for ``outfile`` was called, and it receive a +# change dict that shows the old and new values. +# + + +###################################################################### +# create a tool using a config file: +# + +tool2 = MyTool() + +try: + tool2.run(argv=["--config", "Tools.json"]) +except SystemExit as e: + assert e.code == 0, f"Tool returned with error status {e}" + +print(tool2.advanced.infile) + +print(tool2.config) + +tool2.is_setup + +tool3 = MyTool() + +tool3.is_setup + +tool3.initialize(argv=[]) + +tool3.is_setup + +tool3 + +tool.setup() +tool + +tool.comp2 + + +###################################################################### +# Getting the configuration of an instance +# ---------------------------------------- +# + +tool.get_current_config() + +tool.iterations = 12 +tool.get_current_config() + + +###################################################################### +# Writing a Sample Config File +# ---------------------------- +# + +print(tool.generate_config_file()) diff --git a/examples/examples/core/containers.py b/examples/examples/core/containers.py new file mode 100644 index 00000000000..de499c57daa --- /dev/null +++ b/examples/examples/core/containers.py @@ -0,0 +1,202 @@ +""" +Using Container classes +======================= + +``ctapipe.core.Container`` is the base class for all event-wise data +classes in ctapipe. It works like a object-relational mapper, in that it +defines a set of ``Fields`` along with their metadata (description, +unit, default), which can be later translated automatially into an +output table using a ``ctapipe.io.TableWriter``. + +""" + +from functools import partial + +import numpy as np +from astropy import units as u + +from ctapipe.core import Container, Field, Map + +###################################################################### +# Let’s define a few example containers with some dummy fields in them: +# + +class SubContainer(Container): + junk = Field(-1, "Some junk") + value = Field(0.0, "some value", unit=u.deg) + + +class TelContainer(Container): + # defaults should match the other requirements, e.g. the defaults + # should have the correct unit. It most often also makes sense to use + # an invalid value marker like nan for floats or -1 for positive integers + # as default + tel_id = Field(-1, "telescope ID number") + + # For mutable structures like lists, arrays or containers, use a `default_factory` function or class + # not an instance to assure each container gets a fresh instance and there is no hidden + # shared state between containers. + image = Field(default_factory=lambda: np.zeros(10), description="camera pixel data") + + +class EventContainer(Container): + event_id = Field(-1, "event id number") + + tels_with_data = Field( + default_factory=list, description="list of telescopes with data" + ) + sub = Field( + default_factory=SubContainer, description="stuff" + ) # a sub-container in the hierarchy + + # A Map is like a defaultdictionary with a specific container type as default. + # This can be used to e.g. store a container per telescope + # we use partial here to automatically get a function that creates a map with the correct container type + # as default + tel = Field(default_factory=partial(Map, TelContainer), description="telescopes") + + +###################################################################### +# Basic features +# -------------- +# + +ev = EventContainer() + + +###################################################################### +# Check that default values are automatically filled in +# + +print(ev.event_id) +print(ev.sub) +print(ev.tel) +print(ev.tel.keys()) + +# default dict access will create container: +print(ev.tel[1]) + + +###################################################################### +# print the dict representation +# + +print(ev) + + +###################################################################### +# We also get docstrings “for free” +# + +?EventContainer + +?SubContainer + + +###################################################################### +# values can be set as normal for a class: +# + +ev.event_id = 100 +ev.event_id + +ev.as_dict() # by default only shows the bare items, not sub-containers (See later) + +ev.as_dict(recursive=True) + + +###################################################################### +# and we can add a few of these to the parent container inside the tel +# dict: +# + +ev.tel[10] = TelContainer() +ev.tel[5] = TelContainer() +ev.tel[42] = TelContainer() + +# because we are using a default_factory to handle mutable defaults, the images are actually different: +ev.tel[42].image is ev.tel[32] + + +###################################################################### +# Be careful to use the ``default_factory`` mechanism for mutable fields, +# see this **negative** example: +# + +class DangerousContainer(Container): + image = Field( + np.zeros(10), + description="Attention!!!! Globally mutable shared state. Use default_factory instead", + ) + + +c1 = DangerousContainer() +c2 = DangerousContainer() + +c1.image[5] = 9999 + +print(c1.image) +print(c2.image) +print(c1.image is c2.image) + +ev.tel + + +###################################################################### +# Converion to dictionaries +# ------------------------- +# + +ev.as_dict() + +ev.as_dict(recursive=True, flatten=False) + + +###################################################################### +# for serialization to a table, we can even flatten the output into a +# single set of columns +# + +ev.as_dict(recursive=True, flatten=True) + + +###################################################################### +# Setting and clearing values +# --------------------------- +# + +ev.tel[5].image[:] = 9 +print(ev) + +ev.reset() +ev.as_dict(recursive=True) + + +###################################################################### +# look at a pre-defined Container +# ------------------------------- +# + +from ctapipe.containers import SimulatedShowerContainer + +?SimulatedShowerContainer + +shower = SimulatedShowerContainer() +shower + + +###################################################################### +# Container prefixes +# ------------------ +# +# To store the same container in the same table in a file or give more +# information, containers support setting a custom prefix: +# + +c1 = SubContainer(junk=5, value=3, prefix="foo") +c2 = SubContainer(junk=10, value=9001, prefix="bar") + +# create a common dict with data from both containers: +d = c1.as_dict(add_prefix=True) +d.update(c2.as_dict(add_prefix=True)) +d \ No newline at end of file diff --git a/examples/examples/core/provenance.py b/examples/examples/core/provenance.py new file mode 100644 index 00000000000..d3c1d812450 --- /dev/null +++ b/examples/examples/core/provenance.py @@ -0,0 +1,115 @@ +""" +Using the ctapipe Provenance service +==================================== + +The provenance functionality is used automatically when you use most of +ctapipe functionality (particularly ``ctapipe.core.Tool`` and functions +in ``ctapipe.io`` and ``ctapipe.utils``), so normally you don’t have to +work with it directly. It tracks both input and output files, as well as +details of the machine and software environment on which a Tool +executed. + +Here we show some very low-level functions of this system: + +""" + +from pprint import pprint + +from ctapipe.core import Provenance + +###################################################################### +# Activities +# ---------- +# +# The basis of Provenance is an *activity*, which is generally an +# executable or step in a script. Activities can be nested (e.g. with +# sub-activities), as shown below, but normally this is not required: +# + +p = Provenance() # note this is a singleton, so only ever one global provenence object +p.clear() +p.start_activity() +p.add_input_file("test.txt") + +p.start_activity("sub") +p.add_input_file("subinput.txt") +p.add_input_file("anothersubinput.txt") +p.add_output_file("suboutput.txt") +p.finish_activity("sub") + +p.start_activity("sub2") +p.add_input_file("sub2input.txt") +p.finish_activity("sub2") + +p.finish_activity() + +p.finished_activity_names + + +###################################################################### +# Activities have associated input and output *entities* (files or other +# objects) +# + +[(x["activity_name"], x["input"]) for x in p.provenance] + + +###################################################################### +# Activities track when they were started and finished: +# + +[(x["activity_name"], x["duration_min"]) for x in p.provenance] + + +###################################################################### +# Full provenance +# --------------- +# +# The provence object is a list of activitites, and for each lots of +# details are collected: +# + +p.provenance[0] + + +###################################################################### +# This can be better represented in JSON: +# + +print(p.as_json(indent=2)) + + +###################################################################### +# Storing provenance info in output files +# --------------------------------------- +# +# - already this can be stored in something like an HDF5 file header, +# which allows hierarchies. +# - Try to flatted the data so it can be stored in a key=value header in +# a **FITS file** (using the FITS extended keyword convention to allow +# >8 character keywords), or as a table +# + + +def flatten_dict(y): + out = {} + + def flatten(x, name=""): + if type(x) is dict: + for a in x: + flatten(x[a], name + a + ".") + elif type(x) is list: + i = 0 + for a in x: + flatten(a, name + str(i) + ".") + i += 1 + else: + out[name[:-1]] = x + + flatten(y) + return out + + +d = dict(activity=p.provenance) + +pprint(flatten_dict(d)) diff --git a/examples/examples/core/table_writer_reader.py b/examples/examples/core/table_writer_reader.py new file mode 100644 index 00000000000..6c9e9a6ab4c --- /dev/null +++ b/examples/examples/core/table_writer_reader.py @@ -0,0 +1,281 @@ +""" +Writing Containers to a tabular format +====================================== + +The ``TableWriter``/``TableReader`` sub-classes allow you to write a +``ctapipe.core.Container`` class and its meta-data to an output table. +They treat the ``Field``\ s in the ``Container`` as columns in the +output, and automatically generate a schema. Here we will go through an +example of writing out data and reading it back with *Pandas*, +*PyTables*, and a ``ctapipe.io.TableReader``: + +In this example, we will use the ``HDF5TableWriter``, which writes to +HDF5 datasets using *PyTables*. Currently this is the only implemented +TableWriter. + +""" + + +###################################################################### +# Caveats to think about: \* vector columns in Containers *can* be +# written, but some lilbraries like Pandas can not read those (so you must +# use pytables or astropy to read outputs that have vector columns) \* +# units are stored in the table metadata, but some libraries like Pandas +# ignore them and all other metadata +# + + +###################################################################### +# Create some example Containers +# ------------------------------ +# + +import numpy as np +from astropy import units as u + +from ctapipe.core import Container, Field +from ctapipe.io import HDF5TableWriter + + +class VariousTypesContainer(Container): + + a_int = Field(int, "some int value") + a_float = Field(float, "some float value with a unit", unit=u.m) + a_bool = Field(bool, "some bool value") + a_np_int = Field(np.int64, "a numpy int") + a_np_float = Field(np.float64, "a numpy float") + a_np_bool = Field(np.bool_, "np.bool") + + +###################################################################### +# let’s also make a dummy stream (generator) that will create a series of +# these containers +# + +def create_stream(n_event): + + data = VariousTypesContainer() + for i in range(n_event): + + data.a_int = int(i) + data.a_float = float(i) * u.cm # note unit conversion will happen + data.a_bool = (i % 2) == 0 + data.a_np_int = np.int64(i) + data.a_np_float = np.float64(i) + data.a_np_bool = np.bool_((i % 2) == 0) + + yield data + +for data in create_stream(2): + + for key, val in data.items(): + + print("{}: {}, type : {}".format(key, val, type(val))) + + +###################################################################### +# Writing the Data (and good practices) +# ------------------------------------- +# + + +###################################################################### +# Always use context managers with IO classes, as they will make sure the +# underlying resources are properly closed in case of errors: +# + +try: + with HDF5TableWriter("container.h5", group_name="data") as h5_table: + + for data in create_stream(10): + + h5_table.write("table", data) + 0 / 0 +except Exception as err: + print("FAILED:", err) +print("Done") + +h5_table.h5file.isopen == False + +!ls container.h5 + + +###################################################################### +# Appending new Containers +# ------------------------ +# + + +###################################################################### +# To append some new containers we need to set the writing in append mode +# by using: ‘mode=a’. But let’s now first look at what happens if we +# don’t. +# + +for i in range(2): + + with HDF5TableWriter( + "container.h5", mode="w", group_name="data_{}".format(i) + ) as h5_table: + + for data in create_stream(10): + + h5_table.write("table", data) + + print(h5_table.h5file) + +!rm -f container.h5 + + +###################################################################### +# Ok so the writer destroyed the content of the file each time it opens +# the file. Now let’s try to append some data group to it! (using +# mode=‘a’) +# + +for i in range(2): + + with HDF5TableWriter( + "container.h5", mode="a", group_name="data_{}".format(i) + ) as h5_table: + + for data in create_stream(10): + + h5_table.write("table", data) + + print(h5_table.h5file) + + +###################################################################### +# So we can append some data groups. As long as the data group_name does +# not already exists. Let’s try to overwrite the data group : data_1 +# + +try: + with HDF5TableWriter("container.h5", mode="a", group_name="data_1") as h5_table: + for data in create_stream(10): + h5_table.write("table", data) +except Exception as err: + print("Failed as expected:", err) + + +###################################################################### +# Good ! I cannot overwrite my data. +# + +print(bool(h5_table.h5file.isopen)) + + +###################################################################### +# Reading the Data +# ---------------- +# + + +###################################################################### +# Reading the whole table at once: +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# For this, you have several choices. Since we used the HDF5TableWriter in +# this example, we have at least these options avilable: +# +# - Pandas +# - PyTables +# - Astropy Table +# +# For other TableWriter implementations, others may be possible (depending +# on format) +# + + +###################################################################### +# Reading using ``ctapipe.io.read_table`` +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# This is the preferred method, it returns an astropy ``Table`` and +# supports keeping track of units, metadata and transformations. +# + +from ctapipe.io import read_table + +table = read_table("container.h5", "/data_0/table") +table[:5] + +table.meta + + +###################################################################### +# Reading with Pandas: +# ^^^^^^^^^^^^^^^^^^^^ +# +# Pandas is a convenient way to read the output. **HOWEVER BE WARNED** +# that so far Pandas does not support reading the table *meta-data* or +# *units* for colums, so that information is lost! +# + +import pandas as pd + +data = pd.read_hdf("container.h5", key="/data_0/table") +data.head() + + +###################################################################### +# Reading with PyTables +# ^^^^^^^^^^^^^^^^^^^^^ +# + +import tables + +h5 = tables.open_file("container.h5") +table = h5.root["data_0"]["table"] +table + + +###################################################################### +# note that here we can still access the metadata +# + +table.attrs + + +###################################################################### +# Reading one-row-at-a-time: +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# Rather than using the full-table methods, if you want to read it +# row-by-row (e.g. to maintain compatibility with an existing event loop), +# you can use a ``TableReader`` instance. +# +# The advantage here is that units and other metadata are retained and +# re-applied +# + +from ctapipe.io import HDF5TableReader + + +def read(mode): + + print("reading mode {}".format(mode)) + + with HDF5TableReader("container.h5", mode=mode) as h5_table: + + for group_name in ["data_0/", "data_1/"]: + + group_name = "/{}table".format(group_name) + print(group_name) + + for data in h5_table.read(group_name, VariousTypesContainer): + + print(data.as_dict()) + +read("r") + +read("r+") + +read("a") + +read("w") + diff --git a/examples/examples/index.rst b/examples/examples/index.rst new file mode 100644 index 00000000000..18496722bf3 --- /dev/null +++ b/examples/examples/index.rst @@ -0,0 +1,9 @@ +Examples gallery +================ + +The examples gallery provides an overview of different ctapipe modules and how to use them. + +.. toctree:: + visualization/index + algorithms/index + core/index diff --git a/examples/examples/visualization/README.rst b/examples/examples/visualization/README.rst new file mode 100644 index 00000000000..e4cc3341b62 --- /dev/null +++ b/examples/examples/visualization/README.rst @@ -0,0 +1,4 @@ +Visualization +============= + +.. _visualization-examples-gallery: diff --git a/examples/examples/visualization/array_display.py b/examples/examples/visualization/array_display.py new file mode 100644 index 00000000000..9679cfd9bc3 --- /dev/null +++ b/examples/examples/visualization/array_display.py @@ -0,0 +1,225 @@ +""" +Array Displays +============== + +Like ``CameraDisplays``, ctapipe provides a way to display information +related to the array on the ground: ``ArrayDisplay`` + +""" + +import matplotlib.pyplot as plt +import numpy as np +from astropy import units as u +from astropy.coordinates import SkyCoord + +from ctapipe.containers import HillasParametersContainer +from ctapipe.coordinates import EastingNorthingFrame, GroundFrame +from ctapipe.instrument import SubarrayDescription +from ctapipe.visualization import ArrayDisplay + +plt.rcParams["figure.figsize"] = (8, 6) + +tel_ids = list(range(1, 5)) + list(range(5, 20)) # just LSTs + one set of MSTs + +subarray = SubarrayDescription.read( + "dataset://gamma_20deg_0deg_run1___cta-prod5-lapalma_desert-2158m-LaPalma-dark_100evts.simtel.zst" +).select_subarray(tel_ids) + + +###################################################################### +# An array display is created for example in ``subarray.peek()``: +# + +subarray.peek() + + +###################################################################### +# However, you can make one manually with a bit more flexibility: +# + + +###################################################################### +# Constructing an ArrayDisplay +# ---------------------------- +# + +disp = ArrayDisplay(subarray) + + +###################################################################### +# You can specify the Frame you want as long as it is compatible with +# ``GroundFrame``. ``EastingNorthingFrame`` is probably the most useful. +# You can also add telescope labels +# + +disp = ArrayDisplay(subarray, frame=EastingNorthingFrame()) +disp.add_labels() + + +###################################################################### +# Using color to show information +# ------------------------------- +# +# By default the color of the telescope circles correlates to telescope +# type. However, you can use color to convey other information by setting +# the ``values`` attribute, like a trigger pattern +# + +plt.set_cmap("rainbow") # the array display will use the current colormap for values + +ad = ArrayDisplay(subarray) +ad.telescopes.set_linewidth(0) # to turn off the telescope borders + +trigger_pattern = np.zeros(subarray.n_tels) +trigger_pattern[ + [ + 1, + 4, + 5, + 6, + ] +] = 1 +ad.values = trigger_pattern # display certain telescopes in a color +ad.add_labels() + + +###################################################################### +# or for example, you could use color to represent the telescope distance +# to the impact point +# + +shower_impact = SkyCoord(200 * u.m, -200 * u.m, 0 * u.m, frame=EastingNorthingFrame()) + +plt.set_cmap("rainbow") # the array display will use the current colormap for values +ad = ArrayDisplay(subarray) +ad.telescopes.set_linewidth(0) # to turn off the telescope borders +plt.scatter(shower_impact.easting, shower_impact.northing, marker="+", s=200) + +distances = np.hypot( + subarray.tel_coords.cartesian.x - shower_impact.cartesian.x, + subarray.tel_coords.cartesian.y - shower_impact.cartesian.y, +) +ad.values = distances +plt.colorbar(ad.telescopes, label="Distance (m)") + + +###################################################################### +# Overlaying vectors +# ------------------ +# +# For plotting reconstruction quantities, it’s useful to overlay vectors +# on the telescope positions. ``ArrayDisplay`` provides functions: \* +# ``set_vector_uv`` to set by cartesian coordinates from the center of +# each telescope \* ``set_vector_rho_phi`` to set by polar coorinates from +# the center of each telescope \* ``set_vector_hillas`` to set vectors +# from a ``dict[int,HillasParameters]`` mapping tel_id (not index!) to a +# set of parameters. +# + +np.random.seed(0) +phis = np.random.uniform(0, 180.0, size=subarray.n_tels) * u.deg +rhos = np.ones(subarray.n_tels) * 200 * u.m + + +ad = ArrayDisplay(subarray, frame=EastingNorthingFrame(), tel_scale=2) +ad.set_vector_rho_phi(rho=rhos, phi=phis) + + +###################################################################### +# Overlaying Image Axes +# --------------------- +# +# For the common use case of plotting image axis on an ``ArrayDisplay``, +# the ``set_line_hillas()`` method is provided for convenience. The +# following example shows its use: +# + +import matplotlib.pyplot as plt +from astropy.coordinates import SkyCoord +from IPython import display +from matplotlib.animation import FuncAnimation + +from ctapipe.calib import CameraCalibrator +from ctapipe.image import ImageProcessor +from ctapipe.io import EventSource +from ctapipe.reco import ShowerProcessor +from ctapipe.utils import get_dataset_path +from ctapipe.visualization import ArrayDisplay + +input_url = "dataset://gamma_LaPalma_baseline_20Zd_180Az_prod3b_test.simtel.gz" + + +###################################################################### +# First, we define a function to plot the array with overlaid lines for +# the image axes +# + + +def plot_event(event, subarray, ax): + """ + Draw an ArrayDisplay with image axes and the + true and reconstructed impact position overlaid + """ + + array_pointing = SkyCoord( + az=event.pointing.array_azimuth, + alt=event.pointing.array_altitude, + frame="altaz", + ) + + angle_offset = event.pointing.array_azimuth + disp = ArrayDisplay(subarray, axes=ax) + + hillas_dict = {tid: tel.parameters.hillas for tid, tel in event.dl1.tel.items()} + core_dict = {tid: tel.parameters.core.psi for tid, tel in event.dl1.tel.items()} + + disp.set_line_hillas( + hillas_dict, + core_dict, + 500, + ) + + reco_shower = event.dl2.stereo.geometry["HillasReconstructor"] + + ax.scatter( + event.simulation.shower.core_x, + event.simulation.shower.core_y, + s=200, + c="k", + marker="x", + label="True Impact", + ) + ax.scatter( + reco_shower.core_x, + reco_shower.core_y, + s=200, + c="r", + marker="x", + label="Estimated Impact", + ) + + ax.legend() + + +###################################################################### +# Now, we can loop through some events and plot them. Here we apply +# default calibration, image processing, and reconstruction, however it is +# better to use ``ctapipe-process`` with a well-defined configuration to +# do this in reality. Note that some events will not have images bright +# enough to do parameterization or reconstruction, so they will have no +# image axis lines or no estimated impact position. +# + +fig, ax = plt.subplots(5, 3, figsize=(20, 40), constrained_layout=True) +ax = ax.ravel() + +with EventSource(input_url, max_events=15, focal_length_choice="EQUIVALENT") as source: + calib = CameraCalibrator(subarray=source.subarray) + process_images = ImageProcessor(subarray=source.subarray) + process_shower = ShowerProcessor(subarray=source.subarray) + + for i, event in enumerate(source): + calib(event) + process_images(event) + process_shower(event) + plot_event(event, source.subarray, ax=ax[i]) diff --git a/examples/examples/visualization/camera_display.py b/examples/examples/visualization/camera_display.py new file mode 100644 index 00000000000..7aac6fe6afb --- /dev/null +++ b/examples/examples/visualization/camera_display.py @@ -0,0 +1,416 @@ +""" +Displaying Camera Images +======================== + +""" + +import astropy.coordinates as c +import astropy.units as u +import matplotlib.pylab as plt +import numpy as np + +from ctapipe.coordinates import CameraFrame, EngineeringCameraFrame, TelescopeFrame +from ctapipe.image import hillas_parameters, tailcuts_clean, toymodel +from ctapipe.instrument import SubarrayDescription +from ctapipe.visualization import CameraDisplay + +###################################################################### +# First, let’s create a fake Cherenkov image from a given +# ``CameraGeometry`` and fill it with some data that we can draw later. +# + +# load an example camera geometry from a simulation file +subarray = SubarrayDescription.read("dataset://gamma_prod5.simtel.zst") +geom = subarray.tel[100].camera.geometry + +# create a fake camera image to display: +model = toymodel.Gaussian( + x=0.2 * u.m, + y=0.0 * u.m, + width=0.05 * u.m, + length=0.15 * u.m, + psi="35d", +) + +image, sig, bg = model.generate_image(geom, intensity=1500, nsb_level_pe=10) +mask = tailcuts_clean(geom, image, picture_thresh=15, boundary_thresh=5) + +geom + + +###################################################################### +# Displaying Images +# ----------------- +# +# The simplest plot is just to generate a CameraDisplay with an image in +# its constructor. A figure and axis will be created automatically +# + +CameraDisplay(geom) + + +###################################################################### +# You can also specify the initial ``image``, ``cmap`` and ``norm`` +# (colomap and normalization, see below), ``title`` to use. You can +# specify ``ax`` if you want to draw the camera on an existing +# *matplotlib* ``Axes`` object (otherwise one is created). +# +# To change other options, or to change options dynamically, you can call +# the relevant functions of the ``CameraDisplay`` object that is returned. +# For example to add a color bar, call ``add_colorbar()``, or to change +# the color scale, modify the ``cmap`` or ``norm`` properties directly. +# + + +###################################################################### +# Choosing a coordinate frame +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# The ``CameraGeometry`` object contains a ``ctapipe.coordinates.Frame`` +# used by ``CameraDisplay`` to draw the camera in the correct orientation +# and distance units. The default frame is the ``CameraFrame``, which will +# display the camera in units of *meters* and with an orientation that the +# top of the camera (when parked) is aligned to the X-axis. To show the +# camera in another orientation, it’s useful to apply a coordinate +# transform to the ``CameraGeometry`` before passing it to the +# ``CameraDisplay``. The following ``Frames`` are supported: \* +# ``EngineeringCameraFrame`` : similar to CameraFrame, but with the top of +# the camera aligned to the Y axis \* ``TelescopeFrame``: In *degrees* (on +# the sky) coordinates relative to the telescope Alt/Az pointing position, +# with the Alt axis pointing upward. +# + +fig, ax = plt.subplots(1, 3, figsize=(15, 4)) +CameraDisplay(geom, image=image, ax=ax[0]) +CameraDisplay(geom.transform_to(EngineeringCameraFrame()), image=image, ax=ax[1]) +CameraDisplay(geom.transform_to(TelescopeFrame()), image=image, ax=ax[2]) + + +###################################################################### +# Note the the name of the Frame appears in the lower-right corner +# + + +###################################################################### +# For the rest of this demo, let’s use the ``TelescopeFrame`` +# + +geom_camframe = geom +geom = geom_camframe.transform_to(EngineeringCameraFrame()) + + +###################################################################### +# Changing the color map and scale +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# CameraDisplay supports any `matplotlib color +# map `__ +# It is **highly recommended** to use a *perceptually uniform* map, unless +# you have a good reason not to. +# + +fig, ax = plt.subplots(1, 3, figsize=(15, 4)) +for ii, cmap in enumerate(["PuOr_r", "rainbow", "twilight"]): + disp = CameraDisplay(geom, image=image, ax=ax[ii], title=cmap) + disp.add_colorbar() + disp.cmap = cmap + + +###################################################################### +# By default the minimum and maximum of the color bar are set +# automatically by the data in the image. To choose fixed limits, use:\` +# + +fig, ax = plt.subplots(1, 3, figsize=(15, 4)) +for ii, minmax in enumerate([(10, 50), (-10, 10), (1, 100)]): + disp = CameraDisplay(geom, image=image, ax=ax[ii], title=minmax) + disp.add_colorbar() + disp.set_limits_minmax(minmax[0], minmax[1]) + + +###################################################################### +# Or you can set the maximum limit by percentile of the charge +# distribution: +# + +fig, ax = plt.subplots(1, 3, figsize=(15, 4)) +for ii, pct in enumerate([30, 50, 90]): + disp = CameraDisplay(geom, image=image, ax=ax[ii], title=f"{pct} %") + disp.add_colorbar() + disp.set_limits_percent(pct) + + +###################################################################### +# Using different normalizations +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# You can choose from several preset normalizations (lin, log, symlog) and +# also provide a custom normalization, for example a ``PowerNorm``: +# + +from matplotlib.colors import PowerNorm + +fig, axes = plt.subplots(2, 2, figsize=(14, 10)) +norms = ["lin", "log", "symlog", PowerNorm(0.5)] + +for norm, ax in zip(norms, axes.flatten()): + disp = CameraDisplay(geom, image=image, ax=ax) + disp.norm = norm + disp.add_colorbar() + ax.set_title(str(norm)) + +axes[1, 1].set_title("PowerNorm(0.5)") +plt.show() + + +###################################################################### +# Overlays +# -------- +# + + +###################################################################### +# Marking pixels +# ~~~~~~~~~~~~~~ +# +# here we will mark pixels in the image mask. That will change their +# outline color +# + +fig, ax = plt.subplots(1, 2, figsize=(10, 4)) +disp = CameraDisplay( + geom, image=image, cmap="gray", ax=ax[0], title="Image mask in green" +) +disp.highlight_pixels(mask, alpha=0.8, linewidth=2, color="green") + +disp = CameraDisplay( + geom, image=image, cmap="gray", ax=ax[1], title="Image mask in green (zoom)" +) +disp.highlight_pixels(mask, alpha=1, linewidth=3, color="green") + +ax[1].set_ylim(-0.5, 0.5) +ax[1].set_xlim(-0.5, 0.5) + + +###################################################################### +# Drawing a Hillas-parameter ellipse +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# For this, we will first compute some Hillas Parameters in the current +# frame: +# + +clean_image = image.copy() +clean_image[~mask] = 0 +hillas = hillas_parameters(geom, clean_image) + +plt.figure(figsize=(6, 6)) +disp = CameraDisplay(geom, image=image, cmap="gray_r") +disp.highlight_pixels(mask, alpha=0.5, color="dodgerblue") +disp.overlay_moments(hillas, color="red", linewidth=3, with_label=False) + + +###################################################################### +# Drawing a marker at a coordinate +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# This depends on the coordinate frame of the ``CameraGeometry``. Here we +# will sepcify the coordinate the ``EngineerngCameraFrame``, but if you +# have enough information to do the coordinate transform, you could use +# ``ICRS`` coordinates and overlay star positions. ``CameraDisplay`` will +# convert the coordinate you pass in to the ``Frame`` of the display +# automatically (if sufficient frame attributes are set). +# +# Note that the parameter ``keep_old`` is False by default, meaning adding +# a new point will clear the previous ones (useful for animations, but +# perhaps unexpected for a static plot). Set it to ``True`` to plot +# multiple markers. +# + +plt.figure(figsize=(6, 6)) +disp = CameraDisplay(geom, image=image, cmap="gray_r") + +coord = c.SkyCoord(x=0.5 * u.m, y=0.7 * u.m, frame=geom.frame) +coord_in_another_frame = c.SkyCoord(x=0.5 * u.m, y=0.7 * u.m, frame=CameraFrame()) +disp.overlay_coordinate(coord, markersize=20, marker="*") +disp.overlay_coordinate( + coord_in_another_frame, markersize=20, marker="*", keep_old=True +) + + +###################################################################### +# Generating an animation +# ----------------------- +# +# Here we will make an animation of fake events by re-using a single +# display (much faster than generating a new one each time) +# + +from IPython import display +from matplotlib.animation import FuncAnimation + +subarray = SubarrayDescription.read("dataset://gamma_prod5.simtel.zst") +geom = subarray.tel[1].camera.geometry + +fov = 1.0 +maxwid = 0.05 +maxlen = 0.1 + +fig, ax = plt.subplots(1, 1, figsize=(8, 6)) +disp = CameraDisplay(geom, ax=ax) # we only need one display (it can be re-used) +disp.cmap = "inferno" +disp.add_colorbar(ax=ax) + + +def update(frame): + """this function will be called for each frame of the animation""" + x, y = np.random.uniform(-fov, fov, size=2) + width = np.random.uniform(0.01, maxwid) + length = np.random.uniform(width, maxlen) + angle = np.random.uniform(0, 180) + intens = width * length * (5e4 + 1e5 * np.random.exponential(2)) + + model = toymodel.Gaussian( + x=x * u.m, + y=y * u.m, + width=width * u.m, + length=length * u.m, + psi=angle * u.deg, + ) + image, _, _ = model.generate_image( + geom, + intensity=intens, + nsb_level_pe=5, + ) + disp.image = image + + +# Create the animation and convert to a displayable video: +anim = FuncAnimation(fig, func=update, frames=10, interval=200) +plt.close(fig) # so it doesn't display here +video = anim.to_html5_video() +display.display(display.HTML(video)) + + +###################################################################### +# Using CameraDisplays interactively +# ---------------------------------- +# +# ``CameraDisplays`` can be used interactivly whe displayed in a window, +# and also when using Jupyter notebooks/lab with appropriate backends. +# +# When this is the case, the same ``CameraDisplay`` object can be re-used. +# We can’t show this here in the documentation, but creating an animation +# when in a matplotlib window is quite easy! Try this in an interactive +# ipython session: +# +# Running interactive displays in a matplotlib window +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# .. code:: sh +# +# ipython -i --maplotlib=auto +# +# That will open an ipython session with matplotlib graphics in a separate +# thread, meaning that you can type code and interact with plots +# simultaneneously. +# +# In the ipython session try running the following code and you will see +# an animation (here in the documentation, it will of course be static) +# +# First we load some real data so we have a nice image to view: +# + +import matplotlib.pyplot as plt +import numpy as np + +from ctapipe.io import EventSource +from ctapipe.visualization import CameraDisplay + +DATA = "dataset://gamma_20deg_0deg_run1___cta-prod5-lapalma_desert-2158m-LaPalma-dark_100evts.simtel.zst" + +with EventSource( + DATA, + max_events=1, + focal_length_choice="EQUIVALENT", +) as source: + event = next(iter(source)) + +tel_id = list(event.r0.tel.keys())[0] +geom = source.subarray.tel[tel_id].camera.geometry +waveform = event.r0.tel[tel_id].waveform +n_chan, n_pix, n_samp = waveform.shape + + +###################################################################### +# Running the following the will bring up a window and animate the shower +# image as a function of time. +# + +disp = CameraDisplay(geom) + +for ii in range(n_samp): + disp.image = waveform[0, :, ii] + plt.pause(0.1) # this lets matplotlib re-draw the scene + + +###################################################################### +# The output will be similar to the static animation created as follows: +# + +fig, ax = plt.subplots(1, 1) +disp = CameraDisplay(geom, ax=ax) +disp.add_colorbar() +disp.autoscale = False + + +def draw_sample(frame): + ax.set_title(f"sample: {frame}") + disp.set_limits_minmax(200, 400) + disp.image = waveform[0, :, frame] + + +anim = FuncAnimation(fig, func=draw_sample, frames=n_samp, interval=100) +plt.close(fig) # so it doesn't display here +video = anim.to_html5_video() +display.display(display.HTML(video)) + + +###################################################################### +# Making it clickable +# ~~~~~~~~~~~~~~~~~~~ +# +# Also when running in a window, you can enable the +# ``disp.enable_pixel_picker()`` option. This will then allow the user to +# click a pixel and a function will run. By default the function simply +# prints the pixel and value to stdout, however you can override the +# function ``on_pixel_clicked(pix_id)`` to do anything you want by making +# a subclass +# + + +class MyCameraDisplay(CameraDisplay): + def on_pixel_clicked(self, pix_id): + print(f"{pix_id=} has value {self.image[pix_id]:.2f}") + + +disp = MyCameraDisplay(geom, image=image) +disp.enable_pixel_picker() + + +###################################################################### +# then, when a user clicks a pixel it would print: +# +# :: +# +# pixel 5 has value 2.44 +# diff --git a/examples/tutorials/README.txt b/examples/tutorials/README.txt new file mode 100644 index 00000000000..58f643a3722 --- /dev/null +++ b/examples/tutorials/README.txt @@ -0,0 +1,6 @@ +.. _tutorials_gallery: + +Tutorials gallery +================= + +This gallery contains different tutorials of different use cases for ctapipe. diff --git a/examples/tutorials/calibrated_data_exploration.py b/examples/tutorials/calibrated_data_exploration.py new file mode 100644 index 00000000000..da235ebe648 --- /dev/null +++ b/examples/tutorials/calibrated_data_exploration.py @@ -0,0 +1,206 @@ +""" +Explore Calibrated Data +======================= + +""" + +import numpy as np +from astropy import units as u +from matplotlib import pyplot as plt + +import ctapipe +from ctapipe.instrument import CameraGeometry +from ctapipe.io import EventSeeker, EventSource +from ctapipe.utils.datasets import get_dataset_path +from ctapipe.visualization import CameraDisplay + +# %matplotlib inline +plt.style.use("ggplot") + +print(ctapipe.__version__) +print(ctapipe.__file__) + + +###################################################################### +# Let’s first open a raw event file and get an event out of it: +# + +filename = get_dataset_path("gamma_prod5.simtel.zst") +source = EventSource(filename, max_events=2) + +for event in source: + print(event.index.event_id) + +filename + +source + +event + +print(event.r1) + + +###################################################################### +# Perform basic calibration: +# -------------------------- +# +# Here we will use a ``CameraCalibrator`` which is just a simple wrapper +# that runs the three calibraraton and trace-integration phases of the +# pipeline, taking the data from levels: +# +# **R0** → **R1** → **DL0** → **DL1** +# +# You could of course do these each separately, by using the classes +# ``R1Calibrator``, ``DL0Reducer``, and ``DL1Calibrator``. Note that we +# have not specified any configuration to the ``CameraCalibrator``, so it +# will be using the default algorithms and thresholds, other than +# specifying that the product is a “HESSIOR1Calibrator” (hopefully in the +# near future that will be automatic). +# + +from ctapipe.calib import CameraCalibrator + +calib = CameraCalibrator(subarray=source.subarray) +calib(event) + + +###################################################################### +# Now the *r1*, *dl0* and *dl1* containers are filled in the event +# +# - **r1.tel[x]**: contains the “r1-calibrated” waveforms, after +# gain-selection, pedestal subtraciton, and gain-correction +# - **dl0.tel[x]**: is the same but with optional data volume reduction +# (some pixels not filled), in this case this is not performed by +# default, so it is the same as r1 +# - **dl1.tel[x]**: contains the (possibly re-calibrated) waveforms as +# dl0, but also the time-integrated *image* that has been calculated +# using a ``ImageExtractor`` (a ``NeighborPeakWindowSum`` by default) +# + +for tel_id in event.dl1.tel: + print("TEL{:03}: {}".format(tel_id, source.subarray.tel[tel_id])) + print(" - r0 wave shape : {}".format(event.r0.tel[tel_id].waveform.shape)) + print(" - r1 wave shape : {}".format(event.r1.tel[tel_id].waveform.shape)) + print(" - dl1 image shape : {}".format(event.dl1.tel[tel_id].image.shape)) + + +###################################################################### +# Some image processing: +# ---------------------- +# +# Let’s look at the image +# + +from ctapipe.visualization import CameraDisplay + +tel_id = sorted(event.r1.tel.keys())[1] +sub = source.subarray +geometry = sub.tel[tel_id].camera.geometry +image = event.dl1.tel[tel_id].image + +disp = CameraDisplay(geometry, image=image) + +from ctapipe.image import hillas_parameters, tailcuts_clean + +mask = tailcuts_clean( + geometry, + image, + picture_thresh=10, + boundary_thresh=5, + min_number_picture_neighbors=2, +) +cleaned = image.copy() +cleaned[~mask] = 0 +disp = CameraDisplay(geometry, image=cleaned) + +params = hillas_parameters(geometry, cleaned) +print(params) +params + +params = hillas_parameters(geometry, cleaned) + +plt.figure(figsize=(10, 10)) +disp = CameraDisplay(geometry, image=image) +disp.add_colorbar() +disp.overlay_moments(params, color="red", lw=3) +disp.highlight_pixels(mask, color="white", alpha=0.3, linewidth=2) + +plt.xlim(params.x.to_value(u.m) - 0.5, params.x.to_value(u.m) + 0.5) +plt.ylim(params.y.to_value(u.m) - 0.5, params.y.to_value(u.m) + 0.5) + +source.metadata + + +###################################################################### +# More complex image processing: +# ------------------------------ +# +# Let’s now explore how stereo reconstruction works. +# +# first, look at a summed image from multiple telescopes +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# For this, we want to use a ``CameraDisplay`` again, but since we can’t +# sum and display images with different cameras, we’ll just sub-select +# images from a particular camera type +# +# These are the telescopes that are in this event: +# + +tels_in_event = set( + event.dl1.tel.keys() +) # use a set here, so we can intersect it later +tels_in_event + +cam_ids = set(sub.get_tel_ids_for_type("MST_MST_NectarCam")) +cam_ids + +cams_in_event = tels_in_event.intersection(cam_ids) +first_tel_id = list(cams_in_event)[0] +tel = sub.tel[first_tel_id] +print("{}s in event: {}".format(tel, cams_in_event)) + + +###################################################################### +# Now, let’s sum those images: +# + +image_sum = np.zeros_like( + tel.camera.geometry.pix_x.value +) # just make an array of 0's in the same shape as the camera + +for tel_id in cams_in_event: + image_sum += event.dl1.tel[tel_id].image + + +###################################################################### +# And finally display the sum of those images +# + +plt.figure(figsize=(8, 8)) + +disp = CameraDisplay(tel.camera.geometry, image=image_sum) +disp.overlay_moments(params, with_label=False) +plt.title("Sum of {}x {}".format(len(cams_in_event), tel)) + + +###################################################################### +# let’s also show which telescopes those were. Note that currently +# ArrayDisplay’s value field is a vector by ``tel_index``, not ``tel_id``, +# so we have to convert to a tel_index. (this may change in a future +# version to be more user-friendly) +# + +from ctapipe.visualization import ArrayDisplay + +nectarcam_subarray = sub.select_subarray(cam_ids, name="NectarCam") + +hit_pattern = np.zeros(shape=nectarcam_subarray.n_tels) +hit_pattern[[nectarcam_subarray.tel_indices[x] for x in cams_in_event]] = 100 + +plt.set_cmap(plt.cm.Accent) +plt.figure(figsize=(8, 8)) + +ad = ArrayDisplay(nectarcam_subarray) +ad.values = hit_pattern +ad.add_labels() diff --git a/examples/tutorials/coordinates_example.py b/examples/tutorials/coordinates_example.py new file mode 100644 index 00000000000..87cac268fa4 --- /dev/null +++ b/examples/tutorials/coordinates_example.py @@ -0,0 +1,414 @@ +""" +Coordinates usage in ctapipe +============================ + +""" + +import copy + +import astropy.units as u +import matplotlib.pyplot as plt +import numpy as np +from astropy.coordinates import AltAz, SkyCoord + +from ctapipe.calib import CameraCalibrator +from ctapipe.coordinates import ( + CameraFrame, + GroundFrame, + NominalFrame, + TelescopeFrame, + TiltedGroundFrame, +) +from ctapipe.io import EventSource +from ctapipe.utils import get_dataset_path +from ctapipe.visualization import ArrayDisplay + +# %matplotlib inline + + +# make plots and fonts larger +plt.rcParams["figure.figsize"] = (12, 8) +plt.rcParams["font.size"] = 16 + + +###################################################################### +# Open test dataset +# ----------------- +# + +filename = get_dataset_path("gamma_prod5.simtel.zst") +source = EventSource(filename) + +events = [copy.deepcopy(event) for event in source] +event = events[4] + +layout = set(source.subarray.tel_ids) + + +###################################################################### +# Choose event with LST +# ~~~~~~~~~~~~~~~~~~~~~ +# + + +###################################################################### +# This ensures that the telescope is not “parked” (as it would be in an +# event where it is not triggered) but is actually pointing to a source. +# + +print(f"Telescope with data: {event.r1.tel.keys()}") +tel_id = 3 + + +###################################################################### +# AltAz +# ----- +# +# See `Astropy Docs on +# AltAz `__. +# +# Pointing direction of telescopes or the origin of a simulated shower are +# described in the ``AltAz`` frame. This is a local, angular coordinate +# frame, with angles ``altitude`` and ``azimuth``. Altitude is the +# measured from the Horizon (0°) to the Zenith (90°). For the azimuth, +# there are different conventions. In Astropy und thus ctapipe, Azimuth is +# oriented East of North (i.e., N=0°, E=90°). +# + +from astropy.coordinates import EarthLocation +from astropy.time import Time + +obstime = Time("2013-11-01T03:00") +location = EarthLocation.of_site("Roque de los Muchachos") + +altaz = AltAz(location=location, obstime=obstime) + +array_pointing = SkyCoord( + alt=event.pointing.array_azimuth, + az=event.pointing.array_altitude, + frame=altaz, +) + +print(array_pointing) + + +###################################################################### +# CameraFrame +# ----------- +# +# Camera coordinate frame. +# +# The camera frame is a 2d cartesian frame, describing position of objects +# in the focal plane of the telescope. +# +# The frame is defined as in H.E.S.S., starting at the horizon, the +# telescope is pointed to magnetic north in azimuth and then up to zenith. +# +# Now, x points north and y points west, so in this orientation, the +# camera coordinates line up with the CORSIKA ground coordinate system. +# +# MAGIC and FACT use a different camera coordinate system: Standing at the +# dish, looking at the camera, x points right, y points up. To transform +# MAGIC/FACT to ctapipe, do x’ = -y, y’ = -x. +# +# **Typical usage**: Position of pixels in the focal plane. +# + +geometry = source.subarray.tel[tel_id].camera.geometry +pix_x = geometry.pix_x +pix_y = geometry.pix_y +focal_length = source.subarray.tel[tel_id].optics.equivalent_focal_length + +telescope_pointing = SkyCoord( + alt=event.pointing.tel[tel_id].altitude, + az=event.pointing.tel[tel_id].azimuth, + frame=altaz, +) + +camera_frame = CameraFrame( + focal_length=focal_length, + rotation=0 * u.deg, + telescope_pointing=telescope_pointing, +) + +cam_coords = SkyCoord(x=pix_x, y=pix_y, frame=camera_frame) + +print(cam_coords) + +plt.scatter(cam_coords.x, cam_coords.y) +plt.title(f"Camera type: {geometry.name}") +plt.xlabel(f"x / {cam_coords.x.unit}") +plt.ylabel(f"y / {cam_coords.y.unit}") +plt.axis("square") + + +###################################################################### +# The implementation of the coordinate system with astropy makes it easier +# to use time of the observation and location of the observing site, to +# understand, for example which stars are visible during a certain night +# and how they might be visible in the camera. +# + +from ctapipe.instrument import SubarrayDescription +from ctapipe.visualization import CameraDisplay + +location = EarthLocation.of_site("Roque de los Muchachos") +obstime = Time("2018-11-01T04:00") + +crab = SkyCoord.from_name("crab nebula") + +altaz = AltAz(location=location, obstime=obstime) + +pointing = crab.transform_to(altaz) + +camera_frame = CameraFrame( + telescope_pointing=pointing, + focal_length=focal_length, + obstime=obstime, + location=location, +) + + +subarray = SubarrayDescription.read("dataset://gamma_prod5.simtel.zst") +cam = subarray.tel[1].camera.geometry +fig, ax = plt.subplots() +display = CameraDisplay(cam, ax=ax) + +ax.set_title( + f"La Palma, {obstime}, az={pointing.az.deg:.1f}°, zenith={pointing.zen.deg:.1f}°, camera={geometry.name}" +) + +for i, name in enumerate(["crab nebula", "o tau", "zet tau"]): + star = SkyCoord.from_name(name) + star_cam = star.transform_to(camera_frame) + + x = star_cam.x.to_value(u.m) + y = star_cam.y.to_value(u.m) + + ax.plot(x, y, marker="*", color=f"C{i}") + ax.annotate( + name, + xy=(x, y), + xytext=(5, 5), + textcoords="offset points", + color=f"C{i}", + ) + +plt.show() + + +###################################################################### +# TelescopeFrame +# -------------- +# +# Telescope coordinate frame. A ``Frame`` using a +# ``UnitSphericalRepresentation``. +# +# This is basically the same as a ``HorizonCoordinate``, but the origin is +# at the telescope’s pointing direction. This is what astropy calls a +# ``SkyOffsetFrame``. +# +# The axis of the telescope frame, ``fov_lon`` and ``fov_lat``, are +# aligned with the horizontal system’s azimuth and altitude respectively. +# +# Pointing corrections should applied to the transformation between this +# frame and the camera frame. +# + +telescope_frame = TelescopeFrame( + telescope_pointing=pointing, + obstime=pointing.obstime, + location=pointing.location, +) +telescope_coords = cam_coords.transform_to(telescope_frame) + +wrap_angle = telescope_pointing.az + 180 * u.deg + +plt.axis("equal") +plt.scatter( + telescope_coords.fov_lon.deg, telescope_coords.fov_lat.deg, alpha=0.2, color="gray" +) + + +for i, name in enumerate(["crab nebula", "o tau", "zet tau"]): + star = SkyCoord.from_name(name) + star_tel = star.transform_to(telescope_frame) + + plt.plot(star_tel.fov_lon.deg, star_tel.fov_lat.deg, "*", ms=10) + plt.annotate( + name, + xy=(star_tel.fov_lon.deg, star_tel.fov_lat.deg), + xytext=(5, 5), + textcoords="offset points", + color=f"C{i}", + ) + +plt.xlabel("fov_lon / {}".format(telescope_coords.altaz.az.unit)) +plt.ylabel("fov_lat / {}".format(telescope_coords.altaz.alt.unit)) + + +###################################################################### +# NominalFrame +# ------------ +# +# Nominal coordinate frame. A Frame using a +# ``UnitSphericalRepresentation``. This is basically the same as a +# ``HorizonCoordinate``, but the origin is at an arbitray position in the +# sky. This is what astropy calls a ``SkyOffsetFrame`` If the telescopes +# are in divergent pointing, this ``Frame`` can be used to transform to a +# common system. - 2D reconstruction (``HillasIntersector``) is performed +# in this frame - 3D reconstruction (``HillasReconstructor``) doesn’t need +# this frame +# + + +###################################################################### +# Let’s play a bit with 3 LSTs with divergent pointing +# + +location = EarthLocation.of_site("Roque de los Muchachos") +obstime = Time("2018-11-01T02:00") +altaz = AltAz(location=location, obstime=obstime) + +crab = SkyCoord.from_name("crab nebula") + +# let's observe crab +array_pointing = crab.transform_to(altaz) + + +# let the telescopes point to different positions +alt_offsets = u.Quantity([1, -1, -1], u.deg) +az_offsets = u.Quantity([0, -2, +2], u.deg) + + +tel_pointings = SkyCoord( + alt=array_pointing.alt + alt_offsets, + az=array_pointing.az + az_offsets, + frame=altaz, +) + +camera_frames = CameraFrame( + telescope_pointing=tel_pointings, # multiple pointings, so we get multiple frames + focal_length=focal_length, + obstime=obstime, + location=location, +) + +nom_frame = NominalFrame(origin=array_pointing, obstime=obstime, location=location) + +fig, ax = plt.subplots(figsize=(15, 10)) +ax.set_aspect(1) + +for i in range(3): + cam_coord = SkyCoord(x=pix_x, y=pix_y, frame=camera_frames[i]) + nom_coord = cam_coord.transform_to(nom_frame) + + ax.scatter( + x=nom_coord.fov_lon.deg, + y=nom_coord.fov_lat.deg, + label=f"Telescope {i + 1}", + s=30, + alpha=0.15, + ) + + +for i, name in enumerate(["Crab", "o tau", "zet tau"]): + s = SkyCoord.from_name(name) + s_nom = s.transform_to(nom_frame) + ax.plot( + s_nom.fov_lon.deg, + s_nom.fov_lat.deg, + "*", + ms=10, + ) + ax.annotate( + name, + xy=(s_nom.fov_lon.deg, s_nom.fov_lat.deg), + xytext=(5, 5), + textcoords="offset points", + color=f"C{i}", + ) + + +ax.set_xlabel(f"fov_lon / deg") +ax.set_ylabel(f"fov_lat / deg") + +ax.legend() +plt.show() + + +###################################################################### +# GroundFrame +# ----------- +# +# Ground coordinate frame. The ground coordinate frame is a simple +# cartesian frame describing the 3 dimensional position of objects +# compared to the array ground level in relation to the nomial centre of +# the array. Typically this frame will be used for describing the position +# on telescopes and equipment +# +# **Typical usage**: positions of telescopes on the ground (x, y, z) +# + +source.subarray.peek() + + +###################################################################### +# In case a layout is selected, the following line will produce a +# different output from the picture above. +# + +source.subarray.select_subarray(layout, name="Prod3b layout").peek() + + +###################################################################### +# .. figure:: ground_frame.png +# :alt: Ground Frame +# +# Ground Frame +# + + +###################################################################### +# In this image all the telescope from the ``gamma_test.simtel.gz`` file +# are plotted as spheres in the GroundFrame. +# + + +###################################################################### +# TiltedGroundFrame +# ----------------- +# + + +###################################################################### +# Tilted ground coordinate frame. +# +# The tilted ground coordinate frame is a cartesian system describing the +# 2 dimensional projected positions of objects in a tilted plane described +# by pointing_direction. The plane is rotated along the z_axis by the +# azimuth of the ``pointing_direction`` and then it is inclined with an +# angle equal to the zenith angle of the ``pointing_direction``. +# +# This frame is used for the reconstruction of the shower core position. +# + + +###################################################################### +# .. figure:: tilted_ground_frame.png +# :alt: Tilted Ground Frame +# +# Tilted Ground Frame +# + + +###################################################################### +# This image picture both the telescopes in the GroundFrame (red) and in +# the TiltedGroundFrame (green) are displayed: in this case since the +# azimuth of the ``pointing_direction`` is 0 degrees, then the plane is +# just tilted according to the zenith angle. +# +# For playing with these and with more 3D models of the telescopes +# themselves, have a look at the +# `CREED_VTK `__ library. +# diff --git a/examples/tutorials/ctapipe_handson.py b/examples/tutorials/ctapipe_handson.py new file mode 100644 index 00000000000..a09e5d55051 --- /dev/null +++ b/examples/tutorials/ctapipe_handson.py @@ -0,0 +1,254 @@ +""" +Getting Started with ctapipe +============================ + +This hands-on was presented at the Paris CTA Consoritum meeting (K. +Kosack) + +""" + + +###################################################################### +# Part 1: load and loop over data +# ------------------------------- +# + +import numpy as np +from matplotlib import pyplot as plt + +from ctapipe import utils +from ctapipe.io import EventSource + +# %matplotlib inline + +path = utils.get_dataset_path("gamma_prod5.simtel.zst") + +source = EventSource(path, max_events=5) + +for event in source: + print(event.count, event.index.event_id, event.simulation.shower.energy) + +event + +event.r1 + +for event in EventSource(path, max_events=5): + print(event.count, event.r1.tel.keys()) + +event.r0.tel[3] + +r0tel = event.r0.tel[3] + +r0tel.waveform + +r0tel.waveform.shape + + +###################################################################### +# note that this is (:math:`N_{channels}`, :math:`N_{pixels}`, +# :math:`N_{samples}`) +# + +plt.pcolormesh(r0tel.waveform[0]) + +brightest_pixel = np.argmax(r0tel.waveform[0].sum(axis=1)) +print(f"pixel {brightest_pixel} has sum {r0tel.waveform[0,1535].sum()}") + +plt.plot(r0tel.waveform[0,brightest_pixel], label="channel 0 (high-gain)") +plt.plot(r0tel.waveform[1,brightest_pixel], label="channel 1 (low-gain)") +plt.legend() + +from ipywidgets import interact + + +@interact +def view_waveform(chan=0, pix_id=brightest_pixel): + plt.plot(r0tel.waveform[chan, pix_id]) + + +###################################################################### +# try making this compare 2 waveforms +# + + +###################################################################### +# Part 2: Explore the instrument description +# ------------------------------------------ +# +# This is all well and good, but we don’t really know what camera or +# telescope this is… how do we get instrumental description info? +# +# Currently this is returned *inside* the event (it will soon change to be +# separate in next version or so) +# + +subarray = source.subarray + +subarray + +subarray.peek() + +subarray.to_table() + +subarray.tel[2] + +subarray.tel[2].camera + +subarray.tel[2].optics + +tel = subarray.tel[2] + +tel.camera + +tel.optics + +tel.camera.geometry.pix_x + +tel.camera.geometry.to_table() + +tel.optics.mirror_area + +from ctapipe.visualization import CameraDisplay + +disp = CameraDisplay(tel.camera.geometry) + +disp = CameraDisplay(tel.camera.geometry) +disp.image = r0tel.waveform[0,:,10] # display channel 0, sample 0 (try others like 10) + + +###################################################################### +# \*\* aside: \*\* show demo using a CameraDisplay in interactive mode in +# ipython rather than notebook +# + + +###################################################################### +# Part 3: Apply some calibration and trace integration +# ---------------------------------------------------- +# + +from ctapipe.calib import CameraCalibrator + +calib = CameraCalibrator(subarray=subarray) + +for event in EventSource(path, max_events=5): + calib(event) # fills in r1, dl0, and dl1 + print(event.dl1.tel.keys()) + +event.dl1.tel[3] + +dl1tel = event.dl1.tel[3] + +dl1tel.image.shape # note this will be gain-selected in next version, so will be just 1D array of 1855 + +dl1tel.peak_time + +CameraDisplay(tel.camera.geometry, image=dl1tel.image) + +CameraDisplay(tel.camera.geometry, image=dl1tel.peak_time) + + +###################################################################### +# Now for Hillas Parameters +# + +from ctapipe.image import hillas_parameters, tailcuts_clean + +image = dl1tel.image +mask = tailcuts_clean(tel.camera.geometry, image, picture_thresh=10, boundary_thresh=5) +mask + +CameraDisplay(tel.camera.geometry, image=mask) + +cleaned = image.copy() +cleaned[~mask] = 0 + +disp = CameraDisplay(tel.camera.geometry, image=cleaned) +disp.cmap = plt.cm.coolwarm +disp.add_colorbar() +plt.xlim(0.5, 1.0) +plt.ylim(-1.0, 0.0) + +params = hillas_parameters(tel.camera.geometry, cleaned) +print(params) + +disp = CameraDisplay(tel.camera.geometry, image=cleaned) +disp.cmap = plt.cm.coolwarm +disp.add_colorbar() +plt.xlim(0.5, 1.0) +plt.ylim(-1.0, 0.0) +disp.overlay_moments(params, color='white', lw=2) + + +###################################################################### +# Part 4: Let’s put it all together: +# ---------------------------------- +# +# - loop over events, selecting only telescopes of the same type +# (e.g. LST:LSTCam) +# - for each event, apply calibration/trace integration +# - calculate Hillas parameters +# - write out all hillas paremeters to a file that can be loaded with +# Pandas +# + + +###################################################################### +# first let’s select only those telescopes with LST:LSTCam +# + +subarray.telescope_types + +subarray.get_tel_ids_for_type("LST_LST_LSTCam") + + +###################################################################### +# Now let’s write out program +# + +data = utils.get_dataset_path("gamma_prod5.simtel.zst") +source = EventSource(data) # remove the max_events limit to get more stats + +for event in source: + calib(event) + + for tel_id, tel_data in event.dl1.tel.items(): + tel = source.subarray.tel[tel_id] + mask = tailcuts_clean(tel.camera.geometry, tel_data.image) + if np.count_nonzero(mask) > 0: + params = hillas_parameters(tel.camera.geometry[mask], tel_data.image[mask]) + +from ctapipe.io import HDF5TableWriter + +with HDF5TableWriter(filename='hillas.h5', group_name='dl1', overwrite=True) as writer: + + source = EventSource(data, allowed_tels=[1,2,3,4], max_events=10) + for event in source: + calib(event) + + for tel_id, tel_data in event.dl1.tel.items(): + tel = source.subarray.tel[tel_id] + mask = tailcuts_clean(tel.camera.geometry, tel_data.image) + params = hillas_parameters(tel.camera.geometry[mask], tel_data.image[mask]) + writer.write("hillas", params) + + +###################################################################### +# We can now load in the file we created and plot it +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + +!ls *.h5 + +import pandas as pd + +hillas = pd.read_hdf("hillas.h5", key='/dl1/hillas') +hillas + +_ = hillas.hist(figsize=(8,8)) + + +###################################################################### +# If you do this yourself, chose a larger file to loop over more events to +# get better statistics +# \ No newline at end of file diff --git a/examples/tutorials/ctapipe_overview.py b/examples/tutorials/ctapipe_overview.py new file mode 100644 index 00000000000..7266aac8f0a --- /dev/null +++ b/examples/tutorials/ctapipe_overview.py @@ -0,0 +1,675 @@ +""" +Analyzing Events Using ctapipe +============================== + +""" + + +###################################################################### +# .. container:: +# +# .. raw:: html +# +#

+# +# Initially presented @ LST Analysis Bootcamp +# +# .. raw:: html +# +#

+# +# .. raw:: html +# +#

+# +# Padova, 26.11.2018 +# +# .. raw:: html +# +#

+# +# .. raw:: html +# +#

+# +# Maximilian Nöthe (@maxnoe) & Kai A. Brügge (@mackaiver) +# +# .. raw:: html +# +#

+# + +import matplotlib.pyplot as plt +import numpy as np + +# %matplotlib inline + +plt.rcParams["figure.figsize"] = (12, 8) +plt.rcParams["font.size"] = 14 +plt.rcParams["figure.figsize"] + + +###################################################################### +# .. raw:: html +# +#

+# +# Table of Contents +# +# .. raw:: html +# +#

+# +# .. container:: +# :name: toc +# + + +###################################################################### +# General Information +# ------------------- +# + + +###################################################################### +# Design +# ~~~~~~ +# +# - DL0 → DL3 analysis +# +# - Currently some R0 → DL2 code to be able to analyze simtel files +# +# - ctapipe is built upon the Scientific Python Stack, core dependencies +# are +# +# - numpy +# - scipy +# - astropy +# - numba +# + + +###################################################################### +# Developement +# ~~~~~~~~~~~~ +# +# - ctapipe is developed as Open Source Software (BSD 3-Clause License) +# at https://github.com/cta-observatory/ctapipe +# +# - We use the “Github-Workflow”: +# +# - Few people (e.g. @kosack, @maxnoe) have write access to the main +# repository +# - Contributors fork the main repository and work on branches +# - Pull Requests are merged after Code Review and automatic execution +# of the test suite +# +# - Early developement stage ⇒ backwards-incompatible API changes might +# and will happen +# + + +###################################################################### +# What’s there? +# ~~~~~~~~~~~~~ +# +# - Reading simtel simulation files +# - Simple calibration, cleaning and feature extraction functions +# - Camera and Array plotting +# - Coordinate frames and transformations +# - Stereo-reconstruction using line intersections +# + + +###################################################################### +# What’s still missing? +# ~~~~~~~~~~~~~~~~~~~~~ +# +# - Good integration with machine learning techniques +# - IRF calculation +# - Documentation, e.g. formal definitions of coordinate frames +# + + +###################################################################### +# What can you do? +# ~~~~~~~~~~~~~~~~ +# +# - Report issues +# +# - Hard to get started? Tell us where you are stuck +# - Tell user stories +# - Missing features +# +# - Start contributing +# +# - ctapipe needs more workpower +# - Implement new reconstruction features +# + + +###################################################################### +# A simple hillas analysis +# ------------------------ +# + + +###################################################################### +# Reading in simtel files +# ~~~~~~~~~~~~~~~~~~~~~~~ +# + +from ctapipe.io import EventSource +from ctapipe.utils.datasets import get_dataset_path + +input_url = get_dataset_path("gamma_prod5.simtel.zst") + +# EventSource() automatically detects what kind of file we are giving it, +# if already supported by ctapipe +source = EventSource(input_url, max_events=5) + +print(type(source)) + +for event in source: + print( + "Id: {}, E = {:1.3f}, Telescopes: {}".format( + event.count, event.simulation.shower.energy, len(event.r0.tel) + ) + ) + + +###################################################################### +# Each event is a ``DataContainer`` holding several ``Field``\ s of data, +# which can be containers or just numbers. Let’s look a one event: +# + +event + +source.subarray.camera_types + +len(event.r0.tel), len(event.r1.tel) + + +###################################################################### +# Data calibration +# ~~~~~~~~~~~~~~~~ +# +# The ``CameraCalibrator`` calibrates the event (obtaining the ``dl1`` +# images). +# + +from ctapipe.calib import CameraCalibrator + +calibrator = CameraCalibrator(subarray=source.subarray) + +calibrator(event) + + +###################################################################### +# Event displays +# ~~~~~~~~~~~~~~ +# +# Let’s use ctapipe’s plotting facilities to plot the telescope images +# + +event.dl1.tel.keys() + +tel_id = 130 + +geometry = source.subarray.tel[tel_id].camera.geometry +dl1 = event.dl1.tel[tel_id] + +geometry, dl1 + +dl1.image + +from ctapipe.visualization import CameraDisplay + +display = CameraDisplay(geometry) + +# right now, there might be one image per gain channel. +# This will change as soon as +display.image = dl1.image +display.add_colorbar() + + +###################################################################### +# Image Cleaning +# ~~~~~~~~~~~~~~ +# + +from ctapipe.image.cleaning import tailcuts_clean + +# unoptimized cleaning levels +cleaning_level = { + "CHEC": (2, 4, 2), + "LSTCam": (3.5, 7, 2), + "FlashCam": (3.5, 7, 2), + "NectarCam": (4, 8, 2), +} + +boundary, picture, min_neighbors = cleaning_level[geometry.name] + +clean = tailcuts_clean( + geometry, + dl1.image, + boundary_thresh=boundary, + picture_thresh=picture, + min_number_picture_neighbors=min_neighbors, +) + +fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5)) + +d1 = CameraDisplay(geometry, ax=ax1) +d2 = CameraDisplay(geometry, ax=ax2) + +ax1.set_title("Image") +d1.image = dl1.image +d1.add_colorbar(ax=ax1) + +ax2.set_title("Pulse Time") +d2.image = dl1.peak_time - np.average(dl1.peak_time, weights=dl1.image) +d2.cmap = "RdBu_r" +d2.add_colorbar(ax=ax2) +d2.set_limits_minmax(-20, 20) + +d1.highlight_pixels(clean, color="red", linewidth=1) + + +###################################################################### +# Image Parameters +# ~~~~~~~~~~~~~~~~ +# + +from ctapipe.image import ( + camera_to_shower_coordinates, + concentration_parameters, + hillas_parameters, + leakage_parameters, + number_of_islands, + timing_parameters, +) + +hillas = hillas_parameters(geometry[clean], dl1.image[clean]) + +print(hillas) + +display = CameraDisplay(geometry) + +# set "unclean" pixels to 0 +cleaned = dl1.image.copy() +cleaned[~clean] = 0.0 + +display.image = cleaned +display.add_colorbar() + +display.overlay_moments(hillas, color="xkcd:red") + +timing = timing_parameters(geometry, dl1.image, dl1.peak_time, hillas, clean) + +print(timing) + +long, trans = camera_to_shower_coordinates( + geometry.pix_x, geometry.pix_y, hillas.x, hillas.y, hillas.psi +) + +plt.plot(long[clean], dl1.peak_time[clean], "o") +plt.plot(long[clean], timing.slope * long[clean] + timing.intercept) + +l = leakage_parameters(geometry, dl1.image, clean) +print(l) + +disp = CameraDisplay(geometry) +disp.image = dl1.image +disp.highlight_pixels(geometry.get_border_pixel_mask(1), linewidth=2, color="xkcd:red") + +n_islands, island_id = number_of_islands(geometry, clean) + +print(n_islands) + +conc = concentration_parameters(geometry, dl1.image, hillas) +print(conc) + + +###################################################################### +# Putting it all together / Stereo reconstruction +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# All these steps are now unified in several components configurable +# through the config system, mainly: +# +# - CameraCalibrator for DL0 → DL1 (Images) +# - ImageProcessor for DL1 (Images) → DL1 (Parameters) +# - ShowerProcessor for stereo reconstruction of the shower geometry +# - DataWriter for writing data into HDF5 +# +# A command line tool doing these steps and writing out data in HDF5 +# format is available as ``ctapipe-process`` +# + +import tempfile +from copy import deepcopy + +import astropy.units as u +from astropy.coordinates import AltAz, SkyCoord +from traitlets.config import Config + +from ctapipe.calib import CameraCalibrator +from ctapipe.containers import ImageParametersContainer +from ctapipe.image import ImageProcessor +from ctapipe.io import DataWriter, EventSource +from ctapipe.reco import ShowerProcessor +from ctapipe.utils.datasets import get_dataset_path + +image_processor_config = Config( + { + "ImageProcessor": { + "image_cleaner_type": "TailcutsImageCleaner", + "TailcutsImageCleaner": { + "picture_threshold_pe": [ + ("type", "LST_LST_LSTCam", 7.5), + ("type", "MST_MST_FlashCam", 8), + ("type", "MST_MST_NectarCam", 8), + ("type", "SST_ASTRI_CHEC", 7), + ], + "boundary_threshold_pe": [ + ("type", "LST_LST_LSTCam", 5), + ("type", "MST_MST_FlashCam", 4), + ("type", "MST_MST_NectarCam", 4), + ("type", "SST_ASTRI_CHEC", 4), + ], + }, + } + } +) + +input_url = get_dataset_path("gamma_prod5.simtel.zst") +source = EventSource(input_url) + +calibrator = CameraCalibrator(subarray=source.subarray) +image_processor = ImageProcessor( + subarray=source.subarray, config=image_processor_config +) +shower_processor = ShowerProcessor(subarray=source.subarray) +horizon_frame = AltAz() + +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) + event_id = event.index.event_id + print(f"Id: {event_id}, E = {energy:1.3f}, Telescopes (R1): {n_telescopes_r1}") + + calibrator(event) + image_processor(event) + shower_processor(event) + + stereo = event.dl2.stereo.geometry["HillasReconstructor"] + if stereo.is_valid: + print(" Alt: {:.2f}°".format(stereo.alt.deg)) + print(" Az: {:.2f}°".format(stereo.az.deg)) + print(" Hmax: {:.0f}".format(stereo.h_max)) + print(" CoreX: {:.1f}".format(stereo.core_x)) + print(" CoreY: {:.1f}".format(stereo.core_y)) + print(" Multiplicity: {:d}".format(len(stereo.telescopes))) + + # save a nice event for plotting later + if event.count == 3: + plotting_event = deepcopy(event) + + writer(event) + +import pandas as pd +from astropy.coordinates.angle_utilities import angular_separation + +from ctapipe.io import TableLoader + +loader = TableLoader(f.name, load_dl2=True, load_simulated=True) + +events = loader.read_subarray_events() + +theta = angular_separation( + events["HillasReconstructor_az"].quantity, + events["HillasReconstructor_alt"].quantity, + events["true_az"].quantity, + events["true_alt"].quantity, +) + +plt.hist(theta.to_value(u.deg) ** 2, bins=25, range=[0, 0.3]) +plt.xlabel(r"$\theta² / deg²$") +None + + +###################################################################### +# ArrayDisplay +# ------------ +# + +from ctapipe.visualization import ArrayDisplay + +angle_offset = plotting_event.pointing.array_azimuth + +plotting_hillas = { + tel_id: dl1.parameters.hillas for tel_id, dl1 in plotting_event.dl1.tel.items() +} + +plotting_core = { + tel_id: dl1.parameters.core.psi for tel_id, dl1 in plotting_event.dl1.tel.items() +} + + +disp = ArrayDisplay(source.subarray) + +disp.set_line_hillas(plotting_hillas, plotting_core, 500) + +plt.scatter( + plotting_event.simulation.shower.core_x, + plotting_event.simulation.shower.core_y, + s=200, + c="k", + marker="x", + label="True Impact", +) +plt.scatter( + plotting_event.dl2.stereo.geometry["HillasReconstructor"].core_x, + plotting_event.dl2.stereo.geometry["HillasReconstructor"].core_y, + s=200, + c="r", + marker="x", + label="Estimated Impact", +) + +plt.legend() +# plt.xlim(-400, 400) +# plt.ylim(-400, 400) + + +###################################################################### +# Reading the LST dl1 data +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# + +loader = TableLoader(f.name, load_simulated=True, load_dl1_parameters=True) + +dl1_table = loader.read_telescope_events(["LST_LST_LSTCam"]) + +plt.scatter( + np.log10(dl1_table["true_energy"].quantity / u.TeV), + np.log10(dl1_table["hillas_intensity"]), +) +plt.xlabel("log10(E / TeV)") +plt.ylabel("log10(intensity)") +None + + +###################################################################### +# Isn’t python slow? +# ------------------ +# +# - Many of you might have heard: “Python is slow”. +# - That’s trueish. +# - All python objects are classes living on the heap, even integers. +# - Looping over lots of “primitives” is quite slow compared to other +# languages. +# +# | ⇒ Vectorize as much as possible using numpy +# | ⇒ Use existing interfaces to fast C / C++ / Fortran code +# | ⇒ Optimize using numba +# +# **But: “Premature Optimization is the root of all evil” — Donald Knuth** +# +# So profile to find exactly what is slow. +# +# Why use python then? +# ~~~~~~~~~~~~~~~~~~~~ +# +# - Python works very well as *glue* for libraries of all kinds of +# languages +# - Python has a rich ecosystem for data science, physics, algorithms, +# astronomy +# +# Example: Number of Islands +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Find all groups of pixels, that survived the cleaning +# + +from ctapipe.image import toymodel +from ctapipe.instrument import SubarrayDescription + +geometry = loader.subarray.tel[1].camera.geometry + + +###################################################################### +# Let’s create a toy images with several islands; +# + +np.random.seed(42) + +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, + width=np.random.uniform(0.05, 0.075) * u.m, + length=np.random.uniform(0.1, 0.15) * u.m, + psi=np.random.uniform(0, 2 * np.pi) * u.rad, + ) + + new_image, sig, bg = model.generate_image( + geometry, intensity=np.random.uniform(1000, 3000), nsb_level_pe=5 + ) + image += new_image + +clean = tailcuts_clean( + geometry, + image, + picture_thresh=10, + boundary_thresh=5, + min_number_picture_neighbors=2, +) + +disp = CameraDisplay(geometry) +disp.image = image +disp.highlight_pixels(clean, color="xkcd:red", linewidth=1.5) +disp.add_colorbar() + + +def num_islands_python(camera, clean): + """A breadth first search to find connected islands of neighboring pixels in the cleaning set""" + + # the camera geometry has a [n_pixel, n_pixel] boolean array + # that is True where two pixels are neighbors + neighbors = camera.neighbor_matrix + + island_ids = np.zeros(camera.n_pixels) + current_island = 0 + + # a set to remember which pixels we already visited + visited = set() + + # go only through the pixels, that survived cleaning + for pix_id in np.where(clean)[0]: + if pix_id not in visited: + # remember that we already checked this pixel + visited.add(pix_id) + + # if we land in the outer loop again, we found a new island + current_island += 1 + island_ids[pix_id] = current_island + + # now check all neighbors of the current pixel recursively + to_check = set(np.where(neighbors[pix_id] & clean)[0]) + while to_check: + pix_id = to_check.pop() + + if pix_id not in visited: + visited.add(pix_id) + island_ids[pix_id] = current_island + + to_check.update(np.where(neighbors[pix_id] & clean)[0]) + + n_islands = current_island + return n_islands, island_ids + + +n_islands, island_ids = num_islands_python(geometry, clean) + +from matplotlib.colors import ListedColormap + +cmap = plt.get_cmap("Paired") +cmap = ListedColormap(cmap.colors[:n_islands]) +cmap.set_under("k") + +disp = CameraDisplay(geometry) +disp.image = island_ids +disp.cmap = cmap +disp.set_limits_minmax(0.5, n_islands + 0.5) +disp.add_colorbar() + +# %timeit num_islands_python(geometry, clean) + +from scipy.sparse.csgraph import connected_components + + +def num_islands_scipy(geometry, clean): + neighbors = geometry.neighbor_matrix_sparse + + clean_neighbors = neighbors[clean][:, clean] + num_islands, labels = connected_components(clean_neighbors, directed=False) + + island_ids = np.zeros(geometry.n_pixels) + island_ids[clean] = labels + 1 + + return num_islands, island_ids + + +n_islands_s, island_ids_s = num_islands_scipy(geometry, clean) + +disp = CameraDisplay(geometry) +disp.image = island_ids_s +disp.cmap = cmap +disp.set_limits_minmax(0.5, n_islands_s + 0.5) +disp.add_colorbar() + +# %timeit num_islands_scipy(geometry, clean) + + +###################################################################### +# **A lot less code, and a factor 3 speed improvement** +# + + +###################################################################### +# Finally, current ctapipe implementation is using numba: +# + +# %timeit number_of_islands(geometry, clean) diff --git a/examples/tutorials/ground_frame.png b/examples/tutorials/ground_frame.png new file mode 100644 index 0000000000000000000000000000000000000000..9545a1dfd67d055d5e677906e96240cc5f598c9c GIT binary patch literal 100340 zcmeFYWmuG5+crFif`ox09SSPaAQI9__a#UO3?SXj(4k1fpdww;0s(@Ba60+xz!nbCa4`=UVHi{n+<|kbjhAt`SlZ!eFp#abS3UQjj~-flQ`kf<)h^MauXX$v%hsGR_QxA&GxVnTN&| z$}JKf{^Zl(8^yJC;6jgbkZGMa7P#)8Jzb@@Vl#AqlfF3qOk|a|XZ^V{?j5_lU^WAsQq-Pt$prSU5b;9ph|CPAgTKO8`VH`!)C2)a%yn2I0) z-&ibrO$RKFeV=1YOGcR$AAx?@C46L_B;?AW@l}gN z21yf=ot-VasDR_8ziXG>E3%IW9 zetlK)aBpLh-gVO3b!TVizApP%z2j(k%T`WG^$bR4y^;~pu%k-z)bajcfetx1+IqwJ znSlG2vDdO%s{A)lcKoQ##<*dyo*oU|2c(vTFJ~&sZNfHuG1SG*OMLda4zMA65>YjijjM7+PAg_#9XG3NbK% zC&o&?8Z%Djti8!Du{v8kY`Snuf6`0?ZFE3fhIoPQEJw3I`Ja*OTQ^EQ6 z5o_{as0xRslZ7y8?aq?j5e4gG-|lrK3z6@7h>9!D>uBFI$F*y4gBIdy(o>?!H*jZ3^T*U zMh=TTY4;T_gW^Z&N(_T-+gGipf|dEKM{fDjkTmRf@L&gZ?4;Dx)F6W*X`bF9Apss0 zs(-$#uaagspHWf4pB)rfh_2%=FcRDb7M9vpzqAjQsIRnsMWbKFf*u_;Y6-pLfTDGk zbaHYcB@DRCI%hfIg>5+~3!<-|!R&S>-u@bx$X;PHs!s1)`!e)5+_iCc`z#{36RixZ9bzBLa^lxScXSLv>a|PWaPFL zjlJ4WEh0J5U2*7hE%QTk!9Dj|;jZI%_QzfIon2g{R8%7U_>6#yU}{vvyhl(VrZ9_P zoDX$L_}jg*$=Jj^o@f;6QYI%SlaM%oH*q3fff!^-2A*$!jeThN!?12{^Wvg8@r@g( zj}H_W7}AT1I89AW4acipEigbZ>Z%TtEC?a%uejB*Tnr5jN?x zuCDm;8|_N&8zS3^Cv%LE&6;{{(?8EP8r)|>+!o`_R13pf5Qxr%QUwT@5Ln>ArXB*X zq$k);NJwz*op_EE+g7YCb9b_iyJmTA-I)MA4}0a9(~f`@_*$jnFCT8Bd^=hR2?^|v ziU#j#BV0Q>?`=wUU$hk|%ny$d@^h=W-tqpM9-LG;N1n_>KY#A*?X_ZaQtPexotuMd z6g{u3zt0mJuYG!Uc;`7fequtPP=^Y<7c=MsTs5$wusF&YBpkj`9l~4>tL^y%O#3R2 z$k|RmBK|p^^+fH{6!)X0xT@K}kfpRVH@l(2Pk9Wi^CcA&uqnh}J;Kh-b6*ysSvYg7k$n4E*Ib7xO_Tvi91_nvQsjgEEo&vze# zgVCXPu$t#*lfb9PNUVkmBPu3+gfxouXcxLuBg?FY9%?be8fRW5AaG!?SJ5EwV)Pxc zU!2-wzKCHZY53n>gTV^hCnu}6^CDf9lw0lCUfs$OJsvT)vy1c+rH2+VX^(mE@*Y^^ zHh`D9((PTz^hD;2rb1Tw9?WOu75w$z+?{H%t<(;Ct0P~AR*F4#mno>J!;na%+X)sG z#YqKnKTg5WRSQN2L9v6o38SNo42Dapv0ONQqbh3XD=?UGZ)kqFz7@!;t`*iHO+hzg ztgNh{aD&{QNhKwm(`%Zj*m5B6GAnQuo%Ru4t3D1K*j*P}30zoZ_`LjFLS(9lCm)^L z)SF0=a*+00SU~x+bw3~K`RAm>%DI1#-%j}pa2V%B;B4UI)^oi;(0el2)MJoAptWJP3Uwebj+Y>!^8(q z#>;JrmsmV4&YgkRp+VGYf%-P)1JWanup3w47xEZ-}K1- z-{JmixvII6PFc5rL$0a9%()c$IsPv8@#WuV7F&*%2M1RQU6k^pJ_YXn(L`GZ*ptgp zegPj(!jnN=DiS%dpEborKkbZ`pG|dRNRW{>+?c2%A;JE@r*EdVt0e(7L^k6k%+~0+ z_Qm_CTC<`0Y6RXehE+L}OA>TpnQ&i7eiYmOp?kr|*jVx(qDCbXP*>w!-4?nk&j&Pt zUmSgqnE}`H`wlPxtRme8lxeYs*0Yhqwzfy4gxC-jFNW-#oQ&LD=IZL|^o$I~G!`85 z%ixiZt!z7b^1{xrf2vFrI(GJ2*JJfU^@I$;GN6d+GJa0p&SeB^Gl`NE6cmt<6kCsc zZ($?y!!w3<5bZk?VqV`NmXg34eK5e3Qn`s0+Nix$<~y@palS2v3*p_3(nhS;&O4nMEOfbTqYrS29!b*}wp(r$7A12Q=47*xh&NXfBYNL;8R-K zJw_ys>?a2lSRL2|T&iA~&Yu3YYb`tkgO|a}$3#KV0%g}#&(jiT%CujONZgcHlf_}b z>4yGZvG0>7q?3J87dW(${g*OiBaox@M$1@{VyDg>9Y~r40A|cme6+>?eUSpwMORz# zDK|H_RN){_Ek`*E{!_+yjl!fLB{+D=2-U|i%?LoCVcSx_MJ$#ihr=m6A%-JD|1e@Q zxV*fal;x7&C=|%R=xykLy{}goNk)XEU*(&DH^d9tRmgKWK44Nw4h=Cv%}hkIk{6|D z+w|7&?f%Y!i%W*9D7Q)$kTp1N)RE+<$ml32pdWZkk|BGO5@{Fe>V19xEbkt8QuFEV z5B)+P{?-;Di7i?p2d}hf2|XCYL^A2$gpEnZWJ9exQB)bx8p#Nu?01CMcMX-nw;zJ7 z^$zr91sosTF4LaX*2y?U3fV5M7;_8%?vpwXKG5RY`mJNvQslE0^})(UsUY(!ZN`0% zr-cR1V{q7RpM|X)Iv1w52yQ(F;*N96l z%0y9Bp7^4_ytw$Fd3m5Xt>L`2{vR2chmkL0>dxj9u~pCSRXe`&;5R4sJv~~v6h%5K zB!t-+DVbnZ?TK!e(&QkY3kg|dCGF$LN$a3J{$R+>cbzyBt}J0;Wf9^+ON(u_Dr(6H zb91^18(RnI?)}@-<9*aDYf&>(45v!Xrw(|B&Z>vc&d~~XRR2iTnU}x2h>kqLF0@yb znXYDk;(!;$<6snYy=6h{P%cf5P&q~LPO5gDpwmXlx43G@sxJd8m$u%y%sKYPb`ue* z^=Uqm%++>TRr}^(aJPQ{XyrMY)Y;Wp(yWRGgTX-cr?ef%Pv;|WFV%lMb_Wh3c&&6R zRff%TNmJ`K>KEI@1t!e*V&u8qGREKO#%?vgBZ_6t9ayo5g@vsAo=Zn;WxnUT^7iM> z&cgQFpPzO*m6h!Mp9}Ks>@;_c*BA|GHY6aJVfG3a0Nnh^@!ne4F{3B#*9e15t?(c9 z^`-6RvU8TZtupnFaOcvj_F|QwR8OiG=hjT@ZHYwqwCB#&G!(($7S|qAsRHM9R#a?0 zJ3a2_xB|1411VCrXLi=Ow!td4ID?sjJzA9J%Hs9o=7<>iOXyDmdrQnJijw-GsR+vV zm4$l<_$;%-YCrE#o8s2I&tAan(r{1z?Txk3SOg{5BIH1Cudj~W_NS$_i0yq3PA%P& zWo(yAdipc(<)=J#l<0#p50Ps0Xg>$;t)b}lmKV=nv&`?|5Lxzf4-b)NWT~P}o@5s4 zasZ1rPdGkZQ@z+Ssb$$P)0ZoLR{+)!fBuk(3A1x{T3lTt1b(mVZpdJ{Tg`#~MB$P| zcRbR^n3?{9J@p!)oQ%FGeOVM!KS##BrFlh7{WSY&SD%j+)Pv$$fCF3!6|vHW7C)zCTD{uEwp(*7}VD{paeh z{){LlxU^FLmsLaMp|M}^zTSZex;abre-b}}`>J+)iV}NZ!_Hn@n864v5CyqR`^~;f zjSQm|cKSQ10%_n};C_h-3DnY}c;Is(1n>^#TOWJARIg?#4U7eQ-cuJ07gnBZee;Hb znG9UoYp^i@5z-UMVDoa|U}5Hg*%GQ^Iks&@5)^z zU<8hzq$6Po_4Uv10pIy1EM-+wgK3aYCU`Brv2RwY%r%Na@)nvVqVnfsK8VB4T^@sr{b6{Q0Z2jq!N zxV!`vdo+h`(mi)@pyYSVtSX8EXUr_>`OlwIX@hJh6Yws#w=KTDN?Ezjh~NGQiu{@y z*Bm$=%s2tMs2|`vlpv0cC5R_PZ??HzmGvKo(D~M50)qasF>KBcf-ik$oHibp=av$M z@Sz~edh)eACg#x}%VN99`ipx3q1mWZ?t6X)epqd;mLA6A4R(`(RC}2cck^S8A`?$U zp;&4px-AL>ZqE_n6`a?<|GERt!d(;SdWJ!W@bX3j$9fN?jmkFpqIxQ;wREM5EQxDO=E63>R&x}6J z4lM6J(#)^Es`k?HL@ftwtW~Lh5#)#fHD;&leP*)`e2eDFlgmh?K%K9zqOx)}oDgP= z1Q%obv8}Dm&3C9!YqTFY{530(5MRSfb6Hdr7mbzU{kY&rF)vy1ILKR$w%oyq{+?@I zFrkjD&nYbt(VcQ}IRJ?_DqUE-G>A_fijw6S#m=!WMmXvo!1Bo*%>8Y|4Xbxa|77PE z{S&NOmVltVX7jCKa^V-ukT`3FxBb2l9qs?+L&nf0R=cfwK)3Me(^v)0s83zJV?tisSR#7(b)`^SrjjFeeOljvp7RO$qk3PhH{ePF`A03w6T9E~Ds*Cd>` zTh0T@DLZ5NE1(Dm^SPZovo;DYQtd?N{GMNn|&KS6U3cs=z?8p@DsP;%() z`TtUaMxMqRuJQ=`wfipZ?&E?U~D)DSZy;m2k9^LTp) zJpYt801|~8bb$uG4Yp|Fi~6)akqqk7dje=dWsvx>XN7PqC2dkJT8Cw6N)g%{_0h4* zjqI-)MKTS_=syF?E=5;@gMDa|;vsLQ>3}gtmZSmGJC-f$WQ+`f->U~N3uGK1D7ap_%Yi!u`8yt8i<+uN?Lb*_Q zy2SmPw$?$~`4q?pQa?z>&~bkL*y)7t+_{Ch_)-XNMC6ml zUyzUNhUoc;jA+F@2JlS`G%(o!&`xN5c6HrAx`0G!TlzBe8M%h4 z*&heK%BkbD^0iT;!;)_4(Y>`|Yil-b*VW*?0v+f_dTw3V_;`xc6eM^w2?@8T$mxej zJIMmQ$hqg*m7h5;5@Xnr$aO&9CC6Sab(){RJv%NodZAk<=<|K2>G;n%_zv5Iw>-Ao zy+G|#YvsO&mr|AJ)&~%6OW>IgV0f?C^!wtT^0N*T< z^<`UTYCSRJ#|;Wb5<;J7X=`G5fls>CRN zE>RU?%eW~OUlyHHVOzJ10mhVcDyHa^iImROjsT~D=3D1m=|3p|=?VL8Q=~T4( zcZ*Vk&(g<(1r7__-KZ_I)Cfny*GmTasoXV`+{2Ufj4g&xtOD248tS`+zqcw^LkjK} zbbVYZAc9^%K=)dWxf;JVOUs7Xo)+}tI%WhG5XB#soCz{mw5DeHz zUfABP369l_h=bl?t&?@(l!~)oAL0bPkD6|*SFbdk1I};V)-ts6hacZ_neEQ<9-;ve z&IXzrT?n-s(#=)$r;nhB`89cd%wP0y8dXb^T#zfwO#*r*BKk>dG3Ho|emBM)Kdi-V zMJ@IBY_a^ZnI#H5X?1g2%e7l%$jkO&WWURwPem_h^mR>29n{LGM0~gTu^EGdaY^wZ z4Ro{-_1!*v*9%|WMgbdbc%2^)ozM+!r|1`IU^u0v(_Kl<*SlsWrC!y9q7_a-p;da*IIT}WgX#C2yU2z?F~y{Q zro~mzdHASupi&#Qe%Gd^K6sVSWcd1C6Tk1Kzj;ljXL+vnsl5ue9hrle`_C)@Co0uz zH{YSOga=P-ze<3gn*@K-gLNH*la}M2wIZ!>rMb(42UgQE&76__JMlbWdm=~kl%`4X z$))ehHW?YJV6#X7!&DTTK_Yh-5wj9g8``d}aic$s7T#R1>2;d)?tLOc+dD+QFmF45 z)XbD_F_3q~xBC!!i)^tuiy|KJ-u~Ym>yxt;hB!WLMWKua80z+5cf&yI%XOnDBD}fd zz%4v;>{(Px*rFWbPIUeNfK&8(Uuu3|&sC($As!@kIDOr;etMk2nVRZ)x9p~7-V#%^ z{7I_j61ZT2-K}=+zHwS_{e$N;V|7AEWF%_Zq)TKjW5ZTA`UJO-j4kbiGJ3 z5O^Q#ych|Ul(5@sIWv)_LM{k;A+m;}`0o4amQ725_RG}L{R5lrINH^$bvNr;il8vWr9fC=_W_k2%i zGQ{`r#@VyK++vK3>cydK_Wbtf-){qhGqQ?iBD`@=_Xc*urrl zU&{t4e0S(m(WMr>dixb0@9*LAh(RE7Zu><_+&48*2wgSr>-mmvs8aR}_T36#lrfyF z_r@-%_zJE$!tRnla3c_^aWWbOq3cLwse*+$h<*W=c||V=doF;ICIIkT3JaaHp@-hzQe|naLFJ_<~l#NVAwi4alN^0=0<6 zg@q}D?ozY(Z*Kzh9tg^JbD&p;by!*q2+8ksf7Pew81Q-tn!uM`Qe?^P3q~7G-7TQr zoywZKa->H3J{6`pH~iqApk0^J<{cYh@G!N(P(a1V&H41mZJ}x*9c)7-f8du?^N=dx z*&CL4nRV1@S?SC{n=YPBY@36oVRe!EQp;~0mM)B98xXBW1>N|7}OKwh;IJ3vAOn` zp}+~d?T%+KY5kdKdk(L9Sd<=MVH)4{Dz|N&$g_N+2$)mw^{8Ey(^@aJ2 z`TpEb8Wp-35J#j@#B@t~Zu{_H>}VI;IsF=E?aod&qK_}M-b-}iWUyjp%*4l6AhsK& zFLr2bnelA_k;d0|`0G13iAR?Vz&o0{ZN2|i#=ZTCFnVQWY02iv!Ik2Ms;6hE{`huZ z=0``+>+1sLW-5wZbUd)iWFTewu?|DzL9!?^a)ov7Xo?LJ(0>gMhEg6UOV`YEL+n#Y-{a`Mx&K#x2wXEvv) ziGIbP`cHdP)3xo1_q>1mpB=quoQUDN5&qG(gABkPE)Q$Xf-_@+-5mR~|4iNn9lzZd z&4P<0}3aY4_ew>dedNeH-g^?a;Yk|^!DI}qNT7Lu`t+1Xz}0o(6H{Mm8h0n6Ti36+ADyU$=_FR%O4JJXJW_Iy{))v@ZpA zg8+kysj-TKep-+H0z>-|VZasFzV+JHF3eL_eP0p2Nf%8vAR}v}3XwN}LMDwt7Uk7s zJy6zUskXqWT!ReOe&AkAalyVhD7*qF8^rs`U;Jouym!!G#`*Dd-=y8p!$WX+p<6~% z@+63VGMtIuHryK(iOL%sj64~s;?O2{FS$oF{x+WzEuxjHfl)j3`l{a`S|4wz-o~n) zkNEuaYn5$PGH4rOeNHfLj12W#RoyKrK|w~pR_?0<3&xv(wytYFywYvf0o|a3ojwF~ zu3@HD(tc;!ocp>&1;6(+T3g!?t`G(S$JSF2bfmYUqM{tKwZaDwy&SSD!t3|XnMNy6 zAmA!7V^x(!`K%h<>-Hy|5!4xO>$=X#BFDvI8DV@veKam!xmDx-`qXsD-D~K33+c9q zPfQP0N3DKw?gAr3C?y(B@;6Yb=^tx@3!bet7QdieO5*`#BPnO_iPDKNvMms}6X<(7 zj}JGxw%s)ob{Yp<0L5Ekiyl=4numPWN6LNJd=(JPfV@jVQ0g{Y z;c^s-rRs0WYLZ2MK#e#$xNduSIk~y%bssMYr=n!a%1WE7fn*B6R3Aq`;%A`R0l~02 zZy+BtMCT*mvo;-sSr4K|c9HAJ);#fh32`e7fR=_N!-^NE_`9SXNWcv|l&0NIV{_rX zU440)1-PT(iE}yI6H;R=U?SsOU7Y|^dOpgFph;Y|H>wsAOyBNF12m>e4XLd-I6gj5 zrUfgk4}UErf~Ifm`-63#gXx2H*9K}5k}_@f>vX~tjEsy9v%fw(`F%z96Cge~Q~?v< zatXcin5R4JM%}g-s7TJQ`AL^XbvTHw@HC5}tpNkxg>SDAxAK^r&{Rf0?WSk@Txq+( z;FxjvVZC&{s4$JMN5E%DwRCWMna$!-2q=2WHN>`!{y;`65OUSSVxcjP9dcii z^Chv<9B3Q^8Ul#{YPE=n2!RH{`|E(+?LdPT3na0Eusy+uPRym|_}!HSZ>GZuRc5?^ zxACCr_6#K5+v_z|ut7BNZ8`|@m!JeTttnSG$P>(DH90Ci!+$s(_e?^0Rwc#ju{AfSut@qSM3_ni_a2BKK9>jWiGSpx$DZ+*Fb z*ZaA)-xc$E^PpD9gplRtz*!&_wy*NpzkQXK75SQrPMyywFQ=rJZwfif@7uRu6l%EG zn!W=)yb--kxtlkv;v?WL2JoNdvh=ZXTJ{RM05$o;)F zg&N|*Ca+Og0ZI%B4F!m%Ml#AEn1d~t6#_ve}jPQVd*!S!=D|m?%0A~VBlqG<^ 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

$t0tDZ-%VnmZBHMHy&|ch!{B+2&IUZ?qej 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 300 + +widths = np.array( + [ + 8, + ] +) # peak widths to search for (let's fix it at 8 samples, about the width of the peak) +peaks = [signal.find_peaks_cwt(trace, widths) for trace in data[has_signal]] + +for p, s in zip(pix_ids[has_signal], peaks): + print("pix{} has peaks at sample {}".format(p, s)) + plt.plot(data[p], drawstyle="steps-mid") + plt.scatter(np.array(s), data[p, s]) + + +###################################################################### +# clearly the signal needs to be filtered first, or an appropriate wavelet +# used, but the idea is nice +# diff --git a/examples/tutorials/theta_square.py b/examples/tutorials/theta_square.py new file mode 100644 index 00000000000..1e5dc881653 --- /dev/null +++ b/examples/tutorials/theta_square.py @@ -0,0 +1,77 @@ +""" +Make a theta-square plot +======================== + +This is a basic example to analyze some events and make a +:math:`\Theta^2` plot + +""" + +# %matplotlib inline + +import matplotlib.pyplot as plt +import numpy as np +from astropy import units as u +from astropy.coordinates import AltAz, SkyCoord +from astropy.coordinates.angle_utilities import angular_separation +from tqdm.auto import tqdm + +from ctapipe.calib import CameraCalibrator +from ctapipe.image import ImageProcessor +from ctapipe.io import EventSource +from ctapipe.reco import ShowerProcessor + +###################################################################### +# Get source events in MC dataset. +# + +source = EventSource( + "dataset://gamma_prod5.simtel.zst", + # allowed_tels={1, 2, 3, 4}, +) + +subarray = source.subarray + +calib = CameraCalibrator(subarray=subarray) +image_processor = ImageProcessor(subarray=subarray) +shower_processor = ShowerProcessor(subarray=subarray) + +off_angles = [] + +for event in tqdm(source): + + # calibrating the event + calib(event) + image_processor(event) + shower_processor(event) + + reco_result = event.dl2.stereo.geometry["HillasReconstructor"] + + # get angular offset between reconstructed shower direction and MC + # generated shower direction + true_shower = event.simulation.shower + off_angle = angular_separation( + true_shower.az, true_shower.alt, reco_result.az, reco_result.alt + ) + + # Appending all estimated off angles + off_angles.append(off_angle.to(u.deg).value) + + +###################################################################### +# calculate theta square for angles which are not nan +# + +off_angles = np.array(off_angles) +thetasquare = off_angles[np.isfinite(off_angles)] ** 2 + + +###################################################################### +# Plot the results +# ---------------- +# + +plt.hist(thetasquare, bins=10, range=[0, 0.4]) +plt.xlabel(r"$\theta^2$ (deg)") +plt.ylabel("# of events") +plt.show() diff --git a/examples/tutorials/tilted_ground_frame.png b/examples/tutorials/tilted_ground_frame.png new file mode 100644 index 0000000000000000000000000000000000000000..084f6e0ec0d07f92524447132685cfb97ac7bd66 GIT binary patch literal 129772 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

8%N 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 literal 0 HcmV?d00001 From 1cf97b9722b89cab851ba5dfdcfc972f547d74fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Fr=C3=B6se?= Date: Thu, 6 Jul 2023 12:11:37 +0200 Subject: [PATCH 02/17] copy pngs --- docs/conf.py | 2 +- examples/examples/README.txt | 6 ++ .../algorithms/convert_images_to_2d.py | 2 - examples/examples/algorithms/dilate_image.py | 2 - .../examples/algorithms/nd_interpolation.py | 4 +- .../examples/core/InstrumentDescription.py | 7 +- examples/examples/core/Tools.py | 11 +-- examples/examples/core/containers.py | 46 +++++----- examples/examples/core/table_writer_reader.py | 75 ++++++++-------- .../examples/visualization/array_display.py | 26 ++---- .../examples/visualization/camera_display.py | 14 ++- .../tutorials/calibrated_data_exploration.py | 11 +-- examples/tutorials/coordinates_example.py | 26 ++---- examples/tutorials/ctapipe_handson.py | 85 ++++++++++--------- examples/tutorials/ctapipe_overview.py | 82 ++++++------------ examples/tutorials/raw_data_exploration.py | 6 +- examples/tutorials/theta_square.py | 3 +- 17 files changed, 170 insertions(+), 238 deletions(-) create mode 100644 examples/examples/README.txt diff --git a/docs/conf.py b/docs/conf.py index 854b46773d6..14e9f567bc1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -154,7 +154,7 @@ def setup(app): "tutorials", ], # path to where to save gallery generated output "nested_sections": True, - "copyfile_regex": r"index.rst", + "copyfile_regex": r"index.rst|.*\.png", } diff --git a/examples/examples/README.txt b/examples/examples/README.txt new file mode 100644 index 00000000000..d3c2423283f --- /dev/null +++ b/examples/examples/README.txt @@ -0,0 +1,6 @@ +.. _examples_gallery: + +Examples gallery +================ + +The examples gallery provides an overview of different ctapipe modules and how to use them. diff --git a/examples/examples/algorithms/convert_images_to_2d.py b/examples/examples/algorithms/convert_images_to_2d.py index 4817580d1b1..040baeab210 100644 --- a/examples/examples/algorithms/convert_images_to_2d.py +++ b/examples/examples/algorithms/convert_images_to_2d.py @@ -9,8 +9,6 @@ from ctapipe.image.toymodel import Gaussian from ctapipe.instrument import SubarrayDescription -from ctapipe.io import EventSource -from ctapipe.utils import get_dataset_path from ctapipe.visualization import CameraDisplay # get the subarray from an example file diff --git a/examples/examples/algorithms/dilate_image.py b/examples/examples/algorithms/dilate_image.py index 9b6de53e237..b2d827d27cb 100644 --- a/examples/examples/algorithms/dilate_image.py +++ b/examples/examples/algorithms/dilate_image.py @@ -49,8 +49,6 @@ # Now dialte the mask a few times: # -from ctapipe.image.cleaning import dilate - def show_dilate(mask, times=1): m = mask.copy() diff --git a/examples/examples/algorithms/nd_interpolation.py b/examples/examples/algorithms/nd_interpolation.py index 19b0b06e386..d830f7a2d11 100644 --- a/examples/examples/algorithms/nd_interpolation.py +++ b/examples/examples/algorithms/nd_interpolation.py @@ -119,7 +119,7 @@ ) plt.title("Raw table, uninterpolated {0}".format(energy_table.hist.T.shape)) cb = plt.colorbar() -cb.set_label("$\log_{10}(E/\mathrm{TeV})$") +cb.set_label(r"$\log_{10}(E/\mathrm{TeV})$") # the interpolated table plt.subplot(1, 2, 2) @@ -130,7 +130,7 @@ plt.ylabel("Impact Dist(m)") plt.title("Interpolated to a ({0}, {0}) grid".format(N)) cb = plt.colorbar() -cb.set_label("$\log_{10}(E/\mathrm{TeV})$") +cb.set_label(r"$\log_{10}(E/\mathrm{TeV})$") plt.tight_layout() plt.show() diff --git a/examples/examples/core/InstrumentDescription.py b/examples/examples/core/InstrumentDescription.py index ac4a5ccc1bd..fc3857a9855 100644 --- a/examples/examples/core/InstrumentDescription.py +++ b/examples/examples/core/InstrumentDescription.py @@ -8,10 +8,11 @@ """ -import numpy as np +from astropy.coordinates import SkyCoord from ctapipe.io import EventSource from ctapipe.utils.datasets import get_dataset_path +from ctapipe.visualization import CameraDisplay filename = get_dataset_path("gamma_prod5.simtel.zst") @@ -71,7 +72,6 @@ tel.camera.geometry.pix_x # %matplotlib inline -from ctapipe.visualization import CameraDisplay CameraDisplay(tel.camera.geometry) @@ -106,9 +106,6 @@ subarray.optics_types -from astropy.coordinates import SkyCoord - -from ctapipe.coordinates import GroundFrame center = SkyCoord("10.0 m", "2.0 m", "0.0 m", frame="groundframe") coords = subarray.tel_coords # a flat list of coordinates by tel_index diff --git a/examples/examples/core/Tools.py b/examples/examples/core/Tools.py index 212b860bcff..d97e0e7fe28 100644 --- a/examples/examples/core/Tools.py +++ b/examples/examples/core/Tools.py @@ -4,23 +4,17 @@ """ -import logging from time import sleep -from astropy import units as u - from ctapipe.core import Component, TelescopeComponent, Tool from ctapipe.core.traits import ( - Dict, - Float, FloatTelescopeParameter, Integer, - List, Path, TraitError, - Unicode, observe, ) +from ctapipe.instrument import SubarrayDescription from ctapipe.utils import get_dataset_path GAMMA_FILE = get_dataset_path("gamma_prod5.simtel.zst") @@ -52,8 +46,6 @@ def do_thing(self): class SecondaryMyComponent(MyComponent): """A second component""" - pass - class AdvancedComponent(Component): """An advanced technique""" @@ -100,7 +92,6 @@ class TelescopeWiseComponent(TelescopeComponent): # one: # -from ctapipe.instrument import SubarrayDescription, TelescopeDescription subarray = SubarrayDescription.read(GAMMA_FILE) subarray.info() diff --git a/examples/examples/core/containers.py b/examples/examples/core/containers.py index de499c57daa..360fcdc45c7 100644 --- a/examples/examples/core/containers.py +++ b/examples/examples/core/containers.py @@ -15,11 +15,13 @@ import numpy as np from astropy import units as u +from ctapipe.containers import SimulatedShowerContainer from ctapipe.core import Container, Field, Map ###################################################################### # Let’s define a few example containers with some dummy fields in them: -# +# + class SubContainer(Container): junk = Field(-1, "Some junk") @@ -59,14 +61,14 @@ class EventContainer(Container): ###################################################################### # Basic features # -------------- -# +# ev = EventContainer() ###################################################################### # Check that default values are automatically filled in -# +# print(ev.event_id) print(ev.sub) @@ -79,23 +81,21 @@ class EventContainer(Container): ###################################################################### # print the dict representation -# +# print(ev) ###################################################################### # We also get docstrings “for free” -# - -?EventContainer - -?SubContainer +# +help(EventContainer) +help(SubContainer) ###################################################################### # values can be set as normal for a class: -# +# ev.event_id = 100 ev.event_id @@ -108,7 +108,7 @@ class EventContainer(Container): ###################################################################### # and we can add a few of these to the parent container inside the tel # dict: -# +# ev.tel[10] = TelContainer() ev.tel[5] = TelContainer() @@ -121,12 +121,15 @@ class EventContainer(Container): ###################################################################### # Be careful to use the ``default_factory`` mechanism for mutable fields, # see this **negative** example: -# +# + class DangerousContainer(Container): image = Field( np.zeros(10), - description="Attention!!!! Globally mutable shared state. Use default_factory instead", + description=( + "Attention!!!! Globally mutable shared state. Use default_factory instead" + ), ) @@ -145,7 +148,7 @@ class DangerousContainer(Container): ###################################################################### # Converion to dictionaries # ------------------------- -# +# ev.as_dict() @@ -155,7 +158,7 @@ class DangerousContainer(Container): ###################################################################### # for serialization to a table, we can even flatten the output into a # single set of columns -# +# ev.as_dict(recursive=True, flatten=True) @@ -163,7 +166,7 @@ class DangerousContainer(Container): ###################################################################### # Setting and clearing values # --------------------------- -# +# ev.tel[5].image[:] = 9 print(ev) @@ -175,11 +178,10 @@ class DangerousContainer(Container): ###################################################################### # look at a pre-defined Container # ------------------------------- -# +# -from ctapipe.containers import SimulatedShowerContainer -?SimulatedShowerContainer +help(SimulatedShowerContainer) shower = SimulatedShowerContainer() shower @@ -188,10 +190,10 @@ class DangerousContainer(Container): ###################################################################### # Container prefixes # ------------------ -# +# # To store the same container in the same table in a file or give more # information, containers support setting a custom prefix: -# +# c1 = SubContainer(junk=5, value=3, prefix="foo") c2 = SubContainer(junk=10, value=9001, prefix="bar") @@ -199,4 +201,4 @@ class DangerousContainer(Container): # create a common dict with data from both containers: d = c1.as_dict(add_prefix=True) d.update(c2.as_dict(add_prefix=True)) -d \ No newline at end of file +d diff --git a/examples/examples/core/table_writer_reader.py b/examples/examples/core/table_writer_reader.py index 6c9e9a6ab4c..d1d122477d3 100644 --- a/examples/examples/core/table_writer_reader.py +++ b/examples/examples/core/table_writer_reader.py @@ -4,7 +4,7 @@ The ``TableWriter``/``TableReader`` sub-classes allow you to write a ``ctapipe.core.Container`` class and its meta-data to an output table. -They treat the ``Field``\ s in the ``Container`` as columns in the +They treat the ``Field``s in the ``Container`` as columns in the output, and automatically generate a schema. Here we will go through an example of writing out data and reading it back with *Pandas*, *PyTables*, and a ``ctapipe.io.TableReader``: @@ -19,22 +19,26 @@ ###################################################################### # Caveats to think about: \* vector columns in Containers *can* be # written, but some lilbraries like Pandas can not read those (so you must -# use pytables or astropy to read outputs that have vector columns) \* -# units are stored in the table metadata, but some libraries like Pandas +# use pytables or astropy to read outputs that have vector columns) +# \* units are stored in the table metadata, but some libraries like Pandas # ignore them and all other metadata -# +# ###################################################################### # Create some example Containers # ------------------------------ -# +# + +import os import numpy as np +import pandas as pd +import tables from astropy import units as u from ctapipe.core import Container, Field -from ctapipe.io import HDF5TableWriter +from ctapipe.io import HDF5TableReader, HDF5TableWriter, read_table class VariousTypesContainer(Container): @@ -50,7 +54,8 @@ class VariousTypesContainer(Container): ###################################################################### # let’s also make a dummy stream (generator) that will create a series of # these containers -# +# + def create_stream(n_event): @@ -66,6 +71,7 @@ def create_stream(n_event): yield data + for data in create_stream(2): for key, val in data.items(): @@ -76,13 +82,13 @@ def create_stream(n_event): ###################################################################### # Writing the Data (and good practices) # ------------------------------------- -# +# ###################################################################### # Always use context managers with IO classes, as they will make sure the # underlying resources are properly closed in case of errors: -# +# try: with HDF5TableWriter("container.h5", group_name="data") as h5_table: @@ -97,20 +103,19 @@ def create_stream(n_event): h5_table.h5file.isopen == False -!ls container.h5 - +print(os.listdir()) ###################################################################### # Appending new Containers # ------------------------ -# +# ###################################################################### # To append some new containers we need to set the writing in append mode # by using: ‘mode=a’. But let’s now first look at what happens if we # don’t. -# +# for i in range(2): @@ -124,14 +129,13 @@ def create_stream(n_event): print(h5_table.h5file) -!rm -f container.h5 - +os.remove("container.h5") ###################################################################### # Ok so the writer destroyed the content of the file each time it opens # the file. Now let’s try to append some data group to it! (using # mode=‘a’) -# +# for i in range(2): @@ -149,7 +153,7 @@ def create_stream(n_event): ###################################################################### # So we can append some data groups. As long as the data group_name does # not already exists. Let’s try to overwrite the data group : data_1 -# +# try: with HDF5TableWriter("container.h5", mode="a", group_name="data_1") as h5_table: @@ -161,7 +165,7 @@ def create_stream(n_event): ###################################################################### # Good ! I cannot overwrite my data. -# +# print(bool(h5_table.h5file.isopen)) @@ -169,34 +173,33 @@ def create_stream(n_event): ###################################################################### # Reading the Data # ---------------- -# +# ###################################################################### # Reading the whole table at once: # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# # For this, you have several choices. Since we used the HDF5TableWriter in # this example, we have at least these options avilable: -# +# # - Pandas # - PyTables # - Astropy Table -# +# # For other TableWriter implementations, others may be possible (depending # on format) -# +# ###################################################################### # Reading using ``ctapipe.io.read_table`` # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# +# # This is the preferred method, it returns an astropy ``Table`` and # supports keeping track of units, metadata and transformations. -# +# -from ctapipe.io import read_table table = read_table("container.h5", "/data_0/table") table[:5] @@ -207,13 +210,12 @@ def create_stream(n_event): ###################################################################### # Reading with Pandas: # ^^^^^^^^^^^^^^^^^^^^ -# +# # Pandas is a convenient way to read the output. **HOWEVER BE WARNED** # that so far Pandas does not support reading the table *meta-data* or # *units* for colums, so that information is lost! -# +# -import pandas as pd data = pd.read_hdf("container.h5", key="/data_0/table") data.head() @@ -222,9 +224,8 @@ def create_stream(n_event): ###################################################################### # Reading with PyTables # ^^^^^^^^^^^^^^^^^^^^^ -# +# -import tables h5 = tables.open_file("container.h5") table = h5.root["data_0"]["table"] @@ -233,7 +234,7 @@ def create_stream(n_event): ###################################################################### # note that here we can still access the metadata -# +# table.attrs @@ -241,19 +242,17 @@ def create_stream(n_event): ###################################################################### # Reading one-row-at-a-time: # ~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# ###################################################################### # Rather than using the full-table methods, if you want to read it # row-by-row (e.g. to maintain compatibility with an existing event loop), # you can use a ``TableReader`` instance. -# +# # The advantage here is that units and other metadata are retained and # re-applied -# - -from ctapipe.io import HDF5TableReader +# def read(mode): @@ -271,6 +270,7 @@ def read(mode): print(data.as_dict()) + read("r") read("r+") @@ -278,4 +278,3 @@ def read(mode): read("a") read("w") - diff --git a/examples/examples/visualization/array_display.py b/examples/examples/visualization/array_display.py index 9679cfd9bc3..e5438ae7f7a 100644 --- a/examples/examples/visualization/array_display.py +++ b/examples/examples/visualization/array_display.py @@ -12,9 +12,12 @@ from astropy import units as u from astropy.coordinates import SkyCoord -from ctapipe.containers import HillasParametersContainer -from ctapipe.coordinates import EastingNorthingFrame, GroundFrame +from ctapipe.calib import CameraCalibrator +from ctapipe.coordinates import EastingNorthingFrame +from ctapipe.image import ImageProcessor from ctapipe.instrument import SubarrayDescription +from ctapipe.io import EventSource +from ctapipe.reco import ShowerProcessor from ctapipe.visualization import ArrayDisplay plt.rcParams["figure.figsize"] = (8, 6) @@ -134,17 +137,6 @@ # following example shows its use: # -import matplotlib.pyplot as plt -from astropy.coordinates import SkyCoord -from IPython import display -from matplotlib.animation import FuncAnimation - -from ctapipe.calib import CameraCalibrator -from ctapipe.image import ImageProcessor -from ctapipe.io import EventSource -from ctapipe.reco import ShowerProcessor -from ctapipe.utils import get_dataset_path -from ctapipe.visualization import ArrayDisplay input_url = "dataset://gamma_LaPalma_baseline_20Zd_180Az_prod3b_test.simtel.gz" @@ -161,13 +153,7 @@ def plot_event(event, subarray, ax): true and reconstructed impact position overlaid """ - array_pointing = SkyCoord( - az=event.pointing.array_azimuth, - alt=event.pointing.array_altitude, - frame="altaz", - ) - - angle_offset = event.pointing.array_azimuth + event.pointing.array_azimuth disp = ArrayDisplay(subarray, axes=ax) hillas_dict = {tid: tel.parameters.hillas for tid, tel in event.dl1.tel.items()} diff --git a/examples/examples/visualization/camera_display.py b/examples/examples/visualization/camera_display.py index 7aac6fe6afb..92f66f6d5f5 100644 --- a/examples/examples/visualization/camera_display.py +++ b/examples/examples/visualization/camera_display.py @@ -6,12 +6,16 @@ import astropy.coordinates as c import astropy.units as u -import matplotlib.pylab as plt +import matplotlib.pyplot as plt import numpy as np +from IPython import display +from matplotlib.animation import FuncAnimation +from matplotlib.colors import PowerNorm from ctapipe.coordinates import CameraFrame, EngineeringCameraFrame, TelescopeFrame from ctapipe.image import hillas_parameters, tailcuts_clean, toymodel from ctapipe.instrument import SubarrayDescription +from ctapipe.io import EventSource from ctapipe.visualization import CameraDisplay ###################################################################### @@ -154,7 +158,6 @@ # also provide a custom normalization, for example a ``PowerNorm``: # -from matplotlib.colors import PowerNorm fig, axes = plt.subplots(2, 2, figsize=(14, 10)) norms = ["lin", "log", "symlog", PowerNorm(0.5)] @@ -255,8 +258,6 @@ # display (much faster than generating a new one each time) # -from IPython import display -from matplotlib.animation import FuncAnimation subarray = SubarrayDescription.read("dataset://gamma_prod5.simtel.zst") geom = subarray.tel[1].camera.geometry @@ -330,11 +331,6 @@ def update(frame): # First we load some real data so we have a nice image to view: # -import matplotlib.pyplot as plt -import numpy as np - -from ctapipe.io import EventSource -from ctapipe.visualization import CameraDisplay DATA = "dataset://gamma_20deg_0deg_run1___cta-prod5-lapalma_desert-2158m-LaPalma-dark_100evts.simtel.zst" diff --git a/examples/tutorials/calibrated_data_exploration.py b/examples/tutorials/calibrated_data_exploration.py index da235ebe648..caf80a298a1 100644 --- a/examples/tutorials/calibrated_data_exploration.py +++ b/examples/tutorials/calibrated_data_exploration.py @@ -9,10 +9,11 @@ from matplotlib import pyplot as plt import ctapipe -from ctapipe.instrument import CameraGeometry -from ctapipe.io import EventSeeker, EventSource +from ctapipe.calib import CameraCalibrator +from ctapipe.image import hillas_parameters, tailcuts_clean +from ctapipe.io import EventSource from ctapipe.utils.datasets import get_dataset_path -from ctapipe.visualization import CameraDisplay +from ctapipe.visualization import ArrayDisplay, CameraDisplay # %matplotlib inline plt.style.use("ggplot") @@ -58,7 +59,6 @@ # near future that will be automatic). # -from ctapipe.calib import CameraCalibrator calib = CameraCalibrator(subarray=source.subarray) calib(event) @@ -91,7 +91,6 @@ # Let’s look at the image # -from ctapipe.visualization import CameraDisplay tel_id = sorted(event.r1.tel.keys())[1] sub = source.subarray @@ -100,7 +99,6 @@ disp = CameraDisplay(geometry, image=image) -from ctapipe.image import hillas_parameters, tailcuts_clean mask = tailcuts_clean( geometry, @@ -191,7 +189,6 @@ # version to be more user-friendly) # -from ctapipe.visualization import ArrayDisplay nectarcam_subarray = sub.select_subarray(cam_ids, name="NectarCam") diff --git a/examples/tutorials/coordinates_example.py b/examples/tutorials/coordinates_example.py index 87cac268fa4..8e8b18795c4 100644 --- a/examples/tutorials/coordinates_example.py +++ b/examples/tutorials/coordinates_example.py @@ -8,20 +8,14 @@ import astropy.units as u import matplotlib.pyplot as plt -import numpy as np -from astropy.coordinates import AltAz, SkyCoord - -from ctapipe.calib import CameraCalibrator -from ctapipe.coordinates import ( - CameraFrame, - GroundFrame, - NominalFrame, - TelescopeFrame, - TiltedGroundFrame, -) +from astropy.coordinates import AltAz, EarthLocation, SkyCoord +from astropy.time import Time + +from ctapipe.coordinates import CameraFrame, NominalFrame, TelescopeFrame +from ctapipe.instrument import SubarrayDescription from ctapipe.io import EventSource from ctapipe.utils import get_dataset_path -from ctapipe.visualization import ArrayDisplay +from ctapipe.visualization import CameraDisplay # %matplotlib inline @@ -75,8 +69,6 @@ # oriented East of North (i.e., N=0°, E=90°). # -from astropy.coordinates import EarthLocation -from astropy.time import Time obstime = Time("2013-11-01T03:00") location = EarthLocation.of_site("Roque de los Muchachos") @@ -149,8 +141,6 @@ # and how they might be visible in the camera. # -from ctapipe.instrument import SubarrayDescription -from ctapipe.visualization import CameraDisplay location = EarthLocation.of_site("Roque de los Muchachos") obstime = Time("2018-11-01T04:00") @@ -330,8 +320,8 @@ ) -ax.set_xlabel(f"fov_lon / deg") -ax.set_ylabel(f"fov_lat / deg") +ax.set_xlabel("fov_lon / deg") +ax.set_ylabel("fov_lat / deg") ax.legend() plt.show() diff --git a/examples/tutorials/ctapipe_handson.py b/examples/tutorials/ctapipe_handson.py index a09e5d55051..157e97461d9 100644 --- a/examples/tutorials/ctapipe_handson.py +++ b/examples/tutorials/ctapipe_handson.py @@ -11,13 +11,20 @@ ###################################################################### # Part 1: load and loop over data # ------------------------------- -# +# + +import glob import numpy as np +import pandas as pd +from ipywidgets import interact from matplotlib import pyplot as plt from ctapipe import utils -from ctapipe.io import EventSource +from ctapipe.calib import CameraCalibrator +from ctapipe.image import hillas_parameters, tailcuts_clean +from ctapipe.io import EventSource, HDF5TableWriter +from ctapipe.visualization import CameraDisplay # %matplotlib inline @@ -47,19 +54,17 @@ ###################################################################### # note that this is (:math:`N_{channels}`, :math:`N_{pixels}`, # :math:`N_{samples}`) -# +# plt.pcolormesh(r0tel.waveform[0]) brightest_pixel = np.argmax(r0tel.waveform[0].sum(axis=1)) print(f"pixel {brightest_pixel} has sum {r0tel.waveform[0,1535].sum()}") -plt.plot(r0tel.waveform[0,brightest_pixel], label="channel 0 (high-gain)") -plt.plot(r0tel.waveform[1,brightest_pixel], label="channel 1 (low-gain)") +plt.plot(r0tel.waveform[0, brightest_pixel], label="channel 0 (high-gain)") +plt.plot(r0tel.waveform[1, brightest_pixel], label="channel 1 (low-gain)") plt.legend() -from ipywidgets import interact - @interact def view_waveform(chan=0, pix_id=brightest_pixel): @@ -68,21 +73,21 @@ def view_waveform(chan=0, pix_id=brightest_pixel): ###################################################################### # try making this compare 2 waveforms -# +# ###################################################################### # Part 2: Explore the instrument description # ------------------------------------------ -# +# # This is all well and good, but we don’t really know what camera or # telescope this is… how do we get instrumental description info? -# +# # Currently this is returned *inside* the event (it will soon change to be # separate in next version or so) -# +# -subarray = source.subarray +subarray = source.subarray subarray @@ -108,38 +113,38 @@ def view_waveform(chan=0, pix_id=brightest_pixel): tel.optics.mirror_area -from ctapipe.visualization import CameraDisplay disp = CameraDisplay(tel.camera.geometry) disp = CameraDisplay(tel.camera.geometry) -disp.image = r0tel.waveform[0,:,10] # display channel 0, sample 0 (try others like 10) +disp.image = r0tel.waveform[ + 0, :, 10 +] # display channel 0, sample 0 (try others like 10) ###################################################################### # \*\* aside: \*\* show demo using a CameraDisplay in interactive mode in # ipython rather than notebook -# +# ###################################################################### # Part 3: Apply some calibration and trace integration # ---------------------------------------------------- -# +# -from ctapipe.calib import CameraCalibrator calib = CameraCalibrator(subarray=subarray) for event in EventSource(path, max_events=5): - calib(event) # fills in r1, dl0, and dl1 + calib(event) # fills in r1, dl0, and dl1 print(event.dl1.tel.keys()) event.dl1.tel[3] dl1tel = event.dl1.tel[3] -dl1tel.image.shape # note this will be gain-selected in next version, so will be just 1D array of 1855 +dl1tel.image.shape # note this will be gain-selected in next version, so will be just 1D array of 1855 dl1tel.peak_time @@ -150,9 +155,8 @@ def view_waveform(chan=0, pix_id=brightest_pixel): ###################################################################### # Now for Hillas Parameters -# +# -from ctapipe.image import hillas_parameters, tailcuts_clean image = dl1tel.image mask = tailcuts_clean(tel.camera.geometry, image, picture_thresh=10, boundary_thresh=5) @@ -161,7 +165,7 @@ def view_waveform(chan=0, pix_id=brightest_pixel): CameraDisplay(tel.camera.geometry, image=mask) cleaned = image.copy() -cleaned[~mask] = 0 +cleaned[~mask] = 0 disp = CameraDisplay(tel.camera.geometry, image=cleaned) disp.cmap = plt.cm.coolwarm @@ -177,25 +181,25 @@ def view_waveform(chan=0, pix_id=brightest_pixel): disp.add_colorbar() plt.xlim(0.5, 1.0) plt.ylim(-1.0, 0.0) -disp.overlay_moments(params, color='white', lw=2) +disp.overlay_moments(params, color="white", lw=2) ###################################################################### # Part 4: Let’s put it all together: # ---------------------------------- -# +# # - loop over events, selecting only telescopes of the same type # (e.g. LST:LSTCam) # - for each event, apply calibration/trace integration # - calculate Hillas parameters # - write out all hillas paremeters to a file that can be loaded with # Pandas -# +# ###################################################################### # first let’s select only those telescopes with LST:LSTCam -# +# subarray.telescope_types @@ -204,28 +208,27 @@ def view_waveform(chan=0, pix_id=brightest_pixel): ###################################################################### # Now let’s write out program -# +# -data = utils.get_dataset_path("gamma_prod5.simtel.zst") -source = EventSource(data) # remove the max_events limit to get more stats +data = utils.get_dataset_path("gamma_prod5.simtel.zst") +source = EventSource(data) # remove the max_events limit to get more stats for event in source: calib(event) - + for tel_id, tel_data in event.dl1.tel.items(): tel = source.subarray.tel[tel_id] mask = tailcuts_clean(tel.camera.geometry, tel_data.image) if np.count_nonzero(mask) > 0: params = hillas_parameters(tel.camera.geometry[mask], tel_data.image[mask]) -from ctapipe.io import HDF5TableWriter -with HDF5TableWriter(filename='hillas.h5', group_name='dl1', overwrite=True) as writer: - - source = EventSource(data, allowed_tels=[1,2,3,4], max_events=10) +with HDF5TableWriter(filename="hillas.h5", group_name="dl1", overwrite=True) as writer: + + source = EventSource(data, allowed_tels=[1, 2, 3, 4], max_events=10) for event in source: calib(event) - + for tel_id, tel_data in event.dl1.tel.items(): tel = source.subarray.tel[tel_id] mask = tailcuts_clean(tel.camera.geometry, tel_data.image) @@ -236,19 +239,17 @@ def view_waveform(chan=0, pix_id=brightest_pixel): ###################################################################### # We can now load in the file we created and plot it # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# +glob.glob("*.h5") -!ls *.h5 - -import pandas as pd -hillas = pd.read_hdf("hillas.h5", key='/dl1/hillas') +hillas = pd.read_hdf("hillas.h5", key="/dl1/hillas") hillas -_ = hillas.hist(figsize=(8,8)) +_ = hillas.hist(figsize=(8, 8)) ###################################################################### # If you do this yourself, chose a larger file to loop over more events to # get better statistics -# \ No newline at end of file +# diff --git a/examples/tutorials/ctapipe_overview.py b/examples/tutorials/ctapipe_overview.py index 7266aac8f0a..0e7866d91e0 100644 --- a/examples/tutorials/ctapipe_overview.py +++ b/examples/tutorials/ctapipe_overview.py @@ -39,8 +39,34 @@ #

# +import tempfile +from copy import deepcopy + +import astropy.units as u import matplotlib.pyplot as plt import numpy as np +from astropy.coordinates import AltAz +from astropy.coordinates.angle_utilities import angular_separation +from matplotlib.colors import ListedColormap +from scipy.sparse.csgraph import connected_components +from traitlets.config import Config + +from ctapipe.calib import CameraCalibrator +from ctapipe.image import ( + ImageProcessor, + camera_to_shower_coordinates, + concentration_parameters, + hillas_parameters, + leakage_parameters, + number_of_islands, + timing_parameters, + toymodel, +) +from ctapipe.image.cleaning import tailcuts_clean +from ctapipe.io import DataWriter, EventSource, TableLoader +from ctapipe.reco import ShowerProcessor +from ctapipe.utils.datasets import get_dataset_path +from ctapipe.visualization import ArrayDisplay, CameraDisplay # %matplotlib inline @@ -49,22 +75,6 @@ plt.rcParams["figure.figsize"] -###################################################################### -# .. raw:: html -# -#

-# -# Table of Contents -# -# .. raw:: html -# -#

-# -# .. container:: -# :name: toc -# - - ###################################################################### # General Information # ------------------- @@ -159,8 +169,6 @@ # ~~~~~~~~~~~~~~~~~~~~~~~ # -from ctapipe.io import EventSource -from ctapipe.utils.datasets import get_dataset_path input_url = get_dataset_path("gamma_prod5.simtel.zst") @@ -198,7 +206,6 @@ # images). # -from ctapipe.calib import CameraCalibrator calibrator = CameraCalibrator(subarray=source.subarray) @@ -223,7 +230,6 @@ dl1.image -from ctapipe.visualization import CameraDisplay display = CameraDisplay(geometry) @@ -238,7 +244,6 @@ # ~~~~~~~~~~~~~~ # -from ctapipe.image.cleaning import tailcuts_clean # unoptimized cleaning levels cleaning_level = { @@ -281,14 +286,6 @@ # ~~~~~~~~~~~~~~~~ # -from ctapipe.image import ( - camera_to_shower_coordinates, - concentration_parameters, - hillas_parameters, - leakage_parameters, - number_of_islands, - timing_parameters, -) hillas = hillas_parameters(geometry[clean], dl1.image[clean]) @@ -316,8 +313,8 @@ plt.plot(long[clean], dl1.peak_time[clean], "o") plt.plot(long[clean], timing.slope * long[clean] + timing.intercept) -l = leakage_parameters(geometry, dl1.image, clean) -print(l) +leakage = leakage_parameters(geometry, dl1.image, clean) +print(leakage) disp = CameraDisplay(geometry) disp.image = dl1.image @@ -347,19 +344,6 @@ # format is available as ``ctapipe-process`` # -import tempfile -from copy import deepcopy - -import astropy.units as u -from astropy.coordinates import AltAz, SkyCoord -from traitlets.config import Config - -from ctapipe.calib import CameraCalibrator -from ctapipe.containers import ImageParametersContainer -from ctapipe.image import ImageProcessor -from ctapipe.io import DataWriter, EventSource -from ctapipe.reco import ShowerProcessor -from ctapipe.utils.datasets import get_dataset_path image_processor_config = Config( { @@ -424,10 +408,6 @@ writer(event) -import pandas as pd -from astropy.coordinates.angle_utilities import angular_separation - -from ctapipe.io import TableLoader loader = TableLoader(f.name, load_dl2=True, load_simulated=True) @@ -450,7 +430,6 @@ # ------------ # -from ctapipe.visualization import ArrayDisplay angle_offset = plotting_event.pointing.array_azimuth @@ -539,8 +518,6 @@ # Find all groups of pixels, that survived the cleaning # -from ctapipe.image import toymodel -from ctapipe.instrument import SubarrayDescription geometry = loader.subarray.tel[1].camera.geometry @@ -623,7 +600,6 @@ def num_islands_python(camera, clean): n_islands, island_ids = num_islands_python(geometry, clean) -from matplotlib.colors import ListedColormap cmap = plt.get_cmap("Paired") cmap = ListedColormap(cmap.colors[:n_islands]) @@ -637,8 +613,6 @@ def num_islands_python(camera, clean): # %timeit num_islands_python(geometry, clean) -from scipy.sparse.csgraph import connected_components - def num_islands_scipy(geometry, clean): neighbors = geometry.neighbor_matrix_sparse diff --git a/examples/tutorials/raw_data_exploration.py b/examples/tutorials/raw_data_exploration.py index b12c35ff70c..3e0ad08f3de 100644 --- a/examples/tutorials/raw_data_exploration.py +++ b/examples/tutorials/raw_data_exploration.py @@ -15,10 +15,10 @@ # Setup: # -from astropy import units as u +import numpy as np from matplotlib import pyplot as plt +from scipy import signal -from ctapipe.instrument import CameraGeometry from ctapipe.io import EventSource from ctapipe.utils import get_dataset_path from ctapipe.visualization import CameraDisplay @@ -278,8 +278,6 @@ # https://docs.scipy.org/doc/scipy/reference/signal.html # -import numpy as np -from scipy import signal pix_ids = np.arange(len(data)) has_signal = sums > 300 diff --git a/examples/tutorials/theta_square.py b/examples/tutorials/theta_square.py index 1e5dc881653..b7f9694383e 100644 --- a/examples/tutorials/theta_square.py +++ b/examples/tutorials/theta_square.py @@ -1,4 +1,4 @@ -""" +r""" Make a theta-square plot ======================== @@ -12,7 +12,6 @@ import matplotlib.pyplot as plt import numpy as np from astropy import units as u -from astropy.coordinates import AltAz, SkyCoord from astropy.coordinates.angle_utilities import angular_separation from tqdm.auto import tqdm From f543881acf2cfeb7137fc989825a20c803fa2986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Fr=C3=B6se?= Date: Thu, 6 Jul 2023 16:57:06 +0200 Subject: [PATCH 03/17] add sphinx_gallery to setup.cfg --- .github/workflows/ci.yml | 2 +- setup.cfg | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c7d0abe22c..248d26a8fac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -151,7 +151,7 @@ jobs: - name: Install doc dependencies run: | sudo apt update --yes && sudo apt install --yes git build-essential pandoc graphviz ffmpeg - pip install -U pip towncrier sphinx-gallery + pip install -U pip towncrier pip install -e .[docs] pip install ./test_plugin git describe --tags diff --git a/setup.cfg b/setup.cfg index 767a6b458f6..6b7f6e343a6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,6 +61,7 @@ docs = nbsphinx numpydoc sphinx-design + sphinx_gallery jupyter notebook graphviz From 837bacb842e2cbf093d5d00bd93c9fa9f2ec01fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Fr=C3=B6se?= Date: Fri, 7 Jul 2023 14:04:51 +0200 Subject: [PATCH 04/17] fix output of cells --- docs/conf.py | 8 +- environment.yml | 1 + .../algorithms/convert_images_to_2d.py | 9 +++ examples/examples/algorithms/dilate_image.py | 1 + .../examples/algorithms/nd_interpolation.py | 8 +- .../examples/core/InstrumentDescription.py | 16 ++++ .../core/{Tools.py => command_line_tools.py} | 29 ++++--- .../examples/core/config.json | 0 examples/examples/core/containers.py | 8 ++ examples/examples/core/provenance.py | 3 + examples/examples/core/table_writer_reader.py | 9 +++ .../examples/visualization/array_display.py | 1 + .../examples/visualization/camera_display.py | 26 +++---- .../tutorials/calibrated_data_exploration.py | 12 ++- examples/tutorials/coordinates_example.py | 6 +- examples/tutorials/ctapipe_handson.py | 43 +++++++++++ examples/tutorials/ctapipe_overview.py | 76 ++++++++++--------- examples/tutorials/raw_data_exploration.py | 7 ++ setup.cfg | 1 + 19 files changed, 196 insertions(+), 68 deletions(-) rename examples/examples/core/{Tools.py => command_line_tools.py} (85%) rename docs/user-guide/examples/Tools.json => examples/examples/core/config.json (100%) diff --git a/docs/conf.py b/docs/conf.py index 14e9f567bc1..9fe4be21003 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -154,7 +154,13 @@ def setup(app): "tutorials", ], # path to where to save gallery generated output "nested_sections": True, - "copyfile_regex": r"index.rst|.*\.png", + "copyfile_regex": r"index.rst|.*\.png|.*\.json", + "filename_pattern": r".*\.py", + "promote_jupyter_magic": True, + "line_numbers": True, + "default_thumb_file": "ctapipe_logo.png", + "pypandoc": True, + "matplotlib_animations": True, } diff --git a/environment.yml b/environment.yml index 11268bb1543..7f0f0dadeed 100644 --- a/environment.yml +++ b/environment.yml @@ -22,6 +22,7 @@ dependencies: - numpy>=1.22 - numpydoc - pandas + - pypandoc - pre-commit - psutil - pytables diff --git a/examples/examples/algorithms/convert_images_to_2d.py b/examples/examples/algorithms/convert_images_to_2d.py index 040baeab210..9903df780f1 100644 --- a/examples/examples/algorithms/convert_images_to_2d.py +++ b/examples/examples/algorithms/convert_images_to_2d.py @@ -11,6 +11,7 @@ from ctapipe.instrument import SubarrayDescription from ctapipe.visualization import CameraDisplay +###################################################################### # get the subarray from an example file subarray = SubarrayDescription.read("dataset://gamma_prod5.simtel.zst") @@ -32,6 +33,7 @@ ) _, image, _ = model.generate_image(geom, intensity=500, nsb_level_pe=3) +###################################################################### CameraDisplay(geom, image) @@ -42,10 +44,13 @@ image_square = geom.image_to_cartesian_representation(image) +###################################################################### plt.imshow(image_square) +###################################################################### image_1d = geom.image_from_cartesian_representation(image_square) +###################################################################### CameraDisplay(geom, image_1d) @@ -66,8 +71,10 @@ ) _, image, _ = model.generate_image(geom, intensity=5000) +###################################################################### CameraDisplay(geom, image) +###################################################################### image_square = geom.image_to_cartesian_representation(image) @@ -82,6 +89,8 @@ plt.imshow(image_square) +###################################################################### image_1d = geom.image_from_cartesian_representation(image_square) +###################################################################### disp = CameraDisplay(geom, image_1d) diff --git a/examples/examples/algorithms/dilate_image.py b/examples/examples/algorithms/dilate_image.py index b2d827d27cb..a5757be43b6 100644 --- a/examples/examples/algorithms/dilate_image.py +++ b/examples/examples/algorithms/dilate_image.py @@ -59,6 +59,7 @@ def show_dilate(mask, times=1): ) +###################################################################### plt.figure(figsize=(18, 3)) for ii in range(0, 6): diff --git a/examples/examples/algorithms/nd_interpolation.py b/examples/examples/algorithms/nd_interpolation.py index d830f7a2d11..21a03057e1a 100644 --- a/examples/examples/algorithms/nd_interpolation.py +++ b/examples/examples/algorithms/nd_interpolation.py @@ -139,8 +139,6 @@ ###################################################################### # In the high-stats central region, we get a nice smooth interpolation # function. Of course we can see that there are a few more steps to take -# before using this table: \* need to deal with cases where the table had -# low stats near the edges (smooth or extrapolate, or set bounds) \* may -# need to smooth the table even where there are sufficient stats, to avoid -# wiggles -# +# before using this table: \* +# - need to deal with cases where the table had low stats near the edges (smooth or extrapolate, or set bounds) +# - may need to smooth the table even where there are sufficient stats, to avoid wiggles diff --git a/examples/examples/core/InstrumentDescription.py b/examples/examples/core/InstrumentDescription.py index fc3857a9855..6d26ab7f9b3 100644 --- a/examples/examples/core/InstrumentDescription.py +++ b/examples/examples/core/InstrumentDescription.py @@ -27,6 +27,7 @@ subarray.info() +###################################################################### subarray.to_table() @@ -61,20 +62,27 @@ tel = subarray.tel[1] tel +###################################################################### tel.optics.mirror_area +###################################################################### tel.optics.n_mirror_tiles +###################################################################### tel.optics.equivalent_focal_length +###################################################################### tel.camera +###################################################################### tel.camera.geometry.pix_x +###################################################################### # %matplotlib inline CameraDisplay(tel.camera.geometry) +###################################################################### CameraDisplay(subarray.tel[98].camera.geometry) @@ -92,6 +100,7 @@ subarray.peek() +###################################################################### subarray.footprint @@ -102,11 +111,14 @@ subarray.telescope_types +###################################################################### subarray.camera_types +###################################################################### subarray.optics_types +###################################################################### center = SkyCoord("10.0 m", "2.0 m", "0.0 m", frame="groundframe") coords = subarray.tel_coords # a flat list of coordinates by tel_index coords.separation(center) @@ -134,8 +146,10 @@ subarray.tel_index_array +###################################################################### subarray.tel_index_array[[1, 5, 23]] +###################################################################### subarray.tel_indices[ 1 ] # this is a dict of tel_id -> tel_index, so we can only do one at once @@ -143,9 +157,11 @@ ids = subarray.get_tel_ids_for_type(subarray.telescope_types[0]) ids +###################################################################### idx = subarray.tel_ids_to_indices(ids) idx +###################################################################### subarray.tel_coords[idx] diff --git a/examples/examples/core/Tools.py b/examples/examples/core/command_line_tools.py similarity index 85% rename from examples/examples/core/Tools.py rename to examples/examples/core/command_line_tools.py index d97e0e7fe28..195a8e880a1 100644 --- a/examples/examples/core/Tools.py +++ b/examples/examples/core/command_line_tools.py @@ -17,6 +17,7 @@ from ctapipe.instrument import SubarrayDescription from ctapipe.utils import get_dataset_path +###################################################################### GAMMA_FILE = get_dataset_path("gamma_prod5.simtel.zst") @@ -80,8 +81,10 @@ class TelescopeWiseComponent(TelescopeComponent): ).tag(config=True) +###################################################################### MyComponent() +###################################################################### AdvancedComponent(infile="test.foo", outfile="out.foo") @@ -96,24 +99,19 @@ class TelescopeWiseComponent(TelescopeComponent): subarray = SubarrayDescription.read(GAMMA_FILE) subarray.info() +###################################################################### TelescopeWiseComponent(subarray=subarray) ###################################################################### # This TelescopeParameters can then be set using a list of patterns like: # -# .. code:: python # -# component.param = [ -# ("type", "LST*",3.0), -# ("type", "MST*", 2.0), -# (id, 25, 4.0) -# ] +# component.param = [("type", "LST*",3.0),("type", "MST*", 2.0),(id, 25, 4.0)] # # These get translated into per-telescope-id values once the subarray is # registered. After that one acccess the per-telescope id values via: # -# .. code:: python # # component.param.tel[tel_id] # @@ -184,6 +182,7 @@ def finish(self): tool = MyTool() tool +###################################################################### tool.print_help() @@ -202,7 +201,6 @@ def finish(self): # specified it’s read from ``sys.argv``, so the following is the same as # running: # -# .. code:: sh # # mytool --log_level=INFO --infile gamma_test.simtel.gz --iterations=3 # @@ -219,6 +217,7 @@ def finish(self): tool.log_format = "%(asctime)s : %(levelname)s [%(name)s %(funcName)s] %(message)s" +###################################################################### try: tool.run( argv=[ @@ -302,30 +301,41 @@ def finish(self): tool2 = MyTool() +###################################################################### try: - tool2.run(argv=["--config", "Tools.json"]) + tool2.run(argv=["--config", "config.json"]) except SystemExit as e: assert e.code == 0, f"Tool returned with error status {e}" +###################################################################### print(tool2.advanced.infile) +###################################################################### print(tool2.config) +###################################################################### tool2.is_setup +###################################################################### tool3 = MyTool() +###################################################################### tool3.is_setup +###################################################################### tool3.initialize(argv=[]) +###################################################################### tool3.is_setup +###################################################################### tool3 +###################################################################### tool.setup() tool +###################################################################### tool.comp2 @@ -336,6 +346,7 @@ def finish(self): tool.get_current_config() +###################################################################### tool.iterations = 12 tool.get_current_config() diff --git a/docs/user-guide/examples/Tools.json b/examples/examples/core/config.json similarity index 100% rename from docs/user-guide/examples/Tools.json rename to examples/examples/core/config.json diff --git a/examples/examples/core/containers.py b/examples/examples/core/containers.py index 360fcdc45c7..82242604b55 100644 --- a/examples/examples/core/containers.py +++ b/examples/examples/core/containers.py @@ -91,6 +91,7 @@ class EventContainer(Container): # help(EventContainer) +###################################################################### help(SubContainer) ###################################################################### @@ -100,8 +101,10 @@ class EventContainer(Container): ev.event_id = 100 ev.event_id +###################################################################### ev.as_dict() # by default only shows the bare items, not sub-containers (See later) +###################################################################### ev.as_dict(recursive=True) @@ -114,6 +117,7 @@ class EventContainer(Container): ev.tel[5] = TelContainer() ev.tel[42] = TelContainer() +###################################################################### # because we are using a default_factory to handle mutable defaults, the images are actually different: ev.tel[42].image is ev.tel[32] @@ -142,6 +146,7 @@ class DangerousContainer(Container): print(c2.image) print(c1.image is c2.image) +###################################################################### ev.tel @@ -152,6 +157,7 @@ class DangerousContainer(Container): ev.as_dict() +###################################################################### ev.as_dict(recursive=True, flatten=False) @@ -171,6 +177,7 @@ class DangerousContainer(Container): ev.tel[5].image[:] = 9 print(ev) +###################################################################### ev.reset() ev.as_dict(recursive=True) @@ -183,6 +190,7 @@ class DangerousContainer(Container): help(SimulatedShowerContainer) +###################################################################### shower = SimulatedShowerContainer() shower diff --git a/examples/examples/core/provenance.py b/examples/examples/core/provenance.py index d3c1d812450..2a5ae103bfe 100644 --- a/examples/examples/core/provenance.py +++ b/examples/examples/core/provenance.py @@ -43,6 +43,7 @@ p.finish_activity() +###################################################################### p.finished_activity_names @@ -110,6 +111,8 @@ def flatten(x, name=""): return out +###################################################################### d = dict(activity=p.provenance) +###################################################################### pprint(flatten_dict(d)) diff --git a/examples/examples/core/table_writer_reader.py b/examples/examples/core/table_writer_reader.py index d1d122477d3..e578c264123 100644 --- a/examples/examples/core/table_writer_reader.py +++ b/examples/examples/core/table_writer_reader.py @@ -41,6 +41,7 @@ from ctapipe.io import HDF5TableReader, HDF5TableWriter, read_table +###################################################################### class VariousTypesContainer(Container): a_int = Field(int, "some int value") @@ -72,6 +73,7 @@ def create_stream(n_event): yield data +###################################################################### for data in create_stream(2): for key, val in data.items(): @@ -103,6 +105,7 @@ def create_stream(n_event): h5_table.h5file.isopen == False +###################################################################### print(os.listdir()) ###################################################################### @@ -129,6 +132,7 @@ def create_stream(n_event): print(h5_table.h5file) +###################################################################### os.remove("container.h5") ###################################################################### @@ -204,6 +208,7 @@ def create_stream(n_event): table = read_table("container.h5", "/data_0/table") table[:5] +###################################################################### table.meta @@ -271,10 +276,14 @@ def read(mode): print(data.as_dict()) +###################################################################### read("r") +###################################################################### read("r+") +###################################################################### read("a") +###################################################################### read("w") diff --git a/examples/examples/visualization/array_display.py b/examples/examples/visualization/array_display.py index e5438ae7f7a..3dff647f7ae 100644 --- a/examples/examples/visualization/array_display.py +++ b/examples/examples/visualization/array_display.py @@ -22,6 +22,7 @@ plt.rcParams["figure.figsize"] = (8, 6) +###################################################################### tel_ids = list(range(1, 5)) + list(range(5, 20)) # just LSTs + one set of MSTs subarray = SubarrayDescription.read( diff --git a/examples/examples/visualization/camera_display.py b/examples/examples/visualization/camera_display.py index 92f66f6d5f5..01d7a18ee36 100644 --- a/examples/examples/visualization/camera_display.py +++ b/examples/examples/visualization/camera_display.py @@ -8,7 +8,6 @@ import astropy.units as u import matplotlib.pyplot as plt import numpy as np -from IPython import display from matplotlib.animation import FuncAnimation from matplotlib.colors import PowerNorm @@ -39,6 +38,7 @@ image, sig, bg = model.generate_image(geom, intensity=1500, nsb_level_pe=10) mask = tailcuts_clean(geom, image, picture_thresh=15, boundary_thresh=5) +###################################################################### geom @@ -297,10 +297,7 @@ def update(frame): # Create the animation and convert to a displayable video: anim = FuncAnimation(fig, func=update, frames=10, interval=200) -plt.close(fig) # so it doesn't display here -video = anim.to_html5_video() -display.display(display.HTML(video)) - +plt.show() ###################################################################### # Using CameraDisplays interactively @@ -308,19 +305,22 @@ def update(frame): # # ``CameraDisplays`` can be used interactivly whe displayed in a window, # and also when using Jupyter notebooks/lab with appropriate backends. -# + +###################################################################### # When this is the case, the same ``CameraDisplay`` object can be re-used. # We can’t show this here in the documentation, but creating an animation # when in a matplotlib window is quite easy! Try this in an interactive # ipython session: # + +###################################################################### # Running interactive displays in a matplotlib window # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # -# .. code:: sh -# -# ipython -i --maplotlib=auto -# +# ipython -i --maplotlib=auto + + +###################################################################### # That will open an ipython session with matplotlib graphics in a separate # thread, meaning that you can type code and interact with plots # simultaneneously. @@ -332,6 +332,7 @@ def update(frame): # +###################################################################### DATA = "dataset://gamma_20deg_0deg_run1___cta-prod5-lapalma_desert-2158m-LaPalma-dark_100evts.simtel.zst" with EventSource( @@ -376,10 +377,7 @@ def draw_sample(frame): anim = FuncAnimation(fig, func=draw_sample, frames=n_samp, interval=100) -plt.close(fig) # so it doesn't display here -video = anim.to_html5_video() -display.display(display.HTML(video)) - +plt.show() ###################################################################### # Making it clickable diff --git a/examples/tutorials/calibrated_data_exploration.py b/examples/tutorials/calibrated_data_exploration.py index caf80a298a1..40d9d4bb202 100644 --- a/examples/tutorials/calibrated_data_exploration.py +++ b/examples/tutorials/calibrated_data_exploration.py @@ -32,12 +32,16 @@ for event in source: print(event.index.event_id) +###################################################################### filename +###################################################################### source +###################################################################### event +###################################################################### print(event.r1) @@ -97,9 +101,10 @@ geometry = sub.tel[tel_id].camera.geometry image = event.dl1.tel[tel_id].image +###################################################################### disp = CameraDisplay(geometry, image=image) - +###################################################################### mask = tailcuts_clean( geometry, image, @@ -111,10 +116,12 @@ cleaned[~mask] = 0 disp = CameraDisplay(geometry, image=cleaned) +###################################################################### params = hillas_parameters(geometry, cleaned) print(params) params +###################################################################### params = hillas_parameters(geometry, cleaned) plt.figure(figsize=(10, 10)) @@ -126,6 +133,7 @@ plt.xlim(params.x.to_value(u.m) - 0.5, params.x.to_value(u.m) + 0.5) plt.ylim(params.y.to_value(u.m) - 0.5, params.y.to_value(u.m) + 0.5) +###################################################################### source.metadata @@ -150,9 +158,11 @@ ) # use a set here, so we can intersect it later tels_in_event +###################################################################### cam_ids = set(sub.get_tel_ids_for_type("MST_MST_NectarCam")) cam_ids +###################################################################### cams_in_event = tels_in_event.intersection(cam_ids) first_tel_id = list(cams_in_event)[0] tel = sub.tel[first_tel_id] diff --git a/examples/tutorials/coordinates_example.py b/examples/tutorials/coordinates_example.py index 8e8b18795c4..3d20aca5735 100644 --- a/examples/tutorials/coordinates_example.py +++ b/examples/tutorials/coordinates_example.py @@ -111,6 +111,7 @@ pix_y = geometry.pix_y focal_length = source.subarray.tel[tel_id].optics.equivalent_focal_length +###################################################################### telescope_pointing = SkyCoord( alt=event.pointing.tel[tel_id].altitude, az=event.pointing.tel[tel_id].azimuth, @@ -127,6 +128,7 @@ print(cam_coords) +###################################################################### plt.scatter(cam_coords.x, cam_coords.y) plt.title(f"Camera type: {geometry.name}") plt.xlabel(f"x / {cam_coords.x.unit}") @@ -212,6 +214,7 @@ ) telescope_coords = cam_coords.transform_to(telescope_frame) +###################################################################### wrap_angle = telescope_pointing.az + 180 * u.deg plt.axis("equal") @@ -286,6 +289,7 @@ nom_frame = NominalFrame(origin=array_pointing, obstime=obstime, location=location) +###################################################################### fig, ax = plt.subplots(figsize=(15, 10)) ax.set_aspect(1) @@ -356,7 +360,6 @@ # :alt: Ground Frame # # Ground Frame -# ###################################################################### @@ -389,7 +392,6 @@ # :alt: Tilted Ground Frame # # Tilted Ground Frame -# ###################################################################### diff --git a/examples/tutorials/ctapipe_handson.py b/examples/tutorials/ctapipe_handson.py index 157e97461d9..455cf88d9ba 100644 --- a/examples/tutorials/ctapipe_handson.py +++ b/examples/tutorials/ctapipe_handson.py @@ -28,26 +28,35 @@ # %matplotlib inline +###################################################################### path = utils.get_dataset_path("gamma_prod5.simtel.zst") +###################################################################### source = EventSource(path, max_events=5) for event in source: print(event.count, event.index.event_id, event.simulation.shower.energy) +###################################################################### event +###################################################################### event.r1 +###################################################################### for event in EventSource(path, max_events=5): print(event.count, event.r1.tel.keys()) +###################################################################### event.r0.tel[3] +###################################################################### r0tel = event.r0.tel[3] +###################################################################### r0tel.waveform +###################################################################### r0tel.waveform.shape @@ -58,14 +67,17 @@ plt.pcolormesh(r0tel.waveform[0]) +###################################################################### brightest_pixel = np.argmax(r0tel.waveform[0].sum(axis=1)) print(f"pixel {brightest_pixel} has sum {r0tel.waveform[0,1535].sum()}") +###################################################################### plt.plot(r0tel.waveform[0, brightest_pixel], label="channel 0 (high-gain)") plt.plot(r0tel.waveform[1, brightest_pixel], label="channel 1 (low-gain)") plt.legend() +###################################################################### @interact def view_waveform(chan=0, pix_id=brightest_pixel): plt.plot(r0tel.waveform[chan, pix_id]) @@ -89,33 +101,47 @@ def view_waveform(chan=0, pix_id=brightest_pixel): subarray = source.subarray +###################################################################### subarray +###################################################################### subarray.peek() +###################################################################### subarray.to_table() +###################################################################### subarray.tel[2] +###################################################################### subarray.tel[2].camera +###################################################################### subarray.tel[2].optics +###################################################################### tel = subarray.tel[2] +###################################################################### tel.camera +###################################################################### tel.optics +###################################################################### tel.camera.geometry.pix_x +###################################################################### tel.camera.geometry.to_table() +###################################################################### tel.optics.mirror_area +###################################################################### disp = CameraDisplay(tel.camera.geometry) +###################################################################### disp = CameraDisplay(tel.camera.geometry) disp.image = r0tel.waveform[ 0, :, 10 @@ -136,20 +162,27 @@ def view_waveform(chan=0, pix_id=brightest_pixel): calib = CameraCalibrator(subarray=subarray) +###################################################################### for event in EventSource(path, max_events=5): calib(event) # fills in r1, dl0, and dl1 print(event.dl1.tel.keys()) +###################################################################### event.dl1.tel[3] +###################################################################### dl1tel = event.dl1.tel[3] +###################################################################### dl1tel.image.shape # note this will be gain-selected in next version, so will be just 1D array of 1855 +###################################################################### dl1tel.peak_time +###################################################################### CameraDisplay(tel.camera.geometry, image=dl1tel.image) +###################################################################### CameraDisplay(tel.camera.geometry, image=dl1tel.peak_time) @@ -162,20 +195,25 @@ def view_waveform(chan=0, pix_id=brightest_pixel): mask = tailcuts_clean(tel.camera.geometry, image, picture_thresh=10, boundary_thresh=5) mask +###################################################################### CameraDisplay(tel.camera.geometry, image=mask) +###################################################################### cleaned = image.copy() cleaned[~mask] = 0 +###################################################################### disp = CameraDisplay(tel.camera.geometry, image=cleaned) disp.cmap = plt.cm.coolwarm disp.add_colorbar() plt.xlim(0.5, 1.0) plt.ylim(-1.0, 0.0) +###################################################################### params = hillas_parameters(tel.camera.geometry, cleaned) print(params) +###################################################################### disp = CameraDisplay(tel.camera.geometry, image=cleaned) disp.cmap = plt.cm.coolwarm disp.add_colorbar() @@ -203,6 +241,7 @@ def view_waveform(chan=0, pix_id=brightest_pixel): subarray.telescope_types +###################################################################### subarray.get_tel_ids_for_type("LST_LST_LSTCam") @@ -213,6 +252,7 @@ def view_waveform(chan=0, pix_id=brightest_pixel): data = utils.get_dataset_path("gamma_prod5.simtel.zst") source = EventSource(data) # remove the max_events limit to get more stats +###################################################################### for event in source: calib(event) @@ -223,6 +263,7 @@ def view_waveform(chan=0, pix_id=brightest_pixel): params = hillas_parameters(tel.camera.geometry[mask], tel_data.image[mask]) +###################################################################### with HDF5TableWriter(filename="hillas.h5", group_name="dl1", overwrite=True) as writer: source = EventSource(data, allowed_tels=[1, 2, 3, 4], max_events=10) @@ -243,9 +284,11 @@ def view_waveform(chan=0, pix_id=brightest_pixel): glob.glob("*.h5") +###################################################################### hillas = pd.read_hdf("hillas.h5", key="/dl1/hillas") hillas +###################################################################### _ = hillas.hist(figsize=(8, 8)) diff --git a/examples/tutorials/ctapipe_overview.py b/examples/tutorials/ctapipe_overview.py index 0e7866d91e0..e96cea70f45 100644 --- a/examples/tutorials/ctapipe_overview.py +++ b/examples/tutorials/ctapipe_overview.py @@ -6,40 +6,13 @@ ###################################################################### -# .. container:: -# -# .. raw:: html -# -#

-# -# Initially presented @ LST Analysis Bootcamp -# -# .. raw:: html -# -#

-# -# .. raw:: html -# -#

-# -# Padova, 26.11.2018 -# -# .. raw:: html -# -#

-# -# .. raw:: html -# -#

-# -# Maximilian Nöthe (@maxnoe) & Kai A. Brügge (@mackaiver) -# -# .. raw:: html -# -#

-# +# Initially presented @ LST Analysis Bootcamp +# in Padova, 26.11.2018 +# by Maximilian Nöthe (@maxnoe) & Kai A. Brügge (@mackaiver) + import tempfile +import timeit from copy import deepcopy import astropy.units as u @@ -70,6 +43,7 @@ # %matplotlib inline +###################################################################### plt.rcParams["figure.figsize"] = (12, 8) plt.rcParams["font.size"] = 14 plt.rcParams["figure.figsize"] @@ -178,6 +152,7 @@ print(type(source)) +###################################################################### for event in source: print( "Id: {}, E = {:1.3f}, Telescopes: {}".format( @@ -193,8 +168,10 @@ event +###################################################################### source.subarray.camera_types +###################################################################### len(event.r0.tel), len(event.r1.tel) @@ -209,6 +186,7 @@ calibrator = CameraCalibrator(subarray=source.subarray) +###################################################################### calibrator(event) @@ -221,16 +199,20 @@ event.dl1.tel.keys() +###################################################################### tel_id = 130 +###################################################################### geometry = source.subarray.tel[tel_id].camera.geometry dl1 = event.dl1.tel[tel_id] geometry, dl1 +###################################################################### dl1.image +###################################################################### display = CameraDisplay(geometry) # right now, there might be one image per gain channel. @@ -253,6 +235,7 @@ "NectarCam": (4, 8, 2), } +###################################################################### boundary, picture, min_neighbors = cleaning_level[geometry.name] clean = tailcuts_clean( @@ -263,6 +246,7 @@ min_number_picture_neighbors=min_neighbors, ) +###################################################################### fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5)) d1 = CameraDisplay(geometry, ax=ax1) @@ -291,6 +275,7 @@ print(hillas) +###################################################################### display = CameraDisplay(geometry) # set "unclean" pixels to 0 @@ -302,10 +287,12 @@ display.overlay_moments(hillas, color="xkcd:red") +###################################################################### timing = timing_parameters(geometry, dl1.image, dl1.peak_time, hillas, clean) print(timing) +###################################################################### long, trans = camera_to_shower_coordinates( geometry.pix_x, geometry.pix_y, hillas.x, hillas.y, hillas.psi ) @@ -313,17 +300,21 @@ plt.plot(long[clean], dl1.peak_time[clean], "o") plt.plot(long[clean], timing.slope * long[clean] + timing.intercept) +###################################################################### leakage = leakage_parameters(geometry, dl1.image, clean) print(leakage) +###################################################################### disp = CameraDisplay(geometry) disp.image = dl1.image disp.highlight_pixels(geometry.get_border_pixel_mask(1), linewidth=2, color="xkcd:red") +###################################################################### n_islands, island_id = number_of_islands(geometry, clean) print(n_islands) +###################################################################### conc = concentration_parameters(geometry, dl1.image, hillas) print(conc) @@ -409,10 +400,12 @@ writer(event) +###################################################################### loader = TableLoader(f.name, load_dl2=True, load_simulated=True) events = loader.read_subarray_events() +###################################################################### theta = angular_separation( events["HillasReconstructor_az"].quantity, events["HillasReconstructor_alt"].quantity, @@ -477,6 +470,7 @@ dl1_table = loader.read_telescope_events(["LST_LST_LSTCam"]) +###################################################################### plt.scatter( np.log10(dl1_table["true_energy"].quantity / u.TeV), np.log10(dl1_table["hillas_intensity"]), @@ -546,6 +540,7 @@ ) image += new_image +###################################################################### clean = tailcuts_clean( geometry, image, @@ -554,12 +549,14 @@ min_number_picture_neighbors=2, ) +###################################################################### disp = CameraDisplay(geometry) disp.image = image disp.highlight_pixels(clean, color="xkcd:red", linewidth=1.5) disp.add_colorbar() +###################################################################### def num_islands_python(camera, clean): """A breadth first search to find connected islands of neighboring pixels in the cleaning set""" @@ -598,9 +595,11 @@ def num_islands_python(camera, clean): return n_islands, island_ids +###################################################################### n_islands, island_ids = num_islands_python(geometry, clean) +###################################################################### cmap = plt.get_cmap("Paired") cmap = ListedColormap(cmap.colors[:n_islands]) cmap.set_under("k") @@ -611,9 +610,11 @@ def num_islands_python(camera, clean): disp.set_limits_minmax(0.5, n_islands + 0.5) disp.add_colorbar() -# %timeit num_islands_python(geometry, clean) +###################################################################### +timeit.timeit(lambda: num_islands_python(geometry, clean), number=1000) / 1000 +###################################################################### def num_islands_scipy(geometry, clean): neighbors = geometry.neighbor_matrix_sparse @@ -626,16 +627,18 @@ def num_islands_scipy(geometry, clean): return num_islands, island_ids +###################################################################### n_islands_s, island_ids_s = num_islands_scipy(geometry, clean) +###################################################################### disp = CameraDisplay(geometry) disp.image = island_ids_s disp.cmap = cmap disp.set_limits_minmax(0.5, n_islands_s + 0.5) disp.add_colorbar() -# %timeit num_islands_scipy(geometry, clean) - +###################################################################### +timeit.timeit(lambda: num_islands_scipy(geometry, clean), number=10000) / 10000 ###################################################################### # **A lot less code, and a factor 3 speed improvement** @@ -646,4 +649,5 @@ def num_islands_scipy(geometry, clean): # Finally, current ctapipe implementation is using numba: # -# %timeit number_of_islands(geometry, clean) +###################################################################### +timeit.timeit(lambda: number_of_islands(geometry, clean), number=100000) / 100000 diff --git a/examples/tutorials/raw_data_exploration.py b/examples/tutorials/raw_data_exploration.py index 3e0ad08f3de..7fa6dc3c6b4 100644 --- a/examples/tutorials/raw_data_exploration.py +++ b/examples/tutorials/raw_data_exploration.py @@ -73,6 +73,7 @@ print(event.simulation.shower) +###################################################################### print(event.r0.tel.keys()) @@ -102,12 +103,16 @@ event.simulation.shower.energy +###################################################################### event.simulation.shower.energy.to("GeV") +###################################################################### event.simulation.shower.energy.to("J") +###################################################################### event.simulation.shower.alt +###################################################################### print("Altitude in degrees:", event.simulation.shower.alt.deg) @@ -198,6 +203,7 @@ peds = data[:, 19:24].mean(axis=1) # mean of samples 20 to 29 for all pixels sums = data[:, 5:9].sum(axis=1) / (13 - 8) # simple sum integration +###################################################################### phist = plt.hist(peds, bins=50, range=[0, 150]) plt.title("Pedestal Distribution of all pixels for a single event") @@ -239,6 +245,7 @@ camgeom = source.subarray.tel[24].camera.geometry +###################################################################### title = "CT24, run {} event {} ped-sub".format(event.index.obs_id, event.index.event_id) disp = CameraDisplay(camgeom, title=title) disp.image = sums - peds diff --git a/setup.cfg b/setup.cfg index 6b7f6e343a6..456cabe3a0d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -68,6 +68,7 @@ docs = pandas ipython ffmpeg-python + pypandoc dev = setuptools_scm[toml] From a5d140ad7737ce7588cfd886df19735f7ad324ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Fr=C3=B6se?= Date: Fri, 8 Sep 2023 12:30:34 +0200 Subject: [PATCH 05/17] fix path to logo --- docs/conf.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 9fe4be21003..de105472b71 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -150,15 +150,15 @@ def setup(app): "../examples/tutorials", ], # path to your example scripts "gallery_dirs": [ - "examples", - "tutorials", + "user-guide/examples", + "user-guide/tutorials", ], # path to where to save gallery generated output "nested_sections": True, "copyfile_regex": r"index.rst|.*\.png|.*\.json", "filename_pattern": r".*\.py", "promote_jupyter_magic": True, "line_numbers": True, - "default_thumb_file": "ctapipe_logo.png", + "default_thumb_file": "_static/ctapipe_logo.png", "pypandoc": True, "matplotlib_animations": True, } @@ -216,10 +216,10 @@ def setup(app): ".DS_Store", "**.ipynb_checkpoints", "changes", - "examples/*/*.ipynb", - "examples/*/*.py", - "tutorials/*.ipynb", - "tutorials/*.py", + "user-guide/examples/*/*.ipynb", + "user-guide/examples/*/*.py", + "user-guide/tutorials/*.ipynb", + "user-guide/tutorials/*.py", ] # The name of the Pygments (syntax highlighting) style to use. From 2e49c85525829856f4d679d656b348ac36276d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Fr=C3=B6se?= Date: Fri, 8 Sep 2023 12:30:47 +0200 Subject: [PATCH 06/17] typo --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 456cabe3a0d..dbd35656b60 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,7 +61,7 @@ docs = nbsphinx numpydoc sphinx-design - sphinx_gallery + sphinx-gallery jupyter notebook graphviz From 0e9058d6a1c2e05957e50b89617a22034a4d80f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Fr=C3=B6se?= Date: Fri, 8 Sep 2023 13:43:01 +0200 Subject: [PATCH 07/17] remove notebooks --- .gitignore | 3 --- docs/user-guide/tutorials/ground_frame.png | Bin 100340 -> 0 bytes .../tutorials/tilted_ground_frame.png | Bin 129772 -> 0 bytes 3 files changed, 3 deletions(-) delete mode 100644 docs/user-guide/tutorials/ground_frame.png delete mode 100644 docs/user-guide/tutorials/tilted_ground_frame.png diff --git a/.gitignore b/.gitignore index e098b72a3dc..9ca4ab0cd26 100644 --- a/.gitignore +++ b/.gitignore @@ -86,7 +86,4 @@ distribute-*.tar.gz target .mypy_cache -examples/notebooks/*.html -examples/notebooks/*.png - provenance.log diff --git a/docs/user-guide/tutorials/ground_frame.png b/docs/user-guide/tutorials/ground_frame.png deleted file mode 100644 index 9545a1dfd67d055d5e677906e96240cc5f598c9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 100340 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%0A~VBlqG<^ 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

$t0tDZ-%VnmZBHMHy&|ch!{B+2&IUZ?qej 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

8%N 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 From 1753542db3315786aedabe22128e94e55005729b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Fr=C3=B6se?= Date: Fri, 8 Sep 2023 13:44:04 +0200 Subject: [PATCH 08/17] create notebook folders --- docs/Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/Makefile b/docs/Makefile index 387d531e5bc..4cbd0908bee 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -15,9 +15,15 @@ help: # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile +%: Makefile examples tutorials @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) +examples: + mkdir user-guide/examples + +tutorials: + mkdir user-guide/tutorials + clean: rm -rf api @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) From c6662cd2d13498aac27cd4c70989cec0a5c557d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Fr=C3=B6se?= Date: Fri, 8 Sep 2023 14:52:09 +0200 Subject: [PATCH 09/17] flatten structure --- docs/Makefile | 8 +++----- docs/conf.py | 17 ++++++++++++++--- docs/user-guide/index.rst | 1 - examples/examples/index.rst | 9 --------- .../tutorials/README.rst} | 0 .../tutorials/calibrated_data_exploration.py | 0 .../tutorials/coordinates_example.py | 0 .../tutorials/ctapipe_handson.py | 0 .../tutorials/ctapipe_overview.py | 0 .../{ => examples}/tutorials/ground_frame.png | Bin .../tutorials/raw_data_exploration.py | 0 .../{ => examples}/tutorials/theta_square.py | 0 .../tutorials/tilted_ground_frame.png | Bin 13 files changed, 17 insertions(+), 18 deletions(-) delete mode 100644 examples/examples/index.rst rename examples/{tutorials/README.txt => examples/tutorials/README.rst} (100%) rename examples/{ => examples}/tutorials/calibrated_data_exploration.py (100%) rename examples/{ => examples}/tutorials/coordinates_example.py (100%) rename examples/{ => examples}/tutorials/ctapipe_handson.py (100%) rename examples/{ => examples}/tutorials/ctapipe_overview.py (100%) rename examples/{ => examples}/tutorials/ground_frame.png (100%) rename examples/{ => examples}/tutorials/raw_data_exploration.py (100%) rename examples/{ => examples}/tutorials/theta_square.py (100%) rename examples/{ => examples}/tutorials/tilted_ground_frame.png (100%) diff --git a/docs/Makefile b/docs/Makefile index 4cbd0908bee..238eb0226ea 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -15,17 +15,15 @@ help: # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile examples tutorials +%: Makefile examples @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) examples: - mkdir user-guide/examples - -tutorials: - mkdir user-guide/tutorials + mkdir -p user-guide/examples clean: rm -rf api + rm -rf user-guide/examples @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py index de105472b71..9f453a9dbfc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,6 +29,9 @@ # Get configuration information from setup.cfg from configparser import ConfigParser +# Sphinx gallery +from sphinx_gallery.sorting import ExplicitOrder, FileNameSortKey + import ctapipe setup_cfg = ConfigParser() @@ -144,16 +147,24 @@ def setup(app): ] # Sphinx gallery config + sphinx_gallery_conf = { "examples_dirs": [ "../examples/examples", - "../examples/tutorials", ], # path to your example scripts "gallery_dirs": [ "user-guide/examples", - "user-guide/tutorials", ], # path to where to save gallery generated output - "nested_sections": True, + "subsection_order": ExplicitOrder( + [ + "../examples/examples/algorithms", + "../examples/examples/core", + "../examples/examples/visualization", + "../examples/examples/tutorials", + ] + ), + "within_subsection_order": FileNameSortKey, + "nested_sections": False, "copyfile_regex": r"index.rst|.*\.png|.*\.json", "filename_pattern": r".*\.py", "promote_jupyter_magic": True, diff --git a/docs/user-guide/index.rst b/docs/user-guide/index.rst index 853408d8363..7b738f5c583 100644 --- a/docs/user-guide/index.rst +++ b/docs/user-guide/index.rst @@ -9,6 +9,5 @@ User Guide getting-started tools data_models/index - tutorials/index examples/index FAQ diff --git a/examples/examples/index.rst b/examples/examples/index.rst deleted file mode 100644 index 18496722bf3..00000000000 --- a/examples/examples/index.rst +++ /dev/null @@ -1,9 +0,0 @@ -Examples gallery -================ - -The examples gallery provides an overview of different ctapipe modules and how to use them. - -.. toctree:: - visualization/index - algorithms/index - core/index diff --git a/examples/tutorials/README.txt b/examples/examples/tutorials/README.rst similarity index 100% rename from examples/tutorials/README.txt rename to examples/examples/tutorials/README.rst diff --git a/examples/tutorials/calibrated_data_exploration.py b/examples/examples/tutorials/calibrated_data_exploration.py similarity index 100% rename from examples/tutorials/calibrated_data_exploration.py rename to examples/examples/tutorials/calibrated_data_exploration.py diff --git a/examples/tutorials/coordinates_example.py b/examples/examples/tutorials/coordinates_example.py similarity index 100% rename from examples/tutorials/coordinates_example.py rename to examples/examples/tutorials/coordinates_example.py diff --git a/examples/tutorials/ctapipe_handson.py b/examples/examples/tutorials/ctapipe_handson.py similarity index 100% rename from examples/tutorials/ctapipe_handson.py rename to examples/examples/tutorials/ctapipe_handson.py diff --git a/examples/tutorials/ctapipe_overview.py b/examples/examples/tutorials/ctapipe_overview.py similarity index 100% rename from examples/tutorials/ctapipe_overview.py rename to examples/examples/tutorials/ctapipe_overview.py diff --git a/examples/tutorials/ground_frame.png b/examples/examples/tutorials/ground_frame.png similarity index 100% rename from examples/tutorials/ground_frame.png rename to examples/examples/tutorials/ground_frame.png diff --git a/examples/tutorials/raw_data_exploration.py b/examples/examples/tutorials/raw_data_exploration.py similarity index 100% rename from examples/tutorials/raw_data_exploration.py rename to examples/examples/tutorials/raw_data_exploration.py diff --git a/examples/tutorials/theta_square.py b/examples/examples/tutorials/theta_square.py similarity index 100% rename from examples/tutorials/theta_square.py rename to examples/examples/tutorials/theta_square.py diff --git a/examples/tutorials/tilted_ground_frame.png b/examples/examples/tutorials/tilted_ground_frame.png similarity index 100% rename from examples/tutorials/tilted_ground_frame.png rename to examples/examples/tutorials/tilted_ground_frame.png From 743ee40a3451c16687c99710f1663662e21ccf63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Fr=C3=B6se?= Date: Fri, 8 Sep 2023 16:01:57 +0200 Subject: [PATCH 10/17] move examples to user-guide --- docs/Makefile | 13 ++++++++----- docs/conf.py | 17 ++++++++--------- docs/user-guide/examples/README.txt | 6 ++++++ .../user-guide/examples/algorithms/README.txt | 0 .../algorithms/convert_images_to_2d.py | 0 .../examples/algorithms/dilate_image.py | 0 .../examples/algorithms/nd_interpolation.py | 0 .../examples/core/InstrumentDescription.py | 0 .../user-guide/examples/core/README.txt | 0 .../examples/core/command_line_tools.py | 0 .../user-guide}/examples/core/config.json | 0 .../user-guide}/examples/core/containers.py | 0 .../user-guide}/examples/core/provenance.py | 0 .../examples/core/table_writer_reader.py | 0 .../user-guide/examples/tutorials/README.txt | 4 ++-- .../tutorials/calibrated_data_exploration.py | 0 .../examples/tutorials/coordinates_example.py | 0 .../examples/tutorials/ctapipe_handson.py | 0 .../examples/tutorials/ctapipe_overview.py | 0 .../examples/tutorials/ground_frame.png | Bin .../examples/tutorials/raw_data_exploration.py | 0 .../examples/tutorials/theta_square.py | 0 .../examples/tutorials/tilted_ground_frame.png | Bin .../examples/visualization/README.txt | 0 .../examples/visualization/array_display.py | 0 .../examples/visualization/camera_display.py | 0 examples/examples/README.txt | 6 ------ 27 files changed, 24 insertions(+), 22 deletions(-) create mode 100644 docs/user-guide/examples/README.txt rename examples/examples/algorithms/README.rst => docs/user-guide/examples/algorithms/README.txt (100%) rename {examples => docs/user-guide}/examples/algorithms/convert_images_to_2d.py (100%) rename {examples => docs/user-guide}/examples/algorithms/dilate_image.py (100%) rename {examples => docs/user-guide}/examples/algorithms/nd_interpolation.py (100%) rename {examples => docs/user-guide}/examples/core/InstrumentDescription.py (100%) rename examples/examples/core/README.rst => docs/user-guide/examples/core/README.txt (100%) rename {examples => docs/user-guide}/examples/core/command_line_tools.py (100%) rename {examples => docs/user-guide}/examples/core/config.json (100%) rename {examples => docs/user-guide}/examples/core/containers.py (100%) rename {examples => docs/user-guide}/examples/core/provenance.py (100%) rename {examples => docs/user-guide}/examples/core/table_writer_reader.py (100%) rename examples/examples/tutorials/README.rst => docs/user-guide/examples/tutorials/README.txt (70%) rename {examples => docs/user-guide}/examples/tutorials/calibrated_data_exploration.py (100%) rename {examples => docs/user-guide}/examples/tutorials/coordinates_example.py (100%) rename {examples => docs/user-guide}/examples/tutorials/ctapipe_handson.py (100%) rename {examples => docs/user-guide}/examples/tutorials/ctapipe_overview.py (100%) rename {examples => docs/user-guide}/examples/tutorials/ground_frame.png (100%) rename {examples => docs/user-guide}/examples/tutorials/raw_data_exploration.py (100%) rename {examples => docs/user-guide}/examples/tutorials/theta_square.py (100%) rename {examples => docs/user-guide}/examples/tutorials/tilted_ground_frame.png (100%) rename examples/examples/visualization/README.rst => docs/user-guide/examples/visualization/README.txt (100%) rename {examples => docs/user-guide}/examples/visualization/array_display.py (100%) rename {examples => docs/user-guide}/examples/visualization/camera_display.py (100%) delete mode 100644 examples/examples/README.txt diff --git a/docs/Makefile b/docs/Makefile index 238eb0226ea..f619b16a1fd 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -15,15 +15,18 @@ help: # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile examples +%: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -examples: - mkdir -p user-guide/examples - clean: rm -rf api - rm -rf user-guide/examples + rm -rf user-guide/examples/*.zip + rm -rf user-guide/examples/*.rst + rm -rf user-guide/examples/*/*.ipynb + rm -rf user-guide/examples/*/*.py.md5 + rm -rf user-guide/examples/*/*.pickle + rm -rf user-guide/examples/*/*.rst + rm -rf user-guide/examples/*/images @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py index 9f453a9dbfc..cc1ed9538c9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -150,22 +150,22 @@ def setup(app): sphinx_gallery_conf = { "examples_dirs": [ - "../examples/examples", + "user-guide/examples", ], # path to your example scripts "gallery_dirs": [ "user-guide/examples", ], # path to where to save gallery generated output "subsection_order": ExplicitOrder( [ - "../examples/examples/algorithms", - "../examples/examples/core", - "../examples/examples/visualization", - "../examples/examples/tutorials", + "user-guide/examples/tutorials", + "user-guide/examples/algorithms", + "user-guide/examples/core", + "user-guide/examples/visualization", ] ), "within_subsection_order": FileNameSortKey, "nested_sections": False, - "copyfile_regex": r"index.rst|.*\.png|.*\.json", + # "copyfile_regex": r".*\.rst|.*\.png|.*\.json", "filename_pattern": r".*\.py", "promote_jupyter_magic": True, "line_numbers": True, @@ -229,8 +229,7 @@ def setup(app): "changes", "user-guide/examples/*/*.ipynb", "user-guide/examples/*/*.py", - "user-guide/tutorials/*.ipynb", - "user-guide/tutorials/*.py", + "user-guide/examples/*/README.rst", ] # The name of the Pygments (syntax highlighting) style to use. @@ -245,7 +244,7 @@ def setup(app): # Define the json_url for our version switcher. json_url = "https://ctapipe.readthedocs.io/en/latest/_static/switcher.json" -# Define the version we use for matching in the version switcher. +# Define the version we use for matching in the version switcher., version_match = os.getenv("READTHEDOCS_VERSION") # If READTHEDOCS_VERSION doesn't exist, we're not on RTD # If it is an integer, we're in a PR build and the version isn't correct. diff --git a/docs/user-guide/examples/README.txt b/docs/user-guide/examples/README.txt new file mode 100644 index 00000000000..7bdf1b8226f --- /dev/null +++ b/docs/user-guide/examples/README.txt @@ -0,0 +1,6 @@ +.. _tutorials_and_examples_gallery: + +Tutorials and Examples gallery +============================== + +This gallery provides an overview of different ctapipe modules and how to use them. diff --git a/examples/examples/algorithms/README.rst b/docs/user-guide/examples/algorithms/README.txt similarity index 100% rename from examples/examples/algorithms/README.rst rename to docs/user-guide/examples/algorithms/README.txt diff --git a/examples/examples/algorithms/convert_images_to_2d.py b/docs/user-guide/examples/algorithms/convert_images_to_2d.py similarity index 100% rename from examples/examples/algorithms/convert_images_to_2d.py rename to docs/user-guide/examples/algorithms/convert_images_to_2d.py diff --git a/examples/examples/algorithms/dilate_image.py b/docs/user-guide/examples/algorithms/dilate_image.py similarity index 100% rename from examples/examples/algorithms/dilate_image.py rename to docs/user-guide/examples/algorithms/dilate_image.py diff --git a/examples/examples/algorithms/nd_interpolation.py b/docs/user-guide/examples/algorithms/nd_interpolation.py similarity index 100% rename from examples/examples/algorithms/nd_interpolation.py rename to docs/user-guide/examples/algorithms/nd_interpolation.py diff --git a/examples/examples/core/InstrumentDescription.py b/docs/user-guide/examples/core/InstrumentDescription.py similarity index 100% rename from examples/examples/core/InstrumentDescription.py rename to docs/user-guide/examples/core/InstrumentDescription.py diff --git a/examples/examples/core/README.rst b/docs/user-guide/examples/core/README.txt similarity index 100% rename from examples/examples/core/README.rst rename to docs/user-guide/examples/core/README.txt diff --git a/examples/examples/core/command_line_tools.py b/docs/user-guide/examples/core/command_line_tools.py similarity index 100% rename from examples/examples/core/command_line_tools.py rename to docs/user-guide/examples/core/command_line_tools.py diff --git a/examples/examples/core/config.json b/docs/user-guide/examples/core/config.json similarity index 100% rename from examples/examples/core/config.json rename to docs/user-guide/examples/core/config.json diff --git a/examples/examples/core/containers.py b/docs/user-guide/examples/core/containers.py similarity index 100% rename from examples/examples/core/containers.py rename to docs/user-guide/examples/core/containers.py diff --git a/examples/examples/core/provenance.py b/docs/user-guide/examples/core/provenance.py similarity index 100% rename from examples/examples/core/provenance.py rename to docs/user-guide/examples/core/provenance.py diff --git a/examples/examples/core/table_writer_reader.py b/docs/user-guide/examples/core/table_writer_reader.py similarity index 100% rename from examples/examples/core/table_writer_reader.py rename to docs/user-guide/examples/core/table_writer_reader.py diff --git a/examples/examples/tutorials/README.rst b/docs/user-guide/examples/tutorials/README.txt similarity index 70% rename from examples/examples/tutorials/README.rst rename to docs/user-guide/examples/tutorials/README.txt index 58f643a3722..e8b6be88e86 100644 --- a/examples/examples/tutorials/README.rst +++ b/docs/user-guide/examples/tutorials/README.txt @@ -1,6 +1,6 @@ -.. _tutorials_gallery: +.. _tutorials: -Tutorials gallery +Tutorials ================= This gallery contains different tutorials of different use cases for ctapipe. diff --git a/examples/examples/tutorials/calibrated_data_exploration.py b/docs/user-guide/examples/tutorials/calibrated_data_exploration.py similarity index 100% rename from examples/examples/tutorials/calibrated_data_exploration.py rename to docs/user-guide/examples/tutorials/calibrated_data_exploration.py diff --git a/examples/examples/tutorials/coordinates_example.py b/docs/user-guide/examples/tutorials/coordinates_example.py similarity index 100% rename from examples/examples/tutorials/coordinates_example.py rename to docs/user-guide/examples/tutorials/coordinates_example.py diff --git a/examples/examples/tutorials/ctapipe_handson.py b/docs/user-guide/examples/tutorials/ctapipe_handson.py similarity index 100% rename from examples/examples/tutorials/ctapipe_handson.py rename to docs/user-guide/examples/tutorials/ctapipe_handson.py diff --git a/examples/examples/tutorials/ctapipe_overview.py b/docs/user-guide/examples/tutorials/ctapipe_overview.py similarity index 100% rename from examples/examples/tutorials/ctapipe_overview.py rename to docs/user-guide/examples/tutorials/ctapipe_overview.py diff --git a/examples/examples/tutorials/ground_frame.png b/docs/user-guide/examples/tutorials/ground_frame.png similarity index 100% rename from examples/examples/tutorials/ground_frame.png rename to docs/user-guide/examples/tutorials/ground_frame.png diff --git a/examples/examples/tutorials/raw_data_exploration.py b/docs/user-guide/examples/tutorials/raw_data_exploration.py similarity index 100% rename from examples/examples/tutorials/raw_data_exploration.py rename to docs/user-guide/examples/tutorials/raw_data_exploration.py diff --git a/examples/examples/tutorials/theta_square.py b/docs/user-guide/examples/tutorials/theta_square.py similarity index 100% rename from examples/examples/tutorials/theta_square.py rename to docs/user-guide/examples/tutorials/theta_square.py diff --git a/examples/examples/tutorials/tilted_ground_frame.png b/docs/user-guide/examples/tutorials/tilted_ground_frame.png similarity index 100% rename from examples/examples/tutorials/tilted_ground_frame.png rename to docs/user-guide/examples/tutorials/tilted_ground_frame.png diff --git a/examples/examples/visualization/README.rst b/docs/user-guide/examples/visualization/README.txt similarity index 100% rename from examples/examples/visualization/README.rst rename to docs/user-guide/examples/visualization/README.txt diff --git a/examples/examples/visualization/array_display.py b/docs/user-guide/examples/visualization/array_display.py similarity index 100% rename from examples/examples/visualization/array_display.py rename to docs/user-guide/examples/visualization/array_display.py diff --git a/examples/examples/visualization/camera_display.py b/docs/user-guide/examples/visualization/camera_display.py similarity index 100% rename from examples/examples/visualization/camera_display.py rename to docs/user-guide/examples/visualization/camera_display.py diff --git a/examples/examples/README.txt b/examples/examples/README.txt deleted file mode 100644 index d3c2423283f..00000000000 --- a/examples/examples/README.txt +++ /dev/null @@ -1,6 +0,0 @@ -.. _examples_gallery: - -Examples gallery -================ - -The examples gallery provides an overview of different ctapipe modules and how to use them. From 3a504ec6dc2fa23387d5225a5a9a262a0275b051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Fr=C3=B6se?= Date: Fri, 8 Sep 2023 17:13:37 +0200 Subject: [PATCH 11/17] subsections --- docs/user-guide/examples/algorithms/README.txt | 2 +- docs/user-guide/examples/core/README.txt | 2 +- docs/user-guide/examples/tutorials/README.txt | 2 +- docs/user-guide/examples/visualization/README.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/user-guide/examples/algorithms/README.txt b/docs/user-guide/examples/algorithms/README.txt index 4ddc214f0d3..c798294f73d 100644 --- a/docs/user-guide/examples/algorithms/README.txt +++ b/docs/user-guide/examples/algorithms/README.txt @@ -1,4 +1,4 @@ Algorithms -========== +---------- .. _algorithms-examples-gallery: diff --git a/docs/user-guide/examples/core/README.txt b/docs/user-guide/examples/core/README.txt index 5bcae85324a..8042b0dec57 100644 --- a/docs/user-guide/examples/core/README.txt +++ b/docs/user-guide/examples/core/README.txt @@ -1,4 +1,4 @@ Core Functionality -================== +------------------ .. _core-examples-gallery: diff --git a/docs/user-guide/examples/tutorials/README.txt b/docs/user-guide/examples/tutorials/README.txt index e8b6be88e86..c1c31978d8e 100644 --- a/docs/user-guide/examples/tutorials/README.txt +++ b/docs/user-guide/examples/tutorials/README.txt @@ -1,6 +1,6 @@ .. _tutorials: Tutorials -================= +--------- This gallery contains different tutorials of different use cases for ctapipe. diff --git a/docs/user-guide/examples/visualization/README.txt b/docs/user-guide/examples/visualization/README.txt index e4cc3341b62..c070a546918 100644 --- a/docs/user-guide/examples/visualization/README.txt +++ b/docs/user-guide/examples/visualization/README.txt @@ -1,4 +1,4 @@ Visualization -============= +------------- .. _visualization-examples-gallery: From da32602212d072516541f02cc7ab70502032ac88 Mon Sep 17 00:00:00 2001 From: Maximilian Linhoff Date: Fri, 8 Sep 2023 17:15:24 +0200 Subject: [PATCH 12/17] Fix section heading in gallery --- docs/user-guide/examples/README.txt | 4 ++-- docs/user-guide/examples/algorithms/README.txt | 4 ++-- docs/user-guide/examples/core/README.txt | 4 ++-- docs/user-guide/examples/visualization/README.txt | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/user-guide/examples/README.txt b/docs/user-guide/examples/README.txt index 7bdf1b8226f..cdc806953ae 100644 --- a/docs/user-guide/examples/README.txt +++ b/docs/user-guide/examples/README.txt @@ -1,6 +1,6 @@ .. _tutorials_and_examples_gallery: -Tutorials and Examples gallery -============================== +Tutorials and Examples +====================== This gallery provides an overview of different ctapipe modules and how to use them. diff --git a/docs/user-guide/examples/algorithms/README.txt b/docs/user-guide/examples/algorithms/README.txt index c798294f73d..a53d619994e 100644 --- a/docs/user-guide/examples/algorithms/README.txt +++ b/docs/user-guide/examples/algorithms/README.txt @@ -1,4 +1,4 @@ +.. _algorithms-examples-gallery: + Algorithms ---------- - -.. _algorithms-examples-gallery: diff --git a/docs/user-guide/examples/core/README.txt b/docs/user-guide/examples/core/README.txt index 8042b0dec57..cfd78a64e9e 100644 --- a/docs/user-guide/examples/core/README.txt +++ b/docs/user-guide/examples/core/README.txt @@ -1,4 +1,4 @@ +.. _core-examples-gallery: + Core Functionality ------------------ - -.. _core-examples-gallery: diff --git a/docs/user-guide/examples/visualization/README.txt b/docs/user-guide/examples/visualization/README.txt index c070a546918..ca55d616351 100644 --- a/docs/user-guide/examples/visualization/README.txt +++ b/docs/user-guide/examples/visualization/README.txt @@ -1,4 +1,4 @@ +.. _visualization-examples-gallery: + Visualization ------------- - -.. _visualization-examples-gallery: From 4ea016f5575a6dce4a02a80cce0ee1ef0ed4348a Mon Sep 17 00:00:00 2001 From: Maximilian Linhoff Date: Fri, 8 Sep 2023 17:15:41 +0200 Subject: [PATCH 13/17] add files created by sphinx-gallery to .gitignore --- .gitignore | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index 9ca4ab0cd26..d4366247a72 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ + .pytest_cache *.log @@ -12,6 +13,15 @@ __pycache__ ctapipe/_version_cache.py ctapipe/_version.py +# created by sphinx gallery +docs/user-guide/examples/**/*.ipynb +docs/user-guide/examples/**/*.py.md5 +docs/user-guide/examples/**/*.rst +docs/user-guide/examples/**/*_codeobj.pickle +docs/user-guide/examples/**/images +docs/user-guide/examples/examples_jupyter.zip +docs/user-guide/examples/examples_python.zip + # Ignore .c files by default to avoid including generated code. If you want to # add a non-generated .c extension, use `git add -f filename.c`. *.c From 47004bfedde26b94c8b1cbc29bac547b64155c0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Fr=C3=B6se?= Date: Sat, 9 Sep 2023 11:12:08 +0200 Subject: [PATCH 14/17] txt to rest --- .gitignore | 8 -------- docs/user-guide/examples/{README.txt => README.rst} | 0 .../examples/algorithms/{README.txt => README.rst} | 0 docs/user-guide/examples/core/{README.txt => README.rst} | 0 .../examples/tutorials/{README.txt => README.rst} | 0 .../examples/visualization/{README.txt => README.rst} | 0 6 files changed, 8 deletions(-) rename docs/user-guide/examples/{README.txt => README.rst} (100%) rename docs/user-guide/examples/algorithms/{README.txt => README.rst} (100%) rename docs/user-guide/examples/core/{README.txt => README.rst} (100%) rename docs/user-guide/examples/tutorials/{README.txt => README.rst} (100%) rename docs/user-guide/examples/visualization/{README.txt => README.rst} (100%) diff --git a/.gitignore b/.gitignore index d4366247a72..efa0a60dc5b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,14 +13,6 @@ __pycache__ ctapipe/_version_cache.py ctapipe/_version.py -# created by sphinx gallery -docs/user-guide/examples/**/*.ipynb -docs/user-guide/examples/**/*.py.md5 -docs/user-guide/examples/**/*.rst -docs/user-guide/examples/**/*_codeobj.pickle -docs/user-guide/examples/**/images -docs/user-guide/examples/examples_jupyter.zip -docs/user-guide/examples/examples_python.zip # Ignore .c files by default to avoid including generated code. If you want to # add a non-generated .c extension, use `git add -f filename.c`. diff --git a/docs/user-guide/examples/README.txt b/docs/user-guide/examples/README.rst similarity index 100% rename from docs/user-guide/examples/README.txt rename to docs/user-guide/examples/README.rst diff --git a/docs/user-guide/examples/algorithms/README.txt b/docs/user-guide/examples/algorithms/README.rst similarity index 100% rename from docs/user-guide/examples/algorithms/README.txt rename to docs/user-guide/examples/algorithms/README.rst diff --git a/docs/user-guide/examples/core/README.txt b/docs/user-guide/examples/core/README.rst similarity index 100% rename from docs/user-guide/examples/core/README.txt rename to docs/user-guide/examples/core/README.rst diff --git a/docs/user-guide/examples/tutorials/README.txt b/docs/user-guide/examples/tutorials/README.rst similarity index 100% rename from docs/user-guide/examples/tutorials/README.txt rename to docs/user-guide/examples/tutorials/README.rst diff --git a/docs/user-guide/examples/visualization/README.txt b/docs/user-guide/examples/visualization/README.rst similarity index 100% rename from docs/user-guide/examples/visualization/README.txt rename to docs/user-guide/examples/visualization/README.rst From 1c56a3e7be3492ab9cc232806918cd3a80ef2f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Fr=C3=B6se?= Date: Sat, 9 Sep 2023 11:34:07 +0200 Subject: [PATCH 15/17] move to default build dir --- docs/Makefile | 9 +-------- docs/conf.py | 14 +++++++------- docs/user-guide/examples/index.rst | 1 + 3 files changed, 9 insertions(+), 15 deletions(-) create mode 100644 docs/user-guide/examples/index.rst diff --git a/docs/Makefile b/docs/Makefile index f619b16a1fd..3bf5e372d5f 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -19,14 +19,7 @@ help: @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) clean: - rm -rf api - rm -rf user-guide/examples/*.zip - rm -rf user-guide/examples/*.rst - rm -rf user-guide/examples/*/*.ipynb - rm -rf user-guide/examples/*/*.py.md5 - rm -rf user-guide/examples/*/*.pickle - rm -rf user-guide/examples/*/*.rst - rm -rf user-guide/examples/*/images + rm -rf api auto_examples @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py index cc1ed9538c9..4554ca94ab4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,7 +4,7 @@ # ctapipe documentation build configuration file, created by # sphinx-quickstart on Fri Jan 6 10:22:58 2017. # -# This file is execfile()d with the current directory set to its +# Thi file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this @@ -152,9 +152,6 @@ def setup(app): "examples_dirs": [ "user-guide/examples", ], # path to your example scripts - "gallery_dirs": [ - "user-guide/examples", - ], # path to where to save gallery generated output "subsection_order": ExplicitOrder( [ "user-guide/examples/tutorials", @@ -165,8 +162,8 @@ def setup(app): ), "within_subsection_order": FileNameSortKey, "nested_sections": False, - # "copyfile_regex": r".*\.rst|.*\.png|.*\.json", "filename_pattern": r".*\.py", + "copyfile_regex": r".*\.png", "promote_jupyter_magic": True, "line_numbers": True, "default_thumb_file": "_static/ctapipe_logo.png", @@ -227,9 +224,12 @@ def setup(app): ".DS_Store", "**.ipynb_checkpoints", "changes", - "user-guide/examples/*/*.ipynb", - "user-guide/examples/*/*.py", "user-guide/examples/*/README.rst", + "user-guide/examples/README.rst", + "auto_examples/index.rst", + "auto_examples/*/*.py.md5", + "auto_examples/*/*.py", + "auto_examples/*/*.ipynb", ] # The name of the Pygments (syntax highlighting) style to use. diff --git a/docs/user-guide/examples/index.rst b/docs/user-guide/examples/index.rst new file mode 100644 index 00000000000..d6578dcd835 --- /dev/null +++ b/docs/user-guide/examples/index.rst @@ -0,0 +1 @@ +.. include:: ../../auto_examples/index.rst From 6fb72e88df4f9f03f68d5494485fbcb644b4da7e Mon Sep 17 00:00:00 2001 From: Maximilian Linhoff Date: Mon, 11 Sep 2023 14:08:52 +0200 Subject: [PATCH 16/17] Move examples back out of docs tree This interfered with doctests collection, running the examples during the collection, which, since they are not modules but scripts, blocked it. Also added git and sphinx build dirs to pytest ignore directories. --- .gitignore | 2 +- docs/conf.py | 10 +++++----- docs/user-guide/examples/index.rst | 1 - docs/user-guide/examples_tutorials.rst | 1 + docs/user-guide/index.rst | 2 +- {docs/user-guide/examples => examples}/README.rst | 0 .../examples => examples}/algorithms/README.rst | 0 .../algorithms/convert_images_to_2d.py | 0 .../algorithms/dilate_image.py | 0 .../algorithms/nd_interpolation.py | 0 .../core/InstrumentDescription.py | 0 .../examples => examples}/core/README.rst | 0 .../core/command_line_tools.py | 0 .../examples => examples}/core/config.json | 0 .../examples => examples}/core/containers.py | 0 .../examples => examples}/core/provenance.py | 0 .../core/table_writer_reader.py | 0 .../examples => examples}/tutorials/README.rst | 0 .../tutorials/calibrated_data_exploration.py | 0 .../tutorials/coordinates_example.py | 0 .../tutorials/ctapipe_handson.py | 0 .../tutorials/ctapipe_overview.py | 0 .../tutorials/ground_frame.png | Bin .../tutorials/raw_data_exploration.py | 0 .../examples => examples}/tutorials/theta_square.py | 0 .../tutorials/tilted_ground_frame.png | Bin .../examples => examples}/visualization/README.rst | 0 .../visualization/array_display.py | 0 .../visualization/camera_display.py | 0 pyproject.toml | 5 +++++ 30 files changed, 13 insertions(+), 8 deletions(-) delete mode 100644 docs/user-guide/examples/index.rst create mode 100644 docs/user-guide/examples_tutorials.rst rename {docs/user-guide/examples => examples}/README.rst (100%) rename {docs/user-guide/examples => examples}/algorithms/README.rst (100%) rename {docs/user-guide/examples => examples}/algorithms/convert_images_to_2d.py (100%) rename {docs/user-guide/examples => examples}/algorithms/dilate_image.py (100%) rename {docs/user-guide/examples => examples}/algorithms/nd_interpolation.py (100%) rename {docs/user-guide/examples => examples}/core/InstrumentDescription.py (100%) rename {docs/user-guide/examples => examples}/core/README.rst (100%) rename {docs/user-guide/examples => examples}/core/command_line_tools.py (100%) rename {docs/user-guide/examples => examples}/core/config.json (100%) rename {docs/user-guide/examples => examples}/core/containers.py (100%) rename {docs/user-guide/examples => examples}/core/provenance.py (100%) rename {docs/user-guide/examples => examples}/core/table_writer_reader.py (100%) rename {docs/user-guide/examples => examples}/tutorials/README.rst (100%) rename {docs/user-guide/examples => examples}/tutorials/calibrated_data_exploration.py (100%) rename {docs/user-guide/examples => examples}/tutorials/coordinates_example.py (100%) rename {docs/user-guide/examples => examples}/tutorials/ctapipe_handson.py (100%) rename {docs/user-guide/examples => examples}/tutorials/ctapipe_overview.py (100%) rename {docs/user-guide/examples => examples}/tutorials/ground_frame.png (100%) rename {docs/user-guide/examples => examples}/tutorials/raw_data_exploration.py (100%) rename {docs/user-guide/examples => examples}/tutorials/theta_square.py (100%) rename {docs/user-guide/examples => examples}/tutorials/tilted_ground_frame.png (100%) rename {docs/user-guide/examples => examples}/visualization/README.rst (100%) rename {docs/user-guide/examples => examples}/visualization/array_display.py (100%) rename {docs/user-guide/examples => examples}/visualization/camera_display.py (100%) diff --git a/.gitignore b/.gitignore index efa0a60dc5b..631313313e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ - .pytest_cache *.log @@ -27,6 +26,7 @@ MANIFEST # Sphinx docs/api docs/_build +docs/auto_examples # Editors and IDEs diff --git a/docs/conf.py b/docs/conf.py index 4554ca94ab4..29d5cdee0c5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -150,14 +150,14 @@ def setup(app): sphinx_gallery_conf = { "examples_dirs": [ - "user-guide/examples", + "../examples", ], # path to your example scripts "subsection_order": ExplicitOrder( [ - "user-guide/examples/tutorials", - "user-guide/examples/algorithms", - "user-guide/examples/core", - "user-guide/examples/visualization", + "../examples/tutorials", + "../examples/algorithms", + "../examples/core", + "../examples/visualization", ] ), "within_subsection_order": FileNameSortKey, diff --git a/docs/user-guide/examples/index.rst b/docs/user-guide/examples/index.rst deleted file mode 100644 index d6578dcd835..00000000000 --- a/docs/user-guide/examples/index.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../auto_examples/index.rst diff --git a/docs/user-guide/examples_tutorials.rst b/docs/user-guide/examples_tutorials.rst new file mode 100644 index 00000000000..e297773b493 --- /dev/null +++ b/docs/user-guide/examples_tutorials.rst @@ -0,0 +1 @@ +.. include:: ../auto_examples/index.rst diff --git a/docs/user-guide/index.rst b/docs/user-guide/index.rst index 7b738f5c583..c74bf247684 100644 --- a/docs/user-guide/index.rst +++ b/docs/user-guide/index.rst @@ -9,5 +9,5 @@ User Guide getting-started tools data_models/index - examples/index + examples_tutorials FAQ diff --git a/docs/user-guide/examples/README.rst b/examples/README.rst similarity index 100% rename from docs/user-guide/examples/README.rst rename to examples/README.rst diff --git a/docs/user-guide/examples/algorithms/README.rst b/examples/algorithms/README.rst similarity index 100% rename from docs/user-guide/examples/algorithms/README.rst rename to examples/algorithms/README.rst diff --git a/docs/user-guide/examples/algorithms/convert_images_to_2d.py b/examples/algorithms/convert_images_to_2d.py similarity index 100% rename from docs/user-guide/examples/algorithms/convert_images_to_2d.py rename to examples/algorithms/convert_images_to_2d.py diff --git a/docs/user-guide/examples/algorithms/dilate_image.py b/examples/algorithms/dilate_image.py similarity index 100% rename from docs/user-guide/examples/algorithms/dilate_image.py rename to examples/algorithms/dilate_image.py diff --git a/docs/user-guide/examples/algorithms/nd_interpolation.py b/examples/algorithms/nd_interpolation.py similarity index 100% rename from docs/user-guide/examples/algorithms/nd_interpolation.py rename to examples/algorithms/nd_interpolation.py diff --git a/docs/user-guide/examples/core/InstrumentDescription.py b/examples/core/InstrumentDescription.py similarity index 100% rename from docs/user-guide/examples/core/InstrumentDescription.py rename to examples/core/InstrumentDescription.py diff --git a/docs/user-guide/examples/core/README.rst b/examples/core/README.rst similarity index 100% rename from docs/user-guide/examples/core/README.rst rename to examples/core/README.rst diff --git a/docs/user-guide/examples/core/command_line_tools.py b/examples/core/command_line_tools.py similarity index 100% rename from docs/user-guide/examples/core/command_line_tools.py rename to examples/core/command_line_tools.py diff --git a/docs/user-guide/examples/core/config.json b/examples/core/config.json similarity index 100% rename from docs/user-guide/examples/core/config.json rename to examples/core/config.json diff --git a/docs/user-guide/examples/core/containers.py b/examples/core/containers.py similarity index 100% rename from docs/user-guide/examples/core/containers.py rename to examples/core/containers.py diff --git a/docs/user-guide/examples/core/provenance.py b/examples/core/provenance.py similarity index 100% rename from docs/user-guide/examples/core/provenance.py rename to examples/core/provenance.py diff --git a/docs/user-guide/examples/core/table_writer_reader.py b/examples/core/table_writer_reader.py similarity index 100% rename from docs/user-guide/examples/core/table_writer_reader.py rename to examples/core/table_writer_reader.py diff --git a/docs/user-guide/examples/tutorials/README.rst b/examples/tutorials/README.rst similarity index 100% rename from docs/user-guide/examples/tutorials/README.rst rename to examples/tutorials/README.rst diff --git a/docs/user-guide/examples/tutorials/calibrated_data_exploration.py b/examples/tutorials/calibrated_data_exploration.py similarity index 100% rename from docs/user-guide/examples/tutorials/calibrated_data_exploration.py rename to examples/tutorials/calibrated_data_exploration.py diff --git a/docs/user-guide/examples/tutorials/coordinates_example.py b/examples/tutorials/coordinates_example.py similarity index 100% rename from docs/user-guide/examples/tutorials/coordinates_example.py rename to examples/tutorials/coordinates_example.py diff --git a/docs/user-guide/examples/tutorials/ctapipe_handson.py b/examples/tutorials/ctapipe_handson.py similarity index 100% rename from docs/user-guide/examples/tutorials/ctapipe_handson.py rename to examples/tutorials/ctapipe_handson.py diff --git a/docs/user-guide/examples/tutorials/ctapipe_overview.py b/examples/tutorials/ctapipe_overview.py similarity index 100% rename from docs/user-guide/examples/tutorials/ctapipe_overview.py rename to examples/tutorials/ctapipe_overview.py diff --git a/docs/user-guide/examples/tutorials/ground_frame.png b/examples/tutorials/ground_frame.png similarity index 100% rename from docs/user-guide/examples/tutorials/ground_frame.png rename to examples/tutorials/ground_frame.png diff --git a/docs/user-guide/examples/tutorials/raw_data_exploration.py b/examples/tutorials/raw_data_exploration.py similarity index 100% rename from docs/user-guide/examples/tutorials/raw_data_exploration.py rename to examples/tutorials/raw_data_exploration.py diff --git a/docs/user-guide/examples/tutorials/theta_square.py b/examples/tutorials/theta_square.py similarity index 100% rename from docs/user-guide/examples/tutorials/theta_square.py rename to examples/tutorials/theta_square.py diff --git a/docs/user-guide/examples/tutorials/tilted_ground_frame.png b/examples/tutorials/tilted_ground_frame.png similarity index 100% rename from docs/user-guide/examples/tutorials/tilted_ground_frame.png rename to examples/tutorials/tilted_ground_frame.png diff --git a/docs/user-guide/examples/visualization/README.rst b/examples/visualization/README.rst similarity index 100% rename from docs/user-guide/examples/visualization/README.rst rename to examples/visualization/README.rst diff --git a/docs/user-guide/examples/visualization/array_display.py b/examples/visualization/array_display.py similarity index 100% rename from docs/user-guide/examples/visualization/array_display.py rename to examples/visualization/array_display.py diff --git a/docs/user-guide/examples/visualization/camera_display.py b/examples/visualization/camera_display.py similarity index 100% rename from docs/user-guide/examples/visualization/camera_display.py rename to examples/visualization/camera_display.py diff --git a/pyproject.toml b/pyproject.toml index 7501b7da070..73a42e69d99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,11 @@ filterwarnings = [ "error::astropy.utils.exceptions.AstropyDeprecationWarning", "error::ctapipe.utils.deprecation.CTAPipeDeprecationWarning", ] +norecursedirs = [ + ".git", + "_build", + "auto_examples", +] [tool.towncrier] package = "ctapipe" From 16b544544f0b15def20c5e168cc4ef8645191fab Mon Sep 17 00:00:00 2001 From: Maximilian Linhoff Date: Mon, 11 Sep 2023 16:44:49 +0200 Subject: [PATCH 17/17] Add comment to including rst file --- docs/conftest.py | 5 +++++ docs/user-guide/examples_tutorials.rst | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 docs/conftest.py diff --git a/docs/conftest.py b/docs/conftest.py new file mode 100644 index 00000000000..4c7e9da09ce --- /dev/null +++ b/docs/conftest.py @@ -0,0 +1,5 @@ +collect_ignore = [ + "conf.py", + "_build", + "auto_examples", +] diff --git a/docs/user-guide/examples_tutorials.rst b/docs/user-guide/examples_tutorials.rst index e297773b493..77b1161e010 100644 --- a/docs/user-guide/examples_tutorials.rst +++ b/docs/user-guide/examples_tutorials.rst @@ -1 +1,5 @@ +.. Include rendered examples build with sphinx-gallery +.. Actual files are in the examples/ directory in the +.. base of the repository + .. include:: ../auto_examples/index.rst