Additional tools for PB-M3C2

In this notebook, we will provide extension to the PB-M3C2 workflow that will be occasionally useful based on your application.

Generation of non-correspondent pairs

For best training results, the user should provide both pairs of segments that do correspond to each other, as well as pairs of segments that do not correspond. In manual labelling workflows, it is much easier to produce high quality corresponding pairs that it is to produce non-corresponding pairs. Here, we provide a function that allows you to generate pairs of non-corresponding segments automatically based on heuristic. The general procedure is exactly the same as in the base workflow and will not be further explained here.

[1]:
import py4dgeo
[2]:
py4dgeo.set_interactive_backend("vtk")
[3]:
epoch0, epoch1 = py4dgeo.read_from_xyz(
    "plane_horizontal_t1.xyz", "plane_horizontal_t2.xyz"
)
[2024-05-14 13:04:30][INFO] Reading point cloud from file '/home/docs/.cache/py4dgeo/./plane_horizontal_t1.xyz'
[2024-05-14 13:04:30][INFO] Reading point cloud from file '/home/docs/.cache/py4dgeo/./plane_horizontal_t2.xyz'
[4]:
alg = py4dgeo.PBM3C2()
[5]:
(
    xyz_epoch0,
    xyz_epoch1,
    extracted_segments,
) = alg.export_segmented_point_cloud_and_segments(
    epoch0=epoch0,
    epoch1=epoch1,
)
[2024-05-14 13:04:30][INFO] No pipeline parameter is overwritten
[2024-05-14 13:04:30][INFO] No pipeline parameter is overwritten
[2024-05-14 13:04:30][INFO] Transformer Fit
[2024-05-14 13:04:30][INFO] Transformer Transform
[2024-05-14 13:04:30][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:30][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:30][INFO] Transformer Fit
[2024-05-14 13:04:30][INFO] Transformer Transform
[2024-05-14 13:04:30][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:30][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:30][INFO] Transformer Fit
[2024-05-14 13:04:30][INFO] Transformer Transform
[2024-05-14 13:04:30][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:30][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:30][INFO] Transformer Transform
[2024-05-14 13:04:30][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:30][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:31][INFO] Transformer Transform
[2024-05-14 13:04:31][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:31][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:31][INFO] Transformer Fit
[2024-05-14 13:04:31][INFO] Transformer Transform
[2024-05-14 13:04:31][INFO] ----
 The pipeline parameters after restoration are:
{'_Transform_PerPointComputation': PerPointComputation(),
 '_Transform_PerPointComputation__columns': <class 'py4dgeo.pbm3c2.LLSV_PCA_COLUMNS'>,
 '_Transform_PerPointComputation__output_file_name': None,
 '_Transform_PerPointComputation__radius': 10,
 '_Transform_PerPointComputation__skip': False,
 '_Transform_Second_Segmentation': Segmentation(with_previously_computed_segments=True),
 '_Transform_Second_Segmentation__angle_diff_threshold': 1,
 '_Transform_Second_Segmentation__columns': <class 'py4dgeo.pbm3c2.SEGMENTED_POINT_CLOUD_COLUMNS'>,
 '_Transform_Second_Segmentation__distance_3D_threshold': 1.5,
 '_Transform_Second_Segmentation__distance_orthogonal_threshold': 1.5,
 '_Transform_Second_Segmentation__llsv_threshold': 1,
 '_Transform_Second_Segmentation__max_nr_points_neighborhood': 100,
 '_Transform_Second_Segmentation__min_nr_points_per_segment': 5,
 '_Transform_Second_Segmentation__output_file_name': None,
 '_Transform_Second_Segmentation__radius': 2,
 '_Transform_Second_Segmentation__roughness_threshold': 5,
 '_Transform_Second_Segmentation__skip': False,
 '_Transform_Second_Segmentation__with_previously_computed_segments': True,
 '_Transform_Segmentation': Segmentation(),
 '_Transform_Segmentation__angle_diff_threshold': 1,
 '_Transform_Segmentation__columns': <class 'py4dgeo.pbm3c2.SEGMENTED_POINT_CLOUD_COLUMNS'>,
 '_Transform_Segmentation__distance_3D_threshold': 1.5,
 '_Transform_Segmentation__distance_orthogonal_threshold': 1.5,
 '_Transform_Segmentation__llsv_threshold': 1,
 '_Transform_Segmentation__max_nr_points_neighborhood': 100,
 '_Transform_Segmentation__min_nr_points_per_segment': 5,
 '_Transform_Segmentation__output_file_name': None,
 '_Transform_Segmentation__radius': 2,
 '_Transform_Segmentation__roughness_threshold': 5,
 '_Transform_Segmentation__skip': False,
 '_Transform_Segmentation__with_previously_computed_segments': False,
 'memory': None,
 'steps': [('_Transform_PerPointComputation', PerPointComputation()),
           ('_Transform_Segmentation', Segmentation()),
           ('_Transform_Second_Segmentation',
            Segmentation(with_previously_computed_segments=True))],
 'verbose': False}
----

[2024-05-14 13:04:31][INFO] ----
 The pipeline parameters after restoration are:
{'_Transform_ExtractSegments': ExtractSegments(),
 '_Transform_ExtractSegments__columns': <class 'py4dgeo.pbm3c2.SEGMENT_COLUMNS'>,
 '_Transform_ExtractSegments__output_file_name': None,
 '_Transform_ExtractSegments__skip': False,
 'memory': None,
 'steps': [('_Transform_ExtractSegments', ExtractSegments())],
 'verbose': False}
----

Now, we will use labelling data from the file testdata-labelling-correspondent-only.csv, which does not contain any pairs of non-corresponding segments. Running add_no_corresponding_seg on this data, we automatically generate these. There are two heuristics that can be selected through the algorithm parameter: * random: For each segment in one epoch, label a random segment from the neighborhood in the other epoch as non-corresponding. * closes: For each segment in one epoch, take the closest segment in the other epoch and label it non-corresponding.

The neighborhood of a segment is defined by the threshold parameter given as threshold_max_distance.

[6]:
augmented_extended_y = py4dgeo.add_no_corresponding_seg(
    segments=extracted_segments,
    threshold_max_distance=5,
    algorithm="random",
    extended_y_file_name="testdata-labelling-correspondent-only.csv",
)
[2024-05-14 13:04:31][INFO] Reading tuples of (segment epoch0, segment epoch1, label) from file '/home/docs/.cache/py4dgeo/./testdata-labelling-correspondent-only.csv'
[2024-05-14 13:04:31][INFO] Building KDTree structure with leaf parameter 10

We can then run the training algorithm, passing directly the augmented labelling data:

[7]:
alg.training(
    extracted_segments_file_name="extracted_segments.seg",
    extended_y=augmented_extended_y,
)
[2024-05-14 13:04:31][INFO] Reading segments from file '/home/docs/checkouts/readthedocs.org/user_builds/py4dgeo/checkouts/latest/doc/extracted_segments.seg'
[2024-05-14 13:04:31][INFO] Fit ClassifierWrapper
[8]:
distances, uncertainties = alg.compute_distances(epoch0=epoch0, epoch1=epoch1)
[2024-05-14 13:04:31][INFO] PBM3C2.compute_distances(...)
[2024-05-14 13:04:31][INFO] PBM3C2._compute_distances(...)
[2024-05-14 13:04:31][INFO] No pipeline parameter is overwritten
[2024-05-14 13:04:31][INFO] No pipeline parameter is overwritten
[2024-05-14 13:04:31][INFO] Transformer Fit
[2024-05-14 13:04:31][INFO] Transformer Transform
[2024-05-14 13:04:31][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:31][INFO] Transformer Fit
[2024-05-14 13:04:31][INFO] Transformer Transform
[2024-05-14 13:04:31][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:31][INFO] Transformer Fit
[2024-05-14 13:04:31][INFO] Transformer Transform
[2024-05-14 13:04:31][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:31][INFO] Transformer Transform
[2024-05-14 13:04:31][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:31][INFO] Transformer Transform
[2024-05-14 13:04:31][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:31][INFO] Transformer Fit
[2024-05-14 13:04:31][INFO] Transformer Transform
[2024-05-14 13:04:31][INFO] ----
 The pipeline parameters after restoration are:
{'epoch0_Transform_PerPointComputation': PerPointComputation(),
 'epoch0_Transform_PerPointComputation__columns': <class 'py4dgeo.pbm3c2.LLSV_PCA_COLUMNS'>,
 'epoch0_Transform_PerPointComputation__output_file_name': None,
 'epoch0_Transform_PerPointComputation__radius': 10,
 'epoch0_Transform_PerPointComputation__skip': False,
 'epoch0_Transform_Second_Segmentation': Segmentation(with_previously_computed_segments=True),
 'epoch0_Transform_Second_Segmentation__angle_diff_threshold': 1,
 'epoch0_Transform_Second_Segmentation__columns': <class 'py4dgeo.pbm3c2.SEGMENTED_POINT_CLOUD_COLUMNS'>,
 'epoch0_Transform_Second_Segmentation__distance_3D_threshold': 1.5,
 'epoch0_Transform_Second_Segmentation__distance_orthogonal_threshold': 1.5,
 'epoch0_Transform_Second_Segmentation__llsv_threshold': 1,
 'epoch0_Transform_Second_Segmentation__max_nr_points_neighborhood': 100,
 'epoch0_Transform_Second_Segmentation__min_nr_points_per_segment': 5,
 'epoch0_Transform_Second_Segmentation__output_file_name': None,
 'epoch0_Transform_Second_Segmentation__radius': 2,
 'epoch0_Transform_Second_Segmentation__roughness_threshold': 5,
 'epoch0_Transform_Second_Segmentation__skip': False,
 'epoch0_Transform_Second_Segmentation__with_previously_computed_segments': True,
 'epoch0_Transform_Segmentation': Segmentation(),
 'epoch0_Transform_Segmentation__angle_diff_threshold': 1,
 'epoch0_Transform_Segmentation__columns': <class 'py4dgeo.pbm3c2.SEGMENTED_POINT_CLOUD_COLUMNS'>,
 'epoch0_Transform_Segmentation__distance_3D_threshold': 1.5,
 'epoch0_Transform_Segmentation__distance_orthogonal_threshold': 1.5,
 'epoch0_Transform_Segmentation__llsv_threshold': 1,
 'epoch0_Transform_Segmentation__max_nr_points_neighborhood': 100,
 'epoch0_Transform_Segmentation__min_nr_points_per_segment': 5,
 'epoch0_Transform_Segmentation__output_file_name': None,
 'epoch0_Transform_Segmentation__radius': 2,
 'epoch0_Transform_Segmentation__roughness_threshold': 5,
 'epoch0_Transform_Segmentation__skip': False,
 'epoch0_Transform_Segmentation__with_previously_computed_segments': False,
 'memory': None,
 'steps': [('epoch0_Transform_PerPointComputation', PerPointComputation()),
           ('epoch0_Transform_Segmentation', Segmentation()),
           ('epoch0_Transform_Second_Segmentation',
            Segmentation(with_previously_computed_segments=True))],
 'verbose': False}
----

[2024-05-14 13:04:31][INFO] ----
 The pipeline parameters after restoration are:
{'epoch0_Transform_ExtractSegments': ExtractSegments(),
 'epoch0_Transform_ExtractSegments__columns': <class 'py4dgeo.pbm3c2.SEGMENT_COLUMNS'>,
 'epoch0_Transform_ExtractSegments__output_file_name': None,
 'epoch0_Transform_ExtractSegments__skip': False,
 'memory': None,
 'steps': [('epoch0_Transform_ExtractSegments', ExtractSegments())],
 'verbose': False}
----

[2024-05-14 13:04:31][INFO] No pipeline parameter is overwritten
[2024-05-14 13:04:31][INFO] No pipeline parameter is overwritten
[2024-05-14 13:04:31][INFO] Transformer Fit
[2024-05-14 13:04:31][INFO] Transformer Transform
[2024-05-14 13:04:31][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:31][INFO] Transformer Fit
[2024-05-14 13:04:31][INFO] Transformer Transform
[2024-05-14 13:04:31][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:31][INFO] Transformer Fit
[2024-05-14 13:04:31][INFO] Transformer Transform
[2024-05-14 13:04:31][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:32][INFO] Transformer Transform
[2024-05-14 13:04:32][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:32][INFO] Transformer Transform
[2024-05-14 13:04:32][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:32][INFO] Transformer Fit
[2024-05-14 13:04:32][INFO] Transformer Transform
[2024-05-14 13:04:32][INFO] ----
 The pipeline parameters after restoration are:
{'epoch1_Transform_PerPointComputation': PerPointComputation(),
 'epoch1_Transform_PerPointComputation__columns': <class 'py4dgeo.pbm3c2.LLSV_PCA_COLUMNS'>,
 'epoch1_Transform_PerPointComputation__output_file_name': None,
 'epoch1_Transform_PerPointComputation__radius': 10,
 'epoch1_Transform_PerPointComputation__skip': False,
 'epoch1_Transform_Second_Segmentation': Segmentation(with_previously_computed_segments=True),
 'epoch1_Transform_Second_Segmentation__angle_diff_threshold': 1,
 'epoch1_Transform_Second_Segmentation__columns': <class 'py4dgeo.pbm3c2.SEGMENTED_POINT_CLOUD_COLUMNS'>,
 'epoch1_Transform_Second_Segmentation__distance_3D_threshold': 1.5,
 'epoch1_Transform_Second_Segmentation__distance_orthogonal_threshold': 1.5,
 'epoch1_Transform_Second_Segmentation__llsv_threshold': 1,
 'epoch1_Transform_Second_Segmentation__max_nr_points_neighborhood': 100,
 'epoch1_Transform_Second_Segmentation__min_nr_points_per_segment': 5,
 'epoch1_Transform_Second_Segmentation__output_file_name': None,
 'epoch1_Transform_Second_Segmentation__radius': 2,
 'epoch1_Transform_Second_Segmentation__roughness_threshold': 5,
 'epoch1_Transform_Second_Segmentation__skip': False,
 'epoch1_Transform_Second_Segmentation__with_previously_computed_segments': True,
 'epoch1_Transform_Segmentation': Segmentation(),
 'epoch1_Transform_Segmentation__angle_diff_threshold': 1,
 'epoch1_Transform_Segmentation__columns': <class 'py4dgeo.pbm3c2.SEGMENTED_POINT_CLOUD_COLUMNS'>,
 'epoch1_Transform_Segmentation__distance_3D_threshold': 1.5,
 'epoch1_Transform_Segmentation__distance_orthogonal_threshold': 1.5,
 'epoch1_Transform_Segmentation__llsv_threshold': 1,
 'epoch1_Transform_Segmentation__max_nr_points_neighborhood': 100,
 'epoch1_Transform_Segmentation__min_nr_points_per_segment': 5,
 'epoch1_Transform_Segmentation__output_file_name': None,
 'epoch1_Transform_Segmentation__radius': 2,
 'epoch1_Transform_Segmentation__roughness_threshold': 5,
 'epoch1_Transform_Segmentation__skip': False,
 'epoch1_Transform_Segmentation__with_previously_computed_segments': False,
 'memory': None,
 'steps': [('epoch1_Transform_PerPointComputation', PerPointComputation()),
           ('epoch1_Transform_Segmentation', Segmentation()),
           ('epoch1_Transform_Second_Segmentation',
            Segmentation(with_previously_computed_segments=True))],
 'verbose': False}
----

[2024-05-14 13:04:32][INFO] ----
 The pipeline parameters after restoration are:
{'epoch1_Transform_ExtractSegments': ExtractSegments(),
 'epoch1_Transform_ExtractSegments__columns': <class 'py4dgeo.pbm3c2.SEGMENT_COLUMNS'>,
 'epoch1_Transform_ExtractSegments__output_file_name': None,
 'epoch1_Transform_ExtractSegments__skip': False,
 'memory': None,
 'steps': [('epoch1_Transform_ExtractSegments', ExtractSegments())],
 'verbose': False}
----

[2024-05-14 13:04:32][INFO] No pipeline parameter is overwritten
[2024-05-14 13:04:32][INFO] Building KDTree structure with leaf parameter 10
[2024-05-14 13:04:33][INFO] ----
 The pipeline parameters after restoration are:
{'Classifier': ClassifierWrapper(),
 'Classifier__classifier': RandomForestClassifier(),
 'Classifier__classifier__bootstrap': True,
 'Classifier__classifier__ccp_alpha': 0.0,
 'Classifier__classifier__class_weight': None,
 'Classifier__classifier__criterion': 'gini',
 'Classifier__classifier__max_depth': None,
 'Classifier__classifier__max_features': 'sqrt',
 'Classifier__classifier__max_leaf_nodes': None,
 'Classifier__classifier__max_samples': None,
 'Classifier__classifier__min_impurity_decrease': 0.0,
 'Classifier__classifier__min_samples_leaf': 1,
 'Classifier__classifier__min_samples_split': 2,
 'Classifier__classifier__min_weight_fraction_leaf': 0.0,
 'Classifier__classifier__monotonic_cst': None,
 'Classifier__classifier__n_estimators': 100,
 'Classifier__classifier__n_jobs': None,
 'Classifier__classifier__oob_score': False,
 'Classifier__classifier__random_state': None,
 'Classifier__classifier__verbose': 0,
 'Classifier__classifier__warm_start': False,
 'Classifier__columns': <class 'py4dgeo.pbm3c2.SEGMENT_COLUMNS'>,
 'Classifier__diff_between_most_similar_2': 0.1,
 'Classifier__neighborhood_search_radius': 3,
 'Classifier__threshold_probability_most_similar': 0.8,
 'memory': None,
 'steps': [('Classifier', ClassifierWrapper())],
 'verbose': False}
----