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

fix bbox planar intersection #609

Merged
merged 6 commits into from
Oct 26, 2024
Merged

Conversation

AhmetNSimsek
Copy link
Collaborator

This PR fixes the no intersection issue when the intersection of 2 bounding boxes is planar and hence have volume 0 resulting in None. This meant that while there are some intersections, these were ignored. Also, the edge case, the intersection is just a point, is accounted for.

code to reproduce the issue on current main

import siibra
points = siibra.PointSet(
    [
        [-35.72, -1.87, 35.03],
        [-33.7, -1.87, 36.2],
        [-32.56, -1.87, 34.34],
        [-35.62, -1.87, 35.2],
    ],
    "bigbrain",
)
matched_sections = siibra.features.get(
    points, siibra.features.cellular.CellbodyStainedSection
)
assert len(matched_sections) > 0
voi = points.boundingbox

# this is expected to fetch but fails assuming there is no intersection
patch_1mu = matched_sections[0].fetch(voi=voi, resolution_mm=-1)

This PR fixes the no intersection issue when the intersection of 2 bounding boxes is planar and hence have volume 0 resulting in None. This meant that while there are some intersections, these were ignored.
Also, the edge case, the intersection is just a point, is accounted for.
@AhmetNSimsek AhmetNSimsek requested a review from xgui3783 October 24, 2024 14:29
@xgui3783
Copy link
Member

LGTM, but you should check with @dickscheid to see if volume check is important.

remind me, restrict_space=False is the default for siibra.features.get() right?

This issue does not affect siibra python v2, since there is no volume check.

@AhmetNSimsek
Copy link
Collaborator Author

LGTM, but you should check with @dickscheid to see if volume check is important.

There is a conversation about it here. I thought I remembered all the details but I will make some adjustments to this PR based on Timo's comment.

remind me, restrict_space=False is the default for siibra.features.get() right?

Yes by default it is false, i.e., compares anchors across different spaces.

This issue does not affect siibra python v2, since there is no volume check.

True

@AhmetNSimsek
Copy link
Collaborator Author

NaN centroids will be addressed in a separate PR since changes here only revealed them, not caused them.

@AhmetNSimsek AhmetNSimsek merged commit 0662f69 into main Oct 26, 2024
34 of 37 checks passed
@AhmetNSimsek AhmetNSimsek deleted the fix_bbox_planar_intersection branch October 26, 2024 13:37
@dickscheid
Copy link
Contributor

dickscheid commented Oct 27, 2024

import siibra
points = siibra.PointSet(
    [
        [-35.72, -1.87, 35.03],
        [-33.7, -1.87, 36.2],
        [-32.56, -1.87, 34.34],
        [-35.62, -1.87, 35.2],
    ],
    "bigbrain",
)
matched_sections = siibra.features.get(
    points, siibra.features.cellular.CellbodyStainedSection
)

this should reveal sections where a subset of points is in side the section's nxmx0.02 mm bounding box.

assert len(matched_sections) > 0
voi = points.boundingbox

This should reveal a bounding box with zero volume.

# this is expected to fetch but fails assuming there is no intersection
patch_1mu = matched_sections[0].fetch(voi=voi, resolution_mm=-1)

This is not expected to fetch, since the voi is zero volume. The user would need to extend the voi in the z direction to at least 20 micrometer. I would expect an error message in this direction. There was a tutorial notebook at some point which did this, but we might have removed that part.

@dickscheid
Copy link
Contributor

dickscheid commented Oct 27, 2024

Further to the above, I think in such cases a desired solution for users I to

  1. determine the points which are truly inside the section (that's what the intersection already does)
  2. project them to the "walls" of the section's bounding box in the zero-size dimension , perpendicular to each plane
  3. obtain the bounding box of the resulting points.

But that's definitely a modification of the previous bounding box and should be modeled with an explicit function, not performed implicitly.

Copy link
Contributor

@dickscheid dickscheid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor corrections / comments needed but almost good to merge

siibra/locations/boundingbox.py Show resolved Hide resolved
bbox = BoundingBox(
point1=point.Point(result_minpt, self.space),
point2=point.Point(result_maxpt, self.space),
space=self.space,
)
return bbox if bbox.volume > 0 else None

if bbox.volume == 0 and sum(cmin == cmax for cmin, cmax in zip(result_minpt, result_maxpt)) == 2:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be good to put a comment here, this is not trivial:
we check the three dimensions of the interesection bounding box. If exactly two dimensions are zero (the min and max point in these dimensions are identical), we conclude that there is no intersection.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This neglects the case where the intersection bounding box is planar, but has a sigma defined which gives it a nonzero "thickness" nevertheless.

@AhmetNSimsek how is the sigma computed from the original bounding boxes? we should discuss this. For now, I would be fine with the given solution but there should be a todo or even warning for bounding boxes with sigma. We should have a meeting on the processing of point uncertainties in the different location operations, for siibra v2. siibra v1 should try to raise warnings if locations have a sigma defined, but not used in operations.

@@ -55,10 +56,14 @@ def parse(spec, unit="mm") -> Tuple[float, float, float]:
if len(digits) == 3:
return tuple(float(d) for d in digits)
elif isinstance(spec, (tuple, list)) and len(spec) in [3, 4]:
if any(v is None for v in spec):
raise RuntimeError("Cannot parse cooridantes containing None values.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

if len(spec) == 4:
assert spec[3] == 1
return tuple(float(v.item()) if isinstance(v, np.ndarray) else float(v) for v in spec[:3])
elif isinstance(spec, np.ndarray) and spec.size == 3:
if any(np.isnan(v) for v in spec):
raise RuntimeError("Cannot parse cooridantes containing NaN values.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

siibra/locations/point.py Show resolved Hide resolved
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.

3 participants