/// <summary>
        /// THIS IS ENTRY METHOD FOR TILING SPECTROGRAMS.
        /// </summary>
        public static void DrawTiles(
            AnalysisIoInputDirectory io,
            ZoomParameters common,
            string analysisTag)
        {
            Log.Info("Begin Draw Super Tiles");
            Contract.Requires(common != null, "common can not be null");
            Contract.Requires(common.SpectrogramZoomingConfig != null, "SpectrogramZoomingConfig can not be null");

            var zoomConfig = common.SpectrogramZoomingConfig;
            LdSpectrogramConfig ldsConfig = common.SpectrogramZoomingConfig.LdSpectrogramConfig;
            var distributions             = common.IndexDistributions;
            var indexGenerationData       = common.IndexGenerationData;
            var indexProperties           = zoomConfig.IndexProperties;

            string fileStem = common.OriginalBasename;

            // scales for false color index spectrograms images in seconds per pixel.
            double[] indexScales = zoomConfig.SpectralIndexScale;

            // default scales for standard (FFT) spectrograms in seconds per pixel.
            double[] standardScales = zoomConfig.SpectralFrameScale;

            ValidateScales(indexScales, indexGenerationData.IndexCalculationDuration.TotalSeconds);

            var shouldRenderStandardScale = !standardScales.IsNullOrEmpty();

            if (!shouldRenderStandardScale)
            {
                Log.Warn("Standard spectrograms will not be rendered");
            }

            var allImageScales = indexScales.Concat(standardScales).ToArray();

            Log.Info("Tiling at scales: " + allImageScales.ToCommaSeparatedList());

            // determine what naming format to use for tiles
            var(namingPattern, alignmentPadding) = GetTilingProfile(
                common,
                zoomConfig,
                indexGenerationData,
                indexScales.Max());

            // pad out image so it produces a whole number of tiles
            // this solves the asymmetric right padding of short audio files
            // var paddedWidth = (int)(Math.Ceiling(zoomConfig.TileWidth / xNominalUnitScale) * xNominalUnitScale);

            // create a new tiler
            // pass it scales for x and y-axis
            // also pass it unit scale relations (between unit scale and unit height/width) to use as a reference point
            var tiler = new Tiler(
                new DirectoryInfo(io.OutputBase.FullName),
                namingPattern,
                xScales: new SortedSet <double>(allImageScales),
                xUnitScale: XNominalUnitScale,
                unitWidth: 1440,
                yScales: new SortedSet <double>(allImageScales.Select(x => 1.0)),
                yUnitScale: 1.0,
                unitHeight: namingPattern.TileHeight);

            var(spectra, filteredIndexProperties) = ZoomCommon.LoadSpectra(io, analysisTag, fileStem, zoomConfig.LdSpectrogramConfig, indexProperties);

            // false color index tiles
            GenerateIndexSpectrogramTiles(
                indexScales,
                ldsConfig,
                filteredIndexProperties,
                zoomConfig,
                spectra,
                indexGenerationData,
                fileStem,
                namingPattern,
                tiler,
                alignmentPadding);

            // standard fft frame spectrograms
            if (shouldRenderStandardScale)
            {
                GenerateStandardSpectrogramTiles(
                    spectra,
                    indexGenerationData,
                    ldsConfig,
                    filteredIndexProperties,
                    zoomConfig,
                    standardScales,
                    fileStem,
                    namingPattern,
                    tiler,
                    alignmentPadding);
            }

            Log.Success("Tiling complete");
        }
        private static (TilingProfile Profile, TimeSpan padding) GetTilingProfile(
            ZoomParameters common,
            SpectrogramZoomingConfig zoomConfig,
            IndexGenerationData indexGeneration,
            double maxScale)
        {
            TilingProfile namingPattern;
            TimeSpan      padding;

            switch (zoomConfig.TilingProfile)
            {
            case nameof(PanoJsTilingProfile):
                namingPattern = new PanoJsTilingProfile();
                padding       = TimeSpan.Zero;

                if (zoomConfig.TileWidth != namingPattern.TileWidth)
                {
                    throw new ConfigFileException(
                              "TileWidth must match the default PanoJS TileWidth of " + namingPattern.TileWidth);
                }

                break;

            case nameof(AbsoluteDateTilingProfile):
                // Zooming spectrograms use multiple color profiles at different levels
                // therefore unable to set a useful tag (like ACI-ENT-EVN).
                if (indexGeneration.RecordingStartDate != null)
                {
                    var recordingStartDate = (DateTimeOffset)indexGeneration.RecordingStartDate;
                    var tilingStartDate    = GetPreviousTileBoundary(
                        zoomConfig.TileWidth,
                        maxScale,
                        recordingStartDate);
                    padding = recordingStartDate - tilingStartDate;

                    // if we're not writing tiles to disk, omit the basename because whatever the container format is
                    // it will have the base name attached
                    namingPattern = new AbsoluteDateTilingProfile(
                        common.OmitBasename ? string.Empty : common.OriginalBasename,
                        "BLENDED.Tile",
                        tilingStartDate,
                        indexGeneration.FrameLength / 2,
                        zoomConfig.TileWidth);
                }
                else
                {
                    throw new ArgumentNullException(
                              nameof(zoomConfig.TilingProfile),
                              "`RecordingStateDate` from the `IndexGenerationData.json` cannot be null when `AbsoluteDateTilingProfile` specified");
                }

                break;

            default:
                throw new ConfigFileException(
                          $"The {nameof(zoomConfig.TilingProfile)} configuration property was set to an unsupported value - no profile known by that name");
            }

            Log.Info($"Tiling had a left padding duration of {padding} to align tiles");
            Log.Info(
                $"Tiling using {namingPattern.GetType().Name}, Tile Width: {namingPattern.TileWidth}, Height: {namingPattern.TileHeight}");
            return(namingPattern, padding);
        }