public void SetAspectRatio()
        {
            const double left   = 0;
            const double top    = 0;
            const double right  = 100;
            const double bottom = 100;
            var          rect   = new DRectangle(left, top, right, bottom);

            rect = DRectangle.SetAspectRatio(rect, 2);

            Assert.AreEqual(rect.Left, -20.917784899841294);
            Assert.AreEqual(rect.Top, 14.791107550079353);
            Assert.AreEqual(rect.Right, 120.91778489984129);
            Assert.AreEqual(rect.Bottom, 85.208892449920654);

            try
            {
                rect = DRectangle.SetAspectRatio(rect, 0);
                Assert.Fail($"{nameof(DRectangle.SetAspectRatio)} should throw {nameof(ArgumentOutOfRangeException)} if ration is 0");
            }
            catch (ArgumentOutOfRangeException e)
            {
            }
        }
        private static int ClusterDataset(CommandLineApplication commandLine)
        {
            // make sure the user entered an argument to this program
            if (commandLine.RemainingArguments.Count != 1)
            {
                Console.WriteLine("The --cluster option requires you to give one XML file on the command line.");
                return(ExitFailure);
            }

            var clusterOption = commandLine.Option("-cluster|--cluster", "", CommandOptionType.SingleValue);
            var sizeOption    = commandLine.Option("-size|--size", "", CommandOptionType.SingleValue);

            var clusterValue = clusterOption.HasValue() ? clusterOption.Value() : "2";
            var sizeValue    = sizeOption.HasValue() ? sizeOption.Value() : "8000";

            if (!uint.TryParse(clusterValue, out var numClusters))
            {
                return(ExitFailure);
            }

            if (!uint.TryParse(sizeValue, out var chipSize))
            {
                return(ExitFailure);
            }

            var argument = commandLine.RemainingArguments[0];

            using (var data = Dlib.ImageDatasetMetadata.LoadImageDatasetMetadata(argument))
            {
                Environment.CurrentDirectory = Path.GetDirectoryName(argument);


                double aspectRatio = MeanAspectRatio(data);

                var images = new Array <Array2D <RgbPixel> >();
                var feats  = new List <Matrix <double> >();

                var dataImages = data.Images;
                //console_progress_indicator pbar(dataImages.Length);
                // extract all the object chips and HOG features.
                Console.WriteLine("Loading image data...");

                for (var i = 0; i < dataImages.Length; ++i)
                {
                    //pbar.print_status(i);
                    if (!HasNonIgnoredBoxes(dataImages[i]))
                    {
                        continue;
                    }

                    using (var img = Dlib.LoadImage <RgbPixel>(dataImages[i].FileName))
                    {
                        var boxes = dataImages[i].Boxes;
                        for (var j = 0; j < boxes.Length; ++j)
                        {
                            if (boxes[j].Ignore || boxes[j].Rect.Area < 10)
                            {
                                continue;
                            }

                            var rect = new DRectangle(boxes[j].Rect);
                            rect = DRectangle.SetAspectRatio(rect, aspectRatio);
                            using (var chipDetail = new ChipDetails(rect, chipSize))
                            {
                                var chip = Dlib.ExtractImageChip <RgbPixel>(img, chipDetail);
                                Dlib.ExtractFHogFeatures <RgbPixel>(chip, out var feature);
                                feats.Add(feature);
                                images.PushBack(chip);
                            }
                        }
                    }
                }

                if (!feats.Any())
                {
                    Console.WriteLine("No non-ignored object boxes found in the XML dataset.  You can't cluster an empty dataset.");
                    return(ExitFailure);
                }

                Console.WriteLine("\nClustering objects...");
                var assignments = AngularCluster(feats, numClusters).ToList();


                // Now output each cluster to disk as an XML file.
                for (uint c = 0; c < numClusters; ++c)
                {
                    // We are going to accumulate all the image metadata for cluster c.  We put it
                    // into idata so we can sort the images such that images with central chips
                    // come before less central chips.  The idea being to get the good chips to
                    // show up first in the listing, making it easy to manually remove bad ones if
                    // that is desired.
                    var idata = new List <Pair <double, Image> >(dataImages.Length);
                    var idx   = 0;
                    for (var i = 0; i < dataImages.Length; ++i)
                    {
                        idata.Add(new Pair <double, Image> {
                            Second = new Image()
                        });

                        idata[i].First           = double.PositiveInfinity;
                        idata[i].Second.FileName = dataImages[i].FileName;

                        if (!HasNonIgnoredBoxes(dataImages[i]))
                        {
                            continue;
                        }

                        var idataBoxes = new List <Box>();
                        var boxes      = dataImages[i].Boxes;
                        for (var j = 0; j < boxes.Length; ++j)
                        {
                            idataBoxes.Add(boxes[j]);

                            if (boxes[j].Ignore || boxes[j].Rect.Area < 10)
                            {
                                continue;
                            }

                            // If this box goes into cluster c then update the score for the whole
                            // image based on this boxes' score.  Otherwise, mark the box as
                            // ignored.
                            if (assignments[idx].C == c)
                            {
                                idata[i].First = Math.Min(idata[i].First, assignments[idx].Distance);
                            }
                            else
                            {
                                idataBoxes.Last().Ignore = true;
                            }

                            ++idx;
                        }

                        idata[i].Second.Boxes = idataBoxes.ToArray();
                    }

                    // now save idata to an xml file.
                    idata.Sort((a, b) =>
                    {
                        var diff = a.First - b.First;
                        return(diff > 0 ? 1 : diff < 0 ? -1 : 0);
                    });

                    using (var cdata = new Dataset())
                    {
                        cdata.Comment = $"{data.Comment}\n\n This file contains objects which were clustered into group {c + 1} of {numClusters} groups with a chip size of {chipSize} by imglab.";
                        cdata.Name    = data.Name;

                        var cdataImages = new List <Image>();
                        for (var i = 0; i < idata.Count; ++i)
                        {
                            // if this image has non-ignored boxes in it then include it in the output.
                            if (!double.IsPositiveInfinity(idata[i].First))
                            {
                                cdataImages.Add(idata[i].Second);
                            }
                        }

                        cdata.Images = cdataImages.ToArray();

                        var outfile = $"cluster_{c + 1:D3}.xml";
                        Console.WriteLine($"Saving {outfile}");
                        Dlib.ImageDatasetMetadata.SaveImageDatasetMetadata(cdata, outfile);
                    }
                }

                // Now output each cluster to disk as a big tiled jpeg file.  Sort everything so, just
                // like in the xml file above, the best objects come first in the tiling.
                assignments.Sort();
                for (uint c = 0; c < numClusters; ++c)
                {
                    var temp = new Array <Array2D <RgbPixel> >();
                    for (var i = 0; i < assignments.Count(); ++i)
                    {
                        if (assignments[i].C == c)
                        {
                            temp.PushBack(images[(int)assignments[i].Index]);
                        }
                    }

                    var outfile = $"cluster_{c + 1:D3}.jpg";
                    Console.WriteLine($"Saving {outfile}");
                    using (var tile = Dlib.TileImages(temp))
                        Dlib.SaveJpeg(tile, outfile);
                }
            }

            return(ExitSuccess);
        }