Skip to content

Add SpectrumFileProcessor class for automated spectral data conversion and formatting #257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
135 changes: 135 additions & 0 deletions cosipy/data_io/custom_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import astropy.units as u
from astropy.constants import h

Check warning on line 5 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L1-L5

Added lines #L1 - L5 were not covered by tests

class SpectrumFileProcessor:
def __init__(self, input_file, reformatted_file, energy_col=0, flux_col=1, convert_data=True):

Check warning on line 8 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L7-L8

Added lines #L7 - L8 were not covered by tests
"""
Initialize the SpectrumFileProcessor class with default columns for energy and flux.

Parameters:
- input_file: Path to the input .dat file.
- reformatted_file: Path to save the reformatted data.
- energy_col: Index of the column containing energy or frequency (default: 0).
- flux_col: Index of the column containing flux (default: 1).
- convert_data: Boolean flag indicating if conversion is needed (default: True).
"""
self.input_file = input_file
self.reformatted_file = reformatted_file
self.data = None
self.df_filtered = None
self.energy_col = energy_col # Default to column 0 for energy
self.flux_col = flux_col # Default to column 1 for flux
self.convert_data = convert_data # Whether conversion is necessary

Check warning on line 25 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L19-L25

Added lines #L19 - L25 were not covered by tests

def load_data(self):

Check warning on line 27 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L27

Added line #L27 was not covered by tests
"""Load the data from the .dat file."""
try:
self.data = np.loadtxt(self.input_file)
print(f"Data loaded successfully from {self.input_file}")
except Exception as e:
print(f"Error loading file {self.input_file}: {e}")
raise

Check warning on line 34 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L29-L34

Added lines #L29 - L34 were not covered by tests

def process_data(self):

Check warning on line 36 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L36

Added line #L36 was not covered by tests
"""Process the data: Convert frequency to energy in keV and flux to ph/cm²/sec/keV if needed."""
if self.convert_data:

Check warning on line 38 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L38

Added line #L38 was not covered by tests
# If conversion is required
energy_hz = self.data[:, self.energy_col] * u.Hz # Column defined by user
flux_ergs = self.data[:, self.flux_col] * u.erg / (u.cm**2 * u.s) # Column defined by user

Check warning on line 41 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L40-L41

Added lines #L40 - L41 were not covered by tests

# Convert frequency to energy in keV
energy_keV = (h * energy_hz).to(u.keV)

Check warning on line 44 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L44

Added line #L44 was not covered by tests

# Convert flux from ergs to keV
flux_keV = flux_ergs.to(u.keV / (u.cm**2 * u.s))

Check warning on line 47 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L47

Added line #L47 was not covered by tests

# Convert flux to ph/cm²/sec/keV
flux_ph = (flux_keV / energy_keV**2)

Check warning on line 50 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L50

Added line #L50 was not covered by tests

else:
# If data is already in keV, assume first column is energy (keV) and second column is flux
energy_keV = self.data[:, self.energy_col] * u.keV # Directly use energy in keV
flux_ph = self.data[:, self.flux_col] * u.ph / (u.cm**2 * u.s * u.keV) # Directly use flux in ph/cm²/sec/keV

Check warning on line 55 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L54-L55

Added lines #L54 - L55 were not covered by tests

# Create a DataFrame to store the data
df = pd.DataFrame({

Check warning on line 58 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L58

Added line #L58 was not covered by tests
'Energy (keV)': energy_keV.value,
'Flux (ph/cm²/sec/keV)': flux_ph.value
})

# Filter out rows with energy less than 100 keV and more than 10000 keV
self.df_filtered = df[(df['Energy (keV)'] >= 100) & (df['Energy (keV)'] <= 10000)]
return self.df_filtered

Check warning on line 65 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L64-L65

Added lines #L64 - L65 were not covered by tests

def integrate_flux(self):

Check warning on line 67 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L67

Added line #L67 was not covered by tests
"""
Compute the total flux using the sum of flux multiplied by energy bin widths.

Returns:
- K: The total flux.
"""
if self.df_filtered is None or self.df_filtered.empty:
raise ValueError("No data to integrate. Please run process_data() first.")

Check warning on line 75 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L74-L75

Added lines #L74 - L75 were not covered by tests

# Get energy and flux from the processed data
energy = self.df_filtered['Energy (keV)'].values
flux = self.df_filtered['Flux (ph/cm²/sec/keV)'].values

Check warning on line 79 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L78-L79

Added lines #L78 - L79 were not covered by tests

# Calculate the energy bin widths
ewidths = np.diff(energy, append=energy[-1])

Check warning on line 82 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L82

Added line #L82 was not covered by tests

# Calculate the current total flux (integral of the spectrum)
K = np.sum(flux * ewidths)

Check warning on line 85 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L85

Added line #L85 was not covered by tests

print(f"Calculated Normalization Constant K: {K}")

Check warning on line 87 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L87

Added line #L87 was not covered by tests

return K

Check warning on line 89 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L89

Added line #L89 was not covered by tests

def plot_spectrum(self):

Check warning on line 91 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L91

Added line #L91 was not covered by tests
"""Generate a plot of energy vs flux with a log-log scale."""
if self.df_filtered is not None:
plt.figure(figsize=(10, 6))
plt.plot(self.df_filtered['Energy (keV)'], self.df_filtered['Flux (ph/cm²/sec/keV)'], marker="o", linestyle="-")
plt.xscale("log")
plt.yscale("log")
plt.xlabel("Energy (keV)")
plt.ylabel("Flux (ph/cm²/sec/keV)")
plt.title("Energy Flux Data")
plt.grid(True)
plt.show()

Check warning on line 102 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L93-L102

Added lines #L93 - L102 were not covered by tests
else:
print("No data available for plotting. Please run process_data first.")

Check warning on line 104 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L104

Added line #L104 was not covered by tests

def reformat_data(self):

Check warning on line 106 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L106

Added line #L106 was not covered by tests
"""Reformat the data for the photon spectrum file."""
try:
formatted_lines = []
formatted_lines.append("-Ps photon spectrum file")
formatted_lines.append("#")
formatted_lines.append("# Format: DP <energy in keV> <shape of O-Ps [XX/keV]>")
formatted_lines.append("")
formatted_lines.append("IP LIN")
formatted_lines.append("")

Check warning on line 115 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L108-L115

Added lines #L108 - L115 were not covered by tests

# Iterate through the DataFrame to format each line as required
for index, row in self.df_filtered.iterrows():
energy = row['Energy (keV)']
flux = row['Flux (ph/cm²/sec/keV)']
formatted_line = f"DP\t{energy:.5e}\t{flux:.5e}" # Add DP and reformat the line
formatted_lines.append(formatted_line)

Check warning on line 122 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L118-L122

Added lines #L118 - L122 were not covered by tests

# Append the closing 'EN' line
formatted_lines.append("EN")

Check warning on line 125 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L125

Added line #L125 was not covered by tests

# Save the new formatted data to a file
with open(self.reformatted_file, 'w') as f:
f.write("\n".join(formatted_lines))

Check warning on line 129 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L128-L129

Added lines #L128 - L129 were not covered by tests

print(f"Reformatted data saved to {self.reformatted_file}")

Check warning on line 131 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L131

Added line #L131 was not covered by tests

except Exception as e:
print(f"Error during reformatting: {e}")
raise

Check warning on line 135 in cosipy/data_io/custom_functions.py

View check run for this annotation

Codecov / codecov/patch

cosipy/data_io/custom_functions.py#L133-L135

Added lines #L133 - L135 were not covered by tests