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

Add backend logic for bounding box plots #5250

Merged
merged 23 commits into from
Feb 20, 2024
Merged

Conversation

julieg18
Copy link
Contributor

@julieg18 julieg18 commented Jan 30, 2024

main <- #5227 <- #5241 <- this <- #5305

This PR updates our comparison dvc output fixture with bounding box info and updates the backend accordingly. It also adds logic for a user toggling boxes on/off.

@julieg18 julieg18 self-assigned this Jan 30, 2024
@julieg18 julieg18 changed the base branch from main to stop-passing-bb-coords-as-props January 30, 2024 21:14
Base automatically changed from stop-passing-bb-coords-as-props to bounding-boxes February 1, 2024 13:49
x_max: number
y_min: number
y_max: number
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I based the output off of iterative/dvclive#766 (comment) description, but this will most likely change and we will need to adjust accordingly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to top, bottom, right, left based off the top-left corner after a discussion with Alex (see his comment):

image

@@ -112,6 +116,10 @@ export class PlotsModel extends ModelWithPersistence {
PersistenceKey.PLOTS_COMPARISON_MULTI_PLOT_VALUES,
{}
)
this.comparisonClassesSelected = this.revive(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this.comparisonClassesSelected keeps track of what class labels (and by extension, the boxes) are on/off.

extension/src/plots/model/collect.ts Outdated Show resolved Hide resolved
@julieg18 julieg18 marked this pull request as ready for review February 2, 2024 13:58
extension/src/plots/model/collect.ts Outdated Show resolved Hide resolved
extension/src/plots/model/collect.ts Outdated Show resolved Hide resolved
extension/src/plots/model/collect.ts Outdated Show resolved Hide resolved
extension/src/plots/model/collect.ts Outdated Show resolved Hide resolved
extension/src/test/fixtures/plotsDiff/index.ts Outdated Show resolved Hide resolved
extension/src/test/fixtures/plotsDiff/index.ts Outdated Show resolved Hide resolved
extension/src/plots/model/collect.ts Outdated Show resolved Hide resolved
extension/src/plots/model/collect.ts Outdated Show resolved Hide resolved
const acc: ComparisonPlotClasses = {}

for (const id of selectedRevisionIds) {
for (const path of paths) {
Copy link
Contributor

Choose a reason for hiding this comment

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

What I meant by removing nested loops was not moving one inside of another function (I know realize we have 4 nested loops total), but can we change the code to drop some or at least make them more performant.

I see that in the fourth nested loop, we check for selected state. Maybe for a start, we could loop only through paths that are also in comparisonClassesSelected, since we only care about labels in comparisonClassesSelected[path].

There might be other optimizations to be made as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What I meant by removing nested loops was not moving one inside of another function (I know realize we have 4 nested loops total), but can we change the code to drop some or at least make them more performant.

Thanks for the clarification!

I see that in the fourth nested loop, we check for selected state. Maybe for a start, we could loop only through paths that are also in comparisonClassesSelected, since we only care about labels in comparisonClassesSelected[path].

comparisonClassesSelected only holds classes that the user has toggled directly, it doesn't hold every visible class. Unless I'm missing something, we can't rely on it to detect which paths need to be looped over to collect classes.

The four loops are:

  1. looping over each revision (up to 7)
  2. looping over each path (could be a lot depending on how many plots the user has)
  3. looping over each path img (will usually be just one, unless it's an image step plot)
  4. looping over each class, gathering only the selected class boxes

I don't see a way to get rid of the first three loops, since for us to gather a plot's classes, we need to check for classes in each single image and collect the data. Atleast the third loop will likely just be a single item array.

We could get rid of the fourth loop by passing all image box data to the frontend and have ComparisonTableBoundingBoxImg filter out the boxes, but that means passing larger arrays that the component needs to loop over. Seems like we would want to limit the data passed to the frontend as much as possible but please correct me if I'm wrong.

Another alternative is having collectSelectedComparisonPlots gather class information along with the selected comparison plot data. I chose not to do this since that would make the function more complex, but if we think it's worth it for performance, I'm alright with doing that.

TL;DR

I'm not seeing a good way to get rid of any of our four loops. I'll review the loops and see what we can do to optimize them for now.

@sroy3, what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reviewed optimizing the loops! Instead of looping over every revision/path in comparisonData, we could start by looping over the already formed plots array. The plots already have a collected classDetails that tells you if a path has selected classes or not, lettings us skip early. Aka:

  1. looping over each plots path (skip over if classDetails is empty)
  2. looping over each revision
  3. looping over each path img (will usually be just one, unless it's an image step plot)
  4. looping over each class, gathering only the selected class boxes

Not seeing any other optimizations besides the two ideas I mentioned in my previous comment (have collectSelectedComparisonPlots collect box data, have ComparisonTableBoundingBoxImg do the box filtering).

Copy link
Contributor

Choose a reason for hiding this comment

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

One thing I'm seeing is this:

if (selectedState === false) {
  continue
}
if (imgClasses.length === 0) {
  return
}

Why do we have to go through four loops to return nothing when we know it before hand? I would make this the first loop (the selected classes).

If there are no other possible optimizations, then I guess it's fine to keep as is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if (selectedState === false) {
continue
}
Why do we have to go through four loops to return nothing when we know it before hand? I would make this the first loop (the selected classes).

Aren't we already getting the selected classes in the first loop? In fact the line if (selectedState === false) {... no longer exists in the code. Did Github direct you the latest changes or is there something I'm missing here?

One thing I'm seeing is this:
if (imgClasses.length === 0) {
return
}

Good catch on if (imgClasses.length === 0) {...! I didn't spot the fact that we might be needlessy looping over selected classes if an img has no attached boxes to begin with. I'll update the code to check for img.boxes before running getSelectedImgComparisonPlotClasses.

}

return collectSelectedComparisonPlotClasses({
comparisonClassesSelected: this.getComparisonClassesSelected(),
Copy link
Contributor

Choose a reason for hiding this comment

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

If this is empty, should this return {} as well? This would avoid having to loop through selectedRevisionIds for nothing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On further inspection, comparisonClassesSelected is only going to hold classes that the user has deselected/selected, it doesn't hold every visible class so we are unable to use it as a check for if there are selected classes or not.

Copy link
Contributor Author

@julieg18 julieg18 Feb 15, 2024

Choose a reason for hiding this comment

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

This variable probably needs to be renamed to avoid confusion. Maybe something like comparisonClassesSelectedState 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

But then it's calling collectSelectedComparisonPlotClasses, which should return and empty object anyway, no? If we avoid calling it and the looping when we can, it's a good thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, collectSelectedComparisonPlotClasses could still return selected class boxes. Currently, we select all classes in a plot by default and comparisonClassesSelected only gets updated with a class (setting it as true or false) when a user clicks directly on a class button. Hope that makes sense :)

@julieg18 julieg18 requested a review from sroy3 February 15, 2024 17:40
extension/src/common/colors.ts Show resolved Hide resolved
}

return collectSelectedComparisonPlotClasses({
comparisonClassesSelected: this.getComparisonClassesSelected(),
Copy link
Contributor

Choose a reason for hiding this comment

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

But then it's calling collectSelectedComparisonPlotClasses, which should return and empty object anyway, no? If we avoid calling it and the looping when we can, it's a good thing.

extension/src/plots/model/collect.ts Outdated Show resolved Hide resolved
const acc: ComparisonPlotClasses = {}

for (const id of selectedRevisionIds) {
for (const path of paths) {
Copy link
Contributor

Choose a reason for hiding this comment

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

One thing I'm seeing is this:

if (selectedState === false) {
  continue
}
if (imgClasses.length === 0) {
  return
}

Why do we have to go through four loops to return nothing when we know it before hand? I would make this the first loop (the selected classes).

If there are no other possible optimizations, then I guess it's fine to keep as is.

@julieg18
Copy link
Contributor Author

Going to go ahead and merge this and #5305 into our first PR since they've been sitting for a while and are approved.

@julieg18 julieg18 merged commit 45c88b3 into bounding-boxes Feb 20, 2024
5 checks passed
@julieg18 julieg18 deleted the add-bb-backend-logic branch February 20, 2024 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants