Qt-based interface for a two-dimensional polargraph scanner
Project description
QPolargraph
A Qt-based instrument interface for a two-dimensional polargraph scanner. Built on QInstrument.
A polargraph translates a payload to any position within its scan area using two stepper motors and a timing belt. Originally developed by Jürg Lehni and Uli Franke as a drawing machine, the design scales easily to large areas, delivers millimeter-scale positioning accuracy, and is very cost-effective to build.
Hardware
| Component | Description |
|---|---|
| 2× Nema-17 stepper motor | Mounted above and to either side of the scan area |
| GT2 timing belt | Spans both motors; payload hangs from its midpoint |
| GT2 drive gear (25-tooth) | One per motor; engages the belt |
| Adafruit Motor Shield v2 | Drives both steppers from a single Arduino |
| Arduino (Uno or compatible) | Runs the acam3 firmware; connects to the host via USB |
The belt forms a V-shape between the two motors. Each motor changes the length of one side of the V, moving the payload in two dimensions.
Firmware
The Arduino must be flashed with hardware/arduino/acam3/acam3.ino before first use.
The easiest way is to use the built-in installer:
qpolargraph-flash
This opens a dialog that detects the attached Arduino, installs any missing
Arduino libraries (Adafruit Motor Shield V2 Library, AccelStepper),
compiles, and uploads the firmware — all without opening the Arduino IDE.
It requires arduino-cli to be
installed and on PATH.
Alternatively, open hardware/arduino/acam3/acam3.ino in the
Arduino IDE and upload manually.
The firmware and package versions are coupled: Motors.identify() checks
that the connected Arduino reports the expected firmware version and that
the Adafruit Motor Shield is detected at I2C address 0x60, refusing to
open the port if either check fails.
Installation
Clone the repository and install in editable mode:
git clone https://github.com/davidgrier/QPolargraph
cd QPolargraph
python -m venv .qp
source .qp/bin/activate # Windows: .qp\Scripts\activate
pip install -e ".[dev]"
pip install PyQt6 # or PyQt5, PySide2, PySide6
QInstrument is installed automatically as a dependency.
Calibration
Five geometric parameters describe the polargraph. Set them in the
QPolargraphWidget control panel or pass them directly to Polargraph():
| Parameter | Default | Description |
|---|---|---|
ell |
1.0 m | Horizontal distance between the two motor pulley centres |
y0 |
0.1 m | Vertical distance from the pulleys to the home position |
pitch |
2.0 mm | GT2 belt tooth pitch |
circumference |
25 | Number of belt teeth on the drive gear |
steps |
200 | Motor steps per revolution |
Measure ell and y0 with a ruler after mounting the motors.
pitch, circumference, and steps are determined by the belt and gear
specifications and normally do not need to change.
Settings are saved to ~/.QScanner/ automatically when the application
closes, so calibration only needs to be done once.
Quick start
Scanner application
python -m QPolargraph
# or, after installation:
qpolargraph
The application opens a live plot showing the belt geometry and the scan trajectory. Use the Scan button to start a polar-arc sweep, or Home / Center to move the payload to the home or centre position.
Embedding in your own application
Subclass QScanner to add experiment-specific data acquisition:
from qtpy.QtWidgets import QApplication
from qtpy import QtCore
from QPolargraph.QScanner import QScanner
import sys
class MyScanner(QScanner):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.scanner.dataReady.connect(self.acquire)
@QtCore.Slot(object)
def acquire(self, position):
x, y = position
# measure something at (x, y) and call self.plotData(x, y, hue)
if __name__ == '__main__':
app = QApplication(sys.argv)
scanner = MyScanner()
scanner.show()
sys.exit(app.exec())
Controlling the hardware directly
from qtpy.QtCore import QCoreApplication
from QPolargraph.Polargraph import Polargraph
import sys
app = QCoreApplication(sys.argv)
pg = Polargraph(ell=0.8, y0=0.15).find() # auto-detects USB port
print(pg.position) # (x, y, running) in metres
pg.moveTo(0.0, 0.3)
while pg.running():
pass
pg.release()
Architecture
QInstrument.QSerialInstrument
└── Motors # acam3 serial protocol: goto, speed, position, …
└── Polargraph # geometry: step indexes ↔ Cartesian coordinates
QInstrument.QInstrumentWidget
└── QRasterScanWidget # scan-pattern parameter controls
QInstrument.QInstrumentWidget
└── QPolargraphWidget # polargraph hardware controls
QtCore.QObject
└── QScanPattern # base: rectangular perimeter trajectory
├── RasterScan # row-by-row zigzag raster
└── PolarScan # arc-by-arc sweep centred on the left pulley
QtWidgets.QMainWindow
└── QScanner # full application: live plot + scan controls
Development
Run the test suite:
source .qp/bin/activate
pytest tests/
Tests run automatically before every git push. To install the pre-push
hook in a fresh clone:
git config core.hooksPath .githooks
Acknowledgements
Work on this project at New York University is supported by the National Science Foundation of the United States under award number DMR-2438983.
References
H. W. Gao, K. I. Mishra, A. Winters, S. Wolin, and D. G. Grier, "Flexible wide-field high-resolution scanning camera for continuous-wave acoustic holography," Review of Scientific Instruments 89, 114901 (2018). https://doi.org/10.1063/1.5053666
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file qpolargraph-1.1.5.tar.gz.
File metadata
- Download URL: qpolargraph-1.1.5.tar.gz
- Upload date:
- Size: 40.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
37ba5840752bd9381b9efe2dd180d2e0dc551b332876224d07cb5f7f1040aa4c
|
|
| MD5 |
b06a8cdbd36550d4679f857baddc85c5
|
|
| BLAKE2b-256 |
86fcca7d44a99aa7aa2da11daf65349781776a624238be75650df87f386df85d
|
Provenance
The following attestation bundles were made for qpolargraph-1.1.5.tar.gz:
Publisher:
publish.yml on davidgrier/QPolargraph
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
qpolargraph-1.1.5.tar.gz -
Subject digest:
37ba5840752bd9381b9efe2dd180d2e0dc551b332876224d07cb5f7f1040aa4c - Sigstore transparency entry: 1338816344
- Sigstore integration time:
-
Permalink:
davidgrier/QPolargraph@db6ff3cae9126d808466f1d6301fb78fbc8667fd -
Branch / Tag:
refs/tags/v1.1.5 - Owner: https://github.com/davidgrier
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@db6ff3cae9126d808466f1d6301fb78fbc8667fd -
Trigger Event:
push
-
Statement type:
File details
Details for the file qpolargraph-1.1.5-py3-none-any.whl.
File metadata
- Download URL: qpolargraph-1.1.5-py3-none-any.whl
- Upload date:
- Size: 39.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ccebc606391b0d84d471525b1fa9cd209b6672b745e8b5dd7853723eebceb3a
|
|
| MD5 |
c115a190515978b651ce199ecdad4d51
|
|
| BLAKE2b-256 |
b11c7d0fe32ecb04d217b56389e1711bc17c387a89db72395f6cd47fbdd01304
|
Provenance
The following attestation bundles were made for qpolargraph-1.1.5-py3-none-any.whl:
Publisher:
publish.yml on davidgrier/QPolargraph
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
qpolargraph-1.1.5-py3-none-any.whl -
Subject digest:
3ccebc606391b0d84d471525b1fa9cd209b6672b745e8b5dd7853723eebceb3a - Sigstore transparency entry: 1338816346
- Sigstore integration time:
-
Permalink:
davidgrier/QPolargraph@db6ff3cae9126d808466f1d6301fb78fbc8667fd -
Branch / Tag:
refs/tags/v1.1.5 - Owner: https://github.com/davidgrier
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@db6ff3cae9126d808466f1d6301fb78fbc8667fd -
Trigger Event:
push
-
Statement type: