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,506 @@
1
+ """
2
+ Compression tools for PyIMCOM outputs.
3
+
4
+ Classes
5
+ -------
6
+ CompressedOutput
7
+ Main class for compresseing a FITS file.
8
+
9
+ Functions
10
+ ---------
11
+ _parser
12
+ File name parser; only needed for file names with regular expressions.
13
+ ReadFile
14
+ Stand-alone function to read a compressed FITS file.
15
+
16
+ """
17
+ import re
18
+ from urllib.parse import urlparse
19
+
20
+ import numpy as np
21
+ from astropy.io import fits
22
+
23
+ from ..config import Config
24
+
25
+ # specific compression tools we need
26
+ from .i24 import i24compress, i24decompress
27
+
28
+
29
+ class CompressedOutput:
30
+ """Class for compressing pyimcom output files.
31
+
32
+ Parameters
33
+ ----------
34
+ fname : str
35
+ File name for uncompressed file.
36
+ format : str or None, optional
37
+ Compression format.
38
+ layers : list, optional
39
+ Which layers to de-compress (if given; otherwise de-compresses everything).
40
+ Use only for reading (don't write an instance initialized with `layers` to a file).
41
+ extraargs : dict, optional
42
+ Extra arguments for astropy.io.fits.
43
+
44
+
45
+ Attributes
46
+ ----------
47
+ gzip : bool
48
+ Is gzipped?
49
+ ftype : str
50
+ File type. Currently the only option is 'fits'.
51
+ cprstype : str
52
+ Compression type (for a full file, not currently used).
53
+ origfile : str
54
+ Original file name (when opened)
55
+ hdul : astropy.io.fits.HDUList
56
+ HDU List of information (initially a deep copy of the input file).
57
+ cfg : pyimcom.config.Config
58
+ Configuration file used to generate this image.
59
+
60
+ Methods
61
+ -------
62
+ __init__
63
+ Constructor.
64
+ compress_2d_image
65
+ Wrapper for 2d image compression (staticmethod).
66
+ get_compression_dict
67
+ Extract compression scheme for a PyIMCOM layer as a dictionary.
68
+ decompress_2d_image
69
+ Wrapper for 2d image decompression (staticmethod).
70
+ compress_layer
71
+ Compresses a layer.
72
+ decompress
73
+ Decompresses the whole file.
74
+ recompress
75
+ Recompress previously compressed layers.
76
+ to_file
77
+ Saves to a file.
78
+ close
79
+ Close associated file.
80
+
81
+ Notes
82
+ -----
83
+ At some point, we may add file formats other than FITS, but not yet.
84
+
85
+ This object is big since it stores a deep copy of the whole file.
86
+ Therefore, it is not a good idea to open lots of compressed files at once.
87
+
88
+ """
89
+
90
+ def __init__(self, fname, format=None, layers=None, extraargs={}):
91
+ self.origfile = fname
92
+
93
+ # figure out what type of file it is, and if it is gzipped
94
+ self.gzip = False
95
+
96
+ # determines which layers to decompress
97
+ self.decompress_layers = layers
98
+
99
+ if fname[-3:] == ".gz":
100
+ self.gzip = True
101
+ if format is None:
102
+ pref = fname[:-3] if self.gzip else fname
103
+
104
+ # right now supports fits files
105
+ if pref[-5:] == ".fits":
106
+ self.ftype = "fits"
107
+ self.hdul = fits.open(fname, mode="readonly", decompress_in_memory=True, **extraargs)
108
+ if "CPRSTYPE" in self.hdul[0].header:
109
+ self.cprstype = self.hdul[0].header["CPRSTYPE"]
110
+ else:
111
+ self.cprstype = ""
112
+ self.hdul[0].header["CPRSTYPE"] = ""
113
+ self.cfg = Config(fname, inmode="block")
114
+
115
+ else:
116
+ raise Exception("unrecognized file type")
117
+
118
+ @staticmethod
119
+ def compress_2d_image(im, scheme, pars):
120
+ """Wrapper to compress a 2D image.
121
+
122
+ Parameters
123
+ ----------
124
+ im : np.array
125
+ 2D image to be compressed.
126
+ scheme : str
127
+ Name of the compression scheme.
128
+ pars : dict
129
+ Parameters to be passed to the compression algorithm.
130
+
131
+ Returns
132
+ -------
133
+ imout : np.array
134
+ The compressed 2D image, same shape as `im`.
135
+ ovflow : astropy.io.fits.BinTableHDU
136
+ A table of values that overflowed the quantization range for the
137
+ compression scheme. Returns None if not used.
138
+
139
+ """
140
+
141
+ if scheme[:3] == "I24":
142
+ imout, ovflow = i24compress(im, scheme, pars)
143
+ return imout, ovflow
144
+
145
+ # unrecognized scheme or NULL
146
+ return np.copy(im), None
147
+
148
+ @classmethod
149
+ def get_compression_dict(cls, hdulist, ilayer):
150
+ """Extract compression scheme for a PyIMCOM layer as a dictionary.
151
+
152
+ Parameters
153
+ ----------
154
+ hdulist : astropy.io.fits.HDUList
155
+ The FITS file as an HDUList.
156
+ ilayer : int
157
+ The layer number to extract.
158
+
159
+ Returns
160
+ -------
161
+ dict
162
+ The compression scheme parameter dictionary. Note that all values are
163
+ returned as strings, the calling routine must typecast them as appropriate.
164
+
165
+ If the layer was not
166
+ compressed, returns an empty list, ``{}``.
167
+
168
+ """
169
+
170
+ # extract the compression data if that HDU is present
171
+ try:
172
+ cprs = [x.strip().split(":") for x in hdulist["CPRESS"].data["text"]]
173
+ except KeyError:
174
+ return {} # stop here for uncompressed files
175
+
176
+ cprs_thislayer = [item for item in cprs if int(item[0], 16) == ilayer]
177
+ return dict(
178
+ zip(
179
+ [item[1].strip() for item in cprs_thislayer],
180
+ [item[2].strip() for item in cprs_thislayer],
181
+ strict=False,
182
+ )
183
+ )
184
+
185
+ @staticmethod
186
+ def decompress_2d_image(im, scheme, pars, overflow=None):
187
+ """Wrapper to decompress a 2D image; overflow table used for some formats.
188
+
189
+ Parameters
190
+ ----------
191
+ im : np.array
192
+ The compressed image.
193
+ scheme : str
194
+ Name of the compression scheme.
195
+ pars : dict
196
+ Parameters to be passed to the compression algorithm.
197
+ overflow : astropy.io.fits.BinTableHDU or None, optional
198
+ (If used.) A table of values that overflowed the quantization range for the
199
+ compression scheme. Returns None if not used.
200
+
201
+ Returns
202
+ -------
203
+ imout : np.array
204
+ The de-compressed 2D image, same shape as `im`.
205
+
206
+ """
207
+
208
+ if scheme[:3] == "I24":
209
+ return i24decompress(im, scheme, pars, overflow=overflow)
210
+
211
+ # unrecognized scheme or NULL
212
+ return np.copy(im)
213
+
214
+ def compress_layer(self, layerid, scheme=None, pars={}):
215
+ """Compresses the given layerid with the indicated scheme.
216
+
217
+ The pars is a dictionary of parameters that go with that scheme.
218
+ It must be in a format that supports a FITS header.
219
+ If scheme is None, then the algorithm will re-compress the layer in the same way was
220
+ done previously (if it was compressed before), otherwise it will do the NULL compression.
221
+
222
+ Parameters
223
+ ----------
224
+ layerid : int
225
+ Number of the layer to be compressed.
226
+ scheme : str or None, optional
227
+ Name of the compression scheme. Defaults to None (no compression).
228
+ pars : dict, optional
229
+ Parameters to be passed to the compression algorithm.
230
+
231
+ Returns
232
+ -------
233
+ None
234
+
235
+ """
236
+
237
+ # refuse to compress the science layer
238
+ if layerid == 0:
239
+ return
240
+
241
+ # this failure to write the EXTNAME shouldn't happen, but just in case
242
+ if layerid >= 16**4:
243
+ return
244
+
245
+ # make a blank table if there isn't compression information yet
246
+ if "CPRESS" not in [hdu.name for hdu in self.hdul]:
247
+ hdu = fits.TableHDU.from_columns([fits.Column(name="text", format="A512", array=[], ascii=True)])
248
+ hdu.name = "CPRESS"
249
+ self.hdul.append(hdu)
250
+ # now get compression information
251
+ rows = self.hdul["CPRESS"].data["text"].tolist()
252
+
253
+ # None means we should check if this data has been compressed
254
+ # before and use that method.
255
+ if scheme is None:
256
+ compressiondict = {}
257
+ for kv in rows:
258
+ layer_, key_, val_ = kv.strip().split(":")
259
+ key_ = key_.strip()
260
+ val_ = val_.strip()
261
+ if int(layer_, 0x10) == layerid:
262
+ compressiondict[key_] = val_
263
+ if "SCHEME" in compressiondict:
264
+ # this was done before
265
+ # re-compress without adding new keywords
266
+ cd_data, cd_overflow = CompressedOutput.compress_2d_image(
267
+ self.hdul[0].data[0, layerid, :, :], compressiondict["SCHEME"], compressiondict
268
+ )
269
+ self.hdul[0].data[0, layerid, :, :] = 0
270
+ newhdu = fits.ImageHDU(cd_data)
271
+ for p in compressiondict:
272
+ newhdu.header[p] = compressiondict[p]
273
+ newhdu.name = f"HSHX{layerid:04X}"
274
+ self.hdul.append(newhdu)
275
+ cd_overflow.name = f"HSHV{layerid:04X}"
276
+ self.hdul.append(cd_overflow)
277
+
278
+ # print('re-compressed', compressiondict)
279
+ return
280
+
281
+ # we will do the null compression in this case
282
+ scheme = "NULL"
283
+
284
+ cd_data, cd_overflow = CompressedOutput.compress_2d_image(
285
+ self.hdul[0].data[0, layerid, :, :], scheme, pars
286
+ )
287
+ self.hdul[0].data[0, layerid, :, :] = 0
288
+ newhdu = fits.ImageHDU(cd_data)
289
+ for p in pars:
290
+ newhdu.header[p] = pars[p]
291
+ rows += [f"{layerid:04X}:{p:8s}:{str(pars[p]):s}"]
292
+ newhdu.header["SCHEME"] = scheme
293
+ rows += ["{:04X}:{:8s}:{:s}".format(layerid, "SCHEME", scheme)]
294
+ newhdu.name = f"HSHX{layerid:04X}"
295
+ self.hdul.append(newhdu)
296
+ cd_overflow.name = f"HSHV{layerid:04X}"
297
+ self.hdul.append(cd_overflow)
298
+
299
+ # which HDU has the compression data?
300
+ j_ = -1
301
+ for j in range(len(self.hdul)):
302
+ if self.hdul[j].name == "CPRESS":
303
+ j_ = j
304
+ if j_ == -1:
305
+ raise Exception("Can't find CPRESS: this shouldn't happen.")
306
+ # now overwrite that one
307
+ hdu = fits.TableHDU.from_columns([fits.Column(name="text", format="A512", array=rows, ascii=True)])
308
+ hdu.name = "CPRESS"
309
+ self.hdul[j_] = hdu
310
+
311
+ def decompress(self):
312
+ """Decompresses all the layers that were compressed by compress_layer."""
313
+
314
+ j = 0
315
+ while j < len(self.hdul):
316
+ if self.hdul[j].name[:4] == "HSHX":
317
+ layer_target = int(self.hdul[j].name[-4:], 0x10) # this is a layer number
318
+
319
+ if (self.decompress_layers is not None) and (layer_target not in self.decompress_layers):
320
+ j = j + 1
321
+ continue
322
+
323
+ self.hdul[0].data[0, layer_target, :, :] = CompressedOutput.decompress_2d_image(
324
+ self.hdul[j].data,
325
+ self.hdul[j].header["SCHEME"],
326
+ self.hdul[j].header,
327
+ overflow=self.hdul["HSHV" + self.hdul[j].name[-4:]],
328
+ )
329
+ del self.hdul[j]
330
+ else:
331
+ # we only increment j if the we didn't remove anything, since if we removed an HDU then
332
+ # what was hdul[j+1] is now hdul[j]
333
+ j = j + 1
334
+
335
+ # remove the overflow tables
336
+ j = 0
337
+ while j < len(self.hdul):
338
+ if self.hdul[j].name[:4] == "HSHV":
339
+ del self.hdul[j]
340
+ else:
341
+ j = j + 1
342
+
343
+ def recompress(self):
344
+ """Recompresses all the layers that were previously compressed by compress_layer."""
345
+
346
+ # if this wasn't compressed before, nothing to do
347
+ if "CPRESS" not in [hdu.name for hdu in self.hdul]:
348
+ return
349
+
350
+ # figure out which layers were previously compressed
351
+ nlayer = np.shape(self.hdul[0].data)[-3]
352
+ wascompressed = np.zeros(nlayer, dtype=bool)
353
+ for compressnote in self.hdul["CPRESS"].data["text"].tolist():
354
+ ilayer = int(compressnote.split(":")[0], 16) # which layer this referred to
355
+ wascompressed[ilayer] = True
356
+ # print(wascompressed)
357
+ for ilayer in range(nlayer):
358
+ if wascompressed[ilayer]:
359
+ self.compress_layer(ilayer)
360
+
361
+ def to_file(self, fname, overwrite=False):
362
+ """
363
+ Saves to a file.
364
+
365
+ Parameters
366
+ ----------
367
+ fname : str
368
+ File name.
369
+ overwrite : bool, optional
370
+ Whether to overwrite the file.
371
+
372
+ Returns
373
+ -------
374
+ None
375
+
376
+ """
377
+
378
+ self.hdul.writeto(fname, overwrite=overwrite)
379
+
380
+ def close(self):
381
+ """Closes the associated file."""
382
+ self.hdul.close()
383
+
384
+ def __enter__(self):
385
+ """Entry method."""
386
+ return self
387
+
388
+ def __exit__(self, exc_type, exc_val, exc_tb):
389
+ """Has context manager automatically close."""
390
+ self.close()
391
+ return False # do not suppress exception
392
+
393
+
394
+ def _parser(fname):
395
+ """
396
+ Re-formats a file name containing a regular expression.
397
+
398
+ Regular expressions are separated with the ^ character and contain a row and column index,
399
+ followed by the suffix. For example::
400
+
401
+ >>> _parser('hello_world/Q_02_31.fits') # no ^, regular file name
402
+ 'hello_world/Q_02_31.fits'
403
+ >>> _parser('hello_world/Row{1:2d}/Q_{0:02d}_{1:02d}^_02_31.fits') # FITS file
404
+ 'hello_world/Row31/Q_02_31.fits'
405
+ >>> _parser('hello_world/Row{1:2d}/Q_{0:02d}_{1:02d}^_02_12.fits.gz') # gzipped; suffix is copied over
406
+ 'hello_world/Row12/Q_02_12.fits.gz'
407
+
408
+ This is useful if the files are not all in the same directory.
409
+
410
+ Parameters
411
+ ----------
412
+ fname : str or str-like
413
+ Regular file name (not including ^) or regular expression.
414
+
415
+ Returns
416
+ -------
417
+ str
418
+ The formatted file name.
419
+
420
+ """
421
+
422
+ fname = str(fname) # converts other objects, e.g., pathlib.Path, to a string
423
+
424
+ # normal file name: nothing to be done
425
+ if "^" not in fname:
426
+ return fname
427
+
428
+ # pattern match
429
+ parts = fname.split("^")
430
+ sub = parts[1].split(".")
431
+ coordstring = sub[0]
432
+ m = re.match(r"_(\d+)_(\d+)(\D*)", coordstring)
433
+ if m is not None:
434
+ ix = int(m.group(1))
435
+ iy = int(m.group(2))
436
+ term = m.group(3)
437
+ suffix = term + "." + ".".join(sub[1:])
438
+ outname = "^".join(parts[:-1])
439
+ outname = outname.format(ix, iy) + suffix
440
+ return outname
441
+
442
+
443
+ def ReadFile(fname, layers=None):
444
+ """Wrapper to read a compressed file.
445
+
446
+ This should read a file just like astropy.io.fits.open(fname, mode='readonly'),
447
+ but works even if the file is compressed using compressutils.
448
+
449
+ Parameters
450
+ ----------
451
+ fname : str or str-like
452
+ File name to read.
453
+ layers : list, optional
454
+ Which layers to de-compress (if given; otherwise de-compresses everything).
455
+ Use only for reading (don't write an instance initialized with `layers` to a file).
456
+
457
+ Notes
458
+ -----
459
+ This can also be used with the Python context manager, e.g.::
460
+
461
+ with ReadFile('my.fits.gz') as f:
462
+ ...
463
+
464
+ File names with sepcific types of regular expressions are allowed, and unpacked by the ``_parser``
465
+ function. Regular expressions are separated with the ^ character and contain a row and column index,
466
+ followed by the suffix. For example::
467
+
468
+ >>> _parser('hello_world/Q_02_31.fits') # no ^, regular file name
469
+ 'hello_world/Q_02_31.fits'
470
+ >>> _parser('hello_world/Row{1:2d}/Q_{0:02d}_{1:02d}^_02_31.fits') # FITS file
471
+ 'hello_world/Row31/Q_02_31.fits'
472
+ >>> _parser('hello_world/Row{1:2d}/Q_{0:02d}_{1:02d}^_02_12.fits.gz') # gzipped; suffix is copied over
473
+ 'hello_world/Row12/Q_02_12.fits.gz'
474
+
475
+ """
476
+
477
+ fname = _parser(fname) # if the file name is a regular expression.
478
+
479
+ _o = urlparse(fname)
480
+
481
+ extraargs = {}
482
+ match _o.scheme:
483
+ case "" | "file":
484
+ pass
485
+ case "http" | "https":
486
+ extraargs["use_fsspec"] = True
487
+ case "s3":
488
+ extraargs["use_fsspec"] = True
489
+ extraargs["fsspec_kwargs"] = {"anon": True}
490
+ # extraargs["fsspec_kwargs"] = {"key": "YOUR-SECRET-KEY-ID", "secret": "YOUR-SECRET-KEY"}
491
+ case _:
492
+ raise ValueError(f"Scheme {_o.scheme} not supported")
493
+
494
+ _url = _o.geturl()
495
+
496
+ f = fits.open(_url, **extraargs)
497
+
498
+ if "CPRESS" not in [hdu.name for hdu in f]:
499
+ return f
500
+ else:
501
+ f.close()
502
+
503
+ # otherwise, make a decompressed version
504
+ x = CompressedOutput(fname, layers=layers, extraargs=extraargs)
505
+ x.decompress()
506
+ return fits.HDUList(x.hdul)
@@ -0,0 +1,116 @@
1
+ import multiprocessing as mp
2
+ import os
3
+ import re
4
+ import sys
5
+ from concurrent.futures import ProcessPoolExecutor, as_completed
6
+
7
+ from .compressutils import CompressedOutput
8
+
9
+
10
+ def compress_one_block(cfg, layer_pars_dic, ibx, iby):
11
+ """Helper function to compress one block of the IMCOM output.
12
+
13
+ Parameters
14
+ ----------
15
+ cfg : pyimcom.config.Config
16
+ The IMCOM configuration.
17
+ layer_pars_dic : dict
18
+ A dictionary of layer parameters, where the keys are the layer names and the values are
19
+ dictionaries of parameters for that layer.
20
+ ibx : int
21
+ Block x index.
22
+ iby : int
23
+ Block y index.
24
+
25
+ Returns
26
+ -------
27
+ fout : str
28
+ The file name of the compressed output file for this block.
29
+ (None if the file was missing.)
30
+
31
+ """
32
+
33
+ # Get the input file
34
+ fname = cfg.outstem + f"_{ibx:02d}_{iby:02d}.fits"
35
+ if not os.path.exists(fname):
36
+ return None
37
+ # ... and the output file name
38
+ fout = cfg.outstem + f"_{ibx:02d}_{iby:02d}.cpr.fits.gz"
39
+ print(fname, "-->", fout)
40
+ sys.stdout.flush()
41
+
42
+ with CompressedOutput(fname) as f:
43
+ # Get types of each layer (e.g., 'gsstar', 'cstar', etc.)
44
+ # These match the keys in config.yaml and will set the pars
45
+ # The science layer is special because its type is None.
46
+ layers_types = [""] + [re.sub(r"\d+$", "", item.split(",")[0]) for item in f.cfg.extrainput[1:]]
47
+
48
+ # Loop over layers, check if each is to be compressed
49
+ for j in range(1, len(f.cfg.extrainput)):
50
+ if layers_types[j] in layer_pars_dic:
51
+ pardict = layer_pars_dic[layers_types[j]]
52
+ f.compress_layer(j, scheme=pardict.get("SCHEME", "I24B"), pars=pardict)
53
+
54
+ f.to_file(fout, overwrite=True)
55
+
56
+ return fout
57
+
58
+
59
+ def compress_all_blocks(cfg, layer_pars_dic, workers, require_all=False):
60
+ """
61
+ Helper function to compress all blocks of the IMCOM output in parallel.
62
+
63
+ Parameters
64
+ ----------
65
+ cfg : pyimcom.config.Config
66
+ The IMCOM configuration.
67
+ layer_pars_dic : dict
68
+ A dictionary of layer parameters, where the keys are the layer names and the values are
69
+ dictionaries of parameters for that layer.
70
+ workers : int
71
+ The number of parallel workers to use for compression.
72
+ require_all : boolean, optional
73
+ If True, raises an exception for missing files (default of False just moves on).
74
+
75
+ Returns
76
+ -------
77
+ None
78
+
79
+ """
80
+ nblock = cfg.nblock
81
+ nblock2 = cfg.nblock**2
82
+
83
+ start_method = "forkserver" if os.name.lower() == "posix" else "spawn"
84
+ ctx = mp.get_context(start_method)
85
+ nfail = 0 # number of failures
86
+ nmissing = 0 # number of missing files
87
+
88
+ with ProcessPoolExecutor(max_workers=workers, mp_context=ctx) as executor:
89
+ futures = []
90
+ for i in range(nblock2):
91
+ ibx = i % nblock
92
+ iby = i // nblock
93
+ futures.append(executor.submit(compress_one_block, cfg, layer_pars_dic, ibx, iby))
94
+
95
+ for future in as_completed(futures):
96
+ # Check for existence of output file to confirm completion
97
+ try:
98
+ fout = future.result()
99
+ if fout is None:
100
+ nmissing += 1
101
+ else:
102
+ if not os.path.exists(fout):
103
+ print(f"Error: {fout} was not created.")
104
+ nfail += 1
105
+ except Exception as e:
106
+ nfail += 1
107
+ print(f"Worker failed with exception {e}", flush=True)
108
+
109
+ if nfail > 0:
110
+ raise Exception(f"{nfail:d} instances of compress_one_block failed.")
111
+
112
+ if nmissing > 0:
113
+ if require_all:
114
+ raise Exception(f"{nmissing:d} instances of compress_one_block missing.")
115
+ else:
116
+ print(f"{nmissing:d} blocks missing.")