pyreduce-astro 0.7a4__cp314-cp314-win_amd64.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 (182) hide show
  1. pyreduce/__init__.py +67 -0
  2. pyreduce/__main__.py +322 -0
  3. pyreduce/cli.py +342 -0
  4. pyreduce/clib/Release/_slitfunc_2d.cp311-win_amd64.exp +0 -0
  5. pyreduce/clib/Release/_slitfunc_2d.cp311-win_amd64.lib +0 -0
  6. pyreduce/clib/Release/_slitfunc_2d.cp312-win_amd64.exp +0 -0
  7. pyreduce/clib/Release/_slitfunc_2d.cp312-win_amd64.lib +0 -0
  8. pyreduce/clib/Release/_slitfunc_2d.cp313-win_amd64.exp +0 -0
  9. pyreduce/clib/Release/_slitfunc_2d.cp313-win_amd64.lib +0 -0
  10. pyreduce/clib/Release/_slitfunc_2d.cp314-win_amd64.exp +0 -0
  11. pyreduce/clib/Release/_slitfunc_2d.cp314-win_amd64.lib +0 -0
  12. pyreduce/clib/Release/_slitfunc_2d.obj +0 -0
  13. pyreduce/clib/Release/_slitfunc_bd.cp311-win_amd64.exp +0 -0
  14. pyreduce/clib/Release/_slitfunc_bd.cp311-win_amd64.lib +0 -0
  15. pyreduce/clib/Release/_slitfunc_bd.cp312-win_amd64.exp +0 -0
  16. pyreduce/clib/Release/_slitfunc_bd.cp312-win_amd64.lib +0 -0
  17. pyreduce/clib/Release/_slitfunc_bd.cp313-win_amd64.exp +0 -0
  18. pyreduce/clib/Release/_slitfunc_bd.cp313-win_amd64.lib +0 -0
  19. pyreduce/clib/Release/_slitfunc_bd.cp314-win_amd64.exp +0 -0
  20. pyreduce/clib/Release/_slitfunc_bd.cp314-win_amd64.lib +0 -0
  21. pyreduce/clib/Release/_slitfunc_bd.obj +0 -0
  22. pyreduce/clib/__init__.py +0 -0
  23. pyreduce/clib/_slitfunc_2d.cp311-win_amd64.pyd +0 -0
  24. pyreduce/clib/_slitfunc_2d.cp312-win_amd64.pyd +0 -0
  25. pyreduce/clib/_slitfunc_2d.cp313-win_amd64.pyd +0 -0
  26. pyreduce/clib/_slitfunc_2d.cp314-win_amd64.pyd +0 -0
  27. pyreduce/clib/_slitfunc_bd.cp311-win_amd64.pyd +0 -0
  28. pyreduce/clib/_slitfunc_bd.cp312-win_amd64.pyd +0 -0
  29. pyreduce/clib/_slitfunc_bd.cp313-win_amd64.pyd +0 -0
  30. pyreduce/clib/_slitfunc_bd.cp314-win_amd64.pyd +0 -0
  31. pyreduce/clib/build_extract.py +75 -0
  32. pyreduce/clib/slit_func_2d_xi_zeta_bd.c +1313 -0
  33. pyreduce/clib/slit_func_2d_xi_zeta_bd.h +55 -0
  34. pyreduce/clib/slit_func_bd.c +362 -0
  35. pyreduce/clib/slit_func_bd.h +17 -0
  36. pyreduce/clipnflip.py +147 -0
  37. pyreduce/combine_frames.py +861 -0
  38. pyreduce/configuration.py +191 -0
  39. pyreduce/continuum_normalization.py +329 -0
  40. pyreduce/cwrappers.py +404 -0
  41. pyreduce/datasets.py +238 -0
  42. pyreduce/echelle.py +413 -0
  43. pyreduce/estimate_background_scatter.py +130 -0
  44. pyreduce/extract.py +1362 -0
  45. pyreduce/extraction_width.py +77 -0
  46. pyreduce/instruments/__init__.py +0 -0
  47. pyreduce/instruments/aj.py +9 -0
  48. pyreduce/instruments/aj.yaml +51 -0
  49. pyreduce/instruments/andes.py +102 -0
  50. pyreduce/instruments/andes.yaml +72 -0
  51. pyreduce/instruments/common.py +711 -0
  52. pyreduce/instruments/common.yaml +57 -0
  53. pyreduce/instruments/crires_plus.py +103 -0
  54. pyreduce/instruments/crires_plus.yaml +101 -0
  55. pyreduce/instruments/filters.py +195 -0
  56. pyreduce/instruments/harpn.py +203 -0
  57. pyreduce/instruments/harpn.yaml +140 -0
  58. pyreduce/instruments/harps.py +312 -0
  59. pyreduce/instruments/harps.yaml +144 -0
  60. pyreduce/instruments/instrument_info.py +140 -0
  61. pyreduce/instruments/jwst_miri.py +29 -0
  62. pyreduce/instruments/jwst_miri.yaml +53 -0
  63. pyreduce/instruments/jwst_niriss.py +98 -0
  64. pyreduce/instruments/jwst_niriss.yaml +60 -0
  65. pyreduce/instruments/lick_apf.py +35 -0
  66. pyreduce/instruments/lick_apf.yaml +60 -0
  67. pyreduce/instruments/mcdonald.py +123 -0
  68. pyreduce/instruments/mcdonald.yaml +56 -0
  69. pyreduce/instruments/metis_ifu.py +45 -0
  70. pyreduce/instruments/metis_ifu.yaml +62 -0
  71. pyreduce/instruments/metis_lss.py +45 -0
  72. pyreduce/instruments/metis_lss.yaml +62 -0
  73. pyreduce/instruments/micado.py +45 -0
  74. pyreduce/instruments/micado.yaml +62 -0
  75. pyreduce/instruments/models.py +257 -0
  76. pyreduce/instruments/neid.py +156 -0
  77. pyreduce/instruments/neid.yaml +61 -0
  78. pyreduce/instruments/nirspec.py +215 -0
  79. pyreduce/instruments/nirspec.yaml +63 -0
  80. pyreduce/instruments/nte.py +42 -0
  81. pyreduce/instruments/nte.yaml +55 -0
  82. pyreduce/instruments/uves.py +46 -0
  83. pyreduce/instruments/uves.yaml +65 -0
  84. pyreduce/instruments/xshooter.py +39 -0
  85. pyreduce/instruments/xshooter.yaml +63 -0
  86. pyreduce/make_shear.py +607 -0
  87. pyreduce/masks/mask_crires_plus_det1.fits.gz +0 -0
  88. pyreduce/masks/mask_crires_plus_det2.fits.gz +0 -0
  89. pyreduce/masks/mask_crires_plus_det3.fits.gz +0 -0
  90. pyreduce/masks/mask_ctio_chiron.fits.gz +0 -0
  91. pyreduce/masks/mask_elodie.fits.gz +0 -0
  92. pyreduce/masks/mask_feros3.fits.gz +0 -0
  93. pyreduce/masks/mask_flames_giraffe.fits.gz +0 -0
  94. pyreduce/masks/mask_harps_blue.fits.gz +0 -0
  95. pyreduce/masks/mask_harps_red.fits.gz +0 -0
  96. pyreduce/masks/mask_hds_blue.fits.gz +0 -0
  97. pyreduce/masks/mask_hds_red.fits.gz +0 -0
  98. pyreduce/masks/mask_het_hrs_2x5.fits.gz +0 -0
  99. pyreduce/masks/mask_jwst_miri_lrs_slitless.fits.gz +0 -0
  100. pyreduce/masks/mask_jwst_niriss_gr700xd.fits.gz +0 -0
  101. pyreduce/masks/mask_lick_apf_.fits.gz +0 -0
  102. pyreduce/masks/mask_mcdonald.fits.gz +0 -0
  103. pyreduce/masks/mask_nes.fits.gz +0 -0
  104. pyreduce/masks/mask_nirspec_nirspec.fits.gz +0 -0
  105. pyreduce/masks/mask_sarg.fits.gz +0 -0
  106. pyreduce/masks/mask_sarg_2x2a.fits.gz +0 -0
  107. pyreduce/masks/mask_sarg_2x2b.fits.gz +0 -0
  108. pyreduce/masks/mask_subaru_hds_red.fits.gz +0 -0
  109. pyreduce/masks/mask_uves_blue.fits.gz +0 -0
  110. pyreduce/masks/mask_uves_blue_binned_2_2.fits.gz +0 -0
  111. pyreduce/masks/mask_uves_middle.fits.gz +0 -0
  112. pyreduce/masks/mask_uves_middle_2x2_split.fits.gz +0 -0
  113. pyreduce/masks/mask_uves_middle_binned_2_2.fits.gz +0 -0
  114. pyreduce/masks/mask_uves_red.fits.gz +0 -0
  115. pyreduce/masks/mask_uves_red_2x2.fits.gz +0 -0
  116. pyreduce/masks/mask_uves_red_2x2_split.fits.gz +0 -0
  117. pyreduce/masks/mask_uves_red_binned_2_2.fits.gz +0 -0
  118. pyreduce/masks/mask_xshooter_nir.fits.gz +0 -0
  119. pyreduce/pipeline.py +619 -0
  120. pyreduce/rectify.py +138 -0
  121. pyreduce/reduce.py +2065 -0
  122. pyreduce/settings/settings_AJ.json +19 -0
  123. pyreduce/settings/settings_ANDES.json +89 -0
  124. pyreduce/settings/settings_CRIRES_PLUS.json +89 -0
  125. pyreduce/settings/settings_HARPN.json +73 -0
  126. pyreduce/settings/settings_HARPS.json +69 -0
  127. pyreduce/settings/settings_JWST_MIRI.json +55 -0
  128. pyreduce/settings/settings_JWST_NIRISS.json +55 -0
  129. pyreduce/settings/settings_LICK_APF.json +62 -0
  130. pyreduce/settings/settings_MCDONALD.json +58 -0
  131. pyreduce/settings/settings_METIS_IFU.json +77 -0
  132. pyreduce/settings/settings_METIS_LSS.json +77 -0
  133. pyreduce/settings/settings_MICADO.json +78 -0
  134. pyreduce/settings/settings_NEID.json +73 -0
  135. pyreduce/settings/settings_NIRSPEC.json +58 -0
  136. pyreduce/settings/settings_NTE.json +60 -0
  137. pyreduce/settings/settings_UVES.json +54 -0
  138. pyreduce/settings/settings_XSHOOTER.json +78 -0
  139. pyreduce/settings/settings_pyreduce.json +184 -0
  140. pyreduce/settings/settings_schema.json +850 -0
  141. pyreduce/tools/__init__.py +0 -0
  142. pyreduce/tools/combine.py +117 -0
  143. pyreduce/trace.py +979 -0
  144. pyreduce/util.py +1366 -0
  145. pyreduce/wavecal/MICADO_HK_3arcsec_chip5.npz +0 -0
  146. pyreduce/wavecal/atlas/thar.fits +4946 -13
  147. pyreduce/wavecal/atlas/thar_list.txt +4172 -0
  148. pyreduce/wavecal/atlas/une.fits +0 -0
  149. pyreduce/wavecal/convert.py +38 -0
  150. pyreduce/wavecal/crires_plus_J1228_Open_det1.npz +0 -0
  151. pyreduce/wavecal/crires_plus_J1228_Open_det2.npz +0 -0
  152. pyreduce/wavecal/crires_plus_J1228_Open_det3.npz +0 -0
  153. pyreduce/wavecal/harpn_harpn_2D.npz +0 -0
  154. pyreduce/wavecal/harps_blue_2D.npz +0 -0
  155. pyreduce/wavecal/harps_blue_pol_2D.npz +0 -0
  156. pyreduce/wavecal/harps_red_2D.npz +0 -0
  157. pyreduce/wavecal/harps_red_pol_2D.npz +0 -0
  158. pyreduce/wavecal/mcdonald.npz +0 -0
  159. pyreduce/wavecal/metis_lss_l_2D.npz +0 -0
  160. pyreduce/wavecal/metis_lss_m_2D.npz +0 -0
  161. pyreduce/wavecal/nirspec_K2.npz +0 -0
  162. pyreduce/wavecal/uves_blue_360nm_2D.npz +0 -0
  163. pyreduce/wavecal/uves_blue_390nm_2D.npz +0 -0
  164. pyreduce/wavecal/uves_blue_437nm_2D.npz +0 -0
  165. pyreduce/wavecal/uves_middle_2x2_2D.npz +0 -0
  166. pyreduce/wavecal/uves_middle_565nm_2D.npz +0 -0
  167. pyreduce/wavecal/uves_middle_580nm_2D.npz +0 -0
  168. pyreduce/wavecal/uves_middle_600nm_2D.npz +0 -0
  169. pyreduce/wavecal/uves_middle_665nm_2D.npz +0 -0
  170. pyreduce/wavecal/uves_middle_860nm_2D.npz +0 -0
  171. pyreduce/wavecal/uves_red_580nm_2D.npz +0 -0
  172. pyreduce/wavecal/uves_red_600nm_2D.npz +0 -0
  173. pyreduce/wavecal/uves_red_665nm_2D.npz +0 -0
  174. pyreduce/wavecal/uves_red_760nm_2D.npz +0 -0
  175. pyreduce/wavecal/uves_red_860nm_2D.npz +0 -0
  176. pyreduce/wavecal/xshooter_nir.npz +0 -0
  177. pyreduce/wavelength_calibration.py +1871 -0
  178. pyreduce_astro-0.7a4.dist-info/METADATA +106 -0
  179. pyreduce_astro-0.7a4.dist-info/RECORD +182 -0
  180. pyreduce_astro-0.7a4.dist-info/WHEEL +4 -0
  181. pyreduce_astro-0.7a4.dist-info/entry_points.txt +2 -0
  182. pyreduce_astro-0.7a4.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,1313 @@
1
+ #include <stdio.h>
2
+ #include <string.h>
3
+ #include <stdlib.h>
4
+ #include <math.h>
5
+ #include "slit_func_2d_xi_zeta_bd.h"
6
+
7
+ #define min(a, b) (((a) < (b)) ? (a) : (b))
8
+ #define max(a, b) (((a) > (b)) ? (a) : (b))
9
+ #define signum(a) (((a) > 0) ? 1 : ((a) < 0) ? -1 : 0)
10
+ #define DEBUG 0
11
+
12
+ // Store important sizes in global variables to make access easier
13
+ // When calculating the proper indices
14
+ // When not checking the indices just the variables directly
15
+ #if DEBUG
16
+ int _ncols = 0;
17
+ int _nrows = 0;
18
+ int _ny = 0;
19
+ int _nx = 0;
20
+ int _osample = 0;
21
+ int _n = 0;
22
+ int _nd = 0;
23
+ #else
24
+ #define _ncols ncols
25
+ #define _nrows nrows
26
+ #define _ny ny
27
+ #define _nx nx
28
+ #define _osample osample
29
+ #define _n n
30
+ #define _nd nd
31
+ #endif
32
+
33
+ // Define the sizes of each array
34
+ #define MAX_ZETA_X (_ncols)
35
+ #define MAX_ZETA_Y (_nrows)
36
+ #define MAX_ZETA_Z (3 * ((_osample) + 1))
37
+ #define MAX_ZETA (MAX_ZETA_X * MAX_ZETA_Y * MAX_ZETA_Z)
38
+ #define MAX_MZETA ((_ncols) * (_nrows))
39
+ #define MAX_XI ((_ncols) * (_ny)*4)
40
+ #define MAX_PSF_X (_ncols)
41
+ #define MAX_PSF_Y (3)
42
+ #define MAX_PSF (MAX_PSF_X * MAX_PSF_Y)
43
+ #define MAX_A ((_n) * (_nd))
44
+ #define MAX_R (_n)
45
+ #define MAX_SP (_ncols)
46
+ #define MAX_SL (_ny)
47
+ #define MAX_LAIJ_X (_ny)
48
+ #define MAX_LAIJ_Y (4 * (_osample) + 1)
49
+ #define MAX_LAIJ (MAX_LAIJ_X * MAX_LAIJ_Y)
50
+ #define MAX_PAIJ_X (_ncols)
51
+ #define MAX_PAIJ_Y (_nx)
52
+ #define MAX_PAIJ (MAX_PAIJ_X * MAX_PAIJ_Y)
53
+ #define MAX_LBJ (_ny)
54
+ #define MAX_PBJ (_ncols)
55
+ #define MAX_IM ((_ncols) * (_nrows))
56
+
57
+ // If we want to check the index use functions to represent the index
58
+ // Otherwise a simpler define will do, which should be faster ?
59
+ #if DEBUG
60
+ static long zeta_index(long x, long y, long z)
61
+ {
62
+ long i = z + y * MAX_ZETA_Z + x * MAX_ZETA_Z * _nrows;
63
+ if ((i < 0) | (i >= MAX_ZETA))
64
+ {
65
+ printf("INDEX OUT OF BOUNDS. Zeta[%li, %li, %li]\n", x, y, z);
66
+ return 0;
67
+ }
68
+ return i;
69
+ }
70
+
71
+ static long mzeta_index(long x, long y)
72
+ {
73
+ long i = y + x * _nrows;
74
+ if ((i < 0) | (i >= MAX_MZETA))
75
+ {
76
+ printf("INDEX OUT OF BOUNDS. Mzeta[%li, %li]\n", x, y);
77
+ return 0;
78
+ }
79
+ return i;
80
+ }
81
+
82
+ static long xi_index(long x, long y, long z)
83
+ {
84
+ long i = z + 4 * y + _ny * 4 * x;
85
+ if ((i < 0) | (i >= MAX_XI))
86
+ {
87
+ printf("INDEX OUT OF BOUNDS. Xi[%li, %li, %li]\n", x, y, z);
88
+ return 0;
89
+ }
90
+ return i;
91
+ }
92
+
93
+ static long psf_index(long x, long y)
94
+ {
95
+ long i = ((x)*3 + (y));
96
+ if ((i < 0) | (i >= MAX_PSF))
97
+ {
98
+ printf("INDEX OUT OF BOUNDS. PSF[%li, %li]\n", x, y);
99
+ return 0;
100
+ }
101
+ return i;
102
+ }
103
+
104
+ static long a_index(long x, long y)
105
+ {
106
+ long i = _n * y + x;
107
+ if ((i < 0) | (i >= MAX_A))
108
+ {
109
+ printf("INDEX OUT OF BOUNDS. a[%li, %li]\n", x, y);
110
+ return 0;
111
+ }
112
+ return i;
113
+ }
114
+
115
+ static long r_index(long i)
116
+ {
117
+ if ((i < 0) | (i >= MAX_R))
118
+ {
119
+ printf("INDEX OUT OF BOUNDS. r[%li]\n", i);
120
+ return 0;
121
+ }
122
+ return i;
123
+ }
124
+
125
+ static long sp_index(long i)
126
+ {
127
+ if ((i < 0) | (i >= MAX_SP))
128
+ {
129
+ printf("INDEX OUT OF BOUNDS. sP[%li]\n", i);
130
+ return 0;
131
+ }
132
+ return i;
133
+ }
134
+
135
+ static long laij_index(long x, long y)
136
+ {
137
+ long i = ((y)*_ny) + (x);
138
+ if ((i < 0) | (i >= MAX_LAIJ))
139
+ {
140
+ printf("INDEX OUT OF BOUNDS. l_Aij[%li, %li]\n", x, y);
141
+ return 0;
142
+ }
143
+ return i;
144
+ }
145
+
146
+ static long paij_index(long x, long y)
147
+ {
148
+ long i = ((y)*_ncols) + (x);
149
+ if ((i < 0) | (i >= MAX_PAIJ))
150
+ {
151
+ printf("INDEX OUT OF BOUNDS. p_Aij[%li, %li]\n", x, y);
152
+ return 0;
153
+ }
154
+ return i;
155
+ }
156
+
157
+ static long lbj_index(long i)
158
+ {
159
+ if ((i < 0) | (i >= MAX_LBJ))
160
+ {
161
+ printf("INDEX OUT OF BOUNDS. l_bj[%li]\n", i);
162
+ return 0;
163
+ }
164
+ return i;
165
+ }
166
+
167
+ static long pbj_index(long i)
168
+ {
169
+ if ((i < 0) | (i >= MAX_PBJ))
170
+ {
171
+ printf("INDEX OUT OF BOUNDS. p_bj[%li]\n", i);
172
+ return 0;
173
+ }
174
+ return i;
175
+ }
176
+
177
+ static long im_index(long x, long y)
178
+ {
179
+ long i = ((y)*_ncols) + (x);
180
+ if ((i < 0) | (i >= MAX_IM))
181
+ {
182
+ printf("INDEX OUT OF BOUNDS. im[%li, %li]\n", x, y);
183
+ return 0;
184
+ }
185
+ return i;
186
+ }
187
+
188
+ static long sl_index(long i)
189
+ {
190
+ if ((i < 0) | (i >= MAX_SL))
191
+ {
192
+ printf("INDEX OUT OF BOUNDS. sL[%li]\n", i);
193
+ return 0;
194
+ }
195
+ return i;
196
+ }
197
+ #else
198
+ #define zeta_index(x, y, z) ((z) + (y)*MAX_ZETA_Z + (x)*MAX_ZETA_Z * _nrows)
199
+ #define mzeta_index(x, y) ((y) + (x)*_nrows)
200
+ #define xi_index(x, y, z) ((z) + 4 * (y) + _ny * 4 * (x))
201
+ #define psf_index(x, y) ((x)*3 + (y))
202
+ #define a_index(x, y) ((y)*n + (x))
203
+ #define r_index(i) (i)
204
+ #define sp_index(i) (i)
205
+ #define laij_index(x, y) ((y)*ny) + (x)
206
+ #define paij_index(x, y) ((y)*ncols) + (x)
207
+ #define lbj_index(i) (i)
208
+ #define pbj_index(i) (i)
209
+ #define im_index(x, y) ((y)*ncols) + (x)
210
+ #define sl_index(i) (i)
211
+ #endif
212
+
213
+ int bandsol(double *a, double *r, int n, int nd)
214
+ {
215
+ /*
216
+ bandsol solves a sparse system of linear equations with band-diagonal matrix.
217
+ Band is assumed to be symmetric relative to the main diaginal.
218
+
219
+ ..math:
220
+
221
+ A * x = r
222
+
223
+ Parameters
224
+ ----------
225
+ a : double array of shape [n,nd]
226
+ The left-hand-side of the equation system
227
+ The main diagonal should be in a(*,nd/2),
228
+ the first lower subdiagonal should be in a(1:n-1,nd/2-1),
229
+ the first upper subdiagonal is in a(0:n-2,nd/2+1) etc.
230
+ For example:
231
+ / 0 0 X X X \
232
+ | 0 X X X X |
233
+ | X X X X X |
234
+ | X X X X X |
235
+ A = | X X X X X |
236
+ | X X X X X |
237
+ | X X X X X |
238
+ | X X X X 0 |
239
+ \ X X X 0 0 /
240
+ r : double array of shape [n]
241
+ the right-hand-side of the equation system
242
+ n : int
243
+ The number of equations
244
+ nd : int
245
+ The width of the band (3 for tri-diagonal system). Must be an odd number.
246
+
247
+ Returns
248
+ -------
249
+ code : int
250
+ 0 on success, -1 on incorrect size of "a" and -4 on degenerate matrix.
251
+ */
252
+ double aa;
253
+ int i, j, k;
254
+
255
+ #if DEBUG
256
+ _n = n;
257
+ _nd = nd;
258
+ #endif
259
+
260
+ /* Forward sweep */
261
+ for (i = 0; i < n - 1; i++)
262
+ {
263
+ aa = a[a_index(i, nd / 2)];
264
+ #if DEBUG
265
+ if (aa == 0)
266
+ {
267
+ printf("1, index: %i, %i\n", i, nd / 2);
268
+ aa = 1;
269
+ }
270
+ #endif
271
+ r[r_index(i)] /= aa;
272
+ for (j = 0; j < nd; j++)
273
+ a[a_index(i, j)] /= aa;
274
+ for (j = 1; j < min(nd / 2 + 1, n - i); j++)
275
+ {
276
+ aa = a[a_index(i + j, nd / 2 - j)];
277
+ r[r_index(i + j)] -= r[r_index(i)] * aa;
278
+ for (k = 0; k < nd - j; k++)
279
+ a[a_index(i + j, k)] -= a[a_index(i, k + j)] * aa;
280
+ }
281
+ }
282
+
283
+ /* Backward sweep */
284
+ aa = a[a_index(n - 1, nd / 2)];
285
+ #if DEBUG
286
+ if (aa == 0)
287
+ {
288
+ printf("3, index: %i, %i\n", 0, nd / 2);
289
+ aa = 1;
290
+ }
291
+ #endif
292
+ r[r_index(n - 1)] /= aa;
293
+ for (i = n - 1; i > 0; i--)
294
+ {
295
+ for (j = 1; j <= min(nd / 2, i); j++)
296
+ r[r_index(i - j)] -= r[r_index(i)] * a[a_index(i - j, nd / 2 + j)];
297
+ r[r_index(i - 1)] /= a[a_index(i - 1, nd / 2)];
298
+ }
299
+
300
+ aa = a[a_index(0, nd / 2)];
301
+ #if DEBUG
302
+ if (aa == 0)
303
+ {
304
+ printf("4, index: %i, %i\n", 0, nd / 2);
305
+ aa = 1;
306
+ }
307
+ #endif
308
+ r[r_index(0)] /= aa;
309
+ return 0;
310
+ }
311
+
312
+ // This is the faster median determination method.
313
+ // Algorithm from Numerical recipes in C of 1992
314
+ // see http://ndevilla.free.fr/median/median/
315
+
316
+ #define ELEM_SWAP(a, b) \
317
+ { \
318
+ register double t = (a); \
319
+ (a) = (b); \
320
+ (b) = t; \
321
+ }
322
+
323
+ double quick_select_median(double arr[], unsigned int n)
324
+ {
325
+ int low, high;
326
+ int median;
327
+ int middle, ll, hh;
328
+
329
+ low = 0;
330
+ high = n - 1;
331
+ median = (low + high) / 2;
332
+ for (;;)
333
+ {
334
+ if (high <= low) /* One element only */
335
+ return arr[median];
336
+ if (high == low + 1)
337
+ { /* Two elements only */
338
+ if (arr[low] > arr[high])
339
+ ELEM_SWAP(arr[low], arr[high]);
340
+ return arr[median];
341
+ }
342
+ /* Find median of low, middle and high items; swap into position low */
343
+ middle = (low + high) / 2;
344
+ if (arr[middle] > arr[high])
345
+ ELEM_SWAP(arr[middle], arr[high]);
346
+ if (arr[low] > arr[high])
347
+ ELEM_SWAP(arr[low], arr[high]);
348
+ if (arr[middle] > arr[low])
349
+ ELEM_SWAP(arr[middle], arr[low]);
350
+ /* Swap low item (now in position middle) into position (low+1) */
351
+ ELEM_SWAP(arr[middle], arr[low + 1]);
352
+ /* Nibble from each end towards middle, swapping items when stuck */
353
+ ll = low + 1;
354
+ hh = high;
355
+ for (;;)
356
+ {
357
+ do
358
+ ll++;
359
+ while (arr[low] > arr[ll]);
360
+ do
361
+ hh--;
362
+ while (arr[hh] > arr[low]);
363
+ if (hh < ll)
364
+ break;
365
+ ELEM_SWAP(arr[ll], arr[hh]);
366
+ }
367
+ /* Swap middle item (in position low) back into correct position */
368
+ ELEM_SWAP(arr[low], arr[hh]);
369
+ /* Re-set active partition */
370
+ if (hh <= median)
371
+ low = ll;
372
+ if (hh >= median)
373
+ high = hh - 1;
374
+ }
375
+ }
376
+
377
+ double median_absolute_deviation(double arr[], unsigned int n)
378
+ {
379
+ double median = quick_select_median(arr, n);
380
+ for (size_t i = 0; i < n; i++)
381
+ {
382
+ arr[i] = fabs(arr[i] - median);
383
+ }
384
+ double mad = quick_select_median(arr, n);
385
+ return mad;
386
+ }
387
+
388
+ int xi_zeta_tensors(
389
+ int ncols,
390
+ int nrows,
391
+ int ny,
392
+ double *ycen,
393
+ int *ycen_offset,
394
+ int y_lower_lim,
395
+ int osample,
396
+ double *PSF_curve,
397
+ xi_ref *xi,
398
+ zeta_ref *zeta,
399
+ int *m_zeta)
400
+ {
401
+ /*
402
+ Create the Xi and Zeta tensors, that describe the contribution of each pixel to the subpixels of the image,
403
+ Considering the curvature of the slit.
404
+
405
+ Parameters
406
+ ----------
407
+ ncols : int
408
+ Swath width in pixels
409
+ nrows : int
410
+ Extraction slit height in pixels
411
+ ny : int
412
+ Size of the slit function array: ny = osample * (nrows + 1) + 1
413
+ ycen : double array of shape (ncols,)
414
+ Order centre line offset from pixel row boundary
415
+ ycen_offsets : int array of shape (ncols,)
416
+ Order image column shift
417
+ y_lower_lim : int
418
+ Number of detector pixels below the pixel containing the central line ycen
419
+ osample : int
420
+ Subpixel ovsersampling factor
421
+ PSF_curve : double array of shape (ncols, 3)
422
+ Parabolic fit to the slit image curvature.
423
+ For column d_x = PSF_curve[ncols][0] + PSF_curve[ncols][1] *d_y + PSF_curve[ncols][2] *d_y^2,
424
+ where d_y is the offset from the central line ycen.
425
+ Thus central subpixel of omega[x][y'][delta_x][iy'] does not stick out of column x.
426
+ xi : (out) xi_ref array of shape (ncols, ny, 4)
427
+ Convolution tensor telling the coordinates of detector
428
+ pixels on which {x, iy} element falls and the corresponding projections.
429
+ zeta : (out) zeta_ref array of shape (ncols, nrows, 3 * (osample + 1))
430
+ Convolution tensor telling the coordinates of subpixels {x, iy} contributing
431
+ to detector pixel {x, y}.
432
+ m_zeta : (out) int array of shape (ncols, nrows)
433
+ The actual number of contributing elements in zeta for each pixel
434
+
435
+ Returns
436
+ -------
437
+ code : int
438
+ 0 on success, -1 on failure
439
+ */
440
+ int x, xx, y, yy, ix, ix1, ix2, iy, iy1, iy2, m;
441
+ double step, delta, dy, w, d1, d2;
442
+
443
+ step = 1.e0 / osample;
444
+
445
+ /* Clean xi */
446
+ for (x = 0; x < ncols; x++)
447
+ {
448
+ for (iy = 0; iy < ny; iy++)
449
+ {
450
+ for (m = 0; m < 4; m++)
451
+ {
452
+ xi[xi_index(x, iy, m)].x = -1;
453
+ xi[xi_index(x, iy, m)].y = -1;
454
+ xi[xi_index(x, iy, m)].w = 0.;
455
+ }
456
+ }
457
+ }
458
+
459
+ /* Clean zeta */
460
+ for (x = 0; x < ncols; x++)
461
+ {
462
+ for (y = 0; y < nrows; y++)
463
+ {
464
+ m_zeta[mzeta_index(x, y)] = 0;
465
+ for (ix = 0; ix < MAX_ZETA_Z; ix++)
466
+ {
467
+ zeta[zeta_index(x, y, ix)].x = -1;
468
+ zeta[zeta_index(x, y, ix)].iy = -1;
469
+ zeta[zeta_index(x, y, ix)].w = 0.;
470
+ }
471
+ }
472
+ }
473
+
474
+ /*
475
+ Construct the xi and zeta tensors. They contain pixel references and contribution.
476
+ values going from a given subpixel to other pixels (xi) and coming from other subpixels
477
+ to a given detector pixel (zeta).
478
+ Note, that xi and zeta are used in the equations for sL, sP and for the model but they
479
+ do not involve the data, only the geometry. Thus it can be pre-computed once.
480
+ */
481
+ for (x = 0; x < ncols; x++)
482
+ {
483
+ /*
484
+ I promised to reconsider the initial offset. Here it is. For the original layout
485
+ (no column shifts and discontinuities in ycen) there is pixel y that contains the
486
+ central line yc. There are two options here (by construction of ycen that can be 0
487
+ but cannot be 1): (1) yc is inside pixel y and (2) yc falls at the boundary between
488
+ pixels y and y-1. yc cannot be at the boundary of pixels y+1 and y because we would
489
+ select y+1 to be pixel y in that case.
490
+
491
+ Next we need to define starting and ending indices iy for sL subpixels that contribute
492
+ to pixel y. I call them iy1 and iy2. For both cases we assume osample+1 subpixels covering
493
+ pixel y (wierd). So for case 1 iy1 will be (y-1)*osample and iy2 == y*osample. Special
494
+ treatment of the boundary subpixels will compensate for introducing extra subpixel in
495
+ case 1. In case 2 things are more logical: iy1=(yc-y)*osample+(y-1)*osample;
496
+ iy2=(y+1-yc)*osample)+(y-1)*osample. ycen is yc-y making things simpler. Note also that
497
+ the same pattern repeates for all rows: we only need to initialize iy1 and iy2 and keep
498
+ incrementing them by osample.
499
+ */
500
+
501
+ iy2 = osample - floor(ycen[x] * osample);
502
+ iy1 = iy2 - osample;
503
+
504
+ /*
505
+ Handling partial subpixels cut by detector pixel rows is again tricky. Here we have three
506
+ cases (mostly because of the decision to assume that we always have osample+1 subpixels
507
+ per one detector pixel). Here d1 is the fraction of the subpixel iy1 inside detector pixel y.
508
+ d2 is then the fraction of subpixel iy2 inside detector pixel y. By definition d1+d2==step.
509
+ Case 1: ycen falls on the top boundary of each detector pixel (ycen == 1). Here we conclude
510
+ that the first subpixel is fully contained inside pixel y and d1 is set to step.
511
+ Case 2: ycen falls on the bottom boundary of each detector pixel (ycen == 0). Here we conclude
512
+ that the first subpixel is totally outside of pixel y and d1 is set to 0.
513
+ Case 3: ycen falls inside of each pixel (0>ycen>1). In this case d1 is set to the fraction of
514
+ the first step contained inside of each pixel.
515
+ And BTW, this also means that central line coinsides with the upper boundary of subpixel iy2
516
+ when the y loop reaches pixel y_lower_lim. In other words:
517
+
518
+ dy=(iy-(y_lower_lim+ycen[x])*osample)*step-0.5*step
519
+ */
520
+
521
+ d1 = fmod(ycen[x], step);
522
+ if (d1 == 0)
523
+ d1 = step;
524
+ d2 = step - d1;
525
+
526
+ /*
527
+ The final hurdle for 2D slit decomposition is to construct two 3D reference tensors. We proceed
528
+ similar to 1D case except that now each iy subpixel can be shifted left or right following
529
+ the curvature of the slit image on the detector. We assume for now that each subpixel is
530
+ exactly 1 detector pixel wide. This may not be exactly true if the curvature changes accross
531
+ the focal plane but will deal with it when the necessity will become apparent. For now we
532
+ just assume that a shift delta the weight w assigned to subpixel iy is divided between
533
+ ix1=int(delta) and ix2=int(delta)+signum(delta) as (1-|delta-ix1|)*w and |delta-ix1|*w.
534
+
535
+ The curvature is given by a quadratic polynomial evaluated from an approximation for column
536
+ x: delta = PSF_curve[x][0] + PSF_curve[x][1] * (y-yc[x]) + PSF_curve[x][2] * (y-yc[x])^2.
537
+ It looks easy except that y and yc are set in the global detector coordinate system rather than
538
+ in the shifted and cropped swath passed to slit_func_2d. One possible solution I will try here
539
+ is to modify PSF_curve before the call such as:
540
+ delta = PSF_curve'[x][0] + PSF_curve'[x][1] * (y'-ycen[x]) + PSF_curve'[x][2] * (y'-ycen[x])^2
541
+ where y' = y - floor(yc).
542
+ */
543
+
544
+ /* Define initial distance from ycen */
545
+ /* It is given by the center of the first */
546
+ /* subpixel falling into pixel y_lower_lim */
547
+ dy = ycen[x] - floor((y_lower_lim + ycen[x]) / step) * step - step;
548
+
549
+ /*
550
+ Now we go detector pixels x and y incrementing subpixels looking for their controibutions
551
+ to the current and adjacent pixels. Note that the curvature/tilt of the projected slit
552
+ image could be so large that subpixel iy may no contribute to column x at all. On the
553
+ other hand, subpixels around ycen by definition must contribute to pixel x,y.
554
+ 3rd index in xi refers corners of pixel xx,y: 0:LL, 1:LR, 2:UL, 3:UR.
555
+ */
556
+
557
+ for (y = 0; y < nrows; y++)
558
+ {
559
+ iy1 += osample; // Bottom subpixel falling in row y
560
+ iy2 += osample; // Top subpixel falling in row y
561
+ dy -= step;
562
+ for (iy = iy1; iy <= iy2; iy++)
563
+ {
564
+ if (iy == iy1)
565
+ w = d1;
566
+ else if (iy == iy2)
567
+ w = d2;
568
+ else
569
+ w = step;
570
+ dy += step;
571
+ delta = (PSF_curve[psf_index(x, 1)] + PSF_curve[psf_index(x, 2)] * (dy - ycen[x])) * (dy - ycen[x]);
572
+ ix1 = delta;
573
+ ix2 = ix1 + signum(delta);
574
+
575
+ /* Three cases: subpixel on the bottom boundary of row y, intermediate subpixels and top boundary */
576
+
577
+ if (iy == iy1) /* Case A: Subpixel iy is entering detector row y */
578
+ {
579
+ if (ix1 < ix2) /* Subpixel iy shifts to the right from column x */
580
+ {
581
+ if (x + ix1 >= 0 && x + ix2 < ncols)
582
+ {
583
+ xx = x + ix1; /* Upper right corner of subpixel iy */
584
+ yy = y + ycen_offset[x] - ycen_offset[xx];
585
+ xi[xi_index(x, iy, 3)].x = xx;
586
+ xi[xi_index(x, iy, 3)].y = yy;
587
+ xi[xi_index(x, iy, 3)].w = w - fabs(delta - ix1) * w;
588
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows && xi[xi_index(x, iy, 3)].w > 0)
589
+ {
590
+ m = m_zeta[mzeta_index(xx, yy)];
591
+ zeta[zeta_index(xx, yy, m)].x = x;
592
+ zeta[zeta_index(xx, yy, m)].iy = iy;
593
+ zeta[zeta_index(xx, yy, m)].w = xi[xi_index(x, iy, 3)].w;
594
+ m_zeta[mzeta_index(xx, yy)]++;
595
+ }
596
+ xx = x + ix2; /* Upper left corner of subpixel iy */
597
+ // This offset is required because the iy subpixel
598
+ // is going to contribute to the yy row in xx column
599
+ // of detector pixels where yy and y are in the same
600
+ // row. In the packed array this is not necessarily true.
601
+ // Instead, what we know is that:
602
+ // y+ycen_offset[x] == yy+ycen_offset[xx]
603
+ yy = y + ycen_offset[x] - ycen_offset[xx];
604
+
605
+ xi[xi_index(x, iy, 2)].x = xx;
606
+ xi[xi_index(x, iy, 2)].y = yy;
607
+ xi[xi_index(x, iy, 2)].w = fabs(delta - ix1) * w;
608
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows && xi[xi_index(x, iy, 2)].w > 0)
609
+ {
610
+ m = m_zeta[mzeta_index(xx, yy)];
611
+ zeta[zeta_index(xx, yy, m)].x = x;
612
+ zeta[zeta_index(xx, yy, m)].iy = iy;
613
+ zeta[zeta_index(xx, yy, m)].w = xi[xi_index(x, iy, 2)].w;
614
+ m_zeta[mzeta_index(xx, yy)]++;
615
+ }
616
+ }
617
+ }
618
+ else if (ix1 > ix2) /* Subpixel iy shifts to the left from column x */
619
+ {
620
+ if (x + ix2 >= 0 && x + ix1 < ncols)
621
+ {
622
+ xx = x + ix2; /* Upper left corner of subpixel iy */
623
+ yy = y + ycen_offset[x] - ycen_offset[xx];
624
+ xi[xi_index(x, iy, 2)].x = xx;
625
+ xi[xi_index(x, iy, 2)].y = yy;
626
+ xi[xi_index(x, iy, 2)].w = fabs(delta - ix1) * w;
627
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows && xi[xi_index(x, iy, 2)].w > 0)
628
+ {
629
+ m = m_zeta[mzeta_index(xx, yy)];
630
+ zeta[zeta_index(xx, yy, m)].x = x;
631
+ zeta[zeta_index(xx, yy, m)].iy = iy;
632
+ zeta[zeta_index(xx, yy, m)].w = xi[xi_index(x, iy, 2)].w;
633
+ m_zeta[mzeta_index(xx, yy)]++;
634
+ }
635
+ xx = x + ix1; /* Upper right corner of subpixel iy */
636
+ yy = y + ycen_offset[x] - ycen_offset[xx];
637
+ xi[xi_index(x, iy, 3)].x = xx;
638
+ xi[xi_index(x, iy, 3)].y = yy;
639
+ xi[xi_index(x, iy, 3)].w = w - fabs(delta - ix1) * w;
640
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows && xi[xi_index(x, iy, 3)].w > 0)
641
+ {
642
+ m = m_zeta[mzeta_index(xx, yy)];
643
+ zeta[zeta_index(xx, yy, m)].x = x;
644
+ zeta[zeta_index(xx, yy, m)].iy = iy;
645
+ zeta[zeta_index(xx, yy, m)].w = xi[xi_index(x, iy, 3)].w;
646
+ m_zeta[mzeta_index(xx, yy)]++;
647
+ }
648
+ }
649
+ }
650
+ else
651
+ {
652
+ xx = x + ix1; /* Subpixel iy stays inside column x */
653
+ yy = y + ycen_offset[x] - ycen_offset[xx];
654
+ xi[xi_index(x, iy, 2)].x = xx;
655
+ xi[xi_index(x, iy, 2)].y = yy;
656
+ xi[xi_index(x, iy, 2)].w = w;
657
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows && w > 0)
658
+ {
659
+ m = m_zeta[mzeta_index(xx, yy)];
660
+ zeta[zeta_index(xx, yy, m)].x = x;
661
+ zeta[zeta_index(xx, yy, m)].iy = iy;
662
+ zeta[zeta_index(xx, yy, m)].w = w;
663
+ m_zeta[mzeta_index(xx, yy)]++;
664
+ }
665
+ }
666
+ }
667
+ else if (iy == iy2) /* Case C: Subpixel iy is leaving detector row y */
668
+ {
669
+ if (ix1 < ix2) /* Subpixel iy shifts to the right from column x */
670
+ {
671
+ if (x + ix1 >= 0 && x + ix2 < ncols)
672
+ {
673
+ xx = x + ix1; /* Bottom right corner of subpixel iy */
674
+ yy = y + ycen_offset[x] - ycen_offset[xx];
675
+ xi[xi_index(x, iy, 1)].x = xx;
676
+ xi[xi_index(x, iy, 1)].y = yy;
677
+ xi[xi_index(x, iy, 1)].w = w - fabs(delta - ix1) * w;
678
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows && xi[xi_index(x, iy, 1)].w > 0)
679
+ {
680
+ m = m_zeta[mzeta_index(xx, yy)];
681
+ zeta[zeta_index(xx, yy, m)].x = x;
682
+ zeta[zeta_index(xx, yy, m)].iy = iy;
683
+ zeta[zeta_index(xx, yy, m)].w = xi[xi_index(x, iy, 1)].w;
684
+ m_zeta[mzeta_index(xx, yy)]++;
685
+ }
686
+ xx = x + ix2; /* Bottom left corner of subpixel iy */
687
+ yy = y + ycen_offset[x] - ycen_offset[xx];
688
+ xi[xi_index(x, iy, 0)].x = xx;
689
+ xi[xi_index(x, iy, 0)].y = yy;
690
+ xi[xi_index(x, iy, 0)].w = fabs(delta - ix1) * w;
691
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows && xi[xi_index(x, iy, 0)].w > 0)
692
+ {
693
+ m = m_zeta[mzeta_index(xx, yy)];
694
+ zeta[zeta_index(xx, yy, m)].x = x;
695
+ zeta[zeta_index(xx, yy, m)].iy = iy;
696
+ zeta[zeta_index(xx, yy, m)].w = xi[xi_index(x, iy, 0)].w;
697
+ m_zeta[mzeta_index(xx, yy)]++;
698
+ }
699
+ }
700
+ }
701
+ else if (ix1 > ix2) /* Subpixel iy shifts to the left from column x */
702
+ {
703
+ if (x + ix2 >= 0 && x + ix1 < ncols)
704
+ {
705
+ xx = x + ix2; /* Bottom left corner of subpixel iy */
706
+ yy = y + ycen_offset[x] - ycen_offset[xx];
707
+ xi[xi_index(x, iy, 0)].x = xx;
708
+ xi[xi_index(x, iy, 0)].y = yy;
709
+ xi[xi_index(x, iy, 0)].w = fabs(delta - ix1) * w;
710
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows && xi[xi_index(x, iy, 0)].w > 0)
711
+ {
712
+ m = m_zeta[mzeta_index(xx, yy)];
713
+ zeta[zeta_index(xx, yy, m)].x = x;
714
+ zeta[zeta_index(xx, yy, m)].iy = iy;
715
+ zeta[zeta_index(xx, yy, m)].w = xi[xi_index(x, iy, 0)].w;
716
+ m_zeta[mzeta_index(xx, yy)]++;
717
+ }
718
+ xx = x + ix1; /* Bottom right corner of subpixel iy */
719
+ yy = y + ycen_offset[x] - ycen_offset[xx];
720
+ xi[xi_index(x, iy, 1)].x = xx;
721
+ xi[xi_index(x, iy, 1)].y = yy;
722
+ xi[xi_index(x, iy, 1)].w = w - fabs(delta - ix1) * w;
723
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows && xi[xi_index(x, iy, 1)].w > 0)
724
+ {
725
+ m = m_zeta[mzeta_index(xx, yy)];
726
+ zeta[zeta_index(xx, yy, m)].x = x;
727
+ zeta[zeta_index(xx, yy, m)].iy = iy;
728
+ zeta[zeta_index(xx, yy, m)].w = xi[xi_index(x, iy, 1)].w;
729
+ m_zeta[mzeta_index(xx, yy)]++;
730
+ }
731
+ }
732
+ }
733
+ else /* Subpixel iy stays inside column x */
734
+ {
735
+ xx = x + ix1;
736
+ yy = y + ycen_offset[x] - ycen_offset[xx];
737
+ xi[xi_index(x, iy, 0)].x = xx;
738
+ xi[xi_index(x, iy, 0)].y = yy;
739
+ xi[xi_index(x, iy, 0)].w = w;
740
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows && w > 0)
741
+ {
742
+ m = m_zeta[mzeta_index(xx, yy)];
743
+ zeta[zeta_index(xx, yy, m)].x = x;
744
+ zeta[zeta_index(xx, yy, m)].iy = iy;
745
+ zeta[zeta_index(xx, yy, m)].w = w;
746
+ m_zeta[mzeta_index(xx, yy)]++;
747
+ }
748
+ }
749
+ }
750
+ else /* CASE B: Subpixel iy is fully inside detector row y */
751
+ {
752
+ if (ix1 < ix2) /* Subpixel iy shifts to the right from column x */
753
+ {
754
+ if (x + ix1 >= 0 && x + ix2 < ncols)
755
+ {
756
+ xx = x + ix1; /* Bottom right corner of subpixel iy */
757
+ yy = y + ycen_offset[x] - ycen_offset[xx];
758
+ xi[xi_index(x, iy, 1)].x = xx;
759
+ xi[xi_index(x, iy, 1)].y = yy;
760
+ xi[xi_index(x, iy, 1)].w = w - fabs(delta - ix1) * w;
761
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows && xi[xi_index(x, iy, 1)].w > 0)
762
+ {
763
+ m = m_zeta[mzeta_index(xx, yy)];
764
+ zeta[zeta_index(xx, yy, m)].x = x;
765
+ zeta[zeta_index(xx, yy, m)].iy = iy;
766
+ zeta[zeta_index(xx, yy, m)].w = xi[xi_index(x, iy, 1)].w;
767
+ m_zeta[mzeta_index(xx, yy)]++;
768
+ }
769
+ xx = x + ix2; /* Bottom left corner of subpixel iy */
770
+ yy = y + ycen_offset[x] - ycen_offset[xx];
771
+ xi[xi_index(x, iy, 0)].x = xx;
772
+ xi[xi_index(x, iy, 0)].y = yy;
773
+ xi[xi_index(x, iy, 0)].w = fabs(delta - ix1) * w;
774
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows && xi[xi_index(x, iy, 0)].w > 0)
775
+ {
776
+ m = m_zeta[mzeta_index(xx, yy)];
777
+ zeta[zeta_index(xx, yy, m)].x = x;
778
+ zeta[zeta_index(xx, yy, m)].iy = iy;
779
+ zeta[zeta_index(xx, yy, m)].w = xi[xi_index(x, iy, 0)].w;
780
+ m_zeta[mzeta_index(xx, yy)]++;
781
+ }
782
+ }
783
+ }
784
+ else if (ix1 > ix2) /* Subpixel iy shifts to the left from column x */
785
+ {
786
+ if (x + ix2 >= 0 && x + ix1 < ncols)
787
+ {
788
+ xx = x + ix2; /* Bottom right corner of subpixel iy */
789
+ yy = y + ycen_offset[x] - ycen_offset[xx];
790
+ xi[xi_index(x, iy, 1)].x = xx;
791
+ xi[xi_index(x, iy, 1)].y = yy;
792
+ xi[xi_index(x, iy, 1)].w = fabs(delta - ix1) * w;
793
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows && xi[xi_index(x, iy, 1)].w > 0)
794
+ {
795
+ m = m_zeta[mzeta_index(xx, yy)];
796
+ zeta[zeta_index(xx, yy, m)].x = x;
797
+ zeta[zeta_index(xx, yy, m)].iy = iy;
798
+ zeta[zeta_index(xx, yy, m)].w = xi[xi_index(x, iy, 1)].w;
799
+ m_zeta[mzeta_index(xx, yy)]++;
800
+ }
801
+ xx = x + ix1; /* Bottom left corner of subpixel iy */
802
+ yy = y + ycen_offset[x] - ycen_offset[xx];
803
+ xi[xi_index(x, iy, 0)].x = xx;
804
+ xi[xi_index(x, iy, 0)].y = yy;
805
+ xi[xi_index(x, iy, 0)].w = w - fabs(delta - ix1) * w;
806
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows && xi[xi_index(x, iy, 0)].w > 0)
807
+ {
808
+ m = m_zeta[mzeta_index(xx, yy)];
809
+ zeta[zeta_index(xx, yy, m)].x = x;
810
+ zeta[zeta_index(xx, yy, m)].iy = iy;
811
+ zeta[zeta_index(xx, yy, m)].w = xi[xi_index(x, iy, 0)].w;
812
+ m_zeta[mzeta_index(xx, yy)]++;
813
+ }
814
+ }
815
+ }
816
+ else /* Subpixel iy stays inside column x */
817
+ {
818
+ xx = x + ix2;
819
+ yy = y + ycen_offset[x] - ycen_offset[xx];
820
+ xi[xi_index(x, iy, 0)].x = xx;
821
+ xi[xi_index(x, iy, 0)].y = yy;
822
+ xi[xi_index(x, iy, 0)].w = w;
823
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows && w > 0)
824
+ {
825
+ m = m_zeta[mzeta_index(xx, yy)];
826
+ zeta[zeta_index(xx, yy, m)].x = x;
827
+ zeta[zeta_index(xx, yy, m)].iy = iy;
828
+ zeta[zeta_index(xx, yy, m)].w = w;
829
+ m_zeta[mzeta_index(xx, yy)]++;
830
+ }
831
+ }
832
+ }
833
+ }
834
+ }
835
+ }
836
+ return 0;
837
+ }
838
+
839
+ int slit_func_curved(int ncols,
840
+ int nrows,
841
+ int ny,
842
+ double *im,
843
+ double *pix_unc,
844
+ unsigned char *mask,
845
+ double *ycen,
846
+ int *ycen_offset,
847
+ int y_lower_lim,
848
+ int osample,
849
+ double lambda_sP,
850
+ double lambda_sL,
851
+ int maxiter,
852
+ double *PSF_curve,
853
+ double *sP,
854
+ double *sL,
855
+ double *model,
856
+ double *unc,
857
+ double *info)
858
+ {
859
+ /*
860
+ Extract the spectrum and slit illumination function for a curved slit
861
+
862
+ This function does not assign or free any memory,
863
+ therefore all working arrays are passed as parameters.
864
+ The contents of which will be overriden however
865
+
866
+ Parameters
867
+ ----------
868
+ ncols : int
869
+ Swath width in pixels
870
+ nrows : int
871
+ Extraction slit height in pixels
872
+ nx : int
873
+ Range of columns affected by PSF tilt: nx = 2 * delta_x + 1
874
+ ny : int
875
+ Size of the slit function array: ny = osample * (nrows + 1) + 1
876
+ im : double array of shape (nrows, ncols)
877
+ Image to be decomposed
878
+ pix_unc : double array of shape (nrows, ncols)
879
+ Individual pixel uncertainties. Set to zero if unknown.
880
+ mask : byte array of shape (nrows, ncols)
881
+ Initial and final mask for the swath, both in and output
882
+ ycen : double array of shape (ncols,)
883
+ Order centre line offset from pixel row boundary.
884
+ Should only contain values between 0 and 1.
885
+ ycen_offset : int array of shape (ncols,)
886
+ Order image column shift
887
+ y_lower_lim : int
888
+ Number of detector pixels below the pixel containing
889
+ the central line yc.
890
+ osample : int
891
+ Subpixel ovsersampling factor
892
+ lambda_sP : double
893
+ Smoothing parameter for the spectrum, could be zero
894
+ lambda_sL : double
895
+ Smoothing parameter for the slit function, usually > 0
896
+ PSF_curve : double array of shape (ncols, 3)
897
+ Slit curvature parameters for each point along the spectrum
898
+ sP : (out) double array of shape (ncols,)
899
+ Spectrum resulting from decomposition
900
+ sL : (out) double array of shape (ny,)
901
+ Slit function resulting from decomposition
902
+ model : (out) double array of shape (ncols, nrows)
903
+ Model constructed from sp and sf
904
+ unc : (out) double array of shape (ncols,)
905
+ Spectrum uncertainties based on data - model and pix_unc
906
+ info : (out) double array of shape (5,)
907
+ Returns information about the fit results
908
+ Returns
909
+ -------
910
+ code : int
911
+ 0 on success, -1 on failure (see also bandsol)
912
+ */
913
+ int x, xx, xxx, y, yy, iy, jy, n, m, nx;
914
+ double norm, dev, lambda, diag_tot, ww, www;
915
+ double cost_old, ftol, tmp;
916
+ int iter, delta_x;
917
+ unsigned int isum;
918
+
919
+ // For the solving of the equation system
920
+ double *l_Aij, *l_bj, *p_Aij, *p_bj;
921
+ double *diff;
922
+
923
+ // For the geometry
924
+ xi_ref *xi;
925
+ zeta_ref *zeta;
926
+ int *m_zeta;
927
+
928
+ // The Optimization results
929
+ double success, status, cost;
930
+
931
+ // maxiter = 20; // Maximum number of iterations
932
+ ftol = 1e-7; // Maximum cost difference between two iterations to stop convergence
933
+ success = 1;
934
+ status = 0;
935
+
936
+ cost = INFINITY;
937
+ ny = osample * (nrows + 1) + 1; /* The size of the sL array. Extra osample is because ycen can be between 0 and 1. */
938
+
939
+ #if DEBUG
940
+ _ncols = ncols;
941
+ _nrows = nrows;
942
+ _ny = ny;
943
+ _osample = osample;
944
+ #endif
945
+
946
+ // If we want to smooth the spectrum we need at least delta_x = 1
947
+ // Otherwise delta_x = 0 works if there is no curvature
948
+ delta_x = lambda_sP == 0 ? 0 : 1;
949
+ for (x = 0; x < ncols; x++)
950
+ {
951
+ for (y = -y_lower_lim; y < nrows - y_lower_lim + 1; y++)
952
+ {
953
+ tmp = ceil(fabs(y * PSF_curve[psf_index(x, 1)] + y * y * PSF_curve[psf_index(x, 2)]));
954
+ delta_x = max(delta_x, tmp);
955
+ }
956
+ }
957
+ nx = 4 * delta_x + 1; /* Maximum horizontal shift in detector pixels due to slit image curvature */
958
+
959
+ #if DEBUG
960
+ _nx = nx;
961
+ #endif
962
+
963
+ // The curvature is larger than the number of columns
964
+ // Usually that means that the curvature is messed up
965
+ if (nx > ncols)
966
+ {
967
+ info[0] = 0; //failed
968
+ info[1] = cost; //INFINITY
969
+ info[2] = -2; // curvature to large
970
+ info[3] = 0;
971
+ info[4] = delta_x;
972
+ return -1;
973
+ }
974
+
975
+ l_Aij = malloc(MAX_LAIJ * sizeof(double));
976
+ p_Aij = malloc(MAX_PAIJ * sizeof(double));
977
+ l_bj = malloc(MAX_LBJ * sizeof(double));
978
+ p_bj = malloc(MAX_PBJ * sizeof(double));
979
+ xi = malloc(MAX_XI * sizeof(xi_ref));
980
+ zeta = malloc(MAX_ZETA * sizeof(zeta_ref));
981
+ m_zeta = malloc(MAX_MZETA * sizeof(int));
982
+ diff = malloc(MAX_IM * sizeof(double));
983
+
984
+ xi_zeta_tensors(ncols, nrows, ny, ycen, ycen_offset, y_lower_lim, osample, PSF_curve, xi, zeta, m_zeta);
985
+
986
+ /* Loop through sL , sP reconstruction until convergence is reached */
987
+ iter = 0;
988
+ do
989
+ {
990
+ // Save the total cost (chi-square) from the previous iteration
991
+ cost_old = cost;
992
+
993
+ /* Compute slit function sL */
994
+
995
+ /* Prepare the RHS and the matrix */
996
+ for (iy = 0; iy < MAX_LBJ; iy++)
997
+ l_bj[lbj_index(iy)] = 0.e0; /* Clean RHS */
998
+ for (iy = 0; iy < MAX_LAIJ; iy++)
999
+ l_Aij[iy] = 0;
1000
+
1001
+ /* Fill in SLE arrays for slit function */
1002
+ diag_tot = 0.e0;
1003
+ for (iy = 0; iy < ny; iy++)
1004
+ {
1005
+ for (x = 0; x < ncols; x++)
1006
+ {
1007
+ for (n = 0; n < 4; n++)
1008
+ {
1009
+ ww = xi[xi_index(x, iy, n)].w;
1010
+ if (ww > 0)
1011
+ {
1012
+ xx = xi[xi_index(x, iy, n)].x;
1013
+ yy = xi[xi_index(x, iy, n)].y;
1014
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows)
1015
+ {
1016
+ if (m_zeta[mzeta_index(xx, yy)] > 0)
1017
+ {
1018
+ for (m = 0; m < m_zeta[mzeta_index(xx, yy)]; m++)
1019
+ {
1020
+ xxx = zeta[zeta_index(xx, yy, m)].x;
1021
+ jy = zeta[zeta_index(xx, yy, m)].iy;
1022
+ www = zeta[zeta_index(xx, yy, m)].w;
1023
+ l_Aij[laij_index(iy, jy - iy + 2 * osample)] += sP[sp_index(xxx)] * sP[sp_index(x)] * www * ww * mask[im_index(xx, yy)];
1024
+ }
1025
+ l_bj[lbj_index(iy)] += im[im_index(xx, yy)] * mask[im_index(xx, yy)] * sP[sp_index(x)] * ww;
1026
+ }
1027
+ }
1028
+ }
1029
+ }
1030
+ }
1031
+ diag_tot += l_Aij[laij_index(iy, 2 * osample)];
1032
+ }
1033
+
1034
+ /* Scale regularization parameters */
1035
+ lambda = lambda_sL * diag_tot / ny;
1036
+
1037
+ /* Add regularization parts for the SLE matrix */
1038
+
1039
+ l_Aij[laij_index(0, 2 * osample)] += lambda; /* Main diagonal */
1040
+ l_Aij[laij_index(0, 2 * osample + 1)] -= lambda; /* Upper diagonal */
1041
+ for (iy = 1; iy < ny - 1; iy++)
1042
+ {
1043
+ l_Aij[laij_index(iy, 2 * osample - 1)] -= lambda; /* Lower diagonal */
1044
+ l_Aij[laij_index(iy, 2 * osample)] += lambda * 2.e0; /* Main diagonal */
1045
+ l_Aij[laij_index(iy, 2 * osample + 1)] -= lambda; /* Upper diagonal */
1046
+ }
1047
+ l_Aij[laij_index(ny - 1, 2 * osample - 1)] -= lambda; /* Lower diagonal */
1048
+ l_Aij[laij_index(ny - 1, 2 * osample)] += lambda; /* Main diagonal */
1049
+
1050
+ /* Solve the system of equations */
1051
+ bandsol(l_Aij, l_bj, MAX_LAIJ_X, MAX_LAIJ_Y);
1052
+
1053
+ /* Normalize the slit function */
1054
+
1055
+ norm = 0.e0;
1056
+ for (iy = 0; iy < ny; iy++)
1057
+ {
1058
+ sL[sl_index(iy)] = l_bj[lbj_index(iy)];
1059
+ norm += sL[sl_index(iy)];
1060
+ }
1061
+ norm /= osample;
1062
+ for (iy = 0; iy < ny; iy++)
1063
+ sL[sl_index(iy)] /= norm;
1064
+
1065
+ /* Compute spectrum sP */
1066
+ for (x = 0; x < MAX_PBJ; x++)
1067
+ p_bj[pbj_index(x)] = 0;
1068
+ for (x = 0; x < MAX_PAIJ; x++)
1069
+ p_Aij[x] = 0;
1070
+
1071
+ for (x = 0; x < ncols; x++)
1072
+ {
1073
+ for (iy = 0; iy < ny; iy++)
1074
+ {
1075
+ for (n = 0; n < 4; n++)
1076
+ {
1077
+ ww = xi[xi_index(x, iy, n)].w;
1078
+ if (ww > 0)
1079
+ {
1080
+ xx = xi[xi_index(x, iy, n)].x;
1081
+ yy = xi[xi_index(x, iy, n)].y;
1082
+ if (xx >= 0 && xx < ncols && yy >= 0 && yy < nrows)
1083
+ {
1084
+ if (m_zeta[mzeta_index(xx, yy)] > 0)
1085
+ {
1086
+ for (m = 0; m < m_zeta[mzeta_index(xx, yy)]; m++)
1087
+ {
1088
+ xxx = zeta[zeta_index(xx, yy, m)].x;
1089
+ jy = zeta[zeta_index(xx, yy, m)].iy;
1090
+ www = zeta[zeta_index(xx, yy, m)].w;
1091
+ p_Aij[paij_index(x, xxx - x + 2 * delta_x)] += sL[sl_index(jy)] * sL[sl_index(iy)] * www * ww * mask[im_index(xx, yy)];
1092
+ }
1093
+ p_bj[pbj_index(x)] += im[im_index(xx, yy)] * mask[im_index(xx, yy)] * sL[sl_index(iy)] * ww;
1094
+ }
1095
+ }
1096
+ }
1097
+ }
1098
+ }
1099
+ }
1100
+
1101
+ if (lambda_sP > 0.e0)
1102
+ {
1103
+ norm = 0.e0;
1104
+ for (x = 0; x < ncols; x++)
1105
+ {
1106
+ norm += sP[sp_index(x)];
1107
+ }
1108
+ norm /= ncols;
1109
+ lambda = lambda_sP * norm; /* Scale regularization parameter */
1110
+
1111
+ p_Aij[paij_index(0, 2 * delta_x)] += lambda; /* Main diagonal */
1112
+ p_Aij[paij_index(0, 2 * delta_x + 1)] -= lambda; /* Upper diagonal */
1113
+ for (x = 1; x < ncols - 1; x++)
1114
+ {
1115
+ p_Aij[paij_index(x, 2 * delta_x - 1)] -= lambda; /* Lower diagonal */
1116
+ p_Aij[paij_index(x, 2 * delta_x)] += lambda * 2.e0; /* Main diagonal */
1117
+ p_Aij[paij_index(x, 2 * delta_x + 1)] -= lambda; /* Upper diagonal */
1118
+ }
1119
+ p_Aij[paij_index(ncols - 1, 2 * delta_x - 1)] -= lambda; /* Lower diagonal */
1120
+ p_Aij[paij_index(ncols - 1, 2 * delta_x)] += lambda; /* Main diagonal */
1121
+ }
1122
+
1123
+ /* Solve the system of equations */
1124
+ bandsol(p_Aij, p_bj, MAX_PAIJ_X, MAX_PAIJ_Y);
1125
+
1126
+ for (x = 0; x < ncols; x++)
1127
+ sP[sp_index(x)] = p_bj[pbj_index(x)];
1128
+
1129
+ /* Compute the model */
1130
+ for (x = 0; x < MAX_IM; x++)
1131
+ {
1132
+ model[x] = 0.;
1133
+ }
1134
+
1135
+ for (y = 0; y < nrows; y++)
1136
+ {
1137
+ for (x = 0; x < ncols; x++)
1138
+ {
1139
+ for (m = 0; m < m_zeta[mzeta_index(x, y)]; m++)
1140
+ {
1141
+ xx = zeta[zeta_index(x, y, m)].x;
1142
+ iy = zeta[zeta_index(x, y, m)].iy;
1143
+ ww = zeta[zeta_index(x, y, m)].w;
1144
+ model[im_index(x, y)] += sP[xx] * sL[iy] * ww;
1145
+ }
1146
+ }
1147
+ }
1148
+
1149
+ /* Compare model and data */
1150
+ // We use the Median absolute derivation to estimate the distribution
1151
+ // The MAD is more robust than the usual STD as it uses the median
1152
+ // However the MAD << STD, since we are not dealing with a Gaussian
1153
+ // at all, but a distribution with heavy wings.
1154
+ // Therefore we use the factor 40, instead of 6 to estimate a reasonable range
1155
+ // of values. The cutoff is roughly the same.
1156
+ // Technically the distribution might best be described by a Voigt profile
1157
+ // which we then would have to fit to the distrubtion and then determine,
1158
+ // the range that covers 99% of the data.
1159
+ // Since that is much more complicated we just use the MAD.
1160
+ cost = 0;
1161
+ isum = 0;
1162
+ for (y = 0; y < nrows; y++)
1163
+ {
1164
+ for (x = delta_x; x < ncols - delta_x; x++)
1165
+ {
1166
+ if (mask[im_index(x, y)])
1167
+ {
1168
+ tmp = model[im_index(x, y)] - im[im_index(x, y)];
1169
+ diff[isum] = tmp;
1170
+ tmp /= max(pix_unc[im_index(x, y)], 1);
1171
+ cost += tmp * tmp;
1172
+ isum++;
1173
+ }
1174
+ }
1175
+ }
1176
+ cost /= (isum - (ncols + ny));
1177
+ dev = median_absolute_deviation(diff, isum);
1178
+ // This is the "conversion" factor betweem MAD and STD
1179
+ // i.e. a perfect normal distribution has MAD = sqrt(2/pi) * STD
1180
+ dev *= 1.4826;
1181
+
1182
+ /* Adjust the mask marking outlyers */
1183
+ for (y = 0; y < nrows; y++)
1184
+ {
1185
+ for (x = delta_x; x < ncols - delta_x; x++)
1186
+ {
1187
+ // The MAD is significantly smaller than the STD was, since it describes
1188
+ // only the central peak, not the distribution
1189
+ // The factor 40 was chosen, since it is roughly equal to 6 * STD
1190
+ if (fabs(model[im_index(x, y)] - im[im_index(x, y)]) < 40. * dev)
1191
+ mask[im_index(x, y)] = 1;
1192
+ else
1193
+ mask[im_index(x, y)] = 0;
1194
+ }
1195
+ }
1196
+
1197
+ #if DEBUG
1198
+ if (cost == 0)
1199
+ {
1200
+ printf("Iteration: %i, Reduced chi-square: %f\n", iter, cost);
1201
+ printf("dev: %f\n", dev);
1202
+ printf("isum: %i\n", isum);
1203
+ printf("iteration: %i\n", iter);
1204
+ printf("-----------\n");
1205
+ }
1206
+ #endif
1207
+ /* Check for convergence */
1208
+ } while (((iter++ < maxiter) && (cost_old - cost > ftol)) || ((isfinite(cost) == 0) || ((isfinite(cost_old) == 0))));
1209
+
1210
+ if (iter >= maxiter - 1)
1211
+ {
1212
+ status = -1; // ran out of iterations
1213
+ success = 0;
1214
+ }
1215
+ else if (cost_old - cost <= ftol)
1216
+ status = 1; // cost did not improve enough between iterations
1217
+
1218
+ /* Uncertainty estimate */
1219
+
1220
+ for (x = 0; x < ncols; x++)
1221
+ {
1222
+ unc[sp_index(x)] = 0.;
1223
+ p_bj[pbj_index(x)] = 0.;
1224
+ p_Aij[paij_index(x, 0)] = 0;
1225
+ }
1226
+
1227
+ for (y = 0; y < nrows; y++)
1228
+ {
1229
+ for (x = 0; x < ncols; x++)
1230
+ {
1231
+ for (m = 0; m < m_zeta[mzeta_index(x, y)]; m++) // Loop through all pixels contributing to x,y
1232
+ {
1233
+ if (mask[im_index(x, y)])
1234
+ {
1235
+ // Should pix_unc contribute here?
1236
+ xx = zeta[zeta_index(x, y, m)].x;
1237
+ iy = zeta[zeta_index(x, y, m)].iy;
1238
+ ww = zeta[zeta_index(x, y, m)].w;
1239
+ tmp = im[im_index(x, y)] - model[im_index(x, y)];
1240
+ unc[sp_index(xx)] += tmp * tmp * ww;
1241
+ p_bj[pbj_index(xx)] += ww; // Norm
1242
+ p_Aij[paij_index(xx, 0)] += ww * ww; // Norm squared
1243
+ }
1244
+ }
1245
+ }
1246
+ }
1247
+
1248
+ for (x = 0; x < ncols; x++)
1249
+ {
1250
+ norm = p_bj[pbj_index(x)] - p_Aij[paij_index(x, 0)] / p_bj[pbj_index(x)];
1251
+ unc[sp_index(x)] = sqrt(unc[sp_index(x)] / norm * nrows);
1252
+ }
1253
+
1254
+ for (x = 0; x < delta_x; x++)
1255
+ {
1256
+ sP[sp_index(x)] = unc[sp_index(x)] = 0;
1257
+ }
1258
+ for (x = ncols - delta_x; x < ncols; x++)
1259
+ {
1260
+ sP[sp_index(x)] = unc[sp_index(x)] = 0;
1261
+ }
1262
+
1263
+ free(diff);
1264
+ free(l_Aij);
1265
+ free(p_Aij);
1266
+ free(p_bj);
1267
+ free(l_bj);
1268
+
1269
+ free(xi);
1270
+ free(zeta);
1271
+ free(m_zeta);
1272
+
1273
+ info[0] = success;
1274
+ info[1] = cost;
1275
+ info[2] = status;
1276
+ info[3] = iter;
1277
+ info[4] = delta_x;
1278
+
1279
+ return 0;
1280
+ }
1281
+
1282
+ int create_spectral_model(int ncols, int nrows, int osample, xi_ref* xi, double* spec, double* slitfunc, double* img){
1283
+ int ny, pix_x, pix_y, x, iy, m;
1284
+ double pix_w;
1285
+
1286
+ ny = (nrows + 1) * osample + 1;
1287
+
1288
+ for (x = 0; x < ncols; x++)
1289
+ {
1290
+ for (iy = 0; iy < nrows+1; iy++)
1291
+ {
1292
+ img[im_index(x, iy)] = 0;
1293
+ }
1294
+
1295
+ }
1296
+
1297
+ for (x = 0; x < ncols; x++)
1298
+ {
1299
+ for (iy = 0; iy < ny; iy++)
1300
+ {
1301
+ for (m = 0; m < 4; m++)
1302
+ {
1303
+ pix_x = xi[xi_index(x, iy, m)].x;
1304
+ pix_y = xi[xi_index(x, iy, m)].y;
1305
+ pix_w = xi[xi_index(x, iy, m)].w;
1306
+ if ((pix_x != -1) && (pix_y != -1) && (pix_w != 0)){
1307
+ img[im_index(pix_x, pix_y)] += pix_w * spec[x] * slitfunc[iy];
1308
+ }
1309
+ }
1310
+ }
1311
+ }
1312
+ return 0;
1313
+ }