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

[tensor-widget] Add support for hierarchical menu and image view of tensor values #2729

Merged
merged 16 commits into from
Oct 9, 2019
3 changes: 3 additions & 0 deletions tensorboard/components/tensor_widget/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ tf_ts_library(
tf_ts_library(
name = "tensor_widget_lib",
srcs = [
"colormap.ts",
"dtype-utils.ts",
"health-pill-types.ts",
"menu.ts",
"selection.ts",
"shape-utils.ts",
"slicing-control.ts",
Expand All @@ -63,6 +65,7 @@ tf_ts_library(
name = "test_lib",
testonly = True,
srcs = [
"colormap-test.ts",
"dtype-utils-test.ts",
"selection-test.ts",
"shape-utils-test.ts",
Expand Down
79 changes: 79 additions & 0 deletions tensorboard/components/tensor_widget/colormap-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/* Copyright 2019 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.
==============================================================================*/

/** Unit tests for colormaps. */

import {expect} from 'chai';

import {GrayscaleColorMap} from './colormap';

describe('GrayscaleColorMap', () => {
it('max < min causes constructor error', () => {
const min = 3;
const max = 2;
expect(() => new GrayscaleColorMap(min, max)).to.throw(/max.*<.*min/);
});

it('NaN or Infinity min or max causes constructor error', () => {
expect(() => new GrayscaleColorMap(0, Infinity)).to.throw(
/max.*not finite/
);
expect(() => new GrayscaleColorMap(0, -Infinity)).to.throw(
/max.*not finite/
);
expect(() => new GrayscaleColorMap(0, NaN)).to.throw(/max.*not finite/);
expect(() => new GrayscaleColorMap(Infinity, 0)).to.throw(
/min.*not finite/
);
expect(() => new GrayscaleColorMap(-Infinity, 0)).to.throw(
/min.*not finite/
);
expect(() => new GrayscaleColorMap(NaN, 0)).to.throw(/min.*not finite/);
});

it('max > min, finite values', () => {
const min = 0;
const max = 10;
const colormap = new GrayscaleColorMap(min, max);
expect(colormap.getRGB(0)).to.eql([0, 0, 0]);
expect(colormap.getRGB(5)).to.eql([127.5, 127.5, 127.5]);
expect(colormap.getRGB(10)).to.eql([255, 255, 255]);
// Over-limits.
expect(colormap.getRGB(-100)).to.eql([0, 0, 0]);
expect(colormap.getRGB(500)).to.eql([255, 255, 255]);
});

it('max > min, non-finite values', () => {
const min = 0;
const max = 10;
const colormap = new GrayscaleColorMap(min, max);
expect(colormap.getRGB(NaN)).to.eql([255, 0, 0]);
expect(colormap.getRGB(-Infinity)).to.eql([255, 255 / 2, 0]);
expect(colormap.getRGB(Infinity)).to.eql([0, 0, 255]);
});

it('max === min, non-finite values', () => {
const min = -3.2;
const max = -3.2;
const colormap = new GrayscaleColorMap(min, max);
expect(colormap.getRGB(-32)).to.eql([127.5, 127.5, 127.5]);
expect(colormap.getRGB(-3.2)).to.eql([127.5, 127.5, 127.5]);
expect(colormap.getRGB(0)).to.eql([127.5, 127.5, 127.5]);
expect(colormap.getRGB(32)).to.eql([127.5, 127.5, 127.5]);
expect(colormap.getRGB(NaN)).to.eql([255, 0, 0]);
expect(colormap.getRGB(-Infinity)).to.eql([255, 255 / 2, 0]);
expect(colormap.getRGB(Infinity)).to.eql([0, 0, 255]);
});
});
80 changes: 80 additions & 0 deletions tensorboard/components/tensor_widget/colormap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/* Copyright 2019 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.
==============================================================================*/

/** Colormap used to display numeric values */

const MAX_RGB = 255;

/**
* Abstract base class for colormap.
*
* A colormap maps a numeric value to an RGB color.
*/
export abstract class ColorMap {
/**
* Constructor of ColorMap.
* @param min Minimum. Must be a finite value.
* @param max Maximum. Must be finite and >= `min`.
*/
constructor(protected readonly min: number, protected readonly max: number) {
if (!isFinite(min)) {
throw new Error(`min value (${min}) is not finite`);
}
if (!isFinite(max)) {
throw new Error(`max value (${max}) is not finite`);
}
if (max < min) {
throw new Error(`max (${max}) is < min (${min})`);
}
}

/**
* Get the RGB value based on element value.
* @param value The element value to be mapped to RGB color value.
* @returns RGB color value represented as a length-3 number array.
* The range of RGB values is [0, 255].
*/
abstract getRGB(value: number): [number, number, number];
}

/**
* A grayscale color map implementation.
*/
export class GrayscaleColorMap extends ColorMap {
getRGB(value: number): [number, number, number] {
// This color scheme for pathological values matches tfdbg v1's Health Pills
// feature.
if (isNaN(value)) {
// NaN.
return [MAX_RGB, 0, 0];
} else if (!isFinite(value)) {
if (value > 0) {
// +Infinity.
return [0, 0, MAX_RGB];
} else {
// -Infinity.
return [MAX_RGB, MAX_RGB / 2, 0];
}
}
let relativeValue =
this.min === this.max ? 0.5 : (value - this.min) / (this.max - this.min);
relativeValue = Math.max(Math.min(relativeValue, 1), 0);
return [
MAX_RGB * relativeValue,
MAX_RGB * relativeValue,
MAX_RGB * relativeValue,
];
}
}
2 changes: 1 addition & 1 deletion tensorboard/components/tensor_widget/demo/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ <h1>TensorWidget Demo</h1>
<div id="tensor6" class="tensor"></div>
<div id="tensor7" class="tensor"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/tensorflow/1.2.7/tf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tensorflow/1.2.9/tf.min.js"></script>
<script src="bundle.js"></script>
<script>
const DEMO_MODULE_PATH =
Expand Down
34 changes: 31 additions & 3 deletions tensorboard/components/tensor_widget/demo/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ limitations under the License.

import * as tensorWidget from '../tensor-widget';
import {TensorViewSlicingSpec, TensorView} from '../types';
import {
BaseTensorNumericSummary,
BooleanOrNumericTensorNumericSummary,
} from '../health-pill-types';

// TODO(cais): Find a way to import tfjs-core here, instead of depending on
// a global variable.
Expand Down Expand Up @@ -171,9 +175,33 @@ export function tensorToTensorView(x: any): tensorWidget.TensorView {
// TODO(cais): Check memory leak.
return retval;
},
getHealthPill: async () => {
// return calculateHealthPill(x);
throw new Error('Not implemented yet.');
getNumericSummary: async () => {
if (x.dtype === 'float32' || x.dtype === 'int32' || x.dtype == 'bool') {
// This is not an efficient way to compute the maximum and minimum of
// finite values in a tensor. But it is okay as this is just a demo.
const data = x.dataSync() as Float32Array;
let minimum = Infinity;
let maximum = -Infinity;
for (let i = 0; i < data.length; ++i) {
const value = data[i];
if (!isFinite(value)) {
continue;
}
if (value < minimum) {
minimum = value;
}
if (value > maximum) {
maximum = value;
}
}
return {
elementCount: x.size,
minimum,
maximum,
};
} else {
return {elementCount: x.size};
}
},
};
}
Expand Down
15 changes: 12 additions & 3 deletions tensorboard/components/tensor_widget/health-pill-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ limitations under the License.
==============================================================================*/

/**
* Health pill: A summary of a tensor's element values.
* A summary of a tensor's element values.
*
* It contains information such as
* - the distribution of the tensor's values among different value categories
Expand All @@ -25,10 +25,19 @@ limitations under the License.
* This base health-pill interface is general enough for all tensor
* data types, including boolean, integer, float and string.
*/
export interface BaseTensorHealthPill {
export interface BaseTensorNumericSummary {
/** Number of elements in the tensor. */
elementCount: number;
}

// TODO(cais): Add sub-interfaces of `BaseTensorHealthPill` for other tensor
export interface BooleanOrNumericTensorNumericSummary
extends BaseTensorNumericSummary {
/** Minimum of all finite values. */
minimum: number | boolean;

/** Maximum of all finite values. */
maximum: number | boolean;
}

// TODO(cais): Add sub-interfaces of `BaseTensorNumericSummary` for other tensor
// dtypes.
Loading