This is a program that commands a laser to cut arbitrary shapes defined by the user. You draw a shape, and it works out the path the laser has to follow to clear that shape without touching the material you want to keep.
It talks directly to WITec Control, the software that runs a WITec confocal Raman microscope. The use case it was built for is cutting flakes of graphene, multilayer graphene and graphite on a silicon wafer, using a 1064 nm laser.
A planned cut. The dark red outline is the area you asked to remove and the green circle is an island you asked to keep. The path itself is blue where the laser is driven by a line scan, red where it is a plain stage move, and grey dashed where the laser is off and the stage is just travelling.
The instrument is only needed for the cut itself. Drawing, planning, the preview, saving and loading paths all work anywhere.
Without uv: Install the four dependencies yourself and run the entry point directly:
pip install numpy matplotlib shapely pillow
python run.pyYou get whatever versions pip resolves rather than the tested versions pinned in the lockfile below.
With uv: This ensures the tested versions are used. The prerequisite is uv, which fetches the right Python and installs the pinned dependencies for you:
uv sync
uv run run.pyIf matplotlib fails to import with an Application Control policy has blocked this file error, Windows Smart App Control is refusing the unsigned extension modules inside the Python that uv downloaded. It is on by default on clean Windows 11 installs. Install Python 3.12 from python.org and use uv sync --python 3.12 instead.
The window opens, you can draw a shape, plan it, step through the path move by move, save it and load it again. Only two buttons need the instrument on the other end: Run on hardware and Get microscope image. Everything else is ordinary geometry.
The program creates a list of waypoints:
(x_um, y_um, laser_on, cut_type) # cut_type is "fast" or "safe"laser_on says whether the laser is on while the stage moves to that point. A list of these tuples is the entire plan. Nothing else describes the path.
The instrument gives you two ways to move the stage with the laser on. Each is good at what the other is bad at, so the program uses both and picks per segment.
| Fast (plain stage move) | Safe (WITec line scan) | |
|---|---|---|
| Accurate | along X or Y only | at any angle |
| Time | 0.14 s + length / speed |
about 9 s, whatever the length |
| Used for | filling in the bulk of a shape | edges that run at an angle |
Fast mode is an ordinary "move the stage to this position" command with the shutter open. It is quick, and the stage really does move at the speed it is told. The WITec software cannot be reliably trusted to use this mode to move in a straight line along a non horizontal/vertical path. Hence the second mode.
Safe mode uses WITec's own line-scan function, which drives the beam path along a defined line.
Rectangle. Either a spiral that clears a rectangle, or a spiral that cuts a kerf around a rectangle you want to keep. An axis-aligned rectangle is all horizontal and vertical moves, so it runs entirely in fast mode and needs no line scans at all. Use "cut in reverse order" for inward spiral.
Free shape. Click the corners of the area to remove, and optionally an area to keep. Hold Shift for an exactly straight edge, press Enter to close the shape, then press Plan the cut. Rotating a rectangle off a multiple of 90° hands it to this same planner, since a rotated spiral would need diagonal fast moves.
Import waypoints. Take a path from a file instead of drawing one. Any text containing waypoints will do, including a run log from an earlier cut.
Define waypoints. Type the path in point by point: an X and a Y for each point, and how the laser gets there. fast is a stage move with the laser on, slow a line scan, off a laser-off move at the travel speed. The path is drawn as you type, and a saved file brings the table back when it is imported.
Microscope and pasted images can be flipped horizontally or vertically in the layer panel, for a picture taken in a mirrored orientation.
| What it is | |
|---|---|
| Laser offset | Where the beam actually lands relative to the crosshair. Added to every coordinate sent to the stage. |
| Stage offset | The WITec coordinate of the displayed origin, so the display can be centred on (0, 0) without zeroing the instrument. |
The laser offset is measured by hand, from a burn mark.
Neither offset is written into the waypoints.
By default, every cut writes a run log to the run_data folder: what was cut, every parameter exactly as it read when Run was pressed, both offsets, the camera calibration, the plan geometry, the measured duration of every single instrument call, and the full waypoint list. Save waypoints writes the path on its own, without cutting anything.
Either file can be read straight back into the program. Either can also be plotted with Simple_files/simple_waypoint_plot.py, a standalone script with no dependency on the rest of the code — useful for checking a path on a machine that does not have the app set up.
This is specific to the setup in the David Goldhaber-Gordon lab at Stanford.
Before cutting:
- Enable COM Automation in WITec Control.
- Set the notch filter turret to 1064, move the OD1 filter out by 2 clicks, and take the linearly polarised filter out.
- Use the 100x objective and focus on the sample optically. The laser itself does not need focusing separately.
- Mount the sample with clips. They reduce drift noticeably.
Spot size against laser power, measured:
| Power | Spot |
|---|---|
| 40% | 0.66 µm — recommended, and what the defaults assume |
| 45% | 0.74 µm |
| 50% | 0.82 µm |
| 55% | 0.88 µm |
| 60% | 0.90 µm |
Expect roughly 1 µm of offset between what you draw and what you get. The stage can drift.
The first pass might not be perfect due to hardware limitations. Play around with the parameters and repeat a cut multiple times. My first recommendation would be to use vertical fill lines on the second pass, this should cut away most uncut residue.
| File | What it does |
|---|---|
run.py |
Entry point, and the values the parameter boxes start with. |
gui_app.py |
The window: preview, layers, drawing, typed points, import, run log. |
gui_widgets.py |
Small reusable tkinter pieces. |
plan_types.py |
The waypoint type, its validation, and the conversion both planners share. |
path_rectangle.py |
Rectangle spirals. Hands rotated rectangles to the free planner. |
path_free.py |
The clearance rule, outline passes, edge staircase and fill. |
geometry.py |
Pure 2D helpers. |
design_file.py |
The design behind a path: how it is written and read back. |
hardware.py |
Executes a waypoint list on the instrument. One public function, cut(). |
image_acquisition.py |
A microscope image, plus the calibration needed to place it. |
Simple_files/ |
Minimal reference scripts, and the standalone path plotter. |
The boundary that matters is hardware.py. It contains nothing but stage and laser commands: it takes a waypoint list, executes it, and reports what happened. No planning, no geometry, no drawing. It and image_acquisition.py are the only two files that touch the SDK at all, and hardware.py is the only one that moves the stage or opens the shutter. Everything upstream of it produces a waypoint list and everything downstream reads a file, which is why the rest of the program can be run and checked with no instrument attached.
Simple_files/ holds the two reference scripts that establish how to talk to the instrument at all — run_cut_basic.py for a fast cut and run_cut_linescan.py for a line scan. They are as short as they can possibly be, on purpose, and are the thing to read first if the SDK behaves unexpectedly.
Created by Wiktor Kwapinski while working as a Visiting Student Researcher in the Stanford Goldhaber-Gordon Group. AI tools were used for this project. Released under the MIT licence — see LICENSE.
