001/******************************************************************************* 002 * This software is provided as a supplement to the authors' textbooks on digital 003 * image processing published by Springer-Verlag in various languages and editions. 004 * Permission to use and distribute this software is granted under the BSD 2-Clause 005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause). 006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved. 007 * Visit https://imagingbook.com for additional details. 008 ******************************************************************************/ 009package imagingbook.common.geometry.circle; 010 011import imagingbook.common.math.Arithmetic; 012 013import java.util.Locale; 014 015import static imagingbook.common.math.Arithmetic.isZero; 016import static imagingbook.common.math.Arithmetic.sqr; 017import static java.lang.Math.sqrt; 018 019/** 020 * <p> 021 * Represents an algebraic circle with four parameters A, B, C, D in the form A * (x^2 + y^2) + B * x + C * y + D = 0. 022 * Parameters are normalized such that B^2 + C^2 - 4 * A * D = 1 and A ≥ 0 Circle instances are immutable. See Sec. 023 * 11.1.1 and Appendix F.2.1 of [1] for details. 024 * </p> 025 * <p> 026 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing – An Algorithmic Introduction</em>, 3rd ed, Springer 027 * (2022). 028 * </p> 029 * 030 * @author WB 031 * @version 2022/11/17 032 */ 033public class AlgebraicCircle { 034 035 /** 036 * Circle parameter. 037 */ 038 public final double A, B, C, D; 039 040 /** 041 * Constructor. Creates a {@link AlgebraicCircle} instance whose parameters A, B, C, D are normalized such that B^2 042 * + C^2 - 4 * A * D = 1 and A ≥ 0. Throws an exception if d = B^2 + C^2 - 4 * A * D (the <em>discriminant</em>) 043 * is negative. 044 * 045 * @param A circle parameter A 046 * @param B circle parameter B 047 * @param C circle parameter C 048 * @param D circle parameter D 049 */ 050 public AlgebraicCircle(double A, double B, double C, double D) { 051 // discriminant (d) must be positive! 052 double d = sqr(B) + sqr(C) - 4 * A * D; 053 if (isZero(A) || d < Arithmetic.EPSILON_DOUBLE) { 054 throw new IllegalArgumentException("illegal circle parameters (zero A or non-positive discriminant)"); 055 } 056 // normalize parameters to (B^2 + C^2 - 4 * A * D) = 1 and A >= 0 057 double s = 1 / sqrt(d); 058 if (A >= 0) { 059 this.A = A * s; 060 this.B = B * s; 061 this.C = C * s; 062 this.D = D * s; 063 } 064 else { 065 this.A = -A * s; 066 this.B = -B * s; 067 this.C = -C * s; 068 this.D = -D * s; 069 } 070 } 071 072 /** 073 * Constructor. Creates a {@link AlgebraicCircle} instance from the specified parameter vector [A, B, C, D]. 074 * 075 * @param p algebraic circle parameters 076 */ 077 public AlgebraicCircle(double[] p) { 078 this(p[0], p[1], p[2], p[3]); 079 } 080 081 /** 082 * Constructor. Creates a {@link AlgebraicCircle} instance from a {@link GeometricCircle}. 083 * 084 * @param gc a {@link GeometricCircle} 085 */ 086 public AlgebraicCircle(GeometricCircle gc) { 087 this(getAlgebraicCircleParameters(gc)); 088 } 089 090 private static double[] getAlgebraicCircleParameters(GeometricCircle gc) { 091 double A = 1 / (2 * gc.r); 092 double B = -2 * A * gc.xc; 093 double C = -2 * A * gc.yc; 094 double D = (sqr(B) + sqr(C) - 1) / (4 * A); 095 return new double[] {A, B, C, D}; 096 } 097 098 /** 099 * Returns a vector of algebraic circle parameters (A, B, C, D). 100 * 101 * @return algebraic circle parameters (A, B, C, D) 102 */ 103 public double[] getParameters() { 104 return new double[] {A, B, C, D}; 105 } 106 107 @Override 108 public boolean equals(Object other) { 109 if (this == other) { 110 return true; 111 } 112 if (!(other instanceof AlgebraicCircle)) { 113 return false; 114 } 115 return this.equals((AlgebraicCircle) other, Arithmetic.EPSILON_DOUBLE); 116 } 117 118 /** 119 * Same as {@link #equals(Object)} but with a numeric tolerance on parameters. 120 * @param other some other circle 121 * @param tolerance numeric tolerance 122 * @return true is equal 123 */ 124 public boolean equals(AlgebraicCircle other, double tolerance) { 125 return 126 Arithmetic.equals(A, other.A, tolerance) && 127 Arithmetic.equals(B, other.B, tolerance) && 128 Arithmetic.equals(C, other.C, tolerance) && 129 Arithmetic.equals(D, other.D, tolerance) ; 130 } 131 132 /** 133 * Returns a copy of this circle. 134 * @return a copy of this circle 135 */ 136 public AlgebraicCircle duplicate() { 137 return new AlgebraicCircle(this.getParameters()); 138 } 139 140 141 @Override 142 public String toString() { 143 return String.format(Locale.US, "%s [A=%f, B=%f, C=%f, D=%f]", 144 AlgebraicCircle.class.getSimpleName(), A, B, C, D); 145 } 146 147}