Coverage for python / lsst / multiprofit / plotting / asinhstretchsigned.py: 31%

47 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-28 08:43 +0000

1# This file is part of multiprofit. 

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# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22from __future__ import annotations 

23 

24import astropy.visualization as apvis 

25import numpy as np 

26from numpy.typing import NDArray 

27 

28# This is all hacked from astropy's AsinhStretch 

29 

30__all__ = ["AsinhStretchSigned", "SinhStretchSigned"] 

31 

32 

33def _prepare(values: np.ndarray, clip: bool = True, out: np.ndarray | None = None) -> np.ndarray: 

34 """Return clipped and/or copied values from input. 

35 

36 Parameters 

37 ---------- 

38 values 

39 The values to copy/clip from. 

40 clip 

41 Whether to clip values to between 0 and 1 (inclusive). 

42 out 

43 An existing array to assign to. 

44 

45 Returns 

46 ------- 

47 prepared 

48 The prepared values. 

49 """ 

50 if clip: 

51 return np.clip(values, 0.0, 1.0, out=out) 

52 else: 

53 if out is None: 

54 return np.array(values, copy=True) 

55 else: 

56 out[:] = np.asarray(values) 

57 return out 

58 

59 

60class AsinhStretchSigned(apvis.BaseStretch): 

61 r""" 

62 A signed asinh stretch. 

63 

64 The stretch is given by: 

65 

66 .. math:: 

67 y = 0.5(1 + sign(x - 0.5)\frac{{\rm asinh}(2(x - 0.5) / a)}{{\rm asinh}(1 / a)}). 

68 

69 This is a signed version of the astropy AsinhStretch, which can be used 

70 in plots of scaled model residuals (chi) to give greater contrast between 

71 values around zero. 

72 

73 Parameters 

74 ---------- 

75 a : float, optional 

76 The ``a`` parameter used in the above formula. The value of 

77 this parameter is where the asinh curve transitions from linear 

78 to logarithmic behavior, expressed as a fraction of the 

79 normalized image. Must be in the range between 0 and 1. 

80 Default is 0.1. 

81 """ # noqa: W505 

82 

83 def __init__(self, a: float = 0.1) -> None: 

84 super().__init__() 

85 self.a = a 

86 

87 def __call__(self, values: NDArray, clip: bool = True, out: np.ndarray = None) -> np.ndarray: 

88 values = _prepare(values, clip=clip, out=out) 

89 values *= 2 

90 values -= 1 

91 signs = np.sign(values) 

92 np.abs(values, out=values) 

93 np.true_divide(values, self.a, out=values) 

94 np.arcsinh(values, out=values) 

95 np.true_divide(values, np.arcsinh(1.0 / self.a), out=values) 

96 np.true_divide(1.0 + signs * values, 2.0, out=values) 

97 return values 

98 

99 @property 

100 def inverse(self) -> SinhStretchSigned: 

101 """A stretch object that performs the inverse operation. 

102 

103 Returns 

104 ------- 

105 inverse 

106 The inverse stretch. 

107 """ 

108 return SinhStretchSigned(a=1.0 / np.arcsinh(1.0 / self.a)) 

109 

110 

111class SinhStretchSigned(apvis.BaseStretch): 

112 r""" 

113 A signed sinh stretch. 

114 

115 The stretch is given by: 

116 

117 .. math:: 

118 y = \frac{{\rm sinh}(x / a)}{{\rm sinh}(1 / a)} 

119 

120 This is a signed version of the astropy SinhStretch, which is provided for 

121 completeness rather than for any particular use case. 

122 

123 Parameters 

124 ---------- 

125 a : float, optional 

126 The ``a`` parameter used in the above formula. Default is 1/3. 

127 """ 

128 

129 def __init__(self, a: float = 1.0 / 3.0) -> None: 

130 super().__init__() 

131 self.a = a 

132 

133 def __call__(self, values: NDArray, clip: bool = True, out: np.ndarray = None) -> np.ndarray: 

134 values = _prepare(values, clip=clip, out=out) 

135 values *= 2.0 

136 values -= 1.0 

137 np.true_divide(values, self.a, out=values) 

138 np.sinh(values, out=values) 

139 np.true_divide(values, np.sinh(1.0 / self.a), out=values) 

140 values += 1.0 

141 values /= 2.0 

142 return values 

143 

144 @property 

145 def inverse(self) -> AsinhStretchSigned: 

146 """A stretch object that performs the inverse operation. 

147 

148 Returns 

149 ------- 

150 inverse 

151 The inverse stretch. 

152 """ 

153 return AsinhStretchSigned(a=1.0 / np.sinh(1.0 / self.a))