-
Notifications
You must be signed in to change notification settings - Fork 1.3k
plots: introduce support for images #6431
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
3f56e93
plots: support images initial
pared 629f162
plots: data: unify PlotsData
pared c6be64e
plots: data conversion refactor
pared ed6c43e
plots: renderers: abstract functionality
pared 381b05c
plots: uncommenting tests
pared f7dde5a
plots: self implement grouping
pared 7827fce
plots: rename group
pared a58ebf5
plots: extract rendering tests
pared 3db09b9
plots: move rendering tests to unit
pared ad828e3
plots: add image rendering tests
pared c54f044
plots: dont use dpath to find vega plot
pared b845456
plots: test find_vega
pared a834397
plots: test render function
pared 6950f66
plots: move rendering out of repo package
pared a04558c
plots: refactoring
pared 4ffef00
plots: self review refactor
pared File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import logging | ||
| from typing import TYPE_CHECKING, Dict | ||
|
|
||
| if TYPE_CHECKING: | ||
| from dvc.types import StrPath | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class Renderer: | ||
| def __init__(self, data: Dict): | ||
| self.data = data | ||
|
|
||
| from dvc.render.utils import get_files | ||
|
|
||
| files = get_files(self.data) | ||
|
|
||
| # we assume comparison of same file between revisions for now | ||
| assert len(files) == 1 | ||
| self.filename = files.pop() | ||
|
|
||
| def _convert(self, path: "StrPath"): | ||
| raise NotImplementedError | ||
|
|
||
| @property | ||
| def DIV(self): | ||
| raise NotImplementedError | ||
|
|
||
| def generate_html(self, path: "StrPath"): | ||
| """this method might edit content of path""" | ||
| partial = self._convert(path) | ||
| div_id = f"plot_{self.filename.replace('.', '_').replace('/', '_')}" | ||
| return self.DIV.format(id=div_id, partial=partial) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import os | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from dvc.render import Renderer | ||
| from dvc.render.utils import get_files | ||
| from dvc.utils import relpath | ||
|
|
||
| if TYPE_CHECKING: | ||
| from dvc.types import StrPath | ||
|
|
||
|
|
||
| class ImageRenderer(Renderer): | ||
| DIV = """ | ||
| <div | ||
| id="{id}" | ||
| style="border: 1px solid;"> | ||
| {partial} | ||
| </div>""" | ||
|
|
||
| def _write_image( | ||
| self, | ||
| page_dir_path: "StrPath", | ||
| revision: str, | ||
| filename: str, | ||
| image_data: bytes, | ||
| ): | ||
| static = os.path.join(page_dir_path, "static") | ||
| os.makedirs(static, exist_ok=True) | ||
|
|
||
| img_path = os.path.join( | ||
| static, f"{revision}_{filename.replace(os.sep, '_')}" | ||
| ) | ||
| rel_img_path = relpath(img_path, page_dir_path) | ||
| with open(img_path, "wb") as fd: | ||
| fd.write(image_data) | ||
| return """ | ||
| <div> | ||
| <p>{title}</p> | ||
| <img src="{src}"> | ||
| </div>""".format( | ||
| title=revision, src=rel_img_path | ||
| ) | ||
|
|
||
| def _convert(self, path: "StrPath"): | ||
| div_content = [] | ||
| for rev, rev_data in self.data.items(): | ||
| if "data" in rev_data: | ||
| for file, file_data in rev_data.get("data", {}).items(): | ||
| if "data" in file_data: | ||
| div_content.append( | ||
| self._write_image( | ||
| path, rev, file, file_data["data"] | ||
| ) | ||
| ) | ||
| if div_content: | ||
| div_content.insert(0, f"<p>{self.filename}</p>") | ||
| return "\n".join(div_content) | ||
| return "" | ||
|
|
||
| @staticmethod | ||
| def matches(data): | ||
| files = get_files(data) | ||
| extensions = set(map(lambda f: os.path.splitext(f)[1], files)) | ||
| return extensions.issubset({".jpg", ".jpeg", ".gif", ".png"}) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import os.path | ||
| from typing import Dict, List, Set | ||
|
|
||
|
|
||
| def get_files(data: Dict) -> Set: | ||
| files = set() | ||
| for rev in data.keys(): | ||
| for file in data[rev].get("data", {}).keys(): | ||
| files.add(file) | ||
| return files | ||
|
|
||
|
|
||
| def group_by_filename(plots_data: Dict) -> List[Dict]: | ||
| # TODO use dpath.util.search once | ||
| # https://github.com/dpath-maintainers/dpath-python/issues/147 is released | ||
| # now cannot search when errors are present in data | ||
| files = get_files(plots_data) | ||
| grouped = [] | ||
|
|
||
| for file in files: | ||
| tmp: Dict = {} | ||
| for revision, revision_data in plots_data.items(): | ||
| if file in revision_data.get("data", {}): | ||
| if "data" not in tmp: | ||
| tmp[revision] = {"data": {}} | ||
| tmp[revision]["data"].update( | ||
| {file: revision_data["data"][file]} | ||
| ) | ||
| grouped.append(tmp) | ||
|
|
||
| return grouped | ||
|
|
||
|
|
||
| def find_vega(repo, plots_data, target): | ||
| # TODO same as group_by_filename | ||
| grouped = group_by_filename(plots_data) | ||
| found = None | ||
| for plot_group in grouped: | ||
| files = get_files(plot_group) | ||
| assert len(files) == 1 | ||
| file = files.pop() | ||
| if file == target: | ||
| found = plot_group | ||
| break | ||
|
|
||
| from dvc.render.vega import VegaRenderer | ||
|
|
||
| if found and VegaRenderer.matches(found): | ||
| return VegaRenderer(found, repo.plots.templates).get_vega() | ||
| return "" | ||
|
|
||
|
|
||
| def match_renderers(plots_data, templates): | ||
| from dvc.render.image import ImageRenderer | ||
| from dvc.render.vega import VegaRenderer | ||
|
|
||
| renderers = [] | ||
| for g in group_by_filename(plots_data): | ||
| if VegaRenderer.matches(g): | ||
| renderers.append(VegaRenderer(g, templates)) | ||
| if ImageRenderer.matches(g): | ||
| renderers.append(ImageRenderer(g)) | ||
| return renderers | ||
|
|
||
|
|
||
| def render(repo, plots_data, metrics=None, path=None, html_template_path=None): | ||
| # TODO we could probably remove repo usages (here and in VegaRenderer) | ||
| renderers = match_renderers(plots_data, repo.plots.templates) | ||
| if not html_template_path: | ||
| html_template_path = repo.config.get("plots", {}).get( | ||
| "html_template", None | ||
| ) | ||
| if html_template_path and not os.path.isabs(html_template_path): | ||
| html_template_path = os.path.join(repo.dvc_dir, html_template_path) | ||
|
|
||
| from dvc.render.html import write | ||
|
|
||
| return write( | ||
| path, renderers, metrics=metrics, template_path=html_template_path | ||
| ) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can probably inject HTML code here: