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
|
File without changes
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context manager for writing report figures.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ReportFigContext:
|
|
7
|
+
"""
|
|
8
|
+
This is a context manager for report figures in PyIMCOM.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
mpl : class
|
|
13
|
+
The matplotlib module.
|
|
14
|
+
plt : class
|
|
15
|
+
The pyplot submodule.
|
|
16
|
+
|
|
17
|
+
Attributes
|
|
18
|
+
----------
|
|
19
|
+
mpl : class
|
|
20
|
+
The matplotlib module.
|
|
21
|
+
plt : class
|
|
22
|
+
The pyplot submodule.
|
|
23
|
+
env_backend : str
|
|
24
|
+
The matplotlib backend in the environment.
|
|
25
|
+
usetex : bool or None
|
|
26
|
+
Enviroment self.usetex setting.
|
|
27
|
+
|
|
28
|
+
Methods
|
|
29
|
+
-------
|
|
30
|
+
__init__
|
|
31
|
+
Constructor.
|
|
32
|
+
__enter__
|
|
33
|
+
Set up local configuration.
|
|
34
|
+
__exit__
|
|
35
|
+
Restore old settings.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, mpl, plt):
|
|
40
|
+
self.mpl = mpl
|
|
41
|
+
self.plt = plt
|
|
42
|
+
|
|
43
|
+
def __enter__(self):
|
|
44
|
+
self.env_backend = self.mpl.get_backend()
|
|
45
|
+
self.usetex = self.plt.rcParams.get("text.usetex", None)
|
|
46
|
+
|
|
47
|
+
self.mpl.use("Agg")
|
|
48
|
+
self.plt.switch_backend("Agg")
|
|
49
|
+
self.plt.rcParams["text.usetex"] = True
|
|
50
|
+
|
|
51
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
52
|
+
self.mpl.use(self.env_backend)
|
|
53
|
+
self.plt.switch_backend(self.env_backend)
|
|
54
|
+
if self.usetex is None:
|
|
55
|
+
if hasattr(self.plt.rcParams, "text.usetex"): # this will always be true
|
|
56
|
+
del self.plt.rcParams["text.usetex"]
|
|
57
|
+
else:
|
|
58
|
+
self.plt.rcParams["text.usetex"] = self.usetex
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Code to construct dynamic range estimates from block files.
|
|
3
|
+
|
|
4
|
+
Functions
|
|
5
|
+
---------
|
|
6
|
+
gen_dynrange_data
|
|
7
|
+
Generates dynamic range data and writes to files.
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import re
|
|
13
|
+
import sys
|
|
14
|
+
import warnings
|
|
15
|
+
from os.path import exists
|
|
16
|
+
|
|
17
|
+
import healpy
|
|
18
|
+
import numpy as np
|
|
19
|
+
from astropy import wcs
|
|
20
|
+
|
|
21
|
+
from ..compress.compressutils import ReadFile
|
|
22
|
+
from .outimage_utils.helper import HDU_to_bels
|
|
23
|
+
|
|
24
|
+
nscale = 1
|
|
25
|
+
# nscale=10 # this line is for bug compensation --- will remove it later
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def gen_dynrange_data(inpath, outstem, rpix_try=50, nblockmax=100):
|
|
29
|
+
"""Takes files from inpath and writes histograms to outstem.
|
|
30
|
+
inpath should be a function that takes ix and iy and returns a file name
|
|
31
|
+
|
|
32
|
+
Optional input parameters:
|
|
33
|
+
rpix_try : int = radius over which to compute profiles
|
|
34
|
+
(needs to be an integer less than the padding, will truncate)
|
|
35
|
+
nblockmax : maximum block number to consider (default 100, only need to
|
|
36
|
+
reduce this if you want to make a report generate faster for testing)
|
|
37
|
+
|
|
38
|
+
The output files are:
|
|
39
|
+
|
|
40
|
+
Histograms:
|
|
41
|
+
outstem +'_sqrtS_hist.dat': histogram of noise amplification factor sqrtS
|
|
42
|
+
outstem +'_neff_hist.dat': histogram of effective exposure number
|
|
43
|
+
Both of these have a header that indicates the fraction of data that is off scale high.
|
|
44
|
+
|
|
45
|
+
dynamic range file:
|
|
46
|
+
outstem +'_dynrange.dat': table of percentiles of noisy star images
|
|
47
|
+
(Columns are radius and [1,5,25,50,75,95,99] percentiles)
|
|
48
|
+
|
|
49
|
+
Returns a dictionary of which files were successfully generated:
|
|
50
|
+
output[key] = filename (if successful), None (if not successful)
|
|
51
|
+
Current file keys: 'SQRTS', 'NEFF', 'DYNRANGE'
|
|
52
|
+
Header information is in keys 'SQRTS_HEADER' and 'NEFF_HEADER'
|
|
53
|
+
Number of blocks read is in 'COUNTBLOCK'
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# initialization of output
|
|
58
|
+
output = {"SQRTS": None, "NEFF": None, "DYNRANGE": None, "COUNTBLOCK": 0}
|
|
59
|
+
|
|
60
|
+
# initialize table of pixel values
|
|
61
|
+
vals = []
|
|
62
|
+
for _ in range(rpix_try):
|
|
63
|
+
vals += [np.zeros((0,), dtype=np.float32)]
|
|
64
|
+
|
|
65
|
+
is_first = True
|
|
66
|
+
|
|
67
|
+
# histogram initialization
|
|
68
|
+
N_noise = 100
|
|
69
|
+
d_noise = 0.02
|
|
70
|
+
countnoise = np.zeros((N_noise, 2))
|
|
71
|
+
countnoise[:, 0] = d_noise * np.linspace(0.5, N_noise - 0.5, N_noise)
|
|
72
|
+
tnoise = 0.0
|
|
73
|
+
tnoise_gt = 0.0
|
|
74
|
+
N_neff = 100
|
|
75
|
+
d_neff = 0.1
|
|
76
|
+
countneff = np.zeros((N_neff, 2))
|
|
77
|
+
countneff[:, 0] = d_neff * np.linspace(0.5, N_neff - 0.5, N_neff)
|
|
78
|
+
tneff = 0.0
|
|
79
|
+
tneff_gt = 0.0
|
|
80
|
+
|
|
81
|
+
# now loop over the blocks
|
|
82
|
+
for iby in range(nblockmax):
|
|
83
|
+
for ibx in range(nblockmax):
|
|
84
|
+
# get the input file (if it exists -- otherwise keep going)
|
|
85
|
+
try:
|
|
86
|
+
infile = inpath(ibx, iby)
|
|
87
|
+
if not exists(infile):
|
|
88
|
+
continue
|
|
89
|
+
except (FileNotFoundError, ValueError):
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
# if this is the first block we find, get the configuration file
|
|
93
|
+
if is_first:
|
|
94
|
+
is_first = False
|
|
95
|
+
config = ""
|
|
96
|
+
with ReadFile(infile) as f:
|
|
97
|
+
for g in f["CONFIG"].data["text"].tolist():
|
|
98
|
+
config += g + " "
|
|
99
|
+
configStruct = json.loads(config)
|
|
100
|
+
|
|
101
|
+
blocksize = (
|
|
102
|
+
int(configStruct["OUTSIZE"][0])
|
|
103
|
+
* int(configStruct["OUTSIZE"][1])
|
|
104
|
+
* float(configStruct["OUTSIZE"][2])
|
|
105
|
+
/ 3600.0
|
|
106
|
+
* np.pi
|
|
107
|
+
/ 180
|
|
108
|
+
) # radians
|
|
109
|
+
rs = 1.5 * blocksize / np.sqrt(2.0) # search radius
|
|
110
|
+
|
|
111
|
+
# padding region around the edge
|
|
112
|
+
bd = int(configStruct["OUTSIZE"][1]) * int(configStruct["PAD"])
|
|
113
|
+
rpix = min(rpix_try, bd - 1)
|
|
114
|
+
|
|
115
|
+
# figure out which layer we want
|
|
116
|
+
layers = [""] + configStruct["EXTRAINPUT"]
|
|
117
|
+
framenumber = 0
|
|
118
|
+
res = 9
|
|
119
|
+
nstarlayer = {}
|
|
120
|
+
for i in range(len(layers))[::-1]:
|
|
121
|
+
m = re.match(r"^nstar(\d+),([^,]+),([^,]+),([^,]+)$", layers[i])
|
|
122
|
+
if m:
|
|
123
|
+
framenumber = i
|
|
124
|
+
res = int(m.group(1))
|
|
125
|
+
nstarlayer = {
|
|
126
|
+
"RESOLUTION": res,
|
|
127
|
+
"FLUX": float(m.group(2)),
|
|
128
|
+
"BACKGROUND": float(m.group(3)),
|
|
129
|
+
"SEED": int(m.group(4)),
|
|
130
|
+
}
|
|
131
|
+
print("# using layer", framenumber, "resolution", res)
|
|
132
|
+
print("# rs=", rs)
|
|
133
|
+
|
|
134
|
+
# now we know this file exists
|
|
135
|
+
with ReadFile(infile, layers=[framenumber]) as f:
|
|
136
|
+
n = np.shape(f[0].data)[-1]
|
|
137
|
+
mywcs = wcs.WCS(f[0].header)
|
|
138
|
+
starmap = f[0].data[0, framenumber, :, :]
|
|
139
|
+
|
|
140
|
+
# now extract histogram information
|
|
141
|
+
try:
|
|
142
|
+
sigma_ = 10 ** (
|
|
143
|
+
0.5 * HDU_to_bels(f["SIGMA"]) * f["SIGMA"].data[0, bd : n - bd, bd : n - bd]
|
|
144
|
+
) # noise standard deviation in units of input noise
|
|
145
|
+
for j in range(N_noise):
|
|
146
|
+
countnoise[j, 1] = countnoise[j, 1] + np.count_nonzero(
|
|
147
|
+
np.logical_and(sigma_ / d_noise >= j, sigma_ / d_noise < j + 1)
|
|
148
|
+
)
|
|
149
|
+
tnoise = tnoise + np.size(sigma_)
|
|
150
|
+
tnoise_gt = tnoise_gt + np.count_nonzero(sigma_ >= d_noise * N_noise)
|
|
151
|
+
except KeyError:
|
|
152
|
+
warnings.warn("No valid noise frame: " + infile)
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
neff_ = 10 ** (
|
|
156
|
+
HDU_to_bels(f["EFFCOVER"]) * f["EFFCOVER"].data[0, bd : n - bd, bd : n - bd] * nscale
|
|
157
|
+
) # effective coverage
|
|
158
|
+
for j in range(N_neff):
|
|
159
|
+
countneff[j, 1] = countneff[j, 1] + np.count_nonzero(
|
|
160
|
+
np.logical_and(neff_ / d_neff >= j, neff_ / d_neff < j + 1)
|
|
161
|
+
)
|
|
162
|
+
tneff = tneff + np.size(neff_)
|
|
163
|
+
tneff_gt = tneff_gt + np.count_nonzero(neff_ >= d_neff * N_neff)
|
|
164
|
+
except KeyError:
|
|
165
|
+
warnings.warn("No valid coverage frame: " + infile)
|
|
166
|
+
|
|
167
|
+
# identify which HEALpix positions we have
|
|
168
|
+
ra_cent, dec_cent = mywcs.all_pix2world(
|
|
169
|
+
[(n - 1) / 2], [(n - 1) / 2], [0.0], [0.0], 0, ra_dec_order=True
|
|
170
|
+
)
|
|
171
|
+
ra_cent = ra_cent[0]
|
|
172
|
+
dec_cent = dec_cent[0]
|
|
173
|
+
vec = healpy.ang2vec(ra_cent, dec_cent, lonlat=True)
|
|
174
|
+
qp = healpy.query_disc(2**res, vec, rs, nest=False)
|
|
175
|
+
ra_hpix, dec_hpix = healpy.pix2ang(2**res, qp, nest=False, lonlat=True)
|
|
176
|
+
npix = len(ra_hpix)
|
|
177
|
+
x, y, z1, z2 = mywcs.all_world2pix(ra_hpix, dec_hpix, np.zeros((npix,)), np.zeros((npix,)), 0)
|
|
178
|
+
xi = np.rint(x).astype(np.int16)
|
|
179
|
+
yi = np.rint(y).astype(np.int16)
|
|
180
|
+
grp = np.where(
|
|
181
|
+
np.logical_and(
|
|
182
|
+
np.logical_and(xi >= bd, xi < n - bd), np.logical_and(yi >= bd, yi < n - bd)
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
ra_hpix = ra_hpix[grp]
|
|
186
|
+
dec_hpix = dec_hpix[grp]
|
|
187
|
+
x = x[grp]
|
|
188
|
+
xi = xi[grp]
|
|
189
|
+
y = y[grp]
|
|
190
|
+
yi = yi[grp]
|
|
191
|
+
npix = len(x)
|
|
192
|
+
|
|
193
|
+
print("# read grid position:", (ibx, iby), n, "number of HEALPix pixels =", npix)
|
|
194
|
+
# complain if it didn't find a star
|
|
195
|
+
for ipix in range(npix):
|
|
196
|
+
if starmap[yi[ipix], xi[ipix]] < 1000:
|
|
197
|
+
print(
|
|
198
|
+
"block",
|
|
199
|
+
ibx,
|
|
200
|
+
iby,
|
|
201
|
+
"star",
|
|
202
|
+
ipix,
|
|
203
|
+
"pos",
|
|
204
|
+
xi[ipix],
|
|
205
|
+
yi[ipix],
|
|
206
|
+
"val",
|
|
207
|
+
starmap[yi[ipix], xi[ipix]],
|
|
208
|
+
)
|
|
209
|
+
sys.stdout.flush()
|
|
210
|
+
|
|
211
|
+
# extract profile around each object
|
|
212
|
+
x_, y_ = np.meshgrid(range(n), range(n))
|
|
213
|
+
tempvals = [None] * rpix
|
|
214
|
+
for j in range(rpix):
|
|
215
|
+
tempvals[j] = np.zeros((0,), dtype=np.float32)
|
|
216
|
+
for ipix in range(npix):
|
|
217
|
+
xmin = np.clip(np.floor(x[ipix]).astype(np.int16) - rpix - 1, 0, n)
|
|
218
|
+
xmax = np.clip(np.ceil(x[ipix]).astype(np.int16) + rpix + 1, 0, n)
|
|
219
|
+
ymin = np.clip(np.floor(y[ipix]).astype(np.int16) - rpix - 1, 0, n)
|
|
220
|
+
ymax = np.clip(np.ceil(y[ipix]).astype(np.int16) + rpix + 1, 0, n)
|
|
221
|
+
r = np.floor(
|
|
222
|
+
np.sqrt(
|
|
223
|
+
(x_[ymin:ymax, xmin:xmax] - x[ipix]) ** 2
|
|
224
|
+
+ (y_[ymin:ymax, xmin:xmax] - y[ipix]) ** 2
|
|
225
|
+
)
|
|
226
|
+
).astype(np.int16)
|
|
227
|
+
for j in range(rpix):
|
|
228
|
+
tempvals[j] = np.concatenate((tempvals[j], starmap[ymin:ymax, xmin:xmax][r == j]))
|
|
229
|
+
for j in range(rpix):
|
|
230
|
+
vals[j] = np.concatenate((vals[j], tempvals[j]))
|
|
231
|
+
|
|
232
|
+
output["COUNTBLOCK"] += 1
|
|
233
|
+
|
|
234
|
+
outst = ""
|
|
235
|
+
for j in range(rpix):
|
|
236
|
+
outst += f"{j:3d} {np.size(vals[j]):8d}"
|
|
237
|
+
for q in [1, 5, 25, 50, 75, 95, 99]:
|
|
238
|
+
outst += f" {np.percentile(vals[j], q):12.5E}"
|
|
239
|
+
outst += "\n"
|
|
240
|
+
ofile = outstem + "_dynrange.dat"
|
|
241
|
+
with open(ofile, "w") as fn:
|
|
242
|
+
fn.write(outst)
|
|
243
|
+
if framenumber > 0:
|
|
244
|
+
output["DYNRANGE"] = ofile
|
|
245
|
+
|
|
246
|
+
# save histograms
|
|
247
|
+
ofile = outstem + "_sqrtS_hist.dat"
|
|
248
|
+
np.savetxt(
|
|
249
|
+
ofile, countnoise, header=f" {np.amax(countnoise[:, 1]):11.5E} {100 * tnoise_gt / tnoise:9.6f}"
|
|
250
|
+
)
|
|
251
|
+
output["SQRTS"] = ofile
|
|
252
|
+
output["SQRTS_HEADER"] = (np.amax(countnoise[:, 1]), 100 * tnoise_gt / tnoise)
|
|
253
|
+
ofile = outstem + "_neff_hist.dat"
|
|
254
|
+
np.savetxt(ofile, countneff, header=f" {np.amax(countneff[:, 1]):11.5E} {100 * tneff_gt / tneff:9.6f}")
|
|
255
|
+
output["NEFF"] = ofile
|
|
256
|
+
output["NEFF_HEADER"] = (np.amax(countneff[:, 1]), 100 * tneff_gt / tneff)
|
|
257
|
+
|
|
258
|
+
output["NSTARLAYER"] = nstarlayer
|
|
259
|
+
|
|
260
|
+
return output
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
"""
|
|
264
|
+
Command line driver.
|
|
265
|
+
Format: python3 -m dynrange <file stem> <output filename>
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
if __name__ == "__main__":
|
|
269
|
+
|
|
270
|
+
def fn(ibx, iby): # noqa: D103
|
|
271
|
+
return sys.argv[1] + f"_{ibx:02d}_{iby:02d}.fits"
|
|
272
|
+
|
|
273
|
+
output = gen_dynrange_data(fn, sys.argv[2])
|
|
274
|
+
print("**", output)
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Report section for layer diagnostics.
|
|
3
|
+
|
|
4
|
+
Classes
|
|
5
|
+
-------
|
|
6
|
+
LayerReport
|
|
7
|
+
Layer report section.
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import concurrent.futures
|
|
12
|
+
import contextlib
|
|
13
|
+
import gc
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
from os.path import exists
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
from ..compress.compressutils import ReadFile
|
|
21
|
+
from .report import ReportSection
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _percentiles_and_delete(arr, pctiles, target, delete_arr):
|
|
25
|
+
"""
|
|
26
|
+
Sorts/constructs percentiles from a memmapped array, then (optionally) deletes it.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
arr : np.memmap
|
|
31
|
+
The memory-mapped array.
|
|
32
|
+
pctiles : array-like
|
|
33
|
+
The percentiles to generate.
|
|
34
|
+
target : np.ndarray
|
|
35
|
+
The array to save the percentiles.
|
|
36
|
+
delete_arr : bool
|
|
37
|
+
Whether to delete the array when done.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
None
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
# Begin by sorting the array in place
|
|
46
|
+
arr.sort(kind="mergesort")
|
|
47
|
+
|
|
48
|
+
# Read percentiles from sorted array
|
|
49
|
+
npc = len(pctiles)
|
|
50
|
+
nsize = arr.size
|
|
51
|
+
for k in range(npc):
|
|
52
|
+
pos = (nsize - 1) * pctiles[k] / 100.0
|
|
53
|
+
p1 = max(int(np.floor(pos)), 0)
|
|
54
|
+
if p1 >= nsize - 1:
|
|
55
|
+
p1 = nsize - 2
|
|
56
|
+
frac = np.clip(pos - p1, 0.0, 1.0)
|
|
57
|
+
target[k] = (1 - frac) * arr[p1] + frac * arr[p1 + 1]
|
|
58
|
+
|
|
59
|
+
if delete_arr:
|
|
60
|
+
fn = getattr(arr, "filename", None)
|
|
61
|
+
del arr
|
|
62
|
+
if fn is not None:
|
|
63
|
+
with contextlib.suppress(FileNotFoundError):
|
|
64
|
+
os.remove(fn)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class LayerReport(ReportSection):
|
|
68
|
+
"""
|
|
69
|
+
The layer section of the report.
|
|
70
|
+
|
|
71
|
+
Inherits from pyimcom.diagnostics.report.ReportSection. Overrides build.
|
|
72
|
+
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def build(self, nblockmax=100):
|
|
76
|
+
"""
|
|
77
|
+
Generates the LaTeX for the statistical report on layers.
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
nblockmax : int, optional
|
|
82
|
+
The maximum number of blocks on a side to allow.
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
None
|
|
87
|
+
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
self.tex += "\\section{Layer statistics}\n\n"
|
|
91
|
+
|
|
92
|
+
# figure out which layers we have
|
|
93
|
+
layers = self.cfg.extrainput + []
|
|
94
|
+
layers[0] = "SCI"
|
|
95
|
+
nlayers = len(layers)
|
|
96
|
+
|
|
97
|
+
# dimensions
|
|
98
|
+
ns = self.cfg.Nside # the side length of the unique area
|
|
99
|
+
d = self.cfg.postage_pad * self.cfg.n2 # the gap between the unique area and block edge
|
|
100
|
+
nblock = min(self.cfg.nblock, nblockmax) # block dimension
|
|
101
|
+
|
|
102
|
+
# percentiles to use
|
|
103
|
+
pctiles = [0, 0.01, 0.1, 1, 5, 25, 50, 75, 95, 99, 99.9, 99.99, 100]
|
|
104
|
+
npc = len(pctiles)
|
|
105
|
+
pcarray = np.zeros((nlayers, npc), dtype=np.float32)
|
|
106
|
+
|
|
107
|
+
nsize = (ns * nblock) ** 2
|
|
108
|
+
data = [None] * nlayers
|
|
109
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as exe:
|
|
110
|
+
jobs = [None] * nlayers
|
|
111
|
+
for ilayer in range(nlayers):
|
|
112
|
+
# get data
|
|
113
|
+
if self.tmp_dir is None:
|
|
114
|
+
data[ilayer] = np.zeros((nsize,), dtype=np.float32)
|
|
115
|
+
else:
|
|
116
|
+
data[ilayer] = np.memmap(
|
|
117
|
+
str(self.tmp_dir) + f"/layer_{ilayer:d}.npy",
|
|
118
|
+
dtype="float32",
|
|
119
|
+
mode="w+",
|
|
120
|
+
shape=((nsize,)),
|
|
121
|
+
)
|
|
122
|
+
data[ilayer][:] = 0.0
|
|
123
|
+
|
|
124
|
+
for iby in range(nblock):
|
|
125
|
+
|
|
126
|
+
def _load_row(arg):
|
|
127
|
+
# not binding iby and ilayer is fine since the function is used and removed in the
|
|
128
|
+
# loop
|
|
129
|
+
(ibx, _data) = arg
|
|
130
|
+
print(f"building layer {ilayer:2d}, block ({ibx:2d},{iby:2d}) ") # noqa: B023
|
|
131
|
+
sys.stdout.flush()
|
|
132
|
+
infile = self.infile(ibx, iby) # noqa: B023
|
|
133
|
+
if not exists(infile):
|
|
134
|
+
return None
|
|
135
|
+
chunk = iby * nblock + ibx # noqa: B023
|
|
136
|
+
|
|
137
|
+
# end up here for the other layers
|
|
138
|
+
with ReadFile(infile, layers=[ilayer]) as f: # noqa: B023
|
|
139
|
+
x_ = f[0].data[0, ilayer, :, :] # noqa: B023
|
|
140
|
+
if d > 0:
|
|
141
|
+
x_ = x_[d:-d, d:-d]
|
|
142
|
+
_data[chunk * ns * ns : (chunk + 1) * ns * ns] = x_.ravel()
|
|
143
|
+
|
|
144
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as e:
|
|
145
|
+
e.map(_load_row, [(ix, data[ilayer]) for ix in range(nblock)])
|
|
146
|
+
|
|
147
|
+
del _load_row
|
|
148
|
+
|
|
149
|
+
# wait for previous layer
|
|
150
|
+
if ilayer > 0:
|
|
151
|
+
jobs[ilayer - 1].result()
|
|
152
|
+
data[ilayer - 1] = None
|
|
153
|
+
gc.collect()
|
|
154
|
+
print("percentiles", ilayer - 1, pcarray[ilayer - 1, :]) # remove from final version
|
|
155
|
+
sys.stdout.flush()
|
|
156
|
+
|
|
157
|
+
# get percentiles --- now uses sorting method since this works out of core
|
|
158
|
+
jobs[ilayer] = exe.submit(
|
|
159
|
+
_percentiles_and_delete, data[ilayer], pctiles, pcarray[ilayer, :], True
|
|
160
|
+
)
|
|
161
|
+
# data.sort(kind="mergesort")
|
|
162
|
+
# for k in range(npc):
|
|
163
|
+
# pos = (nsize - 1) * pctiles[k] / 100.0
|
|
164
|
+
# p1 = int(np.floor(pos))
|
|
165
|
+
# if p1 < 0:
|
|
166
|
+
# p1 = 0
|
|
167
|
+
# if p1 >= nsize - 1:
|
|
168
|
+
# p1 = nsize - 2
|
|
169
|
+
# frac = np.clip(pos - p1, 0.0, 1.0)
|
|
170
|
+
# pcarray[ilayer, k] = (1 - frac) * data[p1] + frac * data[p1 + 1]
|
|
171
|
+
# del data
|
|
172
|
+
|
|
173
|
+
# wait for the last one
|
|
174
|
+
jobs[-1].result()
|
|
175
|
+
del data
|
|
176
|
+
del jobs
|
|
177
|
+
gc.collect()
|
|
178
|
+
|
|
179
|
+
# now build table, in segments of size up to ncolmax
|
|
180
|
+
ncolmax = 6
|
|
181
|
+
self.tex += "The percentiles of the various layers are included in the table below."
|
|
182
|
+
self.tex += " Note that a maximum of " + str(ncolmax) + "layers are shown in each table"
|
|
183
|
+
self.tex += " to preserve horizontal space.\n\n"
|
|
184
|
+
self.tex += "The layers are:\n\\begin{itemize}\n"
|
|
185
|
+
for il in range(nlayers):
|
|
186
|
+
self.tex += "\\item"
|
|
187
|
+
self.tex += f" [{il:d}] "
|
|
188
|
+
self.tex += "{\\tt " + str(layers[il]) + "}"
|
|
189
|
+
if il != nlayers - 1:
|
|
190
|
+
self.tex += ";"
|
|
191
|
+
if il == nlayers - 2:
|
|
192
|
+
self.tex += " and"
|
|
193
|
+
self.tex += "\n"
|
|
194
|
+
self.tex += ".\n\\end{itemize}\n"
|
|
195
|
+
start = 0
|
|
196
|
+
while start < nlayers:
|
|
197
|
+
cols = list(range(start, min(start + ncolmax, nlayers)))
|
|
198
|
+
self.data += "# PCTILE |"
|
|
199
|
+
for il in cols:
|
|
200
|
+
self.data += f" LAYER {il:02d} "
|
|
201
|
+
self.data += "\n"
|
|
202
|
+
for k in range(npc):
|
|
203
|
+
self.data += f"{pctiles[k]:7.3f} "
|
|
204
|
+
for il in cols:
|
|
205
|
+
self.data += f" {pcarray[il, k]:10.3E}"
|
|
206
|
+
self.data += "\n"
|
|
207
|
+
self.data += "\n"
|
|
208
|
+
start += ncolmax
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mosaic image for display in a report.
|
|
3
|
+
|
|
4
|
+
Classes
|
|
5
|
+
-------
|
|
6
|
+
MosaicImage
|
|
7
|
+
Report section with mosaic image.
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from ..pictures.genpic import make_picture_1band
|
|
12
|
+
from .report import ReportSection
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MosaicImage(ReportSection):
|
|
16
|
+
"""
|
|
17
|
+
Builds the simulated star section of the report.
|
|
18
|
+
|
|
19
|
+
Inherits from pyimcom.diagnostics.report.ReportSection. Overrides build.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def build(self, nblockmax=100, srange=(-0.1, 100.0)):
|
|
24
|
+
"""
|
|
25
|
+
Builds the mosaic image and associated LaTeX.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
nblockmax : int, optional
|
|
30
|
+
Maximum size of mosaic to build. If larger than the nblock in the
|
|
31
|
+
configuration, builds the whole mosaic.
|
|
32
|
+
srange : (float, float), optional
|
|
33
|
+
Stretch scale to use for the mosaic image.
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
None
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
# which blocks to take
|
|
42
|
+
n = min(nblockmax, self.cfg.nblock)
|
|
43
|
+
ns = self.cfg.n1 * self.cfg.n2
|
|
44
|
+
j = max(int(n * ns / 1999.99999), 1) # prevent j==0
|
|
45
|
+
while ns % j != 0:
|
|
46
|
+
j -= 1
|
|
47
|
+
print("binning=", j, "nside=", ns, "tot=", n * ns)
|
|
48
|
+
|
|
49
|
+
# make the image itself
|
|
50
|
+
make_picture_1band(
|
|
51
|
+
self.stem,
|
|
52
|
+
self.datastem + "_mosaic.png",
|
|
53
|
+
bounds=[0, n, 0, n],
|
|
54
|
+
binning=j,
|
|
55
|
+
srange=srange,
|
|
56
|
+
stretch="asinh",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
self.tex += "\\section{Mosaic image}\n"
|
|
60
|
+
self.tex += (
|
|
61
|
+
"\\begin{figure}\n\\includegraphics[width=6.5in]{" + self.datastem_from_dir + "_mosaic.png}\n"
|
|
62
|
+
)
|
|
63
|
+
self.tex += (
|
|
64
|
+
"\\caption{\\label{fig:MosaicImage1}The mosaic (PNG binned $"
|
|
65
|
+
+ str(j)
|
|
66
|
+
+ "\\times"
|
|
67
|
+
+ str(j)
|
|
68
|
+
+ "$).\n"
|
|
69
|
+
)
|
|
70
|
+
self.tex += (
|
|
71
|
+
"The image is "
|
|
72
|
+
+ f"{n * self.cfg.n1 * self.cfg.n2 * self.cfg.dtheta:7.5f}"
|
|
73
|
+
+ " degrees across.}\n"
|
|
74
|
+
)
|
|
75
|
+
self.tex += "\\end{figure}\n\n"
|
|
76
|
+
self.tex += "The mosaic image is shown in Fig.~\\ref{fig:MosaicImage1}.\n\n"
|
|
77
|
+
|
|
78
|
+
self.data += "N = {:2d}, BIN = {:3d}\nIMAGEFILE = {:s}\n".format(
|
|
79
|
+
n, j, self.datastem_from_dir + "_mosaic.png"
|
|
80
|
+
)
|