haniwers.v1.daq.sampler._base#
Sampler orchestration layer - coordinates reading, writing, and configuration.
This module provides the main Sampler class that coordinates all data acquisition operations by delegating to specialized modules:
EventReader (_reader.py) for reading events from device
EventWriter (_writer.py) for writing events to CSV
Generators (_iterators.py) for iteration logic
Utilities (_helpers.py) for helper functions
The Sampler class itself focuses on orchestration and public API.
Module Contents#
Classes#
Orchestrate data acquisition from detector to CSV files. |
Functions#
Convenience function for creating and running Sampler in one call. |
API#
- class haniwers.v1.daq.sampler._base.Sampler(device: Union[haniwers.v1.daq.device.Device, haniwers.v1.daq.mocker.Mocker, haniwers.v1.daq.mocker.RandomMocker], config: Union[haniwers.v1.config.model.DaqConfig, haniwers.v1.config.model.ScanConfig, haniwers.v1.config.model.SamplerConfig], output_dir: Optional[pathlib.Path] = None, show_progress: bool = True, mac_address: str = 'unknown')#
Orchestrate data acquisition from detector to CSV files.
What is Sampler? The recommended way to capture data from a detector (real or mock) and store it on disk. It handles all the details: reading measurements, adding timestamps, organizing files, and showing progress.
Key features: ✓ Reads events from any source: real Device, Mocker (CSV playback), or RandomMocker (synthetic data) ✓ Multiple acquisition modes: Fixed count or fixed time duration ✓ Streaming or buffered: Write events immediately or collect then write ✓ Progress tracking: Optional progress bar shows data collection status ✓ Timestamped files: Automatically generates human-readable filenames
Life cycle: 1. Create: Sampler(device, config, output_dir) 2. Acquire: Run acquisition (by count or by time) 3. Output: CSV files saved with timestamped names
This class coordinates EventReader and EventWriter for all operations.
Example (real detector with fixed count):
from pathlib import Path from haniwers.v1.config.model import DaqConfig from haniwers.v1.daq.device import Device from haniwers.v1.daq.sampler import Sampler # Setup device = Device(config.device) device.connect() # Create sampler sampler = Sampler( device=device, config=config.daq, output_dir=Path("./data") ) # Collect 1000 events into one file, show progress sampler.acquire_by_count( file_path=Path("./data/measurement.csv"), event_count=1000 ) device.disconnect()
Example (mock detector with multiple files):
from pathlib import Path from tempfile import TemporaryDirectory from haniwers.v1.daq.mocker import RandomMocker, MockerConfig from haniwers.v1.daq.sampler import Sampler from haniwers.v1.config.model import DaqConfig # Create mock device (generates synthetic data) mock_device = RandomMocker( config=MockerConfig(csv_path=None, speed=10.0), seed=42 # Reproducible random data ) # Configuration for DAQ daq_config = DaqConfig( label="test", workspace=".", filename_prefix="synthetic", filename_suffix=".csv", events_per_file=100, number_of_files=5, stream_mode=True # Write immediately, good for data safety ) # Create and run sampler with TemporaryDirectory() as tmpdir: sampler = Sampler( device=mock_device, config=daq_config, output_dir=Path(tmpdir), show_progress=True # Show progress bar ) sampler.run(mode="daq", files=5)
Advanced features: - sampler.acquire_by_count(): Collect fixed number of events - sampler.acquire_by_time(): Collect for fixed duration (e.g., 10 seconds) - show_progress: Optional progress bar (tqdm) during acquisition - stream_mode: Immediate write (safer) vs buffered (faster)
Initialization
Create a Sampler object with device and configuration.
What this does: Stores your detector settings (real, mock, or random) but does NOT start data collection yet. Call run() or acquire_by_count() or acquire_by_time() to actually collect measurements and save to CSV files.
Args: device (Device | Mocker | RandomMocker): The data source - Device: Real OSECHI detector connected to USB/serial port - Mocker: Playback pre-recorded CSV file (for replay/replay testing) - RandomMocker: Generate synthetic measurements (for development)
config (DaqConfig | ScanConfig | SamplerConfig): Settings for session - SamplerConfig: Modern config with mode and workspace built-in (NEW) - DaqConfig/ScanConfig: Legacy config (requires output_dir parameter) - events_per_file: How many measurements per CSV file - number_of_files: How many output files to create (for run() mode) - stream_mode: Write immediately (True) or buffer first (False) - filename_prefix: Name pattern for output files - (SamplerConfig only) mode: "count_based" or "time_based" - (SamplerConfig only) workspace: Directory for output files output_dir (Path, optional): Directory where CSV files will be saved - For SamplerConfig: Uses config.workspace (output_dir not needed) - For DaqConfig/ScanConfig: Required, must exist Example: Path("./data") or Path("/home/user/measurements") show_progress (bool, optional): Show progress bar during collection Default: True (shows tqdm progress bar) Set to False for scripts/batch jobs that don't need visual feedback mac_address (str, optional): Device MAC address for filename traceability Default: "unknown" (fallback when MAC retrieval fails) Expected: 12 lowercase hex characters (e.g., "aabbccddee00") Used to embed device identifier in generated filenamesRaises: FileNotFoundError: If output directory doesn’t exist or can’t be created ValueError: If config missing required fields (events_per_file, number_of_files)
Example (with SamplerConfig - RECOMMENDED):
from pathlib import Path from haniwers.v1.daq.device import Device from haniwers.v1.daq.sampler import Sampler device = Device(config.device) device.connect() # Create sampler with SamplerConfig (workspace managed automatically) sampler = Sampler( device=device, config=config.sampler, # Has workspace built-in show_progress=True ) # Run acquisition based on config.mode (count_based or time_based) sampler.run(files=5) device.disconnect()
Example (with DaqConfig - LEGACY):
from pathlib import Path from haniwers.v1.daq.device import Device from haniwers.v1.daq.sampler import Sampler device = Device(config.device) device.connect() sampler = Sampler( device=device, config=config.daq, output_dir=Path("./data"), show_progress=True ) sampler.acquire_by_count(Path("./data/run1.csv"), 1000) device.disconnect()
- timestamped_filename(fid: int, mac_address: str = 'unknown') pathlib.Path#
Generate timestamped CSV filename with MAC address.
What this does: Creates a unique filename for each CSV file, incorporating: - Filename prefix from config - File ID (0000000, 0000001, etc.) to distinguish multiple files - MAC address for device identification - .csv extension
Args: fid (int): File ID number (incremented for each file in session) Example: 0 → “data_0000000_aabbccddee00.csv” Example: 1 → “data_0000001_aabbccddee00.csv”
mac_address (str, optional): Device MAC address for traceability Default: "unknown" (fallback if MAC not available) Format: 12 lowercase hex characters (e.g., "aabbccddee00")Returns: Path: Full path to output file Example: Path(“./data/data_0000000_aabbccddee00.csv”)
When to use: - Rarely called directly; used internally by acquire_by_count/time() - Useful for custom filename generation if needed
Example:
sampler = Sampler(device, config, Path("./data")) # Generate filenames for files 0-2 for i in range(3): filename = sampler.timestamped_filename(i, "aabbccddee00") print(filename) # Output: # ./data/data_0000000_aabbccddee00.csv # ./data/data_0000001_aabbccddee00.csv # ./data/data_0000002_aabbccddee00.csv
- acquire_by_count(file_path: pathlib.Path, event_count: int)#
Collect exactly N measurements and save to CSV file (with progress bar).
Delegates to EventWriter.acquire_by_count(). See EventWriter documentation for details.
- acquire_by_time(file_path: pathlib.Path, duration: float, sleep_interval: float)#
Collect measurements for a fixed duration and save to CSV file.
Delegates to EventWriter.acquire_by_time(). See EventWriter documentation for details.
- run(mode: str = None, files: int = None)#
High-level API for multi-file data acquisition.
What this does: Collects data in multiple files using either count-based or time-based mode. Generates timestamped filenames automatically and coordinates all I/O.
Args: mode (str, optional): “count_based” or “time_based” Default: Uses self.mode from config “count_based”: Collect self.events measurements per file “time_based”: Collect for self.duration seconds per file
files (int, optional): Number of files to create Default: Uses self.files from config Example: files=5 creates 5 separate CSV filesExample:
sampler = Sampler(device, config, Path("./data")) # Count-based: 5 files, 1000 events each sampler.run(mode="count_based", files=5) # Time-based: 5 files, 10 seconds each sampler.run(mode="time_based", files=5)
- haniwers.v1.daq.sampler._base.run_session(device, config, output_dir, mac_address='unknown', show_progress=True)#
Convenience function for creating and running Sampler in one call.
What this does: Creates a Sampler object and immediately runs acquisition with default settings. Useful for simple scripts that don’t need advanced configuration.
Args: device: Device or Mocker instance config: DaqConfig, ScanConfig, or SamplerConfig instance output_dir: Directory for CSV output files mac_address: Device MAC address (default: “unknown”) show_progress: Whether to show progress bar (default: True)
Example:
from haniwers.v1.daq.sampler import run_session device = Device(config.device) device.connect() # Quick one-liner for acquisition run_session(device, config.daq, Path("./data"), show_progress=True) device.disconnect()