Skip to content

Commit

Permalink
Make tests run on minimal dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
maxnoe committed Nov 8, 2024
1 parent cad1be1 commit 810eb66
Show file tree
Hide file tree
Showing 25 changed files with 128 additions and 59 deletions.
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ all = [
]

tests = [
# at the moment, essentially all tests rely on test data from simtel
# it doesn't make sense to skip all of these.
"eventio >=1.9.1,<2.0.0a0",
"h5py",
"pandas",
"pytest >= 7.0",
Expand Down
1 change: 1 addition & 0 deletions src/ctapipe/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ def dl1_muon_output_file(dl1_tmp_path, dl1_muon_file):
from ctapipe.tools.process import ProcessorTool

output = dl1_tmp_path / "muon_output.dl1.h5"
pytest.importorskip("iminuit")

# prevent running process multiple times in case of parallel tests
with FileLock(output.with_suffix(output.suffix + ".lock")):
Expand Down
2 changes: 1 addition & 1 deletion src/ctapipe/image/muon/intensity_fitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ class MuonIntensityFitter(TelescopeComponent):

def __init__(self, subarray, **kwargs):
if Minuit is None:
raise OptionalDependencyMissing("iminuit")
raise OptionalDependencyMissing("iminuit") from None

super().__init__(subarray=subarray, **kwargs)
self._geometries_tel_frame = {
Expand Down
4 changes: 4 additions & 0 deletions src/ctapipe/image/muon/tests/test_intensity_fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ def test_muon_efficiency_fit(prod5_lst, reference_location):
)
from ctapipe.instrument import SubarrayDescription

pytest.importorskip("iminuit")

tel_id = 1
telescope = prod5_lst
subarray = SubarrayDescription(
Expand Down Expand Up @@ -88,6 +90,8 @@ def test_scts(prod5_sst, reference_location):
from ctapipe.image.muon.intensity_fitter import MuonIntensityFitter
from ctapipe.instrument import SubarrayDescription

pytest.importorskip("iminuit")

telescope = prod5_sst
subarray = SubarrayDescription(
name="ssts",
Expand Down
3 changes: 3 additions & 0 deletions src/ctapipe/image/muon/tests/test_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
Tests for MuonProcessor functionality
"""
import numpy as np
import pytest

from ctapipe.image import ImageProcessor
from ctapipe.image.muon import MuonProcessor
from ctapipe.io import EventSource

pytest.importorskip("iminuit")


def test_processor(dl1_muon_file):
"""Test that the MuonProcessor component analyses events or
Expand Down
2 changes: 2 additions & 0 deletions src/ctapipe/image/muon/tests/test_ring_fitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ def test_MuonRingFitter_has_methods():
@pytest.mark.parametrize("method", MuonRingFitter.fit_method.values)
def test_MuonRingFitter(method, prod5_mst_flashcam):
"""test MuonRingFitter"""
pytest.importorskip("iminuit")

# flashCam example
center_xs = 0.3 * u.m
center_ys = 0.6 * u.m
Expand Down
7 changes: 6 additions & 1 deletion src/ctapipe/instrument/tests/test_subarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ def test_subarray_description(prod5_mst_nectarcam):
"""Test SubarrayDescription functionality"""
n_tels = 10
sub = create_subarray(prod5_mst_nectarcam, n_tels)
sub.peek()

assert len(sub.telescope_types) == 1

Expand Down Expand Up @@ -75,6 +74,12 @@ def test_subarray_description(prod5_mst_nectarcam):
assert sub.telescope_types[0] == sub.tel[1]


def test_subarray_peek(prod5_mst_nectarcam):
pytest.importorskip("matplotlib")
sub = create_subarray(prod5_mst_nectarcam, 10)
sub.peek()


def test_to_table(example_subarray):
"""Check that we can generate astropy Tables from the SubarrayDescription"""
sub = example_subarray
Expand Down
2 changes: 1 addition & 1 deletion src/ctapipe/io/simteleventsource.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ def is_compatible(file_path):
try:
from eventio.file_types import is_eventio
except ModuleNotFoundError:
raise OptionalDependencyMissing("eventio")
raise OptionalDependencyMissing("eventio") from None

path = Path(file_path).expanduser()
if not path.is_file():
Expand Down
2 changes: 1 addition & 1 deletion src/ctapipe/reco/impact.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def __init__(
self, subarray, atmosphere_profile, dummy_reconstructor=False, **kwargs
):
if Minuit is None:
raise OptionalDependencyMissing("iminuit")
raise OptionalDependencyMissing("iminuit") from None

if atmosphere_profile is None:
raise TypeError(
Expand Down
3 changes: 1 addition & 2 deletions src/ctapipe/reco/tests/test_ImPACT.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
)
from ctapipe.utils import get_dataset_path

# CameraHillasParametersContainer,
# ReconstructedEnergyContainer,
pytest.importorskip("iminuit")

SIMTEL_PATH = get_dataset_path(
"gamma_20deg_0deg_run2___cta-prod5-paranal_desert"
Expand Down
39 changes: 20 additions & 19 deletions src/ctapipe/reco/tests/test_reconstruction_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@
from ctapipe.reco.impact import ImPACTReconstructor
from ctapipe.utils import get_dataset_path

reconstructors = [HillasIntersection, HillasReconstructor, ImPACTReconstructor]

@pytest.fixture
def reconstructors():
return [HillasIntersection, HillasReconstructor, ImPACTReconstructor]


def test_reconstructors(reconstructors):
@pytest.mark.parametrize("cls", reconstructors)
def test_reconstructors(cls):
"""
a test of the complete fit procedure on one event including:
• tailcut cleaning
Expand All @@ -37,17 +35,20 @@ def test_reconstructors(reconstructors):
calib(event)
image_processor(event)

for ReconstructorType in reconstructors:
reconstructor = ReconstructorType(
subarray, atmosphere_profile=source.atmosphere_density_profile
)
if ReconstructorType is ImPACTReconstructor:
reconstructor.root_dir = str(template_file.parents[0])
reconstructor(event)

name = ReconstructorType.__name__
# test the container is actually there and not only created by Map
assert name in event.dl2.stereo.geometry
assert event.dl2.stereo.geometry[name].alt.unit.is_equivalent(u.deg)
assert event.dl2.stereo.geometry[name].az.unit.is_equivalent(u.deg)
assert event.dl2.stereo.geometry[name].core_x.unit.is_equivalent(u.m)
if cls is ImPACTReconstructor:
pytest.importorskip("iminuit")

reconstructor = cls(
subarray, atmosphere_profile=source.atmosphere_density_profile
)
if cls is ImPACTReconstructor:
reconstructor.root_dir = str(template_file.parents[0])

reconstructor(event)

name = cls.__name__
# test the container is actually there and not only created by Map
assert name in event.dl2.stereo.geometry
assert event.dl2.stereo.geometry[name].alt.unit.is_equivalent(u.deg)
assert event.dl2.stereo.geometry[name].az.unit.is_equivalent(u.deg)
assert event.dl2.stereo.geometry[name].core_x.unit.is_equivalent(u.m)
1 change: 1 addition & 0 deletions src/ctapipe/tests/test_atmosphere.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def test_models(density_model):
density_model.integral(1 * u.km), density_model.integral(1000 * u.m)
)

pytest.importorskip("matplotlib")
with patch("matplotlib.pyplot.show"):
density_model.peek()

Expand Down
8 changes: 7 additions & 1 deletion src/ctapipe/tests/test_traitlets_configurable.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import ctapipe
from ctapipe.core import Component, Tool
from ctapipe.exceptions import OptionalDependencyMissing

ignore_traits = {
"config",
Expand Down Expand Up @@ -44,7 +45,12 @@ def find_all_traitlets(module, missing_config=None):
if submodule.startswith("test_") or submodule in skip_modules:
continue

submodule = importlib.import_module(module_name + "." + submodule_info.name)
try:
submodule = importlib.import_module(
module_name + "." + submodule_info.name
)
except OptionalDependencyMissing:
continue

if submodule_info.ispkg:
find_all_traitlets(submodule, missing_config=missing_config)
Expand Down
12 changes: 9 additions & 3 deletions src/ctapipe/tools/display_dl1.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@
from contextlib import ExitStack
from copy import copy

from matplotlib import pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages

from ..calib import CameraCalibrator
from ..core import Component, QualityQuery, Tool
from ..core.tool import ToolConfigurationError
from ..core.traits import Bool, Int, Path, classes_with_traits, flag
from ..exceptions import OptionalDependencyMissing
from ..image.extractor import ImageExtractor
from ..io import EventSource
from ..io.datalevels import DataLevel
Expand Down Expand Up @@ -64,6 +62,12 @@ def __init__(self, subarray, config=None, parent=None, **kwargs):
self._init_figure()

def _init_figure(self):
try:
from matplotlib import pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
except ModuleNotFoundError:
raise OptionalDependencyMissing("matplotlib") from None

self.fig = plt.figure(figsize=(16, 7))
self.ax_intensity = self.fig.add_subplot(1, 2, 1)
self.ax_peak_time = self.fig.add_subplot(1, 2, 2)
Expand All @@ -72,6 +76,8 @@ def _init_figure(self):
self.pdf = self._exit_stack.enter_context(PdfPages(self.output_path))

def plot(self, event, tel_id):
from matplotlib import pyplot as plt

image = event.dl1.tel[tel_id].image
peak_time = event.dl1.tel[tel_id].peak_time

Expand Down
5 changes: 4 additions & 1 deletion src/ctapipe/tools/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,10 @@ def setup(self):
self.write = self.enter_context(
DataWriter(event_source=self.event_source, parent=self)
)
self.process_muons = MuonProcessor(subarray=subarray, parent=self)

self.process_muons = None
if self.should_compute_muon_parameters:
self.process_muons = MuonProcessor(subarray=subarray, parent=self)

self.event_type_filter = EventTypeFilter(parent=self)

Expand Down
2 changes: 2 additions & 0 deletions src/ctapipe/tools/tests/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,8 @@ def test_read_from_simtel_and_dl1(prod5_proton_simtel_path, tmp_path):

def test_muon_reconstruction_simtel(tmp_path):
"""ensure processor tool generates expected output when used to analyze muons"""
pytest.importorskip("iminuit")

muon_simtel_output_file = tmp_path / "muon_reco_on_simtel.h5"
run_tool(
ProcessorTool(),
Expand Down
7 changes: 3 additions & 4 deletions src/ctapipe/tools/tests/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@
import subprocess
import sys

import matplotlib as mpl
import matplotlib.pyplot as plt

# pylint: disable=C0103,C0116,C0415
import pytest

from ctapipe.core import run_tool
Expand All @@ -23,6 +19,9 @@
def test_display_dl1(tmp_path, dl1_image_file, dl1_parameters_file):
from ctapipe.tools.display_dl1 import DisplayDL1Calib

mpl = pytest.importorskip("matplotlib")
plt = pytest.importorskip("matplotlib.pyplot")

# close all figures before switching mpl backends
# fixes a deprecation warning / pending error in newer mpl versions
plt.close("all")
Expand Down
9 changes: 7 additions & 2 deletions src/ctapipe/utils/rgbtohex.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import codecs

import numpy as np
from matplotlib import colormaps

viridis = colormaps["viridis"]
from ..exceptions import OptionalDependencyMissing


def intensity_to_rgb(array, minval=None, maxval=None):
Expand All @@ -25,6 +24,12 @@ def intensity_to_rgb(array, minval=None, maxval=None):
rgb tuple representing the intensity as a color
"""
try:
from matplotlib import colormaps
except ModuleNotFoundError:
raise OptionalDependencyMissing("matplotlib") from None

viridis = colormaps["viridis"]
if minval is None:
minval = np.nanmin(array)
if maxval is None:
Expand Down
3 changes: 3 additions & 0 deletions src/ctapipe/utils/tests/test_rgbtohex.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import numpy as np
import pytest

from ctapipe.utils.rgbtohex import intensity_to_hex, intensity_to_rgb

pytest.importorskip("matplotlib")


def test_rgb():
input_ = np.array([4])
Expand Down
9 changes: 2 additions & 7 deletions src/ctapipe/visualization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@
"""
Visualization: Methods for displaying data
"""

try:
from .mpl_array import ArrayDisplay
from .mpl_camera import CameraDisplay
except ModuleNotFoundError:
pass

from .mpl_array import ArrayDisplay
from .mpl_camera import CameraDisplay

__all__ = ["CameraDisplay", "ArrayDisplay"]
4 changes: 2 additions & 2 deletions src/ctapipe/visualization/bokeh.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from bokeh.plotting import figure
from bokeh.transform import transform
except ModuleNotFoundError:
raise OptionalDependencyMissing("bokeh")
raise OptionalDependencyMissing("bokeh") from None


PLOTARGS = dict(tools="", toolbar_location=None, outline_line_color="#595959")
Expand All @@ -53,7 +53,7 @@ def palette_from_mpl_name(name):
import matplotlib.pyplot as plt
from matplotlib.colors import to_hex
except ModuleNotFoundError:
raise OptionalDependencyMissing("matplotlib")
raise OptionalDependencyMissing("matplotlib") from None

rgba = plt.get_cmap(name)(np.linspace(0, 1, 256))
palette = [to_hex(color) for color in rgba]
Expand Down
Loading

0 comments on commit 810eb66

Please sign in to comment.