Skip to content

Commit

Permalink
Merge pull request #24 from ibois-epfl/evaluation_suite
Browse files Browse the repository at this point in the history
Evaluation suite
  • Loading branch information
DamienGilliard authored Sep 16, 2024
2 parents c357761 + be0beab commit 739b537
Show file tree
Hide file tree
Showing 13 changed files with 458 additions and 129 deletions.
75 changes: 75 additions & 0 deletions .github/workflows/evaluate_tree_packing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: unoptimized_tree_packing_usage

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
evaluate_tree_packing_usage:
runs-on: windows-latest

steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Set up conda environment
uses: conda-incubator/setup-miniconda@v2
with:
activate-environment: Carnutes
environment-file: environment.yml

- name: activate conda environment
run: conda activate Carnutes

- name: evaluate tree packing usage
run: python -m tests.evaluate_unoptimized_tree_selection

- name: Read RMSE result
id: read-rmse
shell: bash
run: |
if [ -f rmse_result.txt ]; then
rmse=$(cat rmse_result.txt)
echo "rmse=$rmse" >> $GITHUB_ENV
else
echo "rmse=0" >> $GITHUB_ENV
fi
- name: Read tree usage result
id: read-tree-usage
shell: bash
run: |
if [ -f tree_usage_result.txt ]; then
tree_usage=$(cat tree_usage_result.txt)
echo "tree_usage=$tree_usage" >> $GITHUB_ENV
else
echo "tree_usage=0" >> $GITHUB_ENV
fi
- name: Create badges and update README
shell: pwsh
run: |
$rmse_badge = "![RMSE](https://img.shields.io/badge/RMSE-${{ env.rmse }}-c7a8ad)"
$tree_usage_badge = "![Tree Usage](https://img.shields.io/badge/Tree_Usage-${{ env.tree_usage }}-c7a8ad)"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
if ($env:GITHUB_HEAD_REF) {
# For pull requests, use the source branch (GITHUB_HEAD_REF)
$branch_name = $env:GITHUB_HEAD_REF
} else {
# For regular pushes, use the current branch (GITHUB_REF)
$branch_name = $env:GITHUB_REF -replace 'refs/heads/', ''
}
git fetch
git stash
git checkout $branch_name
git pull origin $branch_name --allow-unrelated-histories
(Get-Content README.md) -replace '<!-- RMSE BADGE -->', $rmse_badge -replace '<!-- TREE USAGE BADGE -->', $tree_usage_badge | Set-Content README.md
git add README.md
git add src/database/tree_database.fs.lock
git commit -m "Update badges in README" || echo "No changes to commit"
git push origin HEAD:$branch_name
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
![basic geometry test](https://github.com/ibois-epfl/Carnutes/actions/workflows/test_geo_basics.yml/badge.svg)
![RMSE](https://img.shields.io/badge/RMSE-0.009386021281659763-c7a8ad)
![Tree Usage](https://img.shields.io/badge/Tree_Usage-0.6148631128778858-c7a8ad)

# Carnutes🌳

Expand Down
24 changes: 4 additions & 20 deletions src/find_multiple_trees_without_optimisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from utils import model, tree, geometry, interact_with_rhino, database_reader
from utils import element as elem
from utils.tree import Tree
from packing import packing_manipulations, packing_combinatorics
from packing import packing_combinatorics

import numpy as np
import Rhino
Expand Down Expand Up @@ -52,34 +52,18 @@ def main():
current_model = interact_with_rhino.create_model_from_rhino_selection()

# For each element in the model, replace it with a point cloud. Starting from the elements with the highest degree.
elements = current_model.elements
elements.sort(key=lambda x: x.degree, reverse=True)
for element in elements:
print(f"Element {element.GUID} has degree {element.degree}")

db_path = os.path.dirname(os.path.realpath(__file__)) + "/database/tree_database.fs"

all_rmse = []

for element in elements:
for element in current_model.elements:
if element.type == elem.ElementType.Point:
continue
reference_pc_as_list = []
element_guid = element.GUID
target_diameter = element.diameter
reference_pc_as_list = element.locations

# at this point the reference_pc_as_list should contain the points, but they are not ordered. We need to order them.
reference_pc_as_list = geometry.sort_points(reference_pc_as_list)
reference_pc_as_list = geometry.sort_points(element.locations)
reference_skeleton = geometry.Pointcloud(reference_pc_as_list)
(
best_tree,
best_reference,
best_target,
best_rmse,
) = packing_combinatorics.find_best_tree_unoptimized(
reference_skeleton, target_diameter, db_path, return_rmse=True
)
best_tree, best_rmse = element.allocate_trees(db_path=db_path, optimized=False)
if best_tree is None:
print("No tree found. Skiping this element.")
continue
Expand Down
98 changes: 48 additions & 50 deletions src/packing/packing_combinatorics.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,12 @@ def compute_best_tree_element_matching(
:param minimum_rmse: float
The minimum rmse to consider the alignment as valid
:return: best_model_element: Pointcloud
The best fitting order of the element point cloud
:return: best_skeleton: Pointcloud
The best fitting segment of the skeleton point cloud
:return: best_rmse: float
The rmse of the best fitting segment
"""
best_rmse = minimum_rmse
best_model_element = None
best_skeleton = None

for i in range(2):
Expand All @@ -70,18 +67,18 @@ def compute_best_tree_element_matching(
)
if result.inlier_rmse < best_rmse:
best_rmse = result.inlier_rmse
best_model_element = model_element
best_skeleton = adapted_skeleton
if best_model_element is None:
return None, None, None
return best_model_element, best_skeleton, best_rmse
if best_rmse is None:
return None, None
return best_skeleton, best_rmse


def find_best_tree_unoptimized(
model_element: utils.geometry.Pointcloud,
reference_diameter: float,
database_path: str,
return_rmse: bool = False,
update_database: bool = True,
):
"""
performs icp registrations bewteen the reference skeleton and the list of targets,
Expand All @@ -97,11 +94,11 @@ def find_best_tree_unoptimized(
The path to the database. The database is updated by removing from it the part of the best fitting skeleton.
:param return_rmse: bool
Whether to return the rmse of the best fitting tree. This is for evaluation purposes.
:param update_database: bool
Whether to update the database by removing the best fitting tree from it.
:return: best_tree: Tree
The best fitting tree for which the skeleton was cropped to the best fitting segment
:return: best_reference: Pointcloud
The best fitting segment of the reference point cloud. Only returned if return_rmse is True.
:return: best_target: Pointcloud
The best fitting segment of the target point cloud. Only returned if return_rmse is True.
:return: rmse: float
Expand All @@ -110,10 +107,8 @@ def find_best_tree_unoptimized(
# unpack the database:
reader = db_reader.DatabaseReader(database_path)
n_tree = reader.get_num_trees()
print(
f"Number of trees in the database: {n_tree}. (degub message from find_best_tree in packing_combinatorics.py)"
)
best_tree = None

# initiallize the best rmse to infinity before the first iteration
best_db_level_rmse = np.inf
# iterate over the trees in the database
Expand All @@ -129,7 +124,6 @@ def find_best_tree_unoptimized(
):
continue
(
best_model_element,
best_skeleton_segment,
best_tree_level_rmse,
) = compute_best_tree_element_matching(model_element, tree.skeleton, np.inf)
Expand All @@ -141,51 +135,50 @@ def find_best_tree_unoptimized(
best_tree_id = i
best_db_level_rmse = best_tree_level_rmse
best_tree = tree
best_reference = best_model_element
best_skeleton = best_skeleton_segment

if (
best_db_level_rmse < 0.01
): # if the RMSE is under 1 cm, we can break the loop
break

if best_db_level_rmse is not None:
if best_tree is not None:
# remove the best tree from the database
print(
f"Best tree is {best_tree.id} with rmse {best_db_level_rmse} and height {best_tree.height}",
"and is being trimmed",
f"Best tree is {best_tree.id} with rmse {best_db_level_rmse} and height {best_tree.height}"
)

selected_tree = copy.deepcopy(best_tree)
selected_tree.skeleton = best_skeleton
best_tree.trim(best_skeleton)

# remove the tree from the database if its skeleton is a single point, or empty.
if len(best_tree.skeleton.points) < 2:
reader.root.trees.pop(best_tree_id)
reader.root.n_trees -= 1
transaction.commit()
if update_database:
# remove the tree from the database if its skeleton is a single point, or empty.
if len(best_tree.skeleton.points) < 2:
reader.root.trees.pop(best_tree_id)
reader.root.n_trees -= 1
transaction.commit()
reader.close()

# update the database, as done in https://zodb.org/en/latest/articles/ZODB1.html#a-simple-example
else:
trees_in_db = reader.root.trees
trees_in_db[best_tree_id] = best_tree
reader.root.trees = trees_in_db
transaction.commit()
# close the database
reader.close()

# update the database, as done in https://zodb.org/en/latest/articles/ZODB1.html#a-simple-example
else:
trees_in_db = reader.root.trees
trees_in_db[best_tree_id] = best_tree
reader.root.trees = trees_in_db
transaction.commit()
# close the database
reader.close()
if return_rmse:
return (
selected_tree,
best_reference,
best_skeleton_segment,
best_db_level_rmse,
)
return selected_tree
else:
reader.close()
print("No tree found in find_best_tree, returning None")
return None, None, None, None
return None, None, None


def find_best_tree_optimized(
Expand All @@ -194,6 +187,7 @@ def find_best_tree_optimized(
database_path: str,
optimisation_basis: int,
return_rmse: bool = False,
update_database: bool = True,
):
"""
performs icp registrations bewteen the reference skeleton and the list of targets,
Expand All @@ -211,6 +205,8 @@ def find_best_tree_optimized(
The number of elements to consider for the optimisation
:param return_rmse: bool
Whether to return the rmse of the best fitting tree. This is for evaluation purposes.
:param update_database: bool
Whether to update the database by removing the best fitting tree from it.
:return: best_tree: Tree
The best fitting tree for which the skeleton was cropped to the best fitting segment
Expand Down Expand Up @@ -313,36 +309,38 @@ def find_best_tree_optimized(
if best_db_level_rmse is not None:
# remove the best tree from the database
print(
f"Best tree is {best_tree.id} with rmse {best_db_level_rmse} and height {best_tree.height}",
"and is being trimmed",
f"Best tree is {best_tree.id} with rmse {best_db_level_rmse} and height {best_tree.height}"
)

selected_tree = copy.deepcopy(best_tree)
selected_tree.skeleton = best_skeleton
best_tree.trim(best_skeleton)

# remove the tree from the database if its skeleton is a single point, or empty.
if len(best_tree.skeleton.points) < 2:
reader.root.trees.pop(best_tree_id)
reader.root.n_trees -= 1
transaction.commit()
reader.close()

# update the database, as done in https://zodb.org/en/latest/articles/ZODB1.html#a-simple-example
else:
trees_in_db = reader.root.trees
trees_in_db[best_tree_id] = best_tree
reader.root.trees = trees_in_db
transaction.commit()
# close the database
reader.close()
if update_database:
# remove the tree from the database if its skeleton is a single point, or empty.
if len(best_tree.skeleton.points) < 2:
reader.root.trees.pop(best_tree_id)
reader.root.n_trees -= 1
transaction.commit()

# update the database, as done in https://zodb.org/en/latest/articles/ZODB1.html#a-simple-example
else:
trees_in_db = reader.root.trees
trees_in_db[best_tree_id] = best_tree
reader.root.trees = trees_in_db
transaction.commit()

# close the database
if return_rmse:
return (
selected_tree,
best_reference,
best_skeleton_segment,
best_db_level_rmse,
)
reader.close()
return selected_tree

else:
reader.close()
print("No tree found in find_best_tree, returning None")
Expand Down
2 changes: 1 addition & 1 deletion src/packing/packing_manipulations.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def match_skeletons(
model_element: utils.geometry.Pointcloud,
original_skeleton: utils.geometry.Pointcloud,
) -> utils.geometry.Pointcloud:
""" "
"""
match the two skeletons by adapting the target skeleton to the reference skeleton,
so the distances between the corresponding points are identical.
The number of points in the target_skeleton is thus matched to the number of points in the reference_skeleton.
Expand Down
Loading

0 comments on commit 739b537

Please sign in to comment.