public static async Task Orchestrate(
            [OrchestrationTrigger] DurableOrchestrationContext ctx)
        {
            var inpaintRequest = ctx.GetInput <InpaintRequest>();

            // TODO: get the setting from parameters
            var settings = new InpaintSettings();

            // TODO: downscale the input image
            // TODO: crop the input image

            var pyramid = await ctx.CallActivityAsync <CloudPyramid>(PyramidsGenerateActivity.Name, inpaintRequest);

            settings.MaxInpaintIterations = 20;
            //settings.PatchMatch.IterationsAmount = 2;
            //var kStep = settings.MeanShift.KDecreaseStep;
            //var minK = settings.MeanShift.MinK;

            for (byte levelIndex = 0; levelIndex < pyramid.LevelsAmount; levelIndex++)
            {
                var imageName   = pyramid.GetImageName(levelIndex);
                var mapping     = pyramid.GetMapping(levelIndex);
                var nnf         = pyramid.GetNnf(levelIndex);
                var inpaintArea = pyramid.GetInpaintArea(levelIndex);
                var mappings    = pyramid.GetSplittedMappings(levelIndex);
                var nnfs        = pyramid.GetSplittedNnfs(levelIndex);

                // TODO: this looks ugly
                var input = NnfInputData.From(nnf, inpaintRequest.Container, imageName, settings, mapping, inpaintArea, false, levelIndex, settings.MeanShift.K, nnfs, mappings);
                await ctx.CallSubOrchestratorAsync(InpaintLevelFunction.Name, input);
            }
        }
        public static async Task BuildNnf([OrchestrationTrigger] DurableOrchestrationContext ctx)
        {
            var input       = ctx.GetInput <NnfInputData>();
            var levelIndex  = input.LevelIndex;
            var settings    = input.Settings;
            var mappings    = input.Mappings;
            var nnfs        = input.SplittedNnfNames;
            var inpaintArea = input.InpaintAreaName;
            var imageName   = input.Image;
            var container   = input.Container;

            var tasks = new Task[mappings.Length];

            var isForward = true;

            for (var pmIteration = 0; pmIteration < settings.PatchMatch.IterationsAmount; pmIteration++)
            {
                // process in parallel
                if (mappings.Length > 1)
                {
                    for (int mapIndex = 0; mapIndex < mappings.Length; mapIndex++)
                    {
                        // TODO: this looks ugly
                        var pminput = NnfInputData.From(nnfs[mapIndex], container, imageName,
                                                        settings, mappings[mapIndex], inpaintArea, isForward, levelIndex,
                                                        settings.MeanShift.K, nnfs, mappings);
                        pminput.PatchMatchIteration = pmIteration;

                        tasks[mapIndex] = ctx.CallActivityAsync(NnfBuildActivity.Name, pminput);
                    }

                    await Task.WhenAll(tasks);

                    // TODO: merge nnf into one
                    await ctx.CallActivityAsync(NnfMergeActivity.Name, (nnfs : nnfs, mappings : mappings, resultNnf : input.NnfName, container : input.Container, input.Mapping));
                }
                else
                {
                    var pminput = NnfInputData.From(input.NnfName, container, imageName,
                                                    settings, input.Mapping, inpaintArea, isForward, levelIndex,
                                                    settings.MeanShift.K, nnfs, mappings);
                    pminput.PatchMatchIteration = pmIteration;

                    await ctx.CallActivityAsync(NnfBuildActivity.Name, pminput);
                }

                isForward = !isForward;
            }
        }