pspipe 0.3__tar.gz → 0.5__tar.gz
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.
- {pspipe-0.3 → pspipe-0.5}/PKG-INFO +6 -5
- {pspipe-0.3 → pspipe-0.5}/pspipe/obsdata.py +19 -7
- {pspipe-0.3 → pspipe-0.5}/pspipe/tasks.py +271 -17
- pspipe-0.5/pspipe/templates/config/eor_bins_hba.parset +4 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/default_settings.toml +26 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/tools/psdb.py +54 -0
- {pspipe-0.3 → pspipe-0.5}/pyproject.toml +1 -1
- pspipe-0.3/pspipe/templates/config/eor_bins_hba.parset +0 -5
- pspipe-0.3/setup.py +0 -47
- {pspipe-0.3 → pspipe-0.5}/LICENSE +0 -0
- {pspipe-0.3 → pspipe-0.5}/README.md +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/__init__.py +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/database.py +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/settings.py +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/config/eor_bins_a12.parset +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/config/eor_bins_alo.parset +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/config/eor_bins_nenufar.parset +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/config/flagger.parset +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/config/flagger_pre_combine.parset +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/config/gpr_config_a12.parset +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/config/gpr_config_hba.parset +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/config/gpr_config_nenufar.parset +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/config/gpr_config_v.parset +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/config/ps_config_a12.parset +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/config/ps_config_alo.parset +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/config/ps_config_hba.parset +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/config/ps_config_nenufar.parset +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/default_a12.toml +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/default_alo.toml +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/default_hba.toml +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/templates/default_nenufar.toml +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/tests/__init__.py +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/tests/test_settings.py +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/tests/test_utils.py +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/tools/__init__.py +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/tools/pspipe.py +0 -0
- {pspipe-0.3 → pspipe-0.5}/pspipe/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pspipe
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5
|
|
4
4
|
Summary: Power spectra pipeline for Cosmic Dawn, Epoch of Reionization radio interferometric experiments
|
|
5
5
|
Home-page: https://gitlab.com/flomertens/pspipe/
|
|
6
6
|
Author: "Florent Mertens"
|
|
@@ -12,14 +12,15 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.9
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
|
|
16
|
-
Requires-Dist: astropy (>=
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Dist: astropy (>=4.0,<5.0) ; python_version >= "3.7" and python_version < "3.8"
|
|
17
|
+
Requires-Dist: astropy (>=5.0,<6.0) ; python_version >= "3.8"
|
|
17
18
|
Requires-Dist: matplotlib (>=3.0,<4.0)
|
|
18
19
|
Requires-Dist: nenucal (>=0.1.11)
|
|
19
20
|
Requires-Dist: ps-eor (>=0.8)
|
|
20
21
|
Requires-Dist: python-casacore (>=3.0,<4.0)
|
|
21
|
-
Requires-Dist: scipy (>=1.6,<2.0); python_version >= "3.7" and python_version < "3.8"
|
|
22
|
-
Requires-Dist: scipy (>=1.8,<2.0); python_version >= "3.8"
|
|
22
|
+
Requires-Dist: scipy (>=1.6,<2.0) ; python_version >= "3.7" and python_version < "3.8"
|
|
23
|
+
Requires-Dist: scipy (>=1.8,<2.0) ; python_version >= "3.8"
|
|
23
24
|
Requires-Dist: tables (>=3.2,<4.0)
|
|
24
25
|
Requires-Dist: tabulate (>=0.8)
|
|
25
26
|
Requires-Dist: toml (>=0.10)
|
|
@@ -33,7 +33,7 @@ class ObsData(object):
|
|
|
33
33
|
assert key in self.all_stokes.keys()
|
|
34
34
|
return self.get_stokes(self.all_stokes[key])
|
|
35
35
|
|
|
36
|
-
def
|
|
36
|
+
def _invalidate_cache(self):
|
|
37
37
|
self.data = {}
|
|
38
38
|
|
|
39
39
|
def get_stokes_i_like(self):
|
|
@@ -48,15 +48,15 @@ class ObsData(object):
|
|
|
48
48
|
|
|
49
49
|
def set_flag(self, flag):
|
|
50
50
|
self.flag = flag
|
|
51
|
-
self.
|
|
51
|
+
self._invalidate_cache()
|
|
52
52
|
|
|
53
53
|
def filter_uvrange(self, umin, umax):
|
|
54
54
|
self.uvrange = [umin, umax]
|
|
55
|
-
self.
|
|
55
|
+
self._invalidate_cache()
|
|
56
56
|
|
|
57
57
|
def average_freqs(self, nfreqs):
|
|
58
58
|
self.nfreqs = nfreqs
|
|
59
|
-
self.
|
|
59
|
+
self._invalidate_cache()
|
|
60
60
|
|
|
61
61
|
def do_flag(self, flagger_file=None, verbose_flagging=True, plot_flagging=True):
|
|
62
62
|
if flagger_file is None:
|
|
@@ -97,9 +97,21 @@ class ObsData(object):
|
|
|
97
97
|
|
|
98
98
|
return i_res
|
|
99
99
|
|
|
100
|
-
def
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
def get_ml_gpr_path(self, eor_bin, umin=50, umax=250, name='', sampler_method='mcmc'):
|
|
101
|
+
return os.path.join(os.path.dirname(self.get_h5_file('I')), name, f'eor{eor_bin}_u{umin}-{umax}')
|
|
102
|
+
|
|
103
|
+
def get_ml_gpr_res(self, eor_bin, umin=50, umax=250, name='', sampler_method='mcmc'):
|
|
104
|
+
path = self.get_ml_gpr_path(eor_bin, umin=umin, umax=umax, name=name, sampler_method=sampler_method)
|
|
105
|
+
return ml_gpr.MLGPRResult.load(path, 'ml_gpr_res_I', sampler_method=sampler_method)
|
|
106
|
+
|
|
107
|
+
def get_ml_gpr_inj_path(self, eor_bin, var_21_over_noise, x1, x2, umin=50, umax=250, name=''):
|
|
108
|
+
d = f'inj_eor{eor_bin}_u{umin}-{umax}_a{var_21_over_noise:.3f}_x1{x1:.3f}_x2{x2:.3f}'
|
|
109
|
+
return os.path.join(os.path.dirname(self.get_h5_file('I')), name, d)
|
|
110
|
+
|
|
111
|
+
def get_ml_gpr_inj_res(self, eor_bin, var_21_over_noise, x1, x2, umin=50, umax=250, name='', sampler_method='mcmc'):
|
|
112
|
+
path = self.get_ml_gpr_inj_path(eor_bin, var_21_over_noise=var_21_over_noise, x1=x1, x2=x2, umin=umin, umax=umax, name=name)
|
|
113
|
+
data_inj = datacube.CartDataCube.load(os.path.join(path, 'c_injected.h5'))
|
|
114
|
+
return ml_gpr.MLGPRInjResult.load(path, 'ml_gpr_res_I', data_inj, sampler_method=sampler_method)
|
|
103
115
|
|
|
104
116
|
def get_ps_gen(self, **kargs):
|
|
105
117
|
return self.ps_builder.get(self.get_stokes_i_like(), **kargs)
|
|
@@ -6,6 +6,8 @@ import os
|
|
|
6
6
|
import re
|
|
7
7
|
import glob
|
|
8
8
|
import tempfile
|
|
9
|
+
import datetime
|
|
10
|
+
import itertools
|
|
9
11
|
|
|
10
12
|
import numpy as np
|
|
11
13
|
import astropy.io.fits as pf
|
|
@@ -87,6 +89,8 @@ class AbstractTask(object):
|
|
|
87
89
|
|
|
88
90
|
def add_sub_task(self, sub_task, run_on_host=None):
|
|
89
91
|
log_file = sub_task.get_log_file()
|
|
92
|
+
if log_file:
|
|
93
|
+
log_file = log_file.rstrip('/')
|
|
90
94
|
|
|
91
95
|
if log_file is not None:
|
|
92
96
|
if not os.path.exists(os.path.dirname(log_file)):
|
|
@@ -137,7 +141,8 @@ class ImagingSubTask(AbstractSubTask):
|
|
|
137
141
|
return f'{self.index:04d}'
|
|
138
142
|
|
|
139
143
|
def get_log_file(self):
|
|
140
|
-
|
|
144
|
+
date_str = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
|
145
|
+
return os.path.join(self.get_dir(), 'logs', os.path.basename(self.get_fits_prefix()) + f'_{date_str}.log')
|
|
141
146
|
|
|
142
147
|
def get_fits_prefix(self):
|
|
143
148
|
img = self.settings.image
|
|
@@ -212,8 +217,8 @@ class ImagingSubTask(AbstractSubTask):
|
|
|
212
217
|
channels_out = self.obs_meta.n_channels // int(channels_out[5:])
|
|
213
218
|
channels_out = int(channels_out)
|
|
214
219
|
|
|
215
|
-
args.append("-oversampling 4095 -kernel-size 15 -nwlayers 1000 -grid-mode kb -taper-edge
|
|
216
|
-
args.append("-padding
|
|
220
|
+
args.append(f"-oversampling 4095 -kernel-size 15 -nwlayers 1000 -grid-mode kb -taper-edge {img.taper_edge}")
|
|
221
|
+
args.append(f"-padding {img.padding} -visibility-weighting-mode unit -reorder -make-psf -no-dirty")
|
|
217
222
|
|
|
218
223
|
# If MS directory is not writable, we need to define a temp directory
|
|
219
224
|
if not all(os.access(ms_file, os.W_OK) for ms_file in self.inp_ms_files):
|
|
@@ -346,8 +351,9 @@ class GenVisCubeSubTask(AbstractSubTask):
|
|
|
346
351
|
return self.vis_rev.get_h5_file(self.obs_id, self.stokes.upper(), self.even, self.odd, lst_idx=self.lst_idx)
|
|
347
352
|
|
|
348
353
|
def get_log_file(self):
|
|
354
|
+
date_str = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
|
349
355
|
return os.path.join(self.vis_rev.get_h5_path(self.obs_id, self.lst_idx), 'logs',
|
|
350
|
-
os.path.basename(self.get_out_file().replace('.h5', '.log')))
|
|
356
|
+
os.path.basename(self.get_out_file().replace('.h5', f'_{date_str}.log')))
|
|
351
357
|
|
|
352
358
|
def done_callback(self):
|
|
353
359
|
meta = self.vis_rev.get_meta(self.obs_id, self.lst_idx)
|
|
@@ -459,7 +465,7 @@ class GenVisCubeTask(AbstractTask):
|
|
|
459
465
|
class MergeMSSubTask(AbstractSubTask):
|
|
460
466
|
|
|
461
467
|
def __init__(self, settings_obj, ms_files, obs_id_out):
|
|
462
|
-
AbstractSubTask.__init__(self, settings_obj, '
|
|
468
|
+
AbstractSubTask.__init__(self, settings_obj, 'DP3')
|
|
463
469
|
self.db = database.Database(self.settings)
|
|
464
470
|
self.ms_files = ms_files
|
|
465
471
|
self.obs_id_out = obs_id_out
|
|
@@ -471,7 +477,8 @@ class MergeMSSubTask(AbstractSubTask):
|
|
|
471
477
|
return os.path.join(self.get_dir(), f'{self.obs_id_out}.MS')
|
|
472
478
|
|
|
473
479
|
def get_log_file(self):
|
|
474
|
-
|
|
480
|
+
date_str = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
|
481
|
+
return os.path.join(self.get_out_file().replace('.MS', f'_{date_str}.log'))
|
|
475
482
|
|
|
476
483
|
def make_link(self):
|
|
477
484
|
inp_ms_dir = os.path.join(self.get_dir(), 'input_ms')
|
|
@@ -499,7 +506,7 @@ class MergeMSSubTask(AbstractSubTask):
|
|
|
499
506
|
merge_ms = self.settings.merge_ms
|
|
500
507
|
|
|
501
508
|
steps = ['filter']
|
|
502
|
-
args = [f'msin={inp_ms_dir}/*.MS', f'msin.datacolumn={merge_ms.data_col}']
|
|
509
|
+
args = [f'msin={inp_ms_dir}/*.MS', f'msin.datacolumn={merge_ms.data_col}', f'numthreads={merge_ms.numthreads}']
|
|
503
510
|
args.append(f'msout={self.get_out_file()} msout.overwrite=true')
|
|
504
511
|
args.append(f'filter.type=filter filter.blrange=[{merge_ms.blmin},{merge_ms.blmax}]')
|
|
505
512
|
|
|
@@ -511,7 +518,7 @@ class MergeMSSubTask(AbstractSubTask):
|
|
|
511
518
|
|
|
512
519
|
if merge_ms.apply_aoflagger:
|
|
513
520
|
steps.append('flagger')
|
|
514
|
-
args.append(f'flagger.type=aoflagger flagger.memoryperc=
|
|
521
|
+
args.append(f'flagger.type=aoflagger flagger.memoryperc={merge_ms.memoryperc} flagger.strategy="{merge_ms.aoflagger_strategy}"')
|
|
515
522
|
|
|
516
523
|
if merge_ms.flag_baselines:
|
|
517
524
|
steps.append('preflag')
|
|
@@ -566,6 +573,102 @@ class MergeMSTask(AbstractTask):
|
|
|
566
573
|
return all_obs_id_out
|
|
567
574
|
|
|
568
575
|
|
|
576
|
+
class AOFlaggerSubTask(AbstractSubTask):
|
|
577
|
+
|
|
578
|
+
def __init__(self, settings_obj, ms_file):
|
|
579
|
+
AbstractSubTask.__init__(self, settings_obj, 'DP3')
|
|
580
|
+
self.ms_file = ms_file
|
|
581
|
+
|
|
582
|
+
def get_log_file(self):
|
|
583
|
+
date_str = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
|
584
|
+
return os.path.join(self.ms_file.replace('.MS', f'_aoflag_{date_str}.log'))
|
|
585
|
+
|
|
586
|
+
def get_parameters(self):
|
|
587
|
+
ao_flag = self.settings.ao_flag
|
|
588
|
+
args = [f'msin={self.ms_file}', 'msout=.', f'msin.datacolumn={ao_flag.data_col}', 'steps=[flagger]', f'numthreads={ao_flag.numthreads}']
|
|
589
|
+
args.append(f'flagger.type=aoflagger flagger.memoryperc={ao_flag.memoryperc} flagger.strategy="{ao_flag.aoflagger_strategy}"')
|
|
590
|
+
|
|
591
|
+
return args
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
class AOFlaggerTask(AbstractTask):
|
|
595
|
+
|
|
596
|
+
name = 'ao_flag'
|
|
597
|
+
desc = 'Run AO flagger on the Measurement Set(s)'
|
|
598
|
+
|
|
599
|
+
def __init__(self, settings_obj):
|
|
600
|
+
AbstractTask.__init__(self, 'AOFlagger', settings_obj, max_concurrent=1)
|
|
601
|
+
self.db = database.Database(self.settings)
|
|
602
|
+
|
|
603
|
+
def run(self, obs_ids):
|
|
604
|
+
all_obs_id_out = []
|
|
605
|
+
|
|
606
|
+
for obs_id in obs_ids:
|
|
607
|
+
if not self.db.has_obs_id(obs_id):
|
|
608
|
+
print(f'Error: Obs ID {obs_id} does not exist.')
|
|
609
|
+
return []
|
|
610
|
+
|
|
611
|
+
ms_files = self.db.get_obs_id_meta(obs_id).ms_files
|
|
612
|
+
|
|
613
|
+
if not ms_files:
|
|
614
|
+
print(f'Error: Obs ID {obs_id} has no MS files registered.')
|
|
615
|
+
return []
|
|
616
|
+
|
|
617
|
+
for ms_file in np.array(ms_files).flatten():
|
|
618
|
+
self.add_sub_task(AOFlaggerSubTask(self.settings, ms_file))
|
|
619
|
+
all_obs_id_out.append(obs_id)
|
|
620
|
+
|
|
621
|
+
self.execute()
|
|
622
|
+
|
|
623
|
+
return all_obs_id_out
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
class DP3SubTask(AbstractSubTask):
|
|
627
|
+
|
|
628
|
+
def __init__(self, settings_obj, ms_file):
|
|
629
|
+
AbstractSubTask.__init__(self, settings_obj, 'DP3')
|
|
630
|
+
self.ms_file = ms_file
|
|
631
|
+
|
|
632
|
+
def get_log_file(self):
|
|
633
|
+
date_str = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
|
634
|
+
return os.path.join(self.ms_file.replace('.MS', f'_dp3_{date_str}.log'))
|
|
635
|
+
|
|
636
|
+
def get_parameters(self):
|
|
637
|
+
return [self.settings.dp3.dp3_file, f'msin={self.ms_file}']
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
class DP3Task(AbstractTask):
|
|
641
|
+
|
|
642
|
+
name = 'dp3'
|
|
643
|
+
desc = 'Run DP3 on the Measurement Set(s)'
|
|
644
|
+
|
|
645
|
+
def __init__(self, settings_obj):
|
|
646
|
+
AbstractTask.__init__(self, 'DP3', settings_obj, max_concurrent=1)
|
|
647
|
+
self.db = database.Database(self.settings)
|
|
648
|
+
|
|
649
|
+
def run(self, obs_ids):
|
|
650
|
+
all_obs_id_out = []
|
|
651
|
+
|
|
652
|
+
for obs_id in obs_ids:
|
|
653
|
+
if not self.db.has_obs_id(obs_id):
|
|
654
|
+
print(f'Error: Obs ID {obs_id} does not exist.')
|
|
655
|
+
return []
|
|
656
|
+
|
|
657
|
+
ms_files = self.db.get_obs_id_meta(obs_id).ms_files
|
|
658
|
+
|
|
659
|
+
if not ms_files:
|
|
660
|
+
print(f'Error: Obs ID {obs_id} has no MS files registered.')
|
|
661
|
+
return []
|
|
662
|
+
|
|
663
|
+
for ms_file in np.array(ms_files).flatten():
|
|
664
|
+
self.add_sub_task(DP3SubTask(self.settings, ms_file))
|
|
665
|
+
all_obs_id_out.append(obs_id)
|
|
666
|
+
|
|
667
|
+
self.execute()
|
|
668
|
+
|
|
669
|
+
return all_obs_id_out
|
|
670
|
+
|
|
671
|
+
|
|
569
672
|
class MakePSSubTask(AbstractSubTask):
|
|
570
673
|
|
|
571
674
|
def __init__(self, settings_obj, obs_id, lst_idx=None):
|
|
@@ -575,7 +678,8 @@ class MakePSSubTask(AbstractSubTask):
|
|
|
575
678
|
self.vis_rev = database.VisRevision(self.settings)
|
|
576
679
|
|
|
577
680
|
def get_log_file(self):
|
|
578
|
-
|
|
681
|
+
date_str = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
|
682
|
+
return os.path.join(self.vis_rev.get_h5_path(self.obs_id, self.lst_idx), 'logs', f'make_ps_{date_str}.log')
|
|
579
683
|
|
|
580
684
|
def get_parameters(self):
|
|
581
685
|
h5_i = self.vis_rev.get_h5_file(self.obs_id, 'I', lst_idx=self.lst_idx)
|
|
@@ -633,7 +737,8 @@ class RunGPRSubTask(AbstractSubTask):
|
|
|
633
737
|
self.vis_rev = database.VisRevision(self.settings)
|
|
634
738
|
|
|
635
739
|
def get_log_file(self):
|
|
636
|
-
|
|
740
|
+
date_str = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
|
741
|
+
return os.path.join(self.vis_rev.get_path(), self.obs_id, 'logs', f'run_gpr_{date_str}.log')
|
|
637
742
|
|
|
638
743
|
def get_parameters(self):
|
|
639
744
|
h5_i = self.vis_rev.get_h5_file(self.obs_id, 'I', lst_idx=self.lst_idx)
|
|
@@ -699,13 +804,16 @@ class RunMLGPRSubTask(AbstractSubTask):
|
|
|
699
804
|
self.vis_rev = database.VisRevision(self.settings)
|
|
700
805
|
|
|
701
806
|
def get_log_file(self):
|
|
702
|
-
|
|
807
|
+
date_str = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
|
808
|
+
return os.path.join(self.vis_rev.get_path(), self.obs_id, 'logs', f'run_ml_gpr_{date_str}.log')
|
|
703
809
|
|
|
704
810
|
def get_parameters(self):
|
|
705
|
-
h5_i = self.vis_rev.get_h5_file(self.obs_id, 'I', lst_idx=self.lst_idx)
|
|
706
|
-
h5_v = self.vis_rev.get_h5_file(self.obs_id, 'V', lst_idx=self.lst_idx)
|
|
707
811
|
psc = self.settings.power_spectra
|
|
708
812
|
gpr = self.settings.ml_gpr
|
|
813
|
+
|
|
814
|
+
h5_i = self.vis_rev.get_h5_file(self.obs_id, gpr.stokes_i, lst_idx=self.lst_idx)
|
|
815
|
+
h5_v = self.vis_rev.get_h5_file(self.obs_id, gpr.stokes_v, lst_idx=self.lst_idx)
|
|
816
|
+
|
|
709
817
|
out_dir = os.path.join(self.vis_rev.get_h5_path(self.obs_id, self.lst_idx), gpr.name)
|
|
710
818
|
plot_out_dir = os.path.join(self.vis_rev.get_result_path(self.obs_id, self.lst_idx), gpr.name)
|
|
711
819
|
|
|
@@ -714,7 +822,7 @@ class RunMLGPRSubTask(AbstractSubTask):
|
|
|
714
822
|
args.append(f'-c {psc.get_path("ps_config")} -o {out_dir} -po {plot_out_dir}')
|
|
715
823
|
|
|
716
824
|
if gpr.use_v_dt_as_noise:
|
|
717
|
-
h5_v_dt = self.vis_rev.get_h5_file(self.obs_id,
|
|
825
|
+
h5_v_dt = self.vis_rev.get_h5_file(self.obs_id, gpr.stokes_dt, lst_idx=self.lst_idx)
|
|
718
826
|
args.append(f'--file_noise {h5_v_dt}')
|
|
719
827
|
|
|
720
828
|
if not gpr.plot_results:
|
|
@@ -756,6 +864,97 @@ class RunMLGPRTask(AbstractTask):
|
|
|
756
864
|
return obs_ids
|
|
757
865
|
|
|
758
866
|
|
|
867
|
+
class RunMLGPRInjSubTask(AbstractSubTask):
|
|
868
|
+
|
|
869
|
+
def __init__(self, settings_obj, obs_id, var_21_over_noise, x1, x2, lst_idx=None):
|
|
870
|
+
AbstractSubTask.__init__(self, settings_obj, 'pstool run_ml_gpr_inj')
|
|
871
|
+
self.obs_id = obs_id
|
|
872
|
+
self.lst_idx = lst_idx
|
|
873
|
+
self.var_21_over_noise = var_21_over_noise
|
|
874
|
+
self.x1 = x1
|
|
875
|
+
self.x2 = x2
|
|
876
|
+
self.vis_rev = database.VisRevision(self.settings)
|
|
877
|
+
|
|
878
|
+
def get_log_file(self):
|
|
879
|
+
date_str = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
|
880
|
+
return os.path.join(self.vis_rev.get_path(), self.obs_id, 'logs', f'run_ml_gpr_inj_{date_str}.log')
|
|
881
|
+
|
|
882
|
+
def get_parameters(self):
|
|
883
|
+
h5_i = self.vis_rev.get_h5_file(self.obs_id, 'I', lst_idx=self.lst_idx)
|
|
884
|
+
h5_v = self.vis_rev.get_h5_file(self.obs_id, 'V', lst_idx=self.lst_idx)
|
|
885
|
+
psc = self.settings.power_spectra
|
|
886
|
+
gpr = self.settings.ml_gpr
|
|
887
|
+
out_dir = os.path.join(self.vis_rev.get_h5_path(self.obs_id, self.lst_idx), gpr.name)
|
|
888
|
+
|
|
889
|
+
args = [h5_i, h5_v, gpr.get_path("config")]
|
|
890
|
+
args.append(f'-f {psc.get_path("flagger")} -e {psc.get_path("eor_bin_list")}')
|
|
891
|
+
args.append(f'-c {psc.get_path("ps_config")} -o {out_dir}')
|
|
892
|
+
|
|
893
|
+
if gpr.use_v_dt_as_noise:
|
|
894
|
+
h5_v_dt = self.vis_rev.get_h5_file(self.obs_id, 'dt_V', lst_idx=self.lst_idx)
|
|
895
|
+
args.append(f'--file_noise {h5_v_dt}')
|
|
896
|
+
|
|
897
|
+
args.append(f'-a {self.var_21_over_noise} --x1 {self.x1} --x2 {self.x2}')
|
|
898
|
+
|
|
899
|
+
if not gpr.plot_results:
|
|
900
|
+
args.append('--no_plot')
|
|
901
|
+
|
|
902
|
+
return args
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
class RunMLGPRInjTask(AbstractTask):
|
|
906
|
+
|
|
907
|
+
name = 'run_ml_gpr_inj'
|
|
908
|
+
desc = 'Run ML-GPR injection and produce power-spectra'
|
|
909
|
+
|
|
910
|
+
def __init__(self, settings_obj):
|
|
911
|
+
AbstractTask.__init__(self, 'ML-GPR', settings_obj)
|
|
912
|
+
self.vis_rev = database.VisRevision(self.settings)
|
|
913
|
+
self.img_rev = database.ImageRevision(self.settings)
|
|
914
|
+
|
|
915
|
+
def run(self, obs_ids):
|
|
916
|
+
if not (self.settings.validate('power_spectra') and self.settings.validate('gpr')):
|
|
917
|
+
return []
|
|
918
|
+
|
|
919
|
+
if not self.vis_rev.exist():
|
|
920
|
+
print(f'Error: visibility revision does not exist.')
|
|
921
|
+
return []
|
|
922
|
+
|
|
923
|
+
for obs_id in obs_ids:
|
|
924
|
+
if not self.vis_rev.has_obs_id(obs_id):
|
|
925
|
+
print(f'Error: visibility revision for obs ID {obs_id} not available')
|
|
926
|
+
return []
|
|
927
|
+
|
|
928
|
+
all_lst_idx = self.img_rev.get_lst_bins(obs_id).keys() if self.settings.image.lst_bins else [None]
|
|
929
|
+
|
|
930
|
+
for lst_idx in all_lst_idx:
|
|
931
|
+
var = self.settings.ml_gpr_inj.var_21_over_noise
|
|
932
|
+
x1s = self.settings.ml_gpr_inj.x1
|
|
933
|
+
x2s = self.settings.ml_gpr_inj.x2
|
|
934
|
+
x1_x2_file = self.settings.ml_gpr_inj.x1_x2_file
|
|
935
|
+
|
|
936
|
+
try:
|
|
937
|
+
if x1_x2_file:
|
|
938
|
+
x1_x2s = np.loadtxt(x1_x2_file, delimiter=',').tolist()
|
|
939
|
+
else:
|
|
940
|
+
x1_x2s = list(itertools.product(x1s, x2s))
|
|
941
|
+
except IOError as e:
|
|
942
|
+
print(f"Error loading file '{x1_x2_file}': {e}")
|
|
943
|
+
return []
|
|
944
|
+
except ValueError as e:
|
|
945
|
+
print(f"Error parsing file '{x1_x2_file}': {e}")
|
|
946
|
+
return []
|
|
947
|
+
|
|
948
|
+
for v, (x1, x2) in itertools.product(var, x1_x2s):
|
|
949
|
+
self.add_sub_task(RunMLGPRInjSubTask(self.settings, obs_id, v, x1, x2, lst_idx))
|
|
950
|
+
|
|
951
|
+
self.add_sub_task(RunMLGPRInjSubTask(self.settings, obs_id, 0, 0, 0, lst_idx))
|
|
952
|
+
|
|
953
|
+
self.execute()
|
|
954
|
+
|
|
955
|
+
return obs_ids
|
|
956
|
+
|
|
957
|
+
|
|
759
958
|
class CombineTask(AbstractTask):
|
|
760
959
|
|
|
761
960
|
name = 'combine'
|
|
@@ -842,7 +1041,8 @@ class CombineTask(AbstractTask):
|
|
|
842
1041
|
umax = self.settings.vis_cube.umax
|
|
843
1042
|
c = self.settings.combine
|
|
844
1043
|
|
|
845
|
-
|
|
1044
|
+
date_str = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
|
1045
|
+
out_log = os.path.join(self.get_path(), f'C{obs_id_out_prefix}_{date_str}.log')
|
|
846
1046
|
|
|
847
1047
|
if is_sph:
|
|
848
1048
|
cmd = (f'pstool combine_sph {self.get_combine_file()} -o {output_template} '
|
|
@@ -948,6 +1148,10 @@ class ApplyFlagSubTask(AbstractSubTask):
|
|
|
948
1148
|
self.ms_file = ms_file
|
|
949
1149
|
self.h5_file = h5_file
|
|
950
1150
|
|
|
1151
|
+
def get_log_file(self):
|
|
1152
|
+
date_str = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
|
1153
|
+
return os.path.join(self.ms_file.replace('.MS', f'apply_flags_{date_str}.log'))
|
|
1154
|
+
|
|
951
1155
|
def get_parameters(self):
|
|
952
1156
|
return [self.ms_file, self.h5_file]
|
|
953
1157
|
|
|
@@ -1027,12 +1231,13 @@ class DelayFlaggerSubTask(AbstractSubTask):
|
|
|
1027
1231
|
def __init__(self, settings_obj, ms_file):
|
|
1028
1232
|
AbstractSubTask.__init__(self, settings_obj, 'flagtool delay_flagger')
|
|
1029
1233
|
self.ms_file = ms_file
|
|
1234
|
+
self.date_str = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
|
1030
1235
|
|
|
1031
1236
|
def get_log_file(self):
|
|
1032
|
-
return os.path.join(self.ms_file.replace('.MS', '
|
|
1237
|
+
return os.path.join(self.ms_file.replace('.MS', f'_delay_flagger_{self.date_str}.log'))
|
|
1033
1238
|
|
|
1034
1239
|
def get_path(self):
|
|
1035
|
-
d = os.path.join(os.path.dirname(self.ms_file), '
|
|
1240
|
+
d = os.path.join(os.path.dirname(self.ms_file), f'flagger_{self.date_str}')
|
|
1036
1241
|
futils.mkdir(d)
|
|
1037
1242
|
return d
|
|
1038
1243
|
|
|
@@ -1076,6 +1281,55 @@ class DelayFlagger(AbstractTask):
|
|
|
1076
1281
|
return obs_ids
|
|
1077
1282
|
|
|
1078
1283
|
|
|
1284
|
+
class VisFlaggerSubTask(AbstractSubTask):
|
|
1285
|
+
|
|
1286
|
+
def __init__(self, settings_obj, ms_file):
|
|
1287
|
+
AbstractSubTask.__init__(self, settings_obj, 'flagtool vis_flagger')
|
|
1288
|
+
self.ms_file = ms_file
|
|
1289
|
+
self.date_str = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
|
1290
|
+
|
|
1291
|
+
def get_log_file(self):
|
|
1292
|
+
return os.path.join(self.ms_file.replace('.MS', f'{self.date_str}_vis_flagger.log'))
|
|
1293
|
+
|
|
1294
|
+
def get_path(self):
|
|
1295
|
+
d = os.path.join(os.path.dirname(self.ms_file), f'vis_flagger_{self.date_str}')
|
|
1296
|
+
futils.mkdir(d)
|
|
1297
|
+
return d
|
|
1298
|
+
|
|
1299
|
+
def get_parameters(self):
|
|
1300
|
+
return [self.ms_file, self.settings.vis_flagger.config_file, f'--plot_dir={self.get_path()}',
|
|
1301
|
+
'--backup', f'--backup_file={H5_FLAG_BACKUP}']
|
|
1302
|
+
|
|
1303
|
+
|
|
1304
|
+
class VisFlagger(AbstractTask):
|
|
1305
|
+
|
|
1306
|
+
name = 'vis_flagger'
|
|
1307
|
+
desc = 'Apply vis flagger on MS'
|
|
1308
|
+
|
|
1309
|
+
def __init__(self, settings_obj):
|
|
1310
|
+
AbstractTask.__init__(self, 'VisFlagger', settings_obj)
|
|
1311
|
+
self.db = database.Database(self.settings)
|
|
1312
|
+
|
|
1313
|
+
def run(self, obs_ids):
|
|
1314
|
+
for obs_id in obs_ids:
|
|
1315
|
+
if not self.db.has_obs_id(obs_id):
|
|
1316
|
+
print(f'Error: Obs ID {obs_id} does not exist.')
|
|
1317
|
+
return []
|
|
1318
|
+
|
|
1319
|
+
ms_files = self.db.get_obs_id_meta(obs_id).ms_files
|
|
1320
|
+
|
|
1321
|
+
if not ms_files:
|
|
1322
|
+
print(f'Error: Obs ID {obs_id} has no MS files registered.')
|
|
1323
|
+
return []
|
|
1324
|
+
|
|
1325
|
+
for ms_file in np.array(ms_files).flatten():
|
|
1326
|
+
self.add_sub_task(VisFlaggerSubTask(self.settings, ms_file))
|
|
1327
|
+
|
|
1328
|
+
self.execute()
|
|
1329
|
+
|
|
1330
|
+
return obs_ids
|
|
1331
|
+
|
|
1332
|
+
|
|
1079
1333
|
class SSINSSubTask(AbstractSubTask):
|
|
1080
1334
|
|
|
1081
1335
|
def __init__(self, settings_obj, ms_file):
|
|
@@ -21,6 +21,18 @@ flag_baselines = ''
|
|
|
21
21
|
avg_timestep = 1
|
|
22
22
|
time_start_index = 0
|
|
23
23
|
time_end_index = 0
|
|
24
|
+
numthreads = 0
|
|
25
|
+
memoryperc = 30
|
|
26
|
+
|
|
27
|
+
[ao_flag]
|
|
28
|
+
data_col = 'DATA'
|
|
29
|
+
numthreads = 0
|
|
30
|
+
memoryperc = 30
|
|
31
|
+
aoflagger_strategy = ''
|
|
32
|
+
|
|
33
|
+
[dp3]
|
|
34
|
+
numthreads = 0
|
|
35
|
+
dp3_file = ''
|
|
24
36
|
|
|
25
37
|
[ssins]
|
|
26
38
|
apply_ssins = true
|
|
@@ -45,6 +57,9 @@ n_times = 20
|
|
|
45
57
|
n_sigma_i = 6
|
|
46
58
|
n_sigma_v = 6
|
|
47
59
|
|
|
60
|
+
[vis_flagger]
|
|
61
|
+
config_file = ''
|
|
62
|
+
|
|
48
63
|
[image]
|
|
49
64
|
name = 'default'
|
|
50
65
|
umin = 50
|
|
@@ -64,6 +79,8 @@ clean_auto_threshold = 5
|
|
|
64
79
|
clean_mgain = 0.6
|
|
65
80
|
clean_residual = true
|
|
66
81
|
clean_spectra_fit_nterms = 4
|
|
82
|
+
taper_edge = 100
|
|
83
|
+
padding = 2
|
|
67
84
|
wsclean_args = {}
|
|
68
85
|
|
|
69
86
|
[vis_cube]
|
|
@@ -100,4 +117,13 @@ use_v_dt_as_noise = false
|
|
|
100
117
|
name = ''
|
|
101
118
|
config = ''
|
|
102
119
|
plot_results = true
|
|
120
|
+
stokes_i = 'I'
|
|
121
|
+
stokes_v = 'V'
|
|
122
|
+
stokes_dt = 'dt_v'
|
|
103
123
|
use_v_dt_as_noise = false
|
|
124
|
+
|
|
125
|
+
[ml_gpr_inj]
|
|
126
|
+
var_21_over_noise = [1]
|
|
127
|
+
x1 = [0]
|
|
128
|
+
x2 = [0]
|
|
129
|
+
x1_x2_file = ''
|
|
@@ -396,5 +396,59 @@ def remove_obs(settings_file, obs_ids):
|
|
|
396
396
|
print('No changes made.')
|
|
397
397
|
|
|
398
398
|
|
|
399
|
+
def concatenate_datacubes(cubes):
|
|
400
|
+
from ps_eor import datacube
|
|
401
|
+
|
|
402
|
+
key_fct = lambda a: a.freqs[0]
|
|
403
|
+
cubes = sorted(cubes, key=key_fct)
|
|
404
|
+
|
|
405
|
+
assert len(cubes) > 1
|
|
406
|
+
assert np.alltrue([isinstance(cube, datacube.DataCube) for cube in cubes])
|
|
407
|
+
assert np.alltrue([np.allclose(cubes[0].uu, cube.uu) for cube in cubes[1:]])
|
|
408
|
+
assert np.alltrue([np.allclose(cubes[0].vv, cube.vv) for cube in cubes[1:]])
|
|
409
|
+
|
|
410
|
+
data = np.concatenate([c.data for c in cubes])
|
|
411
|
+
weights_data = np.concatenate([c.weights.data for c in cubes])
|
|
412
|
+
freqs = np.concatenate([c.freqs for c in cubes])
|
|
413
|
+
weights = datacube.CartWeightCube(weights_data, cubes[0].uu, cubes[0].vv, freqs, cubes[0].meta)
|
|
414
|
+
|
|
415
|
+
return datacube.CartDataCube(data, cubes[0].uu, cubes[0].vv, freqs, cubes[0].meta, weights=weights)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
@main.command('concat_vis_cube')
|
|
419
|
+
@click.argument('settings_file', type=t_file)
|
|
420
|
+
@click.argument('obs_ids', type=str)
|
|
421
|
+
@click.argument('new_obs_id', type=str)
|
|
422
|
+
@click.option('--fmin', type=float, help='Min frequency in MHz', default=None)
|
|
423
|
+
@click.option('--fmax', type=float, help='Max frequency in MHz', default=None)
|
|
424
|
+
def concat_vis_cube(settings_file, obs_ids, new_obs_id, fmin, fmax):
|
|
425
|
+
''' Remove an Obs ID from the given pspipe database
|
|
426
|
+
'''
|
|
427
|
+
rev = database.VisRevision(load_settings(settings_file))
|
|
428
|
+
db = database.Database(load_settings(settings_file))
|
|
429
|
+
obs_id_set = list(db.filter_obs_ids(obs_ids))
|
|
430
|
+
|
|
431
|
+
if not obs_id_set:
|
|
432
|
+
print(f'Error: No obs_ids matching {obs_ids} in database.')
|
|
433
|
+
return
|
|
434
|
+
|
|
435
|
+
meta = db.get_obs_id_meta(obs_id_set[0])
|
|
436
|
+
|
|
437
|
+
add(db, new_obs_id, None, int_time=meta.int_time, total_time=meta.total_time, chan_width=meta.chan_width)
|
|
438
|
+
data1 = rev.get_data(obs_id_set[0])
|
|
439
|
+
|
|
440
|
+
all_stokes = [s for s in data1.all_stokes.values() if data1.has_stokes(s)]
|
|
441
|
+
|
|
442
|
+
for s in all_stokes:
|
|
443
|
+
data_cube = concatenate_datacubes([rev.get_data(obs_id).get_stokes(s) for obs_id in obs_id_set])
|
|
444
|
+
if fmin is not None and fmax is not None:
|
|
445
|
+
data_cube = data_cube.get_slice(fmin, fmax)
|
|
446
|
+
h5_filename = rev.get_h5_file(new_obs_id, s)
|
|
447
|
+
os.makedirs(os.path.dirname(h5_filename), exist_ok=True)
|
|
448
|
+
data_cube.save(h5_filename)
|
|
449
|
+
|
|
450
|
+
print(f'Combined data for {new_obs_id} successfully added.')
|
|
451
|
+
|
|
452
|
+
|
|
399
453
|
if __name__ == '__main__':
|
|
400
454
|
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "pspipe"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5"
|
|
4
4
|
description = "Power spectra pipeline for Cosmic Dawn, Epoch of Reionization radio interferometric experiments"
|
|
5
5
|
authors = ["\"Florent Mertens\" <\"florent.mertens@gmail.com\">"]
|
|
6
6
|
repository = "https://gitlab.com/flomertens/pspipe/"
|
pspipe-0.3/setup.py
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
from setuptools import setup
|
|
3
|
-
|
|
4
|
-
packages = \
|
|
5
|
-
['pspipe', 'pspipe.tests', 'pspipe.tools']
|
|
6
|
-
|
|
7
|
-
package_data = \
|
|
8
|
-
{'': ['*'], 'pspipe': ['templates/*', 'templates/config/*']}
|
|
9
|
-
|
|
10
|
-
install_requires = \
|
|
11
|
-
['matplotlib>=3.0,<4.0',
|
|
12
|
-
'nenucal>=0.1.11',
|
|
13
|
-
'ps-eor>=0.8',
|
|
14
|
-
'python-casacore>=3.0,<4.0',
|
|
15
|
-
'tables>=3.2,<4.0',
|
|
16
|
-
'tabulate>=0.8',
|
|
17
|
-
'toml>=0.10']
|
|
18
|
-
|
|
19
|
-
extras_require = \
|
|
20
|
-
{':python_version >= "3.7" and python_version < "3.8"': ['astropy>=4.0,<5.0',
|
|
21
|
-
'scipy>=1.6,<2.0'],
|
|
22
|
-
':python_version >= "3.8"': ['astropy>=5.0,<6.0', 'scipy>=1.8,<2.0']}
|
|
23
|
-
|
|
24
|
-
entry_points = \
|
|
25
|
-
{'console_scripts': ['psdb = pspipe.tools.psdb:main',
|
|
26
|
-
'pspipe = pspipe.tools.pspipe:main']}
|
|
27
|
-
|
|
28
|
-
setup_kwargs = {
|
|
29
|
-
'name': 'pspipe',
|
|
30
|
-
'version': '0.3',
|
|
31
|
-
'description': 'Power spectra pipeline for Cosmic Dawn, Epoch of Reionization radio interferometric experiments',
|
|
32
|
-
'long_description': 'Power Spectra Generation pipeline for CD/EoR experiments\n========================================================\n\npspipe is an analysis pipeline which aims to make generation of high precision power-spectra for Cosmic Dawn and Epoch of Reionization experiments as easy and reproducible as possible. pspipe is the default analysis pipeline of the [Lofar-EoR](http://www.lofar.org/astronomy/eor-ksp/epoch-reionization.html) and [NenuFAR](https://nenufar.obs-nancay.fr/en/homepage-en/) CD projects. It performs all tasks from calibrated data to final the power-spectra, including:\n\n- Post-calibration flagging (using [AOflagger](https://sourceforge.net/p/aoflagger/wiki/Home/) and [SSINS](https://arxiv.org/abs/1906.01093))\n- Visibility gridding (using [WSClean](https://sourceforge.net/p/wsclean/wiki/Home/))\n- Foregrounds modeling and removal (using [GPR](https://doi.org/10.1093/mnras/sty1207), [GMCA](https://arxiv.org/abs/1209.4769), polynomial fitting)\n- Optimal power-spectra generation (using [ps_eor](https://gitlab.com/flomertens/ps_eor))\n\nThanks to its multi-nodes concurrent processing, pspipe is also very fast at crunching hundreds of hour of calibrated data.\n\nInstallation\n------------\n\npspipe can be installed via pip:\n\n $ pip install pspipe\n\nand requires Python 3.7.0 or higher. For certain tasks, you will also need [DPPP](https://github.com/lofar-astron/DP3/tree/master/DPPP) and [WSClean](https://sourceforge.net/p/wsclean/wiki/Home/)\n\nDocumentation\n--------------\n\nPlease check the [wiki page](https://gitlab.com/flomertens/pspipe/-/wikis/home).\n',
|
|
33
|
-
'author': '"Florent Mertens"',
|
|
34
|
-
'author_email': '"florent.mertens@gmail.com"',
|
|
35
|
-
'maintainer': 'None',
|
|
36
|
-
'maintainer_email': 'None',
|
|
37
|
-
'url': 'https://gitlab.com/flomertens/pspipe/',
|
|
38
|
-
'packages': packages,
|
|
39
|
-
'package_data': package_data,
|
|
40
|
-
'install_requires': install_requires,
|
|
41
|
-
'extras_require': extras_require,
|
|
42
|
-
'entry_points': entry_points,
|
|
43
|
-
'python_requires': '>=3.7,<4.0',
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
setup(**setup_kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|