Added more Pt nodes.

This commit is contained in:
HowToSD
2025-02-25 02:43:17 -08:00
parent 8f00640ebe
commit c17a68cad2
25 changed files with 698 additions and 3 deletions
+3
View File
@@ -43,6 +43,9 @@ Creating sample data and plotting it is incredibly simple, as shown below:
![SNS Line Plot](docs/images/sns_line.png)
An example plot
You can also analyze images generated in ComfyUI's image generation workflows. Below screenshot shows a histogram of red channel pixels.
![Red channel pixel distribution](docs/images/red_pixel_distribution.png)
## Installation
Note: GPU is not required to run this custom module.
+18
View File
@@ -115,14 +115,20 @@ from .modules.pandas_wrapper.pandas_var import PandasVar
from .modules.pandas_wrapper.pandas_vertical_concat import PandasVerticalConcat
from .modules.pandas_wrapper.pandas_vertical_split import PandasVerticalSplit
from .modules.pandas_wrapper.pandas_xs import PandasXs
from .modules.pytorch_wrapper.pt_flatten import PtFlatten
from .modules.pytorch_wrapper.pt_float_create import PtFloatCreate
from .modules.pytorch_wrapper.pt_from_image import PtFromImage
from .modules.pytorch_wrapper.pt_from_latent import PtFromLatent
from .modules.pytorch_wrapper.pt_from_numpy import PtFromNumpy
from .modules.pytorch_wrapper.pt_int_create import PtIntCreate
from .modules.pytorch_wrapper.pt_size import PtSize
from .modules.pytorch_wrapper.pt_size_create import PtSizeCreate
from .modules.pytorch_wrapper.pt_size_to_numpy import PtSizeToNumpy
from .modules.pytorch_wrapper.pt_size_to_string import PtSizeToString
from .modules.pytorch_wrapper.pt_to_image import PtToImage
from .modules.pytorch_wrapper.pt_to_latent import PtToLatent
from .modules.pytorch_wrapper.pt_to_numpy import PtToNumpy
from .modules.pytorch_wrapper.pt_to_rgb_tensors import PtToRgbTensors
from .modules.seaborn_wrapper.sns_bar import SNSBar
from .modules.seaborn_wrapper.sns_histogram import SNSHistogram
from .modules.seaborn_wrapper.sns_line import SNSLine
@@ -248,14 +254,20 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = {
"PandasVerticalConcat": PandasVerticalConcat,
"PandasVerticalSplit": PandasVerticalSplit,
"PandasXs": PandasXs,
"PtFlatten": PtFlatten,
"PtFloatCreate": PtFloatCreate,
"PtFromImage": PtFromImage,
"PtFromLatent": PtFromLatent,
"PtFromNumpy": PtFromNumpy,
"PtIntCreate": PtIntCreate,
"PtSize": PtSize,
"PtSizeCreate": PtSizeCreate,
"PtSizeToNumpy": PtSizeToNumpy,
"PtSizeToString": PtSizeToString,
"PtToImage": PtToImage,
"PtToLatent": PtToLatent,
"PtToNumpy": PtToNumpy,
"PtToRgbTensors": PtToRgbTensors,
"PyKvFloatCreate": PyKvFloatCreate,
"PyKvIntCreate": PyKvIntCreate,
"PyKvStringCreate": PyKvStringCreate,
@@ -384,14 +396,20 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = {
"PandasVerticalConcat": "Pandas Vertical Concat",
"PandasVerticalSplit": "Pandas Vertical Split",
"PandasXs": "Pandas Xs",
"PtFlatten": "Pt Flatten",
"PtFloatCreate": "Pt Float Create",
"PtFromImage": "Pt From Image",
"PtFromLatent": "Pt From Latent",
"PtFromNumpy": "Pt From Numpy",
"PtIntCreate": "Pt Int Create",
"PtSize": "Pt Size",
"PtSizeCreate": "Pt Size Create",
"PtSizeToNumpy": "Pt Size To Numpy",
"PtSizeToString": "Pt Size To String",
"PtToImage": "Pt To Image",
"PtToLatent": "Pt To Latent",
"PtToNumpy": "Pt To Numpy",
"PtToRgbTensors": "Pt To Rgb Tensors",
"PyKvFloatCreate": "Py Kv Float Create",
"PyKvIntCreate": "Py Kv Int Create",
"PyKvStringCreate": "Py Kv String Create",
Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

+9
View File
@@ -113,6 +113,11 @@
| [Pandas Log](pandas_log.md) | Applying the natural logarithm function to a pandas DataFrame and converting non-numeric values to NaN. |
| [Pandas Sin](pandas_sin.md) | Applies the sine function to a pandas DataFrame and converting non-numeric values to NaN. |
| [Pandas Tan](pandas_tan.md) | Applies the tangent function to a pandas DataFrame and converting non-numeric values to NaN. |
## Miscellaneous
| Node | Description |
| --- | --- |
| [Pt Flatten](pt_flatten.md) | Flattens a PyTorch tensor into a 1D tensor. |
| [Pt To Rgb Tensors](pt_to_rgb_tensors.md) | Splits a PyTorch tensor into R, G, and B tensors. |
## Numpy
| Node | Description |
| --- | --- |
@@ -136,6 +141,10 @@
| [Pt From Latent](pt_from_latent.md) | Casts a latent tensor as a PyTorch tensor. |
| [Pt From Numpy](pt_from_numpy.md) | Converts a NumPy ndarray to a PyTorch tensor while preserving its data type. |
| [Pt Int Create](pt_int_create.md) | Creates a PyTorch tensor with 32-bit integer |
| [Pt Size](pt_size.md) | Extracts the PyTorch Size object of a PyTorch tensor using the size() method. |
| [Pt Size Create](pt_size_create.md) | Creates a PyTorch Size using values entered in the text field. |
| [Pt Size To Numpy](pt_size_to_numpy.md) | Converts PyTorch Size object to NumPy ndarray. |
| [Pt Size To String](pt_size_to_string.md) | Converts PyTorch Size object to a Python string. |
| [Pt To Image](pt_to_image.md) | Casts a PyTorch tensor as an Image tensor. |
| [Pt To Latent](pt_to_latent.md) | Casts a PyTorch tensor as a latent tensor. |
| [Pt To Numpy](pt_to_numpy.md) | Converts PyTorch tensor to NumPy ndarray. |
+19
View File
@@ -0,0 +1,19 @@
# Pt Flatten
Flattens a PyTorch tensor into a 1D tensor.
Category: PyTorch wrapper
## Input
| Name | Data type |
|---|---|
| tens | Tensor |
## Output
| Data type |
|---|
| Tensor |
<HR>
Category: Miscellaneous
ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
+17
View File
@@ -0,0 +1,17 @@
# Pt Size
Extracts the PyTorch Size object of a PyTorch tensor using the size() method.
## Input
| Name | Data type |
|---|---|
| tens | Tensor |
## Output
| Data type |
|---|
| Ptsize |
<HR>
Category: PyTorch wrapper
ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
+17
View File
@@ -0,0 +1,17 @@
# Pt Size Create
Creates a PyTorch Size using values entered in the text field.
## Input
| Name | Data type |
|---|---|
| data | String |
## Output
| Data type |
|---|
| Ptsize |
<HR>
Category: PyTorch wrapper
ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
+17
View File
@@ -0,0 +1,17 @@
# Pt Size To Numpy
Converts PyTorch Size object to NumPy ndarray.
## Input
| Name | Data type |
|---|---|
| sz | Ptsize |
## Output
| Data type |
|---|
| Ndarray |
<HR>
Category: PyTorch wrapper
ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
+17
View File
@@ -0,0 +1,17 @@
# Pt Size To String
Converts PyTorch Size object to a Python string.
## Input
| Name | Data type |
|---|---|
| sz | Ptsize |
## Output
| Data type |
|---|
| String |
<HR>
Category: PyTorch wrapper
ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
+21
View File
@@ -0,0 +1,21 @@
# Pt To Rgb Tensors
Splits a PyTorch tensor into R, G, and B tensors.
Category: PyTorch wrapper
## Input
| Name | Data type |
|---|---|
| tens | Tensor |
## Output
| Data type |
|---|
| Tensor |
| Tensor |
| Tensor |
<HR>
Category: Miscellaneous
ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
+40
View File
@@ -0,0 +1,40 @@
from typing import Any, Dict
import torch
class PtFlatten:
"""
Flattens a PyTorch tensor into a 1D tensor.
Category: PyTorch wrapper
"""
@classmethod
def INPUT_TYPES(cls) -> Dict[str, Any]:
"""
Defines the required input types.
Returns:
Dict[str, Any]: A dictionary of required input types.
"""
return {
"required": {
"tens": ("TENSOR", {}),
}
}
RETURN_TYPES: tuple = ("TENSOR",)
FUNCTION: str = "f"
CATEGORY: str = "PyTorch wrapper"
def f(self, tens: torch.Tensor) -> tuple:
"""
Flattens a PyTorch tensor into a 1D tensor.
Args:
tens (torch.Tensor): A PyTorch tensor of any shape.
Returns:
tuple: A tuple containing the flattened tensor.
"""
return (tens.flatten(),)
+1 -1
View File
@@ -37,4 +37,4 @@ class PtFromLatent:
Returns:
tuple: A tuple containing the PyTorch tensor.
"""
return (latent,)
return (latent["samples"],)
+41
View File
@@ -0,0 +1,41 @@
from typing import Any, Dict
import torch
import ast
class PtSize:
"""
Extracts the PyTorch Size object of a PyTorch tensor using the size() method.
category: PyTorch wrapper
"""
@classmethod
def INPUT_TYPES(cls) -> Dict[str, Any]:
"""
Defines the input types for the function.
Returns:
Dict[str, Any]: A dictionary specifying required input types.
"""
return {
"required": {
"tens": ("TENSOR",),
}
}
RETURN_TYPES: tuple = ("PTSIZE",)
FUNCTION: str = "f"
CATEGORY: str = "PyTorch wrapper"
def f(self, tens: torch.Tensor) -> tuple:
"""
Extracts the PyTorch Size object of a PyTorch tensor using the size() method.
Args:
tens (torch.Tensor): The tensor.
Returns:
tuple: A tuple containing the PyTorch Size.
"""
return (tens.size(),)
+46
View File
@@ -0,0 +1,46 @@
from typing import Any, Dict
import torch
import ast
class PtSizeCreate:
"""
Creates a PyTorch Size using values entered in the text field.
category: PyTorch wrapper
"""
@classmethod
def INPUT_TYPES(cls) -> Dict[str, Any]:
"""
Defines the input types for the function.
Returns:
Dict[str, Any]: A dictionary specifying required input types.
"""
return {
"required": {
"data": ("STRING", {"multiline": True}),
}
}
RETURN_TYPES: tuple = ("PTSIZE",)
FUNCTION: str = "f"
CATEGORY: str = "PyTorch wrapper"
def f(self, data: str) -> tuple:
"""
Creates a PyTorch Size using values entered in the text field.
Args:
data (str): String value in the text field.
Returns:
tuple: A tuple containing the PyTorch Size.
"""
# Convert string to Python list
list_data = ast.literal_eval(data)
# Convert list to PyTorch Size
size = torch.Size(list_data)
return (size,)
@@ -0,0 +1,41 @@
from typing import Any, Dict
import torch
import numpy as np
class PtSizeToNumpy:
"""
Converts PyTorch Size object to NumPy ndarray.
category: PyTorch wrapper
"""
@classmethod
def INPUT_TYPES(cls) -> Dict[str, Any]:
"""
Defines the input types for the function.
Returns:
Dict[str, Any]: A dictionary specifying required input types.
"""
return {
"required": {
"sz": ("PTSIZE", {}),
}
}
RETURN_TYPES: tuple = ("NDARRAY",)
FUNCTION: str = "f"
CATEGORY: str = "PyTorch wrapper"
def f(self, sz: torch.Size) -> tuple:
"""
Converts PyTorch Size object to NumPy ndarray.
Args:
sz (torch.Size): PyTorch Size
Returns:
tuple: A tuple containing the ndarray.
"""
array = np.array(sz, dtype=int)
return (array,)
@@ -0,0 +1,40 @@
from typing import Any, Dict
import torch
class PtSizeToString:
"""
Converts PyTorch Size object to a Python string.
category: PyTorch wrapper
"""
@classmethod
def INPUT_TYPES(cls) -> Dict[str, Any]:
"""
Defines the input types for the function.
Returns:
Dict[str, Any]: A dictionary specifying required input types.
"""
return {
"required": {
"sz": ("PTSIZE", {}),
}
}
RETURN_TYPES: tuple = ("STRING",)
FUNCTION: str = "f"
CATEGORY: str = "PyTorch wrapper"
def f(self, sz: torch.Size) -> tuple:
"""
Converts PyTorch Size object to a Python string.
Args:
sz (torch.Size): PyTorch Size
Returns:
tuple: A tuple containing the string.
"""
return (str(sz),)
+1 -1
View File
@@ -37,4 +37,4 @@ class PtToLatent:
Returns:
tuple: A tuple containing the latent tensor.
"""
return (tens,)
return ({"samples":tens},)
@@ -0,0 +1,50 @@
from typing import Any, Dict
import torch
class PtToRgbTensors:
"""
Splits a PyTorch tensor into R, G, and B tensors.
Category: PyTorch wrapper
"""
@classmethod
def INPUT_TYPES(cls) -> Dict[str, Any]:
"""
Defines the required input types.
Returns:
Dict[str, Any]: A dictionary of required input types.
"""
return {
"required": {
"tens": ("TENSOR", {}),
}
}
RETURN_NAMES: tuple = ("R", "G", "B")
RETURN_TYPES: tuple = ("TENSOR", "TENSOR", "TENSOR")
FUNCTION: str = "f"
CATEGORY: str = "PyTorch wrapper"
def f(self, tens: torch.Tensor) -> tuple:
"""
Splits a PyTorch tensor into R, G, and B tensors.
Args:
tens (torch.Tensor): A PyTorch tensor of shape [B, H, W, C] or [H, W, C].
Assumes the last axis represents the color channels.
Returns:
tuple: A tuple containing the R, G, and B tensors.
If the input tensor has a batch axis, it will be retained.
Raises:
ValueError: If the input tensor does not have exactly 3 channels.
"""
if tens.size()[-1] != 3:
raise ValueError(f"Expected a 3-channel tensor, but got {tens.size()[-1]} channels.")
r, g, b = tens[..., 0], tens[..., 1], tens[..., 2]
return (r, g, b)
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "data-analysis"
description = "Data analysis custom modules for ComfyUI - Use Pandas & Matplotlib within ComfyUI"
version = "1.3.2"
version = "1.3.3"
license = {file = "LICENSE"}
[project.urls]
+39
View File
@@ -0,0 +1,39 @@
import os
import sys
import unittest
import torch
PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
from pytorch_wrapper.pt_flatten import PtFlatten
class TestPtFlatten(unittest.TestCase):
def setUp(self):
"""Set up test instance."""
self.node = PtFlatten()
# Test cases: (Input tensor, Expected flattened tensor shape)
self.test_cases = [
(torch.randn(3, 4, 5), (60,)), # 3D Tensor
(torch.randn(2, 3, 4, 5), (120,)), # 4D Tensor
(torch.randn(10, 10), (100,)), # 2D Tensor
(torch.randn(1), (1,)), # 1D Tensor remains unchanged
]
def test_flatten(self):
"""Test flattening tensors correctly."""
for tensor, expected_shape in self.test_cases:
with self.subTest(tensor_shape=tensor.shape):
flattened, = self.node.f(tensor)
# Ensure return value is a tensor
self.assertTrue(isinstance(flattened, torch.Tensor), "Output is not a tensor")
# Ensure shape matches expected flattened shape
self.assertEqual(flattened.shape, expected_shape, f"Shape mismatch: expected {expected_shape}, got {flattened.shape}")
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,46 @@
import os
import sys
import unittest
import torch
PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
from pytorch_wrapper.pt_size_create import PtSizeCreate
class TestPtSizeCreate(unittest.TestCase):
def setUp(self):
"""Set up test data."""
self.node = PtSizeCreate()
# Test cases with torch.Size objects
self.test_cases = [
# Empty size (zero-rank tensor shape)
("[]", torch.Size([])),
# Rank 1 (single-dimension size)
("[3]", torch.Size([3])),
# Rank 2 (matrix size)
("[3, 4]", torch.Size([3, 4])),
# Rank 3 (3D tensor size)
("[2, 3, 4]", torch.Size([2, 3, 4])),
# Rank 4 (higher-dimensional tensor size)
("[1, 2, 3, 4]", torch.Size([1, 2, 3, 4])),
# Rank 5 (very high-dimensional tensor size)
("[5, 4, 3, 2, 1]", torch.Size([5, 4, 3, 2, 1])),
]
def test_create_size(self):
"""Test PyTorch Size creation from various structured inputs."""
for data_str, expected_size in self.test_cases:
with self.subTest(data=data_str):
retval = self.node.f(data_str)[0]
# Ensure the returned value is a PyTorch Size
self.assertTrue(isinstance(retval, torch.Size), f"Expected Size, got {type(retval)}")
# Ensure the value matches expected size
self.assertEqual(retval, expected_size, f"Value mismatch for input: {data_str}")
if __name__ == "__main__":
unittest.main()
+44
View File
@@ -0,0 +1,44 @@
import os
import sys
import unittest
import torch
PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
from pytorch_wrapper.pt_size import PtSize
class TestPtSize(unittest.TestCase):
def setUp(self):
"""Set up test data."""
self.node = PtSize()
# Test cases with torch.Tensor objects
self.test_cases = [
# Rank 0 (scalar tensor)
(torch.tensor(42), torch.Size([])),
# Rank 1 (vector tensor)
(torch.tensor([1, 2, 3]), torch.Size([3])),
# Rank 2 (matrix tensor)
(torch.tensor([[1, 2], [3, 4]]), torch.Size([2, 2])),
# Rank 3 (3D tensor)
(torch.tensor([[[1], [2]], [[3], [4]]]), torch.Size([2, 2, 1])),
# Rank 4 (4D tensor)
(torch.tensor([[[[1, 2], [3, 4]]]]), torch.Size([1, 1, 2, 2])),
]
def test_extract_size(self):
"""Test PyTorch Size extraction from tensors."""
for tensor, expected_size in self.test_cases:
with self.subTest(data=tensor):
retval = self.node.f(tensor)[0]
# Ensure the returned value is a PyTorch Size
self.assertTrue(isinstance(retval, torch.Size), f"Expected Size, got {type(retval)}")
# Ensure the value matches expected size
self.assertEqual(retval, expected_size, f"Value mismatch for input: {tensor}")
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,55 @@
import os
import sys
import unittest
import torch
import numpy as np
PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
from pytorch_wrapper.pt_size_to_numpy import PtSizeToNumpy
class TestPtSizeToNumpy(unittest.TestCase):
def setUp(self):
"""Set up test data."""
self.node = PtSizeToNumpy()
# Test cases with torch.Size objects
self.test_cases = [
# Empty size (zero-rank tensor shape)
(torch.Size([]), np.array([], dtype=int)),
# Rank 1 (single-dimension size)
(torch.Size([3]), np.array([3], dtype=int)),
# Rank 2 (matrix size)
(torch.Size([3, 4]), np.array([3, 4], dtype=int)),
# Rank 3 (3D tensor size)
(torch.Size([2, 3, 4]), np.array([2, 3, 4], dtype=int)),
# Rank 4 (higher-dimensional tensor size)
(torch.Size([1, 2, 3, 4]), np.array([1, 2, 3, 4], dtype=int)),
# Rank 5 (very high-dimensional tensor size)
(torch.Size([5, 4, 3, 2, 1]), np.array([5, 4, 3, 2, 1], dtype=int)),
]
def test_convert_size_to_numpy(self):
"""Test conversion of PyTorch Size to NumPy ndarray."""
for torch_size, expected_array in self.test_cases:
with self.subTest(data=torch_size):
retval = self.node.f(torch_size)[0]
# Ensure the returned value is a NumPy array
self.assertTrue(isinstance(retval, np.ndarray), f"Expected ndarray, got {type(retval)}")
# Ensure the dtype is integer
self.assertEqual(retval.dtype, expected_array.dtype, f"Dtype mismatch for input: {torch_size}")
# Ensure the shape matches
self.assertEqual(retval.shape, expected_array.shape,
f"Shape mismatch for input: {torch_size} (Expected {expected_array.shape}, got {retval.shape})")
# Ensure values match
np.testing.assert_array_equal(retval, expected_array,
f"Value mismatch for input: {torch_size}")
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,46 @@
import os
import sys
import unittest
import torch
PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
from pytorch_wrapper.pt_size_to_string import PtSizeToString
class TestPtSizeToString(unittest.TestCase):
def setUp(self):
"""Set up test data."""
self.node = PtSizeToString()
# Test cases with torch.Size objects
self.test_cases = [
# Empty size (zero-rank tensor shape)
(torch.Size([]), "torch.Size([])"),
# Rank 1 (single-dimension size)
(torch.Size([3]), "torch.Size([3])"),
# Rank 2 (matrix size)
(torch.Size([3, 4]), "torch.Size([3, 4])"),
# Rank 3 (3D tensor size)
(torch.Size([2, 3, 4]), "torch.Size([2, 3, 4])"),
# Rank 4 (higher-dimensional tensor size)
(torch.Size([1, 2, 3, 4]), "torch.Size([1, 2, 3, 4])"),
# Rank 5 (very high-dimensional tensor size)
(torch.Size([5, 4, 3, 2, 1]), "torch.Size([5, 4, 3, 2, 1])"),
]
def test_convert_size_to_string(self):
"""Test conversion of PyTorch Size to string."""
for torch_size, expected_string in self.test_cases:
with self.subTest(data=torch_size):
retval = self.node.f(torch_size)[0]
# Ensure the returned value is a string
self.assertTrue(isinstance(retval, str), f"Expected str, got {type(retval)}")
# Ensure the value matches expected string
self.assertEqual(retval, expected_string, f"Value mismatch for input: {torch_size}")
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,69 @@
import os
import sys
import unittest
import torch
PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
sys.path.append(MODULE_ROOT)
from pytorch_wrapper.pt_to_rgb_tensors import PtToRgbTensors
class TestPtToRgbTensors(unittest.TestCase):
def setUp(self):
"""Set up test instance."""
self.node = PtToRgbTensors()
# Test cases: (Input tensor, Expected R, Expected G, Expected B)
self.test_cases = [
# 3D Tensor: [H, W, C]
(
torch.tensor([[[255, 128, 64], [0, 127, 255]]], dtype=torch.uint8),
torch.tensor([[255, 0]], dtype=torch.uint8),
torch.tensor([[128, 127]], dtype=torch.uint8),
torch.tensor([[64, 255]], dtype=torch.uint8),
),
# 4D Tensor: [B, H, W, C]
(
torch.tensor(
[[[[255, 0, 0], [0, 255, 0]], [[0, 0, 255], [255, 255, 255]]]], dtype=torch.uint8
),
torch.tensor([[[255, 0], [0, 255]]], dtype=torch.uint8),
torch.tensor([[[0, 255], [0, 255]]], dtype=torch.uint8),
torch.tensor([[[0, 0], [255, 255]]], dtype=torch.uint8),
),
]
def test_split_rgb(self):
"""Test splitting RGB tensors correctly."""
for tensor, expected_r, expected_g, expected_b in self.test_cases:
with self.subTest(tensor_shape=tensor.shape):
r, g, b = self.node.f(tensor)
# Ensure return values are tensors
self.assertTrue(isinstance(r, torch.Tensor), "R is not a tensor")
self.assertTrue(isinstance(g, torch.Tensor), "G is not a tensor")
self.assertTrue(isinstance(b, torch.Tensor), "B is not a tensor")
# Ensure shapes match
self.assertEqual(r.shape, expected_r.shape, f"R shape mismatch: expected {expected_r.shape}, got {r.shape}")
self.assertEqual(g.shape, expected_g.shape, f"G shape mismatch: expected {expected_g.shape}, got {g.shape}")
self.assertEqual(b.shape, expected_b.shape, f"B shape mismatch: expected {expected_b.shape}, got {b.shape}")
# Ensure values match
torch.testing.assert_close(r, expected_r, msg="R values mismatch")
torch.testing.assert_close(g, expected_g, msg="G values mismatch")
torch.testing.assert_close(b, expected_b, msg="B values mismatch")
def test_invalid_input(self):
"""Test error handling for non-3-channel input."""
invalid_tensor = torch.rand(1, 512, 512, 4) # 4 channels instead of 3
with self.assertRaises(ValueError) as context:
self.node.f(invalid_tensor)
self.assertIn("Expected a 3-channel tensor", str(context.exception))
if __name__ == "__main__":
unittest.main()