Skip to content

Commit 01a413b

Browse files
authored
time series: show sample on image (#5250)
Image plugin has a notion of sample which was previously not visible to user. This change shows sample, when it is relevant, in the image card.
1 parent f44e0b6 commit 01a413b

File tree

8 files changed

+130
-69
lines changed

8 files changed

+130
-69
lines changed

tensorboard/webapp/metrics/internal_types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface CardMetadata {
4343
plugin: PluginType;
4444
tag: string;
4545
sample?: number;
46+
numSample?: number;
4647

4748
/**
4849
* A `null` runId indicates all runs.

tensorboard/webapp/metrics/store/metrics_reducers.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,13 @@ function buildCardMetadataList(tagMetadata: TagMetadata): CardMetadata[] {
8282
for (const runId of Object.keys(tagRunSampleInfo[tag])) {
8383
const {maxSamplesPerStep} = tagRunSampleInfo[tag][runId];
8484
for (let i = 0; i < maxSamplesPerStep; i++) {
85-
results.push({plugin, tag, runId, sample: i});
85+
results.push({
86+
plugin,
87+
tag,
88+
runId,
89+
sample: i,
90+
numSample: maxSamplesPerStep,
91+
});
8692
}
8793
}
8894
}

tensorboard/webapp/metrics/store/metrics_reducers_test.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,27 @@ describe('metrics reducers', () => {
202202
const expectedCardMetadataList = [
203203
{plugin: PluginType.SCALARS, tag: 'tagA', runId: null},
204204
{plugin: PluginType.HISTOGRAMS, tag: 'tagB', runId: 'run2'},
205-
{plugin: PluginType.IMAGES, tag: 'tagC', runId: 'run3', sample: 0},
206-
{plugin: PluginType.IMAGES, tag: 'tagC', runId: 'run3', sample: 1},
207-
{plugin: PluginType.IMAGES, tag: 'tagC', runId: 'run3', sample: 2},
205+
{
206+
plugin: PluginType.IMAGES,
207+
tag: 'tagC',
208+
runId: 'run3',
209+
sample: 0,
210+
numSample: 3,
211+
},
212+
{
213+
plugin: PluginType.IMAGES,
214+
tag: 'tagC',
215+
runId: 'run3',
216+
sample: 1,
217+
numSample: 3,
218+
},
219+
{
220+
plugin: PluginType.IMAGES,
221+
tag: 'tagC',
222+
runId: 'run3',
223+
sample: 2,
224+
numSample: 3,
225+
},
208226
];
209227
const expectedCardMetadataMap: CardMetadataMap = {};
210228
for (const cardMetadata of expectedCardMetadataList) {

tensorboard/webapp/metrics/views/card_renderer/image_card_component.ng.html

Lines changed: 53 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,49 +15,61 @@
1515
limitations under the License.
1616
-->
1717
<div class="heading">
18-
<tb-truncated-path
19-
class="tag"
20-
title="{{ tag }}"
21-
value="{{ title }}"
22-
></tb-truncated-path>
23-
<div class="run">
24-
<span
25-
class="dot"
26-
[ngStyle]="{backgroundColor: runColorScale(runId)}"
27-
></span>
28-
<card-run-name class="run-text" [runId]="runId"></card-run-name>
18+
<div class="line">
19+
<tb-truncated-path
20+
class="tag"
21+
title="{{ tag }}"
22+
value="{{ title }}"
23+
></tb-truncated-path>
24+
<span class="controls">
25+
<button
26+
mat-icon-button
27+
class="pin-button"
28+
i18n-aria-label="A button to pin a card."
29+
aria-label="Pin card"
30+
[attr.title]="isPinned ? 'Unpin card' : 'Pin card'"
31+
(click)="onPinClicked.emit(!isPinned)"
32+
>
33+
<mat-icon
34+
[svgIcon]="isPinned ? 'keep_24px' : 'keep_outline_24px'"
35+
></mat-icon>
36+
</button>
37+
<button
38+
mat-icon-button
39+
i18n-aria-label="A button on an image card that toggles actual image size."
40+
aria-label="Toggle actual image size"
41+
[disabled]="!allowToggleActualSize"
42+
title="Toggle actual image size"
43+
(click)="onActualSizeToggle.emit()"
44+
>
45+
<mat-icon svgIcon="image_search_24px"></mat-icon>
46+
</button>
47+
</span>
2948
</div>
30-
<div class="step" *ngIf="stepIndex !== null && stepIndex < stepValues.length">
31-
Step {{ stepValues[stepIndex] }}
32-
<mat-spinner
33-
diameter="18"
34-
*ngIf="loadState === DataLoadState.LOADING"
35-
></mat-spinner>
49+
<div class="line">
50+
<span class="run">
51+
<span
52+
class="dot"
53+
[ngStyle]="{backgroundColor: runColorScale(runId)}"
54+
></span>
55+
<card-run-name class="run-text" [runId]="runId"></card-run-name>
56+
</span>
57+
<div class="metadata">
58+
<span
59+
class="step"
60+
*ngIf="stepIndex !== null && stepIndex < stepValues.length"
61+
>Step {{ stepValues[stepIndex] | number }}</span
62+
>
63+
<span class="sample" *ngIf="numSample > 1"
64+
>Sample {{ sample + 1 | number }}/{{ numSample | number}}</span
65+
>
66+
<mat-spinner
67+
class="loading"
68+
diameter="18"
69+
*ngIf="loadState === DataLoadState.LOADING"
70+
></mat-spinner>
71+
</div>
3672
</div>
37-
<span class="controls">
38-
<button
39-
mat-icon-button
40-
class="pin-button"
41-
i18n-aria-label="A button to pin a card."
42-
aria-label="Pin card"
43-
[attr.title]="isPinned ? 'Unpin card' : 'Pin card'"
44-
(click)="onPinClicked.emit(!isPinned)"
45-
>
46-
<mat-icon
47-
[svgIcon]="isPinned ? 'keep_24px' : 'keep_outline_24px'"
48-
></mat-icon>
49-
</button>
50-
<button
51-
mat-icon-button
52-
i18n-aria-label="A button on an image card that toggles actual image size."
53-
aria-label="Toggle actual image size"
54-
[disabled]="!allowToggleActualSize"
55-
title="Toggle actual image size"
56-
(click)="onActualSizeToggle.emit()"
57-
>
58-
<mat-icon svgIcon="image_search_24px"></mat-icon>
59-
</button>
60-
</span>
6173
</div>
6274
<ng-container
6375
*ngIf="stepIndex !== null && stepIndex < stepValues.length; else noImageData"

tensorboard/webapp/metrics/views/card_renderer/image_card_component.scss

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ limitations under the License.
1616
@import '../common';
1717

1818
$_title-to-heading-gap: 12px;
19-
$_second-row-height: 15px;
2019

2120
:host {
2221
display: flex;
@@ -36,18 +35,27 @@ $_second-row-height: 15px;
3635
$heading-content-gap: 4px;
3736

3837
align-items: center;
39-
display: grid;
40-
grid-template-areas:
41-
'tag controls'
42-
'run step';
43-
grid-template-columns: 1fr auto;
4438
font-size: 14px;
4539
margin-bottom: $heading-content-gap;
4640
position: relative;
4741
}
4842

43+
.line {
44+
align-items: center;
45+
display: grid;
46+
grid-template-columns: 1fr max-content;
47+
}
48+
49+
.metadata {
50+
display: flex;
51+
flex-wrap: wrap;
52+
gap: 5px;
53+
justify-content: flex-end;
54+
max-width: 175px; // When step or sample is too long, it makes content wrap.
55+
text-align: end;
56+
}
57+
4958
.tag {
50-
grid-area: tag;
5159
overflow: hidden;
5260
}
5361

@@ -56,8 +64,9 @@ $_second-row-height: 15px;
5664
}
5765

5866
.run {
59-
grid-area: run;
67+
align-self: baseline;
6068
display: flex;
69+
overflow: hidden;
6170
white-space: nowrap;
6271

6372
.dot {
@@ -76,29 +85,16 @@ $_second-row-height: 15px;
7685
}
7786
}
7887

79-
.step {
80-
grid-area: step;
81-
display: flex;
82-
83-
mat-spinner {
84-
$mat-icon-button-diameter: 40px;
85-
$spinner-diameter: 18px;
86-
87-
margin-left: 4px;
88-
margin-right: 0;
89-
}
90-
}
91-
9288
.run,
89+
.sample,
9390
.step {
9491
@include tb-theme-foreground-prop(color, secondary-text);
9592
font-size: 13px;
96-
height: $_second-row-height;
9793
}
9894

9995
.controls {
10096
@include metrics-card-controls;
101-
grid-area: controls;
97+
10298
justify-self: flex-end;
10399
flex-shrink: 0;
104100
// TODO(psybuzz) do not use negative margin.

tensorboard/webapp/metrics/views/card_renderer/image_card_component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ export class ImageCardComponent {
3939
@Input() title!: string;
4040
@Input() tag!: string;
4141
@Input() runId!: string;
42-
@Input() sample!: string;
42+
@Input() sample!: number;
43+
@Input() numSample!: number;
4344
@Input() imageUrl!: string | null;
4445
@Input() stepIndex!: number | null;
4546
@Input() stepValues!: number[];

tensorboard/webapp/metrics/views/card_renderer/image_card_container.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import {getTagDisplayName} from '../utils';
5858
type ImageCardMetadata = CardMetadata & {
5959
plugin: PluginType.IMAGES;
6060
sample: number;
61+
numSample: number;
6162
runId: string;
6263
};
6364

@@ -70,6 +71,7 @@ type ImageCardMetadata = CardMetadata & {
7071
[tag]="tag$ | async"
7172
[runId]="runId$ | async"
7273
[sample]="sample$ | async"
74+
[numSample]="numSample$ | async"
7375
[imageUrl]="imageUrl$ | async"
7476
[stepIndex]="stepIndex$ | async"
7577
[stepValues]="stepValues$ | async"
@@ -112,6 +114,7 @@ export class ImageCardContainer implements CardRenderer, OnInit, OnDestroy {
112114
tag$?: Observable<string>;
113115
runId$?: Observable<string>;
114116
sample$?: Observable<number>;
117+
numSample$?: Observable<number>;
115118
imageUrl$?: Observable<string | null>;
116119
stepIndex$?: Observable<number | null>;
117120
stepValues$?: Observable<number[]>;
@@ -232,6 +235,10 @@ export class ImageCardContainer implements CardRenderer, OnInit, OnDestroy {
232235
})
233236
);
234237

238+
this.numSample$ = cardMetadata$.pipe(
239+
map((cardMetadata) => cardMetadata.numSample)
240+
);
241+
235242
this.imageUrl$ = stepDatum$.pipe(
236243
map((stepDatum: ImageStepDatum | null) => {
237244
if (!stepDatum) {

tensorboard/webapp/metrics/views/card_renderer/image_card_test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,26 @@ describe('image card', () => {
272272
expect(slider).not.toBeTruthy();
273273
});
274274

275+
it('renders sample when numSample is larger than 1', () => {
276+
const timeSeries = [{wallTime: 100, imageId: 'ImageId1', step: 10}];
277+
provideMockCardSeriesData(
278+
selectSpy,
279+
PluginType.IMAGES,
280+
'card1',
281+
{sample: 5, numSample: 1.2e4},
282+
timeSeries,
283+
0 /* stepIndex */
284+
);
285+
286+
const fixture = createImageCardContainer('card1');
287+
fixture.detectChanges();
288+
289+
const metadata = fixture.debugElement.query(By.css('.metadata'));
290+
expect(metadata.nativeElement.textContent.trim()).toBe(
291+
'Step 10Sample 6/12,000'
292+
);
293+
});
294+
275295
it('dispatches event when step slider changes', () => {
276296
const timeSeries = [
277297
{wallTime: 100, imageId: 'ImageId1', step: 10},

0 commit comments

Comments
 (0)