Skip to content

Commit 0082f4f

Browse files
committed
making progress
1 parent 1a72394 commit 0082f4f

10 files changed

Lines changed: 81 additions & 13 deletions

File tree

.DS_Store

8 KB
Binary file not shown.

images/.DS_Store

8 KB
Binary file not shown.

images/auto-scope-cam/center.png

82.8 KB
Loading

images/auto-scope-cam/focus.png

7.25 KB
Loading

images/auto-scope-cam/full.png

83 KB
Loading

images/auto-scope-cam/square.png

76.8 KB
Loading

images/auto-scope-cam/tile.png

119 KB
Loading

images/auto-scope-cam/width.png

5.13 KB
Loading

posts/making-progress

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
.. title: Looking at microscope slides with computer vision
2+
.. slug: auto-scope-progress
3+
.. date: 2018-10-16 00:00:00 UTC+10:00
4+
.. tags: draft, auto-scope, cv2
5+
.. category:
6+
.. link:
7+
.. description:
8+
.. type: text
9+
.. author: Wytamma
10+
11+
We are making progress on the auto-scope project! The mechanical aspects of the microscope are all finished and working*. We’ve written most of the code to control the x, y, and z of the microscope and we’re starting to move onto the whole slide stitching and machine learning aspects of the project.
12+
13+
Check out this (oldish) video of the stage moving around. Look at it go!
14+
video
15+
16+
.. youtube:: 3XsQCkF1SrE
17+
:align: center
18+
19+
.. TEASER_END
20+
21+
Currently, the microscope has stage control (x, y), focus control (z) and a camera. We’re using an 8MP Raspberry Pi V2 camera to capture images of the slide. Two 28byj-48 stepper motors control the stage (one for x and one for y), and another stepper motor works the focus knob.
22+
23+
I’ve recently been working on the interactions between the camera and focus to capture tiles and enabling autofocusing. I thought I should detail some of the aspects of this part of the project while they are fresh in my mind.
24+
25+
Because we are using an `open loop system
26+
<https://en.wikipedia.org/wiki/Motor_control#Open_loop_control>`_ the microscope doesn’t have a way to tell it's current location (only how far it's moved from the starting position). To ensure reproducibility we always initialise the microscope with the focus at maximum height, that way the microscope knows one of its limits, and we can work down from this position.
27+
28+
When taking photos through the microscope with a camera like the RPi V2, the wide lens results in a lot of black surrounding the actual image we want to capture. Ideally, for image stitching, we'd like a square picture with no boarded. We could manually crop this additional border. However, we have Python https://xkcd.com/353/.
29+
30+
.. image:: /images/auto-scope-cam/full.png
31+
:align: center
32+
33+
So, to automatically extract our square region of interest we first need to calculate a few parameters. To find the width of the circle, we mask the image with a back and white threshold and then move in from the sides until we hit something white. This gives us the diameter, easy.
34+
35+
.. image:: /images/auto-scope-cam/width.png
36+
:align: center
37+
38+
To find the center of the circle we use a method from computer vision called `blob centroid detection
39+
<https://www.learnopencv.com/find-center-of-blob-centroid-using-opencv-cpp-python/>`_. in short, we take the average position of all the points in the circle.
40+
41+
.. image:: /images/auto-scope-cam/center.png
42+
:align: center
43+
44+
Using the diameter and centre point we can apply a bit of `trigonometry
45+
<https://en.wikipedia.org/wiki/Special_right_triangle#45%C2%B0%E2%80%9345%C2%B0%E2%80%9390%C2%B0_triangle>`_ to determine the coordinates of a square that is inscribed in the circle.
46+
47+
.. image:: /images/auto-scope-cam/square.png
48+
:align: center
49+
50+
We can then feed this square information to the camera, that will zoom in and return nicely cropped images. The above description is implemented in the `calculate_zoom` method of the autoscope `Camera` class.
51+
52+
.. image:: /images/auto-scope-cam/tile.png
53+
:align: center
54+
55+
Now that we have a tile we need a way to tell if it’s in focus. There are many 'blurriness metrics' for example the `variation of the Laplacian
56+
<https://www.pyimagesearch.com/2015/09/07/blur-detection-with-opencv/>`_. This method returns high values if an image contains high variance in rapid intensity change regions (associated with focused images).
57+
58+
.. image:: /images/auto-scope-cam/focus.png
59+
:align: center
60+
61+
To find the optimal focal height we need to map the focal plane and try to maximise the variation of the Laplacian (see bar plot). The initial scan of the focal plane is done in large steps down the focal plane until we find a peak. Once the course mapping is complete we then switch to fine steps to map the peak in detail. Now that we have a fine grain map of the focal plane peak we can simply move to focus with the highest variation. This course, fine, find algorithm is implemented in the `auto_focus` method of the autoscope `Autoscope` class.
62+
63+
You can check out all this camera code and more on `GitHub
64+
<https://github.com/python-friends/auto-scope>`_ or `pip install opencv-contrib-python` and try it yourself.
65+
66+
* My 3D printed gears suffer from a slight backlash that results in small errors when the stepper motors change direction. I think we should move to a timing belt system to prevent this in the future, however, slide scanning is still possible even with these errors.
67+
68+

posts/microscopes-and-stepper-motors.rst

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -156,20 +156,20 @@ This alows us to setup and set the intial output of each pin on lines 9 and 10.
156156
]
157157
158158
We define the complete sequence of signals required for one cycle with half stepping on line 12.
159-
The 1's and 0's tell us which pins for be off(0) or on(1).
159+
The 1's and 0's tell us which pins should be off(0) or on(1).
160160
This `halfstep_seq` variable is a list of list.
161-
To access the first element of the first nested list we can you the notation halfstep_seq[0][0].
162-
This would return 1. We'll use this nested list indexing to turn our motor.
161+
To access the first element of the first nested list, we can use the notation halfstep_seq[0][0], which returns 1.
162+
We'll use this nested list indexing to turn our motor.
163163

164164
.. code-block:: python
165165
:number-lines:
166166
167167
for i in range(512):
168168
...
169169
170-
The main logic? of our script can be found in the nested for loops on lines X to Y.
171-
The `range(n)` function returns a list of intigers from 0 to n. So range(3) returns [0,1,2].
172-
in our first for loop we use range to create a list of 512 intergers
170+
The main logic of our script can be found in the nested for loops on lines X to Y.
171+
The `range(n)` function returns a list of integers from 0 to n. So range(3) returns [0,1,2].
172+
In our first for loop, we use range to create a list of 512 integers
173173
(the of steps number required for one required for one revolution).
174174

175175
.. code-block:: python
@@ -179,7 +179,7 @@ in our first for loop we use range to create a list of 512 intergers
179179
...
180180
181181
We then cycle through each halfstep in the halfstep_seq,
182-
each halfstep being a list of lengh 4 with the state of each pin for that step.
182+
each halfstep being a list of length 4 with the state of each pin for that step.
183183

184184
.. code-block:: python
185185
:number-lines:
@@ -188,17 +188,17 @@ each halfstep being a list of lengh 4 with the state of each pin for that step.
188188
GPIO.output(control_pins[i], val)
189189
time.sleep(0.001)
190190
191-
In our final for loop we set each of the control pins to the aproprperate state.
192-
We use the `enumerate` function to return the the index and value of each element in the halfstep list.
193-
we use the index (i) to access the correct pin, and set its corisponing value (val).
191+
In our final for loop, we set each of the control pins to the appropriate state.
192+
We use the `enumerate` function to return the index and value of each element in the halfstep list.
193+
We use the index (i) to access the correct pin, and set its corresponding value (val).
194194

195195
.. code-block:: python
196196
:number-lines:
197197
198198
GPIO.cleanup()
199199
200-
on the final line we clean up the GIPO pins.
200+
On the final line, we clean up the GIPO pins.
201201

202202
And that's it, running these X lines of code will turn the attached stepper motor 360˚.
203-
You can try playing with the 512 val to change the aumount the motor will turn.
204-
If you're feeling cofidient you could even try gernlaise this code into a function (or wait for our next post).
203+
You can try playing with the 512 value to change the amount the motor will turn.
204+
If you're feeling confident, you could even try to generalise this code into a function (or wait for our next post).

0 commit comments

Comments
 (0)