# scanengine-3 SRAS Scanning and Instrumentation Control Platform ## Overview scanengine-3 is a unified platform for scanning acoustic microscopy and precision instrumentation control. It integrates multiple hardware control modules into a single cohesive PyQt6-based application. ### Key Features - **Stage Control**: ThorLabs BBD202/BBD203 motor controller with 3-axis positioning - **Laser Systems**: Helios pulsed laser and Genesis CW laser control - **Data Acquisition**: Tektronix oscilloscope integration with fast-frame support - **Scan Planning**: Automated raster scan generation and execution - **Angle Inspection**: Park the rig at random points across a plan's angles to check the SAW response on the scope before committing to a long scan - **Real-time Monitoring**: Live status updates and progress tracking ## Hardware Components ### Motion Control - **ThorLabs BBD202/BBD203 Motor Controller** - 3-channel APT protocol driver - Precision positioning with encoder feedback - Programmable velocity and acceleration - Trigger output support for synchronized data acquisition ### Laser Systems - **Helios Laser System** - Frequency control (16.7-125 kHz) - Current control (0-7000 mA) - Multiple pulse modes - Temperature and power monitoring ### Data Acquisition - **Tektronix MSO/DPO Series Oscilloscopes** - Direct socket communication (no VISA overhead) - Fast-frame acquisition for high-speed scanning - Multi-channel waveform capture - Configurable triggering ### Rotation / Focus - **T3R four-channel stepper controller** - Focus axis plus the GR rotation stage (12.5:1 gear train) - Custom binary framing protocol over USB serial ## Project Structure The codebase is split so that everything needed to run a scan is importable without PyQt6 or any vendor SDK — `core/` is the headless engine, `gui/` is the shared Qt layer, and the root scripts are entry points. ``` scanengine-3/ ├── core/ # Headless: no PyQt6, no vendor SDKs │ ├── scan_engine.py # ScanEngine — full acquisition sequence │ ├── scan_geometry.py # ScanPlan, rotated-bbox planning, limits │ ├── scan_resume.py # Resume planning (frontier rule) │ ├── scope_sras.py # Oscilloscope SCPI policy for SRAS │ ├── scope_burst.py # Burst-mode FastFrame sizing + row splitting │ ├── scope_inspect.py # Scope setup for pre-scan angle inspection │ ├── angle_inspect.py # AngleInspector — park on a point per angle │ ├── rotation.py # GR rotation axis settings + moves │ ├── sras_format.py # v6 .sras writer/reader (memory-mapped) │ ├── sras_analysis.py # Image reducers + SAW matched filter │ └── config.py # ScanDefaults ⇄ aui_defaults.json │ ├── hardware/ # Device drivers (Qt-free) │ ├── serial_util.py # Shared 8N1 open + port enumeration │ ├── t3r_driver.py # T3R stepper controller │ ├── t3r_protocol.py # T3R frame encode/decode │ ├── helios_laser.py # Helios pulsed laser │ ├── tektronix_base.py # Tektronix oscilloscope (raw SCPI) │ ├── uc480_camera.py # IDS/ThorLabs uEye camera (returns QImage) │ ├── genesis_core.py # Genesis laser — QUARANTINED, see below │ └── pybbd202/ # ThorLabs BBD202 stage (APT protocol) │ ├── gui/ # Shared PyQt6 layer │ ├── scan_bridge.py # QtScanController over core.scan_engine │ ├── inspect_bridge.py # QtAngleInspector over core.angle_inspect │ ├── qt_t3r.py # Qt adapter over the T3R driver │ ├── qt_workers.py # QueueWorker / PollingQueueWorker bases │ └── widgets.py # ConnectionBar, LogConsole, PortSelector… │ ├── sc3_aui_app.py # Main acquisition application ├── sras_viewer.py # Scan data viewer ├── sras_scan_manager.py # CLI: inspect/export/delete angles ├── t3r_control_panel.py # T3R panel (used by the main app) ├── helios_test_app.py # Per-device test benches ├── bbd202_test_app.py ├── camera_test_app.py ├── sc3-aui-*.ui # Qt Designer files loaded at runtime │ ├── tests/ # pytest suite │ ├── golden/ # v6 .sras + geometry fixtures │ ├── fakes.py # Recording fake stage/scope/rotator │ └── test_*.py │ ├── docs/ │ ├── hardware/ # Driver notes │ ├── protocols/ # Vendor protocol PDFs │ └── genesis_verification.md # Bench checklist (see KNOWN_ISSUES.md) │ ├── lib/ # Vendored IDS uEye SDK (not in git) ├── aui_defaults.json # Persisted ports / scope IP / save dir ├── scan_format.md # .sras binary format specification ├── KNOWN_ISSUES.md # Open questions needing the hardware └── requirements.txt ``` ## Quick Start ### Installation ```bash # Clone or navigate to project directory cd scanengine-3 # Create virtual environment (recommended) python -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate # Install dependencies pip install -r requirements.txt ``` ### Running the Application ```bash # Main acquisition application python sc3_aui_app.py # Scan data viewer python sras_viewer.py # Inspect / export / delete angles in a .sras file python sras_scan_manager.py path/to/scan.sras # Per-device test benches python helios_test_app.py python bbd202_test_app.py python camera_test_app.py # Genesis laser control tool python tools/genesis_laser_control.py ``` ### Running the tests The suite is hardware-free: fake drivers and committed fixtures stand in for the rig. ```bash pip install pytest ruff python -m pytest tests/ -q ``` ## Dependencies - **PyQt6** (>=6.4.0) - GUI framework - **pyserial** (>=3.5) - Serial communication - **pyvisa** (>=1.13.0) - VISA instrument control - **pyvisa-py** (>=0.7.0) - Pure Python VISA backend - **pyftdi** (>=0.54.0) - FTDI USB device support - **numpy** (>=1.20.0) - Array processing - **scipy** (>=1.10) - Signal processing (viewer SAW pipeline) - **matplotlib** (>=3.7) - Plotting (viewer, live scan preview) - **pyueye** (>=4.95.0) - IDS uEye camera SDK bindings (camera only) ## Known hardware caveats `hardware/genesis_core.py` is quarantined: it diverges from the reference implementation in `tools/genesis_laser_gui.py` in ways that need the laser on the bench to settle. See [KNOWN_ISSUES.md](KNOWN_ISSUES.md) and [docs/genesis_verification.md](docs/genesis_verification.md) before changing either file. ## Usage Examples ### Running a scan without any GUI The acquisition sequence lives in `core.scan_engine` and takes plain drivers plus callbacks, so a script (or a future simpler GUI) can drive the identical scan the main app runs: ```python from pathlib import Path from core.scan_engine import ScanCallbacks, ScanEngine from core.scan_geometry import build_plan from core.rotation import RotationAxis from hardware.pybbd202 import ThorlabsServoDriver from hardware.tektronix_base import TektronixOscilloscopeBase from hardware.t3r_driver import T3RDriver plan = build_plan(x_start=10.0, y_start=10.0, x_delta=20.0, y_delta=10.0, num_angles=3, row_spacing=0.25, laser_freq_hz=20000.0, velocity_mm_s=100.0) stage = ThorlabsServoDriver(); stage.connect("/dev/ttyUSB0") scope = TektronixOscilloscopeBase("192.168.100.105"); scope.connect() t3r = T3RDriver(); t3r.open("/dev/ttyACM0") engine = ScanEngine(stage, scope, RotationAxis(t3r), plan, Path("/data/SRAS/demo.sras"), callbacks=ScanCallbacks(on_status=print, prompt=lambda t, m: input(f"{t}: {m} "))) result = engine.run() # blocking; engine.abort() is thread-safe print(f"wrote {result.rows_written} rows to {result.path}") ``` ### Reading a scan file `SrasFile` memory-maps the data block, so opening a multi-gigabyte scan costs only the pages actually touched: ```python from core.sras_format import SrasFile from core.sras_analysis import CH4_IDX, ChannelCalibration, compute_dc_image with SrasFile("/data/SRAS/demo.sras") as sras: print(sras.header.n_angles, "angles") for st in sras.angle_status(): # handles aborted/partial files print(f" angle {st.index}: {st.n_rows_available}/{st.n_rows} rows ({st.status})") view = sras.load_angle(0) # (rows, channels, frames, samples) calib = ChannelCalibration.from_preambles(sras.preambles) dc_mv = calib.adc_to_mv(compute_dc_image(view, CH4_IDX), CH4_IDX) ``` ### Stage control ```python from hardware.pybbd202 import AXIS_X, AXIS_Y, ThorlabsServoDriver stage = ThorlabsServoDriver() stage.connect("/dev/ttyUSB0") # raises if no bay responds stage.enable_axis(AXIS_X) stage.home_axis(AXIS_X, timeout=120.0) stage.move_axis_absolute(AXIS_X, 25.0, timeout=30.0) ``` ### Oscilloscope acquisition ```python from core.scope_sras import configure_acquisition, configure_channels from hardware.tektronix_base import TektronixOscilloscopeBase scope = TektronixOscilloscopeBase("192.168.100.105", port=4000) scope.connect() configure_channels(scope) # standard SRAS front-end setup samples_per_frame = configure_acquisition(scope) ``` ### Laser control ```python from hardware.helios_laser import HeliosLaser laser = HeliosLaser() laser.connect("/dev/ttyUSB1") laser.set_current_ma(1200) laser.set_laser_enable(True) print(laser.get_diode_temp_c(), "°C") laser.disconnect() # always explicit — no __del__ ``` ### Camera control ```python from hardware.uc480_camera import UC480Camera, find_camera_bus_conflicts find_camera_bus_conflicts() # warns about USB bus contention camera = UC480Camera(camera_id=1) camera.initialize() camera.start_capture() ``` ## Configuration ### Persisted settings `aui_defaults.json` holds the ports, scope IP, and save directory the main app last used. It is read and written through `core.config.ScanDefaults`, which always writes every field — see KNOWN_ISSUES.md history for why partial writes were a problem. ### Fixed acquisition settings Scan velocity, laser frequency, sample rate, and the ramp geometry are constants in `core/scan_engine.py` and `core/scope_sras.py`, not user settings; a `.sras` file records them so resume can refuse a mismatch. ### Serial port configuration - **BBD202**: USB serial, APT protocol (`/dev/ttyUSB*`) - **T3R**: USB serial, custom binary framing (`/dev/ttyACM*`) - **Helios**: RS-232 (9600 baud, 8N1) - **Genesis**: USB serial, I2C-over-serial - **Oscilloscope**: Ethernet/LXI (TCP socket on port 4000) ## Development ### Adding New Hardware 1. Create driver module in `hardware/` directory 2. Implement connection, control, and status methods 3. Add UI elements to main window or create new dialog 4. Connect signals in `main_window.py` ### Testing Without Hardware All hardware modules include stub implementations or simulation modes. The GUI can be developed and tested without physical devices connected. ## Documentation Detailed documentation available in project subdirectories: - [BBD202/203 Driver Guide](docs/hardware/BBD203_DRIVER_README.md) - [BBD202/203 Connection Guide](docs/hardware/BBD203_CONNECTION_GUIDE.md) - [BBD202/203 Communications Protocol](docs/hardware/BBD203_Communications_Protocol.md) - [Helios Laser Guide](docs/hardware/HELIOS_DRIVER_README.md) - [Genesis Laser Guide](docs/hardware/GENESIS_LASER_README.md) - [Laser Control Implementation Guide](docs/hardware/laser_control_implementation_guide.md) - [Setup Instructions](SETUP.md) ## License Copyright (C) 2025 Thomas Ales Licensed under GNU General Public License v2.0 See LICENSE file for full license text. ## Support For issues, questions, or contributions, please refer to the project documentation or contact the development team. ## Version scanengine-3 v0.1.0 - Initial unified release