nf2 0.3__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.
- nf2/__init__.py +0 -0
- nf2/convert/__init__.py +0 -0
- nf2/convert/convert_series.py +64 -0
- nf2/convert/muram_to_vtk.py +33 -0
- nf2/convert/nf2_to_fits.py +45 -0
- nf2/convert/nf2_to_hdf5.py +62 -0
- nf2/convert/nf2_to_npy.py +37 -0
- nf2/convert/nf2_to_vtk.py +62 -0
- nf2/convert/nf2_to_vtk_spherical.py +51 -0
- nf2/convert/potential_vtk.py +118 -0
- nf2/data/__init__.py +0 -0
- nf2/data/analytical_field.py +137 -0
- nf2/data/combine_sst_hmi.py +165 -0
- nf2/data/convert_coordinates.py +19 -0
- nf2/data/convert_muram_slices.py +43 -0
- nf2/data/dataset.py +231 -0
- nf2/data/download.py +56 -0
- nf2/data/download_euv.py +32 -0
- nf2/data/download_full_disk.py +54 -0
- nf2/data/download_range.py +44 -0
- nf2/data/download_sharp.py +26 -0
- nf2/data/download_synoptic.py +29 -0
- nf2/data/get_coordinate_bounds.py +31 -0
- nf2/data/loader.py +65 -0
- nf2/data/noaa_to_sharp.py +26 -0
- nf2/data/sst/__init__.py +0 -0
- nf2/data/sst/mask.py +32 -0
- nf2/data/sst/prep.py +160 -0
- nf2/data/util.py +105 -0
- nf2/evaluation/__init__.py +0 -0
- nf2/evaluation/analytical/__init__.py +0 -0
- nf2/evaluation/analytical/corrugation.py +72 -0
- nf2/evaluation/analytical/metrics.py +28 -0
- nf2/evaluation/compute_metrics.py +31 -0
- nf2/evaluation/current_series.py +40 -0
- nf2/evaluation/energy.py +11 -0
- nf2/evaluation/metric.py +144 -0
- nf2/evaluation/mfr/__init__.py +0 -0
- nf2/evaluation/muram/__init__.py +0 -0
- nf2/evaluation/muram/compare_boundary.py +64 -0
- nf2/evaluation/muram/compare_currents.py +216 -0
- nf2/evaluation/muram/corrugation.py +136 -0
- nf2/evaluation/muram/cube_height.py +47 -0
- nf2/evaluation/muram/height_ff.py +115 -0
- nf2/evaluation/muram/height_metrics.py +124 -0
- nf2/evaluation/muram/metrics.py +24 -0
- nf2/evaluation/muram/potential_metrics.py +26 -0
- nf2/evaluation/muram/slices.py +36 -0
- nf2/evaluation/output.py +423 -0
- nf2/evaluation/output_metrics.py +328 -0
- nf2/evaluation/parameters.py +58 -0
- nf2/evaluation/plot_carrington_map.py +48 -0
- nf2/evaluation/plot_euv_comparison.py +30 -0
- nf2/evaluation/plot_kso_comparison.py +42 -0
- nf2/evaluation/sharp/__init__.py +0 -0
- nf2/evaluation/sharp/convert_series.py +134 -0
- nf2/evaluation/sharp/ensemble.py +102 -0
- nf2/evaluation/sharp/flares.py +171 -0
- nf2/evaluation/sharp/plot_current_series.py +134 -0
- nf2/evaluation/sharp/plot_euv_lightcurve.py +68 -0
- nf2/evaluation/sharp/plot_integrated_energy.py +242 -0
- nf2/evaluation/sharp/plot_integrated_quantities.py +138 -0
- nf2/evaluation/sharp/plot_maps.py +120 -0
- nf2/evaluation/sharp/plot_quality.py +160 -0
- nf2/evaluation/sharp/plot_separatrix.py +107 -0
- nf2/evaluation/sharp/video.py +104 -0
- nf2/evaluation/solis/__init__.py +0 -0
- nf2/evaluation/solis/plot_twist_maps.py +52 -0
- nf2/evaluation/solis/vsm_corrugation.py +89 -0
- nf2/evaluation/spherical/__init__.py +0 -0
- nf2/evaluation/spherical/b_diff.py +86 -0
- nf2/evaluation/spherical/b_diff_synoptic.py +81 -0
- nf2/evaluation/spherical/compare.py +314 -0
- nf2/evaluation/spherical/current_change.py +120 -0
- nf2/evaluation/spherical/currents.py +286 -0
- nf2/evaluation/spherical/disk.py +159 -0
- nf2/evaluation/spherical/energy_diff.py +66 -0
- nf2/evaluation/spherical/energy_gradient.py +74 -0
- nf2/evaluation/spherical/free_energy.py +206 -0
- nf2/evaluation/spherical/input_plot.py +20 -0
- nf2/evaluation/spherical/open_fields.py +123 -0
- nf2/evaluation/spherical/traces.py +104 -0
- nf2/evaluation/sst/__init__.py +0 -0
- nf2/evaluation/sst/compare_muram_mfr.py +180 -0
- nf2/evaluation/sst/compare_muram_sma.py +178 -0
- nf2/evaluation/sst/compare_sharp_disambiguation.py +10 -0
- nf2/evaluation/sst/compare_sst.py +237 -0
- nf2/evaluation/sst/compare_sst_eruption.py +208 -0
- nf2/evaluation/sst/compare_sst_full.py +251 -0
- nf2/evaluation/sst/plot_data.py +99 -0
- nf2/evaluation/unpack.py +190 -0
- nf2/evaluation/vtk.py +51 -0
- nf2/extrapolate.py +127 -0
- nf2/extrapolate_series.py +173 -0
- nf2/loader/__init__.py +0 -0
- nf2/loader/analytical.py +151 -0
- nf2/loader/base.py +337 -0
- nf2/loader/fits.py +310 -0
- nf2/loader/general.py +22 -0
- nf2/loader/muram.py +214 -0
- nf2/loader/spherical.py +396 -0
- nf2/loader/util.py +133 -0
- nf2/loader/vsm.py +17 -0
- nf2/potential/__init__.py +0 -0
- nf2/potential/potential_field.py +132 -0
- nf2/train/__init__.py +0 -0
- nf2/train/callback.py +695 -0
- nf2/train/config_from_template.py +28 -0
- nf2/train/extrapolate_analytic.py +91 -0
- nf2/train/loss.py +403 -0
- nf2/train/mapping.py +40 -0
- nf2/train/model.py +440 -0
- nf2/train/module.py +338 -0
- nf2/train/transform.py +44 -0
- nf2/train/util.py +14 -0
- nf2-0.3.dist-info/LICENSE +674 -0
- nf2-0.3.dist-info/METADATA +23 -0
- nf2-0.3.dist-info/RECORD +121 -0
- nf2-0.3.dist-info/WHEEL +5 -0
- nf2-0.3.dist-info/entry_points.txt +10 -0
- nf2-0.3.dist-info/top_level.txt +1 -0
nf2/__init__.py
ADDED
|
File without changes
|
nf2/convert/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import glob
|
|
3
|
+
import os.path
|
|
4
|
+
|
|
5
|
+
from tqdm import tqdm
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
parser = argparse.ArgumentParser(description='Convert NF2 file to VTK.')
|
|
10
|
+
parser.add_argument('--nf2_dir', type=str, help='path to the source NF2 files', nargs='+', required=True)
|
|
11
|
+
parser.add_argument('--out_dir', type=str, help='path to the target VTK directory', required=False, default=None)
|
|
12
|
+
parser.add_argument('--Mm_per_pixel', type=float, help='spatial resolution (0.36 for original HMI)', required=False,
|
|
13
|
+
default=None)
|
|
14
|
+
parser.add_argument('--height_range', type=float, nargs=2, help='height range in Mm', required=False, default=None)
|
|
15
|
+
parser.add_argument('--metrics', type=str, nargs='*', help='metrics to be computed', required=False, default=['j'])
|
|
16
|
+
parser.add_argument('--type', type=str, help='type of the conversion (vtk, hdf5, npy, fits)', required=False,
|
|
17
|
+
default='vtk')
|
|
18
|
+
parser.add_argument('--overwrite', action='store_true', help='overwrite existing files', required=False,
|
|
19
|
+
default=False)
|
|
20
|
+
|
|
21
|
+
args = parser.parse_args()
|
|
22
|
+
|
|
23
|
+
nf2_dir = args.nf2_dir if isinstance(args.nf2_dir, list) else [args.nf2_dir]
|
|
24
|
+
nf2_paths = [sorted(glob.glob(f)) for f in nf2_dir]
|
|
25
|
+
nf2_paths = [f for files in nf2_paths for f in files] # flatten list
|
|
26
|
+
|
|
27
|
+
Mm_per_pixel = args.Mm_per_pixel
|
|
28
|
+
out_dir = args.out_dir if args.out_dir is not None else os.path.dirname(nf2_dir[0])
|
|
29
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
30
|
+
height_range = args.height_range
|
|
31
|
+
metrics = args.metrics
|
|
32
|
+
|
|
33
|
+
conversion_type = args.type
|
|
34
|
+
if conversion_type == 'vtk':
|
|
35
|
+
d_type = '.vtk'
|
|
36
|
+
from nf2.convert import nf2_to_vtk
|
|
37
|
+
convert_f = nf2_to_vtk.convert
|
|
38
|
+
elif conversion_type == 'hdf5':
|
|
39
|
+
d_type = '.hdf5'
|
|
40
|
+
from nf2.convert import nf2_to_hdf5
|
|
41
|
+
convert_f = nf2_to_hdf5.convert
|
|
42
|
+
elif conversion_type == 'npy':
|
|
43
|
+
d_type = '.npy'
|
|
44
|
+
from nf2.convert import nf2_to_npy
|
|
45
|
+
convert_f = nf2_to_npy.convert
|
|
46
|
+
elif conversion_type == 'fits':
|
|
47
|
+
d_type = '.fits'
|
|
48
|
+
from nf2.convert import nf2_to_fits
|
|
49
|
+
convert_f = nf2_to_fits.convert
|
|
50
|
+
else:
|
|
51
|
+
raise ValueError(f'Unknown conversion type: {conversion_type}')
|
|
52
|
+
|
|
53
|
+
for nf2_path in tqdm(nf2_paths, desc='Converting', unit='file'):
|
|
54
|
+
out_file = os.path.join(out_dir, os.path.basename(nf2_path).replace('.nf2', d_type))
|
|
55
|
+
if os.path.exists(out_file) and not args.overwrite:
|
|
56
|
+
print(f'File exists: {out_file}')
|
|
57
|
+
continue
|
|
58
|
+
convert_f(nf2_path=nf2_path, out_path=out_file,
|
|
59
|
+
Mm_per_pixel=Mm_per_pixel, height_range=height_range, metrics=metrics,
|
|
60
|
+
progress=False)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == '__main__':
|
|
64
|
+
main()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from astropy import constants as const
|
|
5
|
+
from astropy import units as u
|
|
6
|
+
|
|
7
|
+
from nf2.evaluation.metric import curl
|
|
8
|
+
from nf2.evaluation.vtk import save_vtk
|
|
9
|
+
from nf2.loader.muram import MURaMSnapshot
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
parser = argparse.ArgumentParser(description='Convert NF2 file to VTK.')
|
|
14
|
+
parser.add_argument('--source_path', type=str, help='path to the MURaM simulation.')
|
|
15
|
+
parser.add_argument('--iteration', type=int, help='iteration of the snapshot.')
|
|
16
|
+
parser.add_argument('--vtk_path', type=str, help='path to the target VTK file', required=False, default=None)
|
|
17
|
+
parser.add_argument('--Mm_per_pixel', type=float, help='spatial resolution (needs to be a multiple of 0.192)',
|
|
18
|
+
required=False, default=0.192 * 4)
|
|
19
|
+
parser.add_argument('--height', type=float, help='height of the snapshot', required=False, default=None)
|
|
20
|
+
args = parser.parse_args()
|
|
21
|
+
|
|
22
|
+
snapshot = MURaMSnapshot(args.source_path, args.iteration)
|
|
23
|
+
muram_cube = snapshot.load_cube(args.Mm_per_pixel * u.Mm / u.pix, target_tau=1.0, height=args.height * u.Mm)
|
|
24
|
+
b = muram_cube['B']
|
|
25
|
+
j = curl(b) * u.G / (args.Mm_per_pixel * u.Mm) * const.c / (4 * np.pi) # Mm_per_pixel
|
|
26
|
+
j = j.to(u.G / u.s)
|
|
27
|
+
|
|
28
|
+
save_vtk(args.vtk_path, vectors={'b': b, 'j': j.value},
|
|
29
|
+
scalars={'tau': muram_cube['tau']}, Mm_per_pix=args.Mm_per_pixel)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == '__main__':
|
|
33
|
+
main()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from astropy.io import fits
|
|
5
|
+
|
|
6
|
+
from nf2.evaluation.output import CartesianOutput
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def convert(nf2_path, out_path=None, Mm_per_pixel=None, height_range=None, **kwargs):
|
|
10
|
+
out_path = out_path if out_path is not None \
|
|
11
|
+
else os.path.join(os.path.dirname(nf2_path), nf2_path.split(os.sep)[-2] + '.hdf5')
|
|
12
|
+
|
|
13
|
+
nf2_out = CartesianOutput(nf2_path)
|
|
14
|
+
output = nf2_out.load_cube(Mm_per_pixel=Mm_per_pixel, height_range=height_range, **kwargs)
|
|
15
|
+
|
|
16
|
+
b = output['b']
|
|
17
|
+
j = output['j']
|
|
18
|
+
|
|
19
|
+
header = {'Mm_per_pix': output['Mm_per_pixel'],
|
|
20
|
+
'data': nf2_out.data_config,
|
|
21
|
+
'wcs': nf2_out.wcs,
|
|
22
|
+
'DATE_OBS': nf2_out.time}
|
|
23
|
+
|
|
24
|
+
b_hdu = fits.PrimaryHDU(b, header=header)
|
|
25
|
+
j_hdu = fits.FitsHDU(j)
|
|
26
|
+
hdul = fits.HDUList([b_hdu, j_hdu])
|
|
27
|
+
hdul.writeto(out_path)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main():
|
|
31
|
+
parser = argparse.ArgumentParser(description='Convert NF2 file to fits.')
|
|
32
|
+
parser.add_argument('nf2_path', type=str, help='path to the source NF2 file')
|
|
33
|
+
parser.add_argument('--out_path', type=str, help='path to the target numpy file', required=False, default=None)
|
|
34
|
+
parser.add_argument('--strides', type=int, help='downsampling of the volume', required=False, default=1)
|
|
35
|
+
|
|
36
|
+
args = parser.parse_args()
|
|
37
|
+
nf2_path = args.nf2_path
|
|
38
|
+
strides = args.strides
|
|
39
|
+
out_path = args.out_path
|
|
40
|
+
|
|
41
|
+
convert(nf2_path, out_path, strides)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if __name__ == '__main__':
|
|
45
|
+
main()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os.path
|
|
3
|
+
from threading import Thread
|
|
4
|
+
|
|
5
|
+
import h5py
|
|
6
|
+
|
|
7
|
+
from nf2.evaluation.output import CartesianOutput
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _SaveFileTask(Thread):
|
|
11
|
+
|
|
12
|
+
def __init__(self, out_path, output, nf2_out):
|
|
13
|
+
super().__init__()
|
|
14
|
+
self.out_path = out_path
|
|
15
|
+
self.output = output
|
|
16
|
+
self.nf2_out = nf2_out
|
|
17
|
+
|
|
18
|
+
def run(self):
|
|
19
|
+
f = h5py.File(self.out_path, 'w')
|
|
20
|
+
f.create_dataset('B', data=self.output['b'], dtype='f4', compression='gzip')
|
|
21
|
+
f.attrs['INFO'] = f'Magnetic vector field (B_x, B_y, B_z): {self.output["b"].shape} (x, y, z, 3)'
|
|
22
|
+
f.attrs['Mm_per_pixel'] = self.output['Mm_per_pixel']
|
|
23
|
+
f.attrs['type'] = self.nf2_out.data_config['type']
|
|
24
|
+
f.attrs['wcs'] = self.nf2_out.wcs[0].to_header_string()
|
|
25
|
+
f.attrs['time'] = self.nf2_out.time.isoformat('T', timespec='seconds')
|
|
26
|
+
f.close()
|
|
27
|
+
print('File saved:', self.out_path)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def convert(nf2_path, out_path=None, Mm_per_pixel=None, height_range=None, **kwargs):
|
|
31
|
+
out_path = out_path if out_path is not None \
|
|
32
|
+
else os.path.join(os.path.dirname(nf2_path), nf2_path.split(os.sep)[-2] + '.hdf5')
|
|
33
|
+
|
|
34
|
+
nf2_out = CartesianOutput(nf2_path)
|
|
35
|
+
output = nf2_out.load_cube(Mm_per_pixel=Mm_per_pixel, height_range=height_range, **kwargs)
|
|
36
|
+
|
|
37
|
+
# save file in background
|
|
38
|
+
task = _SaveFileTask(out_path, output, nf2_out)
|
|
39
|
+
task.start()
|
|
40
|
+
return task
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def main():
|
|
44
|
+
parser = argparse.ArgumentParser(description='Convert NF2 file to VTK.')
|
|
45
|
+
parser.add_argument('--nf2_path', type=str, help='path to the source NF2 file')
|
|
46
|
+
parser.add_argument('--out_path', type=str, help='path to the target HDF5 file', required=False, default=None)
|
|
47
|
+
parser.add_argument('--Mm_per_pixel', type=float, help='spatial resolution (0.36 for original HMI)', required=False,
|
|
48
|
+
default=None)
|
|
49
|
+
parser.add_argument('--height_range', type=float, nargs=2, help='height range in Mm', required=False, default=None)
|
|
50
|
+
|
|
51
|
+
args = parser.parse_args()
|
|
52
|
+
nf2_path = args.nf2_path
|
|
53
|
+
|
|
54
|
+
Mm_per_pixel = args.Mm_per_pixel
|
|
55
|
+
out_path = args.out_path
|
|
56
|
+
height_range = args.height_range
|
|
57
|
+
|
|
58
|
+
convert(nf2_path, out_path, Mm_per_pixel, height_range)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == '__main__':
|
|
62
|
+
main()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os.path
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from nf2.evaluation.output import CartesianOutput
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def convert(nf2_path, out_path=None, Mm_per_pixel=None, height_range=None, **kwargs):
|
|
10
|
+
out_path = out_path if out_path is not None \
|
|
11
|
+
else os.path.join(os.path.dirname(nf2_path), nf2_path.split(os.sep)[-2] + '.npy')
|
|
12
|
+
|
|
13
|
+
nf2_out = CartesianOutput(nf2_path)
|
|
14
|
+
output = nf2_out.load_cube(Mm_per_pixel=Mm_per_pixel, height_range=height_range, **kwargs)
|
|
15
|
+
np.save(out_path, output['b'])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main():
|
|
19
|
+
parser = argparse.ArgumentParser(description='Convert NF2 file to VTK.')
|
|
20
|
+
parser.add_argument('--nf2_path', type=str, help='path to the source NF2 file')
|
|
21
|
+
parser.add_argument('--out_path', type=str, help='path to the target NPY file', required=False, default=None)
|
|
22
|
+
parser.add_argument('--Mm_per_pixel', type=float, help='spatial resolution (0.36 for original HMI)', required=False,
|
|
23
|
+
default=None)
|
|
24
|
+
parser.add_argument('--height_range', type=float, nargs=2, help='height range in Mm', required=False, default=None)
|
|
25
|
+
|
|
26
|
+
args = parser.parse_args()
|
|
27
|
+
nf2_path = args.nf2_path
|
|
28
|
+
|
|
29
|
+
Mm_per_pixel = args.Mm_per_pixel
|
|
30
|
+
out_path = args.out_path
|
|
31
|
+
height_range = args.height_range
|
|
32
|
+
|
|
33
|
+
convert(nf2_path, out_path, Mm_per_pixel, height_range)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == '__main__':
|
|
37
|
+
main()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os.path
|
|
3
|
+
from threading import Thread
|
|
4
|
+
|
|
5
|
+
from nf2.evaluation.output import CartesianOutput
|
|
6
|
+
from nf2.evaluation.vtk import save_vtk
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _SaveFileTask(Thread):
|
|
10
|
+
|
|
11
|
+
def __init__(self, out_path, output, metrics):
|
|
12
|
+
super().__init__()
|
|
13
|
+
self.out_path = out_path
|
|
14
|
+
self.output = output
|
|
15
|
+
self.metrics = metrics if metrics is not None else []
|
|
16
|
+
|
|
17
|
+
def run(self):
|
|
18
|
+
Mm_per_pixel = self.output['Mm_per_pixel']
|
|
19
|
+
|
|
20
|
+
# split output into vectors and scalars
|
|
21
|
+
vectors = {k: v for k, v in self.output['metrics'].items() if len(v.shape) == 4 and v.shape[-1] == 3}
|
|
22
|
+
scalars = {k: v for k, v in self.output['metrics'].items() if len(v.shape) == 3}
|
|
23
|
+
|
|
24
|
+
vectors['b'] = self.output['b']
|
|
25
|
+
|
|
26
|
+
save_vtk(self.out_path, vectors=vectors, scalars=scalars, Mm_per_pix=Mm_per_pixel)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def convert(nf2_path, out_path=None, Mm_per_pixel=None, height_range=None, metrics=None, **kwargs):
|
|
30
|
+
out_path = out_path if out_path is not None \
|
|
31
|
+
else os.path.join(os.path.dirname(nf2_path), nf2_path.split(os.sep)[-2] + '.vtk')
|
|
32
|
+
|
|
33
|
+
nf2_out = CartesianOutput(nf2_path)
|
|
34
|
+
output = nf2_out.load_cube(Mm_per_pixel=Mm_per_pixel, height_range=height_range, metrics=metrics, **kwargs)
|
|
35
|
+
|
|
36
|
+
# save file in background
|
|
37
|
+
task = _SaveFileTask(out_path, output, metrics)
|
|
38
|
+
task.start()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main():
|
|
42
|
+
parser = argparse.ArgumentParser(description='Convert NF2 file to VTK.')
|
|
43
|
+
parser.add_argument('--nf2_path', type=str, help='path to the source NF2 file')
|
|
44
|
+
parser.add_argument('--out_path', type=str, help='path to the target VTK file', required=False, default=None)
|
|
45
|
+
parser.add_argument('--Mm_per_pixel', type=float, help='spatial resolution (0.36 for original HMI)', required=False,
|
|
46
|
+
default=None)
|
|
47
|
+
parser.add_argument('--height_range', type=float, nargs=2, help='height range in Mm', required=False, default=None)
|
|
48
|
+
parser.add_argument('--metrics', type=str, nargs='*', help='metrics to be computed', required=False, default=['j'])
|
|
49
|
+
|
|
50
|
+
args = parser.parse_args()
|
|
51
|
+
nf2_path = args.nf2_path
|
|
52
|
+
|
|
53
|
+
Mm_per_pixel = args.Mm_per_pixel
|
|
54
|
+
out_path = args.out_path
|
|
55
|
+
height_range = args.height_range
|
|
56
|
+
metrics = args.metrics
|
|
57
|
+
|
|
58
|
+
convert(nf2_path, out_path, Mm_per_pixel, height_range, metrics=metrics, progress=True)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == '__main__':
|
|
62
|
+
main()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from astropy import units as u
|
|
6
|
+
|
|
7
|
+
from nf2.evaluation.output import SphericalOutput
|
|
8
|
+
from nf2.evaluation.output_metrics import current_density, alpha
|
|
9
|
+
from nf2.evaluation.vtk import save_vtk
|
|
10
|
+
|
|
11
|
+
if __name__ == '__main__':
|
|
12
|
+
parser = argparse.ArgumentParser(description='Convert NF2 file to VTK.')
|
|
13
|
+
parser.add_argument('--nf2_path', type=str, help='path to the source NF2 file')
|
|
14
|
+
parser.add_argument('--out_path', type=str, help='path to the target VTK files', required=False, default=None)
|
|
15
|
+
parser.add_argument('--overwrite', action='store_true', help='overwrite existing files')
|
|
16
|
+
|
|
17
|
+
parser.add_argument('--radius_range', nargs='+', type=float, default=(0.999, 1.3), required=False)
|
|
18
|
+
parser.add_argument('--latitude_range', nargs='+', type=float, default=(0, 180), required=False)
|
|
19
|
+
parser.add_argument('--longitude_range', nargs='+', type=float, default=(0, 360), required=False)
|
|
20
|
+
parser.add_argument('--radians', action='store_true', help='latitude and longitude in radians', required=False,
|
|
21
|
+
default=False)
|
|
22
|
+
parser.add_argument('--pixels_per_solRad', type=int, default=64, required=False)
|
|
23
|
+
|
|
24
|
+
args = parser.parse_args()
|
|
25
|
+
nf2_path = args.nf2_path
|
|
26
|
+
out_path = nf2_path.replace('.nf2', '.vtk') if args.out_path is None else args.out_path
|
|
27
|
+
|
|
28
|
+
radius_range = tuple(args.radius_range) * u.solRad
|
|
29
|
+
if args.radians:
|
|
30
|
+
latitude_range = tuple(args.latitude_range) * u.rad
|
|
31
|
+
longitude_range = tuple(args.longitude_range) * u.rad
|
|
32
|
+
else:
|
|
33
|
+
latitude_range = tuple(args.latitude_range) * u.deg
|
|
34
|
+
longitude_range = tuple(args.longitude_range) * u.deg
|
|
35
|
+
pixels_per_solRad = args.pixels_per_solRad * u.pix / u.solRad
|
|
36
|
+
|
|
37
|
+
assert len(radius_range) == 2, 'radius_range must be a tuple of length 2'
|
|
38
|
+
assert len(latitude_range) == 2, 'latitude_range must be a tuple of length 2'
|
|
39
|
+
assert len(longitude_range) == 2, 'longitude_range must be a tuple of length 2'
|
|
40
|
+
|
|
41
|
+
output = SphericalOutput(nf2_path)
|
|
42
|
+
result = output.load(radius_range, latitude_range, longitude_range, pixels_per_solRad, progress=True,
|
|
43
|
+
metrics={'j': current_density, 'alpha': alpha})
|
|
44
|
+
|
|
45
|
+
vectors = {'B': result['b'], 'B_rtp': result['b_rtp']}
|
|
46
|
+
radius = result['spherical_coords'][..., 0]
|
|
47
|
+
metrics = result['metrics']
|
|
48
|
+
scalars = {'radius': radius, 'current_density': np.sum(metrics['j'] ** 2, -1) ** 0.5, 'alpha': metrics['alpha']}
|
|
49
|
+
coords = result['coords']
|
|
50
|
+
|
|
51
|
+
save_vtk(out_path, coords, vectors, scalars)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pfsspy
|
|
6
|
+
import torch
|
|
7
|
+
from astropy import units as u
|
|
8
|
+
from astropy.coordinates import SkyCoord
|
|
9
|
+
from matplotlib import pyplot as plt
|
|
10
|
+
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
|
11
|
+
from sunpy.map import Map
|
|
12
|
+
|
|
13
|
+
from nf2.data.util import cartesian_to_spherical, spherical_to_cartesian, vector_spherical_to_cartesian
|
|
14
|
+
from nf2.evaluation.vtk import save_vtk
|
|
15
|
+
|
|
16
|
+
parser = argparse.ArgumentParser(description='Convert NF2 file to VTK.')
|
|
17
|
+
parser.add_argument('--Br', type=str, help='path to the source radial magnetic field file (full disk map)')
|
|
18
|
+
parser.add_argument('--synoptic', type=str, help='path to the source radial magnetic field file (synoptic map)')
|
|
19
|
+
parser.add_argument('--out_path', type=str, help='path to the target VTK files')
|
|
20
|
+
|
|
21
|
+
parser.add_argument('--radius_range', nargs='+', type=float, default=(0.999, 1.5), required=False)
|
|
22
|
+
parser.add_argument('--latitude_range', nargs='+', type=float, default=(0 * np.pi, 1 * np.pi), required=False)
|
|
23
|
+
parser.add_argument('--longitude_range', nargs='+', type=float, default=(0 * np.pi, 2 * np.pi), required=False)
|
|
24
|
+
parser.add_argument('--pixels_per_solRad', type=int, default=64, required=False)
|
|
25
|
+
|
|
26
|
+
args = parser.parse_args()
|
|
27
|
+
out_path = args.out_path
|
|
28
|
+
|
|
29
|
+
os.makedirs(out_path, exist_ok=True)
|
|
30
|
+
|
|
31
|
+
radius_range = tuple(args.radius_range)
|
|
32
|
+
latitude_range = tuple(args.latitude_range)
|
|
33
|
+
longitude_range = tuple(args.longitude_range)
|
|
34
|
+
pixels_per_solRad = args.pixels_per_solRad
|
|
35
|
+
|
|
36
|
+
assert len(radius_range) == 2, 'radius_range must be a tuple of length 2'
|
|
37
|
+
assert len(latitude_range) == 2, 'latitude_range must be a tuple of length 2'
|
|
38
|
+
assert len(longitude_range) == 2, 'longitude_range must be a tuple of length 2'
|
|
39
|
+
|
|
40
|
+
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')
|
|
41
|
+
|
|
42
|
+
synoptic_map_br = Map(args.synoptic)
|
|
43
|
+
synoptic_map_br = synoptic_map_br.resample([360 * 2, 180 * 2] * u.pix)
|
|
44
|
+
|
|
45
|
+
mag_r_map = Map(args.Br)
|
|
46
|
+
mag_r_map = mag_r_map.reproject_to(synoptic_map_br.wcs)
|
|
47
|
+
|
|
48
|
+
nan_mask = ~np.isnan(mag_r_map.data)
|
|
49
|
+
synoptic_map_br.data[nan_mask] = mag_r_map.data[nan_mask]
|
|
50
|
+
|
|
51
|
+
nrho = 100
|
|
52
|
+
rss = 2.5
|
|
53
|
+
pfss_in = pfsspy.Input(synoptic_map_br, nrho, rss)
|
|
54
|
+
pfss_out = pfsspy.pfss(pfss_in)
|
|
55
|
+
|
|
56
|
+
fig, axs = plt.subplots(10, 3, figsize=(10, 10))
|
|
57
|
+
b_potential = pfss_out.bg
|
|
58
|
+
for i in range(10):
|
|
59
|
+
v_min_max = np.abs(b_potential[:, :, i]).max()
|
|
60
|
+
v_min_max = 500 if v_min_max > 500 else v_min_max
|
|
61
|
+
axs[i, 0].imshow(b_potential[:, :, i, 0].T, vmin=-v_min_max, vmax=v_min_max, cmap='gray', origin='lower')
|
|
62
|
+
axs[i, 1].imshow(b_potential[:, :, i, 1].T, vmin=-v_min_max, vmax=v_min_max, cmap='gray', origin='lower')
|
|
63
|
+
im = axs[i, 2].imshow(b_potential[:, :, i, 2].T, vmin=-v_min_max, vmax=v_min_max, cmap='gray', origin='lower')
|
|
64
|
+
divider = make_axes_locatable(axs[i, 2])
|
|
65
|
+
cax = divider.append_axes("right", size="5%", pad=0.05)
|
|
66
|
+
plt.colorbar(im, cax=cax)
|
|
67
|
+
|
|
68
|
+
[ax.axis('off') for ax in axs.ravel()]
|
|
69
|
+
plt.tight_layout()
|
|
70
|
+
plt.savefig(os.path.join(out_path, 'potential.jpg'), dpi=300)
|
|
71
|
+
plt.close()
|
|
72
|
+
|
|
73
|
+
vtk_path = os.path.join(out_path, 'potential.vtk')
|
|
74
|
+
|
|
75
|
+
spherical_bounds = np.stack(
|
|
76
|
+
np.meshgrid(np.linspace(radius_range[0], radius_range[1], 50),
|
|
77
|
+
np.linspace(latitude_range[0], latitude_range[1], 50),
|
|
78
|
+
np.linspace(longitude_range[0], longitude_range[1], 50), indexing='ij'), -1)
|
|
79
|
+
|
|
80
|
+
cartesian_bounds = spherical_to_cartesian(spherical_bounds)
|
|
81
|
+
|
|
82
|
+
x_min, x_max = cartesian_bounds[..., 0].min(), cartesian_bounds[..., 0].max()
|
|
83
|
+
y_min, y_max = cartesian_bounds[..., 1].min(), cartesian_bounds[..., 1].max()
|
|
84
|
+
z_min, z_max = cartesian_bounds[..., 2].min(), cartesian_bounds[..., 2].max()
|
|
85
|
+
|
|
86
|
+
x_min, x_max = -1.3, 1.3
|
|
87
|
+
y_min, y_max = -1.3, 1.3
|
|
88
|
+
z_min, z_max = -1.3, 1.3
|
|
89
|
+
|
|
90
|
+
coords = np.stack(
|
|
91
|
+
np.meshgrid(np.linspace(x_min, x_max, int((x_max - x_min) * pixels_per_solRad)),
|
|
92
|
+
np.linspace(y_min, y_max, int((y_max - y_min) * pixels_per_solRad)),
|
|
93
|
+
np.linspace(z_max, z_min, int((z_max - z_min) * pixels_per_solRad)), indexing='ij'), -1)
|
|
94
|
+
# flipped z axis
|
|
95
|
+
radius = np.sqrt(np.sum(coords ** 2, -1))
|
|
96
|
+
|
|
97
|
+
spherical_coords = cartesian_to_spherical(coords)
|
|
98
|
+
condition = (spherical_coords[..., 0] >= radius_range[0]) & (spherical_coords[..., 0] < radius_range[1]) \
|
|
99
|
+
& (spherical_coords[..., 1] > latitude_range[0]) & (spherical_coords[..., 1] < latitude_range[1]) \
|
|
100
|
+
# & (spherical_coords[..., 2] > longitude_range[0]) & (spherical_coords[..., 2] < longitude_range[1])
|
|
101
|
+
spherical_coords[..., 1] -= np.pi / 2
|
|
102
|
+
sub_coords = spherical_coords[condition]
|
|
103
|
+
|
|
104
|
+
cube_shape = coords.shape[:-1]
|
|
105
|
+
sky_sub_coords = SkyCoord(lon=sub_coords[..., 2] * u.rad,
|
|
106
|
+
lat=(sub_coords[..., 1]) * u.rad,
|
|
107
|
+
radius=sub_coords[..., 0] * u.solRad, frame=mag_r_map.coordinate_frame)
|
|
108
|
+
sub_b = pfss_out.get_bvec(sky_sub_coords, out_type='cartesian')
|
|
109
|
+
# sub_b[..., 2] *= -1 # flip z axis
|
|
110
|
+
|
|
111
|
+
b = np.zeros(cube_shape + (3,))
|
|
112
|
+
b[condition] = sub_b
|
|
113
|
+
b = np.nan_to_num(b, nan=0)
|
|
114
|
+
|
|
115
|
+
coords = np.stack(np.mgrid[0:b.shape[0], 0:b.shape[1], 0:b.shape[2]], -1).astype(np.int64)
|
|
116
|
+
coords = (coords/ (b.shape[0] - 1) - 0.5) * 2 * 1.3
|
|
117
|
+
|
|
118
|
+
save_vtk(vtk_path, coords=coords, vectors={'B': b})
|
nf2/data/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.integrate import solve_bvp
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _differential_equation(mu, u, n, a2):
|
|
6
|
+
"""
|
|
7
|
+
The differential equation to solve for P
|
|
8
|
+
|
|
9
|
+
:param mu: cos(theta)
|
|
10
|
+
:param u: P function and derivative
|
|
11
|
+
:param n: variable according to Low & Lou (1989)
|
|
12
|
+
:param a2: eigenvalue
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
P, dP = u
|
|
16
|
+
dP_dmu = dP
|
|
17
|
+
d2P_dmu2 = -(n * (n + 1) * P + a2 * (1 + n) / n * P ** (1 + 2 / n)) / (1 - mu ** 2 + 1e-8)
|
|
18
|
+
return (dP_dmu, d2P_dmu2)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_analytic_b_field(n=1, m=1, l=0.3, psi=np.pi / 4, resolution=64, bounds=[-1, 1, -1, 1, 0, 2], tau_surfaces=None):
|
|
22
|
+
"""
|
|
23
|
+
Calculate the analytic NLFF field from Low & Lou (1989).
|
|
24
|
+
|
|
25
|
+
:param n: variable see Low & Lou (1989), only works for n=1
|
|
26
|
+
:param m: used for generating a proper initial condition.
|
|
27
|
+
:param a2: eigenvalue
|
|
28
|
+
:param l: depth below the photosphere
|
|
29
|
+
:param psi: angle of the magnetic field relative to the dipol axis
|
|
30
|
+
:param resolution: spatial resolution of the magnetic field in pixels
|
|
31
|
+
:param bounds: dimensions of the volume (x_start, x_end, y_start, y_end, z_start, z_end)
|
|
32
|
+
:return: magnetic field B (x, y, z, v)
|
|
33
|
+
"""
|
|
34
|
+
sol_P, a2 = solve_P(n, m)
|
|
35
|
+
|
|
36
|
+
resolution = [resolution] * 3 if not isinstance(resolution, list) else resolution
|
|
37
|
+
if tau_surfaces:
|
|
38
|
+
coords = np.stack(np.meshgrid(np.linspace(bounds[0], bounds[1], resolution[0], dtype=np.float32),
|
|
39
|
+
np.linspace(bounds[2], bounds[3], resolution[1], dtype=np.float32),
|
|
40
|
+
np.ones(len(tau_surfaces), dtype=np.float32),
|
|
41
|
+
indexing='ij'), -1)
|
|
42
|
+
for i, c in enumerate(tau_surfaces):
|
|
43
|
+
coords[:, :, i, 2] = c / resolution[2] * (bounds[5] - bounds[4]) + bounds[4]
|
|
44
|
+
if c == 0: # no corrugation for photospheric layer
|
|
45
|
+
continue
|
|
46
|
+
sx = sy = c / resolution[2] + 0.5
|
|
47
|
+
x, y = coords[:, :, i, 0], coords[:, :, i, 1]
|
|
48
|
+
gaussian = np.exp(-(x**2. / (2. * sx**2.) + y **2. / (2. * sy**2.)))
|
|
49
|
+
gaussian /= gaussian.max() # normalize
|
|
50
|
+
coords[:, :, i, 2] *= gaussian
|
|
51
|
+
else:
|
|
52
|
+
coords = np.stack(np.meshgrid(np.linspace(bounds[0], bounds[1], resolution[0], dtype=np.float32),
|
|
53
|
+
np.linspace(bounds[2], bounds[3], resolution[1], dtype=np.float32),
|
|
54
|
+
np.linspace(bounds[4], bounds[5], resolution[2], dtype=np.float32),
|
|
55
|
+
indexing='ij'), -1)
|
|
56
|
+
|
|
57
|
+
x, y, z = coords[..., 0], coords[..., 1], coords[..., 2]
|
|
58
|
+
X = x * np.cos(psi) - (z + l) * np.sin(psi)
|
|
59
|
+
Y = y
|
|
60
|
+
Z = x * np.sin(psi) + (z + l) * np.cos(psi)
|
|
61
|
+
|
|
62
|
+
# to spherical coordinates
|
|
63
|
+
xy = X ** 2 + Y ** 2
|
|
64
|
+
r = np.sqrt(xy + Z ** 2)
|
|
65
|
+
theta = np.arctan2(np.sqrt(xy), Z)
|
|
66
|
+
phi = np.arctan2(Y, X)
|
|
67
|
+
|
|
68
|
+
mu = np.cos(theta)
|
|
69
|
+
|
|
70
|
+
P, dP_dmu = sol_P(mu)
|
|
71
|
+
A = P / r ** n
|
|
72
|
+
dA_dtheta = -np.sin(theta) / (r ** n) * dP_dmu
|
|
73
|
+
dA_dr = P * (-n * r ** (-n - 1))
|
|
74
|
+
Q = np.sqrt(a2) * A * np.abs(A) ** (1 / n)
|
|
75
|
+
|
|
76
|
+
Br = (r ** 2 * np.sin(theta)) ** -1 * dA_dtheta
|
|
77
|
+
Btheta = - (r * np.sin(theta)) ** -1 * dA_dr
|
|
78
|
+
Bphi = (r * np.sin(theta)) ** -1 * Q
|
|
79
|
+
|
|
80
|
+
BX = Br * np.sin(theta) * np.cos(phi) + Btheta * np.cos(theta) * np.cos(phi) - Bphi * np.sin(phi)
|
|
81
|
+
BY = Br * np.sin(theta) * np.sin(phi) + Btheta * np.cos(theta) * np.sin(phi) + Bphi * np.cos(phi)
|
|
82
|
+
BZ = Br * np.cos(theta) - Btheta * np.sin(theta)
|
|
83
|
+
|
|
84
|
+
Bx = BX * np.cos(psi) + BZ * np.sin(psi)
|
|
85
|
+
By = BY
|
|
86
|
+
Bz = - BX * np.sin(psi) + BZ * np.cos(psi)
|
|
87
|
+
|
|
88
|
+
b_field = np.real(np.stack([Bx, By, Bz], -1))
|
|
89
|
+
return b_field
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def solve_P(n, m):
|
|
93
|
+
"""
|
|
94
|
+
Solve the differential equation from Low & Lou (1989).
|
|
95
|
+
|
|
96
|
+
:param n: variable (only n=1)
|
|
97
|
+
:param v0: start condition for dP/dmu
|
|
98
|
+
:param P0: boundary condition for P(-1) and P(1)
|
|
99
|
+
:return: interpolated functions for P and dP/dmu
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def f(x, y, p):
|
|
103
|
+
a2 = p[0]
|
|
104
|
+
d2P_dmu2 = -(n * (n + 1) * y[0] + a2 * (1 + n) / n * y[0] ** (1 + 2 / n)) / (1 - x ** 2 + 1e-6)
|
|
105
|
+
return [y[1], d2P_dmu2]
|
|
106
|
+
|
|
107
|
+
def f_boundary(Pa, Pb, p):
|
|
108
|
+
return np.array([Pa[0] - 0, Pb[0] - 0, Pa[1] - 10])
|
|
109
|
+
|
|
110
|
+
mu = np.linspace(-1, 1, num=256)
|
|
111
|
+
|
|
112
|
+
if m % 2 == 0:
|
|
113
|
+
init = np.cos(mu * (m + 1) * np.pi / 2)
|
|
114
|
+
else:
|
|
115
|
+
init = np.sin(mu * (m + 1) * np.pi / 2)
|
|
116
|
+
|
|
117
|
+
dinit = 10 * np.ones_like(init) #
|
|
118
|
+
initial = np.stack([init, dinit])
|
|
119
|
+
|
|
120
|
+
@np.vectorize
|
|
121
|
+
def shooting(a2_init):
|
|
122
|
+
eval = solve_bvp(f, f_boundary, x=mu, y=initial, p=[a2_init], verbose=0, tol=1e-6)
|
|
123
|
+
if eval.success == False:
|
|
124
|
+
return None
|
|
125
|
+
return eval
|
|
126
|
+
|
|
127
|
+
# use shooting to find eigenvalues
|
|
128
|
+
evals = shooting(np.linspace(0, 10, 100, dtype=np.float32))
|
|
129
|
+
evals = [e for e in evals if e is not None]
|
|
130
|
+
|
|
131
|
+
eigenvalues = np.array([e.p for e in evals])
|
|
132
|
+
eigenvalues = sorted(set(np.round(eigenvalues, 4).reshape((-1,))))
|
|
133
|
+
|
|
134
|
+
# get final solution
|
|
135
|
+
eval = shooting([eigenvalues[-1]])[0]
|
|
136
|
+
|
|
137
|
+
return eval.sol, eval.p[0]
|