429 lines
16 KiB
Plaintext
Executable File
429 lines
16 KiB
Plaintext
Executable File
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
///////// /////////
|
|
///////// OPTIMISED SCAN PLUGIN /////////
|
|
///////// /////////
|
|
///////// Created by Hector Dejea, hector.dejea@psi.ch /////////
|
|
///////// February, 2018 /////////
|
|
///////// /////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
/* Optimised Scan
|
|
|
|
Opt_Scan.ijm is an ImageJ plugin to find the optimal scanning grid for
|
|
irregular objects. Binary and non binary images are accepted. The option
|
|
to have a constant grid on the whole object or independent grids for
|
|
each of the different Y layers is given. The plugin generates a .txt file
|
|
that can be used as input for the beamline macro.
|
|
|
|
Last modified: 14/10/2019 */
|
|
|
|
//Check if there is a file containing the OptScan settings
|
|
settings_path = getDirectory("home") + "/.optscan_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];
|
|
LowResFOVY = lines[11];
|
|
cameraFOVX = lines[13];
|
|
cameraFOVY = lines[15];
|
|
HighResimagePixelSize = lines[17];
|
|
CameraMoveX = lines[19];
|
|
CameraMoveY = lines[21];
|
|
overlapXZ = lines[23];
|
|
overlapY = lines[25];
|
|
|
|
} else {
|
|
|
|
//Otherwise set these default values
|
|
LowResimagePixelSize = 6.5;
|
|
ZZcenterLowRes = 0;
|
|
XXcenterLowRes = 0;
|
|
YposLowRes = 0;
|
|
LowResFOVY = 590;
|
|
cameraFOVX = 2560;
|
|
cameraFOVY = 2160;
|
|
HighResimagePixelSize = 0.65;
|
|
CameraMoveX = 0;
|
|
CameraMoveY = 0;
|
|
overlapXZ = cameraFOVX*0.3;
|
|
overlapY = 50;
|
|
|
|
}
|
|
|
|
repeatFlag = 1;
|
|
while (repeatFlag == 1){
|
|
print("Welcome to the Scan Optimisation plugin!\n");
|
|
|
|
//Ask the user for LowRes and HighRes acquisition parameters
|
|
Dialog.create("Magnification and Camera Settings");
|
|
Dialog.addNumber("LowRes imagePixelSize:", LowResimagePixelSize);
|
|
Dialog.addNumber("LowRes XX center:", XXcenterLowRes);
|
|
Dialog.addNumber("LowRes ZZ center:", ZZcenterLowRes);
|
|
Dialog.addNumber("LowRes Y position (B1):", YposLowRes); //Y of highest volume
|
|
Dialog.addNumber("LowRes FOV Y:", LowResFOVY);
|
|
Dialog.addNumber("HighRes FOV X:", cameraFOVX);
|
|
Dialog.addNumber("HighRes FOV Y:", cameraFOVY);
|
|
Dialog.addNumber("HighRes imagePixelSize:", HighResimagePixelSize);
|
|
Dialog.addNumber("Camera movement X:", CameraMoveX);
|
|
Dialog.addNumber("Camera movement Y:", CameraMoveY);
|
|
Dialog.addNumber("Overlap in X and Z (pixels):", overlapXZ);
|
|
Dialog.addNumber("Overlap in Y (pixels):", overlapY);
|
|
Dialog.addChoice("Projection Method:", newArray("MaxIP" ,"MinIP"), "MaxIP");
|
|
|
|
Dialog.show();
|
|
|
|
LowResimagePixelSize = Dialog.getNumber();
|
|
XXcenterLowRes = Dialog.getNumber();
|
|
ZZcenterLowRes = Dialog.getNumber();
|
|
YposLowRes = Dialog.getNumber();
|
|
LowResFOVY = Dialog.getNumber();
|
|
cameraFOVX = Dialog.getNumber();
|
|
cameraFOVY = Dialog.getNumber();
|
|
HighResimagePixelSize = Dialog.getNumber();
|
|
CameraMoveX = Dialog.getNumber();
|
|
CameraMoveY = Dialog.getNumber();
|
|
overlapXZ = Dialog.getNumber();
|
|
overlapY = Dialog.getNumber();
|
|
projMethod = Dialog.getChoice();
|
|
|
|
// Real magnification
|
|
real_magnification = LowResimagePixelSize/HighResimagePixelSize;
|
|
|
|
// Set the starting slice
|
|
waitForUser("Need for action", "Scroll to the desired starting slice and click OK when you are done!\n");
|
|
StartSlice = getSliceNumber();
|
|
|
|
//Set the ending slice
|
|
waitForUser("Need for action", "Scroll to the desired ending slice and click OK when you are done!\n");
|
|
EndSlice = getSliceNumber();
|
|
|
|
//Compute number of acquisition layers in Y and layer size
|
|
blockSizeY = cameraFOVY-overlapY;
|
|
print("Volume Size in Y is " + (cameraFOVY/real_magnification) + " pixels");
|
|
print("Block Size in Y is " + (blockSizeY/real_magnification) + " pixels");
|
|
nLayersY = computeNumberBlocks("Y",blockSizeY,cameraFOVY,StartSlice,EndSlice,real_magnification);
|
|
|
|
//Compute blocksize XZ
|
|
blockSizeXZ = cameraFOVX-overlapXZ;
|
|
print("Volume Size in XZ is " + (cameraFOVX/real_magnification) + " pixels");
|
|
print("Block Size in XZ is " + (blockSizeXZ/real_magnification) + " pixels");
|
|
|
|
//Check if image is binary
|
|
binaryFlag = checkBinaryImg();
|
|
|
|
getDimensions(imagewidth, imageheight, stackchannels, stackslices, stackframes);
|
|
|
|
auxInit = StartSlice;
|
|
xVals = newArray(nLayersY);
|
|
yVals = newArray(nLayersY);
|
|
xEndVals = newArray(nLayersY);
|
|
yEndVals = newArray(nLayersY);
|
|
|
|
imgList = getList("image.titles"); //Get name of main stack
|
|
imgName = imgList[0];
|
|
|
|
if (binaryFlag) {
|
|
//For binary images
|
|
//Generate maximum/minimum intensity projection images
|
|
for (i = 0; i < nLayersY; i++) {
|
|
auxEnd = auxInit+(cameraFOVY-overlapY)/real_magnification;
|
|
if (auxEnd > EndSlice) {
|
|
auxEnd = EndSlice;
|
|
}
|
|
selectWindow(imgName);
|
|
if (projMethod == "MaxIP") {
|
|
run("Z Project...", "start=" + auxInit + " stop=" + auxEnd + " projection=[Max Intensity]");
|
|
} else {
|
|
run("Z Project...", "start=" + auxInit + " stop=" + auxEnd + " projection=[Min Intensity]");
|
|
}
|
|
auxInit = auxEnd + 1;
|
|
|
|
if (i > 0) { //Show previous rectangle
|
|
makeRectangle(xVals[i-1], yVals[i-1], xEndVals[i-1]-xVals[i-1], yEndVals[i-1]-yVals[i-1]);
|
|
}
|
|
|
|
waitForUser("Need for action", "Draw a rectangle around the object and click OK when you are done!\n");
|
|
getSelectionBounds(x, y, width, height);
|
|
|
|
//Save definition of all rectangles
|
|
xVals[i] = x;
|
|
yVals[i] = y;
|
|
xEndVals[i] = x + width -1;
|
|
yEndVals[i] = y + height -1;
|
|
}
|
|
|
|
//GENERATE GRID
|
|
centresXXZZY = generateYIndependentGrid(blockSizeXZ,blockSizeY,cameraFOVX,cameraFOVY,nLayersY,xVals,
|
|
yVals,xEndVals,yEndVals,real_magnification,XXcenterLowRes,ZZcenterLowRes,LowResimagePixelSize,
|
|
HighResimagePixelSize,LowResFOVY,CameraMoveX,CameraMoveY,imagewidth,imageheight,StartSlice);
|
|
|
|
//COMPLETE FOR BINARY IMAGES -> ELIMINATE EMPTY VOLUMES
|
|
|
|
|
|
|
|
} else {
|
|
//For non binary images
|
|
//Generate maximum/minimum intensity projection images and surround object with bounding box
|
|
for (i = 0; i < nLayersY; i++) {
|
|
auxEnd = auxInit+(cameraFOVY-overlapY)/real_magnification;
|
|
if (auxEnd > EndSlice) {
|
|
auxEnd = EndSlice;
|
|
}
|
|
selectWindow(imgName);
|
|
if (projMethod == "MaxIP") {
|
|
run("Z Project...", "start=" + auxInit + " stop=" + auxEnd + " projection=[Max Intensity]");
|
|
} else {
|
|
run("Z Project...", "start=" + auxInit + " stop=" + auxEnd + " projection=[Min Intensity]");
|
|
}
|
|
auxInit = auxEnd + 1;
|
|
|
|
if (i > 0) { //Show previous rectangle
|
|
makeRectangle(xVals[i-1], yVals[i-1], xEndVals[i-1]-xVals[i-1], yEndVals[i-1]-yVals[i-1]);
|
|
}
|
|
|
|
waitForUser("Need for action", "Draw a rectangle around the object and click OK when you are done!\n");
|
|
getSelectionBounds(x, y, width, height);
|
|
|
|
//Save definition of all rectangles
|
|
xVals[i] = x;
|
|
yVals[i] = y;
|
|
xEndVals[i] = x + width -1;
|
|
yEndVals[i] = y + height -1;
|
|
}
|
|
|
|
//GENERATE GRID
|
|
centresXXZZY = generateYIndependentGrid(blockSizeXZ,blockSizeY,cameraFOVX,cameraFOVY,nLayersY,xVals,
|
|
yVals,xEndVals,yEndVals,real_magnification,XXcenterLowRes,ZZcenterLowRes,LowResimagePixelSize,
|
|
HighResimagePixelSize,LowResFOVY,CameraMoveX,CameraMoveY,imagewidth,imageheight,StartSlice);
|
|
|
|
}
|
|
|
|
//Print .txt file with the generated coordinates
|
|
writeCoordsFile(centresXXZZY);
|
|
//Last dialog and restart flag
|
|
repeatFlag = finalDialog(centresXXZZY, nLayersY);
|
|
|
|
//Close all windows except stack
|
|
if (repeatFlag) {
|
|
closeGeneratedImgs();
|
|
}
|
|
|
|
//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 Opt_Scan.ijm\n" +
|
|
"\nLowResimagePixelSize\n" + LowResimagePixelSize +
|
|
"\nZZcenterLowRes\n" + ZZcenterLowRes +
|
|
"\nXXcenterLowRes\n" + XXcenterLowRes +
|
|
"\nYposLowRes\n" + YposLowRes +
|
|
"\nLowResFOVY\n" + LowResFOVY +
|
|
"\ncameraFOVX\n" + cameraFOVX +
|
|
"\ncameraFOVY\n" + cameraFOVY +
|
|
"\nHighResimagePixelSize\n" + HighResimagePixelSize +
|
|
"\nCameraMoveX\n" + CameraMoveX +
|
|
"\nCameraMoveY\n" + CameraMoveY +
|
|
"\noverlapXZ\n" + overlapXZ +
|
|
"\noverlapY\n" + overlapY;
|
|
print(settings_file, settings_array);
|
|
File.close(settings_file);
|
|
|
|
} //END
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////// FUNCTIONS /////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function checkBinaryImg() {
|
|
//Function that checks if the current image is binary
|
|
//Use bitdepth() to adapt to 16bit
|
|
getHistogram(values, counts, 256);
|
|
gvs = 0;
|
|
binary_flag = true;
|
|
for (i=0;i<=255;i++){
|
|
if (counts[i]>0) {
|
|
gvs++;
|
|
}
|
|
if (gvs>2) {
|
|
binary_flag = false;
|
|
break;
|
|
}
|
|
}
|
|
return binary_flag;
|
|
}
|
|
|
|
////////////////////////////////////////
|
|
|
|
function computeNumberBlocks(axis,blockSize,cameraFOV,StartPoint,EndPoint,real_magnification) {
|
|
//Function that computes the number of blocks in the given axis direction.
|
|
delta = (EndPoint-StartPoint)*real_magnification;
|
|
|
|
//Include the fact that the first Y layer is complete
|
|
if (delta <= cameraFOV) {
|
|
nBlocks = 1; //Number of heights in the new scan
|
|
}else{
|
|
ratio = (delta - cameraFOV)/blockSize;
|
|
nBlocks = floor(ratio) + 2; //Number of heights in the new scan
|
|
}
|
|
print("Number of " + axis + " blocks: " + nBlocks);
|
|
|
|
return nBlocks;
|
|
}
|
|
|
|
////////////////////////////////////////
|
|
|
|
function computeYCoords(nLayersY,YposLowRes,LowResFOVY,StartSlice,LowResimagePixelSize,CameraMoveY,cameraFOVY,
|
|
HighResimagePixelSize,blockSizeY) {
|
|
//Function that computes the Y positions in stage coordinates
|
|
|
|
//Y position of center of first volume in stage coords
|
|
centresYreal = newArray(nLayersY);
|
|
centresYreal[0] = YposLowRes - (LowResFOVY/2 - StartSlice) * LowResimagePixelSize + CameraMoveY +
|
|
(cameraFOVY*HighResimagePixelSize)/2;
|
|
//Y position of each of the layers in stage coords
|
|
for (l = 1; l < nLayersY; l++) {
|
|
centresYreal[l] = centresYreal[l-1] + (blockSizeY * HighResimagePixelSize);
|
|
}
|
|
return centresYreal;
|
|
}
|
|
|
|
////////////////////////////////////////
|
|
|
|
function lowResPx2StageCoords (numBlocksXX,numBlocksZZ,totalBlocks,nLayersY,yVals,xVals,cameraFOVX,
|
|
real_magnification,imagewidth,imageheight,XXcenterLowRes,ZZcenterLowRes,LowResimagePixelSize,
|
|
HighResimagePixelSize,CameraMoveX,blockSizeXZ,centresYreal) {
|
|
//Function that transforms low resolution pixel coordinates to stage coordinates.
|
|
|
|
//Grid in stage coords giving (XX,ZZ,Y) for each volume, from top to bottom, left to right and up to down.
|
|
centresXXZZY = newArray(totalBlocks*3);
|
|
//Counter for filling up the last array
|
|
cnt = 0;
|
|
|
|
for (ll = 0; ll<nLayersY; ll++) {
|
|
|
|
//Position of center of first volume in LowRes pixels
|
|
centre1XXpix = yVals[ll] + cameraFOVX/2/real_magnification;
|
|
centre1ZZpix = xVals[ll] + cameraFOVX/2/real_magnification;
|
|
|
|
im_center_x = imagewidth/2;
|
|
im_center_y = imageheight/2;
|
|
|
|
centresXXreal = newArray(numBlocksXX[ll]);
|
|
centresZZreal = newArray(numBlocksZZ[ll]);
|
|
|
|
//Position of center of first volume in stage coords
|
|
centresXXreal[0] = XXcenterLowRes - (im_center_y - centre1XXpix) * LowResimagePixelSize + CameraMoveX;
|
|
centresZZreal[0] = ZZcenterLowRes + (im_center_x - centre1ZZpix) * LowResimagePixelSize;
|
|
|
|
//XX position of each of the other volumes in stage coords
|
|
for (j = 1; j < numBlocksXX[ll]; j++) {
|
|
centresXXreal[j] = centresXXreal[j-1] + (blockSizeXZ * HighResimagePixelSize);
|
|
}
|
|
//ZZ position of each of the other volumes in stage coords
|
|
for (k = 1; k < numBlocksZZ[ll]; k++) {
|
|
centresZZreal[k] = centresZZreal[k-1] - (blockSizeXZ * HighResimagePixelSize);
|
|
}
|
|
|
|
//Fill grid in stage coords giving (XX,ZZ,Y) for each volume, from top to bottom, left to right
|
|
//and up to down.
|
|
for (jj = 0; jj<numBlocksXX[ll]; jj++) {
|
|
for (kk = 0; kk<numBlocksZZ[ll]; kk++) {
|
|
centresXXZZY[cnt] = centresXXreal[jj];
|
|
centresXXZZY[cnt+1] = centresZZreal[kk];
|
|
centresXXZZY[cnt+2] = centresYreal[ll];
|
|
cnt = cnt+3;
|
|
}
|
|
}
|
|
}
|
|
return centresXXZZY;
|
|
}
|
|
|
|
////////////////////////////////////////
|
|
|
|
function generateYIndependentGrid(blockSizeXZ,blockSizeY,cameraFOVX,cameraFOVY,nLayersY,xVals,yVals,xEndVals,
|
|
yEndVals,real_magnification,XXcenterLowRes,ZZcenterLowRes,LowResimagePixelSize,HighResimagePixelSize,
|
|
LowResFOVY,CameraMoveX,CameraMoveY,imagewidth,imageheight,StartSlice) {
|
|
//Function that generates independent grids for each Y
|
|
|
|
//Compute Y positions in stage coords
|
|
centresYreal = computeYCoords(nLayersY,YposLowRes,LowResFOVY,StartSlice,LowResimagePixelSize,CameraMoveY,
|
|
cameraFOVY,HighResimagePixelSize,blockSizeY);
|
|
|
|
//Compute number of blocks per layer and total number
|
|
numBlocksXX = newArray(nLayersY);
|
|
numBlocksZZ = newArray(nLayersY);
|
|
totalBlocksLayer = 0;
|
|
totalBlocks = 0;
|
|
for (i = 0; i < nLayersY; i++ ) {
|
|
print("\nY layer" + (i+1) + ":");
|
|
numBlocksXX[i] = computeNumberBlocks("XX",blockSizeXZ,cameraFOVX,yVals[i],yEndVals[i],real_magnification);
|
|
numBlocksZZ[i] = computeNumberBlocks("ZZ",blockSizeXZ,cameraFOVX,xVals[i],xEndVals[i],real_magnification);
|
|
|
|
//Total number of XX and ZZ blocks
|
|
totalBlocksLayer = numBlocksXX[i]*numBlocksZZ[i];
|
|
totalBlocks = totalBlocks + totalBlocksLayer;
|
|
}
|
|
|
|
//Grid in stage coords giving (XX,ZZ,Y) for each volume, from top to bottom, left to right and up to down.
|
|
centresXXZZY = lowResPx2StageCoords(numBlocksXX,numBlocksZZ,totalBlocks,nLayersY,yVals,xVals,cameraFOVX,
|
|
real_magnification,imagewidth,imageheight,XXcenterLowRes,ZZcenterLowRes,LowResimagePixelSize,
|
|
HighResimagePixelSize,CameraMoveX,blockSizeXZ,centresYreal);
|
|
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, nLayersY) {
|
|
//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 (" +
|
|
nLayersY + " layer(s)). \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;
|
|
}
|
|
|
|
////////////////////////////////////////
|
|
|
|
function closeGeneratedImgs() {
|
|
//Function that closes all MIP images generated.
|
|
imgList = getList("image.titles");
|
|
for (i=1; i<lengthOf(imgList); i++) {
|
|
selectWindow(imgList[i]);
|
|
run("Close");
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////
|