Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated use of weights in procrustes analysis (update to #1647) #2313

Merged
merged 1 commit into from
Dec 3, 2024

Conversation

tillns
Copy link

@tillns tillns commented Oct 30, 2024

Hi Michael,

I noticed that the use of the weights parameter can lead to incorrect results, e.g., undesired shifts in the transformed points. I reviewed my own changes two years ago in #1647, and I noticed that I oversimplified the problem. Apparently, a procrustes analysis with non-uniform weights cannot be solved analytically but requires iterative optimization approaches. I updated the documentation and also changed the translation and scaling computation of b to also incorporate the weights to avoid some of the undesired effects. As it is now, the code should yield the optimal solution for uniform weights (as usual) and for binary weights; for the latter, I added some code to filter out zero-weighted points from the transform computations. For general non-uniform weights, the optimal translation and scaling are computed, but they're based on a possibly suboptimal rotation. If we wanted to find a better rotation, an iterative optimization approach would need to be added, but I was too lazy to do that haha

Cheers
Till

@mikedh
Copy link
Owner

mikedh commented Oct 31, 2024

Awesome thanks for the follow-up PR!! I've been really impressed with the performance and results in procustes (i.e. using it in reconstruct_instances works shockingly well).

Happy to merge this whenever tests pass! Also if you had a quick test case you could add which demonstrated the prior bad behavior of "undesired shifts in the transformed points" that would be great.

@tillns
Copy link
Author

tillns commented Nov 7, 2024

Hi Michael
Sorry, I only have limited time for this. I checked the test, and the reason it fails is because it assumes that the transformation matrix should be different with procrustes when non-uniform weights should be used, even if no translation and scaling is applied, only rotation. The way I implemented it now, the weights are not incorporated for the rotation. As I wrote, this would require an iterative optimization instead of an analytical LinAlg algorithm, so I'd say it's beyond the scope of this method. They way I integrated the weights now is that they're used to find the optimal translation and scaling, but they're not used in the computation for the rotation, which is why the test now fails; I had used them there before, but that implementation was faulty. I updated the documentation of the method to explain this. What still works analytically correctly is to use binary weighting, so the user can filter out certain points if they think they're not important with a weights parameter like this [0, 0, 1, 0, 1, 1, ...]. If you agree that this is desired behavior, we could adjust the test to only expect different transformation if translation and/or scaling are enabled. If you'd rather we stick to uniform or binary weights and just forbid other weights completely, I could also adjust the code accordingly.

Examples of bad behavior before:
image
In the image above, I loaded the blender monkey mesh (left) and created a shifted copy of it (right). If I try to realign the shifted copy with the original using uniform weighting, I get perfect alignment (as expected). However, if I now try to realign the shifted copy with the original using binary weights [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, ....] (so only the first twenty points are weighted 1, all other weighted zero; 1-weighted points are highlighted in red), I get a weird transformation, as shown below:
image
Note that even the scaling is wrong here. This shows that the previous handling of weights was indeed wrong.

@mikedh mikedh mentioned this pull request Nov 9, 2024
@mikedh mikedh changed the base branch from main to release/weight December 3, 2024 16:52
@mikedh mikedh merged commit 145ba83 into mikedh:release/weight Dec 3, 2024
2 of 9 checks passed
@mikedh
Copy link
Owner

mikedh commented Dec 3, 2024

Sorry for the delay on this! Yeah boolean weights with a todo make sense to me, I'll play around in a branch. Thanks for the PR!

mikedh added a commit that referenced this pull request Jan 21, 2025
A very common source of annoyance and confusion is that `trimesh.load`
can return lots of different types depending on what type of file was
passed (i.e. #2239). This refactor changes the return types for the
loading functions to:

- `trimesh.load_scene -> Scene`
- This loads into a `Scene`, the most general container which can hold
any loadable type. Most people should probably use this to load
geometry.
- `trimesh.load_mesh -> Trimesh`
- Forces all mesh objects in a scene into a single `Trimesh` object.
This potentially has to drop information and irreversibly concatenate
multiple meshes.
- The implementation of the concatenation logic is now in
`Scene.to_mesh` rather than load.
- `trimesh.load_path -> Path`
- This loads into either a `Path2D` or `Path3D` which both inherit from
`Path`
- `trimesh.load -> Geometry`
- This was the original load entry point and is deprecated, but there
are no current plans to remove it. It has been modified into a thin
wrapper for `load_scene` that attempts to match the behavior of the
previous loader for backwards compatibility. In my testing against the
current `main` branch it was returning the same types [99.8% of the
time](https://gist.github.com/mikedh/8de541e066ce842932b1f6cd97c214ca)
although there may be other subtle differences.
- `trimesh.load(..., force='mesh')` will emit a deprecation warning in
favor of `load_mesh`
- `trimesh.load(..., force='scene')` will emit a deprecation warning in
favor of `load_scene`

Additional changes:
- Removes `Geometry.metadata['file_path']` in favor of
`Geometry.source.file_path`. Everything that inherits from `Geometry`
should now have a `.source` attribute which is a typed dataclass. This
was something of a struggle as `file_path` was populated into metadata
on load, but we also try to make sure `metadata` is preserved through
round-trips if at all possible. And so the `load` inserted *different*
keys into the metadata. Making it first-class information rather than a
loose key seems like an improvement, but users will have to replace
`mesh.metadata["file_name"]` with `mesh.source.file_name`.
- Moves all network fetching into `WebResolver` so it can be more easily
gated by `allow_remote`.
- Removes code for the following deprecations:
- January 2025 deprecation for `trimesh.resources.get` in favor of the
typed alternatives (`get_json`, `get_bytes`, etc).
- January 2025 deprecation for `Scene.deduplicated` in favor of a very
short list comprehension on `Scene.duplicate_nodes`
- March 2024 deprecation for `trimesh.graph.smoothed` in favor of
`trimesh.graph.smooth_shaded`.
- Adds the following new deprecations:
- January 2026 `Path3D.to_planar` -> `Path3D.to_2D` to be consistent
with `Path2D.to_3D`.
- Fixes #2335 
- Fixes #2330 
- Fixes #2239
- Releases #2313 
- Releases #2327 
- Releases #2336
- Releases #2339
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants