/* * omeis.providers.re.codomain.CodomainChain * * Copyright 2006 University of Dundee. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package omeis.providers.re.codomain; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import omeis.providers.re.quantum.QuantumStrategy; /** * Queues the contexts that define the spatial domain transformations that have * to be applied to the image. * <p> * A lookup table is built by composing all maps (in the same order as their * contexts were enqueued) in a single tranformation and then by applying this * map to each value in the codomain interval * <code>[intervalStart, intervalEnd]</code> — note that, in order to * compose the maps, this interval has to be both the domain and codomain of * each transformation. The LUT is re-built everytime the definition of the * codomain interval or the state of the queue changes. * </p> * <p> * Contexts are privately owned ({@link #add(CodomainMapContext) add} and * {@link #update(CodomainMapContext) update} make copies) because we want to * exclude the possibility that a context's state can be modified after the * lookup table is built. * </p> * * @author Jean-Marie Burel      <a * href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author <br> * Andrea Falconi      <a * href="mailto:a.falconi@dundee.ac.uk"> a.falconi@dundee.ac.uk</a> * @version 2.2 <small> (<b>Internal version:</b> $Revision$ $Date: * 2005/06/20 10:59:54 $) </small> * @since OME2.2 */ public class CodomainChain { /** * Identity map. This is a singleton and is always the first map in a * codomain chain. */ private static CodomainMapContext identityCtx; /** Codomain lookup table. */ private int[] LUT; /** * A queue to sequence the context of each codomain transformation that has * to be applied. */ private List chain; /** The lower bound of the codomain interval. */ private int intervalStart; /** The upper bound of the codomain interval. */ private int intervalEnd; /** Builds the lookup table. */ private void buildLUT() { LUT = new int[intervalEnd - intervalStart + 1]; CodomainMap map; CodomainMapContext ctx; int v; Iterator i; for (int x = intervalStart; x <= intervalEnd; ++x) { v = x; i = chain.iterator(); while (i.hasNext()) { ctx = (CodomainMapContext) i.next(); map = ctx.getCodomainMap(); map.setContext(ctx); v = map.transform(v); } LUT[x - intervalStart] = v; } } /** * Verifies the bounds of the codomain interval. * * @param start * The lower bound of the interval. * @param end * The upper bound of the interval. * @throws IllegalArgumentException * If the value is not in the interval. */ private void verifyInterval(int start, int end) { if (start >= end) { throw new IllegalArgumentException(start + " cannot be greater than " + end); } if (start < QuantumStrategy.MIN) { throw new IllegalArgumentException(start + " cannot be lower than " + QuantumStrategy.MIN); } if (end > QuantumStrategy.MAX) { throw new IllegalArgumentException(end + " cannot be greater than " + QuantumStrategy.MAX); } } /** * Verifies that the specifed input value is in the codomain interval. If * the value is less (resp. greater) than the {@link #intervalStart} (resp. * {@link #intervalEnd}), the input value is set to {@link #intervalStart} * (resp. {@link #intervalEnd}) * * @param x * The Input value. * @return See above. */ private int verifyInput(int x) { if (x < intervalStart) { x = intervalStart; } else if (x > intervalEnd) { x = intervalEnd; } return x; } /** * Creates a new chain. The chain will contain the identity context. So if * no transformation is added, the {@link #transform(int) transform} method * returns the input value. The interval defined by <code>start</code> and * <code>end</code> must be a sub-interval of * <code>[{@link QuantumStrategy#MIN}, {@link QuantumStrategy#MAX}]</code>. * * @param start * The lower bound of the codomain interval. * @param end * The upper bound of the codomain interval. */ public CodomainChain(int start, int end) { this(start, end, null); } /** * Creates a new chain. The chain will contain the specified contexts — * if <code>mapContexts</code> is <code>null</code> or empty, the chain * will contain only the identity context. The interval defined by * <code>start</code> and <code>end</code> has to be a sub-interval of * <code>[{@link QuantumStrategy#MIN}, {@link QuantumStrategy#MAX}]</code>. * * @param start * The lower bound of the codomain interval. * @param end * The upper bound of the codomain interval. * @param mapContexts * The sequence of {@link CodomainMapContext} objects that define * the chain. No two objects of the same class are allowed. The * objects in this list are copied. * @throws IllegalArgumentException * If one of the contexts is already defined. */ public CodomainChain(int start, int end, List mapContexts) { chain = new ArrayList(); if (identityCtx == null) { identityCtx = new IdentityMapContext(); } if (mapContexts != null && 0 < mapContexts.size()) { Iterator i = mapContexts.iterator(); CodomainMapContext ctx; while (i.hasNext()) { ctx = (CodomainMapContext) i.next(); if (chain.contains(ctx)) { throw new IllegalArgumentException( "Context already defined."); } ctx = ctx.copy(); chain.add(ctx); } } else { chain.add(identityCtx); } setInterval(start, end); } /** * Sets the codomain interval. This triggers an update of all map contexts * in the chain and a re-build of the lookup table. The interval defined by * <code>start</code> and <code>end</code> must be a sub-interval of * <code>[{@link QuantumStrategy#MIN}, {@link QuantumStrategy#MAX}]</code>. * * @param start * The lower bound of the codomain interval. * @param end * The upper bound of the codomain interval. */ public void setInterval(int start, int end) { verifyInterval(start, end); intervalStart = start; intervalEnd = end; CodomainMapContext ctx; Iterator i = chain.iterator(); while (i.hasNext()) { ctx = (CodomainMapContext) i.next(); ctx.setCodomain(start, end); ctx.buildContext(); } buildLUT(); } /** * Returns the upper bound of the codomain interval. * * @return See above. */ public int getIntervalEnd() { return intervalEnd; } /** * Returns the lower bound of the codomain interval. * * @return See above. */ public int getIntervalStart() { return intervalStart; } /** * Removes all {@link CodomainMapContext}s except the identity and resets * the interval. */ public void remove() { intervalStart = QuantumStrategy.MIN; intervalEnd = QuantumStrategy.MAX; chain.removeAll(chain); chain.add(identityCtx); buildLUT(); } /** * Adds a map context to the chain. This means that the transformation * associated to the passed context will be applied after all the currently * queued transformations. An exception will be thrown if the chain already * contains an object of the same class as <code>mapCtx</code>. This is * because we don't want to compose the same transformation twice. This * method adds a copy of <code>mapCtx</code> to the chain. This is because * we want to exclude the possibility that the context's state can be * modified after the lookup table is built. This method triggers a re-build * of the lookup table. * * @param mapCtx * The context to add. Mustn't be <code>null</code>. * @throws IllegalArgumentException * If the context is already defined. */ public void add(CodomainMapContext mapCtx) { if (mapCtx == null) { throw new NullPointerException("No context."); } if (chain.contains(mapCtx)) { throw new IllegalArgumentException("Context already defined."); } mapCtx = mapCtx.copy(); // Get memento and discard original object. mapCtx.setCodomain(intervalStart, intervalEnd); mapCtx.buildContext(); chain.add(mapCtx); buildLUT(); } /** * Updates a map context in the chain. An exception will be thrown if the * chain doesn't contain an object of the same class as <code>mapCtx</code>. * This method replaces the old context with a copy of <code>mapCtx</code>. * This is because we want to exclude the possibility that the context's * state can be modified after the lookup table is built. This method * triggers a re-build of the lookup table. * * @param mapCtx * The context to add. Mustn't be <code>null</code> and already * contained in the chain. * @throws IllegalArgumentException * If the specifed context doesn't exist. */ public void update(CodomainMapContext mapCtx) { if (mapCtx == null) { throw new NullPointerException("No context."); } int i = chain.indexOf(mapCtx); // Recall equals() is overridden. if (i == -1) { throw new IllegalArgumentException("No such a context."); } mapCtx = mapCtx.copy(); // Get memento and discard original object. mapCtx.setCodomain(intervalStart, intervalEnd); mapCtx.buildContext(); chain.set(i, mapCtx); buildLUT(); } /** * Removes a map context from the chain. This method removes the object (if * any) in the chain that is an instance of the same class as * <code>mapCtx</code>. This means that the transformation associated to * the passed context won't be applied. This method triggers a re-build of * the lookup table. * * @param mapCtx * The context to remove. */ public void remove(CodomainMapContext mapCtx) { if (mapCtx != null && chain.contains(mapCtx)) { // Recall equals() is // overridden. chain.remove(mapCtx); buildLUT(); } } /** * Applies the transformation. This transformation is the result of the * composition of all maps defined by the current chain. Composition follows * the chain order. * * @param x * The input value. Must be in the current codomain interval. * @return The output value, y. */ public int transform(int x) { int y = verifyInput(x); return LUT[y - intervalStart]; } /** * Overrides the toString method. * * @see Object#toString() */ @Override public String toString() { StringBuffer buf = new StringBuffer(); Iterator i = chain.iterator(); CodomainMapContext mapCtx; int n = chain.size(); int j = 1; while (i.hasNext()) { mapCtx = (CodomainMapContext) i.next(); buf.append(mapCtx.getCodomainMap()); if (j == n) { buf.append("."); } else { buf.append(", "); } j++; } return buf.toString(); } }