diff --git a/H-line.py b/H-line.py index 7027e9a..d04cd2b 100644 --- a/H-line.py +++ b/H-line.py @@ -1,7 +1,7 @@ import contextlib import os, sys, json from time import sleep -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone # Import necessary classes/modules sys.path.append("src/") @@ -21,8 +21,11 @@ def main(config): sdr = Observation.getSDR(**SDR_PARAM) + # live_view takes priority + live_view = PLOTTING_PARAM['live_view'] + # If user wants 24h observations - if OBSERVATION_PARAM["24h"]: + if not live_view and OBSERVATION_PARAM["24h"]: # Checks if 360 is divisable with the degree interval and calculates number of collections try: deg_interval = OBSERVATION_PARAM["degree_interval"] @@ -33,11 +36,17 @@ def main(config): quit() else: num_data = 1 + if live_view: + num_data = 100 # Do observation(s) # Start time of observation - current_time = datetime.utcnow() + current_time = datetime.now(timezone.utc) for i in range(num_data): + if 0 == i or not live_view: + sdr.set_bias_tee(SDR_PARAM['bias_tee']) + # sleep to allow the amplifier to settle 1ms should be enough time + sleep(1) COORD_CLASS = Observation.getCoordinates(current_time + timedelta(seconds = 24*60**2/num_data * i), **OBSERVER_PARAM) print(current_time + timedelta(seconds = 24*60**2/num_data * i)) @@ -49,12 +58,15 @@ def main(config): print("Analyzing data...") Observation.analyzeData(COORD_CLASS) print("Plotting data...") - Observation.plotData(**PLOTTING_PARAM) - - print(f"Done observing! - {datetime.utcnow()}") + PLOTTING_PARAM['n_plot'] = i + if Observation.plotData(**PLOTTING_PARAM): + print(f"Live view finished!") + break + else: + print(f"Done observing! - {datetime.now(timezone.utc)}") # Next, write datafile if necessary - if OBSERVATION_PARAM["datafile"]: + if not live_view and OBSERVATION_PARAM["datafile"]: user_params = { "SDR": SDR_PARAM, "DSP": DSP_PARAM, @@ -64,13 +76,17 @@ def main(config): Observation.writeDatafile(**user_params) # Wait for next execution - if num_data > 1: + if not live_view and num_data > 1: end_time = current_time + timedelta(seconds = second_interval * (i + 1)) - time_remaining = end_time - datetime.utcnow() - print(f'Waiting for next data collection in {time_remaining.total_seconds()} seconds') - sleep(time_remaining.total_seconds()) + time_remaining = end_time - datetime.now(timezone.utc) + delay = time_remaining.total_seconds() + delay = 1 if delay < 0 else delay + print(f'Waiting for next data collection in {delay} seconds') + sdr.set_bias_tee(False) + sleep(delay) clear_console() + sdr.set_bias_tee(False) # Reads user config def read_config(): @@ -79,7 +95,6 @@ def read_config(): parsed_config = json.load(config) main(parsed_config) - # For clearing console def clear_console(): os.system('cls' if os.name =='nt' else 'clear') diff --git a/config.json b/config.json index 9c1891e..4a20b2f 100644 --- a/config.json +++ b/config.json @@ -4,7 +4,8 @@ "PPM_offset": 0, "TCP_host": false, "connect_to_host": false, - "host_IP": "127.0.0.1" + "host_IP": "127.0.0.1", + "bias_tee": true }, "DSP": { "number_of_fft": 1000, @@ -21,7 +22,8 @@ "plotting": { "plot_map": true, "y_min": 0.0, - "y_max": 0.0 + "y_max": 0.0, + "live_view": true }, "observation": { "24h": false, diff --git a/src/observation.py b/src/observation.py index 6196b3a..fbd59a1 100644 --- a/src/observation.py +++ b/src/observation.py @@ -84,7 +84,10 @@ def analyzeData(self, coord_class): # Plot the data def plotData(self, **params): - PLOT = Plotter(params["plot_map"], params["y_min"], params["y_max"]) + live_view = False + if 'live_view' in params: + live_view = params['live_view'] + PLOT = Plotter(params["plot_map"], params["y_min"], params["y_max"], live_view) plot_info = { "ra": self.RA, @@ -96,8 +99,7 @@ def plotData(self, **params): "SNR": self.max_SNR, "observed_radial_velocity": self.observed_radial_velocity } - PLOT.plot(self.freqs,self.SNR_spectrum,**plot_info) - + return PLOT.plot(self.freqs,self.SNR_spectrum,**plot_info) # Writes a datafile with all the collected data from the observation def writeDatafile(self, **kwargs): @@ -130,4 +132,4 @@ def writeDatafile(self, **kwargs): - \ No newline at end of file + diff --git a/src/plot.py b/src/plot.py index 9c9d307..315e407 100644 --- a/src/plot.py +++ b/src/plot.py @@ -7,10 +7,11 @@ ANALYSIS = Analysis() class Plotter(): - def __init__(self, plot_map, y_min, y_max): + def __init__(self, plot_map, y_min, y_max, live_view = False): self.SHOW_MAP = plot_map self.Y_MIN = y_min self.Y_MAX = y_max + self.live_view = live_view def plot(self, freqs, data, **kwargs): # Unpack info @@ -21,7 +22,8 @@ def plot(self, freqs, data, **kwargs): freq_correction = ANALYSIS.freqFromRadialVel(barycenter_correction + lsr_correction) - ANALYSIS.H_FREQUENCY SNR, radial_velocity = kwargs["SNR"], kwargs["observed_radial_velocity"] - if self.SHOW_MAP: + user_closed_window = False + if not self.live_view and self.SHOW_MAP: fig = plt.figure(figsize=(20,12)) fig.suptitle('Hydrogen line observation', fontsize = 22, y = 0.99) fig.subplots_adjust(hspace=1) @@ -41,18 +43,30 @@ def plot(self, freqs, data, **kwargs): corrected_spectrum_ax.set_yticklabels([]) corrected_spectrum_ax.set_ylabel('') - else: - fig, ax = plt.subplots(figsize = (12, 7)) + elif self.live_view: + if plt.fignum_exists(1): + fig = plt.figure(1) + ax = fig.axes[0] + plt.cla() + else: + fig, ax = plt.subplots(figsize = (12, 7)) + self.spectrumGrid(ax, "Observed spectrum", freqs, data) - - # Saves plot - path = f'./Spectrums/ra={ra},dec={dec}.png' - plt.tight_layout(pad = 1.75) - plt.savefig(path, dpi = 100) - plt.close() + if self.live_view: + plt.pause(1.0) + if len(plt.get_fignums()) == 0: + user_closed_window = True + else: + # Saves plot + path = f'./Spectrums/ra={ra},dec={dec}.png' + plt.tight_layout(pad = 1.75) + plt.savefig(path, dpi = 100) + + plt.close() + + return user_closed_window - # Arrange detail grid # TODO: Redesign table. Perhaps into two subplots def detailsGrid(self, ax, ra, dec,gal_lon, gal_lat, barycenter_correction, lsr_correction, radial_velocity, SNR): @@ -78,8 +92,8 @@ def detailsGrid(self, ax, ra, dec,gal_lon, gal_lat, barycenter_correction, lsr_c table.auto_set_font_size(False) table.set_fontsize(14) table.scale(1, 2.25) - - + + # Arrange sky grid def skyGrid(self, ax, ra, dec): ax.set(title = 'Milky Way H-line map') @@ -108,7 +122,7 @@ def spectrumGrid(self, ax, title, freqs, data): # Plots theoretical H-line frequency ax.axvline(x = ANALYSIS.H_FREQUENCY, color = 'r', linestyle = ':', linewidth = 2, label = 'Theoretical frequency') - + # Sets axis labels and adds legend & grid ylabel ='Signal to noise ratio (SNR) / dB' xlabel = 'Frequency / Hz' @@ -128,11 +142,11 @@ def spectrumGrid(self, ax, title, freqs, data): # Adds top x-axis for radial velocity radial_vel = ax.secondary_xaxis('top', functions = (ANALYSIS.radialVelFromFreq, ANALYSIS.freqFromRadialVel)) radial_vel.set_xlabel(r'Radial velocity / $\frac{km}{s}$') - + # Generates and saves a GIF of 24H observations def generateGIF(self, ra, dec): print('Generating GIF from observations... This may take a while') path = f'Spectrums/ra={ra[0]},dec={dec}.gif' images = [imageio.imread(f'Spectrums/ra={coord},dec={dec}.png') for coord in ra] - imageio.mimsave(path, images) \ No newline at end of file + imageio.mimsave(path, images) diff --git a/src/ui/callbacks.py b/src/ui/callbacks.py index 9ca08a3..366c355 100644 --- a/src/ui/callbacks.py +++ b/src/ui/callbacks.py @@ -7,13 +7,14 @@ SAMPLE_RATES = [3200000,2800000,2560000,2400000,2048000,1920000,1800000,1400000,1024000,900001,250000] # Define default parameters -parameters = { +_parameters = { "SDR": { "sample_rate": 2400000, "PPM_offset": 0, "TCP_host": False, "connect_to_host": False, - "host_IP": "127.0.0.1" + "host_IP": "127.0.0.1", + "bias_tee": False }, "DSP": { "number_of_fft": 1000, @@ -30,7 +31,8 @@ "plotting":{ "plot_map": True, "y_min": 0.0, - "y_max": 0.0 + "y_max": 0.0, + "live_view": False }, "observation": { "24h": False, @@ -39,6 +41,26 @@ } } +parameters = {} + +# Initial with either config.json or _parameters +def load_defaults(): + if len(parameters): + return + + from pathlib import Path + config = Path("config.json") + if config.is_file(): + read_from_config() + + # now add any new default parameters + for category in _parameters.keys(): + for key, value in _parameters[category].items(): + if not category in parameters: + parameters[category] = {} + + if not key in parameters[category]: + parameters[category][key] = value # Write parameters to config def update_config(): @@ -50,11 +72,13 @@ def read_from_config(): with open("config.json", "r") as config_file: parsed_config = json.load(config_file) categories = list(parsed_config.keys()) - - # Iterrate over each key in category (SDR, DSP, etc.) + + # Iterate over each key in category (SDR, DSP, etc.) for category in categories: for key, value in parsed_config[category].items(): dpg.set_value(key, value) + if not category in parameters: + parameters[category] = {} parameters[category][key] = value # Callback functions @@ -84,6 +108,9 @@ def btn_callback(sender, app_data, user_data): # Handle checkbox actions def checkbox_callback(sender, app_data, user_data): parameters[user_data][sender] = app_data + if sender == 'live_view': + update_config() + if app_data: os.system('py H-line.py' if os.name =='nt' else 'python3 H-line.py') # Handle text actions def text_callback(sender, app_data, user_data): diff --git a/src/ui/parameters.py b/src/ui/parameters.py index d10969f..c97cdc5 100644 --- a/src/ui/parameters.py +++ b/src/ui/parameters.py @@ -36,6 +36,7 @@ def sdrWindow(): dpg.add_text("RTL-SDR parameters") dpg.add_text("(help)",tag="RTL-SDR_category") + dpg.add_checkbox(label="bias tee",tag="bias_tee",user_data="SDR",default_value=False,callback=callbacks.checkbox_callback) dpg.add_combo(callbacks.SAMPLE_RATES,label="Sample rate",user_data="SDR",default_value=2400000,tag="sample_rate",callback=callbacks.dropdown_callback) dpg.add_input_int(label="Resolution",user_data="SDR",default_value=11,tag="resolution",callback=callbacks.text_callback) dpg.add_input_int(label="PPM offset",user_data="SDR",default_value=0,tag="PPM_offset",callback=callbacks.text_callback) @@ -43,6 +44,7 @@ def sdrWindow(): with dpg.tooltip("RTL-SDR_category"): help_msg = ''' + bias tee = Configure bias tee (configures voltage on RTL-SDR input port) Sample rate = Sample rate of SDR. Values above 2.4MSPS may cause sample drops. Resolution = 2^Resolution samples are collected pr. FFT. PPM offset = Offset of the RTL-SDR local oscillator in parts per million. @@ -134,8 +136,8 @@ def actionsWindow(): with dpg.group(horizontal=True): dpg.add_text("Perform actions") dpg.add_text("(help)", tag="action_category") - - # dpg.add_button(label="Live view",tag="live_view",callback=callbacks.btn_callback) + + dpg.add_checkbox(label="Live view",tag="live_view",user_data="plotting",default_value=False,callback=callbacks.checkbox_callback) dpg.add_button(label="Edit theme",tag="edit_theme",callback=callbacks.btn_callback) dpg.add_button(label="Browse observations",tag="open_obs_folder",callback=callbacks.btn_callback) dpg.add_button(label="Update parameters from config",tag="update_parameters",callback=callbacks.btn_callback) @@ -143,8 +145,15 @@ def actionsWindow(): with dpg.tooltip("action_category"): # Live view = Open a live spectrum for locating the H-line. help_msg = ''' + Live view = Open a live spectrum for locating the H-line. Edit theme = Edit the look and appearance of the user interface. Browse observations = Browse the folder with previous observations. Update parameters from config = Updates the UI with the parameters from the config file. ''' dpg.add_text(textwrap.dedent(help_msg)) + +def readDefaults(): + # Once the windows have been created populate with default values + callbacks.load_defaults() + callbacks.update_config() + callbacks.read_from_config() diff --git a/ui.py b/ui.py index 3e59d36..a4eae02 100644 --- a/ui.py +++ b/ui.py @@ -15,6 +15,7 @@ def run_ui(): parameters.observerWindow() parameters.observationWindow() parameters.actionsWindow() + parameters.readDefaults() dpg.setup_dearpygui() dpg.show_viewport() @@ -23,4 +24,4 @@ def run_ui(): if __name__ == "__main__": print("Launching user interface!") - run_ui() \ No newline at end of file + run_ui()