lsst-utils 25.2023.600__py3-none-any.whl → 29.2025.4800__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. lsst/utils/__init__.py +0 -3
  2. lsst/utils/_packaging.py +2 -0
  3. lsst/utils/argparsing.py +79 -0
  4. lsst/utils/classes.py +27 -9
  5. lsst/utils/db_auth.py +339 -0
  6. lsst/utils/deprecated.py +10 -7
  7. lsst/utils/doImport.py +8 -9
  8. lsst/utils/inheritDoc.py +34 -6
  9. lsst/utils/introspection.py +285 -19
  10. lsst/utils/iteration.py +193 -7
  11. lsst/utils/logging.py +155 -105
  12. lsst/utils/packages.py +324 -82
  13. lsst/utils/plotting/__init__.py +15 -0
  14. lsst/utils/plotting/figures.py +159 -0
  15. lsst/utils/plotting/limits.py +155 -0
  16. lsst/utils/plotting/publication_plots.py +184 -0
  17. lsst/utils/plotting/rubin.mplstyle +46 -0
  18. lsst/utils/tests.py +231 -102
  19. lsst/utils/threads.py +9 -3
  20. lsst/utils/timer.py +207 -110
  21. lsst/utils/usage.py +6 -6
  22. lsst/utils/version.py +1 -1
  23. lsst/utils/wrappers.py +74 -29
  24. {lsst_utils-25.2023.600.dist-info → lsst_utils-29.2025.4800.dist-info}/METADATA +19 -15
  25. lsst_utils-29.2025.4800.dist-info/RECORD +32 -0
  26. {lsst_utils-25.2023.600.dist-info → lsst_utils-29.2025.4800.dist-info}/WHEEL +1 -1
  27. lsst/utils/_forwarded.py +0 -28
  28. lsst/utils/backtrace/__init__.py +0 -33
  29. lsst/utils/ellipsis.py +0 -54
  30. lsst/utils/get_caller_name.py +0 -45
  31. lsst_utils-25.2023.600.dist-info/RECORD +0 -29
  32. {lsst_utils-25.2023.600.dist-info → lsst_utils-29.2025.4800.dist-info/licenses}/COPYRIGHT +0 -0
  33. {lsst_utils-25.2023.600.dist-info → lsst_utils-29.2025.4800.dist-info/licenses}/LICENSE +0 -0
  34. {lsst_utils-25.2023.600.dist-info → lsst_utils-29.2025.4800.dist-info}/top_level.txt +0 -0
  35. {lsst_utils-25.2023.600.dist-info → lsst_utils-29.2025.4800.dist-info}/zip-safe +0 -0
@@ -0,0 +1,159 @@
1
+ # This file is part of utils.
2
+ #
3
+ # Developed for the LSST Data Management System.
4
+ # This product includes software developed by the LSST Project
5
+ # (https://www.lsst.org).
6
+ # See the COPYRIGHT file at the top-level directory of this distribution
7
+ # for details of code ownership.
8
+ #
9
+ # Use of this source code is governed by a 3-clause BSD-style
10
+ # license that can be found in the LICENSE file.
11
+ """Utilities related to making matplotlib figures."""
12
+
13
+ from __future__ import annotations
14
+
15
+ __all__ = [
16
+ "get_multiband_plot_colors",
17
+ "get_multiband_plot_linestyles",
18
+ "get_multiband_plot_symbols",
19
+ "make_figure",
20
+ ]
21
+
22
+ from typing import TYPE_CHECKING, Any
23
+
24
+ if TYPE_CHECKING:
25
+ from matplotlib.figure import Figure
26
+
27
+
28
+ def make_figure(**kwargs: Any) -> Figure:
29
+ """Make a matplotlib Figure with an Agg-backend canvas.
30
+
31
+ This routine creates a matplotlib figure without using
32
+ ``matplotlib.pyplot``, and instead uses a fixed non-interactive
33
+ backend. The advantage is that these figures are not cached and
34
+ therefore do not need to be explicitly closed -- they
35
+ are completely self-contained and ephemeral unlike figures
36
+ created with `matplotlib.pyplot.figure()`.
37
+
38
+ Parameters
39
+ ----------
40
+ **kwargs : `dict`
41
+ Keyword arguments to be passed to `matplotlib.figure.Figure()`.
42
+
43
+ Returns
44
+ -------
45
+ figure : `matplotlib.figure.Figure`
46
+ Figure with a fixed Agg backend, and no caching.
47
+
48
+ Notes
49
+ -----
50
+ The code here is based on
51
+ https://matplotlib.org/stable/gallery/user_interfaces/canvasagg.html
52
+ """
53
+ try:
54
+ from matplotlib.backends.backend_agg import FigureCanvasAgg
55
+ from matplotlib.figure import Figure
56
+ except ImportError as e:
57
+ raise RuntimeError("Cannot use make_figure without matplotlib.") from e
58
+
59
+ fig = Figure(**kwargs)
60
+ FigureCanvasAgg(fig)
61
+
62
+ return fig
63
+
64
+
65
+ def get_multiband_plot_colors(dark_background: bool = False) -> dict:
66
+ """Get color mappings for multiband plots using SDSS filter names.
67
+
68
+ Notes
69
+ -----
70
+ From https://rtn-045.lsst.io/#colorblind-friendly-plots
71
+
72
+ Parameters
73
+ ----------
74
+ dark_background : `bool`, optional
75
+ Use colors intended for a dark background.
76
+ Default colors are intended for a light background.
77
+
78
+ Returns
79
+ -------
80
+ plot_colors : `dict` of `str`
81
+ Mapping of the LSST bands to colors.
82
+ """
83
+ plot_filter_colors_white_background = {
84
+ "u": "#1600EA",
85
+ "g": "#31DE1F",
86
+ "r": "#B52626",
87
+ "i": "#370201",
88
+ "z": "#BA52FF",
89
+ "y": "#61A2B3",
90
+ }
91
+ plot_filter_colors_black_background = {
92
+ "u": "#3eb7ff",
93
+ "g": "#30c39f",
94
+ "r": "#ff7e00",
95
+ "i": "#2af5ff",
96
+ "z": "#a7f9c1",
97
+ "y": "#fdc900",
98
+ }
99
+ if dark_background:
100
+ return plot_filter_colors_black_background
101
+ else:
102
+ return plot_filter_colors_white_background
103
+
104
+
105
+ def get_multiband_plot_symbols() -> dict:
106
+ """Get symbol mappings for multiband plots using SDSS filter names.
107
+
108
+ Notes
109
+ -----
110
+ From https://rtn-045.lsst.io/#colorblind-friendly-plots
111
+
112
+ Returns
113
+ -------
114
+ plot_symbols : `dict` of `str`
115
+ Mapping of the LSST bands to symbols.
116
+ """
117
+ plot_symbols = {
118
+ "u": "o",
119
+ "g": "^",
120
+ "r": "v",
121
+ "i": "s",
122
+ "z": "*",
123
+ "y": "p",
124
+ }
125
+ return plot_symbols
126
+
127
+
128
+ def get_multiband_plot_linestyles() -> dict:
129
+ """Get line style mappings for multiband plots using SDSS filter names.
130
+
131
+ Notes
132
+ -----
133
+ From https://rtn-045.lsst.io/#colorblind-friendly-plots
134
+
135
+ Returns
136
+ -------
137
+ plot_linestyles : `dict` of `str`
138
+ Mapping of the LSST bands to line styles.
139
+ """
140
+ plot_line_styles = {
141
+ "u": "--",
142
+ "g": (0, (3, 1, 1, 1)),
143
+ "r": "-.",
144
+ "i": "-",
145
+ "z": (0, (3, 1, 1, 1, 1, 1)),
146
+ "y": ":",
147
+ }
148
+
149
+ # [SP-2200]: Restored to using parametric values.
150
+ # To avoid matplotlib v3.10 bug (see DM-49724),
151
+ # manually iterate over `patches` object returned
152
+ # by `plt.hist` when using histtype='step':
153
+ # _, _, patches = plt.hist()
154
+ # linestyle = plot_line_styles[band]
155
+ # for patch in patches:
156
+ # patch.set_linestyle(linestyle)
157
+ # It seems the bug will be fixed in matplotlib v.3.10.2,
158
+ # see DM-49724[TODO]
159
+ return plot_line_styles
@@ -0,0 +1,155 @@
1
+ # This file is part of utils.
2
+ #
3
+ # Developed for the LSST Data Management System.
4
+ # This product includes software developed by the LSST Project
5
+ # (https://www.lsst.org).
6
+ # See the COPYRIGHT file at the top-level directory of this distribution
7
+ # for details of code ownership.
8
+ #
9
+ # Use of this source code is governed by a 3-clause BSD-style
10
+ # license that can be found in the LICENSE file.
11
+
12
+ from __future__ import annotations
13
+
14
+ __all__ = ["calculate_safe_plotting_limits", "make_calculate_safe_plotting_limits"]
15
+
16
+ from collections.abc import Callable, Iterable, Sequence
17
+
18
+ import numpy as np
19
+
20
+
21
+ def calculate_safe_plotting_limits(
22
+ data_series: Sequence,
23
+ percentile: float = 99.9,
24
+ constant_extra: float | None = None,
25
+ symmetric_around_zero: bool = False,
26
+ ) -> tuple[float, float]:
27
+ """Calculate the right limits for plotting for one or more data series.
28
+
29
+ Given one or more data series with potential outliers, calculated the
30
+ values to pass for ymin, ymax so that extreme outliers don't ruin the
31
+ plot. If you are plotting several series on a single axis, pass them
32
+ all in and the overall plotting range will be given.
33
+
34
+ Parameters
35
+ ----------
36
+ data_series : `iterable` or `iterable` of `iterable`
37
+ One or more data series which will be going on the same axis, and
38
+ therefore want to have their common plotting limits calculated.
39
+ percentile : `float`, optional
40
+ The percentile used to clip the outliers from the data.
41
+ constant_extra : `float` or `None`, optional
42
+ The amount that's added on each side of the range so that data does not
43
+ quite touch the axes. If the default ``None`` is left then 5% of the
44
+ data range is added for cosmetics, but if zero is set this will
45
+ overrides this behaviour and zero you will get.
46
+ symmetric_around_zero : `bool`, optional
47
+ Whether to make the limits symmetric around zero.
48
+
49
+ Returns
50
+ -------
51
+ ymin : `float`
52
+ The value to set the ylim minimum to.
53
+ ymax : `float`
54
+ The value to set the ylim maximum to.
55
+ """
56
+ localFunc = make_calculate_safe_plotting_limits(percentile, constant_extra, symmetric_around_zero)
57
+ return localFunc(data_series)
58
+
59
+
60
+ def make_calculate_safe_plotting_limits(
61
+ percentile: float = 99.9,
62
+ constant_extra: float | None = None,
63
+ symmetric_around_zero: bool = False,
64
+ ) -> Callable[[Sequence], tuple[float, float]]:
65
+ """Make a ``calculate_safe_plotting_limits`` closure to get the common
66
+ limits when not all data series are available initially.
67
+
68
+ Parameters
69
+ ----------
70
+ percentile : `float`, optional
71
+ The percentile used to clip the outliers from the data.
72
+ constant_extra : `float`, optional
73
+ The amount that's added on each side of the range so that data does not
74
+ quite touch the axes. If the default ``None`` is left then 5% of the
75
+ data range is added for cosmetics, but if zero is set this will
76
+ overrides this behaviour and zero you will get.
77
+ symmetric_around_zero : `bool`, optional
78
+ Whether to make the limits symmetric around zero.
79
+
80
+ Returns
81
+ -------
82
+ calculate_safe_plotting_limits : `callable`
83
+ The calculate_safe_plotting_limits function to pass the data series to.
84
+ """
85
+ memory: list[Sequence] = []
86
+
87
+ def calculate_safe_plotting_limits(
88
+ data_series: Sequence, # a sequence of sequences is still a sequence
89
+ ) -> tuple[float, float]:
90
+ """Calculate the right limits for plotting for one or more data series.
91
+
92
+ Given one or more data series with potential outliers, calculated the
93
+ values to pass for ymin, ymax so that extreme outliers don't ruin the
94
+ plot. If you are plotting several series on a single axis, pass them
95
+ all in and the overall plotting range will be given.
96
+
97
+ Parameters
98
+ ----------
99
+ data_series : `iterable` or `iterable` of `iterable`
100
+ One or more data series which will be going on the same axis, and
101
+ therefore want to have their common plotting limits calculated.
102
+
103
+ Returns
104
+ -------
105
+ ymin : `float`
106
+ The value to set the ylim minimum to.
107
+ ymax : `float`
108
+ The value to set the ylim maximum to.
109
+ """
110
+ nonlocal constant_extra
111
+ nonlocal percentile
112
+ nonlocal symmetric_around_zero
113
+
114
+ if not isinstance(data_series, Iterable):
115
+ raise TypeError("data_series must be either an iterable, or an iterable of iterables")
116
+
117
+ # now we're sure we have an iterable, if it's just one make it a list
118
+ # of it lsst.utils.ensure_iterable is not suitable here as we already
119
+ # have one, we would need ensure_iterable_of_iterables here
120
+
121
+ # np.array are Iterable but not Sequence so isinstance that
122
+ if not isinstance(data_series[0], Iterable):
123
+ # we have a single data series, not multiple, wrap in [] so we can
124
+ # iterate over it as if we were given many
125
+ data_series = [data_series]
126
+
127
+ memory.extend(data_series)
128
+
129
+ mins = []
130
+ maxs = []
131
+
132
+ for dataSeries in memory:
133
+ max_val = np.nanpercentile(dataSeries, percentile)
134
+ min_val = np.nanpercentile(dataSeries, 100.0 - percentile)
135
+
136
+ if constant_extra is None:
137
+ data_range = max_val - min_val
138
+ constant_extra = 0.05 * data_range
139
+
140
+ max_val += constant_extra
141
+ min_val -= constant_extra
142
+
143
+ maxs.append(max_val)
144
+ mins.append(min_val)
145
+
146
+ max_val = max(maxs)
147
+ min_val = min(mins)
148
+
149
+ if symmetric_around_zero:
150
+ biggest_abs = max(abs(min_val), abs(max_val))
151
+ return -biggest_abs, biggest_abs
152
+
153
+ return min_val, max_val
154
+
155
+ return calculate_safe_plotting_limits
@@ -0,0 +1,184 @@
1
+ # This file is part of utils.
2
+ #
3
+ # Developed for the LSST Data Management System.
4
+ # This product includes software developed by the LSST Project
5
+ # (https://www.lsst.org).
6
+ # See the COPYRIGHT file at the top-level directory of this distribution
7
+ # for details of code ownership.
8
+ #
9
+ # Use of this source code is governed by a 3-clause BSD-style
10
+ # license that can be found in the LICENSE file.
11
+ """Utilities for making publication-quality figures."""
12
+
13
+ __all__ = [
14
+ "accent_color",
15
+ "divergent_cmap",
16
+ "galaxies_cmap",
17
+ "galaxies_color",
18
+ "get_band_dicts",
19
+ "mk_colormap",
20
+ "set_rubin_plotstyle",
21
+ "sso_cmap",
22
+ "sso_color",
23
+ "stars_cmap",
24
+ "stars_color",
25
+ ]
26
+
27
+ import numpy as np
28
+
29
+ from . import (
30
+ get_multiband_plot_colors,
31
+ get_multiband_plot_linestyles,
32
+ get_multiband_plot_symbols,
33
+ )
34
+
35
+
36
+ def set_rubin_plotstyle() -> None:
37
+ """
38
+ Set the matplotlib style for Rubin publications
39
+ """
40
+ from matplotlib import style
41
+
42
+ style.use("lsst.utils.plotting.rubin")
43
+
44
+
45
+ def mk_colormap(colorNames): # type: ignore
46
+ """Make a colormap from the list of color names.
47
+
48
+ Parameters
49
+ ----------
50
+ colorNames : `list`
51
+ A list of strings that correspond to matplotlib named colors.
52
+
53
+ Returns
54
+ -------
55
+ cmap : `matplotlib.colors.LinearSegmentedColormap`
56
+ A colormap stepping through the supplied list of names.
57
+ """
58
+ from matplotlib import colors
59
+
60
+ blues = []
61
+ greens = []
62
+ reds = []
63
+ alphas = []
64
+
65
+ if len(colorNames) == 1:
66
+ # Alpha is between 0 and 1 really but
67
+ # using 1.5 saturates out the top of the
68
+ # colorscale, this looks good for ComCam data
69
+ # but might want to be changed in the future.
70
+ alphaRange = [0.2, 1.0]
71
+ nums = np.linspace(0, 1, len(alphaRange))
72
+ r, g, b = colors.colorConverter.to_rgb(colorNames[0])
73
+ for num, alpha in zip(nums, alphaRange):
74
+ blues.append((num, b, b))
75
+ greens.append((num, g, g))
76
+ reds.append((num, r, r))
77
+ alphas.append((num, alpha, alpha))
78
+
79
+ else:
80
+ nums = np.linspace(0, 1, len(colorNames))
81
+ if len(colorNames) == 3:
82
+ alphaRange = [1.0, 1.0, 1.0]
83
+ elif len(colorNames) == 5:
84
+ alphaRange = [1.0, 0.7, 0.3, 0.7, 1.0]
85
+ else:
86
+ alphaRange = np.ones(len(colorNames))
87
+
88
+ for num, color, alpha in zip(nums, colorNames, alphaRange):
89
+ r, g, b = colors.colorConverter.to_rgb(color)
90
+ blues.append((num, b, b))
91
+ greens.append((num, g, g))
92
+ reds.append((num, r, r))
93
+ alphas.append((num, alpha, alpha))
94
+
95
+ colorDict = {"blue": blues, "red": reds, "green": greens, "alpha": alphas}
96
+ cmap = colors.LinearSegmentedColormap("newCmap", colorDict)
97
+ return cmap
98
+
99
+
100
+ def divergent_cmap(): # type: ignore
101
+ """
102
+ Make a divergent color map.
103
+ """
104
+ cmap = mk_colormap([stars_color(), "#D9DCDE", accent_color()])
105
+
106
+ return cmap
107
+
108
+
109
+ def stars_cmap(single_color=False): # type: ignore
110
+ """Make a color map for stars."""
111
+ import seaborn as sns
112
+ from matplotlib.colors import ListedColormap
113
+
114
+ if single_color:
115
+ cmap = mk_colormap([stars_color()])
116
+ else:
117
+ cmap = ListedColormap(sns.color_palette("mako", 256))
118
+ return cmap
119
+
120
+
121
+ def stars_color() -> str:
122
+ """Return the star color string for lines"""
123
+ return "#084d96"
124
+
125
+
126
+ def accent_color() -> str:
127
+ """Return a contrasting color for overplotting,
128
+ black is the best for this but if you need two colors
129
+ this works well on blue.
130
+ """
131
+ return "#DE8F05"
132
+
133
+
134
+ def galaxies_cmap(single_color=False): # type: ignore
135
+ """Make a color map for galaxies."""
136
+ if single_color:
137
+ cmap = mk_colormap([galaxies_color()])
138
+ else:
139
+ cmap = "inferno"
140
+ return cmap
141
+
142
+
143
+ def galaxies_color() -> str:
144
+ """Return the galaxy color string for lines"""
145
+ return "#961A45"
146
+
147
+
148
+ def sso_color() -> str:
149
+ """Return the SSO color string for lines"""
150
+ return "#01694c"
151
+
152
+
153
+ def sso_cmap(single_color=False): # type: ignore
154
+ """Make a color map for solar system objects."""
155
+ if single_color:
156
+ cmap = mk_colormap([sso_color()])
157
+ else:
158
+ cmap = "viridis"
159
+ return cmap
160
+
161
+
162
+ def get_band_dicts() -> dict:
163
+ """
164
+ Define palettes, from RTN-045. This includes dicts for colors (bandpass
165
+ colors for white background), colors_black (bandpass colors for
166
+ black background), plot symbols, and line_styles, keyed on band (ugrizy).
167
+
168
+ Returns
169
+ -------
170
+ band_dict : `dict` of `dict`
171
+ Dicts of colors, colors_black, symbols, and line_styles,
172
+ keyed on bands 'u', 'g', 'r', 'i', 'z', and 'y'.
173
+ """
174
+ colors = get_multiband_plot_colors()
175
+ colors_black = get_multiband_plot_colors(dark_background=True)
176
+ symbols = get_multiband_plot_symbols()
177
+ line_styles = get_multiband_plot_linestyles()
178
+
179
+ return {
180
+ "colors": colors,
181
+ "colors_black": colors_black,
182
+ "symbols": symbols,
183
+ "line_styles": line_styles,
184
+ }
@@ -0,0 +1,46 @@
1
+ axes.labelweight: normal
2
+ figure.titleweight : normal
3
+ axes.labelsize : large
4
+ axes.linewidth: 1
5
+ axes.titleweight: normal
6
+ axes.titlesize : small
7
+ figure.titlesize: small
8
+ errorbar.capsize: 3.0
9
+
10
+ lines.linewidth : 2
11
+ lines.markersize : 10
12
+
13
+ xtick.labelsize : small
14
+ ytick.labelsize : small
15
+
16
+ figure.dpi : 300.0
17
+ figure.facecolor: White
18
+ figure.figsize : 6.4, 4.8
19
+
20
+ # From seaborn-v0_8-colorblind colormap: https://seaborn.pydata.org/tutorial/color_palettes.html
21
+ axes.prop_cycle: cycler('color', ['0173B2', 'DE8F05', '029E73', 'D55E00', 'CC78BC', 'CA9161', 'FBAFE4', '949494', 'ECE133', '56B4E9'])
22
+ patch.facecolor: 006BA4
23
+
24
+ font.size : 14
25
+ legend.fontsize: x-small
26
+
27
+ xtick.major.width: 1.0
28
+ xtick.minor.width: 0.5
29
+ xtick.major.size: 7
30
+ xtick.minor.size: 4
31
+ xtick.minor.visible: True
32
+ xtick.direction: in
33
+ xtick.top: True
34
+ xtick.bottom: True
35
+ ytick.major.width: 1.0
36
+ ytick.minor.width: 0.5
37
+ ytick.major.size: 7
38
+ ytick.minor.size: 4
39
+ ytick.minor.visible: True
40
+ ytick.direction: in
41
+ ytick.left: True
42
+ ytick.right: True
43
+ xtick.major.pad : 6
44
+ xtick.minor.pad : 6
45
+ ytick.major.pad : 6
46
+ ytick.minor.pad : 6