438 lines
18 KiB
Plaintext
Executable File
438 lines
18 KiB
Plaintext
Executable File
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
///////// /////////
|
|
///////// MULTISCALE TOMOGRAPHY PLUGIN /////////
|
|
///////// /////////
|
|
///////// Created by Hector Dejea, hector.dejea@psi.ch /////////
|
|
///////// May, 2017 /////////
|
|
///////// /////////
|
|
///////// Reference paper: /////////
|
|
///////// Dejea, H. et al. Scientific Reports 9, 6996 (2019) /////////
|
|
///////// /////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
/* Multiscale tomography
|
|
Often, low resolution (LowRes) scans are acquired in order to have a
|
|
global overview of the sample (usually fitting the field of view) and
|
|
identify regions of interest (ROI). These ROIs are then scanned with
|
|
higher resolution (HighRes) in order to observer finer structures
|
|
(usually local tomography). To move the motors into the correct
|
|
position for the HighRes scan requires tedious, not straightforward
|
|
and error-prone calculations that need many parameters to be taken
|
|
into account.
|
|
|
|
Multiscale_Coords.ijm is an ImageJ plugin to find the final/relative
|
|
coordinates for moving the XX, ZZ and Y motors in a visual manner.
|
|
The ROI size of the HighRes scan is shown according to the user-
|
|
supplied parameters of LowRes and HighRes scans. The user can move
|
|
the ROI interactively to the desired position, and the movements in
|
|
um are given as the result. Start and end position values for stitched
|
|
scans are also available.
|
|
|
|
Last modified: 10/10/2019*/
|
|
|
|
//Check if there is a file containing the multiscale settings
|
|
settings_path = getDirectory("home") + "/.multiscale_settings.txt";
|
|
|
|
if (File.exists(settings_path)){
|
|
|
|
//Set default parameters specified in the file
|
|
parameters_file = File.openAsString(settings_path);
|
|
lines=split(parameters_file,"\n");
|
|
|
|
LowResimagePixelSize = lines[3];
|
|
ZZcenterLowRes = lines[5];
|
|
XXcenterLowRes = lines[7];
|
|
YposLowRes = lines[9];
|
|
cameraFOVX = lines[11];
|
|
cameraFOVY = lines[13];
|
|
HighResimagePixelSize = lines[15];
|
|
CameraMoveX = lines[17];
|
|
CameraMoveY = lines[19];
|
|
stageRotation = parseFloat(lines[21]); // TO DO: angular correction //
|
|
|
|
overlapXZ = lines[23];
|
|
overlapY = lines[25];
|
|
|
|
} else {
|
|
|
|
//No settings file has been found. Set default values
|
|
LowResimagePixelSize = 6.5;
|
|
ZZcenterLowRes = 0;
|
|
XXcenterLowRes = 0;
|
|
YposLowRes = 0;
|
|
cameraFOVX = 2560;
|
|
cameraFOVY = 2160;
|
|
HighResimagePixelSize = 0.65;
|
|
CameraMoveX = 0;
|
|
CameraMoveY = 0;
|
|
stageRotation = 0; // TO DO: angular correction //
|
|
|
|
overlapXZ = cameraFOVX*0.3;
|
|
overlapY = 50;
|
|
}
|
|
|
|
ScansInX = 1;
|
|
ScansInZ = 1;
|
|
//SlicesInY is defined later, depending on user inputs
|
|
|
|
//Retrieve dimensions of the loaded stack
|
|
getDimensions(mywidth, myheight, mychannels, myslices, myframes);
|
|
|
|
//Flag to restart the plugin
|
|
repeatFlag = 1;
|
|
while(repeatFlag == 1) {
|
|
|
|
//Ask the user for LowRes and HighRes acquisition parameters
|
|
Dialog.create("Magnification and Camera Settings");
|
|
Dialog.addNumber("LowRes imagePixelSize (um):", LowResimagePixelSize);
|
|
Dialog.addNumber("LowRes XX center (um):", XXcenterLowRes);
|
|
Dialog.addNumber("LowRes ZZ center (um):", ZZcenterLowRes);
|
|
Dialog.addNumber("LowRes Y position (um):", YposLowRes);
|
|
|
|
Dialog.addNumber("HighRes FOV X (px):", cameraFOVX);
|
|
Dialog.addNumber("HighRes FOV Y (px):", cameraFOVY);
|
|
Dialog.addNumber("HighRes imagePixelSize (um):", HighResimagePixelSize);
|
|
|
|
Dialog.addNumber("Camera movement X (um):", CameraMoveX);
|
|
Dialog.addNumber("Camera movement Y (um):", CameraMoveY);
|
|
// TO DO: angular correction // Dialog.addNumber("Stage Rotation (degrees):", stageRotation);
|
|
|
|
Dialog.show();
|
|
LowResimagePixelSize = Dialog.getNumber();
|
|
XXcenterLowRes = Dialog.getNumber();
|
|
ZZcenterLowRes = Dialog.getNumber();
|
|
YposLowRes = Dialog.getNumber();
|
|
cameraFOVX = Dialog.getNumber();
|
|
cameraFOVY = Dialog.getNumber();
|
|
HighResimagePixelSize = Dialog.getNumber();
|
|
CameraMoveX = Dialog.getNumber();
|
|
CameraMoveY = Dialog.getNumber();
|
|
// TO DO: angular correction // stageRotation = Dialog.getNumber();
|
|
|
|
// Transform stageRotation from degrees to rads
|
|
stageRotation = stageRotation*2*PI/360;
|
|
|
|
// Real magnification
|
|
real_magnification = LowResimagePixelSize/HighResimagePixelSize;
|
|
|
|
// Diameter of circle in pixels
|
|
target_circle_pix= cameraFOVX / real_magnification;
|
|
|
|
//Flag to rechoose stitched scan grid size
|
|
newGridFlag = 1;
|
|
while(newGridFlag == 1){
|
|
|
|
//Ask for the scanning grid parameters
|
|
//Use ScansInX ==1 and ScansInZ ==1 for single volume
|
|
|
|
SlicesInY = cameraFOVY*HighResimagePixelSize/LowResimagePixelSize;
|
|
|
|
Dialog.create("Stitched Scan parameters");
|
|
Dialog.addNumber("Overlap in X and Z (px):", overlapXZ);
|
|
Dialog.addNumber("Overlap in Y (px):", overlapY);
|
|
Dialog.addNumber("Scans in X:",ScansInX);
|
|
Dialog.addNumber("Scans in Z:",ScansInZ);
|
|
Dialog.addNumber("Number of Slices in Y (in LR)",SlicesInY);
|
|
Dialog.addChoice("Selected Y slice as:", newArray("first", "middle (only for 1 block in Y)"));
|
|
Dialog.show();
|
|
|
|
overlapXZ = Dialog.getNumber();
|
|
overlapY = Dialog.getNumber();
|
|
ScansInX = Dialog.getNumber();
|
|
ScansInZ = Dialog.getNumber();
|
|
SlicesInY = Dialog.getNumber();
|
|
sliceYref = Dialog.getChoice();
|
|
|
|
//Size of the full stitched scan
|
|
target_rect_pixZZ = target_circle_pix*ScansInZ - (overlapXZ/real_magnification)*(ScansInZ-1);
|
|
target_rect_pixXX = target_circle_pix*ScansInX - (overlapXZ/real_magnification)*(ScansInX-1);
|
|
|
|
//Create ROI in the image
|
|
if (ScansInX==1 && ScansInZ==1) {
|
|
makeOval(mywidth/2-target_circle_pix/2, myheight/2-target_circle_pix/2, target_circle_pix, target_circle_pix);
|
|
} else {
|
|
makeRectangle(mywidth/2-target_rect_pixZZ/2, myheight/2-target_rect_pixXX/2, target_rect_pixZZ, target_rect_pixXX);
|
|
}
|
|
|
|
waitForUser("Need for action", "Shift the ROI to the position you want to scan, and click OK when you are done!\n" +
|
|
" \n\n" +
|
|
"If not satisfied with stitched scan parameters:\n-Press SHIFT and click OK simultaneously\n" +
|
|
"-Choose a new set of parameters");
|
|
|
|
//Check whether to rechoose stitched scan parameters
|
|
if(isKeyDown("shift")){ newGridFlag = 1; } else { newGridFlag = 0; }
|
|
|
|
}
|
|
|
|
CurrentSlice = getSliceNumber();
|
|
getSelectionBounds(x, y, width, height);
|
|
getDimensions(imagewidth, imageheight, stackchannels, stackslices, stackframes);
|
|
im_center_x = imagewidth/2;
|
|
im_center_y = imageheight/2;
|
|
|
|
// Show circles for stitched scans
|
|
for (i=0; i<ScansInZ; i++){
|
|
for (j=0; j<ScansInX; j++){
|
|
ZZp = x+(i*target_circle_pix-i*(overlapXZ/real_magnification));
|
|
XXp = y+(j*target_circle_pix-j*(overlapXZ/real_magnification));
|
|
Overlay.drawEllipse(ZZp, XXp, target_circle_pix, target_circle_pix);
|
|
}
|
|
}
|
|
Overlay.show;
|
|
|
|
//Compute the actual motor movement values
|
|
first_roi_center_x = x + target_circle_pix/2;
|
|
first_roi_center_y = y + target_circle_pix/2;
|
|
last_roi_center_x = x + target_rect_pixZZ - target_circle_pix/2;
|
|
last_roi_center_y = y + target_rect_pixXX - target_circle_pix/2;
|
|
|
|
first_roi_delta_y_pix = im_center_y - first_roi_center_y;
|
|
first_roi_delta_x_pix = im_center_x - first_roi_center_x;
|
|
last_roi_delta_y_pix = im_center_y - last_roi_center_y;
|
|
last_roi_delta_x_pix = im_center_x - last_roi_center_x;
|
|
|
|
if(sliceYref == "first"){
|
|
top_delta_Y_pix = stackslices/2 - CurrentSlice;
|
|
bottom_delta_Y_pix = stackslices/2 - CurrentSlice - SlicesInY;
|
|
} else {
|
|
if(SlicesInY != cameraFOVY*HighResimagePixelSize/LowResimagePixelSize){
|
|
//Check that you input the correct amount for 1 volume height
|
|
SlicesInY = cameraFOVY*HighResimagePixelSize/LowResimagePixelSize;
|
|
}
|
|
top_delta_Y_pix = stackslices/2 - CurrentSlice + (SlicesInY/2);
|
|
bottom_delta_Y_pix = stackslices/2 - CurrentSlice - (SlicesInY/2);
|
|
}
|
|
|
|
top_move_Y = -top_delta_Y_pix * LowResimagePixelSize + CameraMoveY + (cameraFOVY*HighResimagePixelSize)/2;
|
|
// Correct for a number of slices smaller than a single high resolution volume
|
|
if(SlicesInY > cameraFOVY/real_magnification){
|
|
bottom_move_Y = -bottom_delta_Y_pix * LowResimagePixelSize + CameraMoveY - (cameraFOVY*HighResimagePixelSize)/2;
|
|
} else {
|
|
bottom_move_Y = top_move_Y;
|
|
}
|
|
|
|
top_final_Y = YposLowRes + top_move_Y;
|
|
bottom_final_Y = YposLowRes + bottom_move_Y;
|
|
|
|
first_roi_move_XX = -first_roi_delta_y_pix * LowResimagePixelSize + CameraMoveX;
|
|
first_roi_move_ZZ = first_roi_delta_x_pix * LowResimagePixelSize;
|
|
last_roi_move_XX = -last_roi_delta_y_pix * LowResimagePixelSize + CameraMoveX;
|
|
last_roi_move_ZZ = last_roi_delta_x_pix * LowResimagePixelSize;
|
|
|
|
//Angular correction for when the acquisition is performed at a specific stage rotation
|
|
//CHECK SIGNS!!!!!! In theory:
|
|
//move_XX = move_ZZ_0*sin(stageRotation) + move_XX_0*cos(stageRotation);
|
|
//move_ZZ = move_ZZ_0*cos(stageRotation) - move_XX_0*sin(stageRotation);
|
|
|
|
//first_roi_move_XX = first_roi_move_ZZ_0*sin(stageRotation) + first_roi_move_XX_0*cos(stageRotation);
|
|
//first_roi_move_ZZ = first_roi_move_ZZ_0*cos(stageRotation) - first_roi_move_XX_0*sin(stageRotation);
|
|
//last_roi_move_XX = last_roi_move_ZZ_0*sin(stageRotation) + last_roi_move_XX_0*cos(stageRotation);
|
|
//last_roi_move_ZZ = last_roi_move_ZZ_0*cos(stageRotation) - last_roi_move_XX_0*sin(stageRotation);
|
|
|
|
first_roi_final_XX = XXcenterLowRes + first_roi_move_XX;
|
|
first_roi_final_ZZ = ZZcenterLowRes + first_roi_move_ZZ;
|
|
last_roi_final_XX = XXcenterLowRes + last_roi_move_XX;
|
|
last_roi_final_ZZ = ZZcenterLowRes + last_roi_move_ZZ;
|
|
|
|
results = "Calculation based on the following crucial parameters: \n" +
|
|
" - pixel size of LowRes image: " + LowResimagePixelSize + "\n" +
|
|
" - pixel size of HighRes scan: " + HighResimagePixelSize + "\n" +
|
|
" \n\n" +
|
|
"FIRST ROI:\n" +
|
|
"- Move XX " + first_roi_move_XX + " um to position " + first_roi_final_XX + "\n" +
|
|
"- Move ZZ " + first_roi_move_ZZ + " um to position " + first_roi_final_ZZ + "\n" +
|
|
" \n\n" +
|
|
"LAST ROI:\n" +
|
|
"- Move XX " + last_roi_move_XX + " um to position " + last_roi_final_XX + "\n" +
|
|
"- Move ZZ " + last_roi_move_ZZ + " um to position " + last_roi_final_ZZ + "\n" +
|
|
" \n\n" +
|
|
"TOP ROIs:\n" +
|
|
"- Move Y " + top_move_Y + " um to position " + top_final_Y + "\n" +
|
|
" \n\n" +
|
|
"BOTTOM ROIs:\n" +
|
|
"- Move Y " + bottom_move_Y + " um to position " + bottom_final_Y + "\n" +
|
|
" \n\n";
|
|
|
|
inputXX = " " + first_roi_final_XX + " " + last_roi_final_XX;
|
|
inputZZ = " " + first_roi_final_ZZ + " " + last_roi_final_ZZ;
|
|
inputY = " " + top_final_Y + " " + bottom_final_Y;
|
|
inputProj = " " + cameraFOVX + " " + cameraFOVY;
|
|
inputOLap = " " + overlapXZ + " " + overlapY;
|
|
|
|
inputs = inputXX + inputZZ + inputY + inputProj + inputOLap + " " + HighResimagePixelSize;
|
|
command_line = "stitching_scan_20180416.py -t" + inputs + " 0";
|
|
|
|
Dialog.create("MOTOR POSITIONS")
|
|
Dialog.addMessage(results);
|
|
Dialog.addString("Command line:", command_line, 60);
|
|
Dialog.addCheckbox("Good luck! Would you like to restart?", false);
|
|
Dialog.addCheckbox("Would you like to save motor positions in txt file?", false);
|
|
Dialog.addMessage("Reference Paper: Dejea H. et al. Scientific Reports 9, 6996 (2019)");
|
|
Dialog.show();
|
|
|
|
repeatFlag = Dialog.getCheckbox();
|
|
txtFlag = Dialog.getCheckbox();
|
|
|
|
//Set stageRotation back to degrees after the RepeatFlag
|
|
stageRotation = stageRotation*360/(2*PI);
|
|
|
|
Overlay.remove;
|
|
|
|
//Save txt file with all volume coordinates
|
|
if (txtFlag) {
|
|
Dialog.create("Motor movement protocol");
|
|
Dialog.addMessage("Please select the desired protocol: \n" + " -Conventional: typical stitched scan following first ZZ - XX movements, then Y. \n" +
|
|
" -Fast snake (Y): time-optimized protocol due to Y being the fastest motor." );
|
|
Dialog.addChoice("Protocol:", newArray("Conventional", "Fast snake (Y)"));
|
|
Dialog.show();
|
|
|
|
protocol = Dialog.getChoice();
|
|
|
|
txtArray = generateTXTarray(ScansInX,ScansInZ,first_roi_final_XX,first_roi_final_ZZ,top_final_Y,
|
|
bottom_final_Y,HighResimagePixelSize,cameraFOVY,cameraFOVX,overlapY,overlapXZ,protocol);
|
|
|
|
writeCoordsFile(txtArray);
|
|
|
|
repeatFlag = finalDialog(txtArray);
|
|
|
|
Overlay.remove;
|
|
}
|
|
|
|
//Save txt file with the parameters used, so that they are default for next use.
|
|
settings_file = File.open(settings_path);
|
|
settings_array = "File containing the last settings used in Multiscale_Coords.ijm\n" +
|
|
"\nLowResimagePixelSize\n" + LowResimagePixelSize +
|
|
"\nZZcenterLowRes\n" + ZZcenterLowRes +
|
|
"\nXXcenterLowRes\n" + XXcenterLowRes +
|
|
"\nYposLowRes\n" + YposLowRes +
|
|
"\ncameraFOVX\n" + cameraFOVX +
|
|
"\ncameraFOVY\n" + cameraFOVY +
|
|
"\nHighResimagePixelSize\n" + HighResimagePixelSize +
|
|
"\nCameraMoveX\n" + CameraMoveX +
|
|
"\nCameraMoveY\n" + CameraMoveY +
|
|
"\nstageRotation\n" + stageRotation + // TO DO: angular correction //
|
|
"\noverlapXZ\n" + overlapXZ +
|
|
"\noverlapY\n" + overlapY;
|
|
print(settings_file, settings_array);
|
|
File.close(settings_file);
|
|
|
|
} //END
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////// FUNCTIONS /////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function generateTXTarray (ScansInX,ScansInZ,first_roi_final_XX,first_roi_final_ZZ,top_final_Y,
|
|
bottom_final_Y,HighResimagePixelSize,cameraFOVY,cameraFOVX,overlapY,overlapXZ,protocol) {
|
|
//Function that generates the txt file with all motor positions for each volume.
|
|
|
|
//Determine number of Y blocks
|
|
blockSizeY = (cameraFOVY-overlapY)*HighResimagePixelSize;
|
|
if (top_final_Y - bottom_final_Y == 0) {
|
|
ScansInY = 1;
|
|
} else {
|
|
ScansInY = floor(abs(top_final_Y - bottom_final_Y) / blockSizeY) + 2;
|
|
}
|
|
|
|
//Grid in stage coords giving (XX,ZZ,Y) for each volume, from top to bottom, left to right and up to down.
|
|
centresXXZZY = newArray(ScansInX*ScansInZ*ScansInY*3);
|
|
|
|
//Position of center of first volume in stage coords
|
|
centresXXreal = newArray(ScansInX);
|
|
centresZZreal = newArray(ScansInZ);
|
|
centresYreal = newArray(ScansInY);
|
|
|
|
centresXXreal[0] = first_roi_final_XX;
|
|
centresZZreal[0] = first_roi_final_ZZ;
|
|
centresYreal[0] = top_final_Y;
|
|
|
|
//XX position of each of the other volumes in stage coords
|
|
blockSizeXZ = (cameraFOVX-overlapXZ)*HighResimagePixelSize;
|
|
for (j = 1; j < ScansInX; j++) {
|
|
centresXXreal[j] = centresXXreal[j-1] + blockSizeXZ;
|
|
if (abs(centresXXreal[j]) < 0.001) {centresXXreal[j] = 0;} //avoid extremely low exponential values
|
|
}
|
|
//ZZ position of each of the other volumes in stage coords
|
|
for (k = 1; k < ScansInZ; k++) {
|
|
centresZZreal[k] = centresZZreal[k-1] - blockSizeXZ;
|
|
if (abs(centresZZreal[k]) < 0.001) {centresZZreal[k] = 0;} //avoid extremely low exponential values
|
|
}
|
|
//Y position of each of the other volumes in stage coords
|
|
for (l = 1; l<ScansInY; l++) {
|
|
centresYreal[l] = centresYreal[l-1] - blockSizeY;
|
|
if (abs(centresYreal[l]) < 0.001) {centresYreal[l] = 0;} //avoid extremely low exponential values
|
|
}
|
|
|
|
if(protocol == "Conventional"){
|
|
//Fill grid in stage coords giving (XX,ZZ,Y) for each volume. First move ZZ, then XX, then Y.
|
|
cnt = 0;
|
|
for (ll = 0; ll<ScansInY; ll++) {
|
|
for (jj = 0; jj<ScansInX; jj++) {
|
|
for (kk = 0; kk<ScansInZ; kk++) {
|
|
|
|
centresXXZZY[cnt] = centresXXreal[jj];
|
|
centresXXZZY[cnt+1] = centresZZreal[kk];
|
|
centresXXZZY[cnt+2] = centresYreal[ll];
|
|
cnt = cnt+3;
|
|
}
|
|
}
|
|
}
|
|
} else if (protocol == "Fast snake (Y)") {
|
|
//Fill grid in stage coords giving (XX,ZZ,Y) for each volume. First move Y, then ZZ, then XX.
|
|
cnt2 = 0;
|
|
auxY = 1; iterY = 0;
|
|
auxZZ = 1; iterZZ = 0;
|
|
|
|
for (ll = 0; ll<ScansInX; ll++) {
|
|
for (jj = iterZZ; jj<ScansInZ && jj>(-1); jj = jj + auxZZ) {
|
|
for (kk = iterY; kk<ScansInY && kk>(-1); kk = kk + auxY) {
|
|
|
|
centresXXZZY[cnt2] = centresXXreal[ll];
|
|
centresXXZZY[cnt2+1] = centresZZreal[jj];
|
|
centresXXZZY[cnt2+2] = centresYreal[kk];
|
|
cnt2 = cnt2+3;
|
|
}
|
|
iterY = kk-(1*auxY);
|
|
auxY = auxY*(-1);
|
|
}
|
|
iterZZ = jj-(1*auxZZ);
|
|
auxZZ = auxZZ*(-1);
|
|
}
|
|
}
|
|
return centresXXZZY;
|
|
}
|
|
|
|
////////////////////////////////////////
|
|
|
|
function writeCoordsFile(centresXXZZY) {
|
|
//Function that writes the computed coordinates to a .txt file and saves it.
|
|
file = File.open("");
|
|
for (i = 0; i < lengthOf(centresXXZZY); i=i+3) {
|
|
print(file, centresXXZZY[i] + "," + centresXXZZY[i+1] + "," + centresXXZZY[i+2]);
|
|
}
|
|
File.close(file);
|
|
}
|
|
|
|
////////////////////////////////////////
|
|
|
|
function finalDialog(centresXXZZY) {
|
|
//Function that shows the last dialog with a summary and the possibility of restart.
|
|
info = "The motor positions of " + lengthOf(centresXXZZY)/3 + " scans have been computed. \n";
|
|
|
|
Dialog.create("MY WORK HERE IS DONE")
|
|
Dialog.addMessage("Calculations finished and saved. Use the txt file as input for your acquisition. \n" +
|
|
" \n\n" + info + " \n\n");
|
|
Dialog.addCheckbox("Good luck! Would you like to restart?",0);
|
|
Dialog.show();
|
|
|
|
repeatFlag = Dialog.getCheckbox();
|
|
return repeatFlag;
|
|
}
|
|
|
|
////////////////////////////////////////
|
|
|