aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Scholten2025-10-15 17:08:17 +1000
committerSam Scholten2025-10-15 17:42:47 +1000
commit0797f0c5639afca0c6745d223624df1903ec4ef4 (patch)
tree2be6584e530a9eec7ac3f23d8ddc77279d3f4d53
parent55001da9a7add8ae57c66f7c921e4f70309df511 (diff)
downloadpicostream-0797f0c5639afca0c6745d223624df1903ec4ef4.tar.gz
picostream-0797f0c5639afca0c6745d223624df1903ec4ef4.zip
ensure plotter state is reset and metadata cleared before new acq
-rw-r--r--picostream/dfplot.py93
-rw-r--r--picostream/main.py18
-rw-r--r--picostream/pico.py7
3 files changed, 99 insertions, 19 deletions
diff --git a/picostream/dfplot.py b/picostream/dfplot.py
index ebf6f03..e338d58 100644
--- a/picostream/dfplot.py
+++ b/picostream/dfplot.py
@@ -78,6 +78,7 @@ class HDF5LivePlotter(QWidget):
self.max_adc_val: Optional[int] = None
self.downsample_mode: Optional[str] = None
self.analog_offset_v: float = 0.0
+ self.metadata_read: bool = False
# --- Debug Counters ---
self.update_count: int = 0
@@ -116,21 +117,64 @@ class HDF5LivePlotter(QWidget):
# Initial file check
self.check_file_exists()
- def set_hdf5_path(self, hdf5_path: str) -> None:
- """Sets the HDF5 file path to monitor and resets the plot."""
- self.hdf5_path = hdf5_path
- logger.info(f"Plotter path updated to: {hdf5_path}")
- # Reset state
+ def reset_for_new_file(self) -> None:
+ """Reset all state in preparation for a new file.
+
+ This should be called BEFORE set_hdf5_path() when starting a new acquisition
+ to ensure no stale metadata or state is carried over.
+ """
+ logger.debug("Resetting plotter state for new file")
+
+ # Clear display
+ self.curve.setData([], [])
+
+ # Reset all data buffers
self.display_data = np.array([])
self.time_data = np.array([])
self.data_start_sample = 0
+
+ # Reset buffer tracking
self.last_file_size = 0
self.buffer_reset_count = 0
self.total_samples_processed = 0
+
+ # Clear cached metadata - this is critical!
+ self.voltage_range_v = None
+ self.max_adc_val = None
+ self.downsample_mode = None
+ self.analog_offset_v = 0.0
+ self.sample_interval_ns = 16.0
+ self.hardware_downsample_ratio = 1
+ self.metadata_read = False
+
+ # Reset rate checking
self.rate_check_start_time = None
+ self.rate_check_start_samples = 0
self.last_displayed_size = 0
- self.curve.setData([], []) # Clear plot
- self.check_file_exists()
+
+ # Reset counters
+ self.update_count = 0
+ self.file_read_count = 0
+ self.display_update_count = 0
+ self.data_change_count = 0
+ self.stale_update_count = 0
+
+ # Reset status
+ self.is_saturated = False
+ self.last_data_timestamp = None
+ self.display_latency_ms = 0.0
+
+ def set_hdf5_path(self, hdf5_path: str) -> None:
+ """Sets the HDF5 file path to monitor.
+
+ Note: Call reset_for_new_file() BEFORE this method when starting a new
+ acquisition to ensure clean state.
+ """
+ self.hdf5_path = hdf5_path
+ logger.info(f"Plotter path updated to: {hdf5_path}")
+
+ # Don't check file or read metadata here - wait for update_from_file()
+ # to be called, which will only read metadata once the file exists with valid data
def start_updates(self) -> None:
"""Starts the plot update timer."""
@@ -204,7 +248,13 @@ class HDF5LivePlotter(QWidget):
self.plot_widget.setClipToView(True)
def check_file_exists(self) -> None:
- """Checks if the HDF5 file exists and attempts to read metadata."""
+ """Checks if the HDF5 file exists and attempts to read metadata.
+
+ Only reads metadata if it hasn't been read yet for this file.
+ """
+ if self.metadata_read:
+ return
+
try:
with h5py.File(self.hdf5_path, "r") as f:
if "adc_counts" in f:
@@ -227,6 +277,10 @@ class HDF5LivePlotter(QWidget):
Args:
hdf5_file: An open h5py.File object.
"""
+ if self.metadata_read:
+ logger.debug("Metadata already read, skipping")
+ return
+
try:
# Metadata is stored as root-level attributes
base_sample_interval_ns = hdf5_file.attrs["sample_interval_ns"]
@@ -241,14 +295,22 @@ class HDF5LivePlotter(QWidget):
self.max_adc_val = hdf5_file.attrs["max_adc"]
self.downsample_mode = hdf5_file.attrs.get("downsample_mode", "average")
self.analog_offset_v = hdf5_file.attrs.get("analog_offset_v", 0.0)
+
+ self.metadata_read = True
+
+ logger.info(
+ f"Plotter read metadata: voltage_range_v={self.voltage_range_v}V, "
+ f"max_adc={self.max_adc_val}, downsample_mode={self.downsample_mode}, "
+ f"analog_offset_v={self.analog_offset_v}V"
+ )
# Update rate label with configured sample rate
configured_rate_sps = 1e9 / self.sample_interval_ns
self.rate_label.setText(
f"Rate: ... : {self._format_rate_sps(configured_rate_sps)}"
)
- except KeyError:
- logger.debug("Metadata not fully available yet. Will retry.")
+ except KeyError as e:
+ logger.debug(f"Metadata not fully available yet: {e}. Will retry.")
def _update_heartbeat(self) -> None:
"""Update UI heartbeat to show the UI thread is alive."""
@@ -386,15 +448,18 @@ class HDF5LivePlotter(QWidget):
if current_size == 0:
return
+ # Read metadata if not already done
+ if not self.metadata_read:
+ self.read_metadata(f)
+ # If metadata still not available, wait for next update
+ if not self.metadata_read:
+ return
+
# Start the rate check timer on the first data point
if self.rate_check_start_time is None:
self.rate_check_start_time = time.perf_counter()
self.rate_check_start_samples = current_size
- # Read metadata if not already done
- if self.voltage_range_v is None:
- self.read_metadata(f)
-
# Dynamically calculate the number of timesteps for the display window
display_window_timesteps = int(
self.display_window_seconds / (self.sample_interval_ns * 1e-9)
diff --git a/picostream/main.py b/picostream/main.py
index 26a9b06..81d027f 100644
--- a/picostream/main.py
+++ b/picostream/main.py
@@ -148,7 +148,8 @@ class PicoStreamMainWindow(QMainWindow):
# Connect signals
self.start_button.clicked.connect(self.start_acquisition)
self.stop_button.clicked.connect(self.stop_acquisition)
- self.output_file_input.textChanged.connect(self.plotter.set_hdf5_path)
+ # Disconnect the automatic path update - we'll handle it manually in start_acquisition
+ # self.output_file_input.textChanged.connect(self.plotter.set_hdf5_path)
self.load_settings()
@@ -168,16 +169,25 @@ class PicoStreamMainWindow(QMainWindow):
output_file = self.output_file_input.text()
- # Reset plotter state and ensure a clean file for the new acquisition.
- self.plotter.set_hdf5_path(output_file)
+ # Stop plotter updates and clear all state BEFORE changing file path
+ self.plotter.stop_updates()
+
+ # Clear the plotter's display and reset all internal state
+ self.plotter.reset_for_new_file()
+
+ # Remove old file if it exists
if os.path.exists(output_file):
try:
os.remove(output_file)
+ logger.info(f"Removed existing file: {output_file}")
except OSError as e:
self.on_acquisition_error(f"Failed to remove old file: {e}")
self.on_acquisition_finished() # Reset UI state
return
+ # Now set the new file path (plotter is stopped and cleared)
+ self.plotter.set_hdf5_path(output_file)
+
settings = {
"sample_rate_msps": self.sample_rate_input.value(),
"resolution_bits": int(self.resolution_input.currentText()),
@@ -206,6 +216,8 @@ class PicoStreamMainWindow(QMainWindow):
self.worker.error.connect(self.on_acquisition_error)
self.thread.start()
+
+ # Start plotter updates - it will wait for the file to exist with valid data
self.plotter.start_updates()
def stop_acquisition(self) -> None:
diff --git a/picostream/pico.py b/picostream/pico.py
index eca2877..cd1b13b 100644
--- a/picostream/pico.py
+++ b/picostream/pico.py
@@ -153,6 +153,8 @@ class PicoDevice:
status = ps.ps5000aMaximumValue(self.handle, ctypes.byref(self.max_adc))
check_status(status, "ps5000aMaximumValue")
+
+ logger.info(f"Device opened - Resolution: {self.resolution}, max_adc queried: {self.max_adc.value}")
def set_channel(
self, chan: str, en: int, coup: str, voltage_range_str: str, offset: float
@@ -196,8 +198,9 @@ class PicoDevice:
self.handle, channel_enum, en, coupling_enum, channel_range_enum, offset
)
check_status(status, f"ps5000aSetChannel ({chan})")
- logger.debug(
- f"Range '{voltage_range_str}' maps to enum value: {channel_range_enum}, voltage range: {self.voltage_range_v}V"
+ logger.info(
+ f"Channel configured - Range: '{voltage_range_str}' (enum: {channel_range_enum}), "
+ f"voltage_range_v: {self.voltage_range_v}V, max_adc: {self.max_adc.value}"
)
def set_data_buffer(self, chan: str, segment: int, rat: str) -> None: