Repository: gammapy/gammapy
Pull request #5697 (2025-02-10 15:04:59)

Add docstrings for Models.plot_regions/plot_positions
It seems that the `color` keyword was not being handled correctly through `kwargs_point` for the plot_regions


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 42b1dd2ef..83f5580a6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -157,3 +157,28 @@ jobs:
         with:
           name: gammapy-doc-html
           path: docs/_build/html/
+  conda-build:
+    name: Linux python 3.9 conda-build all-deps
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        shell: bash -l {0}
+    steps:
+      - name: checkout repo
+        uses: actions/checkout@v4
+      - name: create and activate env
+        uses: mamba-org/setup-micromamba@v2
+        with:
+          environment-file: environment-dev.yml
+          cache-downloads: true
+      - name: install gammapy
+        run: |
+          pip install -e .
+      - name: test conda build
+        run: |
+          make clean
+          conda install conda-build
+          conda info
+          conda --version
+          conda build --version
+          python setup.py bdist_conda
diff --git a/codemeta.json b/codemeta.json
index 642a291a5..04005bc83 100644
--- a/codemeta.json
+++ b/codemeta.json
@@ -371,4 +371,4 @@
         "iminuit>=2.8.0",
         "matplotlib>=3.4"
     ]
-}
\ No newline at end of file
+}
diff --git a/environment-dev.yml b/environment-dev.yml
index ffa007bba..c191b7aab 100644
--- a/environment-dev.yml
+++ b/environment-dev.yml
@@ -9,6 +9,7 @@ name: gammapy-dev
 
 channels:
   - conda-forge
+  - https://cxc.cfa.harvard.edu/conda/sherpa
 
 variables:
   PYTHONNOUSERSITE: "1"
@@ -72,9 +73,7 @@ dependencies:
   - nbformat
   - h5py
   - ruamel.yaml
-  # incompatible with current jupyter, results in broken env when present
-  # use e.g. pipx if you need cffconvert
-  # - cffconvert
+  - cffconvert
   - pyinstrument
   - memray
   - pip:
diff --git a/gammapy/datasets/__init__.py b/gammapy/datasets/__init__.py
index 9d65a3279..27182a3f0 100644
--- a/gammapy/datasets/__init__.py
+++ b/gammapy/datasets/__init__.py
@@ -6,7 +6,6 @@ from .io import OGIPDatasetReader, OGIPDatasetWriter, FermipyDatasetsReader
 from .map import (
     MapDataset,
     MapDatasetOnOff,
-    MapDatasetWeighted,
     create_empty_map_dataset_from_irfs,
     create_map_dataset_from_observation,
     create_map_dataset_geoms,
@@ -20,7 +19,6 @@ DATASET_REGISTRY = Registry(
     [
         MapDataset,
         MapDatasetOnOff,
-        MapDatasetWeighted,
         SpectrumDataset,
         SpectrumDatasetOnOff,
         FluxPointsDataset,
@@ -41,7 +39,6 @@ __all__ = [
     "MapDataset",
     "MapDatasetEventSampler",
     "MapDatasetOnOff",
-    "MapDatasetWeighted",
     "ObservationEventSampler",
     "OGIPDatasetWriter",
     "OGIPDatasetReader",
diff --git a/gammapy/datasets/core.py b/gammapy/datasets/core.py
index b80a2607d..821203fc0 100644
--- a/gammapy/datasets/core.py
+++ b/gammapy/datasets/core.py
@@ -61,7 +61,7 @@ class Dataset(abc.ABC):
     def mask(self):
         """Combined fit and safe mask."""
         if self.mask_safe is not None and self.mask_fit is not None:
-            return self.mask_safe * self.mask_fit
+            return self.mask_safe & self.mask_fit
         elif self.mask_fit is not None:
             return self.mask_fit
         elif self.mask_safe is not None:
@@ -79,10 +79,7 @@ class Dataset(abc.ABC):
         stat = self.stat_array()
 
         if self.mask is not None:
-            if isinstance(self.mask, np.ndarray):
-                stat = stat[self.mask.astype(bool)]
-            else:
-                stat = stat[self.mask.data.astype(bool)]
+            stat = stat[self.mask.data]
         return np.sum(stat, dtype=np.float64)
 
     @abc.abstractmethod
diff --git a/gammapy/datasets/map.py b/gammapy/datasets/map.py
index e692adbe2..9b308341a 100644
--- a/gammapy/datasets/map.py
+++ b/gammapy/datasets/map.py
@@ -17,7 +17,6 @@ from gammapy.stats import (
     WStatCountsStatistic,
     cash,
     cash_sum_cython,
-    weighted_cash_sum_cython,
     get_wstat_mu_bkg,
     wstat,
 )
@@ -33,7 +32,6 @@ from .utils import get_axes
 __all__ = [
     "MapDataset",
     "MapDatasetOnOff",
-    "MapDatasetWeighted",
     "create_empty_map_dataset_from_irfs",
     "create_map_dataset_geoms",
     "create_map_dataset_from_observation",
@@ -1475,22 +1473,12 @@ class MapDataset(Dataset):
         counts, npred = self.counts.data.astype(float), self.npred().data
 
         if self.mask is not None:
-            mask = ~(self.mask.data == False)  # noqa
-            counts = counts[mask]
-            npred = npred[mask]
-            if self.mask.data.dtype == bool or self.stat_type == "cash":
-                cash_sum = cash_sum_cython(counts, npred)
-            elif self.stat_type == "cash_weighted":
-                weight = self.mask.data[mask]
-                cash_sum = weighted_cash_sum_cython(counts, npred, weight)
-            else:
-                raise ValueError(
-                    f"'stat_type' must be a 'cash' or `cash_weighted`."
-                    f", got `{self.stat_type}` instead."
-                )
+            return (
+                cash_sum_cython(counts[self.mask.data], npred[self.mask.data])
+                + prior_stat_sum
+            )
         else:
-            cash_sum = cash_sum_cython(counts.ravel(), npred.ravel())
-        return cash_sum + prior_stat_sum
+            return cash_sum_cython(counts.ravel(), npred.ravel()) + prior_stat_sum
 
     def _to_asimov_dataset(self):
         """Create Asimov dataset from the current models.
@@ -2393,11 +2381,6 @@ class MapDataset(Dataset):
         plot_mask(ax=axes[3], mask=self.mask_safe_image, hatches=["///"], colors="w")
 
 
-class MapDatasetWeighted(MapDataset):
-    stat_type = "cash_weighted"
-    tag = "MapDatasetWeighted"
-
-
 class MapDatasetOnOff(MapDataset):
     """Map dataset for on-off likelihood fitting.
 
diff --git a/gammapy/maps/region/geom.py b/gammapy/maps/region/geom.py
index 9e874a14e..7701c92c4 100644
--- a/gammapy/maps/region/geom.py
+++ b/gammapy/maps/region/geom.py
@@ -856,9 +856,14 @@ class RegionGeom(Geom):
 
             for key, value in kwargs.items():
                 key_point = ARTIST_TO_LINE_PROPERTIES.get(key, None)
-                if key_point:
+                if key_point and key_point not in kwargs_point:
                     kwargs_point[key_point] = value
 
+            if "color" in kwargs:
+                kwargs_point.setdefault("color", kwargs["color"])
+            if "color" in kwargs_point:
+                kwargs_point["markeredgecolor"] = kwargs_point["color"]
+
             for region in compound_region_to_regions(self.region):
                 region_pix = region.to_pixel(wcs=ax.wcs)
 
diff --git a/gammapy/modeling/models/core.py b/gammapy/modeling/models/core.py
index 39205c5f1..255e7835f 100644
--- a/gammapy/modeling/models/core.py
+++ b/gammapy/modeling/models/core.py
@@ -1147,6 +1147,19 @@ class DatasetModels(collections.abc.Sequence, CovarianceMixin):
         -------
         ax : `~astropy.visualization.WcsAxes`
             WCS axes.
+
+        Examples
+        --------
+        >>> from gammapy.datasets import MapDataset
+        >>> from gammapy.catalog import SourceCatalog3FHL
+        >>> fermi_dataset = MapDataset.read(
+        ...    "$GAMMAPY_DATA/fermi-3fhl-gc/fermi-3fhl-gc.fits.gz", name="fermi_dataset")
+        >>> catalog = SourceCatalog3FHL()
+        >>> models = catalog.to_models().select_from_geom(fermi_dataset.geoms["geom"])
+        >>> ax = fermi_dataset.excess.sum_over_axes().smooth("0.2 deg").plot(add_cbar=True, cmap="Blues")
+        >>> ax = models.plot_regions(ax=ax, linewidth=1,  color="red",
+        ...    kwargs_point={"marker":"o", "markersize":5, "color":"red"}
+        ...            )
         """
         regions = self.to_regions()
 
@@ -1171,6 +1184,19 @@ class DatasetModels(collections.abc.Sequence, CovarianceMixin):
         -------
         ax : `~astropy.visualization.WcsAxes`
             WCS axes.
+
+        Examples
+        --------
+
+        >>> from gammapy.datasets import MapDataset
+        >>> from gammapy.catalog import SourceCatalog3FHL
+        >>> fermi_dataset = MapDataset.read(
+        ...        "$GAMMAPY_DATA/fermi-3fhl-gc/fermi-3fhl-gc.fits.gz", name="fermi_dataset"
+        ...        )
+        >>> catalog = SourceCatalog3FHL()
+        >>> models = catalog.to_models().select_from_geom(fermi_dataset.geoms["geom"])
+        >>> ax = fermi_dataset.excess.sum_over_axes().smooth("0.2 deg").plot(add_cbar=True, cmap="Blues")
+        >>> ax = models.plot_positions(ax=ax, color="red", marker="+", linewidth=1)
         """
         from astropy.visualization.wcsaxes import WCSAxes
 
diff --git a/gammapy/modeling/parameter.py b/gammapy/modeling/parameter.py
index 6b6d99a0b..4b4be5490 100644
--- a/gammapy/modeling/parameter.py
+++ b/gammapy/modeling/parameter.py
@@ -47,54 +47,54 @@ def _get_parameters_str(parameters):
 class Parameter:
     """A model parameter.
 
-     Note that the parameter value has been split into
-     a factor and scale like this::
-
-         value = factor x scale
-
-     Users should interact with the ``value``, ``quantity``
-     or ``min`` and ``max`` properties and consider the fact
-     that there is a ``factor`` and ``scale`` an implementation detail.
-
-     That was introduced for numerical stability in parameter and error
-     estimation methods, only in the Gammapy optimiser interface do we
-     interact with the ``factor``, ``factor_min`` and ``factor_max`` properties,
-     i.e. the optimiser "sees" the well-scaled problem.
-
-     Parameters
-     ----------
-     name : str
-         Name.
-     value : float or `~astropy.units.Quantity`
-         Value.
-     scale : float, optional
-         Scale (sometimes used in fitting).
-     unit : `~astropy.units.Unit` or str, optional
-         Unit.
-     min : float, str or `~astropy.units.quantity`, optional
-         Minimum (sometimes used in fitting). Default is None, which set the attribute to `np.nan`.
-     max : float, str or `~astropy.units.quantity`, optional
-         Maximum (sometimes used in fitting). Default is None, which set the attribute to `np.nan`.
+    Note that the parameter value has been split into
+    a factor and scale like this::
+
+        value = factor x scale
+
+    Users should interact with the ``value``, ``quantity``
+    or ``min`` and ``max`` properties and consider the fact
+    that there is a ``factor``` and ``scale`` an implementation detail.
+
+    That was introduced for numerical stability in parameter and error
+    estimation methods, only in the Gammapy optimiser interface do we
+    interact with the ``factor``, ``factor_min`` and ``factor_max`` properties,
+    i.e. the optimiser "sees" the well-scaled problem.
+
+    Parameters
+    ----------
+    name : str
+        Name.
+    value : float or `~astropy.units.Quantity`
+        Value.
+    scale : float, optional
+        Scale (sometimes used in fitting).
+    unit : `~astropy.units.Unit` or str, optional
+        Unit.
+    min : float, optional
+        Minimum (sometimes used in fitting).
+    max : float, optional
+        Maximum (sometimes used in fitting).
     frozen : bool, optional
-         Frozen (used in fitting).
-     error : float
-         Parameter error.
-     scan_min : float
-         Minimum value for the parameter scan. Overwrites scan_n_sigma.
-     scan_max : float
-         Maximum value for the parameter scan. Overwrites scan_n_sigma.
-     scan_n_values: int
-         Number of values to be used for the parameter scan.
-     scan_n_sigma : int
-         Number of sigmas to scan.
-     scan_values: `numpy.array`
-         Scan values. Overwrites all the scan keywords before.
-     scale_method : {'scale10', 'factor1', None}
-         Method used to set ``factor`` and ``scale``.
-     interp : {"lin", "sqrt", "log"}
-         Parameter scaling to use for the scan.
-     prior : `~gammapy.modeling.models.Prior`
-         Prior set on the parameter.
+        Frozen (used in fitting).
+    error : float
+        Parameter error.
+    scan_min : float
+        Minimum value for the parameter scan. Overwrites scan_n_sigma.
+    scan_max : float
+        Minimum value for the parameter scan. Overwrites scan_n_sigma.
+    scan_n_values: int
+        Number of values to be used for the parameter scan.
+    scan_n_sigma : int
+        Number of sigmas to scan.
+    scan_values: `numpy.array`
+        Scan values. Overwrites all the scan keywords before.
+    scale_method : {'scale10', 'factor1', None}
+        Method used to set ``factor`` and ``scale``.
+    interp : {"lin", "sqrt", "log"}
+        Parameter scaling to use for the scan.
+    prior : `~gammapy.modeling.models.Prior`
+        Prior set on the parameter.
     """
 
     def __init__(
@@ -122,6 +122,8 @@ class Parameter:
         self._name = name
         self._link_label_io = None
         self.scale = scale
+        self.min = min
+        self.max = max
         self.frozen = frozen
         self._error = error
         self._type = None
@@ -136,8 +138,6 @@ class Parameter:
             self.value = float(value)
             self.unit = unit
 
-        self.min = min
-        self.max = max
         self.scan_min = scan_min
         self.scan_max = scan_max
         self.scan_values = scan_values
@@ -201,7 +201,7 @@ class Parameter:
 
     @error.setter
     def error(self, value):
-        self._error = self._set_quantity_str_float(value)
+        self._error = float(u.Quantity(value, unit=self.unit).value)
 
     @property
     def name(self):
@@ -243,10 +243,18 @@ class Parameter:
     @min.setter
     def min(self, val):
         """`~astropy.table.Table` has masked values for NaN. Replacing with NaN."""
-        if isinstance(val, np.ma.core.MaskedConstant) or (val is None):
+        if isinstance(val, np.ma.core.MaskedConstant):
             self._min = np.nan
         else:
-            self._min = self._set_quantity_str_float(val)
+            self._min = float(val)
+
+    @property
+    def factor_min(self):
+        """Factor minimum as a float.
+
+        This ``factor_min = min / scale`` is for the optimizer interface.
+        """
+        return self.min / self.scale
 
     @property
     def max(self):
@@ -256,41 +264,10 @@ class Parameter:
     @max.setter
     def max(self, val):
         """`~astropy.table.Table` has masked values for NaN. Replacing with NaN."""
-        if isinstance(val, np.ma.core.MaskedConstant) or (val is None):
+        if isinstance(val, np.ma.core.MaskedConstant):
             self._max = np.nan
         else:
-            self._max = self._set_quantity_str_float(val)
-
-    def _set_quantity_str_float(self, value):
-        """Logics for min and max setter."""
-        if isinstance(value, u.Quantity) or isinstance(value, str):
-            value = u.Quantity(value)
-            return float(value.to(self.unit).value)
-        else:
-            return float(value)
-
-    def set_lim(self, min=None, max=None):
-        """
-        Set the min and/or max value for the parameter.
-
-        Parameters
-        ----------
-        min, max: float, `~astropy.units.Quantity`, str
-            Minimum and Maximum value to assign to the parameter `min` and `max`.
-            Default is None, which set `min` and `max` to `np.nan`.
-        """
-        if min is not None:
-            self.min = min
-        if max is not None:
-            self.max = max
-
-    @property
-    def factor_min(self):
-        """Factor minimum as a float.
-
-        This ``factor_min = min / scale`` is for the optimizer interface.
-        """
-        return self.min / self.scale
+            self._max = float(val)
 
     @property
     def factor_max(self):
diff --git a/gammapy/stats/__init__.py b/gammapy/stats/__init__.py
index a3a699122..b5216e560 100644
--- a/gammapy/stats/__init__.py
+++ b/gammapy/stats/__init__.py
@@ -4,7 +4,6 @@
 from .counts_statistic import CashCountsStatistic, WStatCountsStatistic
 from .fit_statistics import cash, cstat, get_wstat_gof_terms, get_wstat_mu_bkg, wstat
 from .fit_statistics_cython import (
-    weighted_cash_sum_cython,
     cash_sum_cython,
     f_cash_root_cython,
     norm_bounds_cython,
@@ -37,5 +36,4 @@ __all__ = [
     "structure_function",
     "discrete_correlation",
     "TimmerKonig_lightcurve_simulator",
-    "weighted_cash_sum_cython",
 ]
diff --git a/gammapy/stats/fit_statistics_cython.pyx b/gammapy/stats/fit_statistics_cython.pyx
index 59c58deb1..0dbd73cec 100644
--- a/gammapy/stats/fit_statistics_cython.pyx
+++ b/gammapy/stats/fit_statistics_cython.pyx
@@ -12,44 +12,6 @@ cdef extern from "math.h":
 global TRUNCATION_VALUE
 TRUNCATION_VALUE = 1e-25
 
-@cython.cdivision(True)
-@cython.boundscheck(False)
-def weighted_cash_sum_cython(np.ndarray[np.float_t, ndim=1] counts,
-                             np.ndarray[np.float_t, ndim=1] npred,
-                             np.ndarray[np.float_t, ndim=1] weight):
-    """Cash fit statistics with weights.
-
-    Parameters
-    ----------
-    counts : `~numpy.ndarray`
-        Counts array.
-    npred : `~numpy.ndarray`
-        Predicted counts array.
-    weight : `~numpy.ndarray`
-        likelihood weights array.
-    """
-    cdef np.float_t sum = 0
-    cdef np.float_t npr, lognpr
-    cdef unsigned int i, ni
-    cdef np.float_t trunc = TRUNCATION_VALUE
-    cdef np.float_t logtrunc = log(TRUNCATION_VALUE)
-
-    ni = counts.shape[0]
-    for i in range(ni):
-        npr = npred[i]
-        if npr > trunc:
-            lognpr = log(npr)
-        else:
-            npr = trunc
-            lognpr = logtrunc
-
-        if weight[i] > 0:
-            sum += weight[i] * npr
-            if counts[i] > 0:
-                sum -= weight[i] * counts[i] * lognpr
-
-    return 2 * sum
-
 @cython.cdivision(True)
 @cython.boundscheck(False)
 def cash_sum_cython(np.ndarray[np.float_t, ndim=1] counts,
diff --git a/gammapy/stats/utils.py b/gammapy/stats/utils.py
index 97193a8ea..8d2904d95 100644
--- a/gammapy/stats/utils.py
+++ b/gammapy/stats/utils.py
@@ -1,14 +1,13 @@
 # Licensed under a 3-clause BSD style license - see LICENSE.rst
 
 import numpy as np
-from scipy.stats import ncx2
+from scipy.stats import chi2
 
 
-def sigma_to_ts(n_sigma, df=1, n_sigma_asymov=0):
-    """Convert number of sigma to delta ts.
+def sigma_to_ts(n_sigma, df=1):
+    """Convert number of sigma to delta ts according to the Wilks' theorem.
 
-    Assumes that the TS follows a chi2 distribution according to Wilks theorem [1].
-    This is valid only if:
+    The theorem is valid only if:
     - the two hypotheses tested can be defined in the same parameters space
     - the true value is not at the boundary of this parameters space.
 
@@ -18,39 +17,27 @@ def sigma_to_ts(n_sigma, df=1, n_sigma_asymov=0):
         Significance in number of sigma.
     df : int
         Number of degree of freedom.
-    n_sigma_asymov : float
-        Significance in number of sigma in the Asymov dataset
-        (in which counts equal to the predicted counts).
-        In that case the function applies the Wald test described in [2] and [3],
-        where the TS of H1 under the H0 assumption is assumed to follow a non-central chi2 distribution.
-        Should be used only for sensitivity computations.
 
     Returns
     -------
     ts : float
         Test statistic
 
-    References
-    ----------
-    .. [1] Wilks theorem: https://en.wikipedia.org/wiki/Wilks%27_theorem
-
-    .. [2] Wald (1943): https://www.pp.rhul.ac.uk/~cowan/stat/wald1943.pdf
-
-    .. [3] Cowan et al. (2011), European Physical Journal C, 71, 1554.
-        doi:10.1140/epjc/s10052-011-1554-0.
+    Reference
+    ---------
+    Wilks theorem: https://en.wikipedia.org/wiki/Wilks%27_theorem
     """
-    ts_asymov = n_sigma_asymov**2
-    p_value = ncx2.sf(n_sigma**2, df=1, nc=ts_asymov)
-    return ncx2.isf(p_value, df=df, nc=ts_asymov)
+    p_value = chi2.sf(n_sigma**2, df=1)
+    return chi2.isf(p_value, df=df)
 
 
-def ts_to_sigma(ts, df=1, ts_asymov=0):
-    """Convert delta ts to number of sigma.
+def ts_to_sigma(ts, df=1):
+    """Convert delta ts to number of sigma according to the Wilks' theorem.
 
-    Assumes that the TS follows a chi2 distribution according to Wilks theorem [1].
-    This is valid only if:
+    The theorem is valid only if :
     - the two hypotheses tested can be defined in the same parameters space
-    - the true value is not at the boundary of this parameters space.
+    - the true value is not at the boundary of this parameters space
+    Reference:  https://en.wikipedia.org/wiki/Wilks%27_theorem
 
     Parameters
     ----------
@@ -58,27 +45,11 @@ def ts_to_sigma(ts, df=1, ts_asymov=0):
         Test statistic.
     df : int
         Number of degree of freedom.
-    ts_asymov : float
-        TS value in the Asymov dataset
-        (in which counts equal to the predicted counts).
-        In that case the function applies the Wald test described in [2] and [3],
-        and the TS is assumed to follow a non-central chi2 distribution.
-        Should be used only for sensitivity computations.
-
 
     Returns
     -------
     n_sigma : float
         Significance in number of sigma.
-
-    References
-    ----------
-    .. [1] Wilks theorem: https://en.wikipedia.org/wiki/Wilks%27_theorem
-
-    .. [2] Wald (1943): https://www.pp.rhul.ac.uk/~cowan/stat/wald1943.pdf
-
-    .. [3] Cowan et al. (2011), European Physical Journal C, 71, 1554.
-        doi:10.1140/epjc/s10052-011-1554-0.
     """
-    p_value = ncx2.sf(ts, df=df, nc=ts_asymov)
-    return np.sqrt(ncx2.isf(p_value, df=1, nc=ts_asymov))
+    p_value = chi2.sf(ts, df=df)
+    return np.sqrt(chi2.isf(p_value, df=1))


Test patch:
diff --git a/gammapy/datasets/tests/test_map.py b/gammapy/datasets/tests/test_map.py
index e9c333f0b..9ed66cc15 100644
--- a/gammapy/datasets/tests/test_map.py
+++ b/gammapy/datasets/tests/test_map.py
@@ -17,7 +17,6 @@ from gammapy.datasets import (
     Datasets,
     MapDataset,
     MapDatasetOnOff,
-    MapDatasetWeighted,
     create_empty_map_dataset_from_irfs,
     create_map_dataset_from_observation,
 )
@@ -195,9 +194,7 @@ def sky_model():
     )
 
 
-def get_map_dataset(
-    geom, geom_etrue, edisp="edispmap", name="test", weighted=False, **kwargs
-):
+def get_map_dataset(geom, geom_etrue, edisp="edispmap", name="test", **kwargs):
     """Returns a MapDataset"""
     # define background model
     background = Map.from_geom(geom)
@@ -229,67 +226,16 @@ def get_map_dataset(
 
     models = FoVBackgroundModel(dataset_name=name)
 
-    if weighted:
-        return MapDatasetWeighted(
-            models=models,
-            exposure=exposure,
-            background=background,
-            psf=psf,
-            edisp=edisp,
-            mask_fit=mask_fit,
-            name=name,
-            **kwargs,
-        )
-    else:
-        return MapDataset(
-            models=models,
-            exposure=exposure,
-            background=background,
-            psf=psf,
-            edisp=edisp,
-            mask_fit=mask_fit,
-            name=name,
-            **kwargs,
-        )
-
-
-@requires_data()
-def test_map_dataset_weight(sky_model, geom, geom_etrue):
-    dataset = get_map_dataset(geom, geom_etrue)
-    dataset.stat_type = "cash_weighted"
-
-    bkg_model = FoVBackgroundModel(dataset_name=dataset.name)
-    dataset.models = [sky_model, bkg_model]
-
-    dataset.counts = dataset.npred()
-    dataset.mask_safe = dataset.mask_fit
-    assert_allclose(dataset.stat_sum(), 12824.506311)
-
-    dataset.mask_fit = dataset.mask_fit * 3.0
-    assert_allclose(dataset.stat_sum(), 3.0 * 12824.506311)
-
-    dataset = get_map_dataset(geom, geom_etrue, weighted=True)
-    assert dataset.stat_type == "cash_weighted"
-
-    bkg_model = FoVBackgroundModel(dataset_name=dataset.name)
-    dataset.models = [sky_model, bkg_model]
-
-    dataset.counts = dataset.npred()
-    dataset.mask_safe = dataset.mask_fit
-    assert_allclose(dataset.stat_sum(), 12824.506311)
-
-    dataset.mask_fit = dataset.mask_fit * 3.0
-    assert_allclose(dataset.stat_sum(), 3.0 * 12824.506311)
-
-    dataset = get_map_dataset(geom, geom_etrue, weighted=True)
-    assert dataset.stat_type == "cash_weighted"
-
-    bkg_model = FoVBackgroundModel(dataset_name=dataset.name)
-    dataset.models = [sky_model, bkg_model]
-
-    dataset.counts = dataset.npred()
-    dataset.mask_safe = dataset.mask_fit * 3.0
-    assert_allclose(dataset.stat_sum(), 3.0 * 12824.506311)
+    return MapDataset(
+        models=models,
+        exposure=exposure,
+        background=background,
+        psf=psf,
+        edisp=edisp,
+        mask_fit=mask_fit,
+        name=name,
+        **kwargs,
+    )
 
 
 def test_map_dataset_name():
diff --git a/gammapy/modeling/tests/test_parameter.py b/gammapy/modeling/tests/test_parameter.py
index ba71fbb93..9bf1189de 100644
--- a/gammapy/modeling/tests/test_parameter.py
+++ b/gammapy/modeling/tests/test_parameter.py
@@ -1,26 +1,12 @@
 # Licensed under a 3-clause BSD style license - see LICENSE.rst
 import pytest
 import numpy as np
-from numpy.testing import assert_allclose, assert_equal
+from numpy.testing import assert_allclose
 from astropy.table import Table
-import astropy.units as u
 from gammapy.modeling import Parameter, Parameters, PriorParameter, PriorParameters
 from gammapy.modeling.models import GaussianPrior
 
 
-@pytest.fixture
-def default_parameter():
-    return Parameter(
-        "test",
-        value=1e-10,
-        min=None,
-        max=None,
-        frozen=False,
-        unit="TeV",
-        scale_method="scale10",
-    )
-
-
 def test_parameter_init():
     par = Parameter("spam", 42, "deg")
     assert par.name == "spam"
@@ -291,8 +277,16 @@ def test_parameter_scan_values():
     assert_allclose(p.scan_values, [0.1, 1, 10])
 
 
-def test_update_from_dict(default_parameter):
-    par = default_parameter
+def test_update_from_dict():
+    par = Parameter(
+        "test",
+        value=1e-10,
+        min="nan",
+        max="nan",
+        frozen=False,
+        unit="TeV",
+        scale_method="scale10",
+    )
     par.autoscale()
     data = {
         "model": "gc",
@@ -375,29 +369,3 @@ def test_priorparameters_to_table(priorpars):
     assert len(table.columns) == 7
     assert table["name"][0] == "spam"
     assert table["value"][1] == 99
-
-
-def test_parameter_set_min_max_error(default_parameter):
-    par = default_parameter
-    assert_equal(par.min, np.nan)
-    assert_equal(par.max, np.nan)
-
-    par.set_lim(min=1, max=10)
-    assert par.min == 1
-    assert par.max == 10
-
-    par.set_lim(max=5)
-    assert par.max == 5
-    assert par.min == 1
-
-    assert par.error == 0
-
-
-def test_set_quantity_str_float(default_parameter):
-    par = default_parameter
-    assert par._set_quantity_str_float(1) == 1
-    assert par._set_quantity_str_float("3 TeV") == 3
-    assert par._set_quantity_str_float(2 * u.TeV) == 2
-    assert par._set_quantity_str_float(4e3 * u.GeV) == 4
-    with pytest.raises(u.UnitsError):
-        par._set_quantity_str_float(1 * u.m)
diff --git a/gammapy/stats/tests/test_utils.py b/gammapy/stats/tests/test_utils.py
index 9a6a50845..b0a3476eb 100644
--- a/gammapy/stats/tests/test_utils.py
+++ b/gammapy/stats/tests/test_utils.py
@@ -4,15 +4,12 @@ from gammapy.stats.utils import sigma_to_ts, ts_to_sigma
 
 
 def test_sigma_ts_conversion():
+
     sigma_ref = 3
     ts_ref = 9
     df = 1
     ts = sigma_to_ts(3, df=df)
     assert_allclose(ts, ts_ref)
-    ts = sigma_to_ts(3, df=df)
-    assert_allclose(ts, ts_ref)
-    sigma = ts_to_sigma(ts, df=df)
-    assert_allclose(sigma, sigma_ref)
     sigma = ts_to_sigma(ts, df=df)
     assert_allclose(sigma, sigma_ref)
 
@@ -22,11 +19,3 @@ def test_sigma_ts_conversion():
     assert_allclose(ts, ts_ref)
     sigma = ts_to_sigma(ts, df=df)
     assert_allclose(sigma, sigma_ref)
-
-    sigma_ref = 3
-    ts_ref = 9
-    df = 1
-    ts = sigma_to_ts(3, df=df, n_sigma_asymov=3)
-    assert_allclose(ts, ts_ref)
-    sigma = ts_to_sigma(ts, df=df, ts_asymov=ts)
-    assert_allclose(sigma, sigma_ref)
diff --git a/gammapy/visualization/tests/test_datasets.py b/gammapy/visualization/tests/test_datasets.py
index 5e2c0fb06..711d1bb0e 100644
--- a/gammapy/visualization/tests/test_datasets.py
+++ b/gammapy/visualization/tests/test_datasets.py
@@ -2,6 +2,7 @@
 import pytest
 from numpy.testing import assert_allclose
 import matplotlib
+import matplotlib.pyplot as plt
 from packaging import version
 from gammapy.datasets.tests.test_map import MapDataset
 from gammapy.modeling.models import (
@@ -12,6 +13,7 @@ from gammapy.modeling.models import (
 )
 from gammapy.utils.testing import mpl_plot_check, requires_data
 from gammapy.visualization import plot_npred_signal, plot_spectrum_datasets_off_regions
+from gammapy.catalog import SourceCatalog3FHL
 
 
 @pytest.fixture
@@ -59,7 +61,69 @@ def test_plot_spectrum_datasets_off_regions():
 
     actual = ax.patches[2].get_edgecolor()
     assert_allclose(actual, (1.0, 0.498039, 0.054902, 1.0), rtol=1e-2)
-    assert ax.lines[0].get_color() in ["green", "C0"]
+    assert ax.lines[0].get_color() == "#2ca02c"
+
+
+@requires_data()
+def test_plot_regions_color_point():
+    fermi_dataset = MapDataset.read(
+        "$GAMMAPY_DATA/fermi-3fhl-gc/fermi-3fhl-gc.fits.gz", name="fermi_dataset"
+    )
+    catalog = SourceCatalog3FHL()
+    models = catalog.to_models().select_from_geom(fermi_dataset.geoms["geom"])
+
+    plt.figure()
+    ax = models.plot_regions(linewidth=1)
+    for patch in ax.patches:
+        assert_allclose(
+            patch.get_edgecolor(), (0.121569, 0.466667, 0.705882, 1), rtol=1e-2
+        )
+    for patch in ax.lines:
+        assert patch.get_color() == "C0"
+    # default blue for both lines and points
+
+    plt.figure()
+    ax = models.plot_regions(linewidth=1, color="red")
+    for patch in ax.patches:
+        assert_allclose(patch.get_edgecolor(), (1.0, 0.0, 0.0, 1.0), rtol=1e-2)
+    for patch in ax.lines:
+        assert patch.get_color() == "red"
+    # red because color set for both lines and points
+
+    plt.figure()
+    ax = models.plot_regions(
+        linewidth=1, kwargs_point={"marker": "d", "markersize": 5, "color": "green"}
+    )
+    for patch in ax.patches:
+        assert_allclose(
+            patch.get_edgecolor(), (0.121569, 0.466667, 0.705882, 1), rtol=1e-2
+        )
+    for patch in ax.lines:
+        assert patch.get_color() == "green"
+
+    plt.figure()
+    ax = models.plot_regions(
+        linewidth=1,
+        color="red",
+        kwargs_point={"marker": "d", "markersize": 5, "color": "green"},
+    )
+    for patch in ax.patches:
+        assert_allclose(patch.get_edgecolor(), (1.0, 0.0, 0.0, 1.0), rtol=1e-2)
+    for patch in ax.lines:
+        assert patch.get_color() == "green"
+
+    plt.figure()
+    ax = models.plot_regions(
+        linewidth=1,
+        color="red",
+        edgecolor="green",
+        kwargs_point={"marker": "d", "markersize": 5, "markeredgecolor": "green"},
+    )
+    for patch in ax.patches:
+        assert_allclose(patch.get_edgecolor(), (1.0, 0.0, 0.0, 1.0), rtol=1e-2)
+    for patch in ax.lines:
+        assert patch.get_color() == "red"
+    # red because color has priority over edgecolor as for the lines
 
 
 @requires_data()