From 759314af9913bc7131bb3252f8abdbe7baf1c60f Mon Sep 17 00:00:00 2001 From: Thomas Mansencal Date: Tue, 31 Dec 2024 16:34:54 +1300 Subject: [PATCH] Add `colour.utilities.NodePassthrough`, `colour.utilities.NodeLog`, `colour.utilities.NodeSleep` and `colour.utilities.NodeSetGraphOutputPort` node classes. --- colour/utilities/__init__.py | 8 ++ colour/utilities/network.py | 131 ++++++++++++++++++++++++- colour/utilities/tests/test_network.py | 78 ++++++++++++++- docs/colour.utilities.rst | 4 + 4 files changed, 219 insertions(+), 2 deletions(-) diff --git a/colour/utilities/__init__.py b/colour/utilities/__init__.py index 654dd41f2..95e7d6c3d 100644 --- a/colour/utilities/__init__.py +++ b/colour/utilities/__init__.py @@ -148,6 +148,10 @@ For, ParallelForThread, ParallelForMultiprocess, + NodePassthrough, + NodeLog, + NodeSleep, + NodeSetGraphOutputPort, ) from colour.utilities.deprecation import ModuleAPI, build_API_changes from colour.utilities.documentation import is_documentation_building @@ -297,6 +301,10 @@ "For", "ParallelForThread", "ParallelForMultiprocess", + "NodePassthrough", + "NodeLog", + "NodeSleep", + "NodeSetGraphOutputPort", ] diff --git a/colour/utilities/network.py b/colour/utilities/network.py index 4cfb7c0c5..959f8806d 100644 --- a/colour/utilities/network.py +++ b/colour/utilities/network.py @@ -24,6 +24,11 @@ parallel in the node-graph using threads. - :class:`colour.utilities.ParallelForMultiprocess`: A node performing for loops in parallel in the node-graph using multiprocessing. +- :class:`colour.utilities.NodePassthrough`: A node passing the input data + through. +- :class:`colour.utilities.NodeLog`: A node logging the input data. +- :class:`colour.utilities.NodeSleep`: A node sleeping for given duration in + seconds. """ from __future__ import annotations @@ -34,6 +39,7 @@ import multiprocessing import os import threading +import time import typing if typing.TYPE_CHECKING: @@ -73,6 +79,10 @@ "ParallelForThread", "ProcessPoolExecutorManager", "ParallelForMultiprocess", + "NodePassthrough", + "NodeLog", + "NodeSleep", + "NodeSetGraphOutputPort", ] @@ -873,7 +883,10 @@ def connect(self, port: Port) -> None: attest(isinstance(port, Port), f'"{port}" is not a "Port" instance!') - self.log(f'Connecting "{self.name}" to "{port.name}".', "debug") + self.log( + f'Connecting "{self.node}.{self.name}" to "{port.node}.{port.name}".', + "debug", + ) self.connections[port] = None port.connections[self] = None @@ -2514,3 +2527,119 @@ def process(self) -> None: execution_output_node.process() self.dirty = False + + +class NodePassthrough(PortNode): + """ + Pass the input data through. + + Methods + ------- + - :meth:`~colour.utilities.NodePassthrough.__init__` + - :meth:`~colour.utilities.NodePassthrough.process` + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + self.description = "Pass the input data through" + + self.add_input_port("input") + self.add_output_port("output") + + @notify_process_state + def process(self, **kwargs: Any) -> None: # noqa: ARG002 + """ + Process the node. + """ + + self.set_output("output", self.get_input("input")) + + self.dirty = False + + +class NodeLog(ExecutionNode): + """ + Log the input data. + + Methods + ------- + - :meth:`~colour.utilities.NodeLog.__init__` + - :meth:`~colour.utilities.NodeLog.process` + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + self.description = "Log the input data" + + self.add_input_port("input") + self.add_input_port("verbosity", "info") + + @notify_process_state + def process(self, **kwargs: Any) -> None: # noqa: ARG002 + """ + Process the node. + """ + + self.log(self.get_input("input"), self.get_input("verbosity")) + + self.dirty = False + + +class NodeSleep(ExecutionNode): + """ + Sleep for given duration in seconds. + + Methods + ------- + - :meth:`~colour.utilities.NodeLog.__init__` + - :meth:`~colour.utilities.NodeLog.process` + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + self.description = "Sleep for given duration in seconds" + + self.add_input_port("duration", 0) + + @notify_process_state + def process(self, **kwargs: Any) -> None: # noqa: ARG002 + """ + Process the node. + """ + + time.sleep(self.get_input("duration")) + + self.dirty = False + + +class NodeSetGraphOutputPort(ExecutionNode): + """ + Set the parent graph given output with given value. + + Methods + ------- + - :meth:`~colour.utilities.NodeSetGraphOutputPort.__init__` + - :meth:`~colour.utilities.NodeSetGraphOutputPort.process` + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + self.description = "Set the parent graph given output with given value" + + self.add_input_port("output") + self.add_input_port("value") + + @notify_process_state + def process(self, **kwargs: Any) -> None: # noqa: ARG002 + """ + Process the node. + """ + + if self.parent is not None: + self.parent.set_output(self.get_input("output"), self.get_input("value")) + + self.dirty = False diff --git a/colour/utilities/tests/test_network.py b/colour/utilities/tests/test_network.py index c1624cd44..0b0818bd6 100644 --- a/colour/utilities/tests/test_network.py +++ b/colour/utilities/tests/test_network.py @@ -3,6 +3,7 @@ from __future__ import annotations import re +import time import typing import numpy as np @@ -13,6 +14,10 @@ from colour.utilities import ( ExecutionNode, For, + NodeLog, + NodePassthrough, + NodeSetGraphOutputPort, + NodeSleep, ParallelForMultiprocess, ParallelForThread, Port, @@ -20,11 +25,11 @@ PortNode, TreeNode, is_pydot_installed, + notify_process_state, ) from colour.utilities.network import ( ProcessPoolExecutorManager, ThreadPoolExecutorManager, - notify_process_state, ) __author__ = "Colour Developers" @@ -44,6 +49,10 @@ "TestParallelForThread", "TestProcessPoolExecutorManager", "TestParallelForMultiProcess", + "TestNodePassthrough", + "TestNodeLog", + "TestNodeSleep", + "TestNodeSetGraphOutputPort", ] @@ -1208,3 +1217,70 @@ def test_ParallelForMultiProcess(self) -> None: loop.process() assert sum_array.get_output("summation") == 140 + + +class TestNodePassthrough: + """ + Define :class:`colour.utilities.network.NodePassthrough` class unit tests + methods. + """ + + def test_NodePassthrough(self) -> None: + """Test the :class:`colour.utilities.network.NodePassthrough` class.""" + + node = NodePassthrough() + node.set_input("input", 1) + node.process() + + assert node.get_output("output") == 1 + + +class TestNodeLog: + """ + Define :class:`colour.utilities.network.NodeLog` class unit tests + methods. + """ + + def test_NodeLog(self) -> None: + """Test the :class:`colour.utilities.network.NodeLog` class.""" + + node = NodeLog() + node.set_input("input", "Foo") + node.process() + + +class TestNodeSleep: + """ + Define :class:`colour.utilities.network.NodeSleep` class unit tests + methods. + """ + + def test_NodeSleep(self) -> None: + """Test the :class:`colour.utilities.network.NodeSleep` class.""" + + node = NodeSleep() + node.set_input("duration", 1) + then = time.time() + node.process() + + assert 1.25 > time.time() - then > 0.75 + + +class TestNodeSetGraphOutputPort: + """ + Define :class:`colour.utilities.network.NodeSetGraphOutputPort` class unit tests + methods. + """ + + def test_NodeSetGraphOutputPort(self) -> None: + """Test the :class:`colour.utilities.network.NodeSetGraphOutputPort` class.""" + + graph = PortGraph() + graph.add_output_port("result") + node = NodeSetGraphOutputPort() + node.set_input("output", "result") + node.set_input("value", 1) + graph.add_node(node) + graph.process() + + assert graph.get_output("result") == 1 diff --git a/docs/colour.utilities.rst b/docs/colour.utilities.rst index 4daa02271..599dbc7a8 100644 --- a/docs/colour.utilities.rst +++ b/docs/colour.utilities.rst @@ -189,6 +189,10 @@ Network ParallelForThread ProcessPoolExecutorManager ParallelForMultiprocess + NodePassthrough + NodeLog + NodeSleep + NodeSetGraphOutput .. currentmodule:: colour.utilities