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.
Files changed (52) hide show
  1. pyimcom/__init__.py +1 -0
  2. pyimcom/_version.py +24 -0
  3. pyimcom/analysis.py +1480 -0
  4. pyimcom/coadd.py +2331 -0
  5. pyimcom/compress/__init__.py +0 -0
  6. pyimcom/compress/compressutils.py +506 -0
  7. pyimcom/compress/compressutils_wrapper.py +116 -0
  8. pyimcom/compress/i24.py +514 -0
  9. pyimcom/config.py +1245 -0
  10. pyimcom/diagnostics/__init__.py +0 -0
  11. pyimcom/diagnostics/context_figure.py +58 -0
  12. pyimcom/diagnostics/dynrange.py +274 -0
  13. pyimcom/diagnostics/layer_diagnostics.py +208 -0
  14. pyimcom/diagnostics/mosaicimage.py +80 -0
  15. pyimcom/diagnostics/noise/stability.py +126 -0
  16. pyimcom/diagnostics/noise_diagnostics.py +709 -0
  17. pyimcom/diagnostics/outimage_utils/__init__.py +0 -0
  18. pyimcom/diagnostics/outimage_utils/helper.py +82 -0
  19. pyimcom/diagnostics/report.py +366 -0
  20. pyimcom/diagnostics/run.py +64 -0
  21. pyimcom/diagnostics/starcube_nonoise.py +264 -0
  22. pyimcom/diagnostics/starcube_nonoise_coldescr.txt +24 -0
  23. pyimcom/diagnostics/stars.py +469 -0
  24. pyimcom/imdestripe.py +2454 -0
  25. pyimcom/lakernel.py +805 -0
  26. pyimcom/layer.py +1439 -0
  27. pyimcom/layer_wrapper.py +96 -0
  28. pyimcom/meta/__init__.py +0 -0
  29. pyimcom/meta/distortimage.py +748 -0
  30. pyimcom/meta/ginterp.py +340 -0
  31. pyimcom/pictures/__init__.py +0 -0
  32. pyimcom/pictures/genpic.py +229 -0
  33. pyimcom/psfutil.py +2199 -0
  34. pyimcom/routine.py +588 -0
  35. pyimcom/splitpsf/__init__.py +0 -0
  36. pyimcom/splitpsf/imsubtract.py +793 -0
  37. pyimcom/splitpsf/imsubtract_wrapper.py +107 -0
  38. pyimcom/splitpsf/splitpsf.py +497 -0
  39. pyimcom/splitpsf/splitpsf_wrapper.py +161 -0
  40. pyimcom/splitpsf/update_cube.py +136 -0
  41. pyimcom/truthcats.py +396 -0
  42. pyimcom/utils/__init__.py +0 -0
  43. pyimcom/utils/compareutils.py +207 -0
  44. pyimcom/utils/piffutils.py +223 -0
  45. pyimcom/wcsutil.py +839 -0
  46. pyimcom-1.2.1.dist-info/METADATA +67 -0
  47. pyimcom-1.2.1.dist-info/RECORD +52 -0
  48. pyimcom-1.2.1.dist-info/WHEEL +5 -0
  49. pyimcom-1.2.1.dist-info/licenses/LICENSE +21 -0
  50. pyimcom-1.2.1.dist-info/scm_file_list.json +285 -0
  51. pyimcom-1.2.1.dist-info/scm_version.json +8 -0
  52. 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
+ )