I am done

This commit is contained in:
2024-10-30 22:14:35 +01:00
parent 720dc28c09
commit 40e2a747cf
36901 changed files with 5011519 additions and 0 deletions

View File

@ -0,0 +1,68 @@
# Copyright 2016 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.
# ==============================================================================
"""Public API for the Embedding Projector.
@@ProjectorPluginAsset
@@ProjectorConfig
@@EmbeddingInfo
@@EmbeddingMetadata
@@SpriteMetadata
"""
import os
from google.protobuf import text_format as _text_format
from tensorboard.compat import tf
from tensorboard.plugins.projector import metadata as _metadata
from tensorboard.plugins.projector.projector_config_pb2 import ( # noqa: F401
EmbeddingInfo,
)
from tensorboard.plugins.projector.projector_config_pb2 import ( # noqa: F401
SpriteMetadata,
)
from tensorboard.plugins.projector.projector_config_pb2 import ( # noqa: F401
ProjectorConfig,
)
def visualize_embeddings(logdir, config):
"""Stores a config file used by the embedding projector.
Args:
logdir: Directory into which to store the config file, as a `str`.
For compatibility, can also be a `tf.compat.v1.summary.FileWriter`
object open at the desired logdir.
config: `tf.contrib.tensorboard.plugins.projector.ProjectorConfig`
proto that holds the configuration for the projector such as paths to
checkpoint files and metadata files for the embeddings. If
`config.model_checkpoint_path` is none, it defaults to the
`logdir` used by the summary_writer.
Raises:
ValueError: If the summary writer does not have a `logdir`.
"""
# Convert from `tf.compat.v1.summary.FileWriter` if necessary.
logdir = getattr(logdir, "get_logdir", lambda: logdir)()
# Sanity checks.
if logdir is None:
raise ValueError("Expected logdir to be a path, but got None")
# Saving the config file in the logdir.
config_pbtxt = _text_format.MessageToString(config)
path = os.path.join(logdir, _metadata.PROJECTOR_FILENAME)
with tf.io.gfile.GFile(path, "w") as f:
f.write(config_pbtxt)

View File

@ -0,0 +1,27 @@
# Copyright 2020 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.
# ==============================================================================
"""Internal information about the projector plugin."""
PLUGIN_NAME = "projector"
PLUGINS_DIR = "plugins" # must match plugin_asset_util.PLUGINS_DIR
PLUGIN_ASSETS_NAME = "org_tensorflow_tensorboard_projector"
# FYI - the PROJECTOR_FILENAME is hardcoded in the visualize_embeddings
# method in tf.contrib.tensorboard.plugins.projector module.
# TODO(@decentralion): Fix duplication when we find a permanent home for the
# projector module.
PROJECTOR_FILENAME = "projector_config.pbtxt"

View File

@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: tensorboard/plugins/projector/projector_config.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n4tensorboard/plugins/projector/projector_config.proto\x12\x15tensorboard.projector\">\n\x0eSpriteMetadata\x12\x12\n\nimage_path\x18\x01 \x01(\t\x12\x18\n\x10single_image_dim\x18\x02 \x03(\r\"\xb5\x01\n\rEmbeddingInfo\x12\x13\n\x0btensor_name\x18\x01 \x01(\t\x12\x15\n\rmetadata_path\x18\x02 \x01(\t\x12\x16\n\x0e\x62ookmarks_path\x18\x03 \x01(\t\x12\x14\n\x0ctensor_shape\x18\x04 \x03(\r\x12\x35\n\x06sprite\x18\x05 \x01(\x0b\x32%.tensorboard.projector.SpriteMetadata\x12\x13\n\x0btensor_path\x18\x06 \x01(\t\"\x88\x01\n\x0fProjectorConfig\x12\x1d\n\x15model_checkpoint_path\x18\x01 \x01(\t\x12\x38\n\nembeddings\x18\x02 \x03(\x0b\x32$.tensorboard.projector.EmbeddingInfo\x12\x1c\n\x14model_checkpoint_dir\x18\x03 \x01(\tb\x06proto3')
_SPRITEMETADATA = DESCRIPTOR.message_types_by_name['SpriteMetadata']
_EMBEDDINGINFO = DESCRIPTOR.message_types_by_name['EmbeddingInfo']
_PROJECTORCONFIG = DESCRIPTOR.message_types_by_name['ProjectorConfig']
SpriteMetadata = _reflection.GeneratedProtocolMessageType('SpriteMetadata', (_message.Message,), {
'DESCRIPTOR' : _SPRITEMETADATA,
'__module__' : 'tensorboard.plugins.projector.projector_config_pb2'
# @@protoc_insertion_point(class_scope:tensorboard.projector.SpriteMetadata)
})
_sym_db.RegisterMessage(SpriteMetadata)
EmbeddingInfo = _reflection.GeneratedProtocolMessageType('EmbeddingInfo', (_message.Message,), {
'DESCRIPTOR' : _EMBEDDINGINFO,
'__module__' : 'tensorboard.plugins.projector.projector_config_pb2'
# @@protoc_insertion_point(class_scope:tensorboard.projector.EmbeddingInfo)
})
_sym_db.RegisterMessage(EmbeddingInfo)
ProjectorConfig = _reflection.GeneratedProtocolMessageType('ProjectorConfig', (_message.Message,), {
'DESCRIPTOR' : _PROJECTORCONFIG,
'__module__' : 'tensorboard.plugins.projector.projector_config_pb2'
# @@protoc_insertion_point(class_scope:tensorboard.projector.ProjectorConfig)
})
_sym_db.RegisterMessage(ProjectorConfig)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
_SPRITEMETADATA._serialized_start=79
_SPRITEMETADATA._serialized_end=141
_EMBEDDINGINFO._serialized_start=144
_EMBEDDINGINFO._serialized_end=325
_PROJECTORCONFIG._serialized_start=328
_PROJECTORCONFIG._serialized_end=464
# @@protoc_insertion_point(module_scope)

View File

@ -0,0 +1,806 @@
# Copyright 2016 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.
# ==============================================================================
"""The Embedding Projector plugin."""
import collections
import functools
import imghdr
import mimetypes
import os
import threading
import numpy as np
from werkzeug import wrappers
from google.protobuf import json_format
from google.protobuf import text_format
from tensorboard import context
from tensorboard.backend.event_processing import plugin_asset_util
from tensorboard.backend.http_util import Respond
from tensorboard.compat import tf
from tensorboard.plugins import base_plugin
from tensorboard.plugins.projector import metadata
from tensorboard.plugins.projector.projector_config_pb2 import ProjectorConfig
from tensorboard.util import tb_logging
logger = tb_logging.get_logger()
# Number of tensors in the LRU cache.
_TENSOR_CACHE_CAPACITY = 1
# HTTP routes.
CONFIG_ROUTE = "/info"
TENSOR_ROUTE = "/tensor"
METADATA_ROUTE = "/metadata"
RUNS_ROUTE = "/runs"
BOOKMARKS_ROUTE = "/bookmarks"
SPRITE_IMAGE_ROUTE = "/sprite_image"
_IMGHDR_TO_MIMETYPE = {
"bmp": "image/bmp",
"gif": "image/gif",
"jpeg": "image/jpeg",
"png": "image/png",
}
_DEFAULT_IMAGE_MIMETYPE = "application/octet-stream"
class LRUCache:
"""LRU cache.
Used for storing the last used tensor.
"""
def __init__(self, size):
if size < 1:
raise ValueError("The cache size must be >=1")
self._size = size
self._dict = collections.OrderedDict()
def get(self, key):
try:
value = self._dict.pop(key)
self._dict[key] = value
return value
except KeyError:
return None
def set(self, key, value):
if value is None:
raise ValueError("value must be != None")
try:
self._dict.pop(key)
except KeyError:
if len(self._dict) >= self._size:
self._dict.popitem(last=False)
self._dict[key] = value
class EmbeddingMetadata:
"""Metadata container for an embedding.
The metadata holds different columns with values used for
visualization (color by, label by) in the "Embeddings" tab in
TensorBoard.
"""
def __init__(self, num_points):
"""Constructs a metadata for an embedding of the specified size.
Args:
num_points: Number of points in the embedding.
"""
self.num_points = num_points
self.column_names = []
self.name_to_values = {}
def add_column(self, column_name, column_values):
"""Adds a named column of metadata values.
Args:
column_name: Name of the column.
column_values: 1D array/list/iterable holding the column values. Must be
of length `num_points`. The i-th value corresponds to the i-th point.
Raises:
ValueError: If `column_values` is not 1D array, or of length `num_points`,
or the `name` is already used.
"""
# Sanity checks.
if isinstance(column_values, list) and isinstance(
column_values[0], list
):
raise ValueError(
'"column_values" must be a flat list, but we detected '
"that its first entry is a list"
)
if isinstance(column_values, np.ndarray) and column_values.ndim != 1:
raise ValueError(
'"column_values" should be of rank 1, '
"but is of rank %d" % column_values.ndim
)
if len(column_values) != self.num_points:
raise ValueError(
'"column_values" should be of length %d, but is of '
"length %d" % (self.num_points, len(column_values))
)
if column_name in self.name_to_values:
raise ValueError(
'The column name "%s" is already used' % column_name
)
self.column_names.append(column_name)
self.name_to_values[column_name] = column_values
def _read_tensor_tsv_file(fpath):
with tf.io.gfile.GFile(fpath, "r") as f:
tensor = []
for line in f:
line = line.rstrip("\n")
if line:
tensor.append(list(map(float, line.split("\t"))))
return np.array(tensor, dtype="float32")
def _read_tensor_binary_file(fpath, shape):
if len(shape) != 2:
raise ValueError("Tensor must be 2D, got shape {}".format(shape))
tensor = np.fromfile(fpath, dtype="float32")
return tensor.reshape(shape)
def _assets_dir_to_logdir(assets_dir):
sub_path = os.path.sep + metadata.PLUGINS_DIR + os.path.sep
if sub_path in assets_dir:
two_parents_up = os.pardir + os.path.sep + os.pardir
return os.path.abspath(os.path.join(assets_dir, two_parents_up))
return assets_dir
def _latest_checkpoints_changed(configs, run_path_pairs):
"""Returns true if the latest checkpoint has changed in any of the runs."""
for run_name, assets_dir in run_path_pairs:
if run_name not in configs:
config = ProjectorConfig()
config_fpath = os.path.join(assets_dir, metadata.PROJECTOR_FILENAME)
if tf.io.gfile.exists(config_fpath):
with tf.io.gfile.GFile(config_fpath, "r") as f:
file_content = f.read()
text_format.Parse(file_content, config)
else:
config = configs[run_name]
# See if you can find a checkpoint file in the logdir.
logdir = _assets_dir_to_logdir(assets_dir)
ckpt_path = _find_latest_checkpoint(logdir)
if not ckpt_path:
continue
if config.model_checkpoint_path != ckpt_path:
return True
return False
def _parse_positive_int_param(request, param_name):
"""Parses and asserts a positive (>0) integer query parameter.
Args:
request: The Werkzeug Request object
param_name: Name of the parameter.
Returns:
Param, or None, or -1 if parameter is not a positive integer.
"""
param = request.args.get(param_name)
if not param:
return None
try:
param = int(param)
if param <= 0:
raise ValueError()
return param
except ValueError:
return -1
def _rel_to_abs_asset_path(fpath, config_fpath):
fpath = os.path.expanduser(fpath)
if not os.path.isabs(fpath):
return os.path.join(os.path.dirname(config_fpath), fpath)
return fpath
def _using_tf():
"""Return true if we're not using the fake TF API stub implementation."""
return tf.__version__ != "stub"
class ProjectorPlugin(base_plugin.TBPlugin):
"""Embedding projector."""
plugin_name = metadata.PLUGIN_NAME
def __init__(self, context):
"""Instantiates ProjectorPlugin via TensorBoard core.
Args:
context: A base_plugin.TBContext instance.
"""
self.data_provider = context.data_provider
self.logdir = context.logdir
self.readers = {}
self._run_paths = None
self._configs = {}
self.config_fpaths = None
self.tensor_cache = LRUCache(_TENSOR_CACHE_CAPACITY)
# Whether the plugin is active (has meaningful data to process and serve).
# Once the plugin is deemed active, we no longer re-compute the value
# because doing so is potentially expensive.
self._is_active = False
# The running thread that is currently determining whether the plugin is
# active. If such a thread exists, do not start a duplicate thread.
self._thread_for_determining_is_active = None
def get_plugin_apps(self):
asset_prefix = "tf_projector_plugin"
return {
RUNS_ROUTE: self._serve_runs,
CONFIG_ROUTE: self._serve_config,
TENSOR_ROUTE: self._serve_tensor,
METADATA_ROUTE: self._serve_metadata,
BOOKMARKS_ROUTE: self._serve_bookmarks,
SPRITE_IMAGE_ROUTE: self._serve_sprite_image,
"/index.js": functools.partial(
self._serve_file,
os.path.join(asset_prefix, "index.js"),
),
"/projector_binary.html": functools.partial(
self._serve_file,
os.path.join(asset_prefix, "projector_binary.html"),
),
"/projector_binary.js": functools.partial(
self._serve_file,
os.path.join(asset_prefix, "projector_binary.js"),
),
}
def is_active(self):
"""Determines whether this plugin is active.
This plugin is only active if any run has an embedding, and only
when running against a local log directory.
Returns:
Whether any run has embedding data to show in the projector.
"""
if not self.data_provider or not self.logdir:
return False
if self._is_active:
# We have already determined that the projector plugin should be active.
# Do not re-compute that. We have no reason to later set this plugin to be
# inactive.
return True
if self._thread_for_determining_is_active:
# We are currently determining whether the plugin is active. Do not start
# a separate thread.
return self._is_active
# The plugin is currently not active. The frontend might check again later.
# For now, spin off a separate thread to determine whether the plugin is
# active.
new_thread = threading.Thread(
target=self._determine_is_active,
name="ProjectorPluginIsActiveThread",
)
self._thread_for_determining_is_active = new_thread
new_thread.start()
return False
def frontend_metadata(self):
return base_plugin.FrontendMetadata(
es_module_path="/index.js",
disable_reload=True,
)
def _determine_is_active(self):
"""Determines whether the plugin is active.
This method is run in a separate thread so that the plugin can
offer an immediate response to whether it is active and
determine whether it should be active in a separate thread.
"""
self._update_configs()
if self._configs:
self._is_active = True
self._thread_for_determining_is_active = None
def _update_configs(self):
"""Updates `self._configs` and `self._run_paths`."""
if self.data_provider and self.logdir:
# Create a background context; we may not be in a request.
ctx = context.RequestContext()
run_paths = {
run.run_name: os.path.join(self.logdir, run.run_name)
for run in self.data_provider.list_runs(ctx, experiment_id="")
}
else:
run_paths = {}
run_paths_changed = run_paths != self._run_paths
self._run_paths = run_paths
run_path_pairs = list(self._run_paths.items())
self._append_plugin_asset_directories(run_path_pairs)
# Also accept the root logdir as a model checkpoint directory,
# so that the projector still works when there are no runs.
# (Case on `run` rather than `path` to avoid issues with
# absolute/relative paths on any filesystems.)
if "." not in self._run_paths:
run_path_pairs.append((".", self.logdir))
if run_paths_changed or _latest_checkpoints_changed(
self._configs, run_path_pairs
):
self.readers = {}
self._configs, self.config_fpaths = self._read_latest_config_files(
run_path_pairs
)
self._augment_configs_with_checkpoint_info()
def _augment_configs_with_checkpoint_info(self):
for run, config in self._configs.items():
for embedding in config.embeddings:
# Normalize the name of the embeddings.
if embedding.tensor_name.endswith(":0"):
embedding.tensor_name = embedding.tensor_name[:-2]
# Find the size of embeddings associated with a tensors file.
if embedding.tensor_path:
fpath = _rel_to_abs_asset_path(
embedding.tensor_path, self.config_fpaths[run]
)
tensor = self.tensor_cache.get((run, embedding.tensor_name))
if tensor is None:
try:
tensor = _read_tensor_tsv_file(fpath)
except UnicodeDecodeError:
tensor = _read_tensor_binary_file(
fpath, embedding.tensor_shape
)
self.tensor_cache.set(
(run, embedding.tensor_name), tensor
)
if not embedding.tensor_shape:
embedding.tensor_shape.extend(
[len(tensor), len(tensor[0])]
)
reader = self._get_reader_for_run(run)
if not reader:
continue
# Augment the configuration with the tensors in the checkpoint file.
special_embedding = None
if config.embeddings and not config.embeddings[0].tensor_name:
special_embedding = config.embeddings[0]
config.embeddings.remove(special_embedding)
var_map = reader.get_variable_to_shape_map()
for tensor_name, tensor_shape in var_map.items():
if len(tensor_shape) != 2:
continue
# Optimizer slot values are the same shape as embeddings
# but are not embeddings.
if ".OPTIMIZER_SLOT" in tensor_name:
continue
embedding = self._get_embedding(tensor_name, config)
if not embedding:
embedding = config.embeddings.add()
embedding.tensor_name = tensor_name
if special_embedding:
embedding.metadata_path = (
special_embedding.metadata_path
)
embedding.bookmarks_path = (
special_embedding.bookmarks_path
)
if not embedding.tensor_shape:
embedding.tensor_shape.extend(tensor_shape)
# Remove configs that do not have any valid (2D) tensors.
runs_to_remove = []
for run, config in self._configs.items():
if not config.embeddings:
runs_to_remove.append(run)
for run in runs_to_remove:
del self._configs[run]
del self.config_fpaths[run]
def _read_latest_config_files(self, run_path_pairs):
"""Reads and returns the projector config files in every run
directory."""
configs = {}
config_fpaths = {}
for run_name, assets_dir in run_path_pairs:
config = ProjectorConfig()
config_fpath = os.path.join(assets_dir, metadata.PROJECTOR_FILENAME)
if tf.io.gfile.exists(config_fpath):
with tf.io.gfile.GFile(config_fpath, "r") as f:
file_content = f.read()
text_format.Parse(file_content, config)
has_tensor_files = False
for embedding in config.embeddings:
if embedding.tensor_path:
if not embedding.tensor_name:
embedding.tensor_name = os.path.basename(
embedding.tensor_path
)
has_tensor_files = True
break
if not config.model_checkpoint_path:
# See if you can find a checkpoint file in the logdir.
logdir = _assets_dir_to_logdir(assets_dir)
ckpt_path = _find_latest_checkpoint(logdir)
if not ckpt_path and not has_tensor_files:
continue
if ckpt_path:
config.model_checkpoint_path = ckpt_path
# Sanity check for the checkpoint file existing.
if (
config.model_checkpoint_path
and _using_tf()
and not tf.io.gfile.glob(config.model_checkpoint_path + "*")
):
logger.warning(
'Checkpoint file "%s" not found',
config.model_checkpoint_path,
)
continue
configs[run_name] = config
config_fpaths[run_name] = config_fpath
return configs, config_fpaths
def _get_reader_for_run(self, run):
if run in self.readers:
return self.readers[run]
config = self._configs[run]
reader = None
if config.model_checkpoint_path and _using_tf():
try:
reader = tf.train.load_checkpoint(config.model_checkpoint_path)
except Exception: # pylint: disable=broad-except
logger.warning(
'Failed reading "%s"', config.model_checkpoint_path
)
self.readers[run] = reader
return reader
def _get_metadata_file_for_tensor(self, tensor_name, config):
embedding_info = self._get_embedding(tensor_name, config)
if embedding_info:
return embedding_info.metadata_path
return None
def _get_bookmarks_file_for_tensor(self, tensor_name, config):
embedding_info = self._get_embedding(tensor_name, config)
if embedding_info:
return embedding_info.bookmarks_path
return None
def _canonical_tensor_name(self, tensor_name):
if ":" not in tensor_name:
return tensor_name + ":0"
else:
return tensor_name
def _get_embedding(self, tensor_name, config):
if not config.embeddings:
return None
for info in config.embeddings:
if self._canonical_tensor_name(
info.tensor_name
) == self._canonical_tensor_name(tensor_name):
return info
return None
def _append_plugin_asset_directories(self, run_path_pairs):
extra = []
plugin_assets_name = metadata.PLUGIN_ASSETS_NAME
for run, logdir in run_path_pairs:
assets = plugin_asset_util.ListAssets(logdir, plugin_assets_name)
if metadata.PROJECTOR_FILENAME not in assets:
continue
assets_dir = os.path.join(
self._run_paths[run], metadata.PLUGINS_DIR, plugin_assets_name
)
assets_path_pair = (run, os.path.abspath(assets_dir))
extra.append(assets_path_pair)
run_path_pairs.extend(extra)
@wrappers.Request.application
def _serve_file(self, file_path, request):
"""Returns a resource file."""
res_path = os.path.join(os.path.dirname(__file__), file_path)
with open(res_path, "rb") as read_file:
mimetype = mimetypes.guess_type(file_path)[0]
return Respond(request, read_file.read(), content_type=mimetype)
@wrappers.Request.application
def _serve_runs(self, request):
"""Returns a list of runs that have embeddings."""
self._update_configs()
return Respond(request, list(self._configs.keys()), "application/json")
@wrappers.Request.application
def _serve_config(self, request):
run = request.args.get("run")
if run is None:
return Respond(
request, 'query parameter "run" is required', "text/plain", 400
)
self._update_configs()
config = self._configs.get(run)
if config is None:
return Respond(
request, 'Unknown run: "%s"' % run, "text/plain", 400
)
return Respond(
request, json_format.MessageToJson(config), "application/json"
)
@wrappers.Request.application
def _serve_metadata(self, request):
run = request.args.get("run")
if run is None:
return Respond(
request, 'query parameter "run" is required', "text/plain", 400
)
name = request.args.get("name")
if name is None:
return Respond(
request, 'query parameter "name" is required', "text/plain", 400
)
num_rows = _parse_positive_int_param(request, "num_rows")
if num_rows == -1:
return Respond(
request,
"query parameter num_rows must be integer > 0",
"text/plain",
400,
)
self._update_configs()
config = self._configs.get(run)
if config is None:
return Respond(
request, 'Unknown run: "%s"' % run, "text/plain", 400
)
fpath = self._get_metadata_file_for_tensor(name, config)
if not fpath:
return Respond(
request,
'No metadata file found for tensor "%s" in the config file "%s"'
% (name, self.config_fpaths[run]),
"text/plain",
400,
)
fpath = _rel_to_abs_asset_path(fpath, self.config_fpaths[run])
if not tf.io.gfile.exists(fpath) or tf.io.gfile.isdir(fpath):
return Respond(
request,
'"%s" not found, or is not a file' % fpath,
"text/plain",
400,
)
num_header_rows = 0
with tf.io.gfile.GFile(fpath, "r") as f:
lines = []
# Stream reading the file with early break in case the file doesn't fit in
# memory.
for line in f:
lines.append(line)
if len(lines) == 1 and "\t" in lines[0]:
num_header_rows = 1
if num_rows and len(lines) >= num_rows + num_header_rows:
break
return Respond(request, "".join(lines), "text/plain")
@wrappers.Request.application
def _serve_tensor(self, request):
run = request.args.get("run")
if run is None:
return Respond(
request, 'query parameter "run" is required', "text/plain", 400
)
name = request.args.get("name")
if name is None:
return Respond(
request, 'query parameter "name" is required', "text/plain", 400
)
num_rows = _parse_positive_int_param(request, "num_rows")
if num_rows == -1:
return Respond(
request,
"query parameter num_rows must be integer > 0",
"text/plain",
400,
)
self._update_configs()
config = self._configs.get(run)
if config is None:
return Respond(
request, 'Unknown run: "%s"' % run, "text/plain", 400
)
tensor = self.tensor_cache.get((run, name))
if tensor is None:
# See if there is a tensor file in the config.
embedding = self._get_embedding(name, config)
if embedding and embedding.tensor_path:
fpath = _rel_to_abs_asset_path(
embedding.tensor_path, self.config_fpaths[run]
)
if not tf.io.gfile.exists(fpath):
return Respond(
request,
'Tensor file "%s" does not exist' % fpath,
"text/plain",
400,
)
try:
tensor = _read_tensor_tsv_file(fpath)
except UnicodeDecodeError:
tensor = _read_tensor_binary_file(
fpath, embedding.tensor_shape
)
else:
reader = self._get_reader_for_run(run)
if not reader or not reader.has_tensor(name):
return Respond(
request,
'Tensor "%s" not found in checkpoint dir "%s"'
% (name, config.model_checkpoint_path),
"text/plain",
400,
)
try:
tensor = reader.get_tensor(name)
except tf.errors.InvalidArgumentError as e:
return Respond(request, str(e), "text/plain", 400)
self.tensor_cache.set((run, name), tensor)
if num_rows:
tensor = tensor[:num_rows]
if tensor.dtype != "float32":
tensor = tensor.astype(dtype="float32", copy=False)
data_bytes = tensor.tobytes()
return Respond(request, data_bytes, "application/octet-stream")
@wrappers.Request.application
def _serve_bookmarks(self, request):
run = request.args.get("run")
if not run:
return Respond(
request, 'query parameter "run" is required', "text/plain", 400
)
name = request.args.get("name")
if name is None:
return Respond(
request, 'query parameter "name" is required', "text/plain", 400
)
self._update_configs()
config = self._configs.get(run)
if config is None:
return Respond(
request, 'Unknown run: "%s"' % run, "text/plain", 400
)
fpath = self._get_bookmarks_file_for_tensor(name, config)
if not fpath:
return Respond(
request,
'No bookmarks file found for tensor "%s" in the config file "%s"'
% (name, self.config_fpaths[run]),
"text/plain",
400,
)
fpath = _rel_to_abs_asset_path(fpath, self.config_fpaths[run])
if not tf.io.gfile.exists(fpath) or tf.io.gfile.isdir(fpath):
return Respond(
request,
'"%s" not found, or is not a file' % fpath,
"text/plain",
400,
)
bookmarks_json = None
with tf.io.gfile.GFile(fpath, "rb") as f:
bookmarks_json = f.read()
return Respond(request, bookmarks_json, "application/json")
@wrappers.Request.application
def _serve_sprite_image(self, request):
run = request.args.get("run")
if not run:
return Respond(
request, 'query parameter "run" is required', "text/plain", 400
)
name = request.args.get("name")
if name is None:
return Respond(
request, 'query parameter "name" is required', "text/plain", 400
)
self._update_configs()
config = self._configs.get(run)
if config is None:
return Respond(
request, 'Unknown run: "%s"' % run, "text/plain", 400
)
embedding_info = self._get_embedding(name, config)
if not embedding_info or not embedding_info.sprite.image_path:
return Respond(
request,
'No sprite image file found for tensor "%s" in the config file "%s"'
% (name, self.config_fpaths[run]),
"text/plain",
400,
)
fpath = os.path.expanduser(embedding_info.sprite.image_path)
fpath = _rel_to_abs_asset_path(fpath, self.config_fpaths[run])
if not tf.io.gfile.exists(fpath) or tf.io.gfile.isdir(fpath):
return Respond(
request,
'"%s" does not exist or is directory' % fpath,
"text/plain",
400,
)
f = tf.io.gfile.GFile(fpath, "rb")
encoded_image_string = f.read()
f.close()
image_type = imghdr.what(None, encoded_image_string)
mime_type = _IMGHDR_TO_MIMETYPE.get(image_type, _DEFAULT_IMAGE_MIMETYPE)
return Respond(request, encoded_image_string, mime_type)
def _find_latest_checkpoint(dir_path):
if not _using_tf():
return None
try:
ckpt_path = tf.train.latest_checkpoint(dir_path)
if not ckpt_path:
# Check the parent directory.
ckpt_path = tf.train.latest_checkpoint(
os.path.join(dir_path, os.pardir)
)
return ckpt_path
except tf.errors.NotFoundError:
return None

View File

@ -0,0 +1,32 @@
// 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.
export function render() {
const style = document.createElement('style');
style.innerText = `
html,
body,
iframe {
border: 0;
display: block;
height: 100%;
margin: 0;
width: 100%;
}`;
document.head.appendChild(style);
const iframe = document.createElement('iframe');
iframe.src = './projector_binary.html';
document.body.appendChild(iframe);
}

View File

@ -0,0 +1,523 @@
<!doctype html><!--
@license
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.
--><html><head><style>
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(/font-roboto/uYECMKoHcO9x1wdmbyHIm3-_kf6ByYO6CLYdB4HQE-Y.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(/font-roboto/sTdaA6j0Psb920Vjv-mrzH-_kf6ByYO6CLYdB4HQE-Y.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(/font-roboto/_VYFx-s824kXq_Ul2BHqYH-_kf6ByYO6CLYdB4HQE-Y.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(/font-roboto/tnj4SB6DNbdaQnsM8CFqBX-_kf6ByYO6CLYdB4HQE-Y.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(/font-roboto/oMMgfZMQthOryQo9n22dcuvvDin1pK8aKteLpeZ5c0A.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(/font-roboto/Ks_cVxiCiwUWVsFWFA3Bjn-_kf6ByYO6CLYdB4HQE-Y.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(/font-roboto/NJ4vxlgWwWbEsv18dAhqnn-_kf6ByYO6CLYdB4HQE-Y.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(/font-roboto/isZ-wbCXNKAbnjo6_TwHToX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(/font-roboto/77FXFjRbGzN4aCrSFhlh3oX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(/font-roboto/jSN2CGVDbcVyCnfJfjSdfIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(/font-roboto/UX6i4JxQDm3fVTc1CPuwqoX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(/font-roboto/d-6IYplOFocCacKzxwXSOJBw1xU1rKptJj_0jans920.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(/font-roboto/97uahxiqZRoncBaCEI3aW4X0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url(/font-roboto/PwZc-YbIL414wB9rB1IAPYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
src: local('Roboto Bold Italic'), local('Roboto-BoldItalic'), url(/font-roboto/t6Nd4cfPRhZP44Q5QAjcC14sYYdJg5dU2qzJEVSuta0.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
src: local('Roboto Bold Italic'), local('Roboto-BoldItalic'), url(/font-roboto/t6Nd4cfPRhZP44Q5QAjcC_ZraR2Tg8w2lzm7kLNL0-w.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
src: local('Roboto Bold Italic'), local('Roboto-BoldItalic'), url(/font-roboto/t6Nd4cfPRhZP44Q5QAjcCwt_Rm691LTebKfY2ZkKSmI.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
src: local('Roboto Bold Italic'), local('Roboto-BoldItalic'), url(/font-roboto/t6Nd4cfPRhZP44Q5QAjcC1BW26QxpSj-_ZKm_xT4hWw.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
src: local('Roboto Bold Italic'), local('Roboto-BoldItalic'), url(/font-roboto/t6Nd4cfPRhZP44Q5QAjcC4gp9Q8gbYrhqGlRav_IXfk.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
src: local('Roboto Bold Italic'), local('Roboto-BoldItalic'), url(/font-roboto/t6Nd4cfPRhZP44Q5QAjcC6E8kM4xWR1_1bYURRojRGc.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
src: local('Roboto Bold Italic'), local('Roboto-BoldItalic'), url(/font-roboto/t6Nd4cfPRhZP44Q5QAjcC9DiNsR5a-9Oe_Ivpu8XWlY.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'), url(/font-roboto/OpXUqTo0UgQQhGj_SFdLWBkAz4rYn47Zy2rvigWQf6w.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'), url(/font-roboto/WxrXJa0C3KdtC7lMafG4dRkAz4rYn47Zy2rvigWQf6w.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'), url(/font-roboto/cDKhRaXnQTOVbaoxwdOr9xkAz4rYn47Zy2rvigWQf6w.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'), url(/font-roboto/1hZf02POANh32k2VkgEoUBkAz4rYn47Zy2rvigWQf6w.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'), url(/font-roboto/vPcynSL0qHq_6dX7lKVByXYhjbSpvc47ee6xR_80Hnw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'), url(/font-roboto/vSzulfKSK0LLjjfeaxcREhkAz4rYn47Zy2rvigWQf6w.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
src: local('Roboto Italic'), local('Roboto-Italic'), url(/font-roboto/K23cxWVTrIFD6DJsEVi07RkAz4rYn47Zy2rvigWQf6w.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
src: local('Roboto Light'), local('Roboto-Light'), url(/font-roboto/Fl4y0QdOxyyTHEGMXX8kcYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
src: local('Roboto Light'), local('Roboto-Light'), url(/font-roboto/0eC6fl06luXEYWpBSJvXCIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
src: local('Roboto Light'), local('Roboto-Light'), url(/font-roboto/I3S1wsgSg9YCurV6PUkTOYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
src: local('Roboto Light'), local('Roboto-Light'), url(/font-roboto/-L14Jk06m6pUHB-5mXQQnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
src: local('Roboto Light'), local('Roboto-Light'), url(/font-roboto/Hgo13k-tfSpn0qi1SFdUfZBw1xU1rKptJj_0jans920.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
src: local('Roboto Light'), local('Roboto-Light'), url(/font-roboto/Pru33qjShpZSmG3z6VYwnYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
src: local('Roboto Light'), local('Roboto-Light'), url(/font-roboto/NYDWBdD4gIq26G5XYbHsFIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
src: local('Roboto Light Italic'), local('Roboto-LightItalic'), url(/font-roboto/7m8l7TlFO-S3VkhHuR0at14sYYdJg5dU2qzJEVSuta0.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
src: local('Roboto Light Italic'), local('Roboto-LightItalic'), url(/font-roboto/7m8l7TlFO-S3VkhHuR0at_ZraR2Tg8w2lzm7kLNL0-w.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
src: local('Roboto Light Italic'), local('Roboto-LightItalic'), url(/font-roboto/7m8l7TlFO-S3VkhHuR0atwt_Rm691LTebKfY2ZkKSmI.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
src: local('Roboto Light Italic'), local('Roboto-LightItalic'), url(/font-roboto/7m8l7TlFO-S3VkhHuR0at1BW26QxpSj-_ZKm_xT4hWw.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
src: local('Roboto Light Italic'), local('Roboto-LightItalic'), url(/font-roboto/7m8l7TlFO-S3VkhHuR0at4gp9Q8gbYrhqGlRav_IXfk.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
src: local('Roboto Light Italic'), local('Roboto-LightItalic'), url(/font-roboto/7m8l7TlFO-S3VkhHuR0at6E8kM4xWR1_1bYURRojRGc.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
src: local('Roboto Light Italic'), local('Roboto-LightItalic'), url(/font-roboto/7m8l7TlFO-S3VkhHuR0at9DiNsR5a-9Oe_Ivpu8XWlY.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(/font-roboto/oHi30kwQWvpCWqAhzHcCSIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(/font-roboto/ZLqKeelYbATG60EpZBSDy4X0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(/font-roboto/mx9Uck6uB63VIKFYnEMXrYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(/font-roboto/rGvHdJnr2l75qb0YND9NyIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(/font-roboto/RxZJdnzeo3R5zSexge8UUZBw1xU1rKptJj_0jans920.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(/font-roboto/oOeFwZNlrTefzLYmlVV1UIX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(/font-roboto/mbmhprMH69Zi6eEPBYVFhYX0hVgzZQUfRDuZrPvH3D8.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'), url(/font-roboto/OLffGBTaF0XFOW1gnuHF0V4sYYdJg5dU2qzJEVSuta0.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'), url(/font-roboto/OLffGBTaF0XFOW1gnuHF0fZraR2Tg8w2lzm7kLNL0-w.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'), url(/font-roboto/OLffGBTaF0XFOW1gnuHF0Qt_Rm691LTebKfY2ZkKSmI.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'), url(/font-roboto/OLffGBTaF0XFOW1gnuHF0VBW26QxpSj-_ZKm_xT4hWw.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'), url(/font-roboto/OLffGBTaF0XFOW1gnuHF0Ygp9Q8gbYrhqGlRav_IXfk.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'), url(/font-roboto/OLffGBTaF0XFOW1gnuHF0aE8kM4xWR1_1bYURRojRGc.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 500;
src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'), url(/font-roboto/OLffGBTaF0XFOW1gnuHF0dDiNsR5a-9Oe_Ivpu8XWlY.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'), url(/font-roboto/hMqPNLsu_dywMa4C_DEpY14sYYdJg5dU2qzJEVSuta0.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'), url(/font-roboto/hMqPNLsu_dywMa4C_DEpY_ZraR2Tg8w2lzm7kLNL0-w.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'), url(/font-roboto/hMqPNLsu_dywMa4C_DEpYwt_Rm691LTebKfY2ZkKSmI.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'), url(/font-roboto/hMqPNLsu_dywMa4C_DEpY1BW26QxpSj-_ZKm_xT4hWw.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'), url(/font-roboto/hMqPNLsu_dywMa4C_DEpY4gp9Q8gbYrhqGlRav_IXfk.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'), url(/font-roboto/hMqPNLsu_dywMa4C_DEpY6E8kM4xWR1_1bYURRojRGc.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: local('Roboto Mono'), local('RobotoMono-Regular'), url(/font-roboto/hMqPNLsu_dywMa4C_DEpY9DiNsR5a-9Oe_Ivpu8XWlY.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 700;
src: local('Roboto Mono Bold'), local('RobotoMono-Bold'), url(/font-roboto/N4duVc9C58uwPiY8_59Fz1x-M1I1w5OMiqnVF8xBLhU.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 700;
src: local('Roboto Mono Bold'), local('RobotoMono-Bold'), url(/font-roboto/N4duVc9C58uwPiY8_59FzwXaAXup5mZlfK6xRLrhsco.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 700;
src: local('Roboto Mono Bold'), local('RobotoMono-Bold'), url(/font-roboto/N4duVc9C58uwPiY8_59Fzwn6Wqxo-xwxilDXPU8chVU.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 700;
src: local('Roboto Mono Bold'), local('RobotoMono-Bold'), url(/font-roboto/N4duVc9C58uwPiY8_59Fz1T7aJLK6nKpn36IMwTcMMc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 700;
src: local('Roboto Mono Bold'), local('RobotoMono-Bold'), url(/font-roboto/N4duVc9C58uwPiY8_59Fz_79_ZuUxCigM2DespTnFaw.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 700;
src: local('Roboto Mono Bold'), local('RobotoMono-Bold'), url(/font-roboto/N4duVc9C58uwPiY8_59Fz4gd9OEPUCN3AdYW0e8tat4.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 700;
src: local('Roboto Mono Bold'), local('RobotoMono-Bold'), url(/font-roboto/N4duVc9C58uwPiY8_59Fz8bIQSYZnWLaWC9QNCpTK_U.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
</style>
<style>
html,
body {
height: 100%;
margin: 0;
width: 100%;
font-family: Roboto, sans-serif;
}
</style>
</head><body><vz-projector-dashboard></vz-projector-dashboard><script src="projector_binary.js"></script></body></html>

File diff suppressed because one or more lines are too long