
boofcv.app.BaseFiducialSquare Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of applications Show documentation
Show all versions of applications Show documentation
BoofCV is an open source Java library for real-time computer vision and robotics applications.
/*
* Copyright (c) 2011-2016, Peter Abeles. All Rights Reserved.
*
* This file is part of BoofCV (http://boofcv.org).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package boofcv.app;
import boofcv.struct.image.GrayU8;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
/**
*
* Base class for generating square fiducials postscript documents for printing. Fiducials are placed in a regular grid.
* The width of each element in the grid is the fiducial's width (pattern + black border) and a white border. The
* grid starts in the page's lower left and corner.
*
*
*
* Border: The border is a no-go zone where the fiducial can't be printed inside of. This is only taken in account
* when automatic centering or layout of the grid on the page is requested.
* Offset: Where the fiducial is offset inside the page. Always used. If centering is requested then the offset
* is automatically computed and any user provided value ignored.
* PrintInfo: If true it will draw a string above the fiducial with the fiducial's name and it's size
*
*
* @author Peter Abeles
*/
public abstract class BaseFiducialSquare {
// threshold for converting to a binary image
public int threshold = 255/2;
public double UNIT_TO_POINTS;
public static final double CM_TO_POINTS = 72.0/2.54;
// should it add the file name and size to the document?
public boolean printInfo = true;
public boolean showPreview = false;
// base unit of input lengths
Unit unit = Unit.CENTIMETER;
// if true it will select the size of the grid
boolean autofillGrid = false;
// Request has been made to automatically center the fiducial in the page
boolean centerRequested = true;
// should it print the grid
boolean printGrid = false;
// Number of elements in the grid
public int numCols = 1;
public int numRows = 1;
// draw an invisible border around the document.
boolean boundaryHack = true;
//============= These Parameters specify how it's printed
// Total length of the target. image + black border
double fiducialBoxWidth;
// length of the white border surrounding the fiducial
double whiteBorder;
// length of the black border
double blackBorder;
// length of the inner pattern
double innerWidth ;
// Length of the fiducial plus the white border
double fiducialTotalWidth;
// width and height of the page
double pageWidth,pageHeight;
// offset of the page. basically changes the origin
double offsetX, offsetY;
//============= These parameters are used to automatically generate some of the above parameters
// No printing is allowed inside the border and this information is used to adjust offsets with the user request
double pageBorderX = 1*CM_TO_POINTS;
double pageBorderY = 1*CM_TO_POINTS;
// how wide the fiducial's black border is relative to its total width
public double blackBorderFractionalWidth;
// name of the output file
String outputFileName;
// stream in which the output file is written to
PrintStream out;
// file type extension
String typeExtension = "ps";
public Unit getUnit() {
return unit;
}
public void setUnit(Unit unit) {
this.unit = unit;
}
public void setCentering(boolean centerFiducial) {
this.centerRequested = centerFiducial;
}
public boolean isPrintGrid() {
return printGrid;
}
public void setPrintGrid(boolean printGrid) {
this.printGrid = printGrid;
}
public boolean isPrintInfo() {
return printInfo;
}
public void setPrintInfo(boolean displayInfo) {
this.printInfo = displayInfo;
}
public boolean isShowPreview() {
return showPreview;
}
public void setShowPreview(boolean showPreview) {
this.showPreview = showPreview;
}
public void setPageBorder( double borderX , double borderY , Unit units) {
pageBorderX = units.convert(borderX,Unit.CENTIMETER)*CM_TO_POINTS;
pageBorderY = units.convert(borderY,Unit.CENTIMETER)*CM_TO_POINTS;
}
public void setOffset( double offsetX , double offsetY , Unit units) {
this.offsetX = units.convert(offsetX,Unit.CENTIMETER)*CM_TO_POINTS;
this.offsetY = units.convert(offsetY,Unit.CENTIMETER)*CM_TO_POINTS;
}
public void setBlackBorderFractionalWidth(double blackBorderFractionalWidth) {
this.blackBorderFractionalWidth = blackBorderFractionalWidth;
}
public void setBoundaryHack(boolean boundaryHack) {
this.boundaryHack = boundaryHack;
}
public void setOutputFileName(String outputFileName) {
this.outputFileName = outputFileName;
}
public void generateGrid( double fiducialWidth , double whiteBorder , int numCols , int numRows , PaperSize paper )
throws IOException
{
double pageWidthUnit, pageHeightUnit;
if( paper != null ) {
pageWidthUnit = paper.getUnit().convert(paper.getWidth(), unit);
pageHeightUnit = paper.getUnit().convert(paper.getHeight(), unit);
} else {
pageWidthUnit = -1;
pageHeightUnit = -1;
}
this.numRows = numRows;
this.numCols = numCols;
if( numRows < 1 || numCols < 1 )
this.autofillGrid = true;
if( whiteBorder < 0 ) {
double cm2 = Unit.CENTIMETER.convert(2.0,unit);
whiteBorder = Math.max(cm2, fiducialWidth / 4.0);
}
generate(fiducialWidth, whiteBorder, pageWidthUnit, pageHeightUnit);
}
/**
* Configures and saves a PDF of the fiducial.
*
* @param fiducialWidthUnit Width of the fiducial
* @param whiteBorderUnit Thickness of the border around the fiducial
* @param pageWidthUnit Width of the document. If ≤ 0 the width will be automatically selected
* @param pageHeightUnit Height of the document. If ≤ 0 the height will be automatically selected
* @throws IOException
*/
private void generate(double fiducialWidthUnit, double whiteBorderUnit,
double pageWidthUnit, double pageHeightUnit) throws IOException {
String outputName;
if( this.outputFileName == null ) {
outputName = defaultOutputFileName();
} else {
outputName = this.outputFileName;
}
String imageName = selectDocumentName();
configureDocument(fiducialWidthUnit, whiteBorderUnit, pageWidthUnit, pageHeightUnit);
// print out the selected number in binary for debugging purposes
out = new PrintStream(outputName);
generateDocument(fiducialWidthUnit, imageName);
System.out.println("Saved to "+new File(outputName).getAbsolutePath());
}
/**
* Compute how to build the documetn and setup all parameters
*/
private void configureDocument(double fiducialWidthUnit, double whiteBorderUnit,
double pageWidthUnit, double pageHeightUnit ) throws FileNotFoundException
{
UNIT_TO_POINTS = 72.0/(2.54* Unit.conversion(Unit.CENTIMETER, unit));
if( autofillGrid ) {
if( pageWidthUnit <= 0 || pageHeightUnit <= 0)
throw new IllegalArgumentException("If autofillGrid is turned on then the page size must be specified");
autoSelectGridSize(fiducialWidthUnit, whiteBorderUnit, pageWidthUnit, pageHeightUnit);
}
System.out.println("Fiducial width "+ fiducialWidthUnit +" ("+unit.abbreviation+")");
fiducialBoxWidth = fiducialWidthUnit* UNIT_TO_POINTS;
whiteBorder = whiteBorderUnit* UNIT_TO_POINTS;
blackBorder = fiducialBoxWidth*blackBorderFractionalWidth;
innerWidth = fiducialBoxWidth*(1.0-2.0*blackBorderFractionalWidth);
fiducialTotalWidth = fiducialBoxWidth +whiteBorder*2;
// zone in which the target can't be placed unless it will print inside the border, which is not allowed
double deadZoneX = Math.max(0,pageBorderX-whiteBorder);
double deadZoneY = Math.max(0,pageBorderY-whiteBorder);
if( pageWidthUnit <= 0 ) {
pageWidth = fiducialTotalWidth*numCols + 2*deadZoneX;
} else {
pageWidth = pageWidthUnit*UNIT_TO_POINTS;
}
if( pageHeightUnit <= 0 ) {
pageHeight = fiducialTotalWidth*numRows + 2*deadZoneY;
} else {
pageHeight = pageHeightUnit*UNIT_TO_POINTS;
}
// Center the fiducial inside the page. Reduce the size of the grid if required to fit it inside the
// page, including the border
if( centerRequested ) {
centerPage();
}
}
/**
* Centers the image while taking in account page border where it can't print. Reduces the number of elements in
* the grid if necessary.
*/
private void centerPage() {
double validX0 = pageBorderX;
double validX1 = pageWidth-pageBorderX;
double validY0 = pageBorderY;
double validY1 = pageHeight-pageBorderY;
boolean allGood = false;
while( numCols > 0 && numRows > 0 && !allGood) {
allGood = true;
// center the current target inside the page
offsetX = (pageWidth - fiducialTotalWidth * numCols ) / 2.0;
offsetY = (pageHeight - fiducialTotalWidth * numRows ) / 2.0;
// Find the edges which bound the regions where printing is done
double edgeBlackLeft = offsetX+whiteBorder;
double edgeBlackRight = offsetX+ fiducialTotalWidth *numCols-whiteBorder;
double edgeBlackBottom = offsetY+whiteBorder;
double edgeBlackTop = offsetY+ fiducialTotalWidth *numRows-whiteBorder;
if( edgeBlackLeft+1e-8 < validX0 ||edgeBlackRight-1e-8 > validX1 ) {
allGood = false;
numCols--;
}
if( edgeBlackBottom+1e-8 < validY0 ||edgeBlackTop-1e-8 > validY1 ) {
allGood = false;
numRows--;
}
}
if( numCols == 0 || numRows == 0 )
throw new IllegalArgumentException("Can't place fiducial inside the page and not go outside the page border");
}
/**
* Given the page size and other parameters figure out how many fiducials it can fit along the rows and columns
*/
private void autoSelectGridSize(double fiducialWidthUnit, double whiteBorderUnit, double pageWidthUnit, double pageHeightUnit) {
// find how far away from the page's edge does it need to stay
double pageBorderUnitX = Math.max(pageBorderX/UNIT_TO_POINTS , whiteBorderUnit);
double pageBorderUnitY = Math.max(pageBorderY/UNIT_TO_POINTS , whiteBorderUnit);
// area of fiducial and white border
double totalWidthUnit = fiducialWidthUnit+2*whiteBorderUnit;
// compute how much of the page it can use
double effectiveX = pageWidthUnit - 2*pageBorderUnitX + 2*whiteBorderUnit;
double effectiveY = pageHeightUnit - 2*pageBorderUnitY + 2*whiteBorderUnit;
this.numCols = (int)Math.floor(effectiveX/totalWidthUnit);
this.numRows = (int)Math.floor(effectiveY/totalWidthUnit);
}
/**
* Creates an EPS document from all the specifications in the class
* @param fiducialWidthUnit Width of fiducial (including border) in user specified units.
* @param documentTitle Title of the document
*/
private void generateDocument(double fiducialWidthUnit, String documentTitle) {
int patternsPerPage = numRows*numCols;
int numPages = (int)Math.ceil(totalPatterns()/(double)patternsPerPage);
printHeader(documentTitle, fiducialWidthUnit, numPages);
printPatternDefinitions();
if (printInfo) {
for (int pattern = 0; pattern < totalPatterns(); pattern++) {
String patternName = getPatternName(pattern);
out.print(" /" + getDisplayDef(pattern) + "\n" +
"{\n" +
" /Times-Roman findfont\n" + "7 scalefont setfont b1 " + (fiducialTotalWidth - 10) +
" moveto (" + patternName + " " + fiducialWidthUnit + " " + unit.abbreviation + ") show\n" +
"} def\n");
}
}
for (int i = 0; i < numPages; i++) {
printPageHeader(i+1);
if(boundaryHack)
printInvisibleBoundary();
int startPattern = i*patternsPerPage;
// draws the black border around the fiducial
out.print(" /drawBorder\n"+
"{\n" +
" newpath b0 b0 moveto 0 ow rlineto bb 0 rlineto 0 -1 ow mul rlineto closepath fill\n" +
" newpath b1 b2 moveto iw 0 rlineto 0 bb rlineto -1 iw mul 0 rlineto closepath fill\n" +
" newpath b1 b0 moveto iw 0 rlineto 0 bb rlineto -1 iw mul 0 rlineto closepath fill\n" +
" newpath b2 b0 moveto 0 ow rlineto bb 0 rlineto 0 -1 ow mul rlineto closepath fill\n" +
"} def\n");
for (int row = 0; row < numRows; row++) {
for (int col = 0; col < numCols; col++) {
insertFiducial(startPattern,row,col);
}
}
if( printGrid )
printGrid();
out.print(" showpage\n");
}
out.print("%%EOF\n");
}
/**
* Creates definitions which will render the pattern. Each pattern's difintion will have the name returned
* by {@link #getPatternPrintDef(int)}
*/
protected abstract void printPatternDefinitions();
/**
* Returns the total number of unqiue patterns
*/
protected abstract int totalPatterns();
protected abstract void addPattern( String name );
/**
* Human readable pattern name. Will be printed on document
*/
protected abstract String getPatternName( int num );
protected String getPatternPrintDef(int num) {
return String.format("drawImage%03d",num);
}
protected String getDisplayDef(int num) {
return String.format("displayInfo%03d",num);
}
private void printHeader( String documentTitle , double widthCM, int totalPages) {
out.println("%!PS-Adobe-3.0\n" +
"%%Creator: BoofCV\n" +
"%%DocumentMedia: Plain "+pageWidth+" "+pageHeight+" 75 white ( )\n" +
"%%Title: "+documentTitle+" w="+ widthCM +" "+unit.abbreviation+"\n" +
"%%DocumentData: Clean7Bit\n" +
"%%LanguageLevel: 2\n" +
"%%EndComments\n" +
"%%BeginProlog\n" +
"%%EndProlog\n" +
"%%Pages: "+totalPages+"\n" +
" /iw " + innerWidth + " def\n" +
" /ow " + (innerWidth + 2 * blackBorder) + " def\n" +
" /wb " + whiteBorder + " def\n" +
" /bb " + blackBorder + " def\n" +
" /b0 wb def\n" +
" /b1 { wb bb add} def\n" +
" /b2 { b1 " + innerWidth + " add} def\n" +
" /b3 { b2 bb add} def\n"
);
}
private void printPageHeader( int pageNumber ) {
out.println("%%Page: "+pageNumber+" "+pageNumber+"\n");
}
/**
* Prints an invisible boundary around the document to prevent a smart cropping script from cropping the white space
*/
private void printInvisibleBoundary() {
out.println(" 1.0 setgray");
out.printf(" newpath 0 0 moveto %f 0 rlineto 0 setlinewidth stroke\n", pageWidth);
out.printf(" newpath 0 0 moveto 0 %f lineto 0 setlinewidth stroke\n", pageHeight);
out.printf(" newpath %f 0 moveto %f %f lineto 0 setlinewidth stroke\n", pageWidth, pageWidth, pageHeight);
out.printf(" newpath 0 %f moveto %f %f lineto 0 setlinewidth stroke\n", pageHeight, pageWidth, pageHeight);
out.println(" 0.0 setgray");
out.println();
}
/**
* Draws the grid in light grey on the document
*/
private void printGrid() {
out.println("% grid lines");
out.print(" /drawRow { moveto "+pageWidth+" 0 rlineto 1 setlinewidth stroke} def\n");
out.print(" /drawColumn { moveto 0 "+pageHeight+" rlineto 1 setlinewidth stroke} def\n");
out.print(" 0.75 setgray\n");
for (int i = 0; i <= numCols; i++) {
double x = offsetX + i*fiducialTotalWidth;
out.printf(" newpath %f 0 drawColumn\n",x);
}
for (int i = 0; i <= numRows; i++) {
double y = offsetY + i*fiducialTotalWidth;
out.printf(" newpath 0 %f drawRow\n",y);
}
}
private void insertFiducial(int startPattern, int row, int col ) {
out.print(
" /originX " + (offsetX + col * fiducialTotalWidth) + " def\n" +
" /originY " + (offsetY + row * fiducialTotalWidth) + " def\n" +
" originX originY translate\n" );
out.println();
out.println(" drawBorder");
int imageNum = (startPattern+(row*numCols+col))%totalPatterns();
// print out encoding information for convenience
if(printInfo) {
out.println(" " + getDisplayDef(imageNum));
}
out.println("% Center then draw the image");
out.println(" b1 b1 translate");
out.println(" "+getPatternPrintDef(imageNum));
out.println("% Undo translations");
out.println(" -1 b1 mul -1 b1 mul translate");
out.println(" -1 originX mul -1 originY mul translate");
}
public static String binaryToHex( GrayU8 binary ) {
if( binary.width%8 != 0 )
throw new RuntimeException("Width must be divisible by 8");
StringBuilder s = new StringBuilder(binary.width*binary.height/4);
for (int y = binary.height-1; y >= 0 ; y--) {
int i = y*binary.width;
for (int x = 0; x < binary.width; x += 8, i+= 8) {
int value = 0;
for (int j = 0; j < 8; j++) {
value |= binary.data[i+j] << (7-j);
}
String hex = Integer.toHexString(value);
if( hex.length() == 1 )
hex = "0"+hex;
s.append(hex);
}
}
return s.toString();
}
public void setGrid( int numRows , int numCols ) {
this.numRows = numRows;
this.numCols = numCols;
}
/**
* Returns an automatically selected name/path for the output file
*/
public abstract String defaultOutputFileName();
/**
* Name of the image which will go into the document.
*/
public abstract String selectDocumentName();
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy