from __future__ import annotations
import io
import os
import tempfile
from typing import TYPE_CHECKING, Any
import jpype
from neuralogic.setup import get_default_graphviz_path
if TYPE_CHECKING:
from neuralogic.core.settings import SettingsProxy
[docs]
def get_graphviz_path(path: str | None = None) -> str | None:
"""
Get the path to the Graphviz executable
"""
if path is not None:
return path
return get_default_graphviz_path()
[docs]
def get_drawing_settings(
img_type: str = "png", value_detail: int = 0, graphviz_path: str | None = None
) -> SettingsProxy:
"""Returns the default settings instance for drawing with a specified image type.
Parameters
----------
img_type : str
The image type. Default: "png".
value_detail : int
The level of detail for values. Default: 0.
graphviz_path : str, optional
The path to the Graphviz executable. Default: None.
Returns
-------
SettingsProxy
The settings proxy for drawing.
"""
from neuralogic.core.settings import Settings
settings = Settings().create_proxy()
graphviz = get_graphviz_path(graphviz_path)
if graphviz is not None:
settings.settings.graphvizPath = graphviz
settings.settings.drawing = False
settings.settings.storeNotShow = True
settings.settings.imgType = img_type.lower()
settings.settings.outDir = tempfile.gettempdir()
if value_detail not in [0, 1, 2]:
raise ValueError(f"Invalid value_detail - {value_detail}. Expected 0, 1, or 2.")
settings_class = settings.settings_class
details = [
settings_class.shortNumberFormat,
settings_class.detailedNumberFormat,
settings_class.superDetailedNumberFormat,
]
settings.settings.defaultNumberFormat = details[value_detail]
return settings
[docs]
def get_model_drawer(settings: SettingsProxy) -> Any:
"""Returns the model drawer.
Parameters
----------
settings : SettingsProxy
The settings proxy.
Returns
-------
Any
The model drawer.
"""
return jpype.JClass("cz.cvut.fel.ida.pipelines.debugging.drawing.TemplateDrawer")(settings.settings)
[docs]
def get_sample_drawer(settings: SettingsProxy) -> Any:
"""Returns the sample drawer.
Parameters
----------
settings : SettingsProxy
The settings proxy.
Returns
-------
Any
The sample drawer.
"""
return jpype.JClass("cz.cvut.fel.ida.pipelines.debugging.drawing.NeuralNetDrawer")(settings.settings)
[docs]
def get_grounding_drawer(settings: SettingsProxy) -> Any:
"""Returns the grounding drawer.
Parameters
----------
settings : SettingsProxy
The settings proxy.
Returns
-------
Any
The grounding drawer.
"""
return jpype.JClass("cz.cvut.fel.ida.pipelines.debugging.drawing.GroundingDrawer")(settings.settings)
# todo gusta: + groundingDrawer, pipelineDrawer...
[docs]
def draw(
drawer: Any,
obj: Any,
filename: str | None = None,
show: bool = True,
img_type: str = "png",
*args: Any,
**kwargs: Any,
) -> Any:
"""Draws the object using the provided drawer.
Parameters
----------
drawer : Any
The drawer to use.
obj : Any
The object to draw.
filename : str, optional
The filename to draw into. Default: None.
show : bool
Whether to show the image. Default: True.
img_type : str
The image type. Default: "png".
args : Any
Additional arguments for the drawer.
kwargs : Any
Additional keyword arguments for the drawer.
Returns
-------
Union[Any, bytes, None]
The drawing data, image object, or None if drawn into a file.
"""
if filename is not None:
try:
drawer.drawIntoFile(obj, os.path.abspath(filename))
except jpype.java.lang.NullPointerException as e:
raise RuntimeError(
"Drawing raised NullPointerException. Try to install GraphViz (https://graphviz.org/download/) on "
"your Path or specify the path via the `graphviz_path` parameter"
) from e
return None
data = drawer.drawIntoBytes(obj)
if data is None:
raise RuntimeError(
"Drawing failed. Try to install GraphViz (https://graphviz.org/download/) on your Path or specify the "
"path via the `graphviz_path` parameter"
)
data = bytes(data)
if show:
if is_jupyter():
from IPython.display import SVG, Image
if img_type.lower() == "svg":
return SVG(data, *args, **kwargs)
return Image(data, *args, **kwargs)
else:
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
img = mpimg.imread(io.BytesIO(data), format=img_type)
fig = plt.figure()
if hasattr(fig.canvas, "set_window_title"):
fig.canvas.set_window_title(kwargs.get("title", ""))
ax = fig.add_axes((0, 0, 1, 1))
ax.axis("off")
ax.imshow(img)
plt.show()
return
return data
[docs]
def to_dot_source(drawer: Any, obj: Any) -> str:
return str(drawer.getGraphSource(obj))
[docs]
def draw_model(
model: Any,
filename: str | None = None,
show: bool = True,
img_type: str = "png",
value_detail: int = 0,
graphviz_path: str | None = None,
*args: Any,
**kwargs: Any,
) -> Any:
"""Draws model either as an image of type img_type either into:
* a file - if filename is specified),
* an IPython Image or Image popup - if show is True
* or bytes otherwise
Parameters
----------
model : NeuralModule
The model to draw.
filename : str, optional
The filename to draw into. Default: None.
show : bool
Whether to show the image. Default: True.
img_type : str
The image type. Default: "png".
value_detail : int
The level of detail for values. Default: 0.
graphviz_path : str, optional
The path to the Graphviz executable. Default: None.
args : Any
Additional arguments for the drawer.
kwargs : Any
Additional keyword arguments for the drawer.
Returns
-------
Union[Any, bytes, None]
The model drawing.
"""
if model._need_sync:
model._sync_model()
model = model._parsed_model
template_drawer = get_model_drawer(get_drawing_settings(img_type, value_detail, graphviz_path))
return draw(template_drawer, model, filename, show, img_type, *args, **kwargs)
[docs]
def draw_grounding(
grounding: Any,
filename: str | None = None,
show: bool = True,
img_type: str = "png",
value_detail: int = 0,
graphviz_path: str | None = None,
*args: Any,
**kwargs: Any,
) -> Any:
"""Draws sample's grounding either as an image of type img_type either into:
* a file - if filename is specified),
* an IPython Image or Image popup - if show is True
* or bytes otherwise
Parameters
----------
grounding : Any
The grounding to draw.
filename : str, optional
The filename to draw into. Default: None.
show : bool
Whether to show the image. Default: True.
img_type : str
The image type. Default: "png".
value_detail : int
The level of detail for values. Default: 0.
graphviz_path : str, optional
The path to the Graphviz executable. Default: None.
args : Any
Additional arguments for the drawer.
kwargs : Any
Additional keyword arguments for the drawer.
Returns
-------
Union[Any, bytes, None]
The grounding drawing.
"""
grounding_drawer = get_grounding_drawer(get_drawing_settings(img_type, value_detail, graphviz_path))
return draw(grounding_drawer, grounding, filename, show, img_type, *args, **kwargs)
[docs]
def draw_sample(
sample: Any,
filename: str | None = None,
show: bool = True,
img_type: str = "png",
value_detail: int = 0,
graphviz_path: str | None = None,
*args: Any,
**kwargs: Any,
) -> Any:
"""Draws sample either as an image of type img_type either into:
* a file - if filename is specified),
* an IPython Image or Image popup - if show is True
* or bytes otherwise
Parameters
----------
sample : Any
The sample to draw.
filename : str, optional
The filename to draw into. Default: None.
show : bool
Whether to show the image. Default: True.
img_type : str
The image type. Default: "png".
value_detail : int
The level of detail for values. Default: 0.
graphviz_path : str, optional
The path to the Graphviz executable. Default: None.
args : Any
Additional arguments for the drawer.
kwargs : Any
Additional keyword arguments for the drawer.
Returns
-------
Union[Any, bytes, None]
The sample drawing.
"""
draw_object = sample._java_sample
sample_drawer = get_sample_drawer(get_drawing_settings(img_type, value_detail, graphviz_path))
return draw(sample_drawer, draw_object, filename, show, img_type, *args, **kwargs)
[docs]
def model_to_dot_source(model: Any) -> str:
"""Renders the model into its dot source representation.
Parameters
----------
model : NeuralModule
The model to render.
Returns
-------
str
The dot source representation.
"""
if model._need_sync:
model._sync_model()
model = model._model
template_drawer = get_model_drawer(get_drawing_settings())
return to_dot_source(template_drawer, model)
[docs]
def sample_to_dot_source(sample: Any, value_detail: int = 0) -> str:
"""Renders the sample into its dot source representation.
Parameters
----------
sample : Any
The sample to render.
value_detail : int
The level of detail for values. Default: 0.
Returns
-------
str
The dot source representation.
"""
sample_drawer = get_sample_drawer(get_drawing_settings(value_detail=value_detail))
return to_dot_source(sample_drawer, sample._java_sample)
[docs]
def is_jupyter() -> bool:
try:
__IPYTHON__ # noqa: F821
return True
except NameError:
return False