001package org.biojava.bio.chromatogram.graphic; 002 003import org.biojava.bio.BioError; 004import org.biojava.bio.chromatogram.Chromatogram; 005import org.biojava.bio.chromatogram.ChromatogramTools; 006 007/** 008 * A {@link ChromatogramNonlinearScaler} that scales all the 009 * base calls in a chromatogram to the same width in pixels, 010 * optionally biasing the peak of the call to the center. 011 * 012 * @author Rhett Sutphin (<a href="http://genome.uiowa.edu/">UI CBCB</a>) 013 * @author Matthew Pocock 014 * @since 1.3 015 */ 016public class FixedBaseWidthScaler implements ChromatogramNonlinearScaler { 017 /** Set to true to get copious, cryptic debugging output on System.out */ 018 private static final boolean DEBUG = false; 019 020 private Chromatogram lastC; 021 private int lastSeqLength; 022 private final float baseWidth; 023 private final boolean centerPeaks; 024 private float[] scales; 025 026 /** 027 * Creates a new scaler that will scale bases to the specified width 028 * without attempting to center their peaks. 029 * @param width the desired call width in pixels 030 */ 031 public FixedBaseWidthScaler(float width) { 032 this(width, false); 033 } 034 035 /** 036 * Creates a new scaler that will scale bases to the specified width 037 * and may or may not bias the peaks to the center. 038 * @param width the desired call width in pixels 039 * @param centerPeaks if true, the scaler will try to put the peak of 040 * in the center of the scaled call. Otherwise, the whole call 041 * will be scaled using the same factor. 042 */ 043 public FixedBaseWidthScaler(float width, boolean centerPeaks) { 044 baseWidth = width; 045 this.centerPeaks = centerPeaks; 046 lastC = null; 047 scales = null; 048 lastSeqLength = -1; 049 } 050 051 public float scale(Chromatogram c, int traceSampleIndex) throws IndexOutOfBoundsException { 052 if (traceSampleIndex < 0 || traceSampleIndex >= c.getTraceLength()) 053 throw new IndexOutOfBoundsException("Requested a scale of a trace sample outside of the chromatogram"); 054 synchronized (this) { 055 calcAllScales(c); 056 if (traceSampleIndex >= scales.length) 057 throw new BioError("This shouldn't happen: a valid trace sample is not included in the calculated scales array. This is probably due to a failure in dirty checking."); 058 return scales[traceSampleIndex]; 059 } 060 } 061 062 /** 063 * Calculates all the scaled x-coordinates for the given chromatogram, 064 * but only if it isn't the one that we already have the scales for. 065 * 066 * @param c the Chromatogram to calculate the scale for 067 */ 068 private synchronized void calcAllScales(Chromatogram c) { 069 if (c == null) return; 070 if (scales != null 071 && lastC != null && lastC == c 072 && scales.length == c.getTraceLength() 073 && lastSeqLength == c.getSequenceLength()) 074 return; 075 if (scales == null || scales.length != c.getTraceLength()) 076 scales = new float[c.getTraceLength()]; 077 if (DEBUG) System.out.println("bw=" + baseWidth + " cp="+centerPeaks); 078 int[] peaks = ChromatogramTools.getTraceOffsetArray(c); 079 int left = 0; 080 int center = peaks[0]; 081 int right = peaks[0] + (int) Math.floor( ((double) (peaks[1] - peaks[0])) / 2 ); 082 if (DEBUG) System.out.print("b=1 "); 083 scaleBase(left, center, right, 0.0f, false); 084 for (int i = 1 ; i < peaks.length - 1 ; i++) { 085 left = right + 1; 086 center = peaks[i]; 087 right = peaks[i] + (int) Math.floor( ((double) (peaks[i+1] - peaks[i])) / 2 ); 088 if (DEBUG) System.out.print("b="+(i+1)+" "); 089 scaleBase(left, center, right, i * baseWidth, false); 090 } 091 left = right + 1; 092 center = peaks[peaks.length - 1]; 093 right = c.getTraceLength() - 1; 094 if (DEBUG) System.out.print("b="+(peaks.length)+" "); 095 scaleBase(left, center, right, (peaks.length - 1) * baseWidth, true); 096 097 lastC = c; 098 lastSeqLength = c.getSequenceLength(); 099 } 100 101 /** 102 * Calculate the scaled x coordinates for a base call. The 103 * base call consists of the samples in the range <code>[left, right]</code> 104 * and the peak is at <code>center</code> (which must be in that same range). 105 * The parameter <code>widthSoFar</code> provides the x coordinate for 106 * the sample at <code>left</code> and this method must return a similar 107 * value for the base call immediately following this one (i.e., the one 108 * whose leftmost sample would be <code>right+1</code>). 109 * @param left the leftmost sample index of the base (inclusive) 110 * @param center the peak sample index 111 * @param right the rightmost sample index of the base (inclusive) 112 * @param nextX the total width of all the scaled bases up to this one 113 * @param lastBase true if this is the last base, false otherwise. The 114 * last base must be handled specially so that the last sample is 115 * at baseWidth * c.sequenceLength. 116 */ 117 private void scaleBase(int left, int center, int right, float nextX, boolean lastBase) { 118 if (left > center) throw new BioError("Assertion failure: left > center ; l="+left+"; c="+center+"; r="+right); 119 if (center > right) throw new BioError("Assertion failure: center > right ; l="+left+"; c="+center+"; r="+right); 120 if (left < 0) throw new BioError("Assertion failure: left < 0 ; l="+left+"; c="+center+"; r="+right); 121 if (center < 0) throw new BioError("Assertion failure: center < 0 ; l="+left+"; c="+center+"; r="+right); 122 if (right < 0) throw new BioError("Assertion failure: right < 0 ; l="+left+"; c="+center+"; r="+right); 123 if (DEBUG) System.out.println("l="+left+" c="+center+" r="+right+" nX="+nextX); 124 if (centerPeaks) { 125 float leftIncrement = (baseWidth * 0.5f) / (center - left); 126 if (DEBUG) System.out.println(" lside ("+leftIncrement+"): "); 127 for (int i = left ; i < center ; i++) { 128 scales[i] = nextX; 129 if (DEBUG) System.out.println(" s["+i+"]="+scales[i]); 130 nextX += leftIncrement; 131 } 132 float rightIncrement = (baseWidth * 0.5f) / (right - center + (lastBase?0:1)); 133 if (DEBUG) System.out.println(" rside ("+rightIncrement+"): "); 134 for (int i = center ; i <= right ; i++) { 135 scales[i] = nextX; 136 if (DEBUG) System.out.println(" s["+i+"]="+scales[i]); 137 nextX += rightIncrement; 138 } 139 } 140 else { 141 float increment = baseWidth / (right - left + (lastBase?0:1)); 142 if (DEBUG) System.out.println(" noside ("+increment+"): "); 143 for (int i = left ; i <= right ; i++) { 144 scales[i] = nextX; 145 if (DEBUG) System.out.println(" "+scales[i]); 146 nextX += increment; 147 } 148 } 149 } 150}