Skip to content

Commit a38eeb5

Browse files
committed
Updating goniometer
1 parent 0f7ffdd commit a38eeb5

2 files changed

Lines changed: 330 additions & 4 deletions

File tree

4.49 MB
Loading
Lines changed: 330 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,337 @@
1-
# openUC2 Goniometer
1+
# openUC2 Goniometer – Tutorial
22

3+
Measure sessile-drop contact angles with your **openUC2 modular microscope** running **ImSwitch** on a Raspberry Pi.
34

5+
![](./IMAGES/goniometer.png)
46

5-
![](./IMAGES/IMG_20260430_103804.jpg)
7+
Beware: There are cubes inside :)
68

7-
![](./IMAGES/IMG_20260430_104047.jpg)
9+
10+
11+
## 1. Hardware Overview
12+
13+
```
14+
┌─────────────────────────────┐
15+
│ openUC2 Cube "Stack" │
16+
│ │
17+
│ ┌──────────┐ │
18+
│ │ Pipette │ (syringe or │
19+
│ │ Holder │ dispenser) │
20+
│ └────┬─────┘ │
21+
│ │ droplet │
22+
│ ══════════════ substrate │
23+
│ │
24+
│ ┌───────────┐ │
25+
│ │HIK IMX178 │ USB3 camera │
26+
│ │ CCTV Lens │ ~90° to drop│
27+
│ │ 25 mm │ │
28+
│ └───────────┘ │
29+
│ │
30+
│ ┌──────────────────────┐ │
31+
│ │ LED Array + Diffuser │ │
32+
│ │ (back-light) │ │
33+
│ └──────────────────────┘ │
34+
└─────────────────────────────┘
35+
│ USB3
36+
┌─────────▼──────────┐
37+
│ Raspberry Pi 5 │
38+
│ running ImSwitch │
39+
└────────────────────┘
40+
│ Wi-Fi / Ethernet
41+
┌─────────▼──────────┐
42+
│ Browser (laptop) │
43+
│ ImSwitch UI │
44+
└────────────────────┘
45+
```
46+
47+
| Component | Details |
48+
|-----------|---------|
49+
| **Platform** | openUC2 modular cube system |
50+
| **Controller** | Raspberry Pi 5 (runs ImSwitch) |
51+
| **Camera** | HIK Vision IMX178 (USB 3.0, 6.4 MP, colour/mono) |
52+
| **Illumination** | LED array + diffuser — back-lit, homogeneous |
53+
| **Dispensing** | Syringe pipette held in a cube mount; drops ~1–10 µL |
54+
| **Substrate** | Flat sample placed on the cube base plate |
55+
56+
The camera observes the droplet from the side (macro view) so the contact line between the drop and substrate is visible as the widest horizontal extent of the drop silhouette.
57+
58+
59+
## 2. Software Stack
60+
61+
```
62+
Raspberry Pi
63+
└── ImSwitch (Python, FastAPI REST)
64+
└── GoniometerController (plugin)
65+
├── REST API /GoniometerController/…
66+
└── OpenCV image processing pipeline
67+
68+
Browser
69+
└── ImSwitch Frontend (React)
70+
└── GoniometerController.js (this UI)
71+
```
72+
73+
ImSwitch exposes the goniometer as a REST API.
74+
The React frontend is served alongside ImSwitch and communicates over the
75+
local network.
76+
77+
78+
## 3. In Action
79+
80+
The software workflow is explained in the following video. You have to connect to the Raspberry Pi's wifi or network (PW: `youseetoo`) and then navigate to the website http://192.168.4.1 and enable the Goniometer app in the side bar:
81+
82+
<iframe width="560" height="315" src="https://www.youtube.com/embed/i9ORYjFVBrc?si=PmFjd52UPWCQaZsi" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
83+
84+
85+
## 4. UI Walkthrough
86+
87+
The code for the backend is here:
88+
https://github.com/openUC2/ImSwitch/blob/master/imswitch/imcontrol/controller/controllers/GoniometerController.py
89+
90+
The code for the frontend is here:
91+
https://github.com/openUC2/ImSwitch/blob/master/frontend/src/components/GoniometerController.js
92+
93+
### 4.1 Live Camera Stream
94+
95+
The top-left card shows a **live MJPEG stream** from the HIK camera.
96+
97+
- The stream is updated continuously — use it to position the pipette and
98+
focus the droplet before snapping.
99+
- Zoom and pan are available on the **snapped image** (bottom card), not on
100+
the live view.
101+
102+
### 4.2 Setting a Crop ROI
103+
104+
For best results the algorithm only sees the droplet and its reflection — crop
105+
out any background clutter.
106+
107+
1. Click the **Crop icon** (✂) in the live stream card header.
108+
The cursor changes to a crosshair and an info banner appears.
109+
2. **Click and drag** a rectangle around the droplet (include a little
110+
background above and below).
111+
3. Release the mouse — the crop is sent to the backend and a gold dashed
112+
rectangle persists on the live view.
113+
4. To remove the crop, click the **×** on the orange "Crop active" chip, or
114+
click **Reset Crop**.
115+
116+
> **Why crop?** The algorithm searches for the widest contour in the image.
117+
> Bubbles, pipette edges, or substrate edges that extend further than the drop
118+
> width will confuse the baseline detection.
119+
120+
### 4.3 Camera Settings
121+
122+
Below the live stream you find two fields:
123+
124+
| Field | Typical value | Effect |
125+
|-------|--------------|--------|
126+
| **Exposure (ms)** | 2 – 10 ms | Shorter = less motion blur; too short = dark image |
127+
| **Gain** | 0 – 10 | Amplifies signal; higher gain adds noise |
128+
129+
Press **Enter** or click outside the field to apply.
130+
A well-exposed image shows a bright, uniform background and a clearly dark
131+
drop silhouette.
132+
133+
### 4.4 Snap an Image
134+
135+
Click the green **Snap** button.
136+
137+
- The backend captures a full-resolution frame from the HIK camera.
138+
- If a crop ROI is active, only the cropped region is returned.
139+
- The snapped image appears in the lower card.
140+
- The raw image is also saved as a TIFF on the Raspberry Pi
141+
(`/tmp/goniometer_*.tiff` by default).
142+
143+
### 4.5 Automatic Measurement
8144

9145
![](./IMAGES/contact_angle_result_1777538382521.jpg)
10146

11-
![](./IMAGES/droplet.png)
147+
After snapping, switch to the **Auto** tab (right panel) and click
148+
**Measure (Auto)**.
149+
150+
The algorithm runs in ~100 ms and overlays:
151+
152+
- **Green line** — detected substrate baseline.
153+
- **Cyan circles** — left and right contact points.
154+
- **Cyan curves** — polynomial fit along each drop edge.
155+
- **Magenta lines** — tangent vectors at the contact points.
156+
- **Angle labels** — left angle θ_L and right angle θ_R in degrees.
157+
- **Top-left overlay** — mean angle and wetting regime
158+
(*hydrophilic* θ < 90° or *hydrophobic* θ > 90°).
159+
160+
The results (θ_L, θ_R, baseline tilt, regime) appear in the panel on the
161+
right.
162+
163+
### 4.6 Manual Measurement
164+
165+
![](./IMAGES/droplet.png)
166+
167+
For difficult cases (very small drops, contaminated substrates) use the
168+
**Manual** tab.
169+
170+
You place **3 points** on the snapped image:
171+
172+
| Point | How to place | Purpose |
173+
|-------|-------------|---------|
174+
| **Baseline 1** | Click on the substrate, left of drop | Defines substrate line |
175+
| **Baseline 2** | Click on the substrate, right of drop | Defines substrate line |
176+
| **Tangent** | Click on the drop edge at the contact point | Direction of drop wall |
177+
178+
**Step-by-step:**
179+
180+
1. Switch to the **Manual** tab.
181+
2. Click **Place Points** to enter placement mode (cursor → crosshair).
182+
3. Click three times in the order above.
183+
Small numbered markers appear on the image.
184+
4. Click **Measure (Manual)**.
185+
The angle is computed from the angle between the baseline vector and the
186+
tangent vector. The result is shown immediately.
187+
188+
> **Tip:** Zoom in (use the +/− buttons or scroll) and pan (click-drag) before
189+
> placing points for higher accuracy.
190+
191+
### 4.7 Focus Monitor
192+
193+
Enable the **Focus Monitor** toggle in the right panel.
194+
Every 500 ms the backend computes the **Laplacian variance** (sharpness) of
195+
the live image and sends it back.
196+
197+
A progress bar shows the value relative to the session maximum.
198+
Maximize it while turning the focus ring (or the z-stage) to get the sharpest
199+
possible image before snapping.
200+
201+
### 4.8 Algorithm Configuration
202+
203+
Expand the **Config** section in the right panel to tune the image-processing parameters. You probably don't need to touch them anyway.
204+
205+
| Parameter | Default | Effect |
206+
|-----------|---------|--------|
207+
| **Canny Low** | 30 | Lower Canny hysteresis threshold — increase for noisy images |
208+
| **Canny High** | 100 | Upper Canny threshold — decrease to detect weaker edges |
209+
| **Blur Kernel Size** | 5 | Gaussian pre-blur (odd numbers only) — reduce for sharp images |
210+
| **Local Fit Fraction** | 0.15 | Fraction of drop width used for the tangent polynomial fit |
211+
| **Polynomial Degree** | 2 | Degree of the tangent polynomial (2 = parabola, higher = more flexible) |
212+
| **Tangent Delta (px)** | 1 | Finite-difference step for tangent evaluation |
213+
| **Angle Min (°)** | 1 | Measurements below this are discarded as noise |
214+
| **Angle Max (°)** | 179 | Measurements above this are discarded as noise |
215+
216+
Click **Reset Config** to return all values to defaults.
217+
Changes are sent to the backend immediately and applied on the next measurement.
218+
219+
### 4.9 Measurement History & Export
220+
221+
Every successful measurement can be added to the session history:
222+
223+
1. After a successful auto or manual measurement click **Add to History**.
224+
2. The history table shows: ID, time, mode, θ_L, θ_R.
225+
3. The panel also shows a running **mean ± std** across all recorded values.
226+
4. Use **Export CSV** or **Export JSON** to download the full table.
227+
5. Click the download icon (↓) in the image card to save the annotated result
228+
image as a PNG.
229+
230+
231+
## 5. Measurement Algorithm Explained
232+
233+
The algorithm is fully automatic and adapts to the wetting regime.
234+
235+
### 5.1 Preprocessing
236+
237+
1. The snapped frame is down-scaled so its longest edge ≤ 800 px (speeds up
238+
processing without losing accuracy at typical droplet sizes).
239+
2. A Gaussian blur (kernel = `blur_ksize`) suppresses pixel noise.
240+
241+
### 5.2 Edge Detection & Contour Extraction
242+
243+
Canny edge detection produces a binary edge map.
244+
The largest contour (by bounding-box width) is assumed to be the drop
245+
silhouette including its mirror reflection in the substrate.
246+
247+
### 5.3 Wetting Regime Detection
248+
249+
The contour is profiled row by row to get width vs. vertical position.
250+
The profile is smoothed and the maximum-width row (equator) is found.
251+
252+
- **Hydrophilic (θ < 90°):** No waist below the equator. The two widest
253+
points are the contact points; a tilted baseline is fitted through them.
254+
- **Hydrophobic (θ > 90°):** A width minimum (waist) exists below the equator
255+
— the drop overhangs the contact line. The waist row defines the baseline.
256+
257+
```
258+
Hydrophilic Hydrophobic
259+
___ _____
260+
/ \ / \
261+
| | ___/ \___ ← equator (widest)
262+
\ / ↑
263+
───┴───┴─── ────┼──────── ← waist → baseline
264+
contact pts contact pts
265+
```
266+
267+
### 5.4 Polynomial Tangent Fit
268+
269+
For each contact point a short section of the drop edge
270+
(length = `fit_frac × drop_width`) is fitted with a polynomial of degree
271+
`poly_degree`. The tangent vector is evaluated by finite differences.
272+
273+
- **Hydrophilic:** polynomial y(x) — fit in the horizontal direction.
274+
- **Hydrophobic:** polynomial x(y) — fit in the vertical direction (avoids
275+
near-vertical tangent issues).
276+
277+
The contact angle θ is the angle between the tangent vector and the baseline.
278+
279+
280+
## 6. Step-by-Step Workflow
281+
282+
```
283+
1. Power on Pi + camera + LED array
284+
2. Open ImSwitch in browser
285+
3. Navigate to Goniometer
286+
4. Set Exposure & Gain → bright, sharp live image
287+
5. Place substrate on base plate
288+
6. Dispense droplet with pipette
289+
7. Check live view — is the full drop visible?
290+
8. ├── Yes → proceed to step 9
291+
8. └── No → adjust pipette height / substrate position
292+
9. (Optional) Set Crop ROI around the drop
293+
10. Focus Monitor ON → maximize sharpness
294+
11. Click "Snap"
295+
12. Inspect snapped image
296+
13. ├── Drop clearly visible → Auto Measure
297+
13. └── Poor contrast / noise → adjust Canny / blur, re-snap
298+
14. Review annotated result
299+
15. ├── Correct baseline & tangents → Add to History
300+
15. └── Wrong detection → Manual Measure or retune Config
301+
16. Repeat steps 6–15 for each measurement
302+
17. Export CSV / JSON when done
303+
```
304+
305+
306+
## 7. Tips for Good Results
307+
308+
309+
![](./IMAGES/IMG_20260430_103804.jpg)
310+
311+
- **Back-lighting should be uniform.** The LED array + diffuser should produce a uniform bright background so the drop appears as a solid dark silhouette. Avoid reflections on the substrate surface.
312+
313+
- **Keep the baseline horizontal.** If the substrate is tilted, the algorithm compensates (reports `baseline_tilt_deg`) but accuracy decreases for large tilts. Use the cube levelling feet.
314+
315+
- **Crop tightly but not too tight.** Leave ~20 px of background above the top of the drop and below the substrate. The mirror reflection (below the substrate) is NOT needed — only the real drop is used.
316+
317+
318+
- **Bring sample to focus.** Use the micrometer stage to place the droplet in the image plane
319+
320+
![](./IMAGES/IMG_20260430_104047.jpg)
321+
322+
- **Repeat and average.** Dispense 5–10 drops and record each one.
323+
The mean ± std shown in the history panel is your reported contact angle.
324+
325+
## 8. Troubleshooting
326+
327+
| Symptom | Likely cause | Fix |
328+
|---------|-------------|-----|
329+
| No live stream | Camera not detected by ImSwitch | Check USB3 cable; verify camera name in device config |
330+
| "Detection failed" overlay | No contour found | Increase contrast (exposure, gain); check Canny thresholds |
331+
| Wildly wrong angles | Wrong contour selected (pipette, edge) | Set crop ROI to isolate only the drop |
332+
| Only one angle returned | Drop touches image border | Widen crop ROI or move drop into view |
333+
| Hydrophobic/hydrophilic wrong | Waist threshold too sensitive | Increase `waist_rise_thresh` in DEFAULT_CONFIG (backend) |
334+
| Baseline very tilted | Substrate not level | Level substrate; or use manual mode with explicit baseline |
335+
| Focus bar stuck at low value | Wrong camera name | Verify detector name in ImSwitch config matches `GoniometerController` detector key |
336+
| Export produces empty file | No measurements added to history | Click "Add to History" after each successful measurement |
337+

0 commit comments

Comments
 (0)