diff --git a/tensorboard/plugins/debugger/tensor_helper.py b/tensorboard/plugins/debugger/tensor_helper.py index 012da46f68..a184812ada 100644 --- a/tensorboard/plugins/debugger/tensor_helper.py +++ b/tensorboard/plugins/debugger/tensor_helper.py @@ -82,25 +82,42 @@ def array_view(array, slicing=None, mapping=None): """ dtype = str(array.dtype) + # String-type TensorFlow Tensors are represented as object-type arrays in + # numpy. We map the type name back to 'string' for clarity. + if dtype == 'object': + dtype = 'string' sliced_array = (array[command_parser._parse_slices(slicing)] if slicing else array) - shape = sliced_array.shape - if mapping == "image/png": - if len(sliced_array.shape) == 2: - return dtype, shape, array_to_base64_png(sliced_array) - elif len(sliced_array.shape) == 3: - raise NotImplementedError( - "image/png mapping for 3D array has not been implemented") - else: - raise ValueError("Invalid rank for image/png mapping: %d" % - len(sliced_array.shape)) - elif mapping == 'health-pill': - health_pill = health_pill_calc.calc_health_pill(array) - return dtype, shape, health_pill - elif mapping is None or mapping == '' or mapping.lower() == 'none': - return dtype, shape, sliced_array.tolist() + + if np.isscalar(sliced_array) and str(dtype) == 'string': + # When a string Tensor (for which dtype is 'object') is sliced down to only + # one element, it becomes a string, instead of an numpy array. + # We preserve the dimensionality of original array in the returned shape + # and slice. + ndims = len(array.shape) + slice_shape = [] + for _ in range(ndims): + sliced_array = [sliced_array] + slice_shape.append(1) + return dtype, tuple(slice_shape), sliced_array else: - raise ValueError("Invalid mapping: %s" % mapping) + shape = sliced_array.shape + if mapping == "image/png": + if len(sliced_array.shape) == 2: + return dtype, shape, array_to_base64_png(sliced_array) + elif len(sliced_array.shape) == 3: + raise NotImplementedError( + "image/png mapping for 3D array has not been implemented") + else: + raise ValueError("Invalid rank for image/png mapping: %d" % + len(sliced_array.shape)) + elif mapping == 'health-pill': + health_pill = health_pill_calc.calc_health_pill(array) + return dtype, shape, health_pill + elif mapping is None or mapping == '' or mapping.lower() == 'none': + return dtype, shape, sliced_array.tolist() + else: + raise ValueError("Invalid mapping: %s" % mapping) IMAGE_COLOR_CHANNELS = 3 diff --git a/tensorboard/plugins/debugger/tensor_helper_test.py b/tensorboard/plugins/debugger/tensor_helper_test.py index c819e44450..e9829ed98a 100644 --- a/tensorboard/plugins/debugger/tensor_helper_test.py +++ b/tensorboard/plugins/debugger/tensor_helper_test.py @@ -101,6 +101,42 @@ def testImagePngMappingWorksForArrayWithInfAndNaN(self): self.assertAllClose([255, 255, 255], decoded_x[1, 1, :]) # 3.3. self.assertAllClose(tensor_helper.NAN_RGB, decoded_x[1, 2, :]) # nan. + def testArrayViewSlicingDownNumericTensorToOneElement(self): + x = np.array([[1.1, 2.2, np.inf], [-np.inf, 3.3, np.nan]], dtype=np.float32) + dtype, shape, data = tensor_helper.array_view(x, slicing='[0,0]') + self.assertEqual('float32', dtype) + self.assertEqual(tuple(), shape) + self.assertTrue(np.allclose(1.1, data)) + + def testArrayViewSlicingStringTensorToNonScalarSubArray(self): + # Construct a numpy array that corresponds to a TensorFlow string tensor + # value. + x = np.array([['foo', 'bar', 'qux'], ['baz', 'corge', 'grault']], + dtype=np.object) + dtype, shape, data = tensor_helper.array_view(x, slicing='[:2, :2]') + self.assertEqual('string', dtype) + self.assertEqual((2, 2), shape) + self.assertEqual([['foo', 'bar'], ['baz', 'corge']], data) + + def testArrayViewSlicingStringTensorToScalar(self): + # Construct a numpy array that corresponds to a TensorFlow string tensor + # value. + x = np.array([['foo', 'bar', 'qux'], ['baz', 'corge', 'grault']], + dtype=np.object) + dtype, shape, data = tensor_helper.array_view(x, slicing='[1, 1]') + self.assertEqual('string', dtype) + self.assertEqual((1, 1), shape) + self.assertEqual([['corge']], data) + + def testArrayViewOnScalarString(self): + # Construct a numpy scalar that corresponds to a TensorFlow string tensor + # value. + x = np.array('foo', dtype=np.object) + dtype, shape, data = tensor_helper.array_view(x) + self.assertEqual('string', dtype) + self.assertEqual(tuple(), shape) + self.assertEqual('foo', data) + def testImagePngMappingWorksForArrayWithOnlyInfAndNaN(self): x = np.array([[np.nan, -np.inf], [np.inf, np.nan]], dtype=np.float32) dtype, shape, data = tensor_helper.array_view(x, mapping="image/png")