Openflexure stage program in microscope

Hello,

I’ve successfully built confocal microscope. Here is the picture of 1 euro star from this microscope:

saveData0

Now I’m trying to optimize my program to make it work with smaller steps (link to old documentation):

def micronToStep(v):
    #approximately 62nm/step
    return round((v * 1000)/62)

def mz(self, position, absolute=True):
    """Move the stage to a given position.

    WARNING! If you specify zeros, the stage might move a long way, as
    the default is absolute moves.  Position should be a dictionary
    with keys called "x", "y", and "z", although we will (for now) also
    accept an iterable of three numbers.
    """
    try:
        pos = {k: int(position[k]) for k in ["x", "y", "z", "z2"]}
    except:
        pos = {k: int(position[i]) for i, k in enumerate(["x", "y", "z", "z2"][:len(position)])}
    pos['absolute'] = absolute
    response = self.post_json("/extensions/com.openflexure.stage-mapping/actions/stage/move-measure/MeasureZAPI", pos)
    return response

#microscope = ofm_client.find_first_microscope()
microscope = ofm_client.MicroscopeClient("192.168.0.100")
microscope.measureZ = mz

# homing routine
pos = microscope.position
print("Current position: " + json.dumps(pos))
print("Homing")
pos['x'] = 0
pos['y'] = 0
pos['z'] = 0
#microscope.move(pos)

#zStep = 20
#maxValue = -1
#maxPosition = -1
#for z in range(micronToStep(-2000), micronToStep(2000), micronToStep(zStep)):
#    pos = microscope.position
#    pos['x'] = 0
#    pos['y'] = 0
#    pos['z'] = z
#    values = microscope.moveMeasure(microscope, pos, 1)
#    #values = [a for a in values if a >= 0 ]
#    values = np.asarray(values)
#    value = np.median(values)
#    if (value > maxValue):
#        maxValue = value
#        maxPosition = z
#    print(value, maxValue, maxPosition)

#print("Final values:", maxValue, maxPosition)


# units in microns
xyStep = 5
xRange = 800
yRange = 800
zRange = 400
#zBase = maxPosition - micronToStep(zRange/2)
#zBase = -((30000 - 15000)/2)
zBase = 0

saveData = {
    "xyStep": xyStep,
    "xRange": xRange,
    "yRange": yRange,
    "zRange": zRange,
    "zBase": zBase,
    "data": [[0] * ceil(xRange/xyStep) for i in range(ceil(yRange/xyStep))],
}

measurements = 1
pos = microscope.position
print("Current position: " + json.dumps(pos))

starting_pos = microscope.position
#figure(figsize=(20,5))

ix = 0
iy = 0

totalElapsed = 0
stepsTaken = 1
maxSteps = (xRange/xyStep) * (yRange/xyStep)
up = True

ix = 0
for x in range(-micronToStep(xRange/2), micronToStep(xRange/2) - micronToStep(xyStep), micronToStep(xyStep)):
    iy = 0
    for y in range(-micronToStep(yRange/2), micronToStep(yRange/2) - micronToStep(xyStep), micronToStep(xyStep)):
        start = timer()
        values = []
        if (up):
            pos = {}
            pos['x'] = x
            pos['y'] = y
            pos['z'] = zBase - micronToStep(zRange/2)
            pos['z2'] = zBase + micronToStep(zRange/2)
            print("Scanning up to position:", pos)

            values = microscope.measureZ(microscope, pos)
            values = np.asarray(values)
            thisTime =  timer() - start
            totalElapsed += thisTime
            up = False
        else:
            pos = {}
            pos['x'] = x
            pos['y'] = y
            pos['z'] = zBase + micronToStep(zRange/2)
            pos['z2'] = zBase - micronToStep(zRange/2)
            print("Scanning to position:", pos)

            values = microscope.measureZ(microscope, pos)
            values = np.asarray(values)
            values = np.flip(values)
            thisTime =  timer() - start
            totalElapsed += thisTime
            up = True


        #xs = np.linspace(zBase - micronToStep(zRange/2), zBase + micronToStep(zRange/2), values.shape[0])
        #coef = np.polyfit(xs, values, 1)
        #poly1d_fn = np.poly1d(coef)
        #plt.plot(xs, values, '-ok')
        #plt.axhline(y=np.median(values), color='r', linestyle='-')
        #plt.xlim(0, values.shape[0])
        #annot_max(xs,values)
        #plt.show()



        print(pos)
        saveData['data'][ix][iy] = values

        iy += 1
        timeLeft = (maxSteps - stepsTaken) * (totalElapsed / stepsTaken) / 60 / 60
        print(">>>>>>>>>>>> Estimated time left: [" + str(timeLeft) + "hr], Per Scan: [" + str(totalElapsed / stepsTaken) + "s][" + str(thisTime) + "], Total Elapsed: [" + str(totalElapsed) + "s], Scans taken: [" + str(stepsTaken) + "] out of: [" + str(maxSteps) + "]")
        stepsTaken += 1
        pickle.dump(saveData, open('saveData.bin', 'wb'))

    ix += 1



pickle.dump(saveData, open('saveData.bin', 'wb'))
#pickle.dump(dataMaxPositions, open('dataMaxPositions.bin', 'wb'))

print("Homing")
pos['x'] = 0
pos['y'] = 0
pos['z'] = 0

microscope.move(pos)

The code above works with xyStep > 4 (micron). For smaller steps i. e. xyStep = 0.5 the condition for x in range(-micronToStep(xRange/2), micronToStep(xRange/2) - micronToStep(xyStep), micronToStep(xyStep)) is not fulfilled.

The question is what and how should I change in the code above to make it work with smaller steps e. x. xyStep = 0.6? I suppose it is connected with "data": [[0] * ceil(xRange/xyStep) for i in range(ceil(yRange/xyStep))]? I also read that the stage move in xy plane with accuracy 70 nanometers and in z height with accuracy 50 nanometers, so with that information should I modify my micronToStep() function?

Thank you very much for your help.

2 Likes

Do you mean that the range ends up as an empty range? If that is the case I would try checking the values passed for the three parameters in the range statement. It does not look as though a number <1 makes a difference, unless xystep is interpreted as integer.

If you want to get real-world distances correct you would do best to calibrate you microscope in steps against a known distance target. In the cartesian microscope the xy motion is geometrically different from the z motion, so you would need different conversion factors.

The problem is the saveData["data"] is a 400 x 400 list-of-lists.

For xyStep = 1, xRange = 400, yRange = 400 in case of loops:

for x in range(-micronToStep(xRange/2), micronToStep(xRange/2) - micronToStep(xyStep), micronToStep(xyStep)):
    for y in range(-micronToStep(yRange/2), micronToStep(yRange/2) - micronToStep(xyStep), micronToStep(xyStep)):

after performing the calculations, the actual range conditions on these loops are:

range(-3225, 3209, 16)

They produces slightly more than 400 iterations which is in this case 403. I thought I should modify the command:

"data": [[0] * ceil(xRange/xyStep) for i in range(ceil(yRange/xyStep))]

Could you show me exactly code solutions to better understand your idea?

I’m not really sure what it is that’s not working - from your description I thought maybe you were ending up with an empty range - but I guess not, from this post. If you were to copy your code and then remove everything inside the for loops, and just print out the position it moved to each time, what do you see?

For values xyStep = 10 , xRange = 400 the condition: for x in range(-micronToStep(xRange/2), micronToStep(xRange/2) - micronToStep(xyStep), micronToStep(xyStep)) is equal to: range(-3226, 3065, 161) = 40 which is okay with xRange/xyStep = 400/10 = 40.
But for values xyStep = 4 , xRange = 400 the condition: for x in range(-micronToStep(xRange/2), micronToStep(xRange/2) - micronToStep(xyStep), micronToStep(xyStep)) is equal to: range(-3226, 3161, 65) = 99 which lower than xRange/xyStep = 400/4 = 100. I think it causes to crash for loop.

I figured out that the problem is with micronToStep() function.

Well done! Yes perhaps defining the output array and the ranges from the same data could help the, or even defining the list of lists on the fly with append.

Could you write down, how the solution should look like in my case?

The for x in range() loop commands in my opinion works good but the main problem is with calculations in micronToStep function. Is it possible to generate correct values with micronToStep function?

Could you also explain me, why there are some extra iterations in case of loop for xyStep = 1 while for xyStep = 10 there aren’t?

I noticed that modifying to: for x in range(-micronToStep(1000*xRange/2), micronToStep(1000*xRange/2) - micronToStep(1000*xyStep), micronToStep(1000*xyStep)) gives as it is known incorrect range values but also it gives correct range conditions.

I’ve not tested this, but in case it helps, you could change the "data" line here to start with an empty list:

Then, you would need to add a new list each iteration, i.e.:

Then, each time you want to save a new data point, you would do:

So, you still end up with a list-of-lists of np.array objects, but you don’t have to specify in advance what its dimensions are. “single-sourcing” the array size like this should eliminate your error when the dimensions are slightly off.

You might want to print or otherwise save the values of x,y,z,z2 at each point though, just as a sanity check to let you make sure you don’t have strange things happening due to rounding errors.