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.mappings.nonlinear; 010 011import imagingbook.common.geometry.basic.Pnt2d; 012import imagingbook.common.geometry.mappings.Inversion; 013import imagingbook.common.geometry.mappings.Mapping2D; 014import imagingbook.common.math.Arithmetic; 015 016import static java.lang.Math.cos; 017import static java.lang.Math.exp; 018import static java.lang.Math.hypot; 019import static java.lang.Math.log; 020import static java.lang.Math.sin; 021 022/** 023 * <p> 024 * This class implements a 2D log-polar mapping transformation. Improved version (Version 2), maps radius [rmin,rmax] to 025 * [0,nr]). See Sec. 21.1.6 (Eq 21.71 - 21.74, Alg. 21.1) of [1] for additional details and examples. 026 * </p> 027 * <p> 028 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing – An Algorithmic Introduction</em>, 3rd ed, Springer 029 * (2022). 030 * </p> 031 * 032 * @author WB 033 * @version 2022/11/16 034 */ 035public class LogPolarMapping2 implements Mapping2D, Inversion { 036 037 private static final double PI2 = 2 * Math.PI; 038 039 private final double rmin; // min. radius 040 private final double xc, yc; // center point in source image 041 private final double c1, c2; // pre-calculated constants 042 043 /** 044 * Constructor. 045 * @param xc x-coordinate of center point in source image 046 * @param yc y-coordinate of center point in source image 047 * @param P number of radial steps 048 * @param Q number of angular steps 049 * @param rmax maximum radius 050 * @param rmin minimum radius 051 */ 052 public LogPolarMapping2(double xc, double yc, int P, int Q, double rmax, double rmin) { 053 this.xc = xc; 054 this.yc = yc; 055 this.rmin = (rmin > 0) ? rmin : rmax / exp(2 * Math.PI * (P - 1) / Q); 056 this.c1 = P / log(rmax / rmin); 057 this.c2 = Q / PI2; 058 } 059 060 // -------------------------------------------------------------------- 061 062 @Override 063 public Pnt2d applyTo(Pnt2d xy) { // image -> log-polar 064 double dx = xy.getX() - xc; 065 double dy = xy.getY() - yc; 066 double r = hypot(dx, dy); // = Math.sqrt(sqr(dx) + sqr(dy)); 067 if (r < rmin) { 068 throw new IllegalArgumentException("radius < rmin for xy = " + xy); // this should not happen! 069 } 070 double rho = c1 * log(r / rmin); 071 double theta = Arithmetic.mod(Math.atan2(dy, dx), PI2); // theta in [0, 2 PI) 072 double omega = c2 * theta; 073 return Pnt2d.from(rho, omega); 074 } 075 076 // -------------------------------------------------------------------- 077 078 @Override 079 public Mapping2D getInverse() { 080 return new Mapping2D() { 081 @Override 082 public Pnt2d applyTo(Pnt2d rw) { // log-polar -> image, rw = (rho, omega) 083 double rho = rw.getX(); 084 double omega = rw.getY(); 085 double r = exp(rho / c1) * rmin; 086 double theta = omega / c2; 087 double x = xc + r * cos(theta); 088 double y = yc + r * sin(theta); 089 return Pnt2d.from(x, y); 090 } 091 }; 092 } 093 094}