Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7eeadbc
Update numpy dependency to allow >=2.0 ; Update doctests to work with…
matteobachetti Mar 24, 2026
1a67886
Update numpy dep also in requirements.txt
matteobachetti Mar 24, 2026
dfdc6e9
Expand tested versions
matteobachetti Mar 24, 2026
9fc6859
Be more restrictive with versions for old pythons
matteobachetti Mar 24, 2026
56b6314
Ditch need for requirements.txt
matteobachetti Mar 24, 2026
8924c1f
Fix installation script
matteobachetti Mar 24, 2026
a28b332
Do not forget dash in new pythons
matteobachetti Mar 24, 2026
1fa8b80
Fix spline_through_data
matteobachetti Mar 24, 2026
e7b9b65
Update interpolation function and fix bugs
matteobachetti Mar 24, 2026
349d2f7
Fix issue with missing local bad point database
matteobachetti Mar 27, 2026
528929f
Do look for local bad points file if it exists
matteobachetti Mar 27, 2026
43c4917
avoid inconsistencies in name of bad point file
matteobachetti Mar 27, 2026
92cdb3f
Avoid reloading of app in debug mode
matteobachetti Mar 27, 2026
a3138d9
Avoid longdoubles. Also, do not run in debug mode, just fail
matteobachetti Apr 1, 2026
1d06a11
Update standard deviation threshold in example
matteobachetti Apr 1, 2026
d410339
Allow better debugging
matteobachetti Apr 1, 2026
8a6364d
Log whenever data points are eliminated
matteobachetti Apr 1, 2026
abcec8f
Avoid needless elimination of last points; stabilize spline at the end
matteobachetti Apr 1, 2026
6710523
Add thorough documentation in nustarclock.py
matteobachetti Apr 2, 2026
350499e
Add comments about added table columns
matteobachetti Apr 2, 2026
1a327d8
Use helper function for filtering and logging
matteobachetti Apr 2, 2026
98a4926
Use named variables instead of magic numbers, throughout
matteobachetti Apr 2, 2026
cdbd2dd
Fix __init__ import
matteobachetti Apr 2, 2026
7f2c351
Catch and warn for invalid MET intervals
hpearnshaw Apr 3, 2026
b6e54f8
Re-read and sort temperature table when hdf5 is older
matteobachetti Apr 8, 2026
c192f2c
Fix condition for re-read of temperature
matteobachetti Apr 8, 2026
f2fe2b3
Thoroughly eliminate cache files when recalculating
matteobachetti Apr 8, 2026
36e7d1f
Make points more visible
matteobachetti Apr 8, 2026
5487f00
Improve margins of temperature plots
matteobachetti Apr 8, 2026
d4bb594
Fix axis labels of temperature plots
matteobachetti Apr 8, 2026
fdcb06c
Fix temperature labels
matteobachetti Apr 8, 2026
1d2c352
Sort MET immediately
matteobachetti Apr 8, 2026
7e85876
Test up to Python 3.14
matteobachetti Apr 13, 2026
09752f5
Updated bad samples for April 20, 2026 release
Apr 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11"]
python-version: ["3.9", "3.12", "3.14"]

steps:
- uses: actions/checkout@v4
Expand All @@ -29,6 +29,7 @@ jobs:
python -m pip install --upgrade pip
python -m pip install pytest pytest-astropy
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
pip install .
- name: Test with pytest
run: |
pytest --remote-data -svv nuclockutils
4 changes: 4 additions & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
nuclockutils 0.4.dev5+g48a3009ec.d20260324 (2026-03-24)
=======================================================

No significant changes.
1 change: 0 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ Installation

$ git clone https://github.com/matteobachetti/nustar-clock-utils
$ cd nustar-clock-utils
$ pip install -r requirements.txt
$ pip install .

Usage
Expand Down
1 change: 0 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ Installation

$ git clone https://github.com/matteobachetti/nustar-clock-utils
$ cd nustar-clock-utils
$ pip install -r requirements.txt
$ pip install .

Usage
Expand Down
9 changes: 9 additions & 0 deletions nuclockutils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst

# Time constants (all in seconds unless noted)
SECONDS_PER_DAY = 86400
HALF_DAY_SECONDS = 43200 # Minimum GTI duration for valid processing
SECONDS_PER_MONTH = SECONDS_PER_DAY * 30
SECONDS_PER_YEAR = SECONDS_PER_DAY * 365.25

# Mission timeline constants (MET values)
# MET when science operations began (end of commissioning phase)
SCIENCE_START_MET = 77674700

from .nustarclock import *
from .utils import *
Expand Down
8 changes: 4 additions & 4 deletions nuclockutils/barycorr.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import nuclockutils
from .utils import filter_with_region, high_precision_keyword_read
from .nustarclock import interpolate_clock_function

from . import SECONDS_PER_DAY

class OrbitalFunctions():
lat_fun = None
Expand All @@ -29,7 +29,7 @@ def get_orbital_functions(orbfile):
orbtable = Table.read(orbfile)
mjdref = high_precision_keyword_read(orbtable.meta, 'MJDREF')

times = Time(np.array(orbtable['TIME'] / 86400 + mjdref), format='mjd')
times = Time(np.array(orbtable['TIME'] / SECONDS_PER_DAY + mjdref), format='mjd')
if 'GEODETIC' in orbtable.colnames:
geod = np.array(orbtable['GEODETIC'])
lat, lon, alt = geod[:, 0] * u.deg, geod[:, 1] * u.deg, geod[:,
Expand Down Expand Up @@ -82,8 +82,8 @@ def get_barycentric_correction(orbfile, parfile, dt=5, ephem='DE421'):
mjdref = high_precision_keyword_read(hdul[1].header, 'MJDREF')

knots = no.X.get_knots()
mjds = np.arange(knots[1], knots[-2], dt / 86400)
mets = (mjds - mjdref) * 86400
mjds = np.arange(knots[1], knots[-2], dt / SECONDS_PER_DAY)
mets = (mjds - mjdref) * SECONDS_PER_DAY

obs, scale = 'nustar', "tt"
toalist = [None] * len(mjds)
Expand Down
109 changes: 58 additions & 51 deletions nuclockutils/clean_clock/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from nuclockutils.nustarclock import load_temptable, load_freq_changes, \
load_and_flag_clock_table, find_good_time_intervals, calculate_stats, \
eliminate_trends_in_residuals
eliminate_trends_in_residuals, _BAD_POINTS_FILE, FIXED_CONTROL_POINTS

import dash
from dash import dcc
Expand Down Expand Up @@ -64,7 +64,7 @@ def recalc(outfile='save_all.pickle'):
met_start = clock_offset_table['met'][0]
met_stop = clock_offset_table['met'][-1] + 30
clock_jump_times = \
np.array([77469000, 78708320, 79657575, 81043985, 82055671, 293346772,
np.array([77469000, 78708320, 79657575, 81043985, 82055671, 293346772, 305080000,
392200784, 394825882, 395304135, 407914525, 408299422])
clock_jump_times += 30 # Sum 30 seconds to avoid to exclude these points
# from previous interval
Expand All @@ -80,7 +80,7 @@ def recalc(outfile='save_all.pickle'):

table_new = eliminate_trends_in_residuals(
table_new, clock_offset_table_corr, gtis,
fixed_control_points=np.arange(291e6, 295e6, 86400))
fixed_control_points=FIXED_CONTROL_POINTS)

mets = np.array(table_new['met'])
start = mets[0]
Expand All @@ -100,6 +100,7 @@ def recalc(outfile='save_all.pickle'):
np.array(
clock_offset_table['offset'] - table_new['temp_corr'][tempcorr_idx]
)

clock_residuals_detrend = np.array(
clock_offset_table['offset'] - table_new['temp_corr_detrend'][tempcorr_idx])

Expand Down Expand Up @@ -219,7 +220,7 @@ def plot_dash(all_data, table_new, gti, all_nustar_obs,
'text': text[bad],
'mode': 'markers',
'name': f'Bad clock offset measurements',
'marker': {'color': 'grey', 'symbol': "x-dot", 'size': 3}
'marker': {'color': 'grey', 'symbol': "x-dot", 'size': 5}
}), 1, 1)

all_data_bad = all_data[bad]
Expand All @@ -232,7 +233,7 @@ def plot_dash(all_data, table_new, gti, all_nustar_obs,
'text': text[bad],
'mode': 'markers',
'showlegend': False,
'marker': {'color': 'grey', 'symbol': "x-dot", 'size': 3}
'marker': {'color': 'grey', 'symbol': "x-dot", 'size': 5}
}), row, 1)

for station, color in zip(['MLD', 'SNG', 'UHI'], ['blue', 'red', 'orange']):
Expand All @@ -244,7 +245,7 @@ def plot_dash(all_data, table_new, gti, all_nustar_obs,
'hovertemplate': hovertemplate,
'text': text[good],
'mode': 'markers',
'marker': {'color': color, 'size': 3},
'marker': {'color': color, 'size': 5},
'name': f'Clock offset - {station}'
}), 1, 1)
for ydata, row in zip(['residual', 'residual_detrend'], [2, 3]):
Expand All @@ -255,7 +256,7 @@ def plot_dash(all_data, table_new, gti, all_nustar_obs,
'text': text[good],
'showlegend': False,
'mode': 'markers',
'marker': {'color': color, 'size': 3}
'marker': {'color': color, 'size': 5}
}), row, 1)

# bad_intervals = [[0, 77.767e6]]
Expand All @@ -270,20 +271,23 @@ def plot_dash(all_data, table_new, gti, all_nustar_obs,
continue
if bti[0] > all_data['met'][-1]:
continue
shapes.append(dict(type="rect",
shapes.append(
dict(
type="rect",
# x-reference is assigned to the x-values
xref="x",
# y-reference is assigned to the plot paper [0,1]
yref="paper",
x0=max(bti[0], all_data['met'][0]),
x0=float(max(bti[0], all_data["met"][0])),
y0=0,
x1=min(bti[1], all_data['met'][-1]),
x1=float(min(bti[1], all_data["met"][-1])),
y1=1,
fillcolor="LightSalmon",
opacity=0.5,
layer="below",
line_width=0,
))
)
)

if axis_ranges is not None:
if 'xaxis.range[0]' in axis_ranges:
Expand Down Expand Up @@ -414,8 +418,8 @@ def stored_analysis(file):
html.Div(
className="three columns div-for-charts bg-grey",
children=[
dcc.Graph(id='temperature-time-series'),
dcc.Graph(id='temperature-gradient-time-series'),
dcc.Graph(mathjax=True, id='temperature-time-series'),
dcc.Graph(mathjax=True, id='temperature-gradient-time-series'),
]
),
html.Div(id='dummy', style={'display': 'none'}),
Expand Down Expand Up @@ -515,20 +519,25 @@ def refresh_output(n_cl_refresh, n_cl_bad, n_cl_good, n_cl_recalc, selectedData)

if who_triggered == 'recalculate-button':
log.info("Recalculating all")

for file in ['save_all.pickle', 'dump.hdf5', 'all_data_res.pkl', 'rolling_data.pkl']:

if os.path.exists(file):
log.info(f"Removing file {file}")
os.unlink(file)

stored_analysis.cache_clear()
# cache.delete_memoized(stored_analysis, 'save_all.pickle')
if os.path.exists('save_all.pickle'):
os.unlink('save_all.pickle')

elif who_triggered == 'actually-good-data-button' and len(NEW_BAD_POINTS) > 0:
log.info("Removing point(s) from bad clock offset database")
ALL_BAD_POINTS = eliminate_array_from_array(
ALL_BAD_POINTS, NEW_BAD_POINTS)
np.savetxt('BAD_POINTS_DB.dat', ALL_BAD_POINTS, fmt='%d')
np.savetxt(_BAD_POINTS_FILE, ALL_BAD_POINTS, fmt='%d')
elif who_triggered == 'bad-data-button' and len(NEW_BAD_POINTS) > 0:
log.info("Adding point(s) to bad clock offset database")
ALL_BAD_POINTS = merge_and_sort_arrays(
ALL_BAD_POINTS, NEW_BAD_POINTS)
np.savetxt('BAD_POINTS_DB.dat', ALL_BAD_POINTS, fmt='%d')
np.savetxt(_BAD_POINTS_FILE, ALL_BAD_POINTS, fmt='%d')
else:
log.info("Refreshing plot")
CURRENT_AXES = default_axes()
Expand All @@ -550,39 +559,32 @@ def display_selected_data(selectedData):


def create_temperature_timeseries(x, y, axis_type='linear'):
return {
'data': [dict(
x=x,
y=y,
mode='lines'
)],
'layout': {
'height': 300,
'yaxis': {'title': 'TCXO Temperature',
'type': 'linear' if axis_type == 'Linear' else 'log'},
'xaxis': {'title': 'met', 'showgrid': False,
'margin':{'t': 20}}

}
}
import plotly.graph_objects as go
fig = go.Figure()
fig.add_trace(go.Scattergl(x=np.asarray(x, dtype=float),
y=np.asarray(y, dtype=float), mode="lines"))
fig.update_layout(
height=300,
margin=dict(t=20, b=50, l=60, r=20),
)
fig.update_xaxes(title_text="MET (s)", showgrid=False)
fig.update_yaxes(title_text="TCXO Temperature (°C)",
type="linear" if axis_type == "linear" else "log")
return fig


def create_temperature_gradient_timeseries(x, y, axis_type='linear'):
return {
'data': [dict(
x=x,
y=y,
mode='lines'
)],
'layout': {
'height': 300,
'yaxis': {'title': 'TCXO Temp Gradient',
'type': 'linear'},
'xaxis': {'title': 'met', 'showgrid': False,
'margin':{'t': 20}}

}
}
import plotly.graph_objects as go
fig = go.Figure()
fig.add_trace(go.Scattergl(x=np.asarray(x, dtype=float),
y=np.asarray(y, dtype=float) * 1e3, mode="lines"))
fig.update_layout(
height=300,
margin=dict(t=20, b=50, l=60, r=20),
)
fig.update_xaxes(title_text="MET (s)", showgrid=False)
fig.update_yaxes(title_text=r"TCXO Temp Gradient (1e-3 °C/s)", type="linear")
return fig


@app.callback(
Expand Down Expand Up @@ -615,6 +617,7 @@ def main(args=None):
global FREQFILE
global MODELVERSION
import argparse
import logging
description = ('Clean clock offset measurements with an handy web '
'interface.')
parser = argparse.ArgumentParser(description=description)
Expand All @@ -630,6 +633,8 @@ def main(args=None):
"file in the auxil/directory "
"or the tp_tcxo*.csv file)")
parser.add_argument("--temperature-model-version", default=None, help="Temperature model version")
parser.add_argument("--devel", action='store_true', help="Enable development mode")
parser.add_argument("--debug", action='store_true', help="Enable debug logging")
args = parser.parse_args(args)
if args.temperature_file is not None:
TEMPFILE = args.temperature_file
Expand All @@ -639,14 +644,16 @@ def main(args=None):
FREQFILE = args.frequency_file
if args.temperature_model_version is not None:
MODELVERSION = args.temperature_model_version
if args.debug:
log.setLevel(logging.DEBUG)

print("Creating app")
app = create_app()
try:
app.run(debug=True)
app.run(debug=args.devel, use_reloader=False)
except Exception as e:
# Compatibility with old versions
app.run_server(debug=True)
app.run_server(debug=args.devel, use_reloader=False)



Expand All @@ -657,4 +664,4 @@ def main(args=None):
FREQFILE = os.path.join(datadir, 'sample_freq.dat')
TEMPFILE = os.path.join(datadir, 'tcxo_tmp_sample.csv')

main()
main()
1 change: 1 addition & 0 deletions nuclockutils/data/BAD_POINTS_DB.dat
Original file line number Diff line number Diff line change
Expand Up @@ -1694,3 +1694,4 @@
512698376
512839329
513133512
513704513
6 changes: 3 additions & 3 deletions nuclockutils/diagnostics/bary_and_fold_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
get_ephemeris_from_parfile
from .compare_pulses import main as main_compare_pulses
from nuclockutils.barycorr import main_barycorr as nubarycorr
from nuclockutils.utils import high_precision_keyword_read
from nuclockutils.utils import high_precision_keyword_read, SECONDS_PER_DAY


def mkdir(folder):
Expand All @@ -40,7 +40,7 @@ def get_observing_mjd(fname):
log.error(f"{fname} might be corrupted")
return None

return np.array([tstart, tstop]) / 86400 + mjdref
return np.array([tstart, tstop]) / SECONDS_PER_DAY + mjdref


def fold_file_to_ephemeris(fname, parfile, emin=None, emax=None,
Expand All @@ -61,7 +61,7 @@ def fold_file_to_ephemeris(fname, parfile, emin=None, emax=None,
good = good & (events.energy < emax)

times = events.time[good]
event_mjds = times / 86400 + events.mjdref
event_mjds = times / SECONDS_PER_DAY + events.mjdref
phase = correction_fun(event_mjds)

t = calculate_profile_from_phase(phase, nbin=nbin)
Expand Down
6 changes: 3 additions & 3 deletions nuclockutils/diagnostics/compare_clock_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
import numpy as np
from astropy.table import Table
import matplotlib.pyplot as plt
from nuclockutils.nustarclock import interpolate_clock_function
from nuclockutils.nustarclock import interpolate_clock_function, SECONDS_PER_DAY


def get_todays_met():
t = Time.now()
met = (t.mjd - 55197.00076601852) * 86400
met = (t.mjd - 55197.00076601852) * SECONDS_PER_DAY
return met

def get_launch_met():
t = Time('2012-06-12')
met = (t.mjd - 55197.00076601852) * 86400
met = (t.mjd - 55197.00076601852) * SECONDS_PER_DAY
return met


Expand Down
3 changes: 2 additions & 1 deletion nuclockutils/diagnostics/compare_pulses.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from uncertainties import ufloat
from astropy import log
from statsmodels.robust import mad
from nuclockutils.utils import SECONDS_PER_DAY

from .fftfit import fftfit

Expand All @@ -33,7 +34,7 @@ def format_profile_and_get_phase(file, template=None):
fddot = 0
if 'F2' in table.meta:
fddot = table.meta['F2']
delta_t = (mjd - pepoch) * 86400
delta_t = (mjd - pepoch) * SECONDS_PER_DAY
f0 = freq + fdot * delta_t + 0.5 * fddot * delta_t ** 2

period_ms = 1 / f0 * 1000
Expand Down
Loading
Loading