Phase 6: strip signature-restating docstrings; correct README/SETUP
- Collapsed Args:/Returns:/Raises: blocks that only restated the signature (364 lines): tektronix_base 48% -> ~20% doc density, helios_laser and uc480_camera likewise. Only docstrings whose entire body was those sections were touched. - Preserved verbatim the comments that carry hardware knowledge the code can't express: uc480's USB split-transaction contention note (with its measured fps), the IS_ALLOW_STARTER_FW_UPLOAD segfault explanation, the QImage-copy rationale, and tektronix's NUMFRAMESACQuired warning. - README: project structure, quick start, and every usage example now describe code that exists (they referenced hardware/bbd202.py, CoherentHOPSLaser, get_curve_binary, and 'python -m scanengine.app', none of which do). Added a headless-scan example and a read-a-scan-file example, since reuse without the GUI is the point of the refactor. - SETUP: structure section defers to README instead of keeping a second stale copy; documents the vendored uEye SDK and the Genesis quarantine. - ruff is now clean repo-wide: fixed the remaining raise-from, unused loop variables, placeholder f-strings, and a non-strict zip; the widget-layout semicolon idiom is an explicit config ignore rather than 22 standing warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ scanengine-3 is a unified platform for scanning acoustic microscopy and precisio
|
||||
### Key Features
|
||||
|
||||
- **Stage Control**: ThorLabs BBD202/BBD203 motor controller with 3-axis positioning
|
||||
- **Laser Systems**: Helios and Coherent HOPS laser control
|
||||
- **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
|
||||
- **Real-time Monitoring**: Live status updates and progress tracking
|
||||
@@ -30,11 +30,6 @@ scanengine-3 is a unified platform for scanning acoustic microscopy and precisio
|
||||
- Multiple pulse modes
|
||||
- Temperature and power monitoring
|
||||
|
||||
- **Coherent HOPS Laser**
|
||||
- I2C/FTDI interface
|
||||
- Power and modulation control
|
||||
- Temperature monitoring
|
||||
|
||||
### Data Acquisition
|
||||
- **Tektronix MSO/DPO Series Oscilloscopes**
|
||||
- Direct socket communication (no VISA overhead)
|
||||
@@ -42,69 +37,69 @@ scanengine-3 is a unified platform for scanning acoustic microscopy and precisio
|
||||
- Multi-channel waveform capture
|
||||
- Configurable triggering
|
||||
|
||||
### Microscope Systems
|
||||
- **Genesis Microscope** (stub implementation)
|
||||
- **T3R Timing Device** (stub implementation)
|
||||
### 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/
|
||||
├── scanengine/ # Main application package
|
||||
│ ├── __init__.py
|
||||
│ ├── app.py # Main application entry point
|
||||
│ ├── main_launcher.ui # Main launcher UI
|
||||
│ ├── new_scan_wizard.ui # Scan wizard UI
|
||||
│ └── options.ui # Options dialog UI
|
||||
├── 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
|
||||
│ ├── 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/ # Hardware driver package
|
||||
│ ├── __init__.py
|
||||
│ ├── bbd202.py # ThorLabs stage controller
|
||||
│ ├── uc480_camera.py # IDS/ThorLabs camera
|
||||
│ ├── tektronix_base.py # Tektronix oscilloscope
|
||||
│ ├── coherent_hops_laser.py # Coherent HOPS laser
|
||||
│ └── genesis_core.py # Genesis laser core logic
|
||||
├── 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)
|
||||
│
|
||||
├── scanning/ # Scan planning package
|
||||
│ ├── __init__.py
|
||||
│ ├── sc3_scan_model.py # Scan model
|
||||
│ └── stage_scan_plan_generator.py # Scan path planning
|
||||
├── gui/ # Shared PyQt6 layer
|
||||
│ ├── scan_bridge.py # QtScanController over core.scan_engine
|
||||
│ ├── qt_t3r.py # Qt adapter over the T3R driver
|
||||
│ ├── qt_workers.py # QueueWorker / PollingQueueWorker bases
|
||||
│ └── widgets.py # ConnectionBar, LogConsole, PortSelector…
|
||||
│
|
||||
├── tools/ # Standalone executable tools
|
||||
│ ├── genesis_laser_control.py # Standalone Genesis app
|
||||
│ └── genesis_laser_gui.py # Alternative Genesis GUI
|
||||
├── 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/ # Test files
|
||||
│ ├── __init__.py
|
||||
│ ├── test_camera_integration.py
|
||||
│ ├── test_genesis_connection.py
|
||||
│ ├── test_genesis_protocol.py
|
||||
│ ├── test_rotated_aoi.py
|
||||
│ └── test_temperature_scaling.py
|
||||
├── tests/ # pytest suite
|
||||
│ ├── golden/ # v6 .sras + geometry fixtures
|
||||
│ ├── fakes.py # Recording fake stage/scope/rotator
|
||||
│ └── test_*.py
|
||||
│
|
||||
├── docs/ # Documentation
|
||||
│ ├── hardware/ # Hardware documentation
|
||||
│ │ ├── BBD203_CONNECTION_GUIDE.md
|
||||
│ │ ├── BBD203_Communications_Protocol.md
|
||||
│ │ ├── BBD203_DRIVER_README.md
|
||||
│ │ ├── HELIOS_DRIVER_README.md
|
||||
│ │ ├── GENESIS_LASER_README.md
|
||||
│ │ └── laser_control_implementation_guide.md
|
||||
│ └── protocols/ # Protocol specifications
|
||||
│ ├── apt_communications_protocol.pdf
|
||||
│ ├── helios_comms_protocol.pdf
|
||||
│ └── thorlabs_mls_protocol.pdf
|
||||
├── docs/
|
||||
│ ├── hardware/ # Driver notes
|
||||
│ ├── protocols/ # Vendor protocol PDFs
|
||||
│ └── genesis_verification.md # Bench checklist (see KNOWN_ISSUES.md)
|
||||
│
|
||||
├── lib/ # Binary libraries (not in git)
|
||||
│ ├── libueye_api64.so.3.82
|
||||
│ ├── ueye_loader.c
|
||||
│ └── ueye_loader.so
|
||||
│
|
||||
├── config.json # System configuration
|
||||
├── requirements.txt # Python dependencies
|
||||
├── README.md # This file
|
||||
├── SETUP.md # Setup instructions
|
||||
└── LICENSE # License file
|
||||
├── 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
|
||||
@@ -126,14 +121,32 @@ pip install -r requirements.txt
|
||||
### Running the Application
|
||||
|
||||
```bash
|
||||
# Main GUI application
|
||||
python -m scanengine.app
|
||||
# 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
|
||||
```
|
||||
|
||||
# Alternative Genesis laser GUI
|
||||
python tools/genesis_laser_gui.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
|
||||
@@ -143,66 +156,137 @@ python tools/genesis_laser_gui.py
|
||||
- **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
|
||||
|
||||
### Stage Control
|
||||
### 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 hardware.bbd202 import BBD202Controller
|
||||
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
|
||||
|
||||
# BBD202/BBD203 controller example
|
||||
controller = BBD202Controller()
|
||||
controller.connect("/dev/ttyUSB0") # Serial port
|
||||
# Use controller for stage operations
|
||||
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}")
|
||||
```
|
||||
|
||||
### Oscilloscope Acquisition
|
||||
### 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()
|
||||
scope.connect("192.168.1.100", 4000)
|
||||
scope.set_acquire_mode("SAMPLE")
|
||||
waveform = scope.get_curve_binary(1) # Channel 1
|
||||
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
|
||||
### Laser control
|
||||
|
||||
```python
|
||||
from hardware.coherent_hops_laser import CoherentHOPSLaser
|
||||
from hardware.helios_laser import HeliosLaser
|
||||
|
||||
laser = CoherentHOPSLaser()
|
||||
laser.connect()
|
||||
laser.set_power_level(50.0) # 50% power
|
||||
laser.enable_output(True)
|
||||
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
|
||||
### Camera control
|
||||
|
||||
```python
|
||||
from hardware.uc480_camera import UC480Camera
|
||||
from hardware.uc480_camera import UC480Camera, find_camera_bus_conflicts
|
||||
|
||||
camera = UC480Camera(camera_id=0)
|
||||
find_camera_bus_conflicts() # warns about USB bus contention
|
||||
camera = UC480Camera(camera_id=1)
|
||||
camera.initialize()
|
||||
camera.start_capture()
|
||||
# Camera operations
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Stage Settings
|
||||
Stage configuration is stored in `~/.nuescan/stage_settings.json`:
|
||||
- Velocity and acceleration profiles
|
||||
- Trigger configuration
|
||||
- Axis limits and safety parameters
|
||||
### 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.
|
||||
|
||||
### Serial Port Configuration
|
||||
Hardware devices are accessed via:
|
||||
- **BBD202/203**: USB with automatic serial number detection
|
||||
- **Helios**: RS-232 serial port (9600 baud, 8N1)
|
||||
- **HOPS Laser**: FTDI USB (I2C interface)
|
||||
### 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
|
||||
|
||||
Reference in New Issue
Block a user