Files
tomcat-fiji-plugins/plugins/Multiscale_Coords.ijm

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;
}
////////////////////////////////////////