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,34 @@
1
+ """
2
+ Data models
3
+ """
4
+ # Copyright CFHT/CNRS/CEA/UParisSaclay
5
+ # Licensed under the MIT licence
6
+
7
+ from .dataconfig import (
8
+ DataConfigModel,
9
+ DetectorConfigModel,
10
+ EmissionConfigModel,
11
+ FileConfigModel,
12
+ FiltersConfigModel,
13
+ InstrumentConfigModel,
14
+ OpticsConfigModel,
15
+ SiteConfigModel,
16
+ TelescopeConfigModel,
17
+ TransmissionConfigModel
18
+ )
19
+ from .exceptions import ETCValidationError
20
+ from .query import ETCQueryModel
21
+ from .response import ETCResponseModel
22
+ from .instrument import (
23
+ DetectorModel,
24
+ FiltersModel,
25
+ InstrumentModel,
26
+ OpticsModel,
27
+ SBSEDModel,
28
+ SEDModel,
29
+ SiteModel,
30
+ TelescopeModel,
31
+ TransmissionModel,
32
+ spectral_to_arrays
33
+ )
34
+
@@ -0,0 +1,195 @@
1
+ """
2
+ Data models
3
+ """
4
+ # Copyright CFHT/CNRS/CEA/UParisSaclay
5
+ # Licensed under the MIT licence
6
+
7
+ from astropy import units as u #type: ignore[import-untyped]
8
+ from pydantic import BaseModel, DirectoryPath
9
+
10
+ from ..types import AnnotatedQuantity
11
+
12
+
13
+ class FileConfigModel(BaseModel):
14
+ '''
15
+ Pydantic model for transmission curve configuration.
16
+ '''
17
+ default: bool = False
18
+ id: str = ''
19
+ name: str = ""
20
+ description: str = ""
21
+ vars: dict[str, float|str] = {}
22
+ file: str
23
+
24
+
25
+
26
+ class EmissionConfigModel(BaseModel):
27
+ '''
28
+ Pydantic model for emission curve set configuration.
29
+ '''
30
+ path: str = ""
31
+ temperatures: list[AnnotatedQuantity( #type: ignore[valid-type]
32
+ unit = "K",
33
+ gt = 0. * u.K,
34
+ decimals = 2,
35
+ description = "Device temperature."
36
+ )] = [283 * u.K]
37
+ areas: list[AnnotatedQuantity( #type: ignore[valid-type]
38
+ unit = "m2",
39
+ ge = 0. * u.m**2,
40
+ decimals = 3,
41
+ description = "Emissive area."
42
+ )] = [0. * u.m**2]
43
+ files: list[FileConfigModel] = []
44
+
45
+
46
+
47
+ class TransmissionConfigModel(BaseModel):
48
+ '''
49
+ Pydantic model for transmission curve set configuration.
50
+ '''
51
+ path: str = ""
52
+ files: list[FileConfigModel] = []
53
+
54
+
55
+
56
+ class SiteConfigModel(BaseModel):
57
+ '''
58
+ Pydantic model for the observing site data configuration model.
59
+ '''
60
+ default: bool = False
61
+ id: str
62
+ name: str
63
+ description: str
64
+ path: str
65
+ altitude: AnnotatedQuantity( #type: ignore[valid-type]
66
+ unit = "m",
67
+ ge = -1000. * u.m,
68
+ decimals = 3,
69
+ description = "Altitude of the observation site."
70
+ )
71
+ transmission: TransmissionConfigModel
72
+ emission: EmissionConfigModel
73
+
74
+
75
+
76
+ class TelescopeConfigModel(BaseModel):
77
+ '''
78
+ Pydantic model for the telescope data configuration model.
79
+ '''
80
+ default: bool = False
81
+ id: str
82
+ name: str
83
+ description: str
84
+ path: str
85
+ collecting_area: AnnotatedQuantity( #type: ignore[valid-type]
86
+ unit = "m2",
87
+ gt = 0. * u.m**2,
88
+ decimals = 3,
89
+ description = "Full collecting area, including possible mirror hole and spider branches."
90
+ )
91
+ obstruction_area: AnnotatedQuantity( #type: ignore[valid-type]
92
+ unit = "m2",
93
+ gt = 0. * u.m**2,
94
+ decimals = 3,
95
+ description = "Default obstruction area (only used if not specified for the instrument)."
96
+ )
97
+ transmission: TransmissionConfigModel
98
+ emission: EmissionConfigModel
99
+
100
+
101
+
102
+ class DetectorConfigModel(BaseModel):
103
+ '''
104
+ Pydantic model for the detector data configuration model.
105
+ '''
106
+ path: str
107
+ gain: AnnotatedQuantity( #type: ignore[valid-type]
108
+ unit = "electron/adu",
109
+ gt = 0. * u.electron / u.adu,
110
+ decimals = 3,
111
+ description = "Average gain (conversion factor) in electrons per ADU."
112
+ )
113
+ ron: AnnotatedQuantity( #type: ignore[valid-type]
114
+ unit = "electron",
115
+ ge = 0. * u.electron,
116
+ decimals = 3,
117
+ description = "Total readout noise in electrons."
118
+ )
119
+ scale: AnnotatedQuantity( #type: ignore[valid-type]
120
+ unit = "arcsec/pix",
121
+ gt = [0., 0.] * u.arcsec / u.pix,
122
+ min_shape = (2),
123
+ max_shape = (2),
124
+ decimals = 4,
125
+ description = "Angular pixel scale along each axis."
126
+ )
127
+ transmission: TransmissionConfigModel
128
+ emission: EmissionConfigModel
129
+
130
+
131
+
132
+ class OpticsConfigModel(BaseModel):
133
+ '''
134
+ Pydantic model for the intrument optics data configuration model.
135
+ '''
136
+ path: str
137
+ transmission: TransmissionConfigModel
138
+ emission: EmissionConfigModel
139
+
140
+
141
+
142
+ class FiltersConfigModel(OpticsConfigModel):
143
+ '''
144
+ Pydantic model for the intrument filters data configuration model.
145
+ '''
146
+ pass
147
+
148
+
149
+
150
+ class InstrumentConfigModel(BaseModel):
151
+ '''
152
+ Pydantic model for the instrument data configuration model.
153
+ '''
154
+ default: bool = False
155
+ id: str
156
+ name: str
157
+ description: str
158
+ path: str
159
+ wavelength_range: AnnotatedQuantity( #type: ignore[valid-type]
160
+ unit = "nm",
161
+ gt = [0., 0.] *u. nm,
162
+ min_shape = (2),
163
+ max_shape = (2),
164
+ decimals = 4,
165
+ description = "Instrument minimum and maximum wavelengths."
166
+ )
167
+ obstruction_area: AnnotatedQuantity( #type: ignore[valid-type]
168
+ unit = "m2",
169
+ gt = 0. * u.m**2,
170
+ decimals = 3,
171
+ description = "Obstruction area for this particular instrument."
172
+ )
173
+ overhead: AnnotatedQuantity( #type: ignore[valid-type]
174
+ unit = "s",
175
+ ge = 0. * u.second,
176
+ decimals = 3,
177
+ description = "Total instrument time overhead between exposures."
178
+ )
179
+ site_id: str
180
+ telescope_id: str
181
+ optics: OpticsConfigModel
182
+ filters: FiltersConfigModel
183
+ detector: DetectorConfigModel
184
+
185
+
186
+
187
+ class DataConfigModel(BaseModel):
188
+ '''
189
+ Pydantic model for the data configuration
190
+ '''
191
+ path: DirectoryPath
192
+ sites: list['SiteConfigModel']
193
+ telescopes: list['TelescopeConfigModel']
194
+ instruments: list['InstrumentConfigModel']
195
+
@@ -0,0 +1,9 @@
1
+ """
2
+ Default entries for models
3
+ """
4
+ # Copyright CFHT/CNRS/CEA/UParisSaclay
5
+ # Licensed under the MIT licence
6
+
7
+ # We actually gather default entities from the data module
8
+ from ..data import default_filter, default_instrument, filters, instruments
9
+
@@ -0,0 +1,9 @@
1
+ """
2
+ Exceptions for model validation.
3
+ """
4
+ # Copyright CFHT/CNRS/CEA/UParisSaclay
5
+ # Licensed under the MIT licence
6
+
7
+ class ETCValidationError(Exception):
8
+ pass
9
+
@@ -0,0 +1,314 @@
1
+ """
2
+ Data models
3
+ """
4
+ # Copyright CFHT/CNRS/CEA/UParisSaclay
5
+ # Licensed under the MIT licence
6
+
7
+ from typing import Annotated, Optional, Tuple
8
+
9
+ from astropy import units as u #type: ignore[import-untyped]
10
+ import numpy as np
11
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
12
+ from synphot import ( #type: ignore[import-untyped]
13
+ BlackBody1D,
14
+ ConstFlux1D,
15
+ Observation,
16
+ SourceSpectrum,
17
+ SpectralElement
18
+ )
19
+ from synphot.spectrum import BaseSpectrum #type: ignore[import-untyped]
20
+
21
+ from ... import package
22
+ from ..types import AnnotatedQuantity
23
+
24
+
25
+ def spectral_to_arrays(spectral: BaseSpectrum) -> Tuple[np.ndarray, np.ndarray]:
26
+ w = spectral.waveset
27
+ x = spectral(w)
28
+
29
+ # Trim extra 0 values at beginning and at the end
30
+ idx = np.where(x.value != 0.)[0]
31
+ if len(idx):
32
+ start = idx[0] - 1 if idx[0] > 0 else 0
33
+ end = idx[-1] + 2 if idx[-1] < w.size - 1 else w.size
34
+ w = w[start:end]
35
+ x = x[start:end]
36
+
37
+ return w, x
38
+
39
+
40
+
41
+ class DetectorModel(BaseModel):
42
+ '''
43
+ Pydantic model for an instrument detector (e.g., CMOS or CCD).
44
+ '''
45
+ gain: AnnotatedQuantity( #type: ignore[valid-type]
46
+ unit = "electron / adu",
47
+ gt = 0. * u.electron / u.adu,
48
+ decimals = 4,
49
+ description = "Detector conversion factor."
50
+ )
51
+ ron: AnnotatedQuantity( #type: ignore[valid-type]
52
+ unit = "electron",
53
+ ge = 0. * u.electron,
54
+ decimals = 4,
55
+ description = "Read-out noise RMS amplitude."
56
+ )
57
+ scale: AnnotatedQuantity( #type: ignore[valid-type]
58
+ unit = "arcsec/pix",
59
+ gt = [0., 0.] * u.arcsec / u.pix,
60
+ min_shape = (2),
61
+ max_shape = (2),
62
+ decimals = 4,
63
+ description = "Angular pixel scale along each axis."
64
+ )
65
+ transmissions: dict[str, 'TransmissionModel']
66
+ emissions: dict[str, 'SBSEDModel']
67
+
68
+
69
+
70
+ class InstrumentModel(BaseModel):
71
+ '''
72
+ Pydantic model for a PyDIET instrument.
73
+ '''
74
+ id: str
75
+ name: str
76
+ description: str
77
+ wavelength_range: AnnotatedQuantity( #type: ignore[valid-type]
78
+ unit = "nm",
79
+ gt = [0., 0.] * u.nm,
80
+ min_shape = (2),
81
+ max_shape = (2),
82
+ decimals = 4,
83
+ description = "Instrument minimum and maximum wavelengths."
84
+ )
85
+ obstruction_area: AnnotatedQuantity( #type: ignore[valid-type]
86
+ unit = "m2",
87
+ gt = 0. * u.m**2,
88
+ decimals = 3,
89
+ description = "Default obstruction area (only used if not specified for the instrument)."
90
+ )
91
+ overhead: AnnotatedQuantity( #type: ignore[valid-type]
92
+ unit = "s",
93
+ ge = 0. * u.second,
94
+ decimals = 3,
95
+ description = "Total instrument time overhead between exposures."
96
+ )
97
+ filters: 'FiltersModel'
98
+ optics: 'OpticsModel'
99
+ detector: 'DetectorModel'
100
+ telescope: 'TelescopeModel'
101
+ site: 'SiteModel'
102
+ default: bool = False
103
+
104
+ transmissions: Optional[dict] = Field(default=None) #type: ignore[annotation-unchecked]
105
+ emissions_ct: Optional[dict] = Field(default=None) #type: ignore[annotation-unchecked]
106
+
107
+ @model_validator(mode="after")
108
+ def _update_transmissions(self):
109
+ # Compute extra parameters during initialization
110
+ area = self.telescope.collecting_area - self.obstruction_area
111
+ # Filter emissions and transmissions
112
+ upstream_transmission = 1.
113
+ upstream_emission = SourceSpectrum(ConstFlux1D, amplitude=0.)
114
+ # Pre-filter list of transmissions
115
+ transmissions = [
116
+ *self.telescope.transmissions.values(),
117
+ *self.optics.transmissions.values()
118
+ ]
119
+ emissions = [
120
+ *self.telescope.emissions.values(),
121
+ *self.optics.emissions.values()
122
+ ]
123
+ for i, v in enumerate(transmissions):
124
+ emission = emissions[i].spectral
125
+ transmission = transmissions[i].spectral
126
+ upstream_transmission *= transmission
127
+ upstream_emission = upstream_emission * transmission + emission
128
+ self.transmissions : dict[str, TransmissionModel] = {} #type: ignore[annotation-unchecked]
129
+ self.emissions_ct : dict[str, u.Quantity[u.ct/u.s]] = {} #type: ignore[annotation-unchecked]
130
+ filter_transmissions = self.filters.transmissions
131
+ filter_emissions = self.filters.emissions
132
+ for f in filter_transmissions:
133
+ filter = filter_transmissions[f]
134
+ filter_transmission = filter.spectral
135
+ filter_emission = filter_emissions[f].spectral
136
+ transmission = upstream_transmission * filter_transmission
137
+ emission = upstream_emission * transmission + filter_emission
138
+ transmission *= self.detector.transmissions["0"].spectral
139
+ emission *= self.detector.transmissions["0"].spectral
140
+ wave, response = spectral_to_arrays(transmission)
141
+ self.transmissions[f] = TransmissionModel(
142
+ id = filter.id,
143
+ name = filter.name,
144
+ description = filter.description,
145
+ vars = filter.vars,
146
+ wave = wave,
147
+ response = response,
148
+ spectral = transmission
149
+ )
150
+ # Compute countrate
151
+ if transmission.tpeak() > 0.:
152
+ observation = Observation(emission, transmission, force='taper')
153
+ self.emissions_ct[f] = observation.countrate(area=area).value
154
+ else:
155
+ self.emissions_ct[f] = 0.
156
+ return self
157
+
158
+ model_config = ConfigDict(arbitrary_types_allowed=True)
159
+
160
+
161
+
162
+ class OpticsModel(BaseModel):
163
+ '''
164
+ Pydantic model for optics.
165
+ '''
166
+ transmissions: dict[str, 'TransmissionModel']
167
+ emissions: dict[str, 'SBSEDModel']
168
+
169
+
170
+
171
+ class FiltersModel(OpticsModel):
172
+ '''
173
+ Pydantic model for a filter set.
174
+ '''
175
+ pass
176
+
177
+
178
+
179
+ class SBSEDModel(BaseModel):
180
+ '''
181
+ Pydantic model for a Surface Brightness Spectral Energy Distribution (SBSED).
182
+ '''
183
+ id: str
184
+ name: str
185
+ description: str
186
+ vars: dict[str, float|str] = {}
187
+ wave: AnnotatedQuantity( #type: ignore[valid-type]
188
+ unit = "nm",
189
+ ge = 100. * u.nm,
190
+ le = 100. * u.micron,
191
+ min_shape = (2),
192
+ max_shape = (100000),
193
+ decimals = 4
194
+ ) | None = None
195
+ sbsed: AnnotatedQuantity( #type: ignore[valid-type]
196
+ unit = "Jy / arcsec2",
197
+ ge = 0. * u.Jy / u.arcsec**2,
198
+ min_shape = (2),
199
+ max_shape = (100000),
200
+ decimals = 6
201
+ ) | None = None
202
+ spectral: SourceSpectrum = Field(exclude=True)
203
+ default: bool = False
204
+
205
+ model_config = ConfigDict(arbitrary_types_allowed=True)
206
+
207
+
208
+
209
+ class SEDModel(BaseModel):
210
+ '''
211
+ Pydantic model for a Spectral Energy Distribution (SED).
212
+ '''
213
+ id: str
214
+ name: str
215
+ description: str
216
+ vars: dict[str, float] = {}
217
+ wave: AnnotatedQuantity( #type: ignore[valid-type]
218
+ unit = "nm",
219
+ ge = 100. * u.nm,
220
+ le = 100. * u.micron,
221
+ min_shape = (2),
222
+ max_shape = (100000),
223
+ decimals = 4
224
+ ) | None = None
225
+ sed: AnnotatedQuantity( #type: ignore[valid-type]
226
+ unit = "Jy",
227
+ ge = 0. * u.Jy,
228
+ min_shape = (2),
229
+ max_shape = (100000),
230
+ decimals = 6
231
+ ) | None = None
232
+ spectral: SourceSpectrum = Field(exclude=True)
233
+ default: bool = False
234
+
235
+ model_config = ConfigDict(arbitrary_types_allowed=True)
236
+
237
+
238
+
239
+ class SiteModel(BaseModel):
240
+ '''
241
+ Pydantic model for an observing site.
242
+ '''
243
+ id: str
244
+ name: str
245
+ description: str
246
+ sky_transmissions: dict[str, 'TransmissionModel']
247
+ sky_emissions: dict[str, 'SBSEDModel']
248
+ default: bool = False
249
+
250
+
251
+
252
+ class TelescopeModel(BaseModel):
253
+ '''
254
+ Pydantic model for a telescope.
255
+ '''
256
+ id: str
257
+ name: str
258
+ description: str
259
+ collecting_area: AnnotatedQuantity( #type: ignore[valid-type]
260
+ unit = "m**2",
261
+ gt = 0. * u.m**2,
262
+ decimals = 4,
263
+ description = "Full collecting area, ignoring obstructions."
264
+ )
265
+ obstruction_area: AnnotatedQuantity( #type: ignore[valid-type]
266
+ unit = "m**2",
267
+ gt = 0. * u.m**2,
268
+ decimals = 4,
269
+ description = "Minimum obstruction area."
270
+ )
271
+ transmissions: dict[str, 'TransmissionModel']
272
+ emissions: dict[str, 'SBSEDModel']
273
+ default: bool = False
274
+
275
+
276
+
277
+ class TransmissionModel(BaseModel):
278
+ '''
279
+ Pydantic model for a transmission curve (with wavelength).
280
+ '''
281
+ id: str
282
+ name: str
283
+ description: str = ""
284
+ vars: Optional[dict[str, float | str]] = None
285
+ wave_range: AnnotatedQuantity( #type: ignore[valid-type]
286
+ unit = "nm",
287
+ gt = [0., 0.] * u.nm,
288
+ min_shape = (2),
289
+ max_shape = (2),
290
+ decimals = 4,
291
+ description = "Instrument minimum and maximum wavelengths."
292
+ ) | None = None
293
+ wave: AnnotatedQuantity( #type: ignore[valid-type]
294
+ unit = "nm",
295
+ ge = 100. * u.nm,
296
+ le = 100. * u.micron,
297
+ min_shape = (2),
298
+ max_shape = (100000),
299
+ decimals = 3
300
+ ) | None = None
301
+ response: AnnotatedQuantity( #type: ignore[valid-type]
302
+ unit = "",
303
+ ge = -100.,
304
+ le = 100.,
305
+ min_shape = (2),
306
+ max_shape = (100000),
307
+ decimals = 4
308
+ ) | None = None
309
+ spectral: Optional[SpectralElement] = Field(default=None, exclude=True)
310
+ default: bool = False
311
+
312
+ model_config = ConfigDict(arbitrary_types_allowed=True)
313
+
314
+