PyDIET 0.9.3__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 (177) hide show
  1. pydiet/__init__.py +12 -0
  2. pydiet/api_client/__init__.py +6 -0
  3. pydiet/api_client/client.py +57 -0
  4. pydiet/cmd/__init__.py +9 -0
  5. pydiet/cmd/start.py +107 -0
  6. pydiet/data/data_config.toml +242 -0
  7. pydiet/data/description.txt +1 -0
  8. pydiet/data/instruments/description.txt +1 -0
  9. pydiet/data/instruments/megacam/default +0 -0
  10. pydiet/data/instruments/megacam/description.txt +1 -0
  11. pydiet/data/instruments/megacam/detector/description.txt +1 -0
  12. pydiet/data/instruments/megacam/detector/qe/MegaCam_QE.average.fits +0 -0
  13. pydiet/data/instruments/megacam/detector/qe/description.txt +2 -0
  14. pydiet/data/instruments/megacam/filters/CaHK.MP9303.fits +0 -0
  15. pydiet/data/instruments/megacam/filters/Ha.MP9603.fits +0 -0
  16. pydiet/data/instruments/megacam/filters/HaOFF.MP9604.fits +0 -0
  17. pydiet/data/instruments/megacam/filters/M4112.MP9403.fits +0 -0
  18. pydiet/data/instruments/megacam/filters/M4376.MP9404.fits +0 -0
  19. pydiet/data/instruments/megacam/filters/OIII.MP9501.fits +0 -0
  20. pydiet/data/instruments/megacam/filters/OIIIOFF.MP9502.fits +0 -0
  21. pydiet/data/instruments/megacam/filters/description.txt +1 -0
  22. pydiet/data/instruments/megacam/filters/g.MP9402.fits +0 -0
  23. pydiet/data/instruments/megacam/filters/gri.MP9605.fits +0 -0
  24. pydiet/data/instruments/megacam/filters/i.MP9703.fits +0 -0
  25. pydiet/data/instruments/megacam/filters/r.MP9602.fits +0 -0
  26. pydiet/data/instruments/megacam/filters/u.MP9302.fits +0 -0
  27. pydiet/data/instruments/megacam/filters/z.MP9901.fits +0 -0
  28. pydiet/data/instruments/megacam/optics/description.txt +2 -0
  29. pydiet/data/instruments/megacam/optics/transmission/MegaPrime_transmission.fits +0 -0
  30. pydiet/data/instruments/megacam/optics/transmission/description.txt +2 -0
  31. pydiet/data/instruments/wircam/description.txt +1 -0
  32. pydiet/data/instruments/wircam/detector/description.txt +1 -0
  33. pydiet/data/instruments/wircam/detector/qe/WIRCam_QE.average.fits +0 -0
  34. pydiet/data/instruments/wircam/detector/qe/description.txt +2 -0
  35. pydiet/data/instruments/wircam/filters/BrG.WC8305.fits +0 -0
  36. pydiet/data/instruments/wircam/filters/CH4Off.WC8204.fits +0 -0
  37. pydiet/data/instruments/wircam/filters/CH4On.WC8203.fits +0 -0
  38. pydiet/data/instruments/wircam/filters/CO.WC8306.fits +0 -0
  39. pydiet/data/instruments/wircam/filters/H.WC8201.fits +0 -0
  40. pydiet/data/instruments/wircam/filters/H.WC8202.fits +0 -0
  41. pydiet/data/instruments/wircam/filters/H2.WC8304.fits +0 -0
  42. pydiet/data/instruments/wircam/filters/J.WC8101.fits +0 -0
  43. pydiet/data/instruments/wircam/filters/J.WC8103.fits +0 -0
  44. pydiet/data/instruments/wircam/filters/Kcont.WC8303.fits +0 -0
  45. pydiet/data/instruments/wircam/filters/Ks.WC8301.fits +0 -0
  46. pydiet/data/instruments/wircam/filters/Ks.WC8302.fits +0 -0
  47. pydiet/data/instruments/wircam/filters/LowOH1.WC8104.fits +0 -0
  48. pydiet/data/instruments/wircam/filters/LowOH2.WC8102.fits +0 -0
  49. pydiet/data/instruments/wircam/filters/W.WC8105.fits +0 -0
  50. pydiet/data/instruments/wircam/filters/Y.WC8002.fits +0 -0
  51. pydiet/data/instruments/wircam/filters/description.txt +1 -0
  52. pydiet/data/instruments/wircam/optics/description.txt +2 -0
  53. pydiet/data/instruments/wircam/optics/transmission/WIRCam_transmission.fits +0 -0
  54. pydiet/data/instruments/wircam/optics/transmission/description.txt +2 -0
  55. pydiet/data/sites/description.txt +1 -0
  56. pydiet/data/sites/mko/default +0 -0
  57. pydiet/data/sites/mko/description.txt +2 -0
  58. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.0.fits +0 -0
  59. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.1.fits +0 -0
  60. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.2.fits +0 -0
  61. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.3.fits +0 -0
  62. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.4.fits +0 -0
  63. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.5.fits +0 -0
  64. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.6.fits +0 -0
  65. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.7.fits +0 -0
  66. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.8.fits +0 -0
  67. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.9.fits +0 -0
  68. pydiet/data/sites/mko/emission/MKO_emission.bright.AM2.0.fits +0 -0
  69. pydiet/data/sites/mko/emission/MKO_emission.bright.AM2.5.fits +0 -0
  70. pydiet/data/sites/mko/emission/MKO_emission.bright.AM3.0.fits +0 -0
  71. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.0.fits +0 -0
  72. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.1.fits +0 -0
  73. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.2.fits +0 -0
  74. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.3.fits +0 -0
  75. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.4.fits +0 -0
  76. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.5.fits +0 -0
  77. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.6.fits +0 -0
  78. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.7.fits +0 -0
  79. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.8.fits +0 -0
  80. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.9.fits +0 -0
  81. pydiet/data/sites/mko/emission/MKO_emission.dark.AM2.0.fits +0 -0
  82. pydiet/data/sites/mko/emission/MKO_emission.dark.AM2.5.fits +0 -0
  83. pydiet/data/sites/mko/emission/MKO_emission.dark.AM3.0.fits +0 -0
  84. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.0.fits +0 -0
  85. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.1.fits +0 -0
  86. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.2.fits +0 -0
  87. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.3.fits +0 -0
  88. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.4.fits +0 -0
  89. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.5.fits +0 -0
  90. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.6.fits +0 -0
  91. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.7.fits +0 -0
  92. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.8.fits +0 -0
  93. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.9.fits +0 -0
  94. pydiet/data/sites/mko/emission/MKO_emission.grey.AM2.0.fits +0 -0
  95. pydiet/data/sites/mko/emission/MKO_emission.grey.AM2.5.fits +0 -0
  96. pydiet/data/sites/mko/emission/MKO_emission.grey.AM3.0.fits +0 -0
  97. pydiet/data/sites/mko/emission/description.txt +5 -0
  98. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.0.fits +0 -0
  99. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.1.fits +0 -0
  100. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.2.fits +0 -0
  101. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.3.fits +0 -0
  102. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.4.fits +0 -0
  103. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.5.fits +0 -0
  104. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.6.fits +0 -0
  105. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.7.fits +0 -0
  106. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.8.fits +0 -0
  107. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.9.fits +0 -0
  108. pydiet/data/sites/mko/transmission/MKO_transmission.AM2.0.fits +0 -0
  109. pydiet/data/sites/mko/transmission/MKO_transmission.AM2.5.fits +0 -0
  110. pydiet/data/sites/mko/transmission/MKO_transmission.AM3.0.fits +0 -0
  111. pydiet/data/sites/mko/transmission/MKO_transmission.AM3.5.fits +0 -0
  112. pydiet/data/sites/mko/transmission/MKO_transmission.AM4.0.fits +0 -0
  113. pydiet/data/sites/mko/transmission/MKO_transmission.AM4.5.fits +0 -0
  114. pydiet/data/sites/mko/transmission/MKO_transmission.AM5.0.fits +0 -0
  115. pydiet/data/sites/mko/transmission/description.txt +5 -0
  116. pydiet/data/telescopes/cfht/default +0 -0
  117. pydiet/data/telescopes/cfht/description.txt +1 -0
  118. pydiet/data/telescopes/cfht/emission/description.txt +2 -0
  119. pydiet/data/telescopes/cfht/transmission/CFHT_M1_transmission.fits +0 -0
  120. pydiet/data/telescopes/cfht/transmission/description.txt +1 -0
  121. pydiet/data/telescopes/description.txt +1 -0
  122. pydiet/package.py +55 -0
  123. pydiet/py.typed +0 -0
  124. pydiet/server/__init__.py +9 -0
  125. pydiet/server/app.py +369 -0
  126. pydiet/server/config/__init__.py +51 -0
  127. pydiet/server/config/config.py +330 -0
  128. pydiet/server/config/fields.py +49 -0
  129. pydiet/server/config/settings.py +166 -0
  130. pydiet/server/data.py +31 -0
  131. pydiet/server/datafiles.py +367 -0
  132. pydiet/server/image.py +342 -0
  133. pydiet/server/models/__init__.py +34 -0
  134. pydiet/server/models/dataconfig.py +195 -0
  135. pydiet/server/models/default.py +9 -0
  136. pydiet/server/models/exceptions.py +9 -0
  137. pydiet/server/models/instrument.py +314 -0
  138. pydiet/server/models/query.py +172 -0
  139. pydiet/server/models/response.py +97 -0
  140. pydiet/server/models/types.py +35 -0
  141. pydiet/server/photsys.py +71 -0
  142. pydiet/server/response.py +237 -0
  143. pydiet/server/types/__init__.py +8 -0
  144. pydiet/server/types/quantity.py +532 -0
  145. pydiet/server/types/string.py +318 -0
  146. pydiet/templates/common/base.html +80 -0
  147. pydiet/templates/common/plot_filter.html +17 -0
  148. pydiet/templates/common/privacy.html +132 -0
  149. pydiet/templates/common/settings.html +23 -0
  150. pydiet/templates/common/terms.html +101 -0
  151. pydiet/templates/megacam/etc_form.html +319 -0
  152. pydiet/templates/megacam/etc_results.html +190 -0
  153. pydiet/templates/wircam/etc_form.html +319 -0
  154. pydiet/templates/wircam/etc_results.html +190 -0
  155. pydiet/web_client/css/style.css +221 -0
  156. pydiet/web_client/dist/pydiet.js +31 -0
  157. pydiet/web_client/images/logo.svg +6 -0
  158. pydiet/web_client/images/megacam/background.jpg +0 -0
  159. pydiet/web_client/images/megacam/logo.png +0 -0
  160. pydiet/web_client/images/wircam/background.jpg +0 -0
  161. pydiet/web_client/images/wircam/logo.png +0 -0
  162. pydiet/web_client/js/dom.js +51 -0
  163. pydiet/web_client/js/etc.js +63 -0
  164. pydiet/web_client/js/fetch.js +49 -0
  165. pydiet/web_client/js/instrument.js +62 -0
  166. pydiet/web_client/js/main.js +15 -0
  167. pydiet/web_client/js/plot.js +88 -0
  168. pydiet/web_client/js/settings.js +57 -0
  169. pydiet/web_client/js/theme.js +43 -0
  170. pydiet/web_client/js/url.js +12 -0
  171. pydiet/web_client/jsdoc.json +20 -0
  172. pydiet/web_client/package.json +83 -0
  173. pydiet-0.9.3.dist-info/METADATA +118 -0
  174. pydiet-0.9.3.dist-info/RECORD +177 -0
  175. pydiet-0.9.3.dist-info/WHEEL +4 -0
  176. pydiet-0.9.3.dist-info/entry_points.txt +5 -0
  177. pydiet-0.9.3.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,172 @@
1
+ """
2
+ Query models
3
+ """
4
+ # Copyright CFHT/CNRS/CEA/UParisSaclay
5
+ # Licensed under the MIT licence
6
+
7
+ from typing import Literal
8
+ from pydantic import (
9
+ BaseModel,
10
+ Field,
11
+ PydanticUserError,
12
+ ValidationInfo,
13
+ field_validator
14
+ )
15
+
16
+ from .default import default_filter, default_instrument, filters, instruments
17
+ from .exceptions import ETCValidationError
18
+ from .types import (
19
+ ComputeID,
20
+ FilterID,
21
+ InstrumentID,
22
+ PhotometryID,
23
+ PhotSysID,
24
+ SkyID,
25
+ SourceID,
26
+ StackingID
27
+ )
28
+
29
+ class ETCQueryModel(BaseModel):
30
+
31
+ instrument: InstrumentID = Field(
32
+ default=default_instrument.id,
33
+ description="Instrument ID"
34
+ )
35
+
36
+ airmass: float = Field(
37
+ default=1.2,
38
+ ge=1.,
39
+ description="Observation airmass"
40
+ )
41
+
42
+ aperture: float = Field(
43
+ default=3.,
44
+ gt=0.,
45
+ le=15.,
46
+ description="Photometric aperture diameter [\"]"
47
+ )
48
+
49
+ brightness: float = Field(
50
+ default=20.,
51
+ ge=-100.,
52
+ le=1000.,
53
+ description="Source brightness"
54
+ )
55
+
56
+ compute: ComputeID = Field(
57
+ default='etime',
58
+ description="Computation type"
59
+ )
60
+
61
+ etime: float = Field(
62
+ default=20.,
63
+ ge=0.,
64
+ le=1e30,
65
+ description="Exposure time [s]"
66
+ )
67
+
68
+ exposures: int = Field(
69
+ default=1,
70
+ ge=1,
71
+ le=1000000,
72
+ description="Number of exposures"
73
+ )
74
+
75
+ filter: FilterID = Field(
76
+ default=default_filter,
77
+ description="Instrument filter"
78
+ )
79
+
80
+ photometry: PhotometryID = Field(
81
+ default='model_fitting',
82
+ description="Photometry type"
83
+ )
84
+
85
+ seeing: float = Field(
86
+ default=0.7,
87
+ ge=0.1,
88
+ le=100.
89
+ )
90
+
91
+ sky: SkyID = Field(
92
+ default='dark',
93
+ description="Sky setting"
94
+ )
95
+
96
+ sky_brightness: float = Field(
97
+ default=22.,
98
+ ge=-100.,
99
+ le=1000.,
100
+ description="Sky surface brightness"
101
+ )
102
+
103
+ sersic_radius: float = Field(
104
+ default=1.,
105
+ gt=0.,
106
+ le=10.,
107
+ description="Sérsic effective radius [\"]"
108
+ )
109
+
110
+ sersic_index: float = Field(
111
+ default=1.,
112
+ ge=0.3,
113
+ le=10.,
114
+ description="Sérsic index"
115
+ )
116
+
117
+ sky_unit: PhotSysID = Field(
118
+ default='abmag',
119
+ description="Sky background photometric system"
120
+ )
121
+
122
+ snr: float = Field(
123
+ default=10.,
124
+ gt=0.,
125
+ description="Required source Signal-to-Noise Ratio"
126
+ )
127
+
128
+ source: SourceID = Field(
129
+ default='point_source',
130
+ description="Source type"
131
+ )
132
+
133
+ stacking: StackingID = Field(
134
+ default='median',
135
+ description="Stacking method"
136
+ )
137
+
138
+ transparency: float = Field(
139
+ default=1.,
140
+ gt=0.,
141
+ le=1.,
142
+ description="Sky transparency"
143
+ )
144
+
145
+ unit: PhotSysID = Field(
146
+ default='abmag',
147
+ description="Photometric system"
148
+ )
149
+
150
+ @field_validator('filter')
151
+ def validate_filter(cls, f: str, info: ValidationInfo) -> str:
152
+ """
153
+ Kind of emulate Enum validation and errors.
154
+ """
155
+ instrument = info.data['instrument']
156
+ fids = list(instruments[instrument].filters.transmissions) + ['upload']
157
+ if f not in fids:
158
+ expected = f"'{fids[0]}'" + \
159
+ (
160
+ "".join(f", '{fid}'" for fid in fids[:-1]) \
161
+ if len(fids) > 2 else ""
162
+ ) + (
163
+ f" or '{fids[-1]}'" if len(fids) > 1 else ""
164
+ )
165
+ raise ETCValidationError({
166
+ "type": "enum",
167
+ "loc": ("query", "filter"),
168
+ "input": str(f),
169
+ "expected": expected
170
+ })
171
+ return f
172
+
@@ -0,0 +1,97 @@
1
+ """
2
+ Response models
3
+ """
4
+ # Copyright CFHT/CNRS/CEA/UParisSaclay
5
+ # Licensed under the MIT licence
6
+
7
+ from typing import Optional
8
+
9
+ from astropy import units as u #type: ignore[import-untyped]
10
+ from pydantic import BaseModel, Field, Json
11
+
12
+ from ..types import AnnotatedQuantity
13
+ from .types import ComputeID, FilterID, InstrumentID
14
+ from .instrument import TransmissionModel
15
+
16
+
17
+ class ETCResponseModel(BaseModel):
18
+
19
+ instrument: str
20
+
21
+ filter: str
22
+
23
+ compute: ComputeID
24
+
25
+ zp: float=Field(
26
+ default=0.,
27
+ ge=-100.,
28
+ le=100.,
29
+ description="Estimate magnitude zero-point"
30
+ )
31
+
32
+ snr: float=Field(
33
+ default=10.,
34
+ ge=0.,
35
+ lt=1e30,
36
+ description="Estimated source Signal-to-Noise Ratio"
37
+ )
38
+
39
+ etime: float=Field(
40
+ default=1.,
41
+ ge=0.,
42
+ lt=1e30,
43
+ description="Estimated exposure time"
44
+ )
45
+
46
+ etime_skysat: float=Field(
47
+ default=0.,
48
+ ge=0.,
49
+ lt=1e30,
50
+ description="Estimated exposure time for sky background saturation"
51
+ )
52
+
53
+ etime_sourcesat: float=Field(
54
+ default=0.,
55
+ ge=0.,
56
+ lt=1e30,
57
+ description="Estimated exposure time for source saturation"
58
+ )
59
+
60
+ ttime: float=Field(
61
+ default=1.,
62
+ ge=0.,
63
+ lt=1e30,
64
+ description="Estimated total time"
65
+ )
66
+
67
+ sky_mag: float=Field(
68
+ default=99.,
69
+ ge=-100.,
70
+ le=100.,
71
+ description="Estimated sky background in mag/arcsec2"
72
+ )
73
+
74
+ lambda_pivot: float=Field(
75
+ default=0.,
76
+ ge=0.,
77
+ lt=1e12,
78
+ description="Pivot wavelength of the full filter response in nm"
79
+ )
80
+
81
+ bandwidth_rect: float=Field(
82
+ default=0.,
83
+ ge=0.,
84
+ lt=1e12,
85
+ description="Equivalent rectangular bandwidth of the full filter response in nm"
86
+ )
87
+
88
+ cutout: Optional[str]=Field(
89
+ default=None,
90
+ description="GIF animation of the source"
91
+ )
92
+
93
+ filter_transmission: Optional[Json] = None
94
+
95
+ atmosphere_transmission: Optional[Json] = None
96
+
97
+
@@ -0,0 +1,35 @@
1
+ """
2
+ Custom types for PyDIET data models
3
+ """
4
+ # Copyright CFHT/CNRS/CEA/UParisSaclay
5
+ # Licensed under the MIT licence
6
+
7
+ from enum import Enum
8
+ from typing import Literal
9
+
10
+ from .default import filters, instruments
11
+
12
+ ComputeID = Literal['etime', 'snr']
13
+
14
+ FilterID = Enum( # type: ignore[misc]
15
+ "FilterID",
16
+ {tag : tag for tag in filters.keys()} | {'upload' : 'upload'},
17
+ type=str
18
+ )
19
+
20
+ InstrumentID = Enum( # type: ignore[misc]
21
+ "InstrumentID",
22
+ {tag : tag for tag in instruments.keys()},
23
+ type=str
24
+ )
25
+
26
+ SkyID = Literal['dark', 'grey', 'bright', 'specify']
27
+
28
+ PhotometryID = Literal['model_fitting', 'fixed_aperture', 'optimal_aperture', 'large_aperture']
29
+
30
+ PhotSysID = Literal['abmag', 'vegamag', 'fmegajy', 'fmujy', 'flux', 'photons']
31
+
32
+ SourceID = Literal['point_source', 'galaxy', 'extended']
33
+
34
+ StackingID = Literal['average', 'median']
35
+
@@ -0,0 +1,71 @@
1
+ """
2
+ Photometric system conversion module
3
+ """
4
+ # Copyright CFHT
5
+ # Licensed under the MIT licence
6
+
7
+ from typing import Optional
8
+
9
+ from astropy import units as u #type: ignore[import-untyped]
10
+
11
+ from .models.types import PhotSysID
12
+
13
+ from .data import ab_spectrum, vega_spectrum
14
+
15
+
16
+ class PhotSys(object):
17
+ def __init__(
18
+ self,
19
+ id: PhotSysID = 'abmag',
20
+ wavelength: Optional[u.Quantity[u.nm]] = None,
21
+ dwavelength: Optional[u.Quantity[u.nm]] = None):
22
+
23
+ self.eps = 1e-30
24
+ self.id = id
25
+ if id == 'abmag':
26
+ self.spectrum = ab_spectrum
27
+ self.photon_rate = self._rate_from_mag
28
+ elif id == 'vegamag':
29
+ self.spectrum = vega_spectrum
30
+ self.photon_rate = self._rate_from_mag #type: ignore[assignment]
31
+ elif id == 'fmegajy':
32
+ self.spectrum = ab_spectrum
33
+ self.photon_rate = self._rate_from_fmegajy #type: ignore[assignment]
34
+ elif id == 'fmujy':
35
+ self.spectrum = ab_spectrum
36
+ self.photon_rate = self._rate_from_fmujy #type: ignore[assignment]
37
+ elif id == 'flux':
38
+ self.spectrum = ab_spectrum
39
+ self.photon_rate = self._rate_from_flux #type: ignore[assignment]
40
+ if wavelength is None:
41
+ raise ValueError("wavelength is required when id == 'flux'")
42
+ if dwavelength is None:
43
+ raise ValueError("dwavelength is required when id == 'flux'")
44
+ self.wavelength = wavelength.to_value(u.nm)
45
+ self.dwavelength = dwavelength.to_value(u.nm)
46
+ else:
47
+ self.spectrum = None
48
+ self.photon_rate = self._rate_from_photons #type: ignore[assignment]
49
+
50
+
51
+ def _rate_from_mag(self, mag: float) -> float:
52
+ return 10.**(-0.4 * mag) if mag < 40. else 1e-16
53
+
54
+ def _rate_from_fmegajy(self, fnu: float) -> float:
55
+ return 2.754e+02 * (fnu if fnu >= self.eps else self.eps)
56
+
57
+ def _rate_from_fmujy(self, fnu: float) -> float:
58
+ return 2.754e-10 * (fnu if fnu >= self.eps else self.eps)
59
+
60
+ def _rate_from_flux(self, flux: float) -> float:
61
+ if self.dwavelength < self.eps:
62
+ self.dwavelength = self.eps
63
+ # 10**(0.4*48.6) / 3e17 (in nm/s) = 91.808
64
+ return 91.808 * self.wavelength ** 2 / self.dwavelength \
65
+ * (flux if flux >= self.eps else self.eps) * 1e-15
66
+
67
+ def _rate_from_photons(self, photons: float) -> float:
68
+ return photons if photons >= self.eps else self.eps
69
+
70
+
71
+
@@ -0,0 +1,237 @@
1
+ """
2
+ Computation module
3
+ """
4
+ # Copyright CFHT
5
+ # Licensed under the MIT licence
6
+
7
+ from math import pi, sqrt
8
+ from os import PathLike
9
+ from typing import IO, Optional
10
+
11
+ from astropy import units as u #type: ignore[import-untyped]
12
+ from pydantic import BaseModel, Field
13
+ from synphot import Observation, SpectralElement #type: ignore[import-untyped]
14
+
15
+ from .image import Image
16
+ from .models import (
17
+ ETCQueryModel,
18
+ ETCResponseModel,
19
+ FiltersModel,
20
+ SBSEDModel,
21
+ SEDModel,
22
+ TransmissionModel,
23
+ spectral_to_arrays
24
+ )
25
+ from .models.types import SkyID
26
+ from .data import instruments
27
+ from .datafiles import (
28
+ get_emission_from_transmission,
29
+ get_transmission
30
+ )
31
+ from .photsys import PhotSys
32
+
33
+
34
+ def spectrum_from_airmass(
35
+ models: dict[str, SBSEDModel | SEDModel | TransmissionModel],
36
+ sky: SkyID|None = None,
37
+ am: float = 1.) -> SpectralElement:
38
+ # Build a dictionary of emission or transmission spectra
39
+ am_spectra = {
40
+ model.vars['am'] : model.spectral #type: ignore[index]
41
+ for model in models.values()
42
+ if sky is None or model.vars['sky'] == sky #type: ignore[index]
43
+ }
44
+ ams = sorted(list(am_spectra.keys()))
45
+ # bracket the requested airmass for interpolation
46
+ aml = float(ams[0])
47
+ amp = float(ams[-1])
48
+ for aa in ams:
49
+ a = float(aa)
50
+ if a >= am:
51
+ amp = a
52
+ break
53
+ else:
54
+ aml = a
55
+ # Linear interpolation
56
+ fac = (am - aml) / (amp - aml) if am < amp else 1.
57
+ return am_spectra[aml] * (1. - fac) + am_spectra[amp] * fac #type: ignore[operator]
58
+
59
+
60
+ def get_response(
61
+ q: ETCQueryModel,
62
+ filter: Optional[IO[bytes] | PathLike | str]=None,
63
+ ui: bool=False) -> ETCResponseModel:
64
+ if filter is not None:
65
+ # Read the uploaded filter transmission file
66
+ filter_transmission = get_transmission(filter, id='upload')
67
+ # Compute filter emission based on transmission (and dummy temperature/area)
68
+ emission = get_emission_from_transmission(
69
+ filter_transmission,
70
+ temperature=273. * u.K,
71
+ id='upload'
72
+ )
73
+ # Make a copy of the instrument while adding the uploaded filter
74
+ filters = instruments[q.instrument].filters
75
+ instrument = instruments[q.instrument].model_copy(
76
+ update={
77
+ 'filters': FiltersModel(
78
+ transmissions = filters.transmissions | {
79
+ 'upload' : filter_transmission
80
+ },
81
+ emissions = filters.emissions | {'upload' : emission}
82
+ )
83
+ }
84
+ )
85
+ # Update composite transmissions and emission curves
86
+ instrument._update_transmissions()
87
+ else:
88
+ instrument = instruments[q.instrument]
89
+
90
+ telescope = instrument.telescope
91
+ detector = instrument.detector
92
+ instrument_transmission = instrument.transmissions[q.filter]
93
+
94
+ # Multiply Total instrument transmission with atmospheric transmission
95
+ atmosphere_spec = spectrum_from_airmass(
96
+ instrument.site.sky_transmissions,
97
+ am=q.airmass
98
+ ) * q.transparency
99
+ full_spec = instrument_transmission.spectral * atmosphere_spec
100
+ # Compute effective collecting area, compensating for possible obstruction
101
+ area = instrument.telescope.collecting_area - instrument.obstruction_area
102
+
103
+ # Make virtual observation
104
+ gain = detector.gain.value
105
+ tpeak = full_spec.tpeak()
106
+ lambda_pivot = full_spec.pivot()
107
+ dlambda_rect = full_spec.rectwidth()
108
+ # Actual source
109
+ photsys = PhotSys(q.unit, lambda_pivot, dlambda_rect)
110
+ if tpeak > 0.:
111
+ if photsys.spectrum is None:
112
+ ct_ref = photsys.photon_rate(q.brightness) / gain * u.ct / u.s
113
+ else:
114
+ observation = Observation(photsys.spectrum, full_spec)
115
+ # Compute ref source count rate to get effective zero-point
116
+ ct_ref = observation.countrate(area=area, binned=False) / gain
117
+ zp = u.Magnitude(1. * u.ct / u.s) - u.Magnitude(ct_ref)
118
+ else:
119
+ ct_ref = 0. * u.ct / u.s
120
+ zp = -100. * u.mag
121
+ # Compute total number of reference source electrons (photons per second)
122
+ photon_rate = (ct_ref * gain).value * photsys.photon_rate(q.brightness)
123
+
124
+ # Sky background
125
+ # Atmospheric emission
126
+ if q.sky == 'specify':
127
+ sky_photsys = PhotSys(q.sky_unit, lambda_pivot, dlambda_rect)
128
+ sky_spectrum = sky_photsys.spectrum
129
+ sky_photon_rate = sky_photsys.photon_rate(q.sky_brightness)
130
+ if sky_spectrum is not None:
131
+ # If in Jy per str, convert to arcsec2
132
+ if q.sky_unit == 'fmegajy':
133
+ sky_photon_rate *= 2.350e-11
134
+ sky_spectrum *= sky_photon_rate
135
+ else:
136
+ sky_spectrum = spectrum_from_airmass(
137
+ instrument.site.sky_emissions,
138
+ sky=q.sky,
139
+ am=q.airmass
140
+ )
141
+ if sky_spectrum is not None:
142
+ # Compute background count rate to get background surface brightness
143
+ if tpeak > 0.:
144
+ bkg_observation = Observation(
145
+ sky_spectrum,
146
+ instrument_transmission.spectral,
147
+ force='extrap'
148
+ )
149
+ # Add instrumental thermal background
150
+ ct_bkgsb = (
151
+ bkg_observation.countrate(area=area, binned=False) \
152
+ + instrument.emissions_ct[q.filter] * u.ct / u.s
153
+ ) / gain
154
+ mag_bkgsb = zp + u.Magnitude(ct_bkgsb)
155
+ else:
156
+ ct_bkgsb = 0. * u.ct / u.s
157
+ mag_bkgsb = 100. * u.mag
158
+ if mag_bkgsb.value < -100. :
159
+ mag_bkgsb = u.Quantity('100 mag')
160
+ if mag_bkgsb.value > 100.:
161
+ mag_bkgsb = u.Quantity('100 mag')
162
+
163
+ # Compute number of background electrons per pixel per second
164
+ # We explicitely assume that counts are per arcsec2
165
+ bkg_rate = (ct_bkgsb * gain * (
166
+ detector.scale[0].to(u.arcsec / u.pix)
167
+ * detector.scale[1].to(u.arcsec / u.pix)
168
+ )).value
169
+ else:
170
+ # Counts directly provided by user
171
+ bkg_rate = sky_photon_rate
172
+
173
+ # Instantiate image model
174
+ img = Image(
175
+ source=q.source,
176
+ psf_fwhm=q.seeing * u.arcsec,
177
+ psf_beta=3.2,
178
+ sersic_radius=q.sersic_radius,
179
+ sersic_index=q.sersic_index,
180
+ pixel=detector.scale,
181
+ rate=photon_rate,
182
+ bkg_rate=bkg_rate,
183
+ # Use RON 'counts' instead of electrons for compatibility with synphot
184
+ ron=detector.ron.to_value('electron'),
185
+ gain=gain,
186
+ photometry=q.photometry,
187
+ aperture=q.aperture
188
+ )
189
+
190
+ sexposures = sqrt(q.exposures * 2. / pi) if \
191
+ q.stacking == 'median' and q.exposures > 2 else sqrt(q.exposures)
192
+
193
+ if q.compute == 'etime':
194
+ snr = q.snr / sexposures
195
+ # Compute exposure time (solution to a second degree equation) in s
196
+ etime = img.etime(snr)
197
+ else:
198
+ etime = q.etime
199
+ snr = img.snr(etime)
200
+
201
+ if ui:
202
+ full_wave, full_response = spectral_to_arrays(full_spec)
203
+ atmosphere_wave, atmosphere_response = spectral_to_arrays(atmosphere_spec)
204
+
205
+ return ETCResponseModel(
206
+ instrument = instrument.name,
207
+ filter = instrument_transmission.name,
208
+ compute = q.compute,
209
+ zp = zp.value,
210
+ snr = snr * sexposures,
211
+ etime = etime,
212
+ etime_skysat = img.etime_bkg_sat(),
213
+ etime_sourcesat = img.etime_source_sat(),
214
+ ttime = q.exposures * (etime + instrument.overhead.to_value(u.s)),
215
+ sky_mag = mag_bkgsb.value,
216
+ lambda_pivot = lambda_pivot.to_value(u.nm),
217
+ bandwidth_rect = dlambda_rect.to_value(u.nm),
218
+ cutout = img.gif(etime, exposures=q.exposures) if ui else None,
219
+ filter_transmission = TransmissionModel(
220
+ id = instrument_transmission.id,
221
+ name = instrument_transmission.name,
222
+ wave_range = instrument.wavelength_range,
223
+ wave = full_wave,
224
+ response = full_response
225
+ ).model_dump_json(
226
+ exclude={'spectral'}
227
+ ) if ui else None,
228
+ atmosphere_transmission = TransmissionModel(
229
+ id = 'atmosphere',
230
+ name = "Atmosphere",
231
+ wave_range = instrument.wavelength_range,
232
+ wave = atmosphere_wave,
233
+ response = atmosphere_response
234
+ ).model_dump_json() if ui else None
235
+ )
236
+
237
+
@@ -0,0 +1,8 @@
1
+ """
2
+ Data types
3
+ """
4
+ # Copyright CFHT/CNRS/CEA/UParisSaclay
5
+ # Licensed under the MIT licence
6
+ from .quantity import AnnotatedQuantity, str_to_quantity_array
7
+ from .string import AnnotatedStr
8
+