private Dictionary<Features, double> CalculateFeatures(PaletteData data, FeatureParams options)
        {
            String log = dir + "/out/timelog.txt";
            Stopwatch watch = new Stopwatch();

            //unpack options
            SortedSet<Features> included = options.included;
            CIELAB[,] imageLAB = options.imageLAB;
            double[,] map = options.map;
            double[] colorToSaliency = options.colorToSaliency;
            int[] colorToCounts = options.colorToCounts;
            double totalSaliency = options.totalSaliency;
            ColorNames colorNames = options.colorNames;
            double factor = options.factor;
            double[] colorToCNSaliency = options.colorToCNSaliency;
            double minE = options.minE;
            double maxE = options.maxE;
            Dictionary<CIELAB, int> swatchIndexOf = options.swatchIndexOf;
            Dictionary<Tuple<int, int>, double> imageSwatchDist = options.imageSwatchDist;
            double afactor = options.afactor;
            double[] swatchToPurity = options.swatchToPurity;
            Dictionary<int, double> pToScore = options.pToScore;
            double maxN = options.maxN;
            double avgN = options.avgN;
            double avgE = options.avgE;
            double[] segToSaliency = options.segmentToSaliency;
            Segmentation seg = options.seg;
            int[] swatchIdxToBin = options.swatchIdxToBin;
            int[,] binAssignments = options.binAssignments;
            double totalCapturable = options.totalCapturableSaliency;
            double[,] pixelToDists = options.pixelToDists;
            double[,] pixelToCNDists = options.pixelToNDists;

            double[] segmentToSD = options.segmentToSD;
            double totalSD = options.totalSD;

            //calculate the palette features
            Dictionary<Features, double> features = new Dictionary<Features, double>();

            double sdmin2 = 1;
            double sdmax2 = 0;
            double sdavg2 = 0;

            int width = options.imageLAB.GetLength(0);
            int height = options.imageLAB.GetLength(1);

            int n = data.lab.Count();

            double[] elementToSaliency = new double[n];
            int[] elementToCount = new int[n];

            //Calculate the error of recoloring the image, each pixel weighted the same, and subtract from 1
            double error = 0;
            //Coverage weighted by saliency of pixel
            double errorWeighted = 0;

            //error of recoloring the image, distance is the names distance metric
            double nerror = 0;
            double nerrorWeighted = 0;

            double errorSeg = 0;
            double errorSegSal = 0;

            double colorsPSeg = 0;
            double colorsPSegW = 0;

            double withinVariance = 0;
            double betweenVariance = 0;

            double sqerror = 0;
            double sqerrorW = 0;
            double sqerrorSeg = 0;
            double sqerrorSegSal = 0;

            double nsqerrorSeg = 0;
            double nsqerrorSegSal = 0;

            double nerrorSeg = 0;
            double nerrorSegSal = 0;

            double nsqerror = 0;
            double nsqerrorW = 0;

            double softerror = 0;
            double softwerror = 0;
            double softerrorseg = 0;
            double softerrorwseg = 0;
            double nsofterror = 0;
            double nsoftwerror = 0;
            double nsofterrorseg = 0;
            double nsofterrorwseg = 0;

            double nmeansegerror = 0;
            double nmeansegwerror = 0;

            double softerrorsegsd = 0;
            double nsofterrorsegsd = 0;
            double errorsegsd = 0;
            double nerrorsegsd = 0;
            double nsqerrorsegsd = 0;
            double sqerrorsegsd = 0;
            double meansegsd = 0;
            double nmeansegsd = 0;

            watch.Start();

            int numPixels = width * height;

            //compute the memberships
            double[,] memberships = new double[numPixels, n];
            double[,] nmemberships = new double[numPixels, n];

            //LAB, and SAT coverage
            double[] Lbounds = new double[] { double.PositiveInfinity, double.NegativeInfinity };
            double[] Abounds = new double[] { double.PositiveInfinity, double.NegativeInfinity };
            double[] Bbounds = new double[] { double.PositiveInfinity, double.NegativeInfinity };
            double[] Satbounds = new double[] { double.PositiveInfinity, double.NegativeInfinity };

            for (int i = 0; i < n; i++)
            {
                CIELAB lab = data.lab[i];
                Color c = data.colors[i];
                double s = c.GetSaturation();

                Lbounds[0] = Math.Min(Lbounds[0], lab.L);
                Lbounds[1] = Math.Max(Lbounds[1], lab.L);

                Abounds[0] = Math.Min(Abounds[0], lab.A);
                Abounds[1] = Math.Max(Abounds[1], lab.A);

                Bbounds[0] = Math.Min(Bbounds[0], lab.B);
                Bbounds[1] = Math.Max(Bbounds[1], lab.B);

                Satbounds[0] = Math.Min(Satbounds[0], s);
                Satbounds[1] = Math.Max(Satbounds[1], s);

            }
            double Lspan = Lbounds[1] - Lbounds[0];
            double Aspan = Abounds[1] - Abounds[0];
            double Bspan = Bbounds[1] - Bbounds[0];
            double Satspan = Satbounds[1] - Satbounds[0];

            double Lcov = Math.Min(Lspan, options.Lspan) / Math.Max(Math.Max(Lspan, options.Lspan), 1);
            double Acov = Math.Min(Aspan, options.Aspan) / Math.Max(Math.Max(Aspan, options.Aspan), 1);
            double Bcov = Math.Min(Bspan, options.Bspan) / Math.Max(Math.Max(Bspan, options.Bspan), 1);
            double Scov = Math.Min(Satspan, options.Satspan) / Math.Max(Math.Max(Satspan, options.Satspan), 1);

            double meansegError = 0;
            double meansegErrorWeighted = 0;

            double[,] colorSegDist = new double[n, seg.numSegments];

            double[] segToDist = new double[seg.numSegments];

            for (int j = 0; j < seg.numSegments; j++)
            {
                //find the closest color for each segment
                double bestDist = Double.PositiveInfinity;
                for (int i = 0; i < n; i++)
                {
                    colorSegDist[i, j] = Math.Sqrt(data.lab[i].SqDist(seg.segToMeanColor[j]));
                    segToDist[j] += colorSegDist[i, j];

                    bestDist = Math.Min(colorSegDist[i, j], bestDist);
                }

                double nbestDist = Double.PositiveInfinity;
                for (int i = 0; i < n; i++)
                {
                    double ndist = GetCNDist(colorNames.GetBin(data.lab[i]), colorNames.GetBin(seg.segToMeanColor[j]));
                    nbestDist = Math.Min(ndist, nbestDist);

                }

                meansegError += bestDist;
                meansegErrorWeighted += bestDist * segToSaliency[j];

                nmeansegerror += nbestDist;
                nmeansegwerror += nbestDist * segToSaliency[j];

                meansegsd += bestDist * segmentToSD[j];
                nmeansegsd += nbestDist * segmentToSD[j];

            }

            meansegError /= seg.numSegments * factor;
            meansegErrorWeighted /= totalSaliency * factor;

            nmeansegerror /= seg.numSegments;
            nmeansegwerror /= totalSaliency;

            meansegsd /= totalSD * factor;
            nmeansegsd /= totalSD;

            //now calculate entropy per segment
            //probability is 1-(dist/totaldist)
            double[] segEntropy = new double[seg.numSegments];
            for (int i = 0; i < seg.numSegments; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    double prob = 1 - colorSegDist[j, i] / segToDist[i];
                    segEntropy[i] += prob * Math.Log(prob);
                }
            }

            //average and/or weight by saliency of segment
            for (int i = 0; i < seg.numSegments; i++)
            {
                colorsPSeg += segEntropy[i];
                colorsPSegW += segEntropy[i] * segToSaliency[i];
            }

            colorsPSeg /= seg.numSegments;
            colorsPSegW /= totalSaliency;

            double[,] nsubs = new double[width, height];
            double[,] subs = new double[width, height];

            if (included.Overlaps(options.NameCovFeatures) ||
                included.Overlaps(options.CoverageFeatures) ||
                included.Overlaps(options.SaliencyDensFeatures))
            {

                double[,] errors = new double[width, height];
                int[,] assignment = new int[width, height]; //the pixel to the nearest palette color

                double[,] nerrors = new double[width, height];
                double[,] nerrorsWeighted = new double[width, height];

                double[] clusterSqError = new double[n];

                int[,] nassignment = new int[width, height];

                Parallel.For(0, width, i =>
               {
                   for (int j = 0; j < height; j++)
                   {
                       double bestDist = Double.PositiveInfinity;
                       int bestIdx = -1;
                       for (int s = 0; s < data.lab.Count(); s++)
                       {
                           double dist = data.lab[s].SqDist(options.imageLAB[i, j]);
                           if (dist < bestDist)
                               bestIdx = s;
                           bestDist = Math.Min(dist, bestDist);

                       }
                       assignment[i, j] = bestIdx;
                       double sqrtBestDist = Math.Sqrt(bestDist);
                       errors[i, j] = sqrtBestDist;
                       clusterSqError[bestIdx] += bestDist;
                   }

                   if (included.Overlaps(options.NameCovFeatures))
                   {
                       for (int j = 0; j < height; j++)
                       {
                           int bestIdx = -1;
                           double bestDist = Double.PositiveInfinity;
                           for (int s = 0; s < data.lab.Count(); s++)
                           {
                               int sIdx = swatchIndexOf[data.lab[s]];
                               double dist = GetCNDist(binAssignments[i, j], swatchIdxToBin[sIdx]);//imageSwatchDist[new Tuple<int, int>(binAssignments[i,j], swatchIdxToBin[sIdx])]; //imageSwatchDist[new Tuple<int, int>(colorNames.GetBin(imageLAB[i, j]), colorNames.GetBin(data.lab[s]))];

                               if (dist < bestDist)
                                   bestIdx = s;

                               bestDist = Math.Min(dist, bestDist);
                           }
                           double val = map[i, j];
                           nerrors[i, j] = bestDist;
                           nerrorsWeighted[i, j] = bestDist * val;
                           nassignment[i, j] = bestIdx;
                       }
                   }

                   double epsilon = 0.0001;

                   for (int j = 0; j < height; j++)
                   {
                       //calculate the memberships
                       double[] dists = new double[n];
                       double[] ndists = new double[n];

                       double subtotal = 0;
                       double nsubtotal = 0;

                       int idx = j * width + i;

                       for (int k = 0; k < n; k++)
                       {
                           int sidx = swatchIndexOf[data.lab[k]];

                           dists[k] = Math.Max(pixelToDists[idx, sidx], epsilon);
                           ndists[k] = Math.Max(pixelToCNDists[idx, sidx], epsilon);

                           subtotal += (1.0 / dists[k]);
                           nsubtotal += (1.0 / ndists[k]);
                       }
                       for (int k = 0; k < n; k++)
                       {
                           memberships[idx, k] = 1.0 / (dists[k] * subtotal);
                           nmemberships[idx, k] = 1.0 / (ndists[k] * nsubtotal);
                       }

                       for (int k = 0; k < n; k++)
                       {
                           int sidx = swatchIndexOf[data.lab[k]];

                           double u = memberships[idx, k];
                           subs[i, j] += u * u * Math.Max(pixelToDists[idx, sidx], epsilon);

                           double nu = nmemberships[idx, k];
                           nsubs[i, j] += nu * nu * Math.Max(pixelToCNDists[idx, sidx], epsilon);
                       }
                   }

               });

                //find J

                double[] segToJ = new double[seg.numSegments];
                double[] nsegToJ = new double[seg.numSegments];

                for (int i = 0; i < width; i++)
                {
                    for (int j = 0; j < height; j++)
                    {
                        int idx = j * width + i;

                        double sub = subs[i, j];
                        double nsub = nsubs[i, j];

                        softerror += sub;
                        nsofterror += nsub;

                        softwerror += sub * map[i, j];
                        nsoftwerror += nsub * map[i, j];

                        int segIdx = seg.assignments[i, j];
                        segToJ[segIdx] += sub;
                        nsegToJ[segIdx] += nsub;
                    }
                }

                softerror /= numPixels * factor * factor;
                nsofterror /= numPixels;

                softwerror /= totalSaliency * factor * factor;
                nsoftwerror /= totalSaliency;

                for (int s = 0; s < seg.numSegments; s++)
                {
                    softerrorseg += segToJ[s] / seg.counts[s];
                    softerrorwseg += segToSaliency[s] * segToJ[s] / seg.counts[s];

                    nsofterrorseg += nsegToJ[s] / seg.counts[s];
                    nsofterrorwseg += segToSaliency[s] * nsegToJ[s] / seg.counts[s];

                    softerrorsegsd += segmentToSD[s] * segToJ[s] / seg.counts[s];
                    nsofterrorsegsd += segmentToSD[s] * nsegToJ[s] / seg.counts[s];

                }
                softerrorseg /= seg.numSegments * factor * factor;
                softerrorwseg /= totalSaliency * factor * factor;

                nsofterrorseg /= seg.numSegments;
                nsofterrorwseg /= totalSaliency;

                softerrorsegsd /= totalSD * factor * factor;
                nsofterrorsegsd /= totalSD;

                //calculate within and between palette cluster variance
                //between variance
                CIELAB betweenMean = new CIELAB();

                for (int j = 0; j < n; j++)
                {
                    betweenMean = betweenMean + data.lab[j];
                }
                betweenMean = betweenMean / (double)n;
                for (int j = 0; j < n; j++)
                {
                    betweenVariance += betweenMean.SqDist(data.lab[j]);
                }
                betweenVariance /= (double)n * factor * factor;

                double[] counts = new double[n];

                for (int i = 0; i < width; i++)
                {
                    for (int j = 0; j < height; j++)
                    {
                        counts[assignment[i, j]]++;
                    }
                }

                for (int i = 0; i < n; i++)
                {
                    withinVariance += clusterSqError[i] / Math.Max(1, counts[i]);
                }
                withinVariance /= n * (factor * factor);

                double[] segmentToError = new double[segToSaliency.Count()];
                double[] segToSqError = new double[segToSaliency.Count()];
                Dictionary<int, SortedSet<int>> segToColors = new Dictionary<int, SortedSet<int>>();

                //now aggregate
                for (int i = 0; i < width; i++)
                {
                    for (int j = 0; j < height; j++)
                    {
                        double sqError = errors[i, j] * errors[i, j];
                        double val = map[i, j];
                        errorWeighted += val * errors[i, j];
                        sqerrorW += val * sqError;
                        error += errors[i, j];
                        sqerror += sqError;

                        int idx = assignment[i, j];
                        if (idx >= 0)
                        {
                            elementToSaliency[idx] += val;
                            elementToCount[idx]++;
                        }
                        int segIdx = seg.assignments[i, j];
                        segmentToError[segIdx] += errors[i, j] / seg.counts[segIdx];
                        segToSqError[segIdx] += sqError / seg.counts[segIdx];

                    }
                }

                for (int i = 0; i < seg.numSegments; i++)
                {

                    errorSeg += segmentToError[i];
                    errorSegSal += segmentToError[i] * segToSaliency[i];

                    sqerrorSeg += segToSqError[i];
                    sqerrorSegSal += segToSqError[i] * segToSaliency[i]; //TODO: this is weighted by segment percent salient density

                    errorsegsd += segmentToError[i] * segmentToSD[i];
                    sqerrorsegsd += segToSqError[i] * segmentToSD[i];

                }

                errorSeg /= seg.numSegments * factor;
                errorSegSal /= totalSaliency * factor;

                sqerrorSeg /= seg.numSegments * factor * factor;
                sqerrorSegSal /= totalSaliency * factor * factor;

                errorWeighted /= totalSaliency * factor;
                error /= width * height * factor;

                sqerrorW /= totalSaliency * factor * factor;
                sqerror /= width * height * factor * factor;

                errorsegsd /= totalSD * factor;
                sqerrorsegsd /= totalSD * factor * factor;

                //now sum it all up
                for (int i = 0; i < width; i++)
                {
                    for (int j = 0; j < height; j++)
                    {
                        nerrorWeighted += nerrorsWeighted[i, j];
                        nerror += nerrors[i, j];
                    }
                }

                nerrorWeighted /= totalSaliency;
                nerror /= width * height;

                double[] nsegmentToError = new double[segToSaliency.Count()];
                double[] nsegToSqError = new double[segToSaliency.Count()];

                //now aggregate
                for (int i = 0; i < width; i++)
                {
                    for (int j = 0; j < height; j++)
                    {
                        double sqError = nerrors[i, j] * nerrors[i, j];
                        double val = map[i, j];
                        nerrorWeighted += val * nerrors[i, j];
                        nsqerrorW += val * sqError;
                        nerror += nerrors[i, j];
                        nsqerror += sqError;

                        int idx = nassignment[i, j];
                        if (idx >= 0)
                        {
                            elementToSaliency[idx] += val;
                            elementToCount[idx]++;
                        }
                        int segIdx = seg.assignments[i, j];
                        nsegmentToError[segIdx] += nerrors[i, j] / seg.counts[segIdx];
                        nsegToSqError[segIdx] += sqError / seg.counts[segIdx];

                    }
                }

                for (int i = 0; i < seg.numSegments; i++)
                {

                    nerrorSeg += nsegmentToError[i];
                    nerrorSegSal += nsegmentToError[i] * segToSaliency[i];

                    nsqerrorSeg += nsegToSqError[i];
                    nsqerrorSegSal += nsegToSqError[i] * segToSaliency[i]; //TODO: this is weighted by segment percent salient density

                    nerrorsegsd += nsegmentToError[i] * segmentToSD[i];
                    nsqerrorsegsd += nsegToSqError[i] * segmentToSD[i];

                }

                nerrorSeg /= seg.numSegments;
                nerrorSegSal /= totalSaliency;

                nsqerrorSeg /= seg.numSegments;
                nsqerrorSegSal /= totalSaliency;

                nerrorWeighted /= totalSaliency;
                nerror /= width * height;

                nsqerrorW /= totalSaliency;
                nsqerror /= width * height;

                nerrorsegsd /= totalSD;
                nsqerrorsegsd /= totalSD;

            }
            Log(log, "Coverage and Name Coverage" + watch.ElapsedMilliseconds);
            watch.Restart();

            for (int i = 0; i < n; i++)
            {
                double dens = elementToSaliency[i] / Math.Max(1, elementToCount[i]);
                sdmin2 = Math.Min(dens, sdmin2);
                sdmax2 = Math.Max(dens, sdmax2);
                sdavg2 += dens;
            }
            sdavg2 /= data.lab.Count();

            Log(log, "SaliencyDensClust " + watch.ElapsedMilliseconds);
            watch.Restart();

            //total captured salience (salience sum/salience of the image)
            double stotal = 0;

            //average salience density of each swatch (and min/max)
            double sdmin = 1;
            double sdmax = 0;
            double sdavg = 0;

            foreach (CIELAB c in data.lab)
            {
                double val = colorToSaliency[swatchIndexOf[c]] / Math.Max(1, colorToCounts[swatchIndexOf[c]]);
                sdmin = Math.Min(val, sdmin);
                sdmax = Math.Max(val, sdavg);
                sdavg += val;
                stotal += colorToSaliency[swatchIndexOf[c]];
            }
            sdavg /= data.lab.Count();
            stotal /= totalCapturable;

            Log(log, "SaliencyDens " + watch.ElapsedMilliseconds);
            watch.Restart();

            double cnmin = 1;
            double cnmax = 0;
            double cnavg = 0;

            for (int i = 0; i < n; i++)
            {
                for (int j = i + 1; j < n; j++)
                {
                    double dist = 1 - colorNames.CosineDistance(data.lab[i], data.lab[j]);
                    cnavg += dist;
                    cnmin = Math.Min(cnmin, dist);
                    cnmax = Math.Max(cnmax, dist);
                }
            }
            cnavg /= n * (n - 1) / 2;

            Log(log, "NamesDiff " + watch.ElapsedMilliseconds);
            watch.Restart();

            //Calculate the color name diversity, avg color name distance to closest neighbor
            double cndiv = 0;

            for (int c = 0; c < n; c++)
            {
                double bestDist = 1;
                for (int i = 0; i < n; i++)
                {
                    if (c == i)
                        continue;
                    double curDist = 1 - colorNames.CosineDistance(data.lab[i], data.lab[c]);
                    if (curDist < bestDist)
                        bestDist = curDist;
                }

                cndiv += bestDist / n;
            }

            Log(log, "NamesClosestAvg " + watch.ElapsedMilliseconds);
            watch.Restart();

            //calculate color name salience (average, min, max)
            double csmin = 1;
            double csmax = 0;
            double csavg = 0;

            for (int i = 0; i < n; i++)
            {
                double salience = (colorToCNSaliency[swatchIndexOf[data.lab[i]]] - minE) / (maxE - minE);//colorNames.NormalizedSaliency(data.lab[i], minE, maxE);
                csavg += salience;
                csmin = Math.Min(csmin, salience);
                csmax = Math.Max(csmax, salience);
            }
            csavg /= n;

            Log(log, "NameSalience " + watch.ElapsedMilliseconds);
            watch.Restart();

            //Calculate the diversity, avg distance to closest neighbor
            double div = 0;
            double divr = 0;

            for (int c = 0; c < n; c++)
            {
                double bestDist = Double.PositiveInfinity;
                for (int i = 0; i < n; i++)
                {
                    if (c == i)
                        continue;
                    double curDist = data.lab[c].SqDist(data.lab[i]);
                    if (curDist < bestDist)
                        bestDist = curDist;
                }

                div += Math.Sqrt(bestDist); // (n * factor);
            }
            double temp = div;
            div = temp / (n * factor);
            divr = temp / (n * afactor);

            Log(log, "DiffClosestAvg " + watch.ElapsedMilliseconds);
            watch.Restart();

            //calculate average distance of one color to the rest of the colors
            double dmin = 1;
            double dmax = 0;
            double davg = 0;

            double dminr = 1;
            double dmaxr = 0;
            double davgr = 0;

            for (int i = 0; i < n; i++)
            {
                for (int j = i + 1; j < n; j++)
                {
                    double dist = Math.Sqrt(data.lab[i].SqDist(data.lab[j]));
                    davg += dist;
                    dmin = Math.Min(dmin, dist);
                    dmax = Math.Max(dmax, dist);
                }
            }

            double dtemp = dmin;
            dmin = dtemp / factor;
            dminr = dtemp / afactor;
            dtemp = dmax;
            dmax = dtemp / factor;
            dmaxr = dtemp / afactor;

            davg /= n * (n - 1) / 2;

            dtemp = davg;
            davg = dtemp / factor;
            davgr = dtemp / afactor;

            Log(log, "LABDiff " + watch.ElapsedMilliseconds);
            watch.Restart();

            double puritymin = double.PositiveInfinity;
            double puritymax = double.NegativeInfinity;
            double purityavg = 0;

            double purityminr = puritymin;
            double puritymaxr = puritymax;
            double purityavgr = 0;

            foreach (CIELAB c in data.lab)
            {
                double val = swatchToPurity[swatchIndexOf[c]];
                puritymin = Math.Min(puritymin, val);
                puritymax = Math.Max(puritymax, val);
                purityavg += val;
            }
            double ptemp = puritymin;
            puritymin = ptemp / factor;
            purityminr = ptemp / factor;

            ptemp = puritymax;
            puritymax = ptemp / factor;
            puritymaxr = ptemp / afactor;

            ptemp = purityavg / data.lab.Count();
            purityavg = ptemp / factor;
            purityavgr = ptemp / afactor;

            //Record all the features
            features.Add(Features.CovUnweighted, 1 - error);
            features.Add(Features.CovWeightedSaliency, 1 - errorWeighted);
            features.Add(Features.ErrorUnweighted, error);
            features.Add(Features.ErrorWeightedSaliency, errorWeighted);
            features.Add(Features.NCov, 1 - nerror);
            features.Add(Features.NCovWeightedSaliency, 1 - nerrorWeighted);
            features.Add(Features.NError, nerror);
            features.Add(Features.NErrorWeightedSaliency, nerrorWeighted);
            features.Add(Features.DAvg, davg);
            features.Add(Features.DClosestAvg, div);
            features.Add(Features.DMin, dmin);
            features.Add(Features.DMax, dmax);
            features.Add(Features.NClosestAvg, cndiv);
            features.Add(Features.NDiffAvg, cnavg / maxN);
            features.Add(Features.NDiffMin, cnmin / maxN);
            features.Add(Features.NDiffMax, cnmax / maxN);
            features.Add(Features.NSaliencyMin, csmin);
            features.Add(Features.NSaliencyMax, csmax);
            features.Add(Features.NSaliencyAvg, csavg);
            features.Add(Features.SaliencyDensMin, sdmin);
            features.Add(Features.SaliencyDensMax, sdmax);
            features.Add(Features.SaliencyDensAvg, sdavg);
            features.Add(Features.SaliencyTotal, stotal);
            features.Add(Features.SaliencyDensClustMin, sdmin2);
            features.Add(Features.SaliencyDensClustMax, sdmax2);
            features.Add(Features.SaliencyDensClustAvg, sdavg2);
            features.Add(Features.DMinR, dminr);
            features.Add(Features.DMaxR, dmaxr);
            features.Add(Features.DAvgR, davgr);
            features.Add(Features.DClosestAvgR, divr);
            features.Add(Features.PurityMin, puritymin);
            features.Add(Features.PurityMax, puritymax);
            features.Add(Features.PurityAvg, purityavg);
            features.Add(Features.PurityMinR, purityminr);
            features.Add(Features.PurityMaxR, puritymaxr);
            features.Add(Features.PurityAvgR, purityavgr);

            features.Add(Features.NDiffMinR, cnmin / avgN);
            features.Add(Features.NDiffMaxR, cnmax / avgN);
            features.Add(Features.NDiffAvgR, cnavg / avgN);
            features.Add(Features.NSaliencyAvgR, csavg / avgE);
            features.Add(Features.NSaliencyMaxR, csmax / avgE);
            features.Add(Features.NSaliencyMinR, csmin / avgE);
            features.Add(Features.NClosestAvgR, cndiv / avgE);

            features.Add(Features.ErrorWeightedSegment, errorSegSal);
            features.Add(Features.ErrorUnweightedSegment, errorSeg);

            features.Add(Features.ColorsPerSeg, colorsPSeg);
            features.Add(Features.ColorsPerSegWeighted, colorsPSegW);

            features.Add(Features.MeanSegError, meansegError);
            features.Add(Features.MeanSegErrorWeighted, meansegErrorWeighted);

            features.Add(Features.LCov, Lcov);
            features.Add(Features.ACov, Acov);
            features.Add(Features.BCov, Bcov);
            features.Add(Features.SatCov, Scov);

            features.Add(Features.BetweenVar, betweenVariance);
            features.Add(Features.WithinVar, withinVariance);
            features.Add(Features.SqErrorUnweighted, sqerror);
            features.Add(Features.SqErrorWeightedSaliency, sqerrorW);
            features.Add(Features.SqErrorWSeg, sqerrorSegSal);
            features.Add(Features.SqErrorSeg, sqerrorSeg);

            features.Add(Features.NSqError, nsqerror);
            features.Add(Features.NSqErrorW, nsqerrorW);
            features.Add(Features.NSqErrorSeg, nsqerrorSeg);
            features.Add(Features.NSqErrorWSeg, nsqerrorSegSal);
            features.Add(Features.NErrorSeg, nerrorSeg);
            features.Add(Features.NErrorWSeg, nerrorSegSal);

            features.Add(Features.SoftError, softerror);
            features.Add(Features.SoftWError, softwerror);
            features.Add(Features.SoftErrorSeg, softerrorseg);
            features.Add(Features.SoftErrorWSeg, softerrorwseg);

            features.Add(Features.NSoftError, nsofterror);
            features.Add(Features.NSoftWError, nsoftwerror);
            features.Add(Features.NSoftErrorSeg, nsofterrorseg);
            features.Add(Features.NSoftErrorWSeg, nsofterrorwseg);

            features.Add(Features.NMeanSegError, nmeansegerror);
            features.Add(Features.NMeanSegErrorW, nmeansegwerror);

            features.Add(Features.MeanSegErrorDensity, meansegsd);
            features.Add(Features.NMeanSegErrorDensity, nmeansegsd);

            features.Add(Features.ErrorSegSD, errorsegsd);
            features.Add(Features.NErrorSegSD, nerrorsegsd);
            features.Add(Features.SqErrorSegSD, sqerrorsegsd);
            features.Add(Features.NSqErrorSegSD, nsqerrorsegsd);
            features.Add(Features.SoftErrorSegSD, softerrorsegsd);
            features.Add(Features.NSoftErrorSegSD, nsofterrorsegsd);

            return features;
        }
        //Precompute some parameters
        private FeatureParams SetupFeatureParams(SortedSet<Features> included, String key, String saliencyPattern, bool debug = false, int maxIters=50)
        {
            FeatureParams options = new FeatureParams();

            //find the normalization factor for swatches
            double factor = 0;
            double afactor = 0;

            PaletteData swatches = GetPaletteSwatches(key, maxIters);
            int scount = swatches.lab.Count();
            for (int i = 0; i < swatches.lab.Count(); i++)
            {
                for (int j = i + 1; j < swatches.lab.Count(); j++)
                {
                    double dist = swatches.lab[i].SqDist(swatches.lab[j]);
                    factor = Math.Max(factor, dist);
                    afactor += Math.Sqrt(dist);
                }
            }
            factor = Math.Sqrt(factor);
            afactor /= ((scount - 1) * scount / 2);

            //open the image
            Bitmap orig = new Bitmap(Image.FromFile(dir + "/" + key));
            Bitmap image = orig;
            if (debug)
                image = new Bitmap(orig, orig.Width / 4, orig.Height / 4);

            CIELAB[,] imageLAB = new CIELAB[image.Width, image.Height];
            //convert the image to LAB
            for (int i = 0; i < image.Width; i++)
                for (int j = 0; j < image.Height; j++)
                    imageLAB[i, j] = Util.RGBtoLAB(image.GetPixel(i, j));

            double[,] pixelToDists = new double[image.Width * image.Height, scount];
            double[,] pixelToNDists = new double[image.Width * image.Height, scount];

            //calculate the assignment of a pixel to a swatch
            for (int i = 0; i < image.Width; i++)
            {
                for (int j = 0; j < image.Height; j++)
                {
                    int idx = j * image.Width + i;

                    for (int s = 0; s < swatches.lab.Count(); s++)
                    {
                        pixelToDists[idx, s] = imageLAB[i, j].SqDist(swatches.lab[s]);

                        int a = colorNames.GetBin(imageLAB[i, j]);
                        int b = colorNames.GetBin(swatches.lab[s]);

                        double ndist = GetCNDist(a, b);
                        pixelToNDists[idx, s] = ndist * ndist;
                    }
                }
            }

            //calculate the unnormalized purity of each swatch
            double[] purityAvg = new double[scount];
            int purityCount = (int)Math.Round(image.Width * image.Height / 20.0);
            //for each swatch, find the top 5% pixels closest to it (used by Donovan)
            for (int s = 0; s < scount; s++)
            {
                CIELAB swatch = swatches.lab[s];
                List<double> dists = new List<double>(image.Width * image.Height);

                for (int i = 0; i < image.Width; i++)
                {
                    for (int j = 0; j < image.Height; j++)
                    {
                        dists.Add(imageLAB[i, j].SqDist(swatch));
                    }
                }

                dists.Sort();

                //now pop off the top
                for (int i = 0; i < purityCount; i++)
                {
                    purityAvg[s] += Math.Sqrt(dists[i]);
                }
                purityAvg[s] /= purityCount;

            }

            //read the saliency map, and calculate saliency for each swatch
            String mapPath = Util.ConvertFileName(key, saliencyPattern);//"gbvs_" + key;
            Bitmap map = new Bitmap(Image.FromFile(dir + "/saliency/" + mapPath), image.Width, image.Height);

            double[,] mapData = new double[image.Width, image.Height];
            for (int i = 0; i < image.Width; i++)
            {
                for (int j = 0; j < image.Height; j++)
                {
                    mapData[i, j] = map.GetPixel(i, j).G / 255.0;
                }
            }

            Dictionary<CIELAB, int> swatchIndexOf = new Dictionary<CIELAB, int>();
            for (int i = 0; i < swatches.lab.Count(); i++)
            {
                swatchIndexOf.Add(swatches.lab[i], i);
            }

            //for each cluster, figure out it's total/average saliency
            double[] colorToSaliency = new double[swatches.lab.Count()];
            int[] colorToCounts = new int[swatches.lab.Count()];

            double thresh = Double.PositiveInfinity;
            double totalSaliency = 0;

            for (int i = 0; i < map.Width; i++)
            {
                for (int j = 0; j < map.Height; j++)
                {
                    //find the image color, and its closest swatch, within some max threshold
                    CIELAB c = imageLAB[i, j];
                    double bestDist = thresh;
                    int bestSwatch = -1;
                    for (int s = 0; s < swatches.lab.Count(); s++)
                    {
                        if (swatches.lab[s].SqDist(c) < bestDist)
                        {
                            bestDist = swatches.lab[s].SqDist(c);
                            bestSwatch = s;
                        }
                    }

                    double value = map.GetPixel(i, j).G / 255.0;

                    if (bestSwatch >= 0)
                    {
                        colorToSaliency[bestSwatch] += value;
                        colorToCounts[bestSwatch]++;
                    }

                    totalSaliency += value;
                }
            }

            List<double> sorted = colorToSaliency.ToList<double>();
            sorted.Sort();
            sorted.Reverse();
            double totalCapturable = 0;
            for (int i = 0; i < 5; i++)
            {
                totalCapturable += sorted[i];
            }

            double minE = Double.PositiveInfinity;
            double maxE = Double.NegativeInfinity;
            double[] colorToCNSaliency = new double[swatches.lab.Count()];
            double avgE = 0;
            for (int i = 0; i < swatches.lab.Count(); i++)
            {
                CIELAB s = swatches.lab[i];
                double sal = colorNames.Saliency(s);
                minE = Math.Min(minE, sal);
                maxE = Math.Max(maxE, sal);
                avgE += sal;
                colorToCNSaliency[i] = sal;
            }
            avgE /= swatches.lab.Count();
            avgE = (avgE - minE) / (maxE - minE);

            //find the avg and max color name distance between swatches
            double maxN = Double.NegativeInfinity;
            double avgN = 0;
            for (int i = 0; i < scount; i++)
            {
                for (int j = i + 1; j < scount; j++)
                {
                    double val = 1 - colorNames.CosineDistance(swatches.lab[i], swatches.lab[j]);
                    avgN += val;
                    maxN = Math.Max(maxN, val);
                }
            }
            avgN /= ((scount - 1) * scount / 2);

            //cache the distance between image colors and swatches
            Dictionary<Tuple<int, int>, double> imageSwatchDist = new Dictionary<Tuple<int, int>, double>();
            int[,] binAssignments = new int[image.Width, image.Height];
            int[] swatchIdxToBin = new int[swatches.colors.Count()];

            if (included.Overlaps(options.NameCovFeatures))
            {
                for (int i = 0; i < image.Width; i++)
                {
                    for (int j = 0; j < image.Height; j++)
                    {
                        CIELAB c = imageLAB[i, j];
                        for (int s = 0; s < swatches.lab.Count(); s++)
                        {
                            int a = colorNames.GetBin(imageLAB[i, j]);
                            int b = colorNames.GetBin(swatches.lab[s]);

                            binAssignments[i, j] = a;
                            swatchIdxToBin[s] = b;

                            GetCNDist(a, b);

                        }
                    }
                }
            }

            //segment to total saliency
            Segmentation seg = LoadSegAssignments(key, image.Width, image.Height, imageLAB);
            double[] segmentToSaliency = new double[seg.numSegments];
            double[] segmentToSD = new double[seg.numSegments];

            for (int i = 0; i < image.Width; i++)
            {
                for (int j = 0; j < image.Height; j++)
                {
                    int id = seg.assignments[i, j];
                    segmentToSaliency[id] += mapData[i, j];
                    segmentToSD[id] += mapData[i, j];
                }
            }

            double totalSD = 0;
            for (int i = 0; i < seg.numSegments; i++)
            {
                segmentToSD[i] /= seg.counts[i];
                totalSD += segmentToSD[i];
            }

            //find the l,a,b, sat bounds
            double[] Lbounds = new double[] { double.PositiveInfinity, double.NegativeInfinity };
            double[] Abounds = new double[] { double.PositiveInfinity, double.NegativeInfinity };
            double[] Bbounds = new double[] { double.PositiveInfinity, double.NegativeInfinity };
            double[] Satbounds = new double[] { double.PositiveInfinity, double.NegativeInfinity };

            for (int i = 0; i < swatches.lab.Count(); i++)
            {
                CIELAB lab = swatches.lab[i];
                Color rgb = swatches.colors[i];
                Lbounds[0] = Math.Min(Lbounds[0], lab.L);
                Lbounds[1] = Math.Max(Lbounds[1], lab.L);

                Abounds[0] = Math.Min(Abounds[0], lab.A);
                Abounds[1] = Math.Max(Abounds[1], lab.A);

                Bbounds[0] = Math.Min(Bbounds[0], lab.B);
                Bbounds[1] = Math.Max(Bbounds[1], lab.B);

                double s = rgb.GetSaturation();
                Satbounds[0] = Math.Min(Lbounds[0], s);
                Satbounds[1] = Math.Max(Lbounds[1], s);
            }

            //pack up

            options.included = included;
            options.imageLAB = imageLAB;
            options.map = mapData;
            options.colorToSaliency = colorToSaliency;
            options.colorToCounts = colorToCounts;
            options.totalSaliency = totalSaliency;
            options.colorNames = colorNames;
            options.factor = factor;
            options.colorToCNSaliency = colorToCNSaliency;
            options.minE = minE;
            options.maxE = maxE;
            options.swatchIndexOf = swatchIndexOf;
            options.imageSwatchDist = imageSwatchDist;
            options.afactor = afactor;
            options.swatchToPurity = purityAvg;
            //options.pToScore = pToScore;
            options.maxN = maxN;
            options.avgN = avgN;
            options.avgE = avgE;
            options.segmentToSaliency = segmentToSaliency;
            options.seg = seg;

            options.Lspan = Lbounds[1] - Lbounds[0];
            options.Aspan = Abounds[1] - Abounds[0];
            options.Bspan = Bbounds[1] - Bbounds[0];
            options.Satspan = Satbounds[1] - Satbounds[0];

            options.swatchIdxToBin = swatchIdxToBin;
            options.binAssignments = binAssignments;

            options.totalCapturableSaliency = totalCapturable;
            options.pixelToDists = pixelToDists;

            options.pixelToNDists = pixelToNDists;

            options.totalSD = totalSD;
            options.segmentToSD = segmentToSD;

            return options;
        }