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 ******************************************************************************/ 009 010package imagingbook.common.sift.scalespace; 011 012import ij.ImagePlus; 013import ij.ImageStack; 014import imagingbook.common.util.LinearContainer; 015import imagingbook.common.util.PrintsToStream; 016 017import java.io.PrintStream; 018 019/** 020 * <p> 021 * This abstract class defines a generic hierarchical scale space, consisting of multiple "octaves". See Sec. 25.1.4. of 022 * [1] for details. 023 * </p> 024 * <p> 025 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing – An Algorithmic Introduction</em>, 3rd ed, Springer 026 * (2022). 027 * </p> 028 * 029 * @param <OctaveT> the octave type 030 * @author WB 031 * @version 2022/11/20 032 * @see ScaleLevel 033 * @see ScaleOctave 034 * @see GaussianScaleSpace 035 * @see DogScaleSpace 036 */ 037public abstract class HierarchicalScaleSpace<OctaveT extends ScaleOctave<?>> implements PrintsToStream { 038 039 final int P; // number of octaves 040 final int Q; // number of levels per octave 041 final double sigma_s; // absolute scale of original image 042 final double sigma_0; // absolute base scale of first octave (level 0,0) 043 final int botLevel, topLevel; // bottom and top level index in each octave 044 final LinearContainer<OctaveT> octaves; // array of scale space octaves 0,...,P-1 045 046 /** 047 * Constructor (non-public). 048 * 049 * @param P the number of scale space octaves 050 * @param Q the number of scale steps (levels) per octave 051 * @param sigma_s the assumed sampling scale (typ. 0.5) 052 * @param sigma_0 the base scale of level 0 053 * @param botLevel the index of the bottom level in each octave 054 * @param topLevel the index of the to level in each octave 055 */ 056 HierarchicalScaleSpace(int P, int Q, double sigma_s, double sigma_0, int botLevel, int topLevel) { 057 this.Q = Q; 058 this.P = P; 059 this.sigma_s = sigma_s; 060 this.sigma_0 = sigma_0; 061 this.botLevel = botLevel; 062 this.topLevel = topLevel; 063 this.octaves = new LinearContainer<>(0, P-1); 064 } 065 066 /** 067 * Returns the number of octaves in this scale space. 068 * @return the number of octaves 069 */ 070 public int getP() { 071 return P; 072 } 073 074 /** 075 * Returns the number of scale levels in each octave of this scale space. 076 * @return the number of scale levels 077 */ 078 public int getQ() { 079 return Q; 080 } 081 082 /** 083 * Returns the assumed sampling scale. 084 * @return the assumed sampling scale 085 */ 086 public double getSigma_s() { 087 return sigma_s; 088 } 089 090 /** 091 * Returns the base scale assigned to level 0 of octave 0. 092 * @return the base scale 093 */ 094 public double getSigma_0() { 095 return sigma_0; 096 } 097 098 /** 099 * Returns the bottom level index in each scale space octave (e.g., this is -1 for the Gaussian scale space used in 100 * SIFT). 101 * 102 * @return the bottom level index 103 */ 104 public int getBottomLevelIndex() { 105 return this.botLevel; 106 } 107 108 /** 109 * Returns the top level index in each scale space octave (e.g., this is Q+1 for the Gaussian scale space used in 110 * SIFT). 111 * 112 * @return the top level index 113 */ 114 public int getTopLevelIndex() { 115 return this.topLevel; 116 } 117 118 /** 119 * Returns a reference to the p-th octave in this scale space. Valid octave indexes are p = 0,..,P-1 (see 120 * {@link #getP()}). 121 * 122 * @param p the octave index 123 * @return the associated {@link ScaleOctave} instance 124 * @see #getP() 125 */ 126 public OctaveT getOctave(int p) { 127 //return octaves[p]; 128 return octaves.getElement(p); 129 } 130 131 // used internally only 132 void setOctave(int p, OctaveT oct) { 133// octaves[p] = oct; 134 octaves.setElement(p, oct); 135 } 136 137 /** 138 * Returns the q-th scale space level of octave p in this scale space. Valid octave indexes are p = 0,..,P-1 (see 139 * {@link #getP()}). 140 * 141 * @param p the octave index 142 * @param q the (within-octave) level index 143 * @return the associated {@link ScaleLevel} instance 144 */ 145 public ScaleLevel getScaleLevel(int p, int q) { 146 return getOctave(p).getLevel(q); 147 } 148 149 /** 150 * Returns the absolute scale (σ) at scale level p, q. 151 * 152 * @param p the octave index 153 * @param q the (within-octave) level index 154 * @return the absolute level scale 155 */ 156 public double getAbsoluteScale(int p, float q) { 157 double m = Q * p + q; 158 return sigma_0 * Math.pow(2, m/Q); 159 } 160 161 /** 162 * Calculates and returns the real (unscaled) x-position for a local coordinate at the specified octave. 163 * 164 * @param p the octave index 165 * @param xp the (scale-level) local coordinate 166 * @return the original x-position 167 */ 168 public double getRealX(int p, double xp) { 169 return Math.pow(2, p) * xp; // TODO: optimize (precalculate Math.pow(p, 2)) 170 } 171 172 /** 173 * Calculates and returns the real (unscaled) y-position for a local coordinate at the specified octave. 174 * 175 * @param p the octave index 176 * @param yp the scale-level) local coordinate 177 * @return the original y-position 178 */ 179 public double getRealY(int p, double yp) { 180 return Math.pow(2, p) * yp; 181 } 182 183 // ---------------------------------------------------------------- 184 185 @Override 186 public void printToStream(PrintStream strm) { 187 strm.println("Hierarchical Scale Space (" + this.getClass().getSimpleName() + ")"); 188 for (ScaleOctave<?> oct : octaves) { 189 oct.printToStream(strm); 190 strm.println(); 191 } 192 } 193 194 // ---------------------------------------------------------------- 195 196 /** 197 * Returns the contents of this scale space as an array of ImageJ ({@link ImagePlus}) images, one for each octave. 198 * Each image contains a stack of frames, one for each scale level. 199 * 200 * @param title a string used to compose the title of the images 201 * @return an array of {@link ImagePlus} instances. 202 */ 203 public ImagePlus[] getImages(String title) { 204 ImagePlus[] images = new ImagePlus[P]; 205 for (int p = 0; p < P; p++) { 206// ImageStack stk = octaves[p].getImageStack(); 207 ImageStack stk = octaves.getElement(p).getImageStack(); 208 images[p] = new ImagePlus(title + " Octave p=" + p, stk); 209 } 210 return images; 211 } 212 213}