Skip to content

Instantly share code, notes, and snippets.

@imabug
Created May 24, 2021 03:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save imabug/68792bb0a8480733e1ef1e805a086e60 to your computer and use it in GitHub Desktop.
Save imabug/68792bb0a8480733e1ef1e805a086e60 to your computer and use it in GitHub Desktop.
AAPM Report 270 ImageJ macros
/*
This code will generate a modified TG18PQC pattern following the same basic modifications used in the pacsDisplay iQC pattern. The user should set up the size variable for the desired image size. Typically this is either a 1024x1024 image or 2048x2048 image. In this script, a square pattern is generated, though it can be of arbitrary size. The size of everything is based on the TG18PQC definitions and will just be scaled accordingly (though it may have some errors).
This version of the script will assume an 8-bit grayscale.
*/
size = 1024;
newImage("TG270-pQC", "8-bit black", size, size, 1);
// Define some of the sizes (everything is normalized to 1024)
sidew = floor(size/1024*87);
barh = floor(size/1024*50);
toph = floor(size/1024*62);
// Create the horizontal bars and the contrast modulation within (varying contrast and frequency)
// 18 bars (0, 15, 30, ..., 240, 255)
run("Macro...", "code=[if(y>=" + toph + "&&y<" + size - toph + ") v=floor((y-" + toph + ")/" + barh + ")*15]");
// Contrast modulation
// Horizontal bars
freqH = newArray(18, 12, 6, 4);
for (i = 0; i < 4; i++) {
f = freqH[i];
// Left contrast (8)
run("Macro...", "code=[if(x>=" + sidew + i*barh + "&&x<" + sidew + barh + i*barh + "&&((y-" + toph + ")%" + f + "<" + f/2 + ")) v=v-4]");
run("Macro...", "code=[if(x>=" + sidew + i*barh + "&&x<" + sidew + barh + i*barh + "&&((y-" + toph + ")%" + f + ">=" + f/2 + ")) v=v-4]");
// Add additional four to the top row and subtract four from the bottom row
run("Macro...", "code=[if(x>=" + sidew + i*barh + "&&x<" + sidew + barh + i*barh + "&&y<" + toph + barh + "&&((y-" + toph + ")%" + f + ">=" + f/2 + ")) v=v+4]");
run("Macro...", "code=[if(x>=" + sidew + i*barh + "&&x<" + sidew + barh + i*barh + "&&y>=" + size - toph - barh + "&&((y-" + toph + ")%" + f + "<" + f/2 + ")) v=v-4]");
//Left contrast (2)
run("Macro...", "code=[if(x>=" + sidew + (i + 4)*barh + "&&x<" + sidew + barh + (i + 4)*barh + "&&((y-" + toph + ")%" + f + "<" + f/2 + ")) v=v-1]");
run("Macro...", "code=[if(x>=" + sidew + (i + 4)*barh + "&&x<" + sidew + barh + (i + 4)*barh + "&&((y-" + toph + ")%" + f + ">=" + f/2 + ")) v=v+1]");
// Add additional one to the top row and subtract one from the bottom row
run("Macro...", "code=[if(x>=" + sidew + (i + 4)*barh + "&&x<" + sidew + barh + (i + 4)*barh + "&&y<" + toph + barh + "&&((y-" + toph + ")%" + f + ">=" + f/2 + ")) v=v+1]");
run("Macro...", "code=[if(x>=" + sidew + (i + 4)*barh + "&&x<" + sidew + barh + (i + 4)*barh + "&&y>=" + size - toph - barh + "&&((y-" + toph + ")%" + f + "<" + f/2 + ")) v=v-1]");
}
// Vertical bars
freqV = newArray(4, 6, 12, 18);
for (i = 9; i < 13; i++) {
f = freqV[i - 9];
// Right contrast (8)
run("Macro...", "code=[if(x>=" + sidew + (i + 4)*barh + "&&x<" + sidew + barh + (i + 4)*barh + "&&((x-" + sidew + i*barh + ")%" + f + "<" + f/2 + ")) v=v-4]");
run("Macro...", "code=[if(x>=" + sidew + (i + 4)*barh + "&&x<" + sidew + barh + (i + 4)*barh + "&&((x-" + sidew + i*barh + ")%" + f + ">=" + f/2 + ")) v=v+4]");
//Add additional four to the top row and subtract four from the bottom row
run("Macro...", "code=[if(x>=" + sidew + (i + 4)*barh + "&&x<" + sidew + barh + (i + 4)*barh + "&&y<" + toph + barh + "&&((x-" + sidew + i*barh + ")%" + f + ">=" + f/2 + ")) v=v+4]");
run("Macro...", "code=[if(x>=" + sidew + (i + 4)*barh + "&&x<" + sidew + barh + (i + 4)*barh + "&&y>=" + size - toph - barh + "&&((x-" + sidew + i*barh + ")%" + f + "<" + f/2 + ")) v=v-4]");
// Right contrast (2)
run("Macro...", "code=[if(x>=" + sidew + i*barh + "&&x<" + sidew + barh + i*barh + "&&((x-" + sidew + i*barh + ")%" + f + "<" + f/2 + ")) v=v-1]");
run("Macro...", "code=[if(x>=" + sidew + i*barh + "&&x<" + sidew + barh + i*barh + "&&((x-" + sidew + i*barh + ")%" + f + ">=" + f/2 + ")) v=v+1]");
// Add additional one to the top row and subtract one from the bottom row
run("Macro...", "code=[if(x>=" + sidew + i*barh + "&&x<" + sidew + barh + i*barh + "&&y<" + toph + barh + "&&((x-" + sidew + i*barh + ")%" + f + ">=" + f/2 + ")) v=v+1]");
run("Macro...", "code=[if(x>=" + sidew + i*barh + "&&x<" + sidew + barh + i*barh + "&&y>=" + size - toph - barh + "&&((x-" + sidew + i*barh + ")%" + f + "<" + f/2 + ")) v=v-1]");
}
// Horizontal bars at the top and bottom of the phantom with high-contrast line par phantoms
run("Macro...", "code=[if(y<" + toph + ") v=64]");
run("Macro...", "code=[if(y>=" + size - toph + ") v=191]");
// Line pairs
freqLP = newArray(6, 4, 2, 2, 4, 6);
topM = toph/2;
for (i = 0; i < 3; i++) {
f = freqLP[i];
run("Macro...", "code=[if(x>=" + sidew + (2*i + 2)*barh + "&&x<" + sidew + barh + (2*i + 2)*barh + "&&y>" + topM - barh/2 + "&&y<=" + topM + barh/2 + "&&((y-" + (topM - barh/2) + ")%" + f + "<" + f/2 + ")) v=128]");
run("Macro...", "code=[if(x>=" + sidew + (2*i + 2)*barh + "&&x<" + sidew + barh + (2*i + 2)*barh + "&&y>" + topM - barh/2 + "&&y<=" + topM + barh/2 + "&&((y-" + (topM - barh/2) + ")%" + f + ">=" + f/2 + ")) v=0]");
run("Macro...", "code=[if(x>=" + sidew + (2*i + 2)*barh + "&&x<" + sidew + barh + (2*i + 2)*barh + "&&y>" + size - topM - barh/2 + "&&y<=" + size - topM + barh/2 + "&&((y-" + (topM - barh/2) + ")%" + f + "<" + f/2 + ")) v=128]");
run("Macro...", "code=[if(x>=" + sidew + (2*i + 2)*barh + "&&x<" + sidew + barh + (2*i + 2)*barh + "&&y>" + size - topM - barh/2 + "&&y<=" + size - topM + barh/2 + "&&((y-" + (topM - barh/2) + ")%" + f + ">=" + f/2 + ")) v=255]");
}
for (i = 3; i < 6; i++) {
f = freqLP[i];
run("Macro...", "code=[if(x>=" + sidew + (2*i + 4)*barh + "&&x<" + sidew + barh + (2*i + 4)*barh + "&&y>" + topM - barh/2 + "&&y<=" + topM + barh/2 + "&&((x-" + sidew + (2*i + 4)*barh + ")%" + f + "<" + f/2 + ")) v=128]");
run("Macro...", "code=[if(x>=" + sidew + (2*i + 4)*barh + "&&x<" + sidew + barh + (2*i + 4)*barh + "&&y>" + topM - barh/2 + "&&y<=" + topM + barh/2 + "&&((x-" + sidew + (2*i + 4)*barh + ")%" + f + ">=" + f/2 + ")) v=0]");
run("Macro...", "code=[if(x>=" + sidew + (2*i + 4)*barh + "&&x<" + sidew + barh + (2*i + 4)*barh + "&&y>" + size - topM - barh/2 + "&&y<=" + size - topM + barh/2 + "&&((x-" + sidew + (2*i + 4)*barh + ")%" + f + "<" + f/2 + ")) v=128]");
run("Macro...", "code=[if(x>=" + sidew + (2*i + 4)*barh + "&&x<" + sidew + barh + (2*i + 4)*barh + "&&y>" + size - topM - barh/2 + "&&y<=" + size - topM + barh/2 + "&&((x-" + sidew + (2*i + 4)*barh + ")%" + f + ">=" + f/2 + ")) v=255]");
}
// Vertical side bars with modulating gradient
strip = sidew/2;
// Left side
run("Macro...", "code=[if(x<" + sidew + ") v=y/" + size - 1 + "*1031/1024*255]");
run("Macro...", "code=[if(x<" + sidew/2 + (strip/2) + "&&x>" + sidew/2 - (strip/2) + ") v=v+3*sin(y/2)]");
// Right side
run("Macro...", "code=[if(x>=" + size - sidew + ") v=-1*(y-" + size - 1 + ")/" + size - 1 + "*1031/1024*255]");
run("Macro...", "code=[if(x<" + size - sidew/2 + (strip/2) + "&&x>" + size - sidew/2 - (strip/2) + ") v=v+3*sin(y/1.5)]");
//Set up fonts for labeling
setFont("SansSerif", 18, "antialiased");
setJustification("center");
setColor(128, 128, 128);
drawString("TG270-pQC", size/2 - 1, 32);
setMetadata("Label", "TG270-pQC");
/*
Use this code to generate the sQC (simple QC) TG270 pattern.
This version of the script will assume an 8-bit grayscale.
This pattern is intended to be used by non-physicists as a quick visual
evaluation of display performance. It contains three rows of six incrementing
grayscale squares (for a total of 18 squares) that cover from 0 to 255. Each
square contains a smaller square in the upper left and lower right corner.
These sub-squares contain a modulation pattern iwth a period of 6 p ixels and
50% duty cycle. The patterns use vertical bars that differ from the background
value of the square by 5 gray levels. The pattern in the upper left corner of
each square has vertical bars with a lower pixel value than the square, while
the pattern in the lower right has vertical bars with higher pixel values. For
the first square (GL 0), the pattern in the upper left cannot be lower than
the main square. Its bars have a contrast of 3 gray levels above the
background. Similarly, the last square (GL 255) cannot have a lower right
sub-square with higher pixel values, and so it has a modulation contrast of 3
gray levels below the background.
For a DICOM-conformant display, the use should be able to quickly scan the
three rows of squares and assess if the modulation patterns in each are
equally visible across all 18 gray levels. The upper-leftmost and
lower-rightmost patterns correspond to the ones with the lower contrast (3
gray levels vs. 5); these may only be visible on the highest-performance
displays.
The sQC pattern also includes three larger squares at the bottom of the
pattern that allow for minimum and maximum luminance calculations (and
therefore luminance ratio). These three patterns (black, mid-gray, white) can
be zoomed and panned around the display for bad pixel and uniformity
measurements. Furthermore, any of the squares in the three rows can be zoomed
and panned for similar use. The squares in the three rows correspond to the
gray levels used in the traditional 18-point TG18 luminance response
measurement.
Along the bottom of the pattern, a grayscale gradient is included with
continuous pixel value variation for evaluating bit depth issues, contouring
artifacts, and grayscale errors. At the side of the gradient, line pairs in
the horizontal and vertical direction can be used to verify correct pixel
mapping.
*/
// Define the size of the image (1024 or 2048)
size = 1024;
// Based on the size, set a scaler value
sc = size / 1024;
//Create the image
newImage("TG270-sQC", "8-bit black", size, size, 1);
// Set the background
background = 128;
// Set the period of the bar patterns (pixels/lp)
barper = 6;
// Set the entire page to the background
run("Macro...", "code=v=" + background + "");
// Set up fonts for labeling
setFont("SansSerif", 18, "antialiased");
setJustification("center");
color=background-75;
setColor(color, color, color);
drawString("TG270-sQC", size/2 - 1,32);
setMetadata("Label", "TG270-sQC");
// Create a for loop to draw the boxes and contrast squares
ioff = 1; // Define i offset to move the group of boxes up/down
for (i = 0; i < 3; i++) {
for (j = 0; j < 6; j++) {
// Set the color based on the row, column (i, j) coordinates in steps of 15
/*
The power command allows the grays to be read continuously by
switching the order of the second row. This way the eye can follow the
grays from left to right, move down, then go from right to left in the
second row before going left to right for the final row.
*/
color = (i*6 + 2.5 - 2.5*pow(-1, i) + j*pow(-1, i))*15;
setColor(color, color, color);
// Draw the main squares (128x128 for 1024)
fillRect((sc*160*j + sc*48), sc*128*(i + ioff) + (i - 1)*sc*32, sc*128, sc*128);
// Label the squares (comment the drawString command to remove labels)
setFont("SansSerif", 12, "antialiased");
setJustification("center");
tcolor = (i*6 + 2.5 - 2.5*pow(-1, i) + j*pow(-1, i))%9*15 + 75;
setColor(tcolor, tcolor, tcolor);
// Label the squares with gray levels
drawString((i*6 + 2.5 - 2.5*pow(-1, i) + j*pow(-1, i))*15, (sc*160*j + sc*48) + sc*64, sc*128*(i + ioff) + (i-1)*sc*32 + sc*16);
// Label the squares with indices
// drawString((i*6 + 2.5 - 2.5*pow(-1, i) + j*pow(-1, i))*15, (sc*160*j + sc*48) + sc*64, sc*128*(i + ioff) + (i-1)*sc*32 + sc*16);
// Set the contrast color to 2% of the main square and draw the modulation pattern
// Create special conditions for the first and last box
if ((i == 0) && (j == 0)) {
for (k = 0; k < ((32/barper)*sc); k++) {
lowc = 3;
setColor(lowc, lowc, lowc);
fillRect((sc*160*j + sc*48) + sc*16 + k*barper, sc*128*(i + ioff) + (i - 1)*sc*32 + sc*16, barper/2, sc*32);
cont = 5;
setColor(cont, cont, cont);
fillRect((sc*160*j + sc*48) + sc*80 + k*barper, sc*128*(i + ioff) + (i - 1)*sc*32 + sc*80, barper/2, sc*32);
}
} else {
if ((i == 2) && (j == 5)) {
for (k = 0; k < (32/barper)*sc; k++) {
lowc = (i*6 + 2.5 - 2.5*pow(-1, i) + j*pow(-1, i))*15 - 3;
setColor(lowc, lowc, lowc);
fillRect((sc*160*j + sc*48) + sc*80 + k*barper, sc*128*(i + ioff) + (i - 1)*sc*32 + sc*80, barper/2, sc*32);
cont = (i*6 + 2.5 - 2.5*pow(-1, i) + j*pow(-1, i))*15 - 5;
setColor(cont, cont, cont);
fillRect((sc*160*j + sc*48) + sc*16 + k*barper, sc*128*(i + ioff) + (i - 1)*sc*32 + sc*16, barper/2, sc*32);
}
} else {
for (k = 0; k < (32/barper)*sc; k++) {
cont = (i*6 + 2.5 - 2.5*pow(-1, i) + j*pow(-1, i))*15 - 5;
setColor(cont, cont, cont);
fillRect((sc*160*j + sc*48) + sc*16 + k*barper, sc*128*(i + ioff) + (i - 1)*sc*32 + sc*16, barper/2, sc*32);
cont = (i*6 + 2.5 - 2.5*pow(-1, i) + j*pow(-1, i))*15 + 5;
setColor(cont, cont, cont);
fillRect((sc*160*j + sc*48) + sc*80 + k*barper, sc*128*(i + ioff) + (i - 1)*sc*32 + sc*80, barper/2, sc*32);
}
}
}
}
}
// Draw some big squares along the bottom for bad pixel testing and luminance measuring
koff = 4;
for (k = 0; k < 3; k++) {
color = k*128; // Set the color
setColor(color, color, color);
fillRect(sc*(128 + k*256), sc*128*(koff + 1), sc*256, sc*256); // Draw the main squares (128x128 for 1024)
}
// Draw vertical gradient along the right side:
// run("Macro...", "code=[if(x>"+sc*(size-64)+"&&y<="+size-sc*64+"&&y>="+sc*64+") v=(y-"+sc*64+")/"+size-sc*64*2+"*255]");
// Draw horizontal gradient along the bottom (either with or without line patterns)
// With line patterns at the sides
run("Macro...","code=[if(y>"+(size-sc*64)+"&&x<="+size-2*sc*64+") v=(x-"+sc*64+")/"+size-sc*64*2+"*255]");
run("Macro...","code=[if(y>"+(size-sc*64)+"&&x>"+size-2*sc*64+"&&x<"+size-sc*64+") v=(x%2)*255]");
run("Macro...","code=[if(y>"+(size-sc*64)+"&&x>"+size-sc*64+") v=(y%2)*255]");
// Without the line patterns
// run("Macro...", "code=[if(y>" + sc∗(size − 64) + ") v=(x−" + sc∗64 + ")/" + size − sc∗64∗2 + "∗255]") ;
// Use this code to generate a series of temporal resolution images for an animated gif
// Define the size of the frame
vsize = 1024;
hsize = 1024;
// Define a scalar to adjust from the default size
vsc = vsize/1024;
hsc = hsize/1024;
// Define the number of rows (powers of 2 work best)
rows = 16;
// Define the size of the blocks (assumes square size)
block = 32;
// Define the number of blocks (4-7 recommended)
numbl = 6;
// Define the number of frames (based on block count)
frames = hsize/(2*block) + 2 + (numbl - 1);
// Define frame rate
fps = 60;
// Create the image stack
newImage("TG270-TR2", "black", hsize, vsize, frames);
// Set the gray level pairs. 2*rows pairs are required.
gl = newArray(015, 020, 015,030, 015, 045, 015, 065, 120, 125, 120, 135, 120, 150, 120, 170, 245, 240, 245, 230, 245, 215, 245, 195, 000, 075, 000, 255, 255, 180, 255, 000);
setFont("SansSerif", 10, "antialiased");
setJustification("center");
// a = vsize/rows;
// b = vsc*block;
// c = 2*block*hsc;
// d = hsize - (hsize - hsc*2*numbl*block);
// e = block*hsc;
// f = 0.5*(vsize/rows - vsc*block;
// Create the test pattern background. Loop through each gray level pair base.
for (i = 0; i < rows; i++) {
setColor(gl[2*i], gl[2*i], gl[2*i]);
run("Macro...", "code=[if(y>=" + i*vsize/rows + "&&y<=" + (i + 1)*vsize/rows + ") v=" + gl[2*i] + "] stack");
run("Macro...", "code=[if(y>=" + i*vsize/rows + 0.5*(vsize/rows - vsc*block) + "&&y<=" + (i + 1)*vsize/rows - 0.5*(vsize/rows - vsc*block) + "&&x>=" + 2*block*hsc + "*z-" + hsize - (hsize - hsc*2*numbl*block) + "&&x<" + 2*block*hsc + "*z&&x%" + 2*block*hsc + ">" + block*hsc - 1 + ") v=" + gl[2*i] + " + (" + gl[2*i + 1] + "-" + gl[2*i]+ ")] stack");
//set text color
tcolor = gl[2*i] + (64*pow(-1, floor(gl[2*i]/128)));
setColor(tcolor, tcolor, tcolor);
//set up labels
for (j = 0; j < frames; j ++) {
setSlice(j + 1);
// Label each frame
drawString(j, j*hsc*block*2 - (numbl - 0.5)*block*hsc, i*vsize/rows + 0.5*(vsize/rows - vsc*block));
// Label each pair
drawString(gl[2*i] + "/" + gl[2*i + 1], hsize/2, i*vsize/rows + (vsize/rows - vsc*block) + vsc*block);
}
}
// Set up the animation options, set to desired fps
run("Animation Options...", "speed=" + fps + " first=1 last=" + frames + " start");
// If this command is enabled, just select the option to use the slice labels as the file names.
// run("Image sequence...",);
// Use this code to generate combination UN and LN test patterns (TG270-ULN)
// Define the number of images (18 (15 GL), 52 (5 GL), 86 (3 GL) or 256 (1 GL))
images = 256;
// Define the size of the images (1024 or 2048)
size = 1024;
// Create the image stack (Re-define the image type for other bit-depths)
newImage("TG270-ULN8-", "8-bit black", size, size, images);
// Set the image values and draw the appropriate lines
step = 255/(images - 1);
// Set the entire page to the z*step value
run("Macro...", "code=[v=z*" + step + "] stack");
// Set up fonts for labeling
setFont("SansSerif", 18, "antialiased");
setJustification("center");
// Create a for loop to label the images and draw the grid liens
for (i = 0; i < images; i++) {
setSlice(i + 1);
color = (i%(images/2) + images/6)*step;
setColor(color, color, color);
drawString("TG270-ULN8-" + IJ.pad(i*step, 3) + "", size/2 - 1, 32);
setMetadata("Label", "TG270-ULN8-" + IJ.pad(i*step, 3) + "");
drawRect(340*size/1024, 0, 2, size);
drawRect(682*size/1024, 0, 2, size);
drawRect(0, 340*size/1024, size, 2);
drawRect(0, 682*size/1024, size, 2);
}
//If this command is enabled, just select the option to use the slice labels as the file names
// run("Image Sequence...",);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment