Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export default defineComponent({
if (renderingVals.value === undefined) {
renderingVals.value = {
typeFilter: ['all'],
hideEmpty: true,
displayName: props.selectedAttribute.name,
displayColor: 'auto',
displayTextSize: -1,
Expand Down
10 changes: 10 additions & 0 deletions client/dive-common/components/Attributes/AttributeRendering.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default defineComponent({

const mainSettings = reactive({
selected: props.value.selected || false,
hideEmpty: props.value.hideEmpty ?? true,
typeFilter: props.value.typeFilter || ['all'],
order: props.value.order,
});
Expand Down Expand Up @@ -103,6 +104,7 @@ export default defineComponent({
const updateSettings = () => {
emit('input', {
selected: mainSettings.selected,
hideEmpty: mainSettings.hideEmpty,
typeFilter: mainSettings.typeFilter,
order: mainSettings.order,
displayName: displayNameSettings.displayName,
Expand Down Expand Up @@ -227,6 +229,7 @@ export default defineComponent({
<span>
<ul>
<li><b>Selected Track</b> : Only display attributes when a track is selected</li>
<li><b>Hide Empty</b> : Only display attributes when the value is non-empty</li>
<li><b>Filter Types</b> : Only display attributes on the filtered types</li>
<li><b>Order</b> : Order top to bottom for attributes where 0 is higher</li>
</ul>
Expand All @@ -243,6 +246,13 @@ export default defineComponent({
persistent-hint
class="mx-2"
/>
<v-switch
v-model="mainSettings.hideEmpty"
label="Hide Empty"
hint="Only display when value is non-empty"
persistent-hint
class="mx-2"
/>
<v-select
v-model="mainSettings.typeFilter"
:items="types"
Expand Down
2 changes: 1 addition & 1 deletion client/dive-common/components/Viewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ export default defineComponent({
groupFilters.importTypes(Object.keys(meta.customGroupStyling), false);
}
if (meta.attributes) {
loadAttributes(meta.attributes);
loadAttributes(meta.attributes, { enableStereoLengthRender: meta.subType === 'stereo' });
}
trackFilters.setConfidenceFilters(meta.confidenceFilters);
trackFilters.setTimeFilters(meta.timeFilters ?? null);
Expand Down
62 changes: 62 additions & 0 deletions client/dive-common/utils/stereoLengthRendering.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { Attribute, AttributeRendering } from 'vue-media-annotator/use/AttributeTypes';

export const STEREO_LENGTH_ATTRIBUTE_NAME = 'length';

export function createDefaultAttributeRendering(
displayName: string,
overrides: Partial<AttributeRendering> = {},
): AttributeRendering {
return {
typeFilter: ['all'],
hideEmpty: true,
displayName,
displayColor: 'auto',
displayTextSize: -1,
valueColor: 'auto',
valueTextSize: -1,
order: 0,
location: 'outside',
corner: 'SE',
layout: 'horizontal',
box: false,
boxColor: 'auto',
boxThickness: 1,
displayWidth: {
type: '%',
val: 10,
},
displayHeight: {
type: 'auto',
val: 10,
},
...overrides,
};
}

export function createStereoLengthRendering(displayName = STEREO_LENGTH_ATTRIBUTE_NAME): AttributeRendering {
return createDefaultAttributeRendering(displayName);
}

export function findStereoLengthAttribute(
attributes: Attribute[] | Record<string, Attribute>,
): Attribute | undefined {
const list = Array.isArray(attributes) ? attributes : Object.values(attributes);
return list.find((attribute) => (
attribute.name === STEREO_LENGTH_ATTRIBUTE_NAME && attribute.belongs === 'detection'
));
}

/**
* Enable on-canvas rendering for the detection-level length attribute when it
* exists but has no render settings yet.
*/
export function ensureStereoLengthRendering(
attributes: Attribute[] | Record<string, Attribute>,
): boolean {
const lengthAttribute = findStereoLengthAttribute(attributes);
if (!lengthAttribute || lengthAttribute.render) {
return false;
}
lengthAttribute.render = createStereoLengthRendering(lengthAttribute.name);
return true;
}
17 changes: 16 additions & 1 deletion client/platform/desktop/frontend/components/ViewerLoader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import type {
StereoSegmentationFinalizeParams,
} from 'dive-common/use/useModeManager';
import { HeadPointKey, TailPointKey, HeadTailLineKey } from 'dive-common/recipes/headtail';
import {
createStereoLengthRendering,
STEREO_LENGTH_ATTRIBUTE_NAME,
} from 'dive-common/utils/stereoLengthRendering';
import type { RectBounds } from 'vue-media-annotator/utils';
import {
segmentationPredict, segmentationStereoSegment, segmentationInitialize, segmentationIsReady,
Expand Down Expand Up @@ -694,13 +698,24 @@ export default defineComponent({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const existing = (viewer.attributes || []) as any[];
STEREO_MEASUREMENT_ATTRS.forEach((name) => {
if (!existing.find((a) => a.name === name && a.belongs === 'detection')) {
const existingAttr = existing.find((a) => a.name === name && a.belongs === 'detection');
if (!existingAttr) {
viewer.handler.setAttribute({
data: {
belongs: 'detection',
datatype: 'number',
name,
key: `detection_${name}`,
...(name === STEREO_LENGTH_ATTRIBUTE_NAME
? { render: createStereoLengthRendering(name) }
: {}),
},
});
} else if (name === STEREO_LENGTH_ATTRIBUTE_NAME && !existingAttr.render) {
viewer.handler.setAttribute({
data: {
...existingAttr,
render: createStereoLengthRendering(existingAttr.name),
},
});
}
Expand Down
19 changes: 14 additions & 5 deletions client/src/layers/AnnotationLayers/AttributeBoxLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Attribute } from 'vue-media-annotator/use/AttributeTypes';
import { boundToGeojson } from '../../utils';
import BaseLayer, { LayerStyle, BaseLayerParams } from '../BaseLayer';
import { FrameDataTrack } from '../LayerTypes';
import { calculateAttributeArea } from './AttributeLayer';
import { calculateAttributeArea, getAttributeValue, isEmptyAttributeValue } from './AttributeLayer';

interface RectGeoJSData{
trackId: number;
Expand Down Expand Up @@ -62,12 +62,21 @@ export default class AttributeBoxLayer extends BaseLayer<RectGeoJSData> {
}
return false;
});
for (let i = 0; i < renderFiltered.length; i += 1) {
const currentRender = renderFiltered[i].render;
const visibleAttributes = renderFiltered.filter((item) => {
if (!item.render) {
return false;
}
if ((item.render.hideEmpty ?? true) && isEmptyAttributeValue(getAttributeValue(track, item, ''))) {
return false;
}
return true;
});
for (let i = 0; i < visibleAttributes.length; i += 1) {
const currentRender = visibleAttributes[i].render;
if (currentRender && currentRender.box) {
const { newBounds } = calculateAttributeArea(track.features.bounds, renderFiltered[i].render, i, renderFiltered.length);
const { newBounds } = calculateAttributeArea(track.features.bounds, visibleAttributes[i].render, i, visibleAttributes.length);
const polygon = boundToGeojson(newBounds);
const lineColor = currentRender.boxColor === 'auto' ? renderFiltered[i].color || 'white' : currentRender.boxColor;
const lineColor = currentRender.boxColor === 'auto' ? visibleAttributes[i].color || 'white' : currentRender.boxColor;
const lineThickness = currentRender.boxThickness || 1;
const { boxBackground } = currentRender;
const { boxOpacity } = currentRender;
Expand Down
82 changes: 51 additions & 31 deletions client/src/layers/AnnotationLayers/AttributeLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,38 @@ interface AttributeLayerParams {
}

const lineHeight = 15;

export function getAttributeValue(
annotation: FrameDataTrack,
attr: Attribute,
user: string,
): string | number | boolean | undefined {
const { name } = attr;
if (attr.belongs === 'detection') {
if (annotation.features?.attributes) {
const { attributes } = annotation.features;
if (attr.user && user && attributes.userAttributes?.[user]) {
return (attributes.userAttributes[user] as StringKeyObject)[name] as string | boolean | number;
}
return attributes[name] as string | boolean | number;
}
}
if (attr.belongs === 'track') {
const { attributes } = annotation.track;
if (attributes) {
if (attr.user && user && attributes.userAttributes?.[user]) {
return (attributes.userAttributes[user] as StringKeyObject)[name] as string | boolean | number;
}
return attributes[name] as string | boolean | number;
}
}
return undefined;
}

export function isEmptyAttributeValue(value: string | number | boolean | undefined): boolean {
return value === undefined || value === null || value === '';
}

// function to calculate x,y as well as bounds based on render settings
export function calculateAttributeArea(baseBounds: RectBounds, renderSettings: Attribute['render'], renderIndex: number, renderAttrLength: number) {
// Calculate X Position
Expand Down Expand Up @@ -125,40 +157,30 @@ function defaultFormatter(
return false;
});

for (let i = 0; i < renderFiltered.length; i += 1) {
const currentRender = renderFiltered[i].render;
const { name } = renderFiltered[i];
const visibleAttributes = renderFiltered.filter((item) => {
if (!item.render) {
return false;
}
if ((item.render.hideEmpty ?? true) && isEmptyAttributeValue(getAttributeValue(annotation, item, user))) {
return false;
}
return true;
});

for (let i = 0; i < visibleAttributes.length; i += 1) {
const currentRender = visibleAttributes[i].render;
if (currentRender !== undefined) {
const { displayName } = currentRender;
const type = renderFiltered[i].belongs;
// Calculate Value
let value: string | number | boolean = '';
if (type === 'detection') {
if (annotation.features && annotation.features.attributes) {
const { attributes } = annotation.features;
if (renderFiltered[i].user && user && attributes.userAttributes && attributes.userAttributes[user]) {
value = (attributes.userAttributes[user] as StringKeyObject)[name] as string | boolean | number;
} else {
value = attributes[name] as string | boolean | number;
}
}
}
if (type === 'track') {
const { attributes } = annotation.track;
if (attributes) {
if (renderAttr[i].user && user && attributes.userAttributes && attributes.userAttributes[user]) {
value = (attributes.userAttributes[user] as StringKeyObject)[name] as string | boolean | number;
} else {
value = attributes[name] as string | boolean | number;
}
}
let value = getAttributeValue(annotation, visibleAttributes[i], user);
if (value === undefined) {
value = '';
}

const {
displayX, displayHeight, valueX, valueHeight, offsetY,
} = calculateAttributeArea(bounds, currentRender, i, renderFiltered.length);
} = calculateAttributeArea(bounds, currentRender, i, visibleAttributes.length);

const displayColor = currentRender.displayColor === 'auto' ? renderAttr[i].color : currentRender.displayColor;
const displayColor = currentRender.displayColor === 'auto' ? visibleAttributes[i].color : currentRender.displayColor;
const { displayTextSize } = currentRender;
arr.push({
selected: annotation.selected,
Expand All @@ -172,11 +194,9 @@ function defaultFormatter(
offsetY,
offsetX: displayHeight === valueHeight ? 20 : 0,
});
const valueColor = autoColorIndex[i](value);
const attrIndex = renderAttr.indexOf(visibleAttributes[i]);
const valueColor = autoColorIndex[attrIndex](value);
const { valueTextSize } = currentRender;
if (value === undefined) {
value = '';
}
arr.push({
selected: annotation.selected,
editing: annotation.editing,
Expand Down
1 change: 1 addition & 0 deletions client/src/use/AttributeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface StringAttributeEditorOptions {
export interface AttributeRendering {
typeFilter: string[];
selected?: boolean;
hideEmpty?: boolean;
displayName: string;
displayColor: 'auto' | string;
displayTextSize: number;
Expand Down
9 changes: 8 additions & 1 deletion client/src/use/useAttributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
ref, Ref, computed, set as VueSet, del as VueDel,
} from 'vue';
import { StringKeyObject } from 'vue-media-annotator/BaseAnnotation';
import { ensureStereoLengthRendering } from 'dive-common/utils/stereoLengthRendering';
import { StyleManager, Track } from '..';
import CameraStore from '../CameraStore';
import { isReservedAttributeName, RESERVED_ATTRIBUTES } from '../utils';
Expand Down Expand Up @@ -51,7 +52,13 @@ export default function UseAttributes(
});
const timelineEnabled: Ref<boolean> = ref(false);

function loadAttributes(metadataAttributes: Record<string, Attribute>) {
function loadAttributes(
metadataAttributes: Record<string, Attribute>,
options?: { enableStereoLengthRender?: boolean },
) {
if (options?.enableStereoLengthRender) {
ensureStereoLengthRendering(metadataAttributes);
}
attributes.value = metadataAttributes;
Object.values(attributes.value).forEach((attribute) => {
if (attribute.color === undefined) {
Expand Down
1 change: 1 addition & 0 deletions server/dive_utils/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ class RenderingDisplayDimension(BaseModel):
class RenderingAttributes(BaseModel):
typeFilter: List[str]
selected: Optional[bool]
hideEmpty: Optional[bool]
displayName: str
displayColor: str
displayTextSize: float
Expand Down
Loading