git apply ../printimage/scanimage-integration/patches/scanimage-5.3.1.patch --whitespace=fix
(here I've assumed that scanimage and printimage are subdirectories of a common parent). If you don't see your favourite ScanImage version here, look at the patch: it's dead simple!
Size Calibration is done through parameters in ScanImage's Machine_Data_File.m
, not through PrintImage. This is because we figure you'll want your microscope calibrated properly whether or not you're printing anything today.
I will refer to the axis over which the resonant scanner scans as X. The galvo scans over Y. The "fast Z" stage, perpendicular to the focal plane, is (surprise!) Z.
Unsurprisingly, you will want to set zoom to 1, using either ScanImage's interface or hSI.hRoiManager.scanZoomFactor = 1
For X and Y, three parameters affect two degrees of freedom. I think there's a correct way to do this, but I'll describe the kludge.
ScanImage mainains its idea of the lens's field of view in a variable called hSI.hRoiManager.imagingFovUm
. This is a matrix giving the corner positions of the FOV. I've assumed that this is square and centered around 0, so if it's not, my code will be buggy! But here's one way to read ScanImage's idea of the FOV in X and Y:
fov = hSI.hRoiManager.imagingFovUm
[fov(3,1) - fov(1,1) fov(3,2) - fov(1,2)]
Machine_Data_File.m
objectiveResolution
controls X and Y FOV togetherrScanVoltsPerOpticalDegree
controls the magnitude of the voltage signal to the resonant scanner (X)galvoVoltsPerOpticalDegreeY
controls the magnitude of the voltage signal to the galvo (Y)You'll need a way to measure the actual FOV. This can be done by imaging something of known size, or by printing something and then measuring it on a calibrated device. For this purpose we have calibration rulers. FIXME Add links to our calibration rulers and the paper's instructions.
This accomplished, compute your error in X and Y, and just multiply some combination of those numbers to scale the image. It is likely that the numbers have nominal values (e.g. for our hardware, in theory galvoVoltsPerOpticalDegreeY = 1
), so maybe try not to stray too far from those values, or keep one of them at the nominal value or something. We've found that one iteration of this procedure can get us to within 1% or so.
If the size of objects appears different on the left vs. right sides of the display (e.g. if you print or image a grid, and scrolling left-right changes the apparent grid size), you may need to adjust the scan phase. It's hSI.hScan2D.linePhase
, available in the "Scan Phase" box in ScanImage's CONFIGURATION window. I haven't had much luck with "Auto Adjust".
Note that since this is not available in Machine_Data_File.m
, I set it by hand in printimage.m
. This will be moved to a printimage config file soon... For now, change the value until it looks right, and then modify hSI.hScan2D.linePhase
in printimage.m
.
ScanImage allows you to specify certain known models of FastZ stage. From Machine_Data_File.m
:
actuators(1).controllerType = 'thorlabs.pfm450'; % If supplied, one of {'pi.e665', 'pi.e816', 'npoint.lc40x', 'analog'}.
We have a ThorLabs pfm450, which ScanImage should know how to talk to. However, ours was not moving quite as far as ScanImage expected. In order to correct that, I modified some variables.
Also, note that it is essential to run at least our FastZ controller in closed-loop mode!
The procedure is generally the same as for X and Y: either print something of ostensible size and measure it, or image something of known size. For this we provide a vertical pyramid ruler. FIXME Link to it!
If you need to adjust the scaling, you must not tell ScanImage what kind of FastZ controller you have, so it will actually use your adjusted values rather than its own. Just tell it you're using an analog controller:
actuators(1).controllerType = 'analog'; % If supplied, one of {'thorlabs.pfm450', 'pi.e665', 'pi.e816', 'npoint.lc40x', 'analog'}.
Now, adjust the VoltsPerMicron values according to your measurements:
actuators(1).commandVoltsPerMicron = (10/450)/1.1; % Conversion factor for desired command position in um to output voltage
The maximum voltage is 10 (specified in the manual, transcribed to actuators(1).maxCommandVolts
; likewise the 450 and other constants (see below).
The "/1.1" is the important part--it compensates for the 10% error that we measured.
actuators(1).commandVoltsOffset = []; % Offset in volts for desired command position in um to output voltage
actuators(1).sensorVoltsPerMicron = (10/450)/1.1; % Conversion factor from sensor signal voltage to actuator position in um. Leave empty for automatic calibration
Same thing here--the sensor was off by the same amount. That led us to double-check our measurements (looks like two independent (?) systems were in agreement), but the printed parts really were the wrong size, measured on an SEM and via our slow Z stage (Sutter MOM, which is notoriously inaccurate but which in this case agreed with the SEM, as well as with itself at different locations in its range). So that can happen, I guess. Photoresist shrinkage? Anyway, our parts are the right size now.
actuators(1).sensorVoltsOffset = -0.12; % Sensor signal voltage offset. Leave empty for automatic calibration
actuators(1).maxCommandVolts = 10; % Maximum allowable voltage command
actuators(1).maxCommandPosn = 450; % Maximum allowable position command in microns
actuators(1).minCommandVolts = 0; % Minimum allowable voltage command
actuators(1).minCommandPosn = 0; % Minimum allowable position command in microns
Desiderata:
Now imagine that your printable medium is a piece of something (e.g. an electrode in need of a cuff) loosely taped onto a slide. It may not be parallel to, well, anything. First step is to tilt the medium into alignment with the optical plane. Second step is to align the movement coordinate system.
Start a "focus", and add a bullseye. Focus on the substrate, and then bring the lens up just far enough to see a hint of fluorescence beginning to show. Use the hexapod's angle controls to put that hint of fluorescence in the middle of the FOV (using the bullseye). It will probably be necessary to tilt the object and then re-focus on the hint-of-fluorescence, tilt some more, re-focus again, etc.
Again focus on the substrate and then bring the lens up just far enough to see a hint of fluorescence, as above. Run a "brightness test" and look at the results (FIXME falloff_slide.m
right now, but that's not ready for primetime). The goal will be to set the leveling coordinate system so that the (possibly tilted, but optically aligned) substrate moves along the optical XY plane. This is controlled by
STL.motors.hex.leveling = [ X Y Z U V W ];
Set it in printimage_config.m
, and adjust as needed. For example, on our printer:
My favourite workflow: edit the values in printimage_config.m
, copy and paste the definition into the MATLAB commandline, and run hexapod_set_leveling()
, which will store the values in the hexapod. Run another brightness test and repeat until the brightness traces don't change too much over XY movement (I can usually get them down to about 10% variation, depending on how close I am to the substrate).
The sixth value of STL.motors.hex.leveling
controls the alignment of stitched parts. Print something stitched and adjust W in order to align the hexapod's XY motion with the resonant-galvo axis.
One more parameter should be saved in printimage_config.m
:
STL.motors.hex.slide_level = [ 0 0 0 0.255 -0.09 0 ];
These numbers are the tilt offsets from the tilt calibration process, above, but they must be read after the leveling coordinate system is correctly defined and tuned.
This began as an effort to model the falloff due to optical vignetting. The theoretical model of cos(theta)^4 seemed to fit pretty well, but couldn't be justified for a near-field laser (i.e. working less than a few kilometres from the laser source). And then I reasoned that since all models are wrong, it might be better to do something free-form. Hence the adaptive power compensation.
Zero: Set your desired print zoom level...? Still working on this.
First, focus within the IP-Dip (yielding a fluorescent image that appears approximately Gaussian). Use Calibrate / Save baseline image
to save the default brightness at the camera.
Second, find the substrate: follow the instructions regarding "Finding 0" in "Printing" (below in this document), and use the Calibrate / Calibrate vignetting compensation
menu item to begin. This will:
calibrate_vignetting_slide.m
.height - 2
um) and measure the brightness at lots of pointsTo start from scratch, use the Calibrate / Clear vignetting compensation
menu item. You may then start again. Feel free to add your own calibration procedure! They're applied in printimage_modify_beam.m
.
The working distance of the lens is important, as you do not want the lens to touch anything solid! Our lens has a 380-um working distance, which has the following implications:
PrintImage's "Power Test" button will print a bunch of rectangular prisms at various power settings, which can be seen in ScanImage's "Power Box Settings" window, available through the "POWER CONTROLS" window.
Print something (e.g. a power test pattern). Use the rotation slider to rotate the stage, and see if it rotates about the centre of the screen (the "bullseye" can be used to facilitate this). Move the microscope stage toward what looks like the centre of rotation. Repeat until the image rotates about the centre of the field. This is the alignment point. You may add "STL.motors.mom.understage_centre = [12655 10857 16890];" to your printimage_config, reading those numbers off the microscope stage's location (microns).
Find the substrate, and then pull the lens just a little bit away from it so you can just see some light in the image centre. Scroll up and down on Y while adjusting the hexapod's X rotation control such that scrolling on Y doesn't affect the spot's brightness (closeness to substrate). Vice versa for X-Y.
For Z, print something that involves stitching, such as a linearity test. Scrolling back and forth with the microscope's stage should produce motion parallel with the line of dots that the stitching procedure produced.
The final numbers can be added to printimage_config; e.g., "STL.motors.hex.leveling = [0 0 0 0.9 -0.1 -1.1];"
X and Y resolutions are functions of various things (position on the X axis, zoom level, hardware...), and you have limited control over them. Z is easiest.
Lines / Frame
parameter in the CONFIGURATION window, also available at the command line through hSI.hRoiManager.linesPerFrame
. It is always a power of two, so the choice of actual resolutions is somewhat limited. Resolution is the FOV / number of scan lines. Useful resolutions are determined by your photoresist, optics, etc.STL.print.zstep
, which is in microns, and it will instruct ScanImage correctly assuming ScanImage moves your FastZ stage in the same direction as mine with the same voltage change. You may need to change the sign and do a little minor debugging. (Internally, PrintImage controls ScanImage's internal variable Steps/Slice
in the FAST Z CONTROLS window (hSI.hStackManager.stackZStepSize
).) One version of the PrintImage interface gives you direct control over that, but if you don't see it, then you have to modify the variable in printimage.m
.Because STL files are dimensionless, you have to choose the size of the object.
If the object to be printed exceeds any of the dimensions allowed by your hardware, PrintImage will break it into parts and print them in sequence using your XYZ stage.