pyimcom 1.2.1__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.
- pyimcom/__init__.py +1 -0
- pyimcom/_version.py +24 -0
- pyimcom/analysis.py +1480 -0
- pyimcom/coadd.py +2331 -0
- pyimcom/compress/__init__.py +0 -0
- pyimcom/compress/compressutils.py +506 -0
- pyimcom/compress/compressutils_wrapper.py +116 -0
- pyimcom/compress/i24.py +514 -0
- pyimcom/config.py +1245 -0
- pyimcom/diagnostics/__init__.py +0 -0
- pyimcom/diagnostics/context_figure.py +58 -0
- pyimcom/diagnostics/dynrange.py +274 -0
- pyimcom/diagnostics/layer_diagnostics.py +208 -0
- pyimcom/diagnostics/mosaicimage.py +80 -0
- pyimcom/diagnostics/noise/stability.py +126 -0
- pyimcom/diagnostics/noise_diagnostics.py +709 -0
- pyimcom/diagnostics/outimage_utils/__init__.py +0 -0
- pyimcom/diagnostics/outimage_utils/helper.py +82 -0
- pyimcom/diagnostics/report.py +366 -0
- pyimcom/diagnostics/run.py +64 -0
- pyimcom/diagnostics/starcube_nonoise.py +264 -0
- pyimcom/diagnostics/starcube_nonoise_coldescr.txt +24 -0
- pyimcom/diagnostics/stars.py +469 -0
- pyimcom/imdestripe.py +2454 -0
- pyimcom/lakernel.py +805 -0
- pyimcom/layer.py +1439 -0
- pyimcom/layer_wrapper.py +96 -0
- pyimcom/meta/__init__.py +0 -0
- pyimcom/meta/distortimage.py +748 -0
- pyimcom/meta/ginterp.py +340 -0
- pyimcom/pictures/__init__.py +0 -0
- pyimcom/pictures/genpic.py +229 -0
- pyimcom/psfutil.py +2199 -0
- pyimcom/routine.py +588 -0
- pyimcom/splitpsf/__init__.py +0 -0
- pyimcom/splitpsf/imsubtract.py +793 -0
- pyimcom/splitpsf/imsubtract_wrapper.py +107 -0
- pyimcom/splitpsf/splitpsf.py +497 -0
- pyimcom/splitpsf/splitpsf_wrapper.py +161 -0
- pyimcom/splitpsf/update_cube.py +136 -0
- pyimcom/truthcats.py +396 -0
- pyimcom/utils/__init__.py +0 -0
- pyimcom/utils/compareutils.py +207 -0
- pyimcom/utils/piffutils.py +223 -0
- pyimcom/wcsutil.py +839 -0
- pyimcom-1.2.1.dist-info/METADATA +67 -0
- pyimcom-1.2.1.dist-info/RECORD +52 -0
- pyimcom-1.2.1.dist-info/WHEEL +5 -0
- pyimcom-1.2.1.dist-info/licenses/LICENSE +21 -0
- pyimcom-1.2.1.dist-info/scm_file_list.json +285 -0
- pyimcom-1.2.1.dist-info/scm_version.json +8 -0
- pyimcom-1.2.1.dist-info/top_level.txt +1 -0
pyimcom/config.py
ADDED
|
@@ -0,0 +1,1245 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Encapsulation of pyimcom background settings and configuration.
|
|
3
|
+
|
|
4
|
+
Classes
|
|
5
|
+
-------
|
|
6
|
+
Timer
|
|
7
|
+
All-purpose timer.
|
|
8
|
+
Settings
|
|
9
|
+
pyimcom background settings.
|
|
10
|
+
fpaCoords
|
|
11
|
+
some basic data on the Roman FPA coordinates, needed for some of the tests.
|
|
12
|
+
Config
|
|
13
|
+
pyimcom configuration, with JSON file interface.
|
|
14
|
+
|
|
15
|
+
Functions
|
|
16
|
+
---------
|
|
17
|
+
format_axis
|
|
18
|
+
Format a panel (an axis) of a figure.
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
from importlib.resources import files
|
|
25
|
+
from time import perf_counter
|
|
26
|
+
|
|
27
|
+
import numpy as np
|
|
28
|
+
from astropy import units as u
|
|
29
|
+
from astropy.io import fits
|
|
30
|
+
|
|
31
|
+
JWST = os.environ.get("INSTRUMENT", "WFI") == "NIRCAM"
|
|
32
|
+
# This will be True if the environment variable INSTRUMENT is set to "NIRCAM", and False otherwise.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Timer:
|
|
36
|
+
"""
|
|
37
|
+
All-purpose timer.
|
|
38
|
+
|
|
39
|
+
Methods
|
|
40
|
+
-------
|
|
41
|
+
__init__
|
|
42
|
+
Constructor.
|
|
43
|
+
__call__
|
|
44
|
+
Return the time elapsed since tstart in seconds.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self) -> None:
|
|
49
|
+
self.tstart = perf_counter()
|
|
50
|
+
|
|
51
|
+
def __call__(self, reset: bool = False) -> float:
|
|
52
|
+
"""
|
|
53
|
+
Return the time elapsed since tstart in seconds.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
reset : bool, optional
|
|
58
|
+
Whether to reset tstart.
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
float
|
|
63
|
+
Time elapsed since tstart in seconds.
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
tnow = perf_counter()
|
|
68
|
+
tstart = self.tstart
|
|
69
|
+
if reset:
|
|
70
|
+
self.tstart = tnow
|
|
71
|
+
return tnow - tstart
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Settings:
|
|
75
|
+
"""
|
|
76
|
+
pyimcom background settings.
|
|
77
|
+
|
|
78
|
+
This class contains assorted Roman WFI data needed for the coadd code.
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
# which header in the input file contains the WCS information
|
|
83
|
+
hdu_with_wcs = "SCI"
|
|
84
|
+
|
|
85
|
+
degree = u.degree.to("rad") # == np.pi/180.0 == 0.017453292519943295
|
|
86
|
+
arcmin = u.arcmin.to("rad") # == degree/60.0 == 0.0002908882086657216
|
|
87
|
+
arcsec = u.arcsec.to("rad") # == arcmin/60.0 == 4.84813681109536e-06
|
|
88
|
+
|
|
89
|
+
# filter list
|
|
90
|
+
RomanFilters = ["W146", "F184", "H158", "J129", "Y106", "Z087", "R062", "PRSM", "DARK", "GRSM", "K213"]
|
|
91
|
+
QFilterNative = [1.155, 1.456, 1.250, 1.021, 0.834, 0.689, 0.491, 1.009, 0.000, 1.159, 1.685]
|
|
92
|
+
|
|
93
|
+
# linear obscuration of the telescope
|
|
94
|
+
obsc = 0.31
|
|
95
|
+
|
|
96
|
+
# SCA parameters
|
|
97
|
+
pixscale_native = 0.11 * arcsec
|
|
98
|
+
sca_nside = 4088 # excludes reference pixels
|
|
99
|
+
sca_ctrpix = (sca_nside - 1) / 2
|
|
100
|
+
sca_sidelength = sca_nside * pixscale_native
|
|
101
|
+
|
|
102
|
+
# SCA field of view centers
|
|
103
|
+
# SCAFov[i,j] = position of SCA #[i+1] (i=0..17) in j coordinate (j=0 for X, j=1 for Y)
|
|
104
|
+
# these are in 'WFI local' field angles, in degrees
|
|
105
|
+
# just for checking coverage since only 3 decimal places
|
|
106
|
+
SCAFov = np.asarray(
|
|
107
|
+
[
|
|
108
|
+
[-0.071, -0.037],
|
|
109
|
+
[-0.071, 0.109],
|
|
110
|
+
[-0.070, 0.240],
|
|
111
|
+
[-0.206, -0.064],
|
|
112
|
+
[-0.206, 0.083],
|
|
113
|
+
[-0.206, 0.213],
|
|
114
|
+
[-0.341, -0.129],
|
|
115
|
+
[-0.341, 0.018],
|
|
116
|
+
[-0.342, 0.147],
|
|
117
|
+
[0.071, -0.037],
|
|
118
|
+
[0.071, 0.109],
|
|
119
|
+
[0.070, 0.240],
|
|
120
|
+
[0.206, -0.064],
|
|
121
|
+
[0.206, 0.083],
|
|
122
|
+
[0.206, 0.213],
|
|
123
|
+
[0.341, -0.129],
|
|
124
|
+
[0.341, 0.018],
|
|
125
|
+
[0.342, 0.147],
|
|
126
|
+
]
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def jwst(cls):
|
|
131
|
+
"""
|
|
132
|
+
Method to modify the Settings object to have JWST NIRCam parameters
|
|
133
|
+
instead of Roman WFI parameters.
|
|
134
|
+
|
|
135
|
+
Note: Currently only includes those attributes that are used in imdestripe.py,
|
|
136
|
+
but more can be added as needed.
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
cls.sca_nside = 2048
|
|
141
|
+
nircam_short_bands = [
|
|
142
|
+
"F070W",
|
|
143
|
+
"F090W",
|
|
144
|
+
"F115W",
|
|
145
|
+
"F140M",
|
|
146
|
+
"F150W",
|
|
147
|
+
"F150W2",
|
|
148
|
+
"F162M",
|
|
149
|
+
"F164N",
|
|
150
|
+
"F182M",
|
|
151
|
+
"F187N",
|
|
152
|
+
"F200W",
|
|
153
|
+
"F210M",
|
|
154
|
+
"F212N",
|
|
155
|
+
]
|
|
156
|
+
nircam_long_bands = [
|
|
157
|
+
"F250M",
|
|
158
|
+
"F277W",
|
|
159
|
+
"F300M",
|
|
160
|
+
"F322W2",
|
|
161
|
+
"F323N",
|
|
162
|
+
"F335M",
|
|
163
|
+
"F356W",
|
|
164
|
+
"F360M",
|
|
165
|
+
"F405N",
|
|
166
|
+
"F410M",
|
|
167
|
+
"F430M",
|
|
168
|
+
"F444W",
|
|
169
|
+
"F460M",
|
|
170
|
+
"F466N",
|
|
171
|
+
"F470N",
|
|
172
|
+
"F480M",
|
|
173
|
+
]
|
|
174
|
+
# KL Leaving "Roman" so we don't have to change as much in the code
|
|
175
|
+
cls.RomanFilters = nircam_short_bands + nircam_long_bands
|
|
176
|
+
cls.pixscale_short_native = 0.031 * cls.arcsec
|
|
177
|
+
cls.pixscale_long_native = 0.062 * cls.arcsec
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class fpaCoords:
|
|
181
|
+
"""This contains some static data on the FPA coordinate system.
|
|
182
|
+
|
|
183
|
+
It also has some associated static methods.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
# focal plane coordinates of SCA centers, in mm
|
|
187
|
+
xfpa = np.array(
|
|
188
|
+
[
|
|
189
|
+
-22.14,
|
|
190
|
+
-22.29,
|
|
191
|
+
-22.44,
|
|
192
|
+
-66.42,
|
|
193
|
+
-66.92,
|
|
194
|
+
-67.42,
|
|
195
|
+
-110.70,
|
|
196
|
+
-111.48,
|
|
197
|
+
-112.64,
|
|
198
|
+
22.14,
|
|
199
|
+
22.29,
|
|
200
|
+
22.44,
|
|
201
|
+
66.42,
|
|
202
|
+
66.92,
|
|
203
|
+
67.42,
|
|
204
|
+
110.70,
|
|
205
|
+
111.48,
|
|
206
|
+
112.64,
|
|
207
|
+
]
|
|
208
|
+
)
|
|
209
|
+
yfpa = np.array(
|
|
210
|
+
[
|
|
211
|
+
12.15,
|
|
212
|
+
-37.03,
|
|
213
|
+
-82.06,
|
|
214
|
+
20.90,
|
|
215
|
+
-28.28,
|
|
216
|
+
-73.06,
|
|
217
|
+
42.20,
|
|
218
|
+
-6.98,
|
|
219
|
+
-51.06,
|
|
220
|
+
12.15,
|
|
221
|
+
-37.03,
|
|
222
|
+
-82.06,
|
|
223
|
+
20.90,
|
|
224
|
+
-28.28,
|
|
225
|
+
-73.06,
|
|
226
|
+
42.20,
|
|
227
|
+
-6.98,
|
|
228
|
+
-51.06,
|
|
229
|
+
]
|
|
230
|
+
)
|
|
231
|
+
# radius to circumscribe FPAs
|
|
232
|
+
Rfpa = 151.07129575137697
|
|
233
|
+
|
|
234
|
+
# orientation of SCAs
|
|
235
|
+
# -1 orient means SCA +x pointed along FPA -x, SCA +y pointed along FPA -y
|
|
236
|
+
# +1 orient means SCA +x pointed along FPA +x, SCA +y pointed along FPA +y (SCA #3,6,9,12,15,18)
|
|
237
|
+
sca_orient = np.array([-1, -1, 1, -1, -1, 1, -1, -1, 1, -1, -1, 1, -1, -1, 1, -1, -1, 1]).astype(np.int16)
|
|
238
|
+
|
|
239
|
+
pixsize = 0.01 # in mm
|
|
240
|
+
nside = 4088 # number of active pixels on a side
|
|
241
|
+
|
|
242
|
+
@classmethod
|
|
243
|
+
def pix2fpa(cls, sca, x, y):
|
|
244
|
+
"""Method to convert pixel (x,y) on a given sca to focal plane coordinates.
|
|
245
|
+
|
|
246
|
+
Inputs:
|
|
247
|
+
sca (in form 1..18)
|
|
248
|
+
x and y (in pixels)
|
|
249
|
+
sca, x, y may be scalars or arrays
|
|
250
|
+
|
|
251
|
+
Outputs:
|
|
252
|
+
xfpa, yfpa (in mm)
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
if np.amin(sca) < 1 or np.amax(sca) > 18:
|
|
256
|
+
raise ValueError(f"Invalid SCA in fpadata.pix2fpa, range={np.amin(sca):d},{np.amax(sca):d}")
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
cls.xfpa[sca - 1] + cls.pixsize * (x - (cls.nside - 1) / 2.0) * cls.sca_orient[sca - 1],
|
|
260
|
+
cls.yfpa[sca - 1] + cls.pixsize * (y - (cls.nside - 1) / 2.0) * cls.sca_orient[sca - 1],
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class Config:
|
|
265
|
+
"""
|
|
266
|
+
pyimcom configuration, with JSON file interface.
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
cfg_file : str or None, optional
|
|
271
|
+
File path to or text content of a JSON configuration file.
|
|
272
|
+
The default is ''. This uses pyimcom/configs/default_config.json.
|
|
273
|
+
Set cfg_file=None to build a configuration from scratch.
|
|
274
|
+
inmode : str or None, optional
|
|
275
|
+
Directives for special behavior. Right now the only one supported
|
|
276
|
+
is 'block' (meaning the configuration is read from a block output).
|
|
277
|
+
|
|
278
|
+
Methods
|
|
279
|
+
-------
|
|
280
|
+
__init__
|
|
281
|
+
Constructor.
|
|
282
|
+
__call__
|
|
283
|
+
Calculate or update derived quantities.
|
|
284
|
+
_from_dict
|
|
285
|
+
Build a configuration from a dictionary.
|
|
286
|
+
_get_attrs_wrapper
|
|
287
|
+
Wrapper for getting an attribute or a set of attributes.
|
|
288
|
+
_build_config
|
|
289
|
+
Terminal interface to build a configuration from scratch.
|
|
290
|
+
to_file
|
|
291
|
+
Save the configuration to a JSON file.
|
|
292
|
+
to_dict
|
|
293
|
+
Convert to a dictionary.
|
|
294
|
+
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
__slots__ = (
|
|
298
|
+
"cfg_file",
|
|
299
|
+
"NsideP",
|
|
300
|
+
"n1P",
|
|
301
|
+
"n2f", # __init__, __call__
|
|
302
|
+
"obsfile",
|
|
303
|
+
"inpath",
|
|
304
|
+
"informat",
|
|
305
|
+
"use_filter",
|
|
306
|
+
"inpsf_path",
|
|
307
|
+
"inpsf_format",
|
|
308
|
+
"inpsf_oversamp",
|
|
309
|
+
"psfsplit",
|
|
310
|
+
"psfsplit_r1",
|
|
311
|
+
"psfsplit_r2",
|
|
312
|
+
"psfsplit_epsilon", # SECTION I
|
|
313
|
+
"permanent_mask",
|
|
314
|
+
"cr_mask_rate",
|
|
315
|
+
"extrainput",
|
|
316
|
+
"n_inframe",
|
|
317
|
+
"labnoisethreshold", # SECTION II
|
|
318
|
+
"ra",
|
|
319
|
+
"dec",
|
|
320
|
+
"lonpole",
|
|
321
|
+
"nblock",
|
|
322
|
+
"n1",
|
|
323
|
+
"n2",
|
|
324
|
+
"dtheta",
|
|
325
|
+
"Nside", # SECTION III
|
|
326
|
+
"fade_kernel",
|
|
327
|
+
"postage_pad",
|
|
328
|
+
"pad_sides",
|
|
329
|
+
"stoptile", # SECTION IV
|
|
330
|
+
"outmaps",
|
|
331
|
+
"outstem",
|
|
332
|
+
"tempfile",
|
|
333
|
+
"inlayercache", # SECTION V
|
|
334
|
+
"n_out",
|
|
335
|
+
"outpsf",
|
|
336
|
+
"sigmatarget",
|
|
337
|
+
"outpsf_extra",
|
|
338
|
+
"sigmatarget_extra", # SECTION VI
|
|
339
|
+
"npixpsf",
|
|
340
|
+
"psf_circ",
|
|
341
|
+
"psf_norm",
|
|
342
|
+
"amp_penalty",
|
|
343
|
+
"flat_penalty",
|
|
344
|
+
"psf_interp",
|
|
345
|
+
"instamp_pad", # SECTION VII
|
|
346
|
+
"linear_algebra",
|
|
347
|
+
"iter_rtol",
|
|
348
|
+
"iter_max",
|
|
349
|
+
"no_qlt_ctrl",
|
|
350
|
+
"kappaC_arr",
|
|
351
|
+
"uctarget",
|
|
352
|
+
"sigmamax", # SECTION VIII
|
|
353
|
+
"ds_model",
|
|
354
|
+
"ds_rows",
|
|
355
|
+
"ds_outpath",
|
|
356
|
+
"ds_outstem",
|
|
357
|
+
"cg_model",
|
|
358
|
+
"cost_model",
|
|
359
|
+
"ds_obsfile",
|
|
360
|
+
"ds_noisefile",
|
|
361
|
+
"ds_restart",
|
|
362
|
+
"cost_prior",
|
|
363
|
+
"resid_model",
|
|
364
|
+
"hub_thresh",
|
|
365
|
+
"cg_maxiter",
|
|
366
|
+
"cg_tol",
|
|
367
|
+
"gaindir", # SECTION IX
|
|
368
|
+
"col_pars",
|
|
369
|
+
"amp_cols",
|
|
370
|
+
"col_boundary_const",
|
|
371
|
+
"tileschm",
|
|
372
|
+
"rerun",
|
|
373
|
+
"mosaic", # SECTION X
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
def __init__(self, cfg_file: str = "", inmode=None) -> None:
|
|
377
|
+
# option to load from a block output file
|
|
378
|
+
if inmode == "block":
|
|
379
|
+
with fits.open(cfg_file) as f:
|
|
380
|
+
c = f["CONFIG"].data["text"]
|
|
381
|
+
n = len(c)
|
|
382
|
+
cf = ""
|
|
383
|
+
for j in range(n):
|
|
384
|
+
cf += c[j] + "\n"
|
|
385
|
+
self._from_dict(json.loads(cf))
|
|
386
|
+
self()
|
|
387
|
+
return
|
|
388
|
+
|
|
389
|
+
self.cfg_file = cfg_file
|
|
390
|
+
if cfg_file is not None:
|
|
391
|
+
if cfg_file == "":
|
|
392
|
+
# print('> Using pyimcom/configs/default_config.json', flush=True)
|
|
393
|
+
self.cfg_file = files(__package__).joinpath("configs/default_config.json")
|
|
394
|
+
|
|
395
|
+
try:
|
|
396
|
+
with open(self.cfg_file) as f:
|
|
397
|
+
cfg_dict = json.load(f)
|
|
398
|
+
except (OSError, FileNotFoundError):
|
|
399
|
+
cfg_dict = json.loads(self.cfg_file)
|
|
400
|
+
self._from_dict(cfg_dict)
|
|
401
|
+
|
|
402
|
+
else:
|
|
403
|
+
self._build_config()
|
|
404
|
+
|
|
405
|
+
self()
|
|
406
|
+
|
|
407
|
+
def __call__(self) -> None:
|
|
408
|
+
"""Calculate or update derived quantities."""
|
|
409
|
+
|
|
410
|
+
### SECTION I: INPUT FILES ###
|
|
411
|
+
if self.psfsplit:
|
|
412
|
+
self.psfsplit_r1 = float(self.psfsplit[0])
|
|
413
|
+
self.psfsplit_r2 = float(self.psfsplit[1])
|
|
414
|
+
self.psfsplit_epsilon = float(self.psfsplit[2])
|
|
415
|
+
|
|
416
|
+
### SECTION II: MASKS AND LAYERS ###
|
|
417
|
+
self.n_inframe = len(self.extrainput)
|
|
418
|
+
|
|
419
|
+
### SECTION III: WHAT AREA TO COADD ###
|
|
420
|
+
self.Nside = self.n1 * self.n2
|
|
421
|
+
self.NsideP = self.Nside + self.postage_pad * self.n2 * 2
|
|
422
|
+
self.n1P = self.n1 + self.postage_pad * 2
|
|
423
|
+
self.n2f = self.n2 + self.fade_kernel * 2
|
|
424
|
+
|
|
425
|
+
### SECTION VIII: SOLVING LINEAR SYSTEMS ###
|
|
426
|
+
if self.linear_algebra == "Empirical":
|
|
427
|
+
self.outmaps = self.outmaps.replace("T", "")
|
|
428
|
+
if self.no_qlt_ctrl:
|
|
429
|
+
self.outmaps = self.outmaps.replace("U", "").replace("S", "")
|
|
430
|
+
elif "U" not in self.outmaps and "S" not in self.outmaps:
|
|
431
|
+
self.no_qlt_ctrl = True
|
|
432
|
+
|
|
433
|
+
if self.linear_algebra == "Empirical" or self.kappaC_arr.size == 1:
|
|
434
|
+
self.outmaps = self.outmaps.replace("K", "")
|
|
435
|
+
|
|
436
|
+
### SECTION IX: DESTRIPING PARAMS ###
|
|
437
|
+
if hasattr(self, "cost_model"):
|
|
438
|
+
if self.cost_model == "quadratic":
|
|
439
|
+
self.resid_model = "quad_prime"
|
|
440
|
+
elif self.cost_model == "absolute":
|
|
441
|
+
self.resid_model = "abs_prime"
|
|
442
|
+
elif self.cost_model == "huber_loss":
|
|
443
|
+
self.resid_model = "hub_prime"
|
|
444
|
+
|
|
445
|
+
def _from_dict(self, cfg_dict: dict) -> None:
|
|
446
|
+
"""
|
|
447
|
+
Build a configuration from a dictionary.
|
|
448
|
+
|
|
449
|
+
Parameters
|
|
450
|
+
----------
|
|
451
|
+
cfg_dict : dict
|
|
452
|
+
This is a dictionary, usually built from a JSON file.
|
|
453
|
+
|
|
454
|
+
Returns
|
|
455
|
+
-------
|
|
456
|
+
None
|
|
457
|
+
|
|
458
|
+
"""
|
|
459
|
+
|
|
460
|
+
### SECTION I: INPUT FILES ###
|
|
461
|
+
# input files
|
|
462
|
+
self.obsfile = cfg_dict["OBSFILE"]
|
|
463
|
+
self.inpath, self.informat = cfg_dict["INDATA"]
|
|
464
|
+
# which filter to make coadd
|
|
465
|
+
self.use_filter = cfg_dict["FILTER"]
|
|
466
|
+
# input PSF information
|
|
467
|
+
self.inpsf_path, self.inpsf_format, self.inpsf_oversamp = cfg_dict["INPSF"]
|
|
468
|
+
# if PSF splitting is used
|
|
469
|
+
self.psfsplit = cfg_dict.get("PSFSPLIT", "")
|
|
470
|
+
|
|
471
|
+
### SECTION II: MASKS AND LAYERS ###
|
|
472
|
+
# permanent mask file
|
|
473
|
+
self.permanent_mask = cfg_dict.get("PMASK")
|
|
474
|
+
# CR hit probability for stochastic mask
|
|
475
|
+
self.cr_mask_rate = cfg_dict.get("CMASK", 0.0)
|
|
476
|
+
# input images to stack at once
|
|
477
|
+
self.extrainput = [None] + cfg_dict.get("EXTRAINPUT", [])
|
|
478
|
+
# threshold for masking lab noise data
|
|
479
|
+
self.labnoisethreshold = cfg_dict.get("LABNOISETHRESHOLD", 3.0)
|
|
480
|
+
|
|
481
|
+
### SECTION III: WHAT AREA TO COADD ###
|
|
482
|
+
# tile center in degrees RA, DEC
|
|
483
|
+
self.ra, self.dec = cfg_dict["CTR"]
|
|
484
|
+
self.lonpole = float(cfg_dict.get("LONPOLE", 180.0))
|
|
485
|
+
# if we are doing a nblock x nblock array on the same projection
|
|
486
|
+
self.nblock = cfg_dict["BLOCK"]
|
|
487
|
+
# and output size: n1 (number of IMCOM postage stamps)
|
|
488
|
+
# n2 (size of single run), dtheta (arcsec)
|
|
489
|
+
# output array size will be (n1 x n2 x dtheta) on a side
|
|
490
|
+
# with padding, it is (n1 + 2*postage_pad) n2 x dtheta on a side
|
|
491
|
+
self.n1, self.n2, self.dtheta = cfg_dict["OUTSIZE"]
|
|
492
|
+
assert self.n1 % 2 == 0, "Error: n1 must be even since PSF computations are in 2x2 groups"
|
|
493
|
+
self.dtheta *= u.arcsec.to("degree")
|
|
494
|
+
|
|
495
|
+
### SECTION IV: MORE ABOUT POSTAGE STAMPS ###
|
|
496
|
+
# fading kernel width
|
|
497
|
+
self.fade_kernel = cfg_dict.get("FADE", 3)
|
|
498
|
+
# pad this many IMCOM postage stamps around the edge
|
|
499
|
+
self.postage_pad = cfg_dict.get("PAD", 0)
|
|
500
|
+
# according to the strategy or on the sides specified by the user
|
|
501
|
+
self.pad_sides = cfg_dict.get("PADSIDES", "auto")
|
|
502
|
+
# stop bulding the tile after a certain number of postage stamps
|
|
503
|
+
self.stoptile = cfg_dict.get("STOP", 0)
|
|
504
|
+
|
|
505
|
+
### SECTION V: WHAT AND WHERE TO OUTPUT ###
|
|
506
|
+
# choose which outputs to report
|
|
507
|
+
self.outmaps = cfg_dict.get("OUTMAPS", "USKTN")
|
|
508
|
+
# output stem
|
|
509
|
+
self.outstem = cfg_dict["OUT"]
|
|
510
|
+
# temporary storage
|
|
511
|
+
# virtual memory will be used if this is not empty str
|
|
512
|
+
self.tempfile = cfg_dict.get("TEMPFILE", "")
|
|
513
|
+
if not self.tempfile:
|
|
514
|
+
self.tempfile = None
|
|
515
|
+
# cache input layers here
|
|
516
|
+
self.inlayercache = cfg_dict.get("INLAYERCACHE", "")
|
|
517
|
+
if not self.inlayercache:
|
|
518
|
+
self.inlayercache = None
|
|
519
|
+
|
|
520
|
+
### SECTION VI: TARGET OUTPUT PSF(S) ###
|
|
521
|
+
# number of target output PSF(s)
|
|
522
|
+
self.n_out = cfg_dict.get("NOUT", 1)
|
|
523
|
+
# target output PSF type
|
|
524
|
+
self.outpsf = cfg_dict.get("OUTPSF", "AIRYOBSC")
|
|
525
|
+
# target output PSF extra smearing
|
|
526
|
+
self.sigmatarget = cfg_dict.get("EXTRASMOOTH", 1.5 / 2.355)
|
|
527
|
+
|
|
528
|
+
if self.n_out > 1: # more than one target output PSF
|
|
529
|
+
self.outpsf_extra = []
|
|
530
|
+
self.sigmatarget_extra = []
|
|
531
|
+
for j_out in range(1, self.n_out):
|
|
532
|
+
self.outpsf_extra.append(cfg_dict.get(f"OUTPSF{j_out + 1}", "AIRYOBSC"))
|
|
533
|
+
self.sigmatarget_extra.append(cfg_dict.get(f"EXTRASMOOTH{j_out + 1}", 1.5 / 2.355))
|
|
534
|
+
|
|
535
|
+
### SECTION VII: BUILDING LINEAR SYSTEMS ###
|
|
536
|
+
# width of PSF sampling/overlap arrays in native pixels
|
|
537
|
+
self.npixpsf = cfg_dict.get("NPIXPSF", 48)
|
|
538
|
+
# experimental feature to apply a circular cutout to PSFs
|
|
539
|
+
self.psf_circ = cfg_dict.get("PSFCIRC", False)
|
|
540
|
+
# experimental feature to normalize PSFs after sampling
|
|
541
|
+
self.psf_norm = cfg_dict.get("PSFNORM", False)
|
|
542
|
+
|
|
543
|
+
# experimental feature to change the weighting of Fourier modes
|
|
544
|
+
self.amp_penalty = cfg_dict.get("AMPPEN", (0.0, 0.0))
|
|
545
|
+
# amount by which to penalize having different contributions
|
|
546
|
+
# to the output from different input images
|
|
547
|
+
self.flat_penalty = cfg_dict.get("FLATPEN", 0.0)
|
|
548
|
+
# PSF interpolators
|
|
549
|
+
self.psf_interp = cfg_dict.get("PSFINTERP", "D5512")
|
|
550
|
+
# input stamp size padding (aka acceptance radius)
|
|
551
|
+
self.instamp_pad = cfg_dict.get("INPAD", 1.055) * Settings.arcsec
|
|
552
|
+
|
|
553
|
+
### SECTION VIII: SOLVING LINEAR SYSTEMS ###
|
|
554
|
+
# kernel to solve linear systems
|
|
555
|
+
self.linear_algebra = cfg_dict.get("LAKERNEL", "Cholesky")
|
|
556
|
+
if self.linear_algebra == "Iterative":
|
|
557
|
+
# relative tolerance and maximum number of iterations
|
|
558
|
+
self.iter_rtol = cfg_dict.get("ITERRTOL", 1.5e-3)
|
|
559
|
+
self.iter_max = cfg_dict.get("ITERMAX", 30)
|
|
560
|
+
elif self.linear_algebra == "Empirical":
|
|
561
|
+
# no-quality control option
|
|
562
|
+
self.no_qlt_ctrl = cfg_dict.get("EMPIRNQC", False)
|
|
563
|
+
|
|
564
|
+
### SECTION IX: DESTRIPING PARAMS ###
|
|
565
|
+
self.ds_model, self.ds_rows = cfg_dict.get("DSMODEL", [None, None])
|
|
566
|
+
self.ds_outpath, self.ds_outstem = cfg_dict.get("DSOUT", [None, None])
|
|
567
|
+
self.cg_model, self.cg_maxiter, self.cg_tol = cfg_dict.get("CGMODEL", [None, None, None])
|
|
568
|
+
self.cost_model, self.cost_prior, self.hub_thresh = cfg_dict.get("DSCOST", [None, None, None])
|
|
569
|
+
self.ds_obsfile = cfg_dict.get("DSOBSFILE")
|
|
570
|
+
self.ds_noisefile = cfg_dict.get("DSNOISEFILE", False)
|
|
571
|
+
self.ds_restart = cfg_dict.get("DSRESTART")
|
|
572
|
+
self.gaindir = cfg_dict.get("GAINDIR", False)
|
|
573
|
+
self.col_pars = cfg_dict.get("AMPCOLS", [None, 0.0])
|
|
574
|
+
self.amp_cols = self.col_pars[0]
|
|
575
|
+
self.col_boundary_const = self.col_pars[1]
|
|
576
|
+
|
|
577
|
+
# Lagrange multiplier (kappa) information
|
|
578
|
+
# list of kappa/C values, ascending order
|
|
579
|
+
self.kappaC_arr = np.array(cfg_dict.get("KAPPAC", [1e-5, 1e-4, 1e-3]))
|
|
580
|
+
# target (minimum) leakage
|
|
581
|
+
self.uctarget = cfg_dict.get("UCMIN", 1e-6)
|
|
582
|
+
# maximum allowed value of Sigma
|
|
583
|
+
self.sigmamax = cfg_dict.get("SMAX", 0.5)
|
|
584
|
+
|
|
585
|
+
### SECTION IX: PASS THROUGHS ###
|
|
586
|
+
self.tileschm = cfg_dict.get("TILESCHM", "Not_specified")
|
|
587
|
+
self.rerun = cfg_dict.get("RERUN", "Not_specified")
|
|
588
|
+
self.mosaic = cfg_dict.get("MOSAIC", -1)
|
|
589
|
+
|
|
590
|
+
cfg_dict.clear()
|
|
591
|
+
del cfg_dict
|
|
592
|
+
|
|
593
|
+
def _get_attrs_wrapper(self, code: str, newline: bool = True) -> None:
|
|
594
|
+
"""
|
|
595
|
+
Wrapper for getting an attribute or a set of attributes.
|
|
596
|
+
|
|
597
|
+
Parameters
|
|
598
|
+
----------
|
|
599
|
+
code : str
|
|
600
|
+
Code segment to execute.
|
|
601
|
+
newline : bool, optional
|
|
602
|
+
Whether to print a blank line when finished.
|
|
603
|
+
|
|
604
|
+
Returns
|
|
605
|
+
-------
|
|
606
|
+
None
|
|
607
|
+
|
|
608
|
+
"""
|
|
609
|
+
|
|
610
|
+
while True:
|
|
611
|
+
try:
|
|
612
|
+
exec(code)
|
|
613
|
+
except ValueError as error:
|
|
614
|
+
print(error)
|
|
615
|
+
print("# Invalid input, please try again.", flush=True)
|
|
616
|
+
else:
|
|
617
|
+
break
|
|
618
|
+
if newline:
|
|
619
|
+
print()
|
|
620
|
+
|
|
621
|
+
def _build_config(self) -> None:
|
|
622
|
+
"""
|
|
623
|
+
Terminal interface to build a configuration from scratch.
|
|
624
|
+
|
|
625
|
+
The prompts are based on comments in old text configuration files.
|
|
626
|
+
assert statements for further validity checks to be added.
|
|
627
|
+
|
|
628
|
+
Returns
|
|
629
|
+
-------
|
|
630
|
+
None
|
|
631
|
+
|
|
632
|
+
"""
|
|
633
|
+
|
|
634
|
+
print("### GENERAL NOTE: INPUT NOTHING TO USE DEFAULT ###\n", flush=True)
|
|
635
|
+
|
|
636
|
+
print("### SECTION I: INPUT FILES ###\n", flush=True)
|
|
637
|
+
# input files: OBSFILE, INDATA, FILTER, INPSF, PSFSPLIT
|
|
638
|
+
|
|
639
|
+
print("# input observation list", flush=True)
|
|
640
|
+
self._get_attrs_wrapper("self.obsfile = input('OBSFILE (str): ')")
|
|
641
|
+
|
|
642
|
+
print(
|
|
643
|
+
"# reference input file directory and naming convention\n# (including the WCS used for stacking)",
|
|
644
|
+
flush=True,
|
|
645
|
+
)
|
|
646
|
+
self._get_attrs_wrapper("self.inpath, self.informat = input('INDATA (str str): ').split(' ')")
|
|
647
|
+
|
|
648
|
+
print("# which filter", flush=True)
|
|
649
|
+
self._get_attrs_wrapper("self.use_filter = int(input('FILTER (int): '))")
|
|
650
|
+
|
|
651
|
+
print("# input PSF files & format & oversamp", flush=True)
|
|
652
|
+
self._get_attrs_wrapper(
|
|
653
|
+
"self.inpsf_path, self.inpsf_format, OVERSAMP = input('INPSF (str str int): ').split(' ')"
|
|
654
|
+
"\n"
|
|
655
|
+
"self.inpsf_oversamp = int(OVERSAMP)"
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
print("# PSF splitting", flush=True)
|
|
659
|
+
self._get_attrs_wrapper(
|
|
660
|
+
"self.psfsplit_r1, self.psfsplit_r2, self.psfsplit_epsilon = input('PSFSPLIT (float float float) "
|
|
661
|
+
"[default: no split]: ').split(' ')"
|
|
662
|
+
"\n"
|
|
663
|
+
"self.psfsplit = [self.psfsplit_r1, self.psfsplit_r2, self.psfsplit_epsilon] if self.psfsplit_r1 "
|
|
664
|
+
"else ''"
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
print("### SECTION II: MASKS AND LAYERS ###\n", flush=True)
|
|
668
|
+
# masks and layers: PMASK, CMASK, EXTRAINPUT, LABNOISETHRESHOLD
|
|
669
|
+
|
|
670
|
+
print(
|
|
671
|
+
"# mask options:"
|
|
672
|
+
"\n"
|
|
673
|
+
"# PMASK --> permanent mask (from file)"
|
|
674
|
+
"\n"
|
|
675
|
+
"# default: no permanent pixel mask"
|
|
676
|
+
"\n"
|
|
677
|
+
"# CMASK --> cosmic ray hit probability for stochastic mask",
|
|
678
|
+
flush=True,
|
|
679
|
+
)
|
|
680
|
+
self._get_attrs_wrapper(
|
|
681
|
+
"PMASK = input('PMASK (str) [default: None]: ')\nself.permanent_mask = PMASK if PMASK else None",
|
|
682
|
+
newline=False,
|
|
683
|
+
)
|
|
684
|
+
self._get_attrs_wrapper(
|
|
685
|
+
"CMASK = input('CMASK (float) [default: 0.0]: ')"
|
|
686
|
+
"\n"
|
|
687
|
+
"self.cr_mask_rate = float(CMASK) if CMASK else 0.0"
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
print(
|
|
691
|
+
"# extra inputs (input images to stack at once)"
|
|
692
|
+
"\n"
|
|
693
|
+
"# (use names for each one, space-delimited; meaning of names must be coded into"
|
|
694
|
+
"\n"
|
|
695
|
+
"# layer.get_all_data, with the meaning based on the naming convention in INDATA)",
|
|
696
|
+
flush=True,
|
|
697
|
+
)
|
|
698
|
+
self._get_attrs_wrapper(
|
|
699
|
+
"EXTRAINPUT = input('EXTRAINPUT (str str ...) [default: None]: ')"
|
|
700
|
+
"\n"
|
|
701
|
+
"self.extrainput = [None] + (EXTRAINPUT.split() if EXTRAINPUT else [])"
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
print(
|
|
705
|
+
"# mask out pixels with lab noise beyond this threshold"
|
|
706
|
+
"\n"
|
|
707
|
+
"# (ignored if labnoise is not in EXTRAINPUT or does not exist)",
|
|
708
|
+
flush=True,
|
|
709
|
+
)
|
|
710
|
+
self._get_attrs_wrapper(
|
|
711
|
+
"LABNOISETHRESHOLD = input('LABNOISETHRESHOLD (float) [default: 3.0]: ')"
|
|
712
|
+
"\n"
|
|
713
|
+
"self.labnoisethreshold = float(LABNOISETHRESHOLD) if LABNOISETHRESHOLD else 3.0"
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
print("### SECTION III: WHAT AREA TO COADD ###\n", flush=True)
|
|
717
|
+
# what area to coadd: CTR, BLOCK, OUTSIZE
|
|
718
|
+
|
|
719
|
+
print("# location of the output region to make", flush=True)
|
|
720
|
+
self._get_attrs_wrapper(
|
|
721
|
+
"self.ra, self.dec = map(float, input('CTR (float float): ').split(' '))", newline=False
|
|
722
|
+
)
|
|
723
|
+
self._get_attrs_wrapper(
|
|
724
|
+
"self.lonpole = map(float, input('LONPOLE (float): ').split(' '))", newline=False
|
|
725
|
+
)
|
|
726
|
+
self._get_attrs_wrapper("self.nblock = int(input('BLOCK (int): '))", newline=False)
|
|
727
|
+
self._get_attrs_wrapper(
|
|
728
|
+
"self.n1, self.n2, self.dtheta = map(eval, input('OUTSIZE (int int float): ').split(' '))"
|
|
729
|
+
"\n"
|
|
730
|
+
"assert self.n1 % 2 == 0, 'Error: n1 must be even since PSF computations are in 2x2 groups'"
|
|
731
|
+
"\n"
|
|
732
|
+
"self.dtheta *= u.arcsec.to('degree')"
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
print("### SECTION IV: MORE ABOUT POSTAGE STAMPS ###\n", flush=True)
|
|
736
|
+
# more about postage stamps: FADE, PAD, PADSIDES, STOP
|
|
737
|
+
|
|
738
|
+
print(
|
|
739
|
+
"# fading kernel width (number of rows or columns"
|
|
740
|
+
"\n"
|
|
741
|
+
"# of transition pixels on each side of a postage stamp)",
|
|
742
|
+
flush=True,
|
|
743
|
+
)
|
|
744
|
+
self._get_attrs_wrapper(
|
|
745
|
+
"FADE = input('FADE (int) [default: 3]: ')"
|
|
746
|
+
"\n"
|
|
747
|
+
"self.fade_kernel = int(FADE) if FADE else 3"
|
|
748
|
+
"\n"
|
|
749
|
+
"assert self.n2 > self.fade_kernel * 2, 'insufficient patch size'"
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
print("# number of IMCOM postage stamps to pad around each output region", flush=True)
|
|
753
|
+
self._get_attrs_wrapper(
|
|
754
|
+
"PAD = input('PAD (int) [default: 0]: ')\nself.postage_pad = int(PAD) if PAD else 0"
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
print(
|
|
758
|
+
"# on which side(s) to pad IMCOM postage stamps"
|
|
759
|
+
"\n"
|
|
760
|
+
'# "all": pad on all sides;'
|
|
761
|
+
"\n"
|
|
762
|
+
'# "auto": pad on mosaic boundaries only;'
|
|
763
|
+
"\n"
|
|
764
|
+
'# "none": pad on none of the sides;'
|
|
765
|
+
"\n"
|
|
766
|
+
"# otherwise, please specify which side(s) to pad on"
|
|
767
|
+
"\n"
|
|
768
|
+
"# using CAPITAL letters (the order does not matter)"
|
|
769
|
+
"\n"
|
|
770
|
+
'# "B" (bottom), "T" (top), "L" (left), and "R" (right)',
|
|
771
|
+
flush=True,
|
|
772
|
+
)
|
|
773
|
+
self._get_attrs_wrapper(
|
|
774
|
+
"PADSIDES = input('PADSIDES (str) [default: \"auto\"]: ')"
|
|
775
|
+
"\n"
|
|
776
|
+
"self.pad_sides = PADSIDES if PADSIDES else 'auto'"
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
print(
|
|
780
|
+
"# stop execution after a certain number of postage stamps"
|
|
781
|
+
"\n"
|
|
782
|
+
"# (for testing so we don't have to wait for all the postage stamps)"
|
|
783
|
+
"\n"
|
|
784
|
+
"# good choices: 16 to see if it runs; 624 to get a portion of the image without using lots of "
|
|
785
|
+
"time"
|
|
786
|
+
"\n"
|
|
787
|
+
"# default: don't stop until we get to the end",
|
|
788
|
+
flush=True,
|
|
789
|
+
)
|
|
790
|
+
self._get_attrs_wrapper(
|
|
791
|
+
"STOP = input('STOP (int) [default: 0]: ')\nself.stoptile = int(STOP) if STOP else 0"
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
print("### SECTION V: WHAT AND WHERE TO OUTPUT ###\n", flush=True)
|
|
795
|
+
# what and where to output: OUTMAPS, OUT, TEMPFILE, INLAYERCACHE
|
|
796
|
+
|
|
797
|
+
print(
|
|
798
|
+
"# choose which outputs to report"
|
|
799
|
+
"\n"
|
|
800
|
+
"# U = PSF leakage map (U_alpha/C)"
|
|
801
|
+
"\n"
|
|
802
|
+
"# S = noise map"
|
|
803
|
+
"\n"
|
|
804
|
+
"# K = kappa (Lagrange multiplier map)"
|
|
805
|
+
"\n"
|
|
806
|
+
"# T = total weight (sum over all input pixels)"
|
|
807
|
+
"\n"
|
|
808
|
+
"# N = effective coverage",
|
|
809
|
+
flush=True,
|
|
810
|
+
)
|
|
811
|
+
self._get_attrs_wrapper(
|
|
812
|
+
"OUTMAPS = input('OUTMAPS (str) [default: \"USKTN\"]: ')"
|
|
813
|
+
"\n"
|
|
814
|
+
"self.outmaps = OUTMAPS if OUTMAPS else 'USKTN'"
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
print("# output location:\n# set to something in your directory", flush=True)
|
|
818
|
+
self._get_attrs_wrapper("self.outstem = input('OUT (str): ')")
|
|
819
|
+
|
|
820
|
+
print("# temporary storage location (prefix):\n# not to use virtual memory", flush=True)
|
|
821
|
+
self._get_attrs_wrapper(
|
|
822
|
+
"TEMPFILE = input('TEMPFILE (str) [default: '']: ')\nself.tempfile = TEMPFILE",
|
|
823
|
+
newline=False,
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
print("# input layer cache (prefix):", flush=True)
|
|
827
|
+
self._get_attrs_wrapper(
|
|
828
|
+
"INLAYERCACHE = input('INLAYERCACHE (str) [default: '']: ')\nself.inlayercache = INLAYERCACHE",
|
|
829
|
+
newline=False,
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
print("### SECTION VI: TARGET OUTPUT PSF(S) ###\n", flush=True)
|
|
833
|
+
# target output PSF(s): NOUT, OUTPSF, EXTRASMOOTH
|
|
834
|
+
# (optional: OUTPSF2, EXTRASMOOTH2, etc.)
|
|
835
|
+
|
|
836
|
+
print("# number of target output PSF(s)", flush=True)
|
|
837
|
+
self._get_attrs_wrapper(
|
|
838
|
+
"NOUT = input('NOUT (int) [default: 1]: ')"
|
|
839
|
+
"\n"
|
|
840
|
+
"self.n_out = (int(NOUT) if NOUT else 1)"
|
|
841
|
+
"\n"
|
|
842
|
+
"assert self.n_out >= 1, 'NOUT should be at least 1'"
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
print(
|
|
846
|
+
"# target output PSF type, options include"
|
|
847
|
+
"\n"
|
|
848
|
+
'# "GAUSSIAN": simple Gaussian'
|
|
849
|
+
"\n"
|
|
850
|
+
'# "AIRYOBSC": obscured Airy disk convolved with Gaussian'
|
|
851
|
+
"\n"
|
|
852
|
+
'# "AIRYUNOBSC": unobscured Airy disk convolved with Gaussian',
|
|
853
|
+
flush=True,
|
|
854
|
+
)
|
|
855
|
+
self._get_attrs_wrapper(
|
|
856
|
+
"OUTPSF = input('OUTPSF (str) [default: \"AIRYOBSC\"]: ')"
|
|
857
|
+
"\n"
|
|
858
|
+
"assert OUTPSF in ['', 'GAUSSIAN', 'AIRYOBSC', 'AIRYUNOBSC'], 'unrecognized type'"
|
|
859
|
+
"\n"
|
|
860
|
+
"self.outpsf = OUTPSF if OUTPSF else 'AIRYOBSC'"
|
|
861
|
+
)
|
|
862
|
+
|
|
863
|
+
print(
|
|
864
|
+
"# smoothing of output PSF (units: input pixels, 1 sigma)"
|
|
865
|
+
"\n"
|
|
866
|
+
"# default: FWHM Gaussian smoothing divided by 2.355 to be a sigma",
|
|
867
|
+
flush=True,
|
|
868
|
+
)
|
|
869
|
+
self._get_attrs_wrapper(
|
|
870
|
+
"EXTRASMOOTH = input('EXTRASMOOTH (float) [default: 1.5 / 2.355]: ')"
|
|
871
|
+
"\n"
|
|
872
|
+
"self.sigmatarget = float(EXTRASMOOTH) if EXTRASMOOTH else (1.5 / 2.355)"
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
if self.n_out > 1: # more than one target output PSF
|
|
876
|
+
self.outpsf_extra = []
|
|
877
|
+
self.sigmatarget_extra = []
|
|
878
|
+
for j_out in range(1, self.n_out):
|
|
879
|
+
print(f"# now talking about target output PSF {j_out + 1}:\n# output PSF type", flush=True)
|
|
880
|
+
self._get_attrs_wrapper(
|
|
881
|
+
f"OUTPSF{j_out + 1} = input('OUTPSF{j_out + 1} (str) [default: \"AIRYOBSC\"]: ')"
|
|
882
|
+
"\n"
|
|
883
|
+
f"assert OUTPSF{j_out + 1} in ['', 'GAUSSIAN', 'AIRYOBSC', 'AIRYUNOBSC'], "
|
|
884
|
+
"'unrecognized type'"
|
|
885
|
+
"\n"
|
|
886
|
+
f"self.outpsf_extra.append(OUTPSF{j_out + 1} if OUTPSF{j_out + 1} else 'AIRYOBSC')"
|
|
887
|
+
)
|
|
888
|
+
print("# smoothing of output PSF", flush=True)
|
|
889
|
+
self._get_attrs_wrapper(
|
|
890
|
+
f"EXTRASMOOTH{j_out + 1} = input('EXTRASMOOTH{j_out + 1} (float) "
|
|
891
|
+
"[default: 1.5 / 2.355]: ')"
|
|
892
|
+
"\n"
|
|
893
|
+
f"self.sigmatarget_extra.append(float(EXTRASMOOTH{j_out + 1}) if EXTRASMOOTH{j_out + 1} "
|
|
894
|
+
"else (1.5 / 2.355))"
|
|
895
|
+
)
|
|
896
|
+
|
|
897
|
+
print("### SECTION VII: BUILDING LINEAR SYSTEMS ###\n", flush=True)
|
|
898
|
+
# building linear systems: NPIXPSF, AMPPEN, FLATPEN, INPAD
|
|
899
|
+
|
|
900
|
+
# print('# size of PSF postage stamp in native pixels')
|
|
901
|
+
print(
|
|
902
|
+
"# width of PSF sampling/overlap arrays in native pixels"
|
|
903
|
+
"\n"
|
|
904
|
+
"# preferably a nice number for FFT purposes",
|
|
905
|
+
flush=True,
|
|
906
|
+
)
|
|
907
|
+
self._get_attrs_wrapper(
|
|
908
|
+
"NPIXPSF = input('NPIXPSF (int) [default: 48]: ')"
|
|
909
|
+
"\n"
|
|
910
|
+
"self.npixpsf = (int(NPIXPSF) if NPIXPSF else 48)"
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
print("# whether to apply a circular cutout to PSFs", flush=True)
|
|
914
|
+
self._get_attrs_wrapper(
|
|
915
|
+
"PSFCIRC = input('PSFCIRC (bool) [default: False]: ')"
|
|
916
|
+
"\n"
|
|
917
|
+
"self.psf_circ = (bool(eval(PSFCIRC)) if PSFCIRC else False)"
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
print("# whether to normalize PSFs after sampling", flush=True)
|
|
921
|
+
self._get_attrs_wrapper(
|
|
922
|
+
"PSFNORM = input('PSFNORM (bool) [default: False]: ')"
|
|
923
|
+
"\n"
|
|
924
|
+
"self.psf_norm = (bool(eval(PSFNORM)) if PSFNORM else False)"
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
print(
|
|
928
|
+
"# experimental feature to change the weighting of Fourier modes"
|
|
929
|
+
"\n"
|
|
930
|
+
"# format: (amp, sig), where amp is the amplitude,"
|
|
931
|
+
"\n"
|
|
932
|
+
"# sig is the width in units of input pixels",
|
|
933
|
+
flush=True,
|
|
934
|
+
)
|
|
935
|
+
self._get_attrs_wrapper(
|
|
936
|
+
"AMPPEN = input('AMPPEN (float float) [default: (0.0, 0.0)]: ')"
|
|
937
|
+
"\n"
|
|
938
|
+
"self.amp_penalty = map(float, AMPPEN.split(' ')) if AMPPEN else (0.0, 0.0)"
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
print(
|
|
942
|
+
"# amount by which to penalize having different contributions"
|
|
943
|
+
"\n"
|
|
944
|
+
"# to the output from different input images",
|
|
945
|
+
flush=True,
|
|
946
|
+
)
|
|
947
|
+
self._get_attrs_wrapper(
|
|
948
|
+
"FLATPEN = input('FLATPEN (float) [default: 0.0]: ')"
|
|
949
|
+
"\n"
|
|
950
|
+
"self.flat_penalty = (float(FLATPEN) if FLATPEN else 0.0)"
|
|
951
|
+
)
|
|
952
|
+
self._get_attrs_wrapper(
|
|
953
|
+
"PSFINTERP = input('PSFINTERP (str) [default: \"D5512\"]: ')"
|
|
954
|
+
"\n"
|
|
955
|
+
"self.psf_interp = PSFINTERP if PSFINTERP else 'D5512'"
|
|
956
|
+
)
|
|
957
|
+
|
|
958
|
+
print("# input stamp size padding (aka acceptance radius) in arcsec", flush=True)
|
|
959
|
+
self._get_attrs_wrapper(
|
|
960
|
+
"INPAD = input('INPAD (float) [default: 1.055]: ')"
|
|
961
|
+
"\n"
|
|
962
|
+
"self.instamp_pad = (float(INPAD) if INPAD else 1.055) * Settings.arcsec"
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
print("### SECTION VIII: SOLVING LINEAR SYSTEMS ###\n", flush=True)
|
|
966
|
+
# solving linear systems: LAKERNEL, KAPPAC, UCMIN, SMAX
|
|
967
|
+
|
|
968
|
+
print(
|
|
969
|
+
"# kernel to solve linear systems, options include"
|
|
970
|
+
"\n"
|
|
971
|
+
'# "Eigen": kernel using eigendecomposition'
|
|
972
|
+
"\n"
|
|
973
|
+
'# "Cholesky": kernel using Cholesky decomposition'
|
|
974
|
+
"\n"
|
|
975
|
+
'# "Iterative": kernel using iterative method'
|
|
976
|
+
"\n"
|
|
977
|
+
'# "Empirical": kernel using empirical method',
|
|
978
|
+
flush=True,
|
|
979
|
+
)
|
|
980
|
+
self._get_attrs_wrapper(
|
|
981
|
+
"LAKERNEL = input('LAKERNEL (str) [default: \"Cholesky\"]: ')"
|
|
982
|
+
"\n"
|
|
983
|
+
"self.linear_algebra = LAKERNEL if LAKERNEL else 'Cholesky'"
|
|
984
|
+
"\n"
|
|
985
|
+
"assert self.linear_algebra in ['Eigen', 'Cholesky', 'Iterative', 'Empirical'], "
|
|
986
|
+
"'unrecognized kernel'"
|
|
987
|
+
)
|
|
988
|
+
|
|
989
|
+
if self.linear_algebra == "Iterative":
|
|
990
|
+
print("# Iterative kernel: relative tolerance", flush=True)
|
|
991
|
+
self._get_attrs_wrapper(
|
|
992
|
+
"ITERRTOL = input('ITERRTOL (float) [default: 1.5e-3]: ')"
|
|
993
|
+
"\n"
|
|
994
|
+
"self.iter_rtol = (float(ITERRTOL) if ITERRTOL else 1.5e-3)"
|
|
995
|
+
)
|
|
996
|
+
print("# Iterative kernel: maximum number of iterations", flush=True)
|
|
997
|
+
self._get_attrs_wrapper(
|
|
998
|
+
"ITERMAX = input('ITERMAX (int) [default: 30]: ')"
|
|
999
|
+
"\n"
|
|
1000
|
+
"self.iter_max = (int(ITERMAX) if ITERMAX else 30)"
|
|
1001
|
+
)
|
|
1002
|
+
|
|
1003
|
+
elif self.linear_algebra == "Empirical":
|
|
1004
|
+
print(
|
|
1005
|
+
"# Empirical kernel: no-quality control option"
|
|
1006
|
+
"\n"
|
|
1007
|
+
"# coadd images without computing system matrices A and B"
|
|
1008
|
+
"\n"
|
|
1009
|
+
'# "U" and "S" will be automatically removed from OUTMAPS;'
|
|
1010
|
+
"\n"
|
|
1011
|
+
'# automatically set to true if neither "U" nor "S" is in OUTMAPS',
|
|
1012
|
+
flush=True,
|
|
1013
|
+
)
|
|
1014
|
+
self._get_attrs_wrapper(
|
|
1015
|
+
"EMPIRNQC = input('EMPIRNQC (bool) [default: False]: ')"
|
|
1016
|
+
"\n"
|
|
1017
|
+
"self.no_qlt_ctrl = (bool(eval(EMPIRNQC)) if EMPIRNQC else False)"
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
print(
|
|
1021
|
+
"# Lagrange multiplier (kappa) information"
|
|
1022
|
+
"\n"
|
|
1023
|
+
"# list of kappa/C values, ascending order"
|
|
1024
|
+
"\n"
|
|
1025
|
+
'# if LAKERNEL == "Empirical", only the first kappa/C value is used;'
|
|
1026
|
+
"\n"
|
|
1027
|
+
"# otherwise if single value: use this fixed kappa/C value;"
|
|
1028
|
+
"\n"
|
|
1029
|
+
'# if multiple values: "Eigen" performs a bisection search'
|
|
1030
|
+
"\n"
|
|
1031
|
+
'# between the first and last kappa/C values, while "Cholesky"'
|
|
1032
|
+
"\n"
|
|
1033
|
+
'# and "Iterative" kernels use these kappa/C values as nodes',
|
|
1034
|
+
flush=True,
|
|
1035
|
+
)
|
|
1036
|
+
self._get_attrs_wrapper(
|
|
1037
|
+
"KAPPAC = input('KAPPAC (float ...) [default: [1e-5, 1e-4, 1e-3]]: ')"
|
|
1038
|
+
"\n"
|
|
1039
|
+
"self.kappaC_arr = np.array(list(map(float, KAPPAC.split(' '))) if KAPPAC else "
|
|
1040
|
+
"[1e-5, 1e-4, 1e-3])"
|
|
1041
|
+
"\n"
|
|
1042
|
+
"assert np.all(np.diff(self.kappaC_arr) > 0.0), 'must be in ascending order'"
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
print("# target (minimum) leakage", flush=True)
|
|
1046
|
+
self._get_attrs_wrapper(
|
|
1047
|
+
"UCMIN = input('UCMIN (float) [default: 1e-6]: ')"
|
|
1048
|
+
"\n"
|
|
1049
|
+
"self.uctarget = float(UCMIN) if UCMIN else 1e-6"
|
|
1050
|
+
)
|
|
1051
|
+
|
|
1052
|
+
print("# maximum allowed value of Sigma", flush=True)
|
|
1053
|
+
self._get_attrs_wrapper(
|
|
1054
|
+
"SMAX = input('SMAX (float) [default: 0.5]: ')\nself.sigmamax = float(SMAX) if SMAX else 0.5"
|
|
1055
|
+
)
|
|
1056
|
+
|
|
1057
|
+
print("### SECTION IX: PASS THROUGHS ###\n", flush=True)
|
|
1058
|
+
# pass throughs: TILESCHM, RERUN, MOSAIC
|
|
1059
|
+
print("# tiling scheme name", flush=True)
|
|
1060
|
+
self._get_attrs_wrapper(
|
|
1061
|
+
"TILESCHM = input('TILESCHM (str) [default: \"Not_specified\"]: ')"
|
|
1062
|
+
"\n"
|
|
1063
|
+
"self.tileschm = TILESCHM if TILESCHM else 'Not_specified'"
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
print("# rerun name", flush=True)
|
|
1067
|
+
self._get_attrs_wrapper(
|
|
1068
|
+
"RERUN = input('RERUN (str) [default: \"Not_specified\"]: ')"
|
|
1069
|
+
"\n"
|
|
1070
|
+
"self.rerun = RERUN if RERUN else 'Not_specified'"
|
|
1071
|
+
)
|
|
1072
|
+
|
|
1073
|
+
print("# mosaic index", flush=True)
|
|
1074
|
+
self._get_attrs_wrapper(
|
|
1075
|
+
"MOSAIC = input('MOSAIC (int) [default: \"-1\"]: ')"
|
|
1076
|
+
"\n"
|
|
1077
|
+
"self.mosaic = int(MOSAIC) if MOSAIC else -1"
|
|
1078
|
+
)
|
|
1079
|
+
|
|
1080
|
+
print("# To save this configuration, call Config.to_file.\n", flush=True)
|
|
1081
|
+
|
|
1082
|
+
def to_file(self, fname: str = "") -> None:
|
|
1083
|
+
"""
|
|
1084
|
+
Save the configuration to a JSON file.
|
|
1085
|
+
|
|
1086
|
+
Parameters
|
|
1087
|
+
----------
|
|
1088
|
+
fname : str or None, optional
|
|
1089
|
+
JSON configuration file name to save to.
|
|
1090
|
+
The default is ''. This overwrites pyimcom/configs/default_config.json.
|
|
1091
|
+
Set fname=None to get a text version of the configuration.
|
|
1092
|
+
|
|
1093
|
+
Returns
|
|
1094
|
+
-------
|
|
1095
|
+
str or None
|
|
1096
|
+
Text version of the configuration.
|
|
1097
|
+
|
|
1098
|
+
"""
|
|
1099
|
+
|
|
1100
|
+
cfg_dict = {}
|
|
1101
|
+
|
|
1102
|
+
### SECTION I: INPUT FILES ###
|
|
1103
|
+
cfg_dict["OBSFILE"] = self.obsfile
|
|
1104
|
+
cfg_dict["INDATA"] = [self.inpath, self.informat]
|
|
1105
|
+
cfg_dict["FILTER"] = self.use_filter
|
|
1106
|
+
cfg_dict["INPSF"] = [self.inpsf_path, self.inpsf_format, self.inpsf_oversamp]
|
|
1107
|
+
if self.psfsplit:
|
|
1108
|
+
cfg_dict["PSFSPLIT"] = [self.psfsplit_r1, self.psfsplit_r2, self.psfsplit_epsilon]
|
|
1109
|
+
|
|
1110
|
+
### SECTION II: MASKS AND LAYERS ###
|
|
1111
|
+
cfg_dict["PMASK"] = self.permanent_mask
|
|
1112
|
+
cfg_dict["CMASK"] = self.cr_mask_rate
|
|
1113
|
+
cfg_dict["EXTRAINPUT"] = self.extrainput[1:]
|
|
1114
|
+
cfg_dict["LABNOISETHRESHOLD"] = self.labnoisethreshold
|
|
1115
|
+
|
|
1116
|
+
### SECTION III: WHAT AREA TO COADD ###
|
|
1117
|
+
cfg_dict["CTR"] = [self.ra, self.dec]
|
|
1118
|
+
cfg_dict["LONPOLE"] = self.lonpole
|
|
1119
|
+
cfg_dict["BLOCK"] = self.nblock
|
|
1120
|
+
cfg_dict["OUTSIZE"] = [self.n1, self.n2, self.dtheta * u.degree.to("arcsec")]
|
|
1121
|
+
|
|
1122
|
+
### SECTION IV: MORE ABOUT POSTAGE STAMPS ###
|
|
1123
|
+
cfg_dict["FADE"] = self.fade_kernel
|
|
1124
|
+
cfg_dict["PAD"] = self.postage_pad
|
|
1125
|
+
cfg_dict["PADSIDES"] = self.pad_sides
|
|
1126
|
+
cfg_dict["STOP"] = self.stoptile
|
|
1127
|
+
|
|
1128
|
+
### SECTION V: WHAT AND WHERE TO OUTPUT ###
|
|
1129
|
+
cfg_dict["OUTMAPS"] = self.outmaps
|
|
1130
|
+
cfg_dict["OUT"] = self.outstem
|
|
1131
|
+
cfg_dict["TEMPFILE"] = self.tempfile if self.tempfile else ""
|
|
1132
|
+
cfg_dict["INLAYERCACHE"] = self.inlayercache if self.inlayercache else ""
|
|
1133
|
+
|
|
1134
|
+
### SECTION VI: TARGET OUTPUT PSF(S) ###
|
|
1135
|
+
cfg_dict["NOUT"] = self.n_out
|
|
1136
|
+
cfg_dict["OUTPSF"] = self.outpsf
|
|
1137
|
+
cfg_dict["EXTRASMOOTH"] = self.sigmatarget
|
|
1138
|
+
if self.n_out > 1: # more than one target output PSF
|
|
1139
|
+
for j_out in range(1, self.n_out):
|
|
1140
|
+
cfg_dict[f"OUTPSF{j_out + 1}"] = self.outpsf_extra[j_out - 1]
|
|
1141
|
+
cfg_dict[f"EXTRASMOOTH{j_out + 1}"] = self.sigmatarget_extra[j_out - 1]
|
|
1142
|
+
|
|
1143
|
+
### SECTION VII: BUILDING LINEAR SYSTEMS ###
|
|
1144
|
+
cfg_dict["NPIXPSF"] = self.npixpsf
|
|
1145
|
+
cfg_dict["PSFCIRC"] = self.psf_circ
|
|
1146
|
+
cfg_dict["PSFNORM"] = self.psf_norm
|
|
1147
|
+
|
|
1148
|
+
cfg_dict["AMPPEN"] = self.amp_penalty
|
|
1149
|
+
cfg_dict["FLATPEN"] = self.flat_penalty
|
|
1150
|
+
cfg_dict["PSFINTERP"] = self.psf_interp
|
|
1151
|
+
cfg_dict["INPAD"] = self.instamp_pad / Settings.arcsec
|
|
1152
|
+
|
|
1153
|
+
### SECTION VIII: SOLVING LINEAR SYSTEMS ###
|
|
1154
|
+
cfg_dict["LAKERNEL"] = self.linear_algebra
|
|
1155
|
+
if self.linear_algebra == "Iterative":
|
|
1156
|
+
cfg_dict["ITERRTOL"] = self.iter_rtol
|
|
1157
|
+
cfg_dict["ITERMAX"] = self.iter_max
|
|
1158
|
+
elif self.linear_algebra == "Empirical":
|
|
1159
|
+
cfg_dict["EMPIRNQC"] = self.no_qlt_ctrl
|
|
1160
|
+
|
|
1161
|
+
cfg_dict["KAPPAC"] = list(self.kappaC_arr)
|
|
1162
|
+
cfg_dict["UCMIN"] = self.uctarget
|
|
1163
|
+
cfg_dict["SMAX"] = self.sigmamax
|
|
1164
|
+
|
|
1165
|
+
### SECTION IX: PASS THROUGHS ###
|
|
1166
|
+
cfg_dict["TILESCHM"] = self.tileschm
|
|
1167
|
+
cfg_dict["RERUN"] = self.rerun
|
|
1168
|
+
cfg_dict["MOSAIC"] = self.mosaic
|
|
1169
|
+
|
|
1170
|
+
if fname is not None:
|
|
1171
|
+
if fname == "":
|
|
1172
|
+
print("> Overwriting pyimcom/configs/default_config.json", flush=True)
|
|
1173
|
+
fname = files(__package__).joinpath("configs/default_config.json")
|
|
1174
|
+
|
|
1175
|
+
with open(fname, "w") as f:
|
|
1176
|
+
json.dump(cfg_dict, f, indent=4)
|
|
1177
|
+
cfg_dict.clear()
|
|
1178
|
+
del cfg_dict
|
|
1179
|
+
|
|
1180
|
+
else: # return text version of the configuration
|
|
1181
|
+
res = json.dumps(cfg_dict, indent=4)
|
|
1182
|
+
cfg_dict.clear()
|
|
1183
|
+
del cfg_dict
|
|
1184
|
+
return res
|
|
1185
|
+
|
|
1186
|
+
def to_dict(self):
|
|
1187
|
+
"""
|
|
1188
|
+
Save the configuration as a dictionary.
|
|
1189
|
+
|
|
1190
|
+
Parameters
|
|
1191
|
+
----------
|
|
1192
|
+
None
|
|
1193
|
+
|
|
1194
|
+
Returns
|
|
1195
|
+
-------
|
|
1196
|
+
dict
|
|
1197
|
+
Dictionary version of the configuration.
|
|
1198
|
+
|
|
1199
|
+
"""
|
|
1200
|
+
return json.loads(self.to_file(None))
|
|
1201
|
+
|
|
1202
|
+
|
|
1203
|
+
# Parameters for format_axis.
|
|
1204
|
+
# Can be imported for context via
|
|
1205
|
+
# with mpl.rc_context(config.format_axis_pars):
|
|
1206
|
+
format_axis_pars = {
|
|
1207
|
+
"font.family": "serif",
|
|
1208
|
+
"mathtext.fontset": "dejavuserif",
|
|
1209
|
+
"font.size": 12,
|
|
1210
|
+
"text.latex.preamble": r"\usepackage{amsmath}",
|
|
1211
|
+
"xtick.major.pad": 2,
|
|
1212
|
+
"ytick.major.pad": 2,
|
|
1213
|
+
"xtick.major.size": 6,
|
|
1214
|
+
"ytick.major.size": 6,
|
|
1215
|
+
"xtick.minor.size": 3,
|
|
1216
|
+
"ytick.minor.size": 3,
|
|
1217
|
+
"axes.linewidth": 2,
|
|
1218
|
+
"axes.labelpad": 1,
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
def format_axis(ax, grid_on=True):
|
|
1223
|
+
"""
|
|
1224
|
+
Format a panel (an axis) of a figure.
|
|
1225
|
+
|
|
1226
|
+
Parameters
|
|
1227
|
+
----------
|
|
1228
|
+
ax : mpl.axes._axes.Axes
|
|
1229
|
+
Panel to be formatted.
|
|
1230
|
+
grid_on : bool, optional
|
|
1231
|
+
Whether to add grid to the panel.
|
|
1232
|
+
|
|
1233
|
+
Returns
|
|
1234
|
+
-------
|
|
1235
|
+
None
|
|
1236
|
+
|
|
1237
|
+
"""
|
|
1238
|
+
|
|
1239
|
+
ax.minorticks_on()
|
|
1240
|
+
if grid_on:
|
|
1241
|
+
ax.grid(visible=True, which="major", linestyle=":")
|
|
1242
|
+
ax.tick_params(axis="both", which="both", direction="out")
|
|
1243
|
+
ax.xaxis.set_ticks_position("both")
|
|
1244
|
+
ax.yaxis.set_ticks_position("both")
|
|
1245
|
+
ax.patch.set_alpha(0.0)
|