Skip to content

Commit

Permalink
histogram: make legacy histogram visualization compatible with v3 dat…
Browse files Browse the repository at this point in the history
…a format (tensorflow#5391)

* make legacy histogram visualization compatible with v3

* buildifier

* update copyright date

* fixes

  - Remove edge case special handling.
  - Apply offsets to `minmin` and `maxmax` for better visualization.

* move min/max offset for single value data back to intermediateToD3

* use toBeCloseTo for floating point number comparison

* use const since reference to the array does not change

* change type back to const
  • Loading branch information
yatbear committed Mar 27, 2023
1 parent 9d96b7b commit 2387a79
Show file tree
Hide file tree
Showing 3 changed files with 229 additions and 15 deletions.
27 changes: 26 additions & 1 deletion tensorboard/plugins/histogram/tf_histogram_dashboard/BUILD
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("//tensorboard/defs:defs.bzl", "tf_ts_library")
load("//tensorboard/defs:defs.bzl", "tf_ng_web_test_suite", "tf_ts_library")

package(default_visibility = ["//tensorboard:internal"])

Expand Down Expand Up @@ -31,3 +31,28 @@ tf_ts_library(
"@npm//lodash",
],
)

tf_ts_library(
name = "histogram_core_test_lib",
testonly = True,
srcs = [
"histogram_core_test.ts",
],
deps = [
":tf_histogram_dashboard",
"//tensorboard/webapp/angular:expect_angular_core_testing",
"//tensorboard/webapp/angular:expect_angular_platform_browser_animations",
"//tensorboard/webapp/widgets/intersection_observer:intersection_observer_testing",
"//tensorboard/webapp/widgets/linked_time_fob",
"@npm//@angular/core",
"@npm//@angular/platform-browser",
"@npm//@types/jasmine",
],
)

tf_ng_web_test_suite(
name = "karma_test",
deps = [
":histogram_core_test_lib",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export type BackendHistogram = [
export type IntermediateHistogram = {
wall_time: number; // in seconds
step: number;
min: number;
max: number;
min: number | undefined;
max: number | undefined;
buckets: {
left: number;
right: number;
Expand Down Expand Up @@ -82,20 +82,27 @@ export function backendToIntermediate(
*/
export function intermediateToD3(
histogram: IntermediateHistogram,
min: number,
max: number,
min: number | undefined,
max: number | undefined,
numBins = 30
): D3HistogramBin[] {
if (min === undefined || max == undefined) {
min = 0;
max = 0;
}
if (max === min) {
// Create bins even if all the data has a single value.
// If the output range is 0 width, use a default non 0 range for
// visualization purpose.
max = min * 1.1 + 1;
min = min / 1.1 - 1;
}
// Terminology note: _buckets_ are the input to this function,
// while _bins_ are our output.
const binWidth = (max - min) / numBins;
let bucketIndex = 0;
return d3.range(min, max, binWidth).map((binLeft) => {
const d3HistogramBins: D3HistogramBin[] = [];
for (let i = 0; i < numBins; i++) {
const binLeft = min + i * binWidth;
const binRight = binLeft + binWidth;
// Take the count of each existing bucket, multiply it by the
// proportion of overlap with the new bin, then sum and store as the
Expand All @@ -107,12 +114,21 @@ export function intermediateToD3(
// infinite-sized.
const bucketRight = Math.min(max, histogram.buckets[bucketIndex].right);
const bucketLeft = Math.max(min, histogram.buckets[bucketIndex].left);
const intersect =
Math.min(bucketRight, binRight) - Math.max(bucketLeft, binLeft);
const count =
(intersect / (bucketRight - bucketLeft)) *
histogram.buckets[bucketIndex].count;
binY += intersect > 0 ? count : 0;
const bucketWidth = bucketRight - bucketLeft;
if (bucketWidth > 0) {
const intersect =
Math.min(bucketRight, binRight) - Math.max(bucketLeft, binLeft);
const count =
(intersect / (bucketRight - bucketLeft)) *
histogram.buckets[bucketIndex].count;
binY += intersect > 0 ? count : 0;
} else {
const isFinalBin = binRight >= max;
const singleValueOverlap =
binLeft <= bucketLeft &&
(isFinalBin ? bucketRight <= binRight : bucketRight < binRight);
binY += singleValueOverlap ? histogram.buckets[bucketIndex].count : 0;
}
// If `bucketRight` is bigger than `binRight`, then this bin is
// finished and there is data for the next bin, so don't increment
// `bucketIndex`.
Expand All @@ -121,8 +137,9 @@ export function intermediateToD3(
}
bucketIndex++;
}
return {x: binLeft, dx: binWidth, y: binY};
});
d3HistogramBins.push({x: binLeft, dx: binWidth, y: binY});
}
return d3HistogramBins;
}

export function backendToVz(histograms: BackendHistogram[]): VzHistogram[] {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

import {
BackendHistogramBin,
backendToIntermediate,
backendToVz,
intermediateToD3,
} from './histogramCore';

describe('histogram core', () => {
describe('backendToIntermediate', () => {
it('handles empty case', () => {
expect(backendToIntermediate([0, 0, []])).toEqual({
wall_time: 0,
step: 0,
min: undefined,
max: undefined,
buckets: [],
});
});

it('converts backend histogram to intermediate', () => {
const bins: BackendHistogramBin[] = [
[1, 2, 2],
[2, 3, 1],
[3, 4, 1],
];
expect(backendToIntermediate([1000, 2, bins])).toEqual({
wall_time: 1000,
step: 2,
min: 1,
max: 4,
buckets: [
{left: 1, right: 2, count: 2},
{left: 2, right: 3, count: 1},
{left: 3, right: 4, count: 1},
],
});
});
});

describe('intermediateToD3', () => {
it('generates empty bins when input is empty', () => {
const results = intermediateToD3(
backendToIntermediate([0, 0, []]),
0,
10,
20
);
let total_count: number = 0;
results.forEach((bin) => (total_count += bin.y));
expect(results.length).toEqual(20);
expect(total_count).toEqual(0);
});

it('handles data consisting of one single value', () => {
const bins: BackendHistogramBin[] = [
[1, 1, 0],
[1, 1, 0],
[1, 1, 10],
];
const newMin = 1 / 1.1 - 1;
const newMax = 1 * 1.1 + 1;
const binWidth = (newMax - newMin) / 2;
const results = intermediateToD3(
backendToIntermediate([0, 0, bins]),
1,
1,
2
);
expect(results.length).toEqual(2);
expect(results[0].x).toBeCloseTo(newMin);
expect(results[0].dx).toBeCloseTo(binWidth);
expect(results[0].y).toEqual(10);
expect(results[1].x).toBeCloseTo(newMin + binWidth);
expect(results[1].dx).toBeCloseTo(binWidth);
expect(results[1].y).toEqual(0);
});

it('converts intermediate histogram data to D3 format', () => {
const bins: BackendHistogramBin[] = [
[1, 5, 2],
[5, 10, 4],
];
expect(
intermediateToD3(backendToIntermediate([0, 0, bins]), 1, 10, 2)
).toEqual([
{x: 1, dx: 4.5, y: 2.4},
{x: 5.5, dx: 4.5, y: 3.6},
]);
});
});

describe('backendToVz', () => {
it('handles empty case', () => {
expect(backendToVz([])).toEqual([]);
});

it('handles data consisting of one single value', () => {
const bins: BackendHistogramBin[] = [[11, 11, 100]];
const results = backendToVz([[0, 0, bins]]);
let total_count: number = 0;
for (let result of results) {
result.bins.forEach((bin) => (total_count += bin.y));
}
expect(results.length).toEqual(1);
expect(results[0].bins.length).toEqual(30);
expect(total_count).toEqual(100);
// Middle bin gets the count.
expect(results[0].bins[14].y).toEqual(100);
});

it('converts backend histogram data to VzHistogram', () => {
const bins: BackendHistogramBin[] = [
[10, 20, 100],
[20, 30, 300],
[30, 40, 600],
];
expect(backendToVz([[1000, 1, bins]])).toEqual([
{
wall_time: 1000,
step: 1,
bins: [
{x: 10, dx: 1, y: 10},
{x: 11, dx: 1, y: 10},
{x: 12, dx: 1, y: 10},
{x: 13, dx: 1, y: 10},
{x: 14, dx: 1, y: 10},
{x: 15, dx: 1, y: 10},
{x: 16, dx: 1, y: 10},
{x: 17, dx: 1, y: 10},
{x: 18, dx: 1, y: 10},
{x: 19, dx: 1, y: 10},
{x: 20, dx: 1, y: 30},
{x: 21, dx: 1, y: 30},
{x: 22, dx: 1, y: 30},
{x: 23, dx: 1, y: 30},
{x: 24, dx: 1, y: 30},
{x: 25, dx: 1, y: 30},
{x: 26, dx: 1, y: 30},
{x: 27, dx: 1, y: 30},
{x: 28, dx: 1, y: 30},
{x: 29, dx: 1, y: 30},
{x: 30, dx: 1, y: 60},
{x: 31, dx: 1, y: 60},
{x: 32, dx: 1, y: 60},
{x: 33, dx: 1, y: 60},
{x: 34, dx: 1, y: 60},
{x: 35, dx: 1, y: 60},
{x: 36, dx: 1, y: 60},
{x: 37, dx: 1, y: 60},
{x: 38, dx: 1, y: 60},
{x: 39, dx: 1, y: 60},
],
},
]);
});
});
});

0 comments on commit 2387a79

Please sign in to comment.