diff options
author | 2016-12-14 07:46:46 -0800 | |
---|---|---|
committer | 2016-12-14 08:07:36 -0800 | |
commit | 72db22494e491cdf7b18ea9736b3d2fb87b6b28c (patch) | |
tree | 759e6440f125da7d648893bb62dcf1c8ce2278ff | |
parent | 5adafddfbe0628e4de9ecf72c204ae8828539dfb (diff) |
Improve error messages in the embedding projector.
- When we fail to parse the bytes, give a meaningful error message.
- When we fail to make requests to tensors / metadata / config, give a meaningful error message
Change: 142016403
6 files changed, 61 insertions, 14 deletions
diff --git a/tensorflow/tensorboard/components/vz_projector/data-provider-demo.ts b/tensorflow/tensorboard/components/vz_projector/data-provider-demo.ts index 0d54526db1..bf1bc0b255 100644 --- a/tensorflow/tensorboard/components/vz_projector/data-provider-demo.ts +++ b/tensorflow/tensorboard/components/vz_projector/data-provider-demo.ts @@ -49,7 +49,14 @@ export class DemoDataProvider implements DataProvider { let msgId = logging.setModalMessage('Fetching projector config...'); d3.json(this.projectorConfigPath, (err, projectorConfig) => { if (err) { - logging.setErrorMessage(err.responseText); + let errorMessage = err; + // If the error is a valid XMLHttpResponse, it's possible this is a + // cross-origin error. + if (err.responseText != null) { + errorMessage = 'Cannot fetch projector config, possibly a ' + + 'Cross-Origin request error.'; + } + logging.setErrorMessage(errorMessage, 'fetching projector config'); return; } logging.setModalMessage(null, msgId); @@ -71,7 +78,7 @@ export class DemoDataProvider implements DataProvider { logging.setModalMessage('Fetching tensors...', TENSORS_MSG_ID); d3.text(url, (error: any, dataString: string) => { if (error) { - logging.setErrorMessage(error.responseText); + logging.setErrorMessage(error.responseText, 'fetching tensors'); return; } dataProvider.parseTensors(dataString).then(points => { diff --git a/tensorflow/tensorboard/components/vz_projector/data-provider-server.ts b/tensorflow/tensorboard/components/vz_projector/data-provider-server.ts index 1ce68a956f..ff535468de 100644 --- a/tensorflow/tensorboard/components/vz_projector/data-provider-server.ts +++ b/tensorflow/tensorboard/components/vz_projector/data-provider-server.ts @@ -52,7 +52,7 @@ export class ServerDataProvider implements DataProvider { let msgId = logging.setModalMessage('Fetching runs...'); d3.json(`${this.routePrefix}/runs`, (err, runs: string[]) => { if (err) { - logging.setErrorMessage(err.responseText); + logging.setErrorMessage(err.responseText, 'fetching runs'); return; } logging.setModalMessage(null, msgId); @@ -71,7 +71,7 @@ export class ServerDataProvider implements DataProvider { d3.json(`${this.routePrefix}/info?run=${run}`, (err, config: ProjectorConfig) => { if (err) { - logging.setErrorMessage(err.responseText); + logging.setErrorMessage(err.responseText, 'fetching projector config'); return; } logging.setModalMessage(null, msgId); diff --git a/tensorflow/tensorboard/components/vz_projector/data-provider.ts b/tensorflow/tensorboard/components/vz_projector/data-provider.ts index 9c6d675fc9..b8db61cf7c 100644 --- a/tensorflow/tensorboard/components/vz_projector/data-provider.ts +++ b/tensorflow/tensorboard/components/vz_projector/data-provider.ts @@ -105,10 +105,17 @@ export function retrieveTensorAsBytes( xhr.onload = () => { if (xhr.status !== 200) { let msg = String.fromCharCode.apply(null, new Uint8Array(xhr.response)); - logging.setErrorMessage(msg); + logging.setErrorMessage(msg, 'fetching tensors'); return; } - let data = new Float32Array(xhr.response); + let data: Float32Array; + try { + data = new Float32Array(xhr.response); + } catch (e) { + logging.setErrorMessage(e, 'parsing tensor bytes'); + return; + } + let dim = embedding.tensorShape[1]; let N = data.length / dim; if (embedding.tensorShape[0] > N) { @@ -311,7 +318,7 @@ export function retrieveSpriteAndMetadataInfo(metadataPath: string, logging.setModalMessage('Fetching metadata...', METADATA_MSG_ID); d3.text(metadataPath, (err: any, rawMetadata: string) => { if (err) { - logging.setErrorMessage(err.responseText); + logging.setErrorMessage(err.responseText, 'fetching metadata'); reject(err); return; } diff --git a/tensorflow/tensorboard/components/vz_projector/data.ts b/tensorflow/tensorboard/components/vz_projector/data.ts index bae0a33e9e..f39ffb7af1 100644 --- a/tensorflow/tensorboard/components/vz_projector/data.ts +++ b/tensorflow/tensorboard/components/vz_projector/data.ts @@ -339,15 +339,45 @@ export class DataSet { }); } - mergeMetadata(metadata: SpriteAndMetadataInfo) { + /** + * Merges metadata to the dataset and returns whether it succeeded. + */ + mergeMetadata(metadata: SpriteAndMetadataInfo): boolean { if (metadata.pointsInfo.length !== this.points.length) { - logging.setWarningMessage( - `Number of tensors (${this.points.length}) do not match` + - ` the number of lines in metadata (${metadata.pointsInfo.length}).`); + let errorMessage = `Number of tensors (${this.points.length}) do not` + + ` match the number of lines in metadata` + + ` (${metadata.pointsInfo.length}).`; + + if (metadata.stats.length === 1 && + this.points.length + 1 === metadata.pointsInfo.length) { + // If there is only one column of metadata and the number of points is + // exactly one less than the number of metadata lines, this is due to an + // unnecessary header line in the metadata and we can show a meaningful + // error. + logging.setErrorMessage( + errorMessage + ' Single column metadata should not have a header ' + + 'row.', + 'merging metadata'); + return false; + } else if ( + metadata.stats.length > 1 && + this.points.length - 1 === metadata.pointsInfo.length) { + // If there are multiple columns of metadata and the number of points is + // exactly one greater than the number of lines in the metadata, this + // means there is a missing metadata header. + logging.setErrorMessage( + errorMessage + ' Multi-column metadata should have a header ' + + 'row with column labels.', + 'merging metadata'); + return false; + } + + logging.setWarningMessage(errorMessage); } this.spriteAndMetadataInfo = metadata; metadata.pointsInfo.slice(0, this.points.length) .forEach((m, i) => this.points[i].metadata = m); + return true; } stopTSNE() { diff --git a/tensorflow/tensorboard/components/vz_projector/logging.ts b/tensorflow/tensorboard/components/vz_projector/logging.ts index 7cfaf84a09..b51b702653 100644 --- a/tensorflow/tensorboard/components/vz_projector/logging.ts +++ b/tensorflow/tensorboard/components/vz_projector/logging.ts @@ -85,8 +85,8 @@ export function setModalMessage( return id; } -export function setErrorMessage(errMsg: string) { - setModalMessage(errMsg, null, 'Error', true); +export function setErrorMessage(errMsg: string, task?: string) { + setModalMessage(errMsg, null, 'Error ' + (task != null ? task : ''), true); } /** diff --git a/tensorflow/tensorboard/components/vz_projector/vz-projector.ts b/tensorflow/tensorboard/components/vz_projector/vz-projector.ts index b559f01af3..107062ea07 100644 --- a/tensorflow/tensorboard/components/vz_projector/vz-projector.ts +++ b/tensorflow/tensorboard/components/vz_projector/vz-projector.ts @@ -171,7 +171,10 @@ export class Projector extends ProjectorPolymer implements spriteAndMetadata.pointsInfo = pointsInfo; spriteAndMetadata.stats = stats; } - ds.mergeMetadata(spriteAndMetadata); + let metadataMergeSucceeded = ds.mergeMetadata(spriteAndMetadata); + if (!metadataMergeSucceeded) { + return; + } } if (this.projectorScatterPlotAdapter != null) { if (ds == null) { |