//Jan Wisniewski, Experimental Immunology Branch, National Cancer Institute, National Institutes of Health, Bethesda, Maryland 20892, USA //This macro requires separated mosaic tiles. //The mosaic should include some empty area around a tissue section or at least on empty field, so the macro can extract flat field data. //All tiles are flat-field and color balance corrected prior to row-by-row assembly. //There is an option to just annotate already assembled mosaics or re-start process from intermediate stages. //To establish tile offsets, this macro use "Pairwise Stitching" plug-in (Preibisch et al., Bioinformatics(2009) 25(11) 1463-65) present in Fiji installation of ImageJ. //Mosaics are saved as three-channel 16-bit tif and RGB image print("Histological image mosaic correction and display\nJan Wisniewski, Experimental Immunology Branch\nNational Cancer Institute, NIH, Bethesda, Maryland"); getDateAndTime(year, month, dayOfWeek, dayOfMonth, hour, minute, second, msec); print(""); print("run: ", month, "/", dayOfMonth, "/", year, " at ", hour, ":", minute); print(""); run("Set Measurements...", "area mean min integrated median display scientific redirect=None decimal=0"); prc=getBoolean("Do you want to process a set of already assembled mosaics, stored in a separate folder?"); if(prc==0) {Dialog.create("Already assembled tiles options"); Dialog.addMessage("Check all that apply:"); Dialog.addCheckbox("Check if you already have corrected tiles", false); Dialog.addCheckbox("Check if they are sorted into subfolders according to the mosaic row", false); Dialog.addCheckbox("Check if rows are padded to equal length", false); Dialog.show(); res=Dialog.getCheckbox(); srt=Dialog.getCheckbox(); pdd=Dialog.getCheckbox(); if(res==0) {mscs=getBoolean("Do you want to store copies of all mosaic in a single folder?"); if(mscs==1) {mosaics=getDirectory("Select Folder to store copies of all mosaics"); } output=getDirectory("Choose_Directory for Results and temporary Directories"); myDir0 = output+"scale applied"+File.separator; File.makeDirectory(myDir0); myDir1 = output+"red"+File.separator; File.makeDirectory(myDir1); myDir2 = output+"grn"+File.separator; File.makeDirectory(myDir2); myDir3 = output+"blu"+File.separator; File.makeDirectory(myDir3); myDir4 = output+"tiles"+File.separator; File.makeDirectory(myDir4); myDir5 = output+"stripes"+File.separator; File.makeDirectory(myDir5); myDir6 = output+"corr-red"+File.separator; File.makeDirectory(myDir6); myDir7 = output+"corr-grn"+File.separator; File.makeDirectory(myDir7); myDir8 = output+"corr-blu"+File.separator; File.makeDirectory(myDir8); input=getDirectory("Select a Source Directory"); NAMES=getFileList(input); Dialog.create("Image scale"); Dialog.addNumber("Objective magnification:", 10); items=newArray("AxioCam MRc5", "Orca Flash 4.0", "other"); Dialog.addChoice("Camera used:", items, "AxioCam MRc5"); Dialog.addNumber("If OTHER, type detector's pixel size:", 0); Dialog.show(); obj=Dialog.getNumber(); cam=Dialog.getChoice(); othp=Dialog.getNumber(); if(cam=="other") {pxl=othp/obj; } if(cam=="AxioCam MRc5") {pxl=6.8/obj; } if(cam=="Orca Flash 4.0") {pxl=6.5/obj; } print("Camera:", cam, " magnification:", obj, "effective pixel size:", pxl); for (i=0; iL check_peaks=5 compute_overlap x=0 y=0 registration_channel_image_1=[Average all channels] registration_channel_image_2=[Average all channels]"); close("R"); close("L"); close("R<->L"); waitForUser("Identify two tiles adjacent vertically and open BOTTOM, then TOP of them !"); open(); rename("B"); open(); rename("T"); print(" "); print("vertical:"); run("Pairwise stitching", "first_image=B second_image=T fusion_method=[Linear Blending] fused_image=B<->T check_peaks=5 compute_overlap x=0 y=0 registration_channel_image_1=[Average all channels] registration_channel_image_2=[Average all channels]"); close("B"); close("T"); close("B<->T"); selectWindow("Log"); } Dialog.create("Stitching setup"); Dialog.addMessage("Use (x,y) numbers listed in the Log"); Dialog.addNumber("Enter x offset for horizontal pair:", 0); Dialog.addNumber("Enter y offset for horizontal pair:", 0); Dialog.addNumber("Enter x offset for vertical pair:", 0); Dialog.addNumber("Enter y offset for vertival pair:", 0); Dialog.addMessage("Open TILES folder separately and figure out size of the mosaic size"); Dialog.addNumber("How many rows?", 0); Dialog.addNumber("How many columns?", 0); Dialog.addCheckbox("Annotate RGB mosaic with its filename", true); Dialog.addNumber("Length of scale bar in um (none if 0):", 0); Dialog.show(); ohx=Dialog.getNumber(); ohy=Dialog.getNumber(); ovx=Dialog.getNumber(); ovy=Dialog.getNumber(); rws=Dialog.getNumber(); cls=Dialog.getNumber(); lbl=Dialog.getCheckbox(); scbr=Dialog.getNumber(); print("horizontal offset (x,y): ", ohx, ohy); print("vertical offset (x,y): ", ovx, ovy); print("mosaic: ", rws, " rows x ",cls, " columns"); print("filename imprinted onto RGB mosaic:", lbl); if(scbr>0) {print("scale bar:", scbr, "um"); } if(srt==0) {myDir11 = myDir4+"row_1_bottom"+File.separator; File.makeDirectory(myDir11); myDir12 = myDir4+"row_2"+File.separator; File.makeDirectory(myDir12); myDir13 = myDir4+"row_3"+File.separator; File.makeDirectory(myDir13); myDir14 = myDir4+"row_4"+File.separator; File.makeDirectory(myDir14); myDir15 = myDir4+"row_5"+File.separator; File.makeDirectory(myDir15); myDir16 = myDir4+"row_6"+File.separator; File.makeDirectory(myDir16); myDir17 = myDir4+"row_7"+File.separator; File.makeDirectory(myDir17); myDir18 = myDir4+"row_8"+File.separator; File.makeDirectory(myDir18); myDir19 = myDir4+"row_9"+File.separator; File.makeDirectory(myDir19); myDir20 = myDir4+"row_10"+File.separator; File.makeDirectory(myDir20); Dialog.create("Sort tiles into rows"); Dialog.addMessage("Open TILES folder separately and manually sort tiles into corresponding subfolders,\nstarting from the bottom row (Create more folders if needed or delete some, if not !)"); Dialog.addMessage("Then, for each consecutive row (starting with the bottom one), repeat following steps:\n1) Select subfolder containing row's tiles\n2) Separately, open that subfolder for inspection\n (if needed, make changes to file numbers manually inside the folder to re-oreder them)\n3) Mark choices in the consecutive Dialog Box\n4) Click OK\nOnce loop completes, continue upwards with the next row, etc."); Dialog.show(); } if(srt==1) {waitForUser("Next, follow those steps for each row (starting with the bottom one):\n1) Select subfolder containing row's tiles\n2) Separately, open that subfolder for inspection\n (if needed, make changes to file numbers manually inside the folder to re-oreder them)\n3) Mark choices in the consecutive Dialog Box\n4) Click OK\nOnce loop completes, continue upwards with the next row, etc."); } if(pdd==0) {newImage("C1-x", "16-bit black", width, height, 1); run("Duplicate...", "title=C2-x"); run("Duplicate...", "title=C3-x"); run("Merge Channels...", "c1=C1-x c2=C2-x c3=C3-x create"); rename("extra_b"); run("Add...", "value=100"); run("Duplicate...", "title=extra_w duplicate"); run("Add...", "value=65435"); for (s = 0; s < rws; s++) {j=101+s; nxt=getDirectory("Select subfolder containing tiles of single row. Start with the bottom row and continue upwards"); Dialog.create("Row setup"); items=newArray("Yes", "No"); Dialog.addCheckbox("The tiles inside the folder are numbered as expected in the final mosaic, from left to right?", false); Dialog.addNumber("How many extra tiles are needed on the LEFT end of this row?", 0); Dialog.addNumber("How many extra tiles are needed on the RIGHT end of this row?", 0); itemc=newArray("White", "Black"); Dialog.addChoice("What color is preferred for extra tiles", itemc); Dialog.show(); ord=Dialog.getCheckbox(); lpd=Dialog.getNumber(); rpd=Dialog.getNumber(); bw=Dialog.getChoice(); print("tile_folder: ", nxt, " L-->R orientation? ", ord, " extra tiles on the left: ", lpd, " extra tiles on the right: ", rpd, " color: ", bw); if(bw=="White") {selectWindow("extra_w"); run("Duplicate...", "title=extra duplicate"); } else {selectWindow("extra_b"); run("Duplicate...", "title=extra duplicate"); } if(lpd>0) {selectWindow("extra"); for (i = 0; i < lpd; i++) {if(ord==0) {saveAs("Tiff", nxt+2000+i); rename("extra"); } else {saveAs("Tiff", nxt+1000+i); rename("extra"); } } } if(rpd>0) {selectWindow("extra"); for (i = 0; i < rpd; i++) {if(ord==0) {saveAs("Tiff", nxt+1000+i); rename("extra"); } else {saveAs("Tiff", nxt+2000+i); rename("extra"); } } } close("extra"); rowlist=getFileList(nxt); open(nxt+rowlist[0]); setBatchMode(true); if(ord==1) {run("Flip Horizontally"); } rename("R"); for (u = 1; u < rowlist.length; u++) {open(nxt+rowlist[u]); if(ord==1) {run("Flip Horizontally"); } rename("L"); run("Pairwise stitching", "first_image=R second_image=L fusion_method=[Linear Blending] fused_image=R<->L check_peaks=5 x=ohx y=ohy registration_channel_image_1=[Average all channels] registration_channel_image_2=[Average all channels]"); close("R"); close("L"); rename("R"); } if(ord==1) {run("Flip Horizontally"); } saveAs("Tiff", myDir5+"row_"+j); close(); setBatchMode(false); } close("extra_w"); close("extra_b"); } if(pdd==1) { for (s = 0; s < rws; s++) {j=101+s; nxt=getDirectory("Select subfolder containing tiles of single row. Start with the bottom row and continue upwards"); Dialog.create("Setup check"); Dialog.addCheckbox("Indicate if tiles inside the folder are numbered as expected in the final mosaic, from left to right?", false); Dialog.show(); ord=Dialog.getCheckbox(); print(" L-->R orientation? ", ord); rowlist=getFileList(nxt); open(nxt+rowlist[0]); setBatchMode(true); if(ord==1) {run("Flip Horizontally"); } rename("R"); for (u = 1; u < rowlist.length; u++) {open(nxt+rowlist[u]); if(ord==1) {run("Flip Horizontally"); } rename("L"); run("Pairwise stitching", "first_image=R second_image=L fusion_method=[Linear Blending] fused_image=R<->L check_peaks=5 x=ohx y=ohy registration_channel_image_1=[Average all channels] registration_channel_image_2=[Average all channels]"); close("R"); close("L"); rename("R"); } if(ord==1) {run("Flip Horizontally"); } saveAs("Tiff", myDir5+"row_"+j); close(); setBatchMode(false); } } tlist5=getFileList(myDir5); open(myDir5+tlist5[0]); rename("B"); for (g = 1; g < tlist5.length; g++) {open(myDir5+tlist5[g]); rename("T"); run("Pairwise stitching", "first_image=B second_image=T fusion_method=[Linear Blending] fused_image=B<->T check_peaks=5 x=ovx y=ovy registration_channel_image_1=[Average all channels] registration_channel_image_2=[Average all channels]"); close("B"); close("T"); rename("B"); } mos=getBoolean("Is assembly correct?", "Yes", "No - start again from sorted tiles (correct sorting/padding manually)"); print(mos); if(mos==0) {for (h=0; h0) {run("Scale Bar...", "width=scbr height=20 font=fntsz color=Black background=None location=[Upper Right] bold overlay"); run("Flatten"); } else {run("Duplicate...", " "); } rename(ttl); run("Images to Stack", "name=Stack title=[] use"); if(lbl==1) {run("Make Montage...", "columns=1 rows=1 first=2 last=2 scale=1 font=fntsz label"); } else {run("Make Montage...", "columns=1 rows=1 first=2 last=2 scale=1 font=fntsz"); } close("Stack"); saveAs("Tiff", output+ttl+"_RGB_mosaic"); if(mscs==1) {saveAs("Tiff", mosaics+ttl+"_RGB_mosaic"); } print("Gamma=", gmma); close(); for (h=0; h0) {print("scale bar:", scbr, "um"); } print(""); NAMES=getFileList(mosaics); for (i=0; i0) {run("Scale Bar...", "width=scbr height=20 font=fntsz color=Black background=None location=[Upper Right] bold overlay"); run("Flatten"); } else {run("Duplicate...", " "); } rename(ttl); run("Images to Stack", "name=Stack title=[] use"); if(lbl==1) {run("Make Montage...", "columns=1 rows=1 first=2 last=2 scale=1 font=fntsz label"); } else {run("Make Montage...", "columns=1 rows=1 first=2 last=2 scale=1 font=fntsz"); } close("Stack"); saveAs("Tiff", mosaics+ttl+"_RGB_mosaic"); close(); setBatchMode(false); print(""); } selectWindow("Log"); saveAs("Text", mosaics+"Log"+"_"+ttl+"_"+month+"_"+dayOfMonth+"_"+year+"_"+hour+"_"+minute+".txt"); close("Log"); }