Skip to content
Merged
1 change: 1 addition & 0 deletions tensorboard/components/tensor_widget/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ ts_library(
"dtype-utils.ts",
"health-pill-types.ts",
"shape-utils.ts",
"slicing-control.ts",
"string-utils.ts",
"tensor-widget.ts",
"tensor-widget-impl.ts",
Expand Down
1 change: 1 addition & 0 deletions tensorboard/components/tensor_widget/demo/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ <h1>TensorWidget Demo</h1>
<div id="tensor2" class="tensor"></div>
<div id="tensor3" class="tensor"></div>
<div id="tensor4" class="tensor"></div>
<div id="tensor5" class="tensor"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/tensorflow/1.2.7/tf.min.js"></script>
<script src="bundle.js"></script>
Expand Down
33 changes: 21 additions & 12 deletions tensorboard/components/tensor_widget/demo/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ function demo() {
tensorWidget.VERSION;

/////////////////////////////////////////////////////////////
// Render tensor0: a float32 scalar.
// float32 scalar.
const tensorDiv0 = document.getElementById('tensor0') as HTMLDivElement;
// TODO(cais): Replace this with a TensorFlow.js-based TensorView.
const tensorView0 = tensorToTensorView(tf.scalar(28));
Expand All @@ -128,7 +128,7 @@ function demo() {
tensorWidget0.render();

/////////////////////////////////////////////////////////////
// Render tensor1: a 1D int32 tensor.
// 1D int32 tensor.
const tensorDiv1 = document.getElementById('tensor1') as HTMLDivElement;
// TODO(cais): Replace this with a TensorFlow.js-based TensorView.
const tensorView1 = tensorToTensorView(
Expand All @@ -140,7 +140,7 @@ function demo() {
tensorWidget1.render();

/////////////////////////////////////////////////////////////
// Render tensor2: a 2D float32 scalar.
// 2D float32 scalar.
const tensor2Div = document.getElementById('tensor2') as HTMLDivElement;
const tensorView2 = tensorToTensorView(tf.randomNormal([128, 64]));
const tensorWidget2 = tensorWidget.tensorWidget(tensor2Div, tensorView2, {
Expand All @@ -149,7 +149,7 @@ function demo() {
tensorWidget2.render();

/////////////////////////////////////////////////////////////
// Render tensor3: a 2D float32 scalar with NaN and Infinities in it.
// 2D float32 scalar with NaN and Infinities in it.
const tensorDiv3 = document.getElementById('tensor3') as HTMLDivElement;
const tensorView3 = tensorToTensorView(
tf.tensor2d([[NaN, -Infinity], [Infinity, 0]])
Expand All @@ -161,17 +161,26 @@ function demo() {
tensorWidget3.render();

/////////////////////////////////////////////////////////////
// Render tensor4: a 3D float32 scalar, without the optional name.
// 3D float32 scalar, without the optional name.
const tensorDiv4 = document.getElementById('tensor4') as HTMLDivElement;
// TODO(cais): Replace this with a TensorFlow.js-based TensorView.
const tensorView4: any = {
spec: {
dtype: 'float32',
shape: [64, 32, 50],
},
};
const tensorView4 = tensorToTensorView(
tf.linspace(0, (64 * 32 * 50 - 1) / 100, 64 * 32 * 50).reshape([64, 32, 50])
);
const tensorWidget4 = tensorWidget.tensorWidget(tensorDiv4, tensorView4); // No name.
tensorWidget4.render();

/////////////////////////////////////////////////////////////
// 4D float32 scalar, without the optional name.
const tensorDiv5 = document.getElementById('tensor5') as HTMLDivElement;
const tensorView5 = tensorToTensorView(
tf
.linspace(0, (2 * 4 * 15 * 20 - 1) / 100, 2 * 4 * 15 * 20)
.reshape([2, 4, 15, 20])
);
const tensorWidget5 = tensorWidget.tensorWidget(tensorDiv5, tensorView5, {
name: 'FourDimensionalTensor',
});
tensorWidget5.render();
}

demo();
111 changes: 110 additions & 1 deletion tensorboard/components/tensor_widget/shape-utils-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ limitations under the License.

import {expect} from 'chai';

import {formatShapeForDisplay, getDefaultSlicingSpec} from './shape-utils';
import {
formatShapeForDisplay,
getDefaultSlicingSpec,
areSlicingSpecsCompatible,
} from './shape-utils';
import {TensorViewSlicingSpec} from './types';

describe('formatShapeForDisplay', () => {
it('returns string scalar for []', () => {
Expand Down Expand Up @@ -116,3 +121,107 @@ describe('getDefaultSlicingSpec', () => {
});
});
});

describe('dimensionsDiffer', () => {
it('Different orders in slicing dimensions are ignored', () => {
const spec0: TensorViewSlicingSpec = {
slicingDimsAndIndices: [
{
dim: 0,
index: 0,
},
{
dim: 1,
index: 0,
},
],
viewingDims: [2, 3],
verticalRange: null,
horizontalRange: null,
};
const spec1: TensorViewSlicingSpec = {
slicingDimsAndIndices: [
{
dim: 1,
index: 0,
},
{
dim: 0,
index: 0,
},
],
viewingDims: [2, 3],
verticalRange: null,
horizontalRange: null,
};
expect(areSlicingSpecsCompatible(spec0, spec1)).to.be.true;
});

it('Different slicing indices are ignored', () => {
const spec0: TensorViewSlicingSpec = {
slicingDimsAndIndices: [
{
dim: 0,
index: 8,
},
{
dim: 1,
index: 0,
},
],
viewingDims: [2, 3],
verticalRange: null,
horizontalRange: null,
};
const spec1: TensorViewSlicingSpec = {
slicingDimsAndIndices: [
{
dim: 0,
index: 0,
},
{
dim: 1,
index: 9,
},
],
viewingDims: [2, 3],
verticalRange: null,
horizontalRange: null,
};
expect(areSlicingSpecsCompatible(spec0, spec1)).to.be.true;
});

it('Different slicing and viewing dimensions are captured', () => {
const spec0: TensorViewSlicingSpec = {
slicingDimsAndIndices: [
{
dim: 0,
index: 0,
},
{
dim: 3,
index: 0,
},
],
viewingDims: [1, 2],
verticalRange: null,
horizontalRange: null,
};
const spec1: TensorViewSlicingSpec = {
slicingDimsAndIndices: [
{
dim: 0,
index: 0,
},
{
dim: 1,
index: 0,
},
],
viewingDims: [2, 3],
verticalRange: null,
horizontalRange: null,
};
expect(areSlicingSpecsCompatible(spec0, spec1)).to.be.false;
});
});
36 changes: 36 additions & 0 deletions tensorboard/components/tensor_widget/shape-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,39 @@ export function getDefaultSlicingSpec(shape: Shape): TensorViewSlicingSpec {
}
return slicingSpec;
}

/**
* Check if two slicing specs involve the compatible dimension arrangements.
*
* "Compatible dimension arrangement" means the same dimensions are used
* for viewing and the same dimensions are used for slicing.
*
* Note that the slicing indicies in the slicing dimensions are ignored.
* So are the ordering of the slicing dimensions.
*
* @param spec0
* @param spec1
* @return `true` if and only if the slicing dimensions and the viewing
* dimensions are compatible between `spec0` and `spec1`.
*/
export function areSlicingSpecsCompatible(
spec0: TensorViewSlicingSpec,
spec1: TensorViewSlicingSpec
): boolean {
if (spec0.viewingDims[0] !== spec1.viewingDims[0]) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this the canonical way to do this even if the length of the constituent arrays may be less than 1?

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 the length is less than 1, then [0] will give you undefined, which works for the logic here.

return false;
} else if (spec0.viewingDims[1] !== spec1.viewingDims[1]) {
return false;
} else {
// Check the slicing dimension.
const slicingDims0 = spec0.slicingDimsAndIndices.map(
(dimAndIndex) => dimAndIndex.dim
);
slicingDims0.sort();
const slicingDims1 = spec0.slicingDimsAndIndices.map(
(dimAndIndex) => dimAndIndex.dim
);
slicingDims1.sort();
return JSON.stringify(slicingDims0) === JSON.stringify(slicingDims1);
}
}
Loading