comparison Generate_figures.py @ 0:f40d05521dca draft default tip

planemo upload for repository https://github.com/esg-epfl-apc/tools-astro/tree/main/tools commit de01e3c02a26cd6353a6b9b6f8d1be44de8ccd54
author astroteam
date Fri, 25 Apr 2025 19:33:20 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:f40d05521dca
1 #!/usr/bin/env python
2 # coding: utf-8
3
4 #!/usr/bin/env python
5
6 # This script is generated with nb2galaxy
7
8 # flake8: noqa
9
10 import json
11 import os
12 import shutil
13
14 from oda_api.json import CustomJSONEncoder
15
16 # oda:usesResource oda:CRBeamS3 .
17 # oda:CRBeamS3 a oda:S3 .
18 # oda:CRBeamS3 oda:resourceBindingEnvVarName "S3_CREDENTIALS" .
19
20 src_name = "NGC 1365" # http://odahub.io/ontology#AstrophysicalObject
21
22 z_start = 0 # http://odahub.io/ontology#Float
23 Npart = 2000 # http://odahub.io/ontology#Integer ; oda:lower_limit 1 ; oda:upper_limit 100000
24 particle_type = "gamma" # http://odahub.io/ontology#String ; oda:allowed_value "gamma","electron","proton"
25 Emax = 30 # http://odahub.io/ontology#Energy_TeV
26 Emin = 0.01 # http://odahub.io/ontology#Energy_TeV
27 EminSource = 1.0 # http://odahub.io/ontology#Energy_TeV
28 Gamma = 2.0 # http://odahub.io/ontology#Float
29 EGMF_fG = 10 # http://odahub.io/ontology#Float
30 lmaxEGMF_Mpc = 5 # http://odahub.io/ontology#Float
31 jet_half_size = 5.0 # oda:AngleDegrees
32 jet_direction = 0.0 # oda:AngleDegrees
33 psf = 1.0 # oda:AngleDegrees
34 window_size_RA = 2.0 # oda:AngleDegrees
35 window_size_DEC = 1.0 # oda:AngleDegrees
36 EBL = "Franceschini 2017" # http://odahub.io/ontology#String ; oda:allowed_value "Franceschini 2017","Stecker 2016 lower limit","Stecker 2016 upper limit","Inoue 2012 Baseline","Inoue 2012 lower limit","Inoue 2012 upper limit"
37
38 _galaxy_wd = os.getcwd()
39
40 with open("inputs.json", "r") as fd:
41 inp_dic = json.load(fd)
42 if "C_data_product_" in inp_dic.keys():
43 inp_pdic = inp_dic["C_data_product_"]
44 else:
45 inp_pdic = inp_dic
46 src_name = str(inp_pdic["src_name"])
47 z_start = float(inp_pdic["z_start"])
48 Npart = int(inp_pdic["Npart"])
49 particle_type = str(inp_pdic["particle_type"])
50 Emax = float(inp_pdic["Emax"])
51 Emin = float(inp_pdic["Emin"])
52 EminSource = float(inp_pdic["EminSource"])
53 Gamma = float(inp_pdic["Gamma"])
54 EGMF_fG = float(inp_pdic["EGMF_fG"])
55 lmaxEGMF_Mpc = float(inp_pdic["lmaxEGMF_Mpc"])
56 jet_half_size = float(inp_pdic["jet_half_size"])
57 jet_direction = float(inp_pdic["jet_direction"])
58 psf = float(inp_pdic["psf"])
59 window_size_RA = float(inp_pdic["window_size_RA"])
60 window_size_DEC = float(inp_pdic["window_size_DEC"])
61 EBL = str(inp_pdic["EBL"])
62
63 import os
64
65 from astropy import units as u
66 from astropy.coordinates import SkyCoord
67 from astropy.nddata import StdDevUncertainty
68 from astropy.table import Table
69 from gammapy.maps import Map, MapAxis
70 from oda_api.api import ProgressReporter
71 from oda_api.data_products import (
72 LightCurveDataProduct,
73 NumpyDataProduct,
74 ODAAstropyTable,
75 PictureProduct,
76 )
77 from specutils import Spectrum1D
78 from utils import find_redshift
79
80 if z_start <= 0:
81 z_start = find_redshift(src_name)
82
83 source = SkyCoord.from_name(src_name, frame="icrs", parse=False, cache=True)
84
85 pr = ProgressReporter()
86 pr.report_progress(
87 stage="Initialization",
88 progress=0,
89 substage="spectra",
90 subprogress=30,
91 message="some message",
92 )
93
94 import subprocess
95
96 import light_curve as lc
97 import numpy as np
98 from crbeam import CRbeam
99 from matplotlib import pyplot as plt
100 from spec_plot import plot_spec
101
102 EGMF = EGMF_fG * 1e-15
103
104 # internal units are eV
105 Emax *= 1e12
106 Emin *= 1e12
107 EminSource *= 1e12
108
109 background = {
110 "Franceschini 2017": 12,
111 "Stecker 2016 lower limit": 10,
112 "Stecker 2016 upper limit": 11,
113 "Inoue 2012 Baseline": 3,
114 "Inoue 2012 lower limit": 4,
115 "Inoue 2012 upper limit": 5,
116 }[EBL]
117
118 prog = CRbeam(
119 z=z_start,
120 nparticles=Npart,
121 primary=particle_type,
122 emax=Emax,
123 emin=Emin,
124 emin_source=EminSource,
125 EGMF=EGMF,
126 lmaxEGMF=lmaxEGMF_Mpc,
127 background=background,
128 )
129 cmd = prog.command
130 cmd
131
132 # Here is one way to call CRbeam without global cache
133 # prog.run(force_overwrite=False)
134 # Here we call CRbeam with global cache
135 # prog.run_cached(overwrite_local_cache=True)
136 # to clear s3 cache run the following command
137 # prog.remove_cache()
138
139 # To report the progress we will split running CRbeam into 10 parts
140 n_steps = 10
141 # Initialize multistep simulation
142 data_exists = not prog.start_multistage_run(
143 overwrite_local_cache=True, n_steps=n_steps
144 )
145 proceed = not data_exists
146
147 if proceed:
148 for step in range(n_steps):
149 pr.report_progress(
150 stage="Running Simulation", progress=100 * step // n_steps
151 )
152 proceed = prog.simulation_step()
153 # todo: report progress using rest API
154 pr.report_progress(stage="Running Simulation", progress=100)
155
156 assert not proceed, "must be completed before this cell"
157 if not data_exists:
158 prog.end_multistep_run()
159
160 def adjust_weights(mc_file, power):
161 # converting weights to mimic required injection spectrum power
162 header = ""
163 with open(mc_file, "rt") as lines:
164 for line in lines:
165 if len(line) > 0 and line[0] == "#":
166 header += line[1:].strip() + "\n"
167 else:
168 break
169 weight_col = 2
170 E_src_col = 12
171 data = np.loadtxt(mc_file)
172 weight = data[:, weight_col - 1]
173 E_src = data[:, E_src_col - 1]
174 orig_power = (
175 prog.power_law
176 ) # CRBeam is always called with fixed power=1 to optimize cache usage
177 weight *= np.power(E_src / Emax, -(power - orig_power))
178 output_file = f"{mc_file}_p{power}"
179 np.savetxt(output_file, data, header=header.strip(), fmt="%.6g")
180 return output_file
181
182 # this code will not execute program since files already exist on server
183 # prog.run(force_overwrite=False)
184
185 # one can upload cache explicitely by call
186 # prog.upload_cache()
187
188 pr.report_progress(stage="Building Plots and Tables", progress=0)
189
190 print(prog.output_path)
191 get_ipython().system("ls {prog.output_path}") # noqa: F821
192
193 mc_file = prog.output_path + "/photon"
194
195 # how the data looks like
196 get_ipython().system("cat {mc_file} | awk 'NR<=5'") # noqa: F821
197
198 if Emax != EminSource:
199 mc_file = adjust_weights(mc_file, Gamma)
200
201 # how the data looks like
202 get_ipython().system("cat {mc_file} | awk 'NR<=5'") # noqa: F821
203
204 subprocess.run(
205 [
206 "bash",
207 os.path.join(os.environ.get("BASEDIR", os.getcwd()), "makeSpecE2.sh"),
208 mc_file,
209 ]
210 )
211
212 # rotating the beam
213
214 if EGMF > 0:
215 from mc_rotate import mc_rotate
216
217 mc_rotated_file = mc_rotate(
218 mc_file, jet_half_size, jet_direction, psf_deg=psf
219 )
220 else:
221 mc_rotated_file = mc_file
222 mc_rotated_file
223
224 subprocess.run(
225 [
226 "bash",
227 os.path.join(os.environ.get("BASEDIR", os.getcwd()), "makeSpecE2.sh"),
228 mc_rotated_file,
229 ]
230 )
231
232 # how the rotated data looks like
233 get_ipython().system("cat {mc_rotated_file} | awk 'NR<=5'") # noqa: F821
234
235 # reading the source distance in Mpc from the data file
236 T_Mpc = lc.get_distance_Mpc(mc_file)
237 T_Mpc
238
239 # building the spectrum figure for total flux
240
241 spec_file = mc_file + ".spec"
242 spec_fig = plot_spec(
243 spec_file, title="spectrum", ext="png", show=False, logscale=True
244 )
245 spec_image = PictureProduct.from_file(spec_fig)
246
247 # building the spectrum figure for the flux within PSF
248 spec_rotated_file = mc_rotated_file + ".spec"
249 spec_rotated_fig = plot_spec(
250 spec_rotated_file,
251 title="spectrum",
252 Emin=Emin,
253 Emax=Emax,
254 ext="png",
255 show=False,
256 logscale=True,
257 )
258 spec_rotated_image = PictureProduct.from_file(spec_rotated_fig)
259
260 spec_rotated_file
261
262 lc_params = dict(
263 logscale=True,
264 max_t=-99, # 8760000,
265 n_points=30,
266 psf=psf,
267 min_n_particles=10,
268 cut_0=True,
269 )
270
271 # building the light curve figure
272 if EGMF > 0:
273 light_curve_fig = lc.make_plot(mc_rotated_file, **lc_params)
274 light_curve_image = PictureProduct.from_file(light_curve_fig)
275 else:
276 # to avoid possible problems with absent figure
277 light_curve_image = PictureProduct.from_file(spec_fig)
278
279 l_curve = None
280 if EGMF > 0:
281 delay, weights = lc.get_counts(mc_rotated_file, **lc_params)
282 t, f, N = lc.light_curve(delay, weights, **lc_params)
283 l_curve = LightCurveDataProduct.from_arrays(
284 times=t * 3600.0, # t is in hours
285 fluxes=f,
286 errors=f / np.sqrt(N),
287 time_format="unix",
288 )
289
290 def convert_to_ICRS(phi: np.array, theta: np.array, source: SkyCoord):
291 # prime system has z-axes pointing the source centered at observer
292 # it is obtained by rotation of source system by 180 degrees about x-axis
293 # prime system coords have suffix "prime" (')
294 # icrs system coords has empty suffix
295 # TODO: add param ic_jet_plane_direction: SkyCoord - gives plane which contains jet
296 # by definition in prime system the jet is in y'-z' plane and z' axes points from the source towards the observer
297 # for simplicity we will first assume that prime frame is oriented as ICRS frame (use SkyOffsetFrame with rotation=0)
298
299 # coordinates of unit direction vector in prime system
300
301 # direction of event arrival at prime system
302 z_prime = np.cos(
303 theta / 180.0 * np.pi
304 ) # (-1)*(-1) = 1 (rotating system and tacking oposite vector)
305 r_xy_prime = np.sin(theta / 180.0 * np.pi)
306 x_prime = -r_xy_prime * np.cos(
307 phi / 180.0 * np.pi
308 ) # (1)*(-1) = -1 (rotating system and tacking oposite vector)
309 y_prime = r_xy_prime * np.sin(
310 phi / 180.0 * np.pi
311 ) # (-1)*(-1) = 1 (rotating system and tacking oposite vector)
312
313 print("x',y',z' =", x_prime, y_prime, z_prime)
314
315 # angle between z and z' axes
316 delta1 = 90 * u.deg - source.dec
317
318 print("source:", source.cartesian)
319 print("delta1 = ", delta1)
320
321 # rotating about y-axes
322
323 x1 = x_prime * np.cos(delta1) + z_prime * np.sin(delta1)
324 y1 = y_prime
325 z1 = -x_prime * np.sin(delta1) + z_prime * np.cos(delta1)
326
327 print("step 1: x,y,z =", x1, y1, z1)
328
329 # rotation to -RA about z axis
330 delta2 = source.ra
331 x = x1 * np.cos(delta2) - y1 * np.sin(delta2)
332 y = x1 * np.sin(delta2) + y1 * np.cos(delta2)
333 z = z1
334
335 print("x,y,z =", x, y, z)
336
337 event_coords = SkyCoord(
338 x=x, y=y, z=z, frame="icrs", representation_type="cartesian"
339 ).spherical
340 # print(event_coords.spherical)
341 return event_coords
342
343 def LoadCubeTemplate(
344 mc_file: str,
345 source: SkyCoord,
346 Emax=1e15,
347 Emin=1e9,
348 bins_per_decade=20,
349 binsz=0.02,
350 theta_mult=1,
351 sec_component_mult=1,
352 remove_straight=False,
353 symmetric_split=0,
354 ):
355 pass
356
357 mc_data = np.loadtxt(mc_file)
358 E = mc_data[:, 0]
359 print(f"dataset energy: {np.min(E)} <= E/eV <= {np.max(E)}")
360 Theta = mc_data[:, 2] * theta_mult # theta_mult is used for debug only
361 print(f"dataset Theta: {np.min(Theta)} <= Theta <= {np.max(Theta)}")
362 # idx = np.where((E >= Emin) & (E<=Emax) & (Theta<theta_max))[0]
363 print(f"Filters: {Emin} <= E/eV <= {Emax}")
364 condition = (E >= Emin) & (E <= Emax)
365 if remove_straight:
366 condition &= Theta > 0
367
368 idx = np.where(condition)[0]
369
370 print("filtered dataset length:", len(idx))
371
372 E = E[idx]
373 Theta = Theta[idx]
374 w = mc_data[:, 1][idx]
375 Phi = mc_data[:, 3][idx]
376 # t = mc_data[:,5][idx]
377 # t *= time_scale_hours
378 if sec_component_mult != 1: # sec_component_mult is used for debug only
379 print("WARNING: Sec component multiplicity:", sec_component_mult)
380 w[Theta > 0] *= sec_component_mult
381
382 if len(idx) > 0:
383 # print(f'{np.min(t)} <= t/h <= {np.max(t)}')
384 print(f"{np.min(E)} <= E <= {np.max(E)}")
385
386 energy_axis_name = "energy_true"
387
388 energy_axis = MapAxis.from_energy_bounds(
389 Emin * u.eV,
390 Emax * u.eV,
391 nbin=bins_per_decade,
392 name=energy_axis_name,
393 per_decade=True,
394 )
395
396 m_cube = Map.create(
397 binsz=binsz,
398 width=(window_size_RA * u.deg, window_size_DEC * u.deg),
399 frame="icrs",
400 axes=[energy_axis],
401 skydir=SkyCoord(source),
402 )
403
404 print(m_cube.geom)
405
406 if len(idx) > 0:
407 if symmetric_split > 1:
408 Theta = np.repeat(Theta, symmetric_split)
409 E = np.repeat(E, symmetric_split)
410 w = np.repeat(w, symmetric_split) / symmetric_split
411 Phi = np.random.random(len(Theta)) * 360
412
413 sc = convert_to_ICRS(Phi, Theta, source)
414 m_cube.fill_by_coord(
415 {"lat": sc.lat, "lon": sc.lon, energy_axis_name: E * u.eV},
416 weights=w,
417 )
418
419 return m_cube
420
421 def LoadTemplate4D(
422 mc_file: str,
423 source: SkyCoord,
424 redshift,
425 Emax=1e15,
426 Emin=1e9,
427 bins_per_decade=20,
428 binsz=0.02,
429 theta_mult=1,
430 max_time_quantile=1.0,
431 n_time_bins=100,
432 symmetric_split=0,
433 ):
434 from astropy.cosmology import FlatLambdaCDM
435
436 cosmo = FlatLambdaCDM(
437 H0=67.8 * u.km / u.s / u.Mpc, Tcmb0=2.725 * u.K, Om0=(1 - 0.692)
438 )
439 time_scale_hours = float(
440 ((cosmo.age(0) - cosmo.age(z_start)) / u.h).decompose()
441 )
442
443 mc_data = np.loadtxt(mc_file)
444 E = mc_data[:, 0]
445
446 print("dataset length:", len(mc_data))
447 print(f"dataset energy: {np.min(E)} <= E/eV <= {np.max(E)}")
448 Theta = mc_data[:, 2] * theta_mult # theta_mult is used for debug only
449 print(f"dataset Theta: {np.min(Theta)} <= Theta <= {np.max(Theta)}")
450 # idx = np.where((E >= Emin) & (E<=Emax) & (Theta<theta_max))[0]
451
452 idx = np.where((E >= Emin) & (E <= Emax))[0]
453
454 print(f"Filters: {Emin} <= E/eV <= {Emax}")
455 print("filtered dataset length:", len(idx))
456
457 E = E[idx]
458 Theta = Theta[idx]
459 w = mc_data[:, 1][idx]
460 Phi = mc_data[:, 3][idx]
461 t = mc_data[:, 5][idx]
462 t *= time_scale_hours
463 min_time = np.min(t)
464 if max_time_quantile >= 1.0:
465 max_time = np.max(t)
466 else:
467 max_time = np.quantile(t, max_time_quantile)
468
469 if max_time == min_time:
470 max_time = min_time + 1e-10
471
472 idx = np.where(t <= max_time)[0]
473 print(f"Filters: {Emin} <= E/eV <= {Emax}, t/h <= {max_time}")
474 print("filtered dataset length:", len(idx))
475
476 E = E[idx]
477 Theta = Theta[idx]
478 w = w[idx]
479 Phi = Phi[idx]
480 t = t[idx]
481
482 energy_axis_name = "energy_true"
483
484 energy_axis = MapAxis.from_energy_bounds(
485 Emin * u.eV,
486 Emax * u.eV,
487 nbin=bins_per_decade,
488 name=energy_axis_name,
489 per_decade=True,
490 )
491
492 from astropy.time import Time
493
494 reference_time = Time(0, format="unix")
495
496 delta = max_time - min_time
497 (min_time + delta * np.linspace(0, 1, n_time_bins + 1)) * u.h
498 # time_axis = TimeMapAxis.from_time_edges(
499 # time_min=time_edges[:-1],
500 # time_max=time_edges[1:],
501 # interp="lin",
502 # unit=u.h,
503 # name='time',
504 # reference_time=reference_time
505 # )
506
507 time_axis = MapAxis.from_bounds(
508 min_time, max_time, n_time_bins, name="time", unit=u.h
509 )
510
511 # time_axis = TimeMapAxis(time_edges[:-1], time_edges[1:], reference_time, name='time', interp='lin')
512
513 # time_axis = TimeMapAxis.from_time_bounds(min_time, max_time, n_time_bins, unit=u.h, name='time')
514
515 map4d = Map.create(
516 binsz=binsz,
517 width=(window_size_RA * u.deg, window_size_DEC * u.deg),
518 frame="icrs",
519 axes=[energy_axis, time_axis],
520 skydir=SkyCoord(source),
521 )
522
523 print(map4d.geom)
524
525 if len(idx) > 0:
526 if symmetric_split > 1:
527 Theta = np.repeat(Theta, symmetric_split)
528 E = np.repeat(E, symmetric_split)
529 t = np.repeat(t, symmetric_split)
530 w = np.repeat(w, symmetric_split) / symmetric_split
531 Phi = np.random.random(len(Theta)) * 360
532
533 sc = convert_to_ICRS(Phi, Theta, source)
534 map4d.fill_by_coord(
535 {
536 "lat": sc.lat,
537 "lon": sc.lon,
538 energy_axis_name: E * u.eV,
539 "time": t * u.h,
540 },
541 weights=w,
542 )
543
544 return map4d
545
546 bins_per_decade = 20
547
548 symmetric_split = 0 if jet_direction != 0 else 100
549
550 map3d = LoadCubeTemplate(
551 mc_rotated_file,
552 source=source,
553 Emin=Emin,
554 Emax=Emax,
555 bins_per_decade=bins_per_decade,
556 symmetric_split=symmetric_split,
557 )
558
559 map3d.write("map3d.fits", overwrite=True)
560
561 map4d = LoadTemplate4D(
562 mc_rotated_file,
563 source=source,
564 redshift=z_start,
565 Emin=Emin,
566 Emax=Emax,
567 bins_per_decade=bins_per_decade,
568 symmetric_split=symmetric_split,
569 )
570 map4d.write("map4d.fits", overwrite=True)
571
572 d = np.genfromtxt(spec_file)
573 ee = d[:, 0]
574 ff = d[:, 1]
575 ff_err = ff / np.sqrt(d[:, 2])
576 plt.errorbar(ee, ff, yerr=ff_err, label="total flux")
577 d = np.genfromtxt(spec_rotated_file)
578 ee1 = d[:, 0]
579 ff1 = d[:, 1]
580 ff_err1 = ff1 / np.sqrt(d[:, 2])
581 plt.errorbar(ee1, ff1, yerr=ff_err1, label="PSF flux")
582 plt.xscale("log")
583 plt.yscale("log")
584 plt.xlabel("Energy, eV")
585 plt.ylabel("$E^2dN/dE$, arbitrary units")
586 plt.legend(loc="lower right")
587 plt.savefig("Spectrum.png", format="png", bbox_inches="tight")
588
589 bin_image = PictureProduct.from_file("Spectrum.png")
590
591 data = [ee, ff, ff_err]
592 names = ("Energy", "Total_flux", "Total_flux_err")
593 table = ODAAstropyTable(Table(data, names=names))
594 data = [ee, ff, ff_err]
595 names = ("Energy", "PSF_flux", "PSF_flux_err")
596 table1 = ODAAstropyTable(Table(data, names=names))
597
598 flux_unit = u.eV / u.cm / u.cm / u.s / u.sr
599 spec = Spectrum1D(
600 spectral_axis=ee * u.eV,
601 flux=ff * flux_unit,
602 uncertainty=StdDevUncertainty(ff_err),
603 )
604 spec.write("spec.fits", overwrite=True)
605 dp = NumpyDataProduct.from_fits_file("spec.fits")
606 spec_rotated = Spectrum1D(
607 spectral_axis=ee1 * u.eV,
608 flux=ff1 * flux_unit,
609 uncertainty=StdDevUncertainty(ff_err1),
610 )
611 spec_rotated.write("spec_rotated.fits", overwrite=True)
612 dp_rotated = NumpyDataProduct.from_fits_file("spec_rotated.fits")
613 map3d = NumpyDataProduct.from_fits_file("map3d.fits")
614 map4d = NumpyDataProduct.from_fits_file("map4d.fits")
615
616 pr.report_progress(stage="Building Plots and Tables", progress=100)
617
618 spectrum_png = bin_image # http://odahub.io/ontology#ODAPictureProduct
619 light_curve_png = (
620 light_curve_image # http://odahub.io/ontology#ODAPictureProduct
621 )
622 total_spectrum_table = table # http://odahub.io/ontology#ODAAstropyTable
623 psf_spectrum_table = table1 # http://odahub.io/ontology#ODAAstropyTable
624 lc_result = l_curve # http://odahub.io/ontology#LightCurve
625 spectrum = dp # http://odahub.io/ontology#Spectrum
626 spectrum_rotated = dp_rotated # http://odahub.io/ontology#Spectrum
627 map3d = map3d # http://odahub.io/ontology#Spectrum
628 map4d = map4d # http://odahub.io/ontology#Spectrum
629
630 # spec_png = spec_image # http://odahub.io/ontology#ODAPictureProduct
631 # spec_rotated_png = spec_rotated_image # http://odahub.io/ontology#ODAPictureProduct
632
633 # output gathering
634 _galaxy_meta_data = {}
635 _oda_outs = []
636 _oda_outs.append(
637 (
638 "out_Generate_figures_spectrum_png",
639 "spectrum_png_galaxy.output",
640 spectrum_png,
641 )
642 )
643 _oda_outs.append(
644 (
645 "out_Generate_figures_light_curve_png",
646 "light_curve_png_galaxy.output",
647 light_curve_png,
648 )
649 )
650 _oda_outs.append(
651 (
652 "out_Generate_figures_total_spectrum_table",
653 "total_spectrum_table_galaxy.output",
654 total_spectrum_table,
655 )
656 )
657 _oda_outs.append(
658 (
659 "out_Generate_figures_psf_spectrum_table",
660 "psf_spectrum_table_galaxy.output",
661 psf_spectrum_table,
662 )
663 )
664 _oda_outs.append(
665 ("out_Generate_figures_lc_result", "lc_result_galaxy.output", lc_result)
666 )
667 _oda_outs.append(
668 ("out_Generate_figures_spectrum", "spectrum_galaxy.output", spectrum)
669 )
670 _oda_outs.append(
671 (
672 "out_Generate_figures_spectrum_rotated",
673 "spectrum_rotated_galaxy.output",
674 spectrum_rotated,
675 )
676 )
677 _oda_outs.append(("out_Generate_figures_map3d", "map3d_galaxy.output", map3d))
678 _oda_outs.append(("out_Generate_figures_map4d", "map4d_galaxy.output", map4d))
679
680 for _outn, _outfn, _outv in _oda_outs:
681 _galaxy_outfile_name = os.path.join(_galaxy_wd, _outfn)
682 if isinstance(_outv, str) and os.path.isfile(_outv):
683 shutil.move(_outv, _galaxy_outfile_name)
684 _galaxy_meta_data[_outn] = {"ext": "_sniff_"}
685 elif getattr(_outv, "write_fits_file", None):
686 _outv.write_fits_file(_galaxy_outfile_name)
687 _galaxy_meta_data[_outn] = {"ext": "fits"}
688 elif getattr(_outv, "write_file", None):
689 _outv.write_file(_galaxy_outfile_name)
690 _galaxy_meta_data[_outn] = {"ext": "_sniff_"}
691 else:
692 with open(_galaxy_outfile_name, "w") as fd:
693 json.dump(_outv, fd, cls=CustomJSONEncoder)
694 _galaxy_meta_data[_outn] = {"ext": "json"}
695
696 with open(os.path.join(_galaxy_wd, "galaxy.json"), "w") as fd:
697 json.dump(_galaxy_meta_data, fd)
698 print("*** Job finished successfully ***")