Skip to content

Commit ae205a7

Browse files
authored
[tensor-widget] Add slicing control to support 3D+ tensors (#2565)
* Motivation for features / changes * Continue developing TensorWidget * Technical description of changes * Add a slicing control UI, included in the newly created file silcing-control.ts * This UI allows the user to move between slices and to flexibly change which dimensions of a 3D+ tensor are sliced and which are viewed in the value table. * The dimension selection is supported by an ad hoc dropdown menu. Co-author: @bileschi
1 parent f06f40b commit ae205a7

File tree

8 files changed

+641
-27
lines changed

8 files changed

+641
-27
lines changed

tensorboard/components/tensor_widget/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ ts_library(
3333
"dtype-utils.ts",
3434
"health-pill-types.ts",
3535
"shape-utils.ts",
36+
"slicing-control.ts",
3637
"string-utils.ts",
3738
"tensor-widget.ts",
3839
"tensor-widget-impl.ts",

tensorboard/components/tensor_widget/demo/demo.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ <h1>TensorWidget Demo</h1>
3737
<div id="tensor2" class="tensor"></div>
3838
<div id="tensor3" class="tensor"></div>
3939
<div id="tensor4" class="tensor"></div>
40+
<div id="tensor5" class="tensor"></div>
4041

4142
<script src="https://cdnjs.cloudflare.com/ajax/libs/tensorflow/1.2.7/tf.min.js"></script>
4243
<script src="bundle.js"></script>

tensorboard/components/tensor_widget/demo/demo.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ function demo() {
118118
tensorWidget.VERSION;
119119

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

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

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

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

163163
/////////////////////////////////////////////////////////////
164-
// Render tensor4: a 3D float32 scalar, without the optional name.
164+
// 3D float32 scalar, without the optional name.
165165
const tensorDiv4 = document.getElementById('tensor4') as HTMLDivElement;
166-
// TODO(cais): Replace this with a TensorFlow.js-based TensorView.
167-
const tensorView4: any = {
168-
spec: {
169-
dtype: 'float32',
170-
shape: [64, 32, 50],
171-
},
172-
};
166+
const tensorView4 = tensorToTensorView(
167+
tf.linspace(0, (64 * 32 * 50 - 1) / 100, 64 * 32 * 50).reshape([64, 32, 50])
168+
);
173169
const tensorWidget4 = tensorWidget.tensorWidget(tensorDiv4, tensorView4); // No name.
174170
tensorWidget4.render();
171+
172+
/////////////////////////////////////////////////////////////
173+
// 4D float32 scalar, without the optional name.
174+
const tensorDiv5 = document.getElementById('tensor5') as HTMLDivElement;
175+
const tensorView5 = tensorToTensorView(
176+
tf
177+
.linspace(0, (2 * 4 * 15 * 20 - 1) / 100, 2 * 4 * 15 * 20)
178+
.reshape([2, 4, 15, 20])
179+
);
180+
const tensorWidget5 = tensorWidget.tensorWidget(tensorDiv5, tensorView5, {
181+
name: 'FourDimensionalTensor',
182+
});
183+
tensorWidget5.render();
175184
}
176185

177186
demo();

tensorboard/components/tensor_widget/shape-utils-test.ts

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ limitations under the License.
1515

1616
import {expect} from 'chai';
1717

18-
import {formatShapeForDisplay, getDefaultSlicingSpec} from './shape-utils';
18+
import {
19+
formatShapeForDisplay,
20+
getDefaultSlicingSpec,
21+
areSlicingSpecsCompatible,
22+
} from './shape-utils';
23+
import {TensorViewSlicingSpec} from './types';
1924

2025
describe('formatShapeForDisplay', () => {
2126
it('returns string scalar for []', () => {
@@ -116,3 +121,107 @@ describe('getDefaultSlicingSpec', () => {
116121
});
117122
});
118123
});
124+
125+
describe('dimensionsDiffer', () => {
126+
it('Different orders in slicing dimensions are ignored', () => {
127+
const spec0: TensorViewSlicingSpec = {
128+
slicingDimsAndIndices: [
129+
{
130+
dim: 0,
131+
index: 0,
132+
},
133+
{
134+
dim: 1,
135+
index: 0,
136+
},
137+
],
138+
viewingDims: [2, 3],
139+
verticalRange: null,
140+
horizontalRange: null,
141+
};
142+
const spec1: TensorViewSlicingSpec = {
143+
slicingDimsAndIndices: [
144+
{
145+
dim: 1,
146+
index: 0,
147+
},
148+
{
149+
dim: 0,
150+
index: 0,
151+
},
152+
],
153+
viewingDims: [2, 3],
154+
verticalRange: null,
155+
horizontalRange: null,
156+
};
157+
expect(areSlicingSpecsCompatible(spec0, spec1)).to.be.true;
158+
});
159+
160+
it('Different slicing indices are ignored', () => {
161+
const spec0: TensorViewSlicingSpec = {
162+
slicingDimsAndIndices: [
163+
{
164+
dim: 0,
165+
index: 8,
166+
},
167+
{
168+
dim: 1,
169+
index: 0,
170+
},
171+
],
172+
viewingDims: [2, 3],
173+
verticalRange: null,
174+
horizontalRange: null,
175+
};
176+
const spec1: TensorViewSlicingSpec = {
177+
slicingDimsAndIndices: [
178+
{
179+
dim: 0,
180+
index: 0,
181+
},
182+
{
183+
dim: 1,
184+
index: 9,
185+
},
186+
],
187+
viewingDims: [2, 3],
188+
verticalRange: null,
189+
horizontalRange: null,
190+
};
191+
expect(areSlicingSpecsCompatible(spec0, spec1)).to.be.true;
192+
});
193+
194+
it('Different slicing and viewing dimensions are captured', () => {
195+
const spec0: TensorViewSlicingSpec = {
196+
slicingDimsAndIndices: [
197+
{
198+
dim: 0,
199+
index: 0,
200+
},
201+
{
202+
dim: 3,
203+
index: 0,
204+
},
205+
],
206+
viewingDims: [1, 2],
207+
verticalRange: null,
208+
horizontalRange: null,
209+
};
210+
const spec1: TensorViewSlicingSpec = {
211+
slicingDimsAndIndices: [
212+
{
213+
dim: 0,
214+
index: 0,
215+
},
216+
{
217+
dim: 1,
218+
index: 0,
219+
},
220+
],
221+
viewingDims: [2, 3],
222+
verticalRange: null,
223+
horizontalRange: null,
224+
};
225+
expect(areSlicingSpecsCompatible(spec0, spec1)).to.be.false;
226+
});
227+
});

tensorboard/components/tensor_widget/shape-utils.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,39 @@ export function getDefaultSlicingSpec(shape: Shape): TensorViewSlicingSpec {
8080
}
8181
return slicingSpec;
8282
}
83+
84+
/**
85+
* Check if two slicing specs involve the compatible dimension arrangements.
86+
*
87+
* "Compatible dimension arrangement" means the same dimensions are used
88+
* for viewing and the same dimensions are used for slicing.
89+
*
90+
* Note that the slicing indicies in the slicing dimensions are ignored.
91+
* So are the ordering of the slicing dimensions.
92+
*
93+
* @param spec0
94+
* @param spec1
95+
* @return `true` if and only if the slicing dimensions and the viewing
96+
* dimensions are compatible between `spec0` and `spec1`.
97+
*/
98+
export function areSlicingSpecsCompatible(
99+
spec0: TensorViewSlicingSpec,
100+
spec1: TensorViewSlicingSpec
101+
): boolean {
102+
if (spec0.viewingDims[0] !== spec1.viewingDims[0]) {
103+
return false;
104+
} else if (spec0.viewingDims[1] !== spec1.viewingDims[1]) {
105+
return false;
106+
} else {
107+
// Check the slicing dimension.
108+
const slicingDims0 = spec0.slicingDimsAndIndices.map(
109+
(dimAndIndex) => dimAndIndex.dim
110+
);
111+
slicingDims0.sort();
112+
const slicingDims1 = spec0.slicingDimsAndIndices.map(
113+
(dimAndIndex) => dimAndIndex.dim
114+
);
115+
slicingDims1.sort();
116+
return JSON.stringify(slicingDims0) === JSON.stringify(slicingDims1);
117+
}
118+
}

0 commit comments

Comments
 (0)