/* * Copyright 2011 Uwe Krueger. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.mandelsoft.mand.mapping; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import com.mandelsoft.mand.MandelRaster; /** * * @author Uwe Krueger */ public class StatisticMapper extends MapperSupport { static public boolean debug=false; static public boolean usebuilder=true; static public final int VERSION=1; private double factor; private double limit; public StatisticMapper() { this(0); } public StatisticMapper(double factor) { this(factor,1.0); } public StatisticMapper(double factor, double limit) { this.factor=factor; this.limit=limit; } public String getName() { return "Statistic"; } public String getParamDesc() { return "f="+factor+",l="+limit; } public double getFactor() { return factor; } public double getLimit() { return limit; } /////////////////////////////////////////////////////////////// // statistic /////////////////////////////////////////////////////////////// private static boolean hdeb=false; protected static interface Histogram { void add(int i); // add iteration value for new pixel int get(int i); // get number of pixels for given iteration value int getSize(); int histMax(); int mapIndexToValue(int i); // iteration value at index int mapIndexToCount(int i); // number of pixels at index } /////////////////////////////////////////////////////////////// // Tree Implementation protected static class TreeHistogram extends BalancedTreeSupport implements Histogram { class Node extends TreeNode<Node> { int index; int value; int cnt; Node(int i, int c) { value=i; cnt=c; if (hdeb) System.out.println("CREATE "+i); indexed=false; } Node(int i) { this(i,1); } int createIndex(int index) { if (right!=null) index=right.createIndex(index); this.index=index++; if (left!=null) index=left.createIndex(index); return index; } @Override public String toString() { return ""+value+"("+cnt+")"; } } private int size; private int max; private boolean indexed; public TreeHistogram(int size) { this.size=size; this.root=new Node(size-1,0); } public void add(int i) { if (hdeb) System.out.println("add "+i); root=add((Node)root,i); } private Node add(Node n, int i) { if (n==null) { if (max==0) max=1; return new Node(i); } if (hdeb) System.out.println(" handle "+n.value); if (i==n.value) { n.cnt++; if (n.cnt>max) max=n.cnt; } else { if (i>n.value) { n.left=add((Node)n.left, i); n=n.balanceLeft(); } else { n.right=add((Node)n.right, i); n=n.balanceRight(); } } return n; } public int get(int i) { Node n=(Node)root; while (n!=null) { if (n.value==i) return n.cnt; if (i>n.value) n=n.left; else n=n.right; } return 0; } public int getSize() { return nodecount; } public int histMax() { return max; } public int mapIndexToValue(int i) { createIndex(); Node n=(Node)root; while (n!=null) { if (n.index==i) return n.value; if (i>n.index) n=n.left; else n=n.right; } return -1; } public int mapIndexToCount(int i) { createIndex(); Node n=(Node)root; while (n!=null) { if (n.index==i) return n.cnt; if (i>n.index) n=n.left; else n=n.right; } return -1; } private void createIndex() { if (!indexed) { if (root!=null) ((Node)root).createIndex(0); indexed=true; } } } /////////////////////////////////////////////////////////////// // Array Implementation protected static class ArrayHistogram implements Histogram { private int[] histogram; public ArrayHistogram(int size) { histogram=new int[size]; } public void add(int i) { histogram[i]++; } public int get(int i) { return histogram[i]; } public int getSize() { return histogram.length; } public int histMax() { int max=0; for (int i=0; i<histogram.length; i++) { if (histogram[i]>max) max=histogram[i]; } return max; } public int mapIndexToValue(int i) { if (i>=histogram.length) return -1; return i; } public int mapIndexToCount(int i) { if (i>=histogram.length) return -1; return histogram[i]; } } protected class StatisticRasterInfo extends RasterInfo { Histogram histogram; StatisticRasterInfo(MandelRaster r) { super(r); } @Override protected void analyseRaster(MandelRaster r) { super.analyseRaster(r); histogram=new TreeHistogram(getSize()); int[][] raster=r.getRaster(); for (int y=0; y<r.getRY(); y++) { for (int x=0; x<r.getRX(); x++) { int i=raster[y][x]; if (i>0) { histogram.add(i-minIt); } } } } int histMax() { return histogram.histMax(); } ///////////////////////////////////////////////////////////////////////// // joining method ///////////////////////////////////////////////////////////////////////// int[] points; int[] accu; int last; int setupColors() { points=new int[histogram.getSize()]; accu=new int[histogram.getSize()]; last=-1; int cnt=0; int cur=histogram.getSize()-1; for (int i=histogram.getSize()-1; i>=0; i--) { if ((accu[i]=histogram.mapIndexToCount(i))>0) { if (last<0) { last=i; } else { points[cur]=i; } cur=i; cnt++; } } points[cur]=-1; return cnt; } private class Pointer { int cur; int prev; int cnt; int min; } Pointer minUsedColor(Pointer p) { if (p.cnt>0 && p.min>0) { // try next points first int cur=points[p.prev]; int prev=p.prev; p.cur=cur; while (cur>=0) { if (accu[cur]==p.min) { p.cur=cur; p.prev=prev; p.cnt--; return p; } prev=cur; cur=points[cur]; } } return _minUsedColor(p); } Pointer _minUsedColor(Pointer p) { int cur=points.length-1; int prev=-1; p.cur=cur; p.prev=prev; p.cnt=0; p.min=0; while (cur>=0) { if (accu[cur]<=accu[p.cur]) { if (accu[cur]<accu[p.cur]) { p.cur=cur; p.prev=prev; p.min=accu[cur]; p.cnt=1; } else { p.cnt++; } } prev=cur; cur=points[cur]; } return p; } int joinNext(Pointer p) { if (p.cur==1 || p.prev==1) if (debug) System.out.println("join next "+p.cur); accu[p.cur]+=accu[points[p.cur]]; points[p.cur]=points[points[p.cur]]; return p.cur; } int joinPrev(Pointer p) { if (p.cur==1 || p.prev==1) if (debug) System.out.println("join prev "+p.cur+" ("+p.prev+")"); accu[p.prev]+=accu[p.cur]; points[p.prev]=points[p.cur]; return p.prev; } // int joinColor() // { Pointer p=minUsedColor(); // return joinColor(p); // } int joinColor(Pointer p) { if (points[p.cur]<0) { return joinPrev(p); } else if (p.prev<0) { return joinNext(p); } else if (accu[points[p.cur]]<accu[p.prev]) { return joinNext(p); } else { return joinPrev(p); } } int compressColors(int n, int[] mapping, MappingBuilder mb, BreakCondition c) { int num=setupColors(); int max=histMax(); int bound=(int)(limit*max); Pointer p=new Pointer(); while (num>n) { p=minUsedColor(p); if (c!=null && c.done(num, accu[p.cur])) break; int cur=joinColor(p); if (bound-accu[cur]>0) { accu[cur]+=(bound-accu[cur])*factor; } num--; } if (num>n) mb.setTarget(num+1); // change colormap size for mapping //System.out.println("compresed "+num); if (debug) System.out.println("p(0)->"+points[0]); if (debug) System.out.println("p(1)->"+points[1]); if (debug) System.out.println("compressed"); // all iteration values between two indices // ( curidx and points[curidx]) get the same color value // therefore the indices must be mapped to iteration values // for the final mapping table to support sparse histograms // This is done by the mapIndexToValue method of the histogram. if (usebuilder) { int curidx=points.length-1; n=num; while (curidx>=0) { mb.add(histogram.mapIndexToValue(curidx), n--); curidx=points[curidx]; } } else { int curidx=points.length-1; int idx=curidx; int it=histogram.mapIndexToValue(curidx); int nextit=histogram.mapIndexToValue(points[curidx]); n=num; while (it>=0) { if (it==nextit) { curidx=points[curidx]; nextit=histogram.mapIndexToValue(points[curidx]); n--; } if (n<=0) { throw new IllegalStateException( "need more colors than expected (it="+idx+")"); } //System.out.println(" "+it+": "+n); mapping[it--]=n; } } return num; } } public interface BreakCondition { public boolean done(int num, int accu); } /////////////////////////////////////////////////////////////// // mapping /////////////////////////////////////////////////////////////// public Mapping createMapping(MandelRaster raster, int colmapsize) { StatisticRasterInfo info=new StatisticRasterInfo(raster); int[] mapping=usebuilder?null:new int[info.getSize()]; colmapsize=adjustColmapSize(colmapsize); MappingBuilder mb=new MappingBuilder(info.getMinIt(),info.getMaxIt(), colmapsize); int s=info.compressColors(colmapsize-1,mapping,mb,createBreakCondition(info))+1; if (debug) System.out.println("MAPPING: "+s); MappingRepresentation mr; if (usebuilder) { if (mb.getSourceSize()<2000000) mr=mb.createArrayMapping(); else mr=mb.createTreeMapping(); } else { mr=new ArrayMapping(mapping); } return new Mapping(mb,mr); } protected BreakCondition createBreakCondition(StatisticRasterInfo info) { return null; } protected int adjustColmapSize(int colmapsize) { return colmapsize; } /////////////////////////////////////////////////////////////// // io info /////////////////////////////////////////////////////////////// @Override protected int getDefaultVersion() { return VERSION; } @Override protected boolean validVersion(int v) { return 1<=v && v<=VERSION; } ///////////////////////////////////////////////////////////////////////// // IO ///////////////////////////////////////////////////////////////////////// @Override protected void _write(DataOutputStream dos, int v) throws IOException { switch (v) { case 1: writeV1(dos); break; default: throw new IOException("unknown cyclic mapping version "+v); } } protected void writeV1(DataOutputStream dos) throws IOException { dos.writeDouble(factor); dos.writeDouble(limit); } @Override protected void _read(DataInputStream dis, int v) throws IOException { switch (v) { case 1: readV1(dis); break; default: throw new IOException("unknown cyclic mapping version "+v); } } protected void readV1(DataInputStream dis) throws IOException { factor=dis.readDouble(); limit=dis.readDouble(); } }