public override bool Equals(object obj)
            {
                ClassifyProcessParam o = obj as ClassifyProcessParam;

                return((o is ClassifyProcessParam) && zoom == o.zoom && o.TileX == TileX && o.TileY == TileY);
            }
        public static void Classifiy(BackgroundWorker sender, ImageGrid g, ClassifyProcessParam p, int classifier, int NStep, string path, int blurRadius)
        {
            MainWindow.Log("Loading Image (" + p.WorldX + " " + p.WorldY + ")");

            DateTime time = DateTime.Now;

            ImageTile it  = null;
            int       max = (int)Math.Pow(2, p.zoom);

            int TileX = p.TileX - 1;

            for (int x = p.WorldX - 1; x <= p.WorldX + 1; x++, TileX++)
            {
                int TileY = p.TileY - 1;
                for (int y = p.WorldY - 1; y <= p.WorldY + 1; y++, TileY++)
                {
                    if (x >= 0 && x < max && y >= 0 && y < max && TileX >= 0 && TileX < g.width && TileY >= 0 && TileY < g.height)
                    {
                        string id       = g.id + "_" + p.zoom + "_" + y + "_" + x;
                        string filename = "tmp\\sat\\" + (p.zoom) + "_" + (y) + "_" + (x) + ".png";

                        if (x == p.WorldX && y == p.WorldY)
                        {
                            MainWindow.dispatcher.Invoke(() =>
                            {
                                if (imageTiles.ContainsKey(id))
                                {
                                    it = imageTiles[id];
                                }
                                else
                                {
                                    it = new ImageTile(MainWindow.Instance, g, filename, id, p.zoom, TileX, TileY, false);
                                }
                            });
                        }
                        else
                        {
                            MainWindow.dispatcher.Invoke(() =>
                            {
                                if (!imageTiles.ContainsKey(id))
                                {
                                    ImageTile it_ = new ImageTile(MainWindow.Instance, g, filename, id, p.zoom, TileX, TileY, false);
                                    g.AddTile(it_);
                                }
                            });
                        }
                    }
                }
            }

            if (it == null)
            {
                return;
            }
            g.AddTile(it);
            it.Status(new PixelColor(0, 255, 255, 100));

            it.classified = false;
            it.Lock();

            MainWindow.Log("Classifying Image (" + p.WorldX + " " + p.WorldY + ")");

            try
            {
                MainWindow.dispatcher.Invoke(() =>
                {
                    foreach (Classes c in MainWindow.classesList)
                    {
                        g.AddOverlayClass(it, c, MainWindow.Instance);

                        if (c.classifiedPointsList.ContainsKey(it.id))
                        {
                            c.classifiedPointsList.Remove(it.id);
                        }

                        c.classifiedPointsList.Add(it.id, new FeaturePoint[256, 256]);
                    }
                });

                Parallel.For(0, 256, x =>
                {
                    for (int y = 0; y < 256; y++)
                    {
                        FeaturePoint fp = FeaturePoint.GetOrAddFeaturePoint(x, y, it.id);
                        var output      = Array.Empty <double>();

                        switch (classifier)
                        {
                        case 0:
                            dfprocess(MainWindow.Instance.DecisionForest, fp.GetFeatures(), ref output);
                            break;

                        case 1:
                            mlpprocess(MainWindow.Instance.NeuralNetwork, fp.GetFeatures(), ref output);
                            break;

                        case 2:
                            mlpeprocess(MainWindow.Instance.NeuralNetworkEnsemble, fp.GetFeatures(), ref output);
                            break;

                        default: break;
                        }

                        int predictedClass = 0;
                        for (int k = 1; k < output.Length; k++)
                        {
                            if (output[k] > output[predictedClass])
                            {
                                predictedClass = k;
                            }
                        }

                        MainWindow.GetClassByNum(predictedClass).classifiedPointsList[it.id][fp.y, fp.x] = fp;
                    }
                });

                it.Unlock();

                it.classified = true;

                float t = (float)(DateTime.Now - time).TotalSeconds;

                averageClassificationTime = (averageClassificationTime * classificationDone + t) / (classificationDone + 1);

                classificationDone++;
                ReportProgress(sender);

                classificationQueue--;

                MainWindow.Log("Image (" + p.WorldX + " " + p.WorldY + ") Classification done in " + t + "s. Now waiting for postprocessing");

                GC.Collect();

                it.Status(new PixelColor(0, 255, 0, 100));

                classificationDoneQueue.Enqueue(it.id);
            }
            catch (Exception e)
            {
                MainWindow.Log("ERROR when classifying Image (" + p.WorldX + " " + p.WorldY + "): " + e.Message);

                it.Status(new PixelColor(0, 0, 255, 100));

                if (it.classified)
                {
                    classificationQueue++; classificationDone--;
                }

                it.classified = false;
                it.Unlock();

                errorClassificationRecoveryQueue.Enqueue(p);

                GC.Collect();
            }
        }
        public static void BatchClassification(BackgroundWorker sender, ImageGrid g, Queue <ClassifyProcessParam> q, int classifier, string path)
        {
            int NStep      = 0;
            int blurRadius = 0;

            errorClassificationRecoveryQueue = new ConcurrentQueue <ClassifyProcessParam>();
            classificationDoneQueue          = new ConcurrentQueue <string>();
            postProcessingQueue      = new ConcurrentQueue <string>();
            ErrorPostProcessingQueue = new ConcurrentQueue <string>();
            postprocessDoneQueue     = new ConcurrentQueue <string>();
            outputDone = 0;

            MainWindow.dispatcher.Invoke(() =>
            {
                NStep               = int.Parse(MainWindow.Instance.NSteps.Text);
                blurRadius          = int.Parse(MainWindow.Instance.BlurRadius.Text);
                string[] thresholds = MainWindow.Instance.LonelinessThreshold.Text.Split(';');
                string[] radius     = MainWindow.Instance.LookRadius.Text.Split(';');

                for (int i = 0; i < MainWindow.classesList.Count; i++)
                {
                    int i_t = i < thresholds.Length ? i : thresholds.Length - 1;
                    int i_r = i < radius.Length ? i : radius.Length - 1;

                    MainWindow.classesList[i].threshold = int.Parse(thresholds[i_t]);
                    MainWindow.classesList[i].radius    = int.Parse(radius[i_r]);
                }

                Stitch    = new List <WriteableBitmap>();
                StichLock = new SpinLock();
                for (int i = 0; i < MainWindow.classesList.Count; i += 4)
                {
                    Stitch.Add(new WriteableBitmap(256 * g.width, 256 * g.height, 96, 96, PixelFormats.Bgra32, null));
                }
            });
            postprocessQueue    = 0;
            classificationQueue = 0;

            Size = g.width * g.height;

            postProcessed = new bool[g.width, g.height];
            for (int i = 0; i < g.width; i++)
            {
                for (int j = 0; j < g.height; j++)
                {
                    postProcessed[i, j] = false;
                }
            }

            while (outputDone != Size)
            {
                //PostProcess is prioritize
                if (postprocessQueue < MaxConcurrent)
                {
                    if (ErrorPostProcessingQueue.Count > 0)
                    {
                        ErrorPostProcessingQueue.TryDequeue(out string s);
                        if (s != null)
                        {
                            Task.Run(() => PostProcess(sender, g, s, NStep, blurRadius));
                            postprocessQueue++;
                        }
                    }
                    else if (postProcessingQueue.Count > 0)
                    {
                        postProcessingQueue.TryDequeue(out string s);
                        if (s != null)
                        {
                            Task.Run(() => PostProcess(sender, g, s, NStep, blurRadius));
                            postprocessQueue++;
                        }
                    }
                }

                //Output whenever it is possible, even if all thread are in use
                int n = postprocessDoneQueue.Count;
                for (int i = 0; i < n; i++)
                {
                    postprocessDoneQueue.TryDequeue(out string s);
                    if (s != null)
                    {
                        if (canOutput(g, imageTiles[s]))
                        {
                            Task.Run(() => Output(sender, g, s, path));
                        }
                        else
                        {
                            postprocessDoneQueue.Enqueue(s);
                        }
                    }
                }

                if (classificationQueue < MaxConcurrent)
                {
                    if (errorClassificationRecoveryQueue.Count > 0)
                    {
                        errorClassificationRecoveryQueue.TryDequeue(out ClassifyProcessParam p_);
                        if (p_ != null)
                        {
                            Task.Run(() => Classifiy(sender, g, p_, classifier, NStep, path, blurRadius));
                            classificationQueue++;
                        }
                    }
                    else if (q.Count > 0)
                    {
                        ClassifyProcessParam p = q.Dequeue();
                        Task.Run(() => Classifiy(sender, g, p, classifier, NStep, path, blurRadius));
                        classificationQueue++;
                    }
                }

                n = classificationDoneQueue.Count;
                for (int i = 0; i < n; i++)
                {
                    classificationDoneQueue.TryDequeue(out string s);
                    if (s != null)
                    {
                        if (canPostProcess(g, imageTiles[s]))
                        {
                            postProcessingQueue.Enqueue(s);
                        }
                        else
                        {
                            classificationDoneQueue.Enqueue(s);
                        }
                    }
                }
            }

            while (outputDone != Size)
            {
                Thread.Yield();
                int n = postprocessDoneQueue.Count;
                for (int i = 0; i < n; i++)
                {
                    postprocessDoneQueue.TryDequeue(out string s);
                    if (s != null)
                    {
                        if (canOutput(g, imageTiles[s]))
                        {
                            Task.Run(() => Output(sender, g, s, path));
                        }
                        else
                        {
                            postprocessDoneQueue.Enqueue(s);
                        }
                    }
                }
            }

            OutputStitch(sender, path, blurRadius);

            Thread.Sleep(500);
        }