def test_convolution2(self):
     # corresponds to convolution_test02 in C++
     sampling.set_focus_coordinates(0, 0)
     vector_field = np.array(
         [[[0., 0.], [0., 0.], [-0.35937524, -0.18750024],
           [-0.13125, -0.17500037]],
          [[0., 0.], [-0.4062504, -0.4062496], [-0.09375, -0.1874992],
           [-0.04375001, -0.17499907]],
          [[0., 0.], [-0.65624946, -0.21874908], [-0.09375, -0.1499992],
           [-0.04375001, -0.21874908]],
          [[0., 0.], [-0.5312497, -0.18750025], [-0.09374999, -0.15000032],
           [-0.13125001, -0.2625004]]],
         dtype=np.float32)
     kernel = np.array([0.06742075, 0.99544406, 0.06742075],
                       dtype=np.float32)
     expected_output = np.array(
         [[[0., 0.], [0., 0.], [-0.37140754, -0.21091977],
           [-0.1575381, -0.19859035]],
          [[0., 0.], [-0.45495197, -0.43135524], [-0.1572882, -0.25023922],
           [-0.06344876, -0.21395193]],
          [[0., 0.], [-0.7203466, -0.2682102], [-0.15751791, -0.20533603],
           [-0.06224134, -0.2577237]],
          [[0., 0.], [-0.57718134, -0.2112256], [-0.14683421, -0.19089346],
           [-0.13971105, -0.2855439]]],
         dtype=np.float32)
     mc.convolve_with_kernel_preserve_zeros(vector_field, np.flip(kernel))
     self.assertTrue(np.allclose(vector_field, expected_output, rtol=0.0))
Exemplo n.º 2
0
    def test_sdf_2_sdf_optimizer01(self):
        canonical_frame_path = "tests/testdata/depth_000000.exr"
        live_frame_path = "tests/testdata/depth_000003.exr"

        if not os.path.exists(canonical_frame_path) or not os.path.exists(
                live_frame_path):
            canonical_frame_path = "testdata/depth_000000.exr"
            live_frame_path = "testdata/depth_000003.exr"

        image_pixel_row = 240

        intrinsic_matrix = np.array(
            [
                [570.3999633789062, 0, 320
                 ],  # FX = 570.3999633789062 CX = 320.0
                [0, 570.3999633789062, 240
                 ],  # FY = 570.3999633789062 CY = 240.0
                [0, 0, 1]
            ],
            dtype=np.float32)
        camera = DepthCamera(intrinsics=DepthCamera.Intrinsics(
            resolution=(480, 640), intrinsic_matrix=intrinsic_matrix))
        field_size = 32
        offset = np.array([-16, -16, 93.4375])

        data_to_use = ImageBasedSingleFrameDataset(
            canonical_frame_path,  # dataset from original sdf2sdf paper, reference frame
            live_frame_path,  # dataset from original sdf2sdf paper, current frame
            image_pixel_row,
            field_size,
            offset,
            camera)

        # depth_interpolation_method = tsdf.DepthInterpolationMethod.NONE
        out_path = "output/test_rigid_out"
        sampling.set_focus_coordinates(0, 0)
        narrow_band_width_voxels = 2.
        iteration = 40
        optimizer = sdf2sdfo.Sdf2SdfOptimizer2d(
            verbosity_parameters=sdf2sdfo.Sdf2SdfOptimizer2d.
            VerbosityParameters(print_max_warp_update=False,
                                print_iteration_energy=False),
            visualization_parameters=sdf2sdfv.Sdf2SdfVisualizer.Parameters(
                out_path=out_path,
                save_initial_fields=False,
                save_final_fields=False,
                save_live_progression=True))
        optimizer.optimize(data_to_use,
                           narrow_band_width_voxels=narrow_band_width_voxels,
                           iteration=iteration)
        expected_twist = np.array([[-0.079572], [0.006052], [0.159114]])
        twist = optimizer.optimize(
            data_to_use,
            narrow_band_width_voxels=narrow_band_width_voxels,
            iteration=iteration)

        self.assertTrue(np.allclose(expected_twist, twist, atol=10e-6))
    def test_convolve_with_kernel_preserve_zeros01(self):
        sampling.set_focus_coordinates(0, 0)
        field = np.array([1, 4, 7, 2, 5, 8, 3, 6, 9], dtype=np.float32).reshape(3, 3)
        vector_field = np.dstack([field] * 2)
        kernel = np.array([1, 2, 3])
        mc.convolve_with_kernel_preserve_zeros(vector_field, np.flip(kernel))
        expected_output = np.dstack([np.array([[85, 168, 99],
                                               [124, 228, 132],
                                               [67, 120, 69]], dtype=np.float32)] * 2)

        self.assertTrue(np.allclose(vector_field, expected_output))
def main():
    data_to_use = ds.PredefinedDatasetEnum.REAL3D_SNOOPY_SET03
    depth_interpolation_method = tsdf.GenerationMethod.NONE
    out_path = "output/hnso"
    sampling.set_focus_coordinates(0, 0)
    generate_test_data = False

    live_field, canonical_field = \
        ds.datasets[data_to_use].generate_2d_sdf_fields(method=depth_interpolation_method)

    if generate_test_data:
        live_field = live_field[36:52, 21:37].copy()
        canonical_field = canonical_field[36:52, 21:37].copy()
        print("initial live:")
        print(repr(live_field))
        print("canonical:")
        print(repr(canonical_field))

    optimizer = hnso.HierarchicalNonrigidSLAMOptimizer2d(
        rate=0.2,
        data_term_amplifier=1.0,
        tikhonov_strength=0.0,
        kernel=generate_1d_sobolev_kernel(size=7, strength=0.1),
        maximum_warp_update_threshold=0.001,
        verbosity_parameters=hnso.HierarchicalNonrigidSLAMOptimizer2d.
        VerbosityParameters(
            print_max_warp_update=True,
            print_iteration_data_energy=True,
            print_iteration_tikhonov_energy=True,
        ),
        visualization_parameters=hnsov.HNSOVisualizer.Parameters(
            out_path=out_path,
            save_live_progression=True,
            save_initial_fields=True,
            save_final_fields=True,
            save_warp_field_progression=True,
            save_data_gradients=True,
            save_tikhonov_gradients=False))
    warp_field = optimizer.optimize(canonical_field, live_field)

    if generate_test_data:
        resampled_live = resampling.resample_field(live_field, warp_field)
        print("final live:")
        print(repr(resampled_live))
        print("warp field:")
        print(repr(warp_field))

    return EXIT_CODE_SUCCESS
    def test_nonrigid_optimization02(self):
        sampling.set_focus_coordinates(0, 0)
        field_size = 4
        live_field_template = np.array(
            [[1., 1., 0.49999955, 0.42499956],
             [1., 0.44999936, 0.34999937, 0.32499936],
             [1., 0.35000065, 0.25000066, 0.22500065],
             [1., 0.20000044, 0.15000044, 0.07500044]],
            dtype=np.float32)
        live_field = live_field_template.copy()
        canonical_field = np.array(
            [[1.0000000e+00, 1.0000000e+00, 3.7499955e-01, 2.4999955e-01],
             [1.0000000e+00, 3.2499936e-01, 1.9999936e-01, 1.4999935e-01],
             [1.0000000e+00, 1.7500064e-01, 1.0000064e-01, 5.0000645e-02],
             [1.0000000e+00, 7.5000443e-02, 4.4107438e-07, -9.9999562e-02]],
            dtype=np.float32)
        optimizer = make_optimizer(ComputeMethod.DIRECT, field_size, 2)
        optimizer.optimize(live_field, canonical_field)
        expected_live_field_out = np.array(
            [[1., 1., 0.48917317, 0.43777004],
             [1., 0.43342987, 0.3444094, 0.3287867],
             [1., 0.33020678, 0.24566807, 0.22797936],
             [1., 0.2261582, 0.17907946, 0.14683424]],
            dtype=np.float32)

        report1 = optimizer.get_convergence_report()

        self.assertTrue(np.allclose(live_field, expected_live_field_out))
        live_field = live_field_template.copy()
        optimizer = make_optimizer(ComputeMethod.VECTORIZED, field_size, 2)
        optimizer.optimize(live_field, canonical_field)
        self.assertTrue(np.allclose(live_field, expected_live_field_out))

        report2 = optimizer.get_convergence_report()
        self.assertTrue(report1 == report2)

        expected_warp_stats = \
            cpp_module.WarpDeltaStatistics2d(0.272727, 0.0, 0.0684823, 0.0364445,
                                             0.0167321, cpp_module.Vector2i(1, 2), False, False)
        expected_diff_stats = \
            cpp_module.TsdfDifferenceStatistics2d(0, 0.246834, 0.111843, 0.0812234, cpp_module.Vector2i(3, 3))

        expected_report = cpp_module.ConvergenceReport2d(
            2, True, expected_warp_stats, expected_diff_stats)

        self.assertTrue(report2 == expected_report)
Exemplo n.º 6
0
def main():
    canonical_frame_path = "../Data/Synthetic_Kenny_Circle/depth_000000.exr"
    live_frame_path = "../Data/Synthetic_Kenny_Circle/depth_000003.exr"
    image_pixel_row = 240

    intrinsic_matrix = np.array(
        [
            [570.3999633789062, 0, 320],  # FX = 570.3999633789062 CX = 320.0
            [0, 570.3999633789062, 240],  # FY = 570.3999633789062 CY = 240.0
            [0, 0, 1]
        ],
        dtype=np.float32)
    camera = DepthCamera(intrinsics=DepthCamera.Intrinsics(
        resolution=(480, 640), intrinsic_matrix=intrinsic_matrix))
    field_size = 32
    # offset = np.array([-16, -16, 102.875])
    offset = np.array([-16, -16, 93.4375])

    data_to_use = ImageBasedSingleFrameDataset(
        canonical_frame_path,  # dataset from original sdf2sdf paper, reference frame
        live_frame_path,  # dataset from original sdf2sdf paper, current frame
        image_pixel_row,
        field_size,
        offset,
        camera)

    # depth_interpolation_method = tsdf.DepthInterpolationMethod.NONE
    out_path = "output/sdf_2_sdf"
    sampling.set_focus_coordinates(0, 0)
    narrow_band_width_voxels = 2.
    iteration = 40
    optimizer = sdf2sdfo.Sdf2SdfOptimizer2d(
        verbosity_parameters=sdf2sdfo.Sdf2SdfOptimizer2d.VerbosityParameters(
            print_max_warp_update=True, print_iteration_energy=True),
        visualization_parameters=sdf2sdfv.Sdf2SdfVisualizer.Parameters(
            out_path=out_path,
            save_initial_fields=True,
            save_final_fields=True,
            save_live_progression=True))
    optimizer.optimize(data_to_use,
                       narrow_band_width_voxels=narrow_band_width_voxels,
                       iteration=iteration)

    return EXIT_CODE_SUCCESS
    def test_convolve_with_kernel_2d(self):
        sampling.set_focus_coordinates(0, 0)
        vector_field = np.array([[[0., 0.],
                                  [0., 0.],
                                  [-0.35937524, -0.18750024],
                                  [-0.13125, -0.17500037]],

                                 [[0., 0.],
                                  [-0.4062504, -0.4062496],
                                  [-0.09375, -0.1874992],
                                  [-0.04375001, -0.17499907]],

                                 [[0., 0.],
                                  [-0.65624946, -0.21874908],
                                  [-0.09375, -0.1499992],
                                  [-0.04375001, -0.21874908]],

                                 [[0., 0.],
                                  [-0.5312497, -0.18750025],
                                  [-0.09374999, -0.15000032],
                                  [-0.13125001, -0.2625004]]], dtype=np.float32)
        kernel = np.array([0.06742075, 0.99544406, 0.06742075], dtype=np.float32)
        mc.convolve_with_kernel(vector_field, np.flip(kernel))

        expected_output = np.array([[[-0.00184663, -0.00184663],
                                     [-0.05181003, -0.04070097],
                                     [-0.37325418, -0.2127664],
                                     [-0.1575381, -0.19859035]],

                                    [[-0.03024794, -0.0282592],
                                     [-0.45495197, -0.43135524],
                                     [-0.1572882, -0.25023922],
                                     [-0.06344876, -0.21395193]],

                                    [[-0.04830472, -0.01737996],
                                     [-0.7203466, -0.2682102],
                                     [-0.15751791, -0.20533603],
                                     [-0.06224134, -0.2577237]],

                                    [[-0.03863709, -0.01357815],
                                     [-0.57718134, -0.2112256],
                                     [-0.14683421, -0.19089346],
                                     [-0.13971105, -0.2855439]]], dtype=np.float32)
        self.assertTrue(np.allclose(vector_field, expected_output))
    def test_nonrigid_optimization01(self):
        # corresponds to test case test_sobolev_optimizer01 for C++
        sampling.set_focus_coordinates(0, 0)
        field_size = 4
        live_field_template = np.array(
            [[1., 1., 0.49999955, 0.42499956],
             [1., 0.44999936, 0.34999937, 0.32499936],
             [1., 0.35000065, 0.25000066, 0.22500065],
             [1., 0.20000044, 0.15000044, 0.07500044]],
            dtype=np.float32)
        live_field = live_field_template.copy()
        canonical_field = np.array(
            [[1.0000000e+00, 1.0000000e+00, 3.7499955e-01, 2.4999955e-01],
             [1.0000000e+00, 3.2499936e-01, 1.9999936e-01, 1.4999935e-01],
             [1.0000000e+00, 1.7500064e-01, 1.0000064e-01, 5.0000645e-02],
             [1.0000000e+00, 7.5000443e-02, 4.4107438e-07, -9.9999562e-02]],
            dtype=np.float32)

        expected_live_field_out = np.array(
            [[1., 1., 0.49408937, 0.4321034],
             [1., 0.44113636, 0.34710377, 0.32715625],
             [1., 0.3388706, 0.24753733, 0.22598255],
             [1., 0.21407352, 0.16514614, 0.11396749]],
            dtype=np.float32)
        expected_warps_out = np.array(
            [[[0., 0.], [0., 0.], [0.03714075, 0.02109198],
              [0.01575381, 0.01985904]],
             [[0., 0.], [0.0454952, 0.04313552], [0.01572882, 0.02502392],
              [0.00634488, 0.02139519]],
             [[0., 0.], [0.07203466, 0.02682102], [0.01575179, 0.0205336],
              [0.00622413, 0.02577237]],
             [[0., 0.], [0.05771814, 0.02112256], [0.01468342, 0.01908935],
              [0.01397111, 0.02855439]]],
            dtype=np.float32)
        live_field = live_field_template.copy()
        optimizer = make_optimizer(ComputeMethod.VECTORIZED, field_size, 1)
        optimizer.optimize(live_field, canonical_field)
        self.assertTrue(np.allclose(live_field, expected_live_field_out))
        optimizer = make_optimizer(ComputeMethod.DIRECT, field_size, 1)
        live_field = live_field_template.copy()
        optimizer.optimize(live_field, canonical_field)
        self.assertTrue(np.allclose(live_field, expected_live_field_out))
Exemplo n.º 9
0
def main():
    data_to_use = ds.PredefinedDatasetEnum.REAL3D_SNOOPY_SET05
    # tsdf_generation_method = tsdf.GenerationMethod.EWA_TSDF_INCLUSIVE_CPP
    tsdf_generation_method = tsdf.GenerationMethod.BASIC
    # optimizer_implementation_language = build_opt.ImplementationLanguage.CPP
    optimizer_implementation_language = build_opt.ImplementationLanguage.CPP
    visualize_and_save_initial_and_final_fields = False
    out_path = "output/ho/single"
    if not os.path.exists(out_path):
        os.makedirs(out_path)

    sampling.set_focus_coordinates(0, 0)
    generate_test_data = False

    live_field, canonical_field = \
        ds.datasets[data_to_use].generate_2d_sdf_fields(method=tsdf_generation_method, smoothing_coefficient=0.5)

    view_scaling_factor = 1024 // ds.datasets[data_to_use].field_size

    if visualize_and_save_initial_and_final_fields:
        viz.visualize_and_save_initial_fields(canonical_field, live_field,
                                              out_path, view_scaling_factor)

    if generate_test_data:
        live_field = live_field[36:52, 21:37].copy()
        canonical_field = canonical_field[36:52, 21:37].copy()

    shared_parameters = build_opt.HierarchicalOptimizer2dSharedParameters()
    shared_parameters.maximum_warp_update_threshold = 0.01
    shared_parameters.maximum_iteration_count = 100
    verbosity_parameters_py = build_opt.make_common_hierarchical_optimizer2d_py_verbosity_parameters(
    )
    verbosity_parameters_cpp = ho_cpp.HierarchicalOptimizer2d.VerbosityParameters(
        print_max_warp_update=True,
        print_iteration_mean_tsdf_difference=True,
        print_iteration_std_tsdf_difference=True,
        print_iteration_data_energy=True,
        print_iteration_tikhonov_energy=True,
    )
    visualization_parameters_py = build_opt.make_common_hierarchical_optimizer2d_visualization_parameters(
    )
    visualization_parameters_py.out_path = out_path
    logging_parameters_cpp = ho_cpp.HierarchicalOptimizer2d.LoggingParameters(
        collect_per_level_convergence_reports=True,
        collect_per_level_iteration_data=True)
    resampling_strategy_cpp = ho_cpp.HierarchicalOptimizer2d.ResamplingStrategy.NEAREST_AND_AVERAGE
    #resampling_strategy_cpp = ho_cpp.HierarchicalOptimizer2d.ResamplingStrategy.LINEAR

    optimizer = build_opt.make_hierarchical_optimizer2d(
        implementation_language=optimizer_implementation_language,
        shared_parameters=shared_parameters,
        verbosity_parameters_cpp=verbosity_parameters_cpp,
        logging_parameters_cpp=logging_parameters_cpp,
        verbosity_parameters_py=verbosity_parameters_py,
        visualization_parameters_py=visualization_parameters_py,
        resampling_strategy_cpp=resampling_strategy_cpp)

    warp_field = optimizer.optimize(canonical_field, live_field)

    if optimizer_implementation_language == build_opt.ImplementationLanguage.CPP:
        print(
            "==================================================================================="
        )
        print_convergence_reports(
            optimizer.get_per_level_convergence_reports())
        telemetry_log = optimizer.get_per_level_iteration_data()
        metadata = viz_ho.get_telemetry_metadata(telemetry_log)
        frame_count = viz_ho.get_number_of_frames_to_save_from_telemetry_logs(
            [telemetry_log])
        progress_bar = progressbar.ProgressBar(max_value=frame_count)
        viz_ho.convert_cpp_telemetry_logs_to_video(telemetry_log,
                                                   metadata,
                                                   canonical_field,
                                                   live_field,
                                                   out_path,
                                                   progress_bar=progress_bar)

    warped_live = resampling.warp_field(live_field, warp_field)

    if visualize_and_save_initial_and_final_fields:
        viz.visualize_final_fields(canonical_field, warped_live,
                                   view_scaling_factor)

    return EXIT_CODE_SUCCESS
def perform_multiple_tests(
        start_from_sample=0,
        data_term_method=DataTermMethod.BASIC,
        optimizer_choice=OptimizerChoice.CPP,
        depth_interpolation_method=GenerationMethod.BASIC,
        out_path="out2D/Snoopy MultiTest",
        input_case_file=None,
        calibration_path="/media/algomorph/Data/Reconstruction/real_data/snoopy/snoopy_calib.txt",
        frame_path="/media/algomorph/Data/Reconstruction/real_data/snoopy/frames/",
        z_offset=128):
    # CANDIDATES FOR ARGS
    save_initial_and_final_fields = input_case_file is not None
    enable_warp_statistics_logging = input_case_file is not None
    save_frame_images = input_case_file is not None
    use_masks = True

    # TODO a tiled image with 6x6 bad cases and 6x6 good cases (SDF fields)
    save_tiled_good_vs_bad_case_comparison_image = True
    save_per_case_results_in_root_output_folder = False

    rebuild_optimizer = optimizer_choice != OptimizerChoice.CPP
    max_iterations = 400 if optimizer_choice == OptimizerChoice.CPP else 100

    # dataset location
    frame_count, frame_filename_format, use_masks = shared.check_frame_count_and_format(
        frame_path, not use_masks)
    if frame_filename_format == shared.FrameFilenameFormat.SIX_DIGIT:
        frame_path_format_string = frame_path + os.path.sep + "depth_{:0>6d}.png"
        mask_path_format_string = frame_path + os.path.sep + "mask_{:0>6d}.png"
    else:  # has to be FIVE_DIGIT
        frame_path_format_string = frame_path + os.path.sep + "depth_{:0>5d}.png"
        mask_path_format_string = frame_path + os.path.sep + "mask_{:0>5d}.png"

    # CANDIDATES FOR ARGS
    field_size = 128
    offset = [-64, -64, z_offset]
    line_range = (214, 400)
    view_scaling_factor = 1024 // field_size

    # region ================ Generation of lists of frames & pixel rows to work with ==================================
    check_empty_row = True

    if input_case_file:
        frame_row_and_focus_set = np.genfromtxt(input_case_file,
                                                delimiter=",",
                                                dtype=np.int32)
        # drop column headers
        frame_row_and_focus_set = frame_row_and_focus_set[1:]
        # drop live frame indexes
        frame_row_and_focus_set = np.concatenate(
            (frame_row_and_focus_set[:, 0].reshape(
                -1, 1), frame_row_and_focus_set[:, 2].reshape(
                    -1, 1), frame_row_and_focus_set[:, 3:5]),
            axis=1)
    else:
        frame_set = list(range(0, frame_count - 1, 5))
        pixel_row_set = line_range[0] + (
            (line_range[1] - line_range[0]) *
            np.random.rand(len(frame_set))).astype(np.int32)
        focus_x = np.zeros((
            len(frame_set),
            1,
        ))
        focus_y = np.zeros((
            len(frame_set),
            1,
        ))
        frame_row_and_focus_set = zip(frame_set, pixel_row_set, focus_x,
                                      focus_y)
        if check_empty_row:
            # replace empty rows
            new_pixel_row_set = []
            for canonical_frame_index, pixel_row_index, _, _ in frame_row_and_focus_set:
                live_frame_index = canonical_frame_index + 1
                canonical_frame_path = frame_path_format_string.format(
                    canonical_frame_index)
                canonical_mask_path = mask_path_format_string.format(
                    canonical_frame_index)
                live_frame_path = frame_path_format_string.format(
                    live_frame_index)
                live_mask_path = mask_path_format_string.format(
                    live_frame_index)
                while shared.is_image_row_empty(canonical_frame_path, canonical_mask_path, pixel_row_index, use_masks) \
                        or shared.is_image_row_empty(live_frame_path, live_mask_path, pixel_row_index, use_masks):
                    pixel_row_index = line_range[0] + (
                        line_range[1] - line_range[0]) * np.random.rand()
                new_pixel_row_set.append(pixel_row_index)
            frame_row_and_focus_set = zip(frame_set, pixel_row_set, focus_x,
                                          focus_y)

    # endregion ========================================================================================================

    # logging
    convergence_status_log = []
    convergence_status_log_file_path = os.path.join(
        out_path, "convergence_status_log.csv")

    max_case_count = 36
    good_case_sdfs = []
    bad_case_sdfs = []

    save_log_every_n_runs = 5

    if start_from_sample == 0 and os.path.exists(
            os.path.join(out_path, "output_log.txt")):
        os.unlink(os.path.join(out_path, "output_log.txt"))

    i_sample = 0

    optimizer = None if rebuild_optimizer else \
        build_optimizer(optimizer_choice, out_path, field_size, view_scaling_factor=8, max_iterations=max_iterations,
                        enable_warp_statistics_logging=enable_warp_statistics_logging,
                        data_term_method=data_term_method)

    # run the optimizers
    for canonical_frame_index, pixel_row_index, focus_x, focus_y in frame_row_and_focus_set:
        if i_sample < start_from_sample:
            i_sample += 1
            continue

        sampling.set_focus_coordinates(focus_x, focus_y)

        live_frame_index = canonical_frame_index + 1
        out_subpath = os.path.join(
            out_path, "frames {:0>6d}-{:0>6d} line {:0>3d}".format(
                canonical_frame_index, live_frame_index, pixel_row_index))

        canonical_frame_path = frame_path_format_string.format(
            canonical_frame_index)
        canonical_mask_path = mask_path_format_string.format(
            canonical_frame_index)
        live_frame_path = frame_path_format_string.format(live_frame_index)
        live_mask_path = mask_path_format_string.format(live_frame_index)

        if save_frame_images:

            def highlight_row_and_save_image(path_to_original, output_name,
                                             ix_row):
                output_image = highlight_row_on_gray(
                    rescale_depth_to_8bit(
                        cv2.imread(path_to_original, cv2.IMREAD_UNCHANGED)),
                    ix_row)
                cv2.imwrite(os.path.join(out_subpath, output_name),
                            output_image)

            highlight_row_and_save_image(canonical_frame_path,
                                         "canonical_frame_rh.png",
                                         pixel_row_index)
            highlight_row_and_save_image(live_frame_path, "live_frame_rh.png",
                                         pixel_row_index)

        # Generate SDF fields
        if use_masks:
            dataset = MaskedImageBasedFramePairDataset(
                calibration_path, canonical_frame_path, canonical_mask_path,
                live_frame_path, live_mask_path, pixel_row_index, field_size,
                offset)
        else:
            dataset = ImageBasedFramePairDataset(calibration_path,
                                                 canonical_frame_path,
                                                 live_frame_path,
                                                 pixel_row_index, field_size,
                                                 offset)

        live_field, canonical_field = dataset.generate_2d_sdf_fields(
            method=depth_interpolation_method)

        if save_initial_and_final_fields:
            save_initial_fields(canonical_field, live_field, out_subpath,
                                view_scaling_factor)

        print(
            "{:s} OPTIMIZATION BETWEEN FRAMES {:0>6d} AND {:0>6d} ON LINE {:0>3d}{:s}"
            .format(BOLD_LIGHT_CYAN, canonical_frame_index, live_frame_index,
                    pixel_row_index, RESET),
            end="")

        if rebuild_optimizer:
            optimizer = build_optimizer(
                optimizer_choice,
                out_subpath,
                field_size,
                view_scaling_factor=8,
                max_iterations=max_iterations,
                enable_warp_statistics_logging=enable_warp_statistics_logging,
                data_term_method=data_term_method)
        original_live_field = live_field.copy()
        live_field = optimizer.optimize(live_field, canonical_field)

        # ===================== LOG AFTER-RUN RESULTS ==================================================================

        if save_initial_and_final_fields:
            save_final_fields(canonical_field, live_field, out_subpath,
                              view_scaling_factor)

        if optimizer_choice != OptimizerChoice.CPP:
            # call python-specific logging routines
            optimizer.plot_logged_sdf_and_warp_magnitudes()
            optimizer.plot_logged_energies_and_max_warps()
        else:
            # call C++-specific logging routines
            if enable_warp_statistics_logging:
                warp_statistics = optimizer.get_warp_statistics_as_matrix()
                root_subpath = os.path.join(
                    out_path,
                    "warp_statistics_frames_{:0>6d}-{:0>6d}_row_{:0>3d}.png".
                    format(canonical_frame_index, live_frame_index,
                           pixel_row_index))
                if save_per_case_results_in_root_output_folder:
                    plot_warp_statistics(out_subpath,
                                         warp_statistics,
                                         extra_path=root_subpath)
                else:
                    plot_warp_statistics(out_subpath,
                                         warp_statistics,
                                         extra_path=None)

        convergence_report = optimizer.get_convergence_report()
        max_warp_at_cpp = convergence_report.warp_delta_statistics.longest_warp_location
        max_warp_at = Point2d(max_warp_at_cpp.x, max_warp_at_cpp.y)
        if not convergence_report.iteration_limit_reached:
            if convergence_report.warp_delta_statistics.is_largest_above_max_threshold:
                print(": DIVERGED", end="")
            else:
                print(": CONVERGED", end="")

            if (save_tiled_good_vs_bad_case_comparison_image
                    and not convergence_report.warp_delta_statistics.
                    is_largest_above_max_threshold
                    and len(good_case_sdfs) < max_case_count):
                good_case_sdfs.append(
                    (canonical_field, original_live_field, max_warp_at))

        else:
            print(": NOT CONVERGED", end="")
            if save_tiled_good_vs_bad_case_comparison_image and len(
                    bad_case_sdfs) < max_case_count:
                bad_case_sdfs.append(
                    (canonical_field, original_live_field, max_warp_at))
        print(" IN", convergence_report.iteration_count, "ITERATIONS")

        log_convergence_status(convergence_status_log, convergence_report,
                               canonical_frame_index, live_frame_index,
                               pixel_row_index)

        if rebuild_optimizer:
            del optimizer
            plt.close('all')
            gc.collect()

        i_sample += 1

        if i_sample % save_log_every_n_runs == 0:
            record_convergence_status_log(convergence_status_log,
                                          convergence_status_log_file_path)

    record_convergence_status_log(convergence_status_log,
                                  convergence_status_log_file_path)
    record_cases_files(convergence_status_log, out_path)
    if save_tiled_good_vs_bad_case_comparison_image:
        if len(good_case_sdfs) > 0 and len(bad_case_sdfs) > 0:
            save_tiled_tsdf_comparison_image(
                os.path.join(out_path, "good_vs_bad.png"), good_case_sdfs,
                bad_case_sdfs)
        else:
            if len(good_case_sdfs) == 0:
                print(
                    "Warning: no 'good' cases; skipping saving comparison image"
                )
            elif len(bad_case_sdfs) == 0:
                print(
                    "Warning: no 'bad' cases; skipping saving comparison image"
                )