Skip to content

Commit 9a1a217

Browse files
authored
studio: send images. (#525)
Closes #520.
1 parent 8e4c7f0 commit 9a1a217

File tree

2 files changed

+54
-1
lines changed

2 files changed

+54
-1
lines changed

src/dvclive/studio.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# ruff: noqa: SLF001
2+
import base64
23
import os
34
from pathlib import Path
45

@@ -43,6 +44,21 @@ def _adapt_plot_datapoints(live, plot):
4344
return _cast_to_numbers(datapoints)
4445

4546

47+
def _adapt_image(image_path):
48+
with open(image_path, "rb") as fobj:
49+
return base64.b64encode(fobj.read()).decode("utf-8")
50+
51+
52+
def _adapt_images(live):
53+
return {
54+
_adapt_plot_name(live, image.output_path): {
55+
"image": _adapt_image(image.output_path)
56+
}
57+
for image in live._images.values()
58+
if image.step > live._latest_studio_step
59+
}
60+
61+
4662
def get_studio_updates(live):
4763
if os.path.isfile(live.params_file):
4864
params_file = live.params_file
@@ -65,4 +81,6 @@ def get_studio_updates(live):
6581
}
6682
plots = {k: {"data": v} for k, v in plots.items()}
6783

84+
plots.update(_adapt_images(live))
85+
6886
return metrics, params, plots

tests/test_studio.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from pathlib import Path
22

33
import pytest
4+
from PIL import Image as ImagePIL
45

56
from dvclive import Live
67
from dvclive.env import DVC_EXP_BASELINE_REV, DVC_EXP_NAME
7-
from dvclive.plots import Metric
8+
from dvclive.plots import Image, Metric
9+
from dvclive.studio import _adapt_image
810

911

1012
def test_post_to_studio(tmp_dir, mocked_dvc_repo, mocked_studio_post):
@@ -378,3 +380,36 @@ def test_post_to_studio_inside_subdir_dvc_exp(
378380
def test_post_to_studio_requires_exp(tmp_dir, mocked_dvc_repo, mocked_studio_post):
379381
assert Live()._studio_events_to_skip == {"start", "data", "done"}
380382
assert not Live(save_dvc_exp=True)._studio_events_to_skip
383+
384+
385+
def test_post_to_studio_images(tmp_dir, mocked_dvc_repo, mocked_studio_post):
386+
mocked_post, _ = mocked_studio_post
387+
388+
live = Live(save_dvc_exp=True)
389+
live.log_image("foo.png", ImagePIL.new("RGB", (10, 10), (0, 0, 0)))
390+
live.next_step()
391+
392+
dvc_path = Path(live.dvc_file).as_posix()
393+
metrics_path = Path(live.metrics_file).as_posix()
394+
foo_path = (Path(live.plots_dir) / Image.subfolder / "foo.png").as_posix()
395+
396+
mocked_post.assert_called_with(
397+
"https://0.0.0.0/api/live",
398+
json={
399+
"type": "data",
400+
"repo_url": "STUDIO_REPO_URL",
401+
"baseline_sha": live._baseline_rev,
402+
"name": live._exp_name,
403+
"step": 0,
404+
"metrics": {f"{metrics_path}": {"data": {"step": 0}}},
405+
"plots": {
406+
f"{dvc_path}::{foo_path}": {"image": _adapt_image(foo_path)},
407+
},
408+
"client": "dvclive",
409+
},
410+
headers={
411+
"Authorization": "token STUDIO_TOKEN",
412+
"Content-type": "application/json",
413+
},
414+
timeout=5,
415+
)

0 commit comments

Comments
 (0)