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 bounding boxes plot frontend components #5227

Closed
wants to merge 17 commits into from
Closed

Conversation

julieg18
Copy link
Contributor

@julieg18 julieg18 commented Jan 22, 2024

main <- this <- #5241 <- #5250 <- #5305 <- #5313

Demo

https://github.com/iterative/vscode-dvc/assets/43496356/68bf681b-d6e3-4069-818f-d3d5d2052633

Screen.Recording.2024-02-15.at.11.49.48.AM.mov

To Do

A start to #4917

@julieg18 julieg18 added the product PR that affects product label Jan 22, 2024
@julieg18 julieg18 self-assigned this Jan 22, 2024
}

const boundingBoxImgCoords: { [rev: string]: ComparisonPlotBoundingBoxes } = {
'exp-83425': [
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 3 locations. Consider refactoring.

{ boxes: [{ h: 75, w: 100, x: 100, y: 100 }], label: 'traffic light' },
{ boxes: [{ h: 30, w: 30, x: 190, y: 310 }], label: 'car' }
],
'exp-e7a67': [
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 3 locations. Consider refactoring.

{ boxes: [{ h: 100, w: 100, x: 100, y: 100 }], label: 'traffic light' },
{ boxes: [{ h: 30, w: 30, x: 190, y: 310 }], label: 'car' }
],
'test-branch': [
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 3 locations. Consider refactoring.

@julieg18 julieg18 changed the title Add bounding boxes plots (wip, just frontend components) Add bounding boxes plot frontend components Jan 23, 2024
export type ComparisonPlots = {
path: string
boundingBoxLabels: ComparisonBoundingBoxLabels
revisions: ComparisonRevisionData
Copy link
Contributor Author

@julieg18 julieg18 Jan 23, 2024

Choose a reason for hiding this comment

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

I've added two values to the PlotsComparisonData. ComparisonBoundingBoxClasses inside of ComparisonPlots (holds the data on all classes in that plot/image row) and ComparisonPlotBoundingBox (holds the class/coordinates for all boxes in each single image).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently, I was thinking that our backend would manage the "selected" state of the classes, but we could also manage that in redux instead.

]
}

export const addBoundingBoxes = (
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've created a util in the frontend that adds the bounding boxes to a comparison fixture for testing and storybooks. Once we've got the backend part of this working (iterative/dvc#10198), this will all be removed and bounding box data will be part of our original comparison fixture.

PlotsComparisonData
} from 'dvc/src/plots/webview/contract'

const boxColors = [
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looking at other yolo and other bounding box examples, they seem to rely on a variation of a Tableau color palette. I've grabbed the set of colors that yolo uses for the boxes for now.

import { ComparisonTableBoundingBoxColorFilter } from './ComparisonTableBoundingBoxColorFilter'
import styles from '../styles.module.scss'

export const ComparisonTableBoundingBoxImg: React.FC<{
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 2 locations. Consider refactoring.

@julieg18 julieg18 marked this pull request as ready for review January 24, 2024 14:23
/>
<label
className={styles.boundingBoxClassesButton}
style={{ background: color }}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we ever get a background color on which to foreground color makes the text not accessible? If so, we can maybe calculate whether it should use a dark or light foreground color.

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 I understood the libraries that I got the colors from, they always use white as the text color. While testing, while I think a couple colors could benefit from a darker color, white
does seem to work well enough:

Screenshot 2024-01-25 at 8 29 08 AM

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, apart from the neon green they all look fine. And I don't think there is anything we can do about that neon green anyway.

{plotImg.url && plotImg.boundingBoxes ? (
<ComparisonTableBoundingBoxImg
src={plotImg.url}
boxCoords={plotImg.boundingBoxes}
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of passing down complex objects (here and further up), can we just pass down an id and go grab the coordinates where they are used instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point on the fact that we're passing down complex objects! I'll look into moving the bounding box data into an object that ComparisonTableBoundingBoxImg could grab from.

Copy link
Contributor

@sroy3 sroy3 Jan 25, 2024

Choose a reason for hiding this comment

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

If I understood the code correctly, plotImg.boundingBoxes is not too big of an object and only one level down, you could also pass each property from it separately using the spread operator.

extension/src/plots/webview/contract.ts Outdated Show resolved Hide resolved
WithBoundingBoxes.args = {
data: {
...defaultPlotsData,
comparison: addBoundingBoxes(comparisonPlotsFixture)
Copy link
Member

Choose a reason for hiding this comment

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

[Q] Why not add these for a single image and have them in the base fixture?

I can see this is the plan

)}
<image href={src} width={naturalWidth} height={naturalHeight} />
{boxCoords.map(({ label, boxes }) => {
const labelColor = classes[label].color
Copy link
Member

Choose a reason for hiding this comment

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

[Q] What happens if there is no color found?

Copy link
Member

Choose a reason for hiding this comment

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

[Q] Can you clean up the names? i.e. boxCoords and classes. When props are getting drilled down they should have a consistent name the whole way through.

Copy link
Contributor Author

@julieg18 julieg18 Jan 31, 2024

Choose a reason for hiding this comment

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

[Q] What happens if there is no color found?

Good catch! I've updated the code to not create any boxes if it can't find a color so nothing breaks. We could also still render the boxes with a default grayish color 🤔

[Q] Can you clean up the names? i.e. boxCoords and classes. When props are getting drilled down they should have a consistent name the whole way through.

Done!

const labelColor = classes[label].color
return boxes.map(({ h, w, x, y }) => (
<React.Fragment key={label + h + w + x + y}>
<text
Copy link
Member

Choose a reason for hiding this comment

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

With the current design, I don't think we need this text as we are repeating a lot of information:

image

I.e. there is a big box that shows traffic light objects are yellow. I would drop this text unless we include the threshold along with the text.

Once there are a lot of boxes this will become even more crowded:

image

WDYT @dberenbaum?

Copy link
Member

@mattseddon mattseddon Jan 29, 2024

Choose a reason for hiding this comment

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

Let's also consider how we can filter the boxes by a provided threshold (i.e. "Objects detected with prediction threshold >= 0.5")

Doesn't have to be implemented as part of this PR but the data structure/UI will probably need to support it.

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.e. there is a big box that shows traffic light objects are yellow. I would drop this text unless we include the threshold along with the text.
Once there are a lot of boxes this will become even more crowded

Good point! I chose to add labels since all classes toggle buttons may not be showing (if there are too many, we're going to add a "Show More" button that shows the hidden classes).

No strong preference though, I'm happy to remove them!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@AlexandreKempf, what do you think about keeping text labels on each individual box or would it be better to remove them?

Choose a reason for hiding this comment

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

Working on object detection, we get used to overlapping text ^^", so we can keep them it is not a problem.
In my opinion, the best behavior would be to have the image with all the labels when not hovered. When hovered, only the bounding box below the mouse gets its text displayed. It is pretty tricky to get which bounding boxes to select based on mouse location, especially with lots of overlapping cases. So generally, what we end up doing is filtering the bounding boxes based on label or score ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In my opinion, the best behavior would be to have the image with all the labels when not hovered. When hovered, only the bounding box below the mouse gets its text displayed. It is pretty tricky to get which bounding boxes to select based on mouse location, especially with lots of overlapping cases.

Sounds good! I'll add this feature to our todo list.

@mattseddon
Copy link
Member

Something else that we will need to revisit/implement:

How are bounding boxes displayed on/with a zoomed image? In VS Code we currently open the image in an editor and that is it.

rev: string
classes: ComparisonPlotClass[]
}[] = [
{
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 4 locations. Consider refactoring.

],
rev: 'exp-83425'
},
{
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 4 locations. Consider refactoring.

],
rev: 'exp-e7a67'
},
{
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 4 locations. Consider refactoring.

],
rev: 'main'
},
{
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 4 locations. Consider refactoring.

@AlexandreKempf
Copy link

Images shown by Matt on your work looks super nice ! Congrats 🎉

@@ -310,10 +332,26 @@ export class PlotsModel extends ModelWithPersistence {
)
}

public toggleComparisonClass(path: string, label: string, selected: boolean) {
Copy link

Choose a reason for hiding this comment

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

Similar blocks of code found in 2 locations. Consider refactoring.

Copy link

codeclimate bot commented Feb 20, 2024

Code Climate has analyzed commit 511dfea and detected 3 issues on this pull request.

Here's the issue category breakdown:

Category Count
Duplication 3

The test coverage on the diff in this pull request is 95.0% (85% is the threshold).

This pull request will bring the total coverage in the repository to 95.3% (0.0% change).

View more on Code Climate.

export type ImagePlotOutput = {
revisions: string[]
type: PlotsType
url: string
annotations?: { [label: string]: BoundingBox[] }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

DVC spec has been changed from boxes to annotations. I've updated the code accordingly.

@mattseddon
Copy link
Member

Will get back to this later

@mattseddon mattseddon closed this Mar 12, 2024
@mattseddon mattseddon deleted the bounding-boxes branch March 12, 2024 05:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
do not merge product PR that affects product
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants