diff --git a/__init__.py b/__init__.py index 37cddc6..5964921 100644 --- a/__init__.py +++ b/__init__.py @@ -13,6 +13,8 @@ from .modules.cda.py_kv_string_create import PyKvStringCreate from .modules.matplotlib.mpl_bar import MPLBar from .modules.matplotlib.mpl_line import MPLLine from .modules.matplotlib.mpl_scatter import MPLScatter +from .modules.numpy_wrapper.numpy_float_create import NumpyFloatCreate +from .modules.numpy_wrapper.numpy_int_create import NumpyIntCreate from .modules.pandas_wrapper.pandas_add import PandasAdd from .modules.pandas_wrapper.pandas_add_scalar_float import PandasAddScalarFloat from .modules.pandas_wrapper.pandas_add_scalar_int import PandasAddScalarInt @@ -28,6 +30,7 @@ from .modules.pandas_wrapper.pandas_cov import PandasCov from .modules.pandas_wrapper.pandas_create import PandasCreate from .modules.pandas_wrapper.pandas_create_from_dict import PandasCreateFromDict from .modules.pandas_wrapper.pandas_create_from_multiple_dict import PandasCreateFromMultipleDict +from .modules.pandas_wrapper.pandas_create_from_numpy import PandasCreateFromNumpy from .modules.pandas_wrapper.pandas_create_series_from_dict import PandasCreateSeriesFromDict from .modules.pandas_wrapper.pandas_create_with_index import PandasCreateWithIndex from .modules.pandas_wrapper.pandas_cummax import PandasCummax @@ -105,12 +108,17 @@ from .modules.pandas_wrapper.pandas_sum import PandasSum from .modules.pandas_wrapper.pandas_summary import PandasSummary from .modules.pandas_wrapper.pandas_tan import PandasTan from .modules.pandas_wrapper.pandas_to_datetime import PandasToDatetime +from .modules.pandas_wrapper.pandas_to_numpy import PandasToNumpy from .modules.pandas_wrapper.pandas_to_string import PandasToString from .modules.pandas_wrapper.pandas_transpose import PandasTranspose 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_float_create import PtFloatCreate +from .modules.pytorch_wrapper.pt_from_numpy import PtFromNumpy +from .modules.pytorch_wrapper.pt_int_create import PtIntCreate +from .modules.pytorch_wrapper.pt_to_numpy import PtToNumpy from .modules.seaborn_wrapper.sns_bar import SNSBar from .modules.seaborn_wrapper.sns_histogram import SNSHistogram from .modules.seaborn_wrapper.sns_line import SNSLine @@ -134,6 +142,8 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = { "MPLBar": MPLBar, "MPLLine": MPLLine, "MPLScatter": MPLScatter, + "NumpyFloatCreate": NumpyFloatCreate, + "NumpyIntCreate": NumpyIntCreate, "PandasAdd": PandasAdd, "PandasAddScalarFloat": PandasAddScalarFloat, "PandasAddScalarInt": PandasAddScalarInt, @@ -149,6 +159,7 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = { "PandasCreate": PandasCreate, "PandasCreateFromDict": PandasCreateFromDict, "PandasCreateFromMultipleDict": PandasCreateFromMultipleDict, + "PandasCreateFromNumpy": PandasCreateFromNumpy, "PandasCreateSeriesFromDict": PandasCreateSeriesFromDict, "PandasCreateWithIndex": PandasCreateWithIndex, "PandasCummax": PandasCummax, @@ -226,12 +237,17 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = { "PandasSummary": PandasSummary, "PandasTan": PandasTan, "PandasToDatetime": PandasToDatetime, + "PandasToNumpy": PandasToNumpy, "PandasToString": PandasToString, "PandasTranspose": PandasTranspose, "PandasVar": PandasVar, "PandasVerticalConcat": PandasVerticalConcat, "PandasVerticalSplit": PandasVerticalSplit, "PandasXs": PandasXs, + "PtFloatCreate": PtFloatCreate, + "PtFromNumpy": PtFromNumpy, + "PtIntCreate": PtIntCreate, + "PtToNumpy": PtToNumpy, "PyKvFloatCreate": PyKvFloatCreate, "PyKvIntCreate": PyKvIntCreate, "PyKvStringCreate": PyKvStringCreate, @@ -258,6 +274,8 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = { "MPLBar": "MPL Bar Chart", "MPLLine": "MPL Line Plot", "MPLScatter": "MPL Scatter Plot", + "NumpyFloatCreate": "Numpy Float Create", + "NumpyIntCreate": "Numpy Int Create", "PandasAdd": "Pandas Add", "PandasAddScalarFloat": "Pandas Add Scalar Float", "PandasAddScalarInt": "Pandas Add Scalar Int", @@ -273,6 +291,7 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = { "PandasCreate": "Pandas Create", "PandasCreateFromDict": "Pandas Create From Dict", "PandasCreateFromMultipleDict": "Pandas Create From Multiple Dict", + "PandasCreateFromNumpy": "Pandas Create From Numpy", "PandasCreateSeriesFromDict": "Pandas Create Series From Dict", "PandasCreateWithIndex": "Pandas Create With Index", "PandasCummax": "Pandas Cummax", @@ -350,12 +369,17 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = { "PandasSummary": "Pandas Summary", "PandasTan": "Pandas Tan", "PandasToDatetime": "Pandas To Datetime", + "PandasToNumpy": "Pandas To Numpy", "PandasToString": "Pandas To String", "PandasTranspose": "Pandas Transpose", "PandasVar": "Pandas Var", "PandasVerticalConcat": "Pandas Vertical Concat", "PandasVerticalSplit": "Pandas Vertical Split", "PandasXs": "Pandas Xs", + "PtFloatCreate": "Pt Float Create", + "PtFromNumpy": "Pt From Numpy", + "PtIntCreate": "Pt Int Create", + "PtToNumpy": "Pt To Numpy", "PyKvFloatCreate": "Py Kv Float Create", "PyKvIntCreate": "Py Kv Int Create", "PyKvStringCreate": "Py Kv String Create", diff --git a/dev/scripts/run_tests.sh b/dev/scripts/run_tests.sh index 1bc4915..cd29541 100755 --- a/dev/scripts/run_tests.sh +++ b/dev/scripts/run_tests.sh @@ -3,6 +3,9 @@ # ``` # dev/scripts/run_tests.sh # ```` +# If you add a new test file, make sure that the directory contains an empty +# __init__.py. Otherwise, unittest won't be able to find the directory and +# the tests in the directory will be skipped. export PYTHONPATH=modules:$PYTHONPATH echo $PYTHONPATH python -m unittest discover -s tests -p "*_test.py" -t . diff --git a/docs/reference/node_reference.md b/docs/reference/node_reference.md index f838250..e8de662 100644 --- a/docs/reference/node_reference.md +++ b/docs/reference/node_reference.md @@ -61,6 +61,7 @@ | [Pandas As Float](pandas_as_float.md) | Converts all cells in a pandas DataFrame to float type. | | [Pandas As Int](pandas_as_int.md) | Converts all cells in a pandas DataFrame to integer type. | | [Pandas As String](pandas_as_string.md) | Converts all cells in a pandas DataFrame to string type. | +| [Pandas To Numpy](pandas_to_numpy.md) | Converts a pandas DataFrame to Numpy ndarray. | ## DataFrame information | Node | Description | | --- | --- | @@ -94,6 +95,7 @@ | [Pandas Create](pandas_create.md) | Creates a pandas DataFrame using values entered in the text field. | | [Pandas Create From Dict](pandas_create_from_dict.md) | Creates a pandas DataFrame from a Python dictionary. | | [Pandas Create From Multiple Dict](pandas_create_from_multiple_dict.md) | Creates a pandas DataFrame from multiple Python dictionaries. | +| [Pandas Create From Numpy](pandas_create_from_numpy.md) | Creates a pandas DataFrame from a NumPy ndarray. | | [Pandas Create Series From Dict](pandas_create_series_from_dict.md) | Creates a Pandas Series from a Python dictionary. | | [Pandas Create With Index](pandas_create_with_index.md) | Creates a pandas DataFrame using values entered in the text field. Input data is assumed to have an index. | | [Pandas Load CSV](pandas_load_csv.md) | Loads CSV files into a pandas DataFrame. | @@ -111,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. | +## Numpy +| Node | Description | +| --- | --- | +| [Numpy Float Create](numpy_float_create.md) | Creates a NumPy ndarray with 32-bit floating point precision | +| [Numpy Int Create](numpy_int_create.md) | Creates a NumPy ndarray with 32-bit integer | ## Plot | Node | Description | | --- | --- | @@ -121,6 +128,13 @@ | [SNS Histogram](sns_histogram.md) | Generates a histogram from a Pandas DataFrame using Seaborn. | | [SNS Line Plot](sns_line.md) | Generates a line plot from a pandas DataFrame using Seaborn. | | [SNS Scatter Plot](sns_scatter.md) | Generates a scatter plot from a pandas DataFrame using Seaborn. | +## PyTorch wrapper +| Node | Description | +| --- | --- | +| [Pt Float Create](pt_float_create.md) | Creates a PyTorch tensor with 32-bit floating point precision | +| [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 To Numpy](pt_to_numpy.md) | Converts PyTorch tensor to NumPy ndarray. | ## Python wrapper | Node | Description | | --- | --- | diff --git a/docs/reference/numpy_float_create.md b/docs/reference/numpy_float_create.md new file mode 100644 index 0000000..61152d5 --- /dev/null +++ b/docs/reference/numpy_float_create.md @@ -0,0 +1,18 @@ +# Numpy Float Create +Creates a NumPy ndarray with 32-bit floating point precision +using values entered in the text field. + +## Input +| Name | Data type | +|---|---| +| data | String | + +## Output +| Data type | +|---| +| Ndarray | + +
+Category: Numpy + +ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/numpy_int_create.md b/docs/reference/numpy_int_create.md new file mode 100644 index 0000000..d53aceb --- /dev/null +++ b/docs/reference/numpy_int_create.md @@ -0,0 +1,18 @@ +# Numpy Int Create +Creates a NumPy ndarray with 32-bit integer +using values entered in the text field. + +## Input +| Name | Data type | +|---|---| +| data | String | + +## Output +| Data type | +|---| +| Ndarray | + +
+Category: Numpy + +ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pandas_create_from_numpy.md b/docs/reference/pandas_create_from_numpy.md new file mode 100644 index 0000000..13cd86a --- /dev/null +++ b/docs/reference/pandas_create_from_numpy.md @@ -0,0 +1,17 @@ +# Pandas Create From Numpy +Creates a pandas DataFrame from a NumPy ndarray. + +## Input +| Name | Data type | +|---|---| +| data | Ndarray | + +## Output +| Data type | +|---| +| DataFrame | + +
+Category: IO + +ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pandas_to_numpy.md b/docs/reference/pandas_to_numpy.md new file mode 100644 index 0000000..a265537 --- /dev/null +++ b/docs/reference/pandas_to_numpy.md @@ -0,0 +1,17 @@ +# Pandas To Numpy +Converts a pandas DataFrame to Numpy ndarray. + +## Input +| Name | Data type | +|---|---| +| dataframe | DataFrame | + +## Output +| Data type | +|---| +| Ndrray | + +
+Category: Data type conversion + +ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_float_create.md b/docs/reference/pt_float_create.md new file mode 100644 index 0000000..a9f9b85 --- /dev/null +++ b/docs/reference/pt_float_create.md @@ -0,0 +1,18 @@ +# Pt Float Create +Creates a PyTorch tensor with 32-bit floating point precision +using values entered in the text field. + +## Input +| Name | Data type | +|---|---| +| data | String | + +## Output +| Data type | +|---| +| Tensor | + +
+Category: PyTorch wrapper + +ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_from_numpy.md b/docs/reference/pt_from_numpy.md new file mode 100644 index 0000000..77db3d5 --- /dev/null +++ b/docs/reference/pt_from_numpy.md @@ -0,0 +1,17 @@ +# Pt From Numpy +Converts a NumPy ndarray to a PyTorch tensor while preserving its data type. + +## Input +| Name | Data type | +|---|---| +| array | Ndarray | + +## Output +| Data type | +|---| +| Tensor | + +
+Category: PyTorch wrapper + +ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_int_create.md b/docs/reference/pt_int_create.md new file mode 100644 index 0000000..ffb9681 --- /dev/null +++ b/docs/reference/pt_int_create.md @@ -0,0 +1,18 @@ +# Pt Int Create +Creates a PyTorch tensor with 32-bit integer +using values entered in the text field. + +## Input +| Name | Data type | +|---|---| +| data | String | + +## Output +| Data type | +|---| +| Tensor | + +
+Category: PyTorch wrapper + +ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_to_numpy.md b/docs/reference/pt_to_numpy.md new file mode 100644 index 0000000..be9698a --- /dev/null +++ b/docs/reference/pt_to_numpy.md @@ -0,0 +1,17 @@ +# Pt To Numpy +Converts PyTorch tensor to NumPy ndarray. + +## Input +| Name | Data type | +|---|---| +| tens | Tensor | + +## Output +| Data type | +|---| +| Ndarray | + +
+Category: PyTorch wrapper + +ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/modules/numpy_wrapper/numpy_float_create.py b/modules/numpy_wrapper/numpy_float_create.py new file mode 100644 index 0000000..657003a --- /dev/null +++ b/modules/numpy_wrapper/numpy_float_create.py @@ -0,0 +1,47 @@ +from typing import Any, Dict +import numpy as np +import ast + + +class NumpyFloatCreate: + """ + Creates a NumPy ndarray with 32-bit floating point precision + using values entered in the text field. + + category: Numpy + """ + + @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 = ("NDARRAY",) + FUNCTION: str = "f" + CATEGORY: str = "Numpy wrapper" + + def f(self, data: str) -> tuple: + """ + Creates a Numpy ndarray using values entered in the text field. + + Args: + data (str): String value in the text field. + + Returns: + tuple: A tuple containing a the DataFrame. + """ + # Convert string to Python list + list_data = ast.literal_eval(data) + + # Convert list to NumPy array + array = np.array(list_data, dtype=np.float32) + return (array,) diff --git a/modules/numpy_wrapper/numpy_int_create.py b/modules/numpy_wrapper/numpy_int_create.py new file mode 100644 index 0000000..6f11fd3 --- /dev/null +++ b/modules/numpy_wrapper/numpy_int_create.py @@ -0,0 +1,48 @@ +from typing import Any, Dict +import numpy as np +import ast + + +class NumpyIntCreate: + """ + Creates a NumPy ndarray with 32-bit integer + using values entered in the text field. + + category: Numpy + """ + + @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 = ("NDARRAY",) + FUNCTION: str = "f" + CATEGORY: str = "Numpy wrapper" + + def f(self, data: str) -> tuple: + """ + Creates a NumPy ndarray with 32-bit integer using values + entered in the text field. + + Args: + data (str): String value in the text field. + + Returns: + tuple: A tuple containing the NumPy ndarray. + """ + # Convert string to Python list + list_data = ast.literal_eval(data) + + # Convert list to NumPy int32 array + array = np.array(list_data, dtype=np.int32) + return (array,) diff --git a/modules/pandas_wrapper/pandas_create_from_numpy.py b/modules/pandas_wrapper/pandas_create_from_numpy.py new file mode 100644 index 0000000..9c3c3eb --- /dev/null +++ b/modules/pandas_wrapper/pandas_create_from_numpy.py @@ -0,0 +1,48 @@ +from typing import Any, Dict +import pandas as pd +import numpy as np + + +class PandasCreateFromNumpy: + """ + Creates a pandas DataFrame from a NumPy ndarray. + + category: IO + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the `create` function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "data": ("NDARRAY", {}), + } + } + + RETURN_TYPES: tuple = ("DATAFRAME",) + FUNCTION: str = "f" + CATEGORY: str = "Data Analysis" + + def f(self, data: np.ndarray) -> tuple: + """ + Creates a pandas DataFrame from a NumPy ndarray. + + Args: + data (np.ndarray): A NumPy array. + + Returns: + tuple: A tuple containing the DataFrame. + + Raises: + ValueError: If the array is not rank 1 or rank 2. + """ + if len(data.shape) not in [1, 2]: + raise ValueError("Only rank 1 and rank 2 arrays are supported.") + + return (pd.DataFrame(data),) + diff --git a/modules/pandas_wrapper/pandas_to_numpy.py b/modules/pandas_wrapper/pandas_to_numpy.py new file mode 100644 index 0000000..fa56e25 --- /dev/null +++ b/modules/pandas_wrapper/pandas_to_numpy.py @@ -0,0 +1,41 @@ +from typing import Any, Dict +import pandas as pd + +class PandasToNumpy: + """ + Converts a pandas DataFrame to Numpy ndarray. + + category: Data type conversion + """ + + @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": { + "dataframe": ("DATAFRAME", {}), + } + } + + RETURN_TYPES: tuple = ("NDARRAY",) + FUNCTION: str = "f" + CATEGORY: str = "Data Analysis" + + def f(self, dataframe: pd.DataFrame) -> tuple: + """ + Converts a pandas DataFrame to a NumPy ndarray. + + Args: + dataframe (DataFrame): The DataFrame. + + Returns: + tuple: A tuple containing the NumPy representation of the DataFrame. + """ + if not isinstance(dataframe, pd.DataFrame): + raise TypeError("PandasToNumpy expects pd.DataFrame.") + return (dataframe.to_numpy(),) diff --git a/modules/pytorch_wrapper/pt_float_create.py b/modules/pytorch_wrapper/pt_float_create.py new file mode 100644 index 0000000..48672fe --- /dev/null +++ b/modules/pytorch_wrapper/pt_float_create.py @@ -0,0 +1,47 @@ +from typing import Any, Dict +import torch +import ast + + +class PtFloatCreate: + """ + Creates a PyTorch tensor with 32-bit floating point precision + 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 = ("TENSOR",) + FUNCTION: str = "f" + CATEGORY: str = "PyTorch wrapper" + + def f(self, data: str) -> tuple: + """ + Creates a PyTorch tensor using values entered in the text field. + + Args: + data (str): String value in the text field. + + Returns: + tuple: A tuple containing the tensor. + """ + # Convert string to Python list + list_data = ast.literal_eval(data) + + # Convert list to PyTorch tensor + tensor = torch.tensor(list_data, dtype=torch.float32) + return (tensor,) diff --git a/modules/pytorch_wrapper/pt_from_numpy.py b/modules/pytorch_wrapper/pt_from_numpy.py new file mode 100644 index 0000000..3ea0b91 --- /dev/null +++ b/modules/pytorch_wrapper/pt_from_numpy.py @@ -0,0 +1,42 @@ +from typing import Any, Dict, Tuple +import numpy as np +import torch + + +class PtFromNumpy: + """ + Converts a NumPy ndarray to a PyTorch tensor while preserving its data type. + + 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": { + "array": ("NDARRAY", {}), + } + } + + RETURN_TYPES: tuple = ("TENSOR",) + FUNCTION: str = "f" + CATEGORY: str = "PyTorch wrapper" + + def f(self, array: np.ndarray) -> Tuple[torch.Tensor]: + """ + Converts a NumPy ndarray to a PyTorch tensor while preserving its data type. + + Args: + array (np.ndarray): NumPy ndarray. + + Returns: + tuple: A tuple containing the PyTorch tensor. + """ + tens = torch.tensor(array, dtype=torch.from_numpy(array).dtype) + return (tens,) diff --git a/modules/pytorch_wrapper/pt_int_create.py b/modules/pytorch_wrapper/pt_int_create.py new file mode 100644 index 0000000..6739ad0 --- /dev/null +++ b/modules/pytorch_wrapper/pt_int_create.py @@ -0,0 +1,47 @@ +from typing import Any, Dict +import torch +import ast + + +class PtIntCreate: + """ + Creates a PyTorch tensor with 32-bit integer + 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 = ("TENSOR",) + FUNCTION: str = "f" + CATEGORY: str = "PyTorch wrapper" + + def f(self, data: str) -> tuple: + """ + Creates a PyTorch tensor using values entered in the text field. + + Args: + data (str): String value in the text field. + + Returns: + tuple: A tuple containing the tensor. + """ + # Convert string to Python list + list_data = ast.literal_eval(data) + + # Convert list to PyTorch tensor + tensor = torch.tensor(list_data, dtype=torch.int32) + return (tensor,) diff --git a/modules/pytorch_wrapper/pt_to_numpy.py b/modules/pytorch_wrapper/pt_to_numpy.py new file mode 100644 index 0000000..f5ff4fc --- /dev/null +++ b/modules/pytorch_wrapper/pt_to_numpy.py @@ -0,0 +1,41 @@ +from typing import Any, Dict +import torch + + +class PtToNumpy: + """ + Converts PyTorch tensor 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": { + "tens": ("TENSOR", {}), + } + } + + RETURN_TYPES: tuple = ("NDARRAY",) + FUNCTION: str = "f" + CATEGORY: str = "PyTorch wrapper" + + def f(self, tens: torch.Tensor) -> tuple: + """ + Converts PyTorch tensor to NumPy ndarray. + + Args: + tens (torch.Tensor): PyTorch Tensor + + Returns: + tuple: A tuple containing the ndarray. + """ + array = tens.cpu().detach().numpy() + return (array,) diff --git a/pyproject.toml b/pyproject.toml index 4457378..de52c99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "data-analysis" description = "Data analysis custom modules for ComfyUI - Use Pandas & Matplotlib within ComfyUI" -version = "1.2.2" +version = "1.3.0" license = {file = "LICENSE"} [project.urls] diff --git a/tests/numpy_wrapper/__init__.py b/tests/numpy_wrapper/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/numpy_wrapper/numpy_float_create_test.py b/tests/numpy_wrapper/numpy_float_create_test.py new file mode 100644 index 0000000..15d69ce --- /dev/null +++ b/tests/numpy_wrapper/numpy_float_create_test.py @@ -0,0 +1,66 @@ +import os +import sys +import unittest +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 numpy_wrapper.numpy_float_create import NumpyFloatCreate + +class TestNumpyFloatCreate(unittest.TestCase): + def setUp(self): + """Set up test data.""" + self.node = NumpyFloatCreate() + + # Test cases covering rank 0 (scalar) to rank 5 tensors + self.test_cases = [ + # Rank 0 (scalar) + ("12345", np.array(12345, dtype=np.float32)), + ("12345.6", np.array(12345.6, dtype=np.float32)), + + # Rank 1 (1D array) + ("[1, 2, 3]", np.array([1, 2, 3], dtype=np.float32)), + ("[1.1, 2.2, 3.3]", np.array([1.1, 2.2, 3.3], dtype=np.float32)), + + # Rank 2 (2D matrix) + ("[[1, 2, 3], [4, 5, 6]]", np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32)), + ("[[1.1, 2.2], [3.3, 4.4]]", np.array([[1.1, 2.2], [3.3, 4.4]], dtype=np.float32)), + + # Rank 3 (3D tensor) + ("[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]", + np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], dtype=np.float32)), + + # Rank 4 (4D tensor) + ("[[[[1, 2], [3, 4]]], [[[5, 6], [7, 8]]]]", + np.array([[[[1, 2], [3, 4]]], [[[5, 6], [7, 8]]]], dtype=np.float32)), + + # Rank 5 (5D tensor) + ("[[[[[1], [2]], [[3], [4]]]], [[[[5], [6]], [[7], [8]]]]]", + np.array([[[[[1], [2]], [[3], [4]]]], [[[[5], [6]], [[7], [8]]]]], dtype=np.float32)), + ] + + def test_create_ndarray(self): + """Test NumPy ndarray creation from various structured inputs.""" + for data_str, expected_array in self.test_cases: + with self.subTest(data=data_str): + retval = self.node.f(data_str)[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 float32 + self.assertEqual(retval.dtype, np.float32, f"Dtype mismatch for input: {data_str}") + + # Ensure the shape matches + self.assertEqual(retval.shape, expected_array.shape, + f"Shape mismatch for input: {data_str} (Expected {expected_array.shape}, got {retval.shape})") + + # Ensure values match + np.testing.assert_array_equal(retval, expected_array, + f"Value mismatch for input: {data_str}") + +if __name__ == "__main__": + unittest.main() diff --git a/tests/numpy_wrapper/numpy_int_create_test.py b/tests/numpy_wrapper/numpy_int_create_test.py new file mode 100644 index 0000000..022d4eb --- /dev/null +++ b/tests/numpy_wrapper/numpy_int_create_test.py @@ -0,0 +1,63 @@ +import os +import sys +import unittest +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 numpy_wrapper.numpy_int_create import NumpyIntCreate + +class TestNumpyIntCreate(unittest.TestCase): + def setUp(self): + """Set up test data.""" + self.node = NumpyIntCreate() + + # Test cases covering rank 0 (scalar) to rank 5 tensors + self.test_cases = [ + # Rank 0 (scalar) + ("12345", np.array(12345, dtype=np.int32)), + + # Rank 1 (1D array) + ("[1, 2, 3]", np.array([1, 2, 3], dtype=np.int32)), + + # Rank 2 (2D matrix) + ("[[1, 2, 3], [4, 5, 6]]", np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)), + + # Rank 3 (3D tensor) + ("[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]", + np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], dtype=np.int32)), + + # Rank 4 (4D tensor) + ("[[[[1, 2], [3, 4]]], [[[5, 6], [7, 8]]]]", + np.array([[[[1, 2], [3, 4]]], [[[5, 6], [7, 8]]]], dtype=np.int32)), + + # Rank 5 (5D tensor) + ("[[[[[1], [2]], [[3], [4]]]], [[[[5], [6]], [[7], [8]]]]]", + np.array([[[[[1], [2]], [[3], [4]]]], [[[[5], [6]], [[7], [8]]]]], dtype=np.int32)), + ] + + def test_create_ndarray(self): + """Test NumPy int32 ndarray creation from various structured inputs.""" + for data_str, expected_array in self.test_cases: + with self.subTest(data=data_str): + retval = self.node.f(data_str)[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 int32 + self.assertEqual(retval.dtype, np.int32, f"Dtype mismatch for input: {data_str}") + + # Ensure the shape matches + self.assertEqual(retval.shape, expected_array.shape, + f"Shape mismatch for input: {data_str} (Expected {expected_array.shape}, got {retval.shape})") + + # Ensure values match + np.testing.assert_array_equal(retval, expected_array, + f"Value mismatch for input: {data_str}") + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pandas_wrapper/pandas_create_from_numpy_test.py b/tests/pandas_wrapper/pandas_create_from_numpy_test.py new file mode 100644 index 0000000..a4fa173 --- /dev/null +++ b/tests/pandas_wrapper/pandas_create_from_numpy_test.py @@ -0,0 +1,61 @@ +import os +import sys +import unittest +import numpy as np +import pandas as pd + +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 pandas_wrapper.pandas_create_from_numpy import PandasCreateFromNumpy + +class TestPandasCreateFromNumpy(unittest.TestCase): + def setUp(self): + """Set up test data.""" + self.node = PandasCreateFromNumpy() + + self.test_cases = [ + # Rank 0 + (np.array(42), ValueError), + (np.array(3.14), ValueError), + (np.array("hello"), ValueError), + (np.array(True), ValueError), + + # Rank 1 + (np.array([1, 2, 3]), pd.DataFrame([1, 2, 3])), + (np.array([1.1, 2.2, 3.3]), pd.DataFrame([1.1, 2.2, 3.3])), + (np.array(["a", "b", "c"]), pd.DataFrame(["a", "b", "c"])), + (np.array([True, False, True]), pd.DataFrame([True, False, True])), + + # Rank 2 + (np.array([[1, 2], [3, 4]]), pd.DataFrame([[1, 2], [3, 4]])), + (np.array([[1.1, 2.2], [3.3, 4.4]]), pd.DataFrame([[1.1, 2.2], [3.3, 4.4]])), + (np.array([["a", "b"], ["c", "d"]]), pd.DataFrame([["a", "b"], ["c", "d"]])), + (np.array([[True, False], [False, True]]), pd.DataFrame([[True, False], [False, True]])), + + # Rank 3 (Invalid) + (np.array([[[1, 2], [3, 4]]]), ValueError), + (np.array([[["x", "y"], ["z", "w"]]]), ValueError), + + # Rank 4 (Invalid) + (np.random.rand(2, 2, 2, 2), ValueError) + ] + + def test_create_dataframe(self): + """Test DataFrame creation from various NumPy arrays.""" + for data, expected in self.test_cases: + with self.subTest(data=data): + if isinstance(expected, type) and issubclass(expected, Exception): + with self.assertRaises(expected): + self.node.f(data) + else: + retval = self.node.f(data)[0] + + # Test if DataFrame structure matches + self.assertTrue(isinstance(retval, pd.DataFrame)) + + # Test if data matches + pd.testing.assert_frame_equal(retval, expected) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pandas_wrapper/pandas_create_to_numpy_test.py b/tests/pandas_wrapper/pandas_create_to_numpy_test.py new file mode 100644 index 0000000..f8e2a36 --- /dev/null +++ b/tests/pandas_wrapper/pandas_create_to_numpy_test.py @@ -0,0 +1,53 @@ +import os +import sys +import unittest +import numpy as np +import pandas as pd + +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 pandas_wrapper.pandas_to_numpy import PandasToNumpy + +class TestPandasToNumpy(unittest.TestCase): + def setUp(self): + """Set up test data.""" + self.node = PandasToNumpy() + + self.test_cases = [ + # Rank 1 + (pd.DataFrame([1, 2, 3]), np.array([[1], [2], [3]])), + (pd.DataFrame([1.1, 2.2, 3.3]), np.array([[1.1], [2.2], [3.3]])), + (pd.DataFrame(["a", "b", "c"]), np.array([["a"], ["b"], ["c"]], dtype=object)), + (pd.DataFrame([True, False, True]), np.array([[True], [False], [True]])), + + # Rank 2 + (pd.DataFrame([[1, 2], [3, 4]]), np.array([[1, 2], [3, 4]])), + (pd.DataFrame([[1.1, 2.2], [3.3, 4.4]]), np.array([[1.1, 2.2], [3.3, 4.4]])), + (pd.DataFrame([["a", "b"], ["c", "d"]]), np.array([["a", "b"], ["c", "d"]], dtype=object)), + (pd.DataFrame([[True, False], [False, True]]), np.array([[True, False], [False, True]])), + + # Invalid cases + (None, TypeError), + ("not a DataFrame", TypeError), + (np.array([[1, 2], [3, 4]]), TypeError) + ] + + def test_dataframe_to_numpy(self): + """Test NumPy array conversion from various Pandas DataFrames.""" + for dataframe, expected in self.test_cases: + with self.subTest(dataframe=dataframe): + if isinstance(expected, type) and issubclass(expected, Exception): + with self.assertRaises(expected): + self.node.f(dataframe) + else: + retval = self.node.f(dataframe)[0] + + # Ensure the return type is a NumPy array + self.assertTrue(isinstance(retval, np.ndarray)) + + # Check array equality + np.testing.assert_array_equal(retval, expected) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pytorch_wrapper/__init__.py b/tests/pytorch_wrapper/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/pytorch_wrapper/pt_float_create_test.py b/tests/pytorch_wrapper/pt_float_create_test.py new file mode 100644 index 0000000..f5acb08 --- /dev/null +++ b/tests/pytorch_wrapper/pt_float_create_test.py @@ -0,0 +1,65 @@ +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_float_create import PtFloatCreate + +class TestPtFloatCreate(unittest.TestCase): + def setUp(self): + """Set up test data.""" + self.node = PtFloatCreate() + + # Test cases covering rank 0 (scalar) to rank 5 tensors + self.test_cases = [ + # Rank 0 (scalar) + ("12345", torch.tensor(12345, dtype=torch.float32)), + ("12345.6", torch.tensor(12345.6, dtype=torch.float32)), + + # Rank 1 (1D array) + ("[1, 2, 3]", torch.tensor([1, 2, 3], dtype=torch.float32)), + ("[1.1, 2.2, 3.3]", torch.tensor([1.1, 2.2, 3.3], dtype=torch.float32)), + + # Rank 2 (2D matrix) + ("[[1, 2, 3], [4, 5, 6]]", torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float32)), + ("[[1.1, 2.2], [3.3, 4.4]]", torch.tensor([[1.1, 2.2], [3.3, 4.4]], dtype=torch.float32)), + + # Rank 3 (3D tensor) + ("[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]", + torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], dtype=torch.float32)), + + # Rank 4 (4D tensor) + ("[[[[1, 2], [3, 4]]], [[[5, 6], [7, 8]]]]", + torch.tensor([[[[1, 2], [3, 4]]], [[[5, 6], [7, 8]]]], dtype=torch.float32)), + + # Rank 5 (5D tensor) + ("[[[[[1], [2]], [[3], [4]]]], [[[[5], [6]], [[7], [8]]]]]", + torch.tensor([[[[[1], [2]], [[3], [4]]]], [[[[5], [6]], [[7], [8]]]]], dtype=torch.float32)), + ] + + def test_create_tensor(self): + """Test PyTorch tensor creation from various structured inputs.""" + for data_str, expected_tensor in self.test_cases: + with self.subTest(data=data_str): + retval = self.node.f(data_str)[0] + + # Ensure the returned value is a PyTorch tensor + self.assertTrue(isinstance(retval, torch.Tensor), f"Expected tensor, got {type(retval)}") + + # Ensure the dtype is float32 + self.assertEqual(retval.dtype, torch.float32, f"Dtype mismatch for input: {data_str}") + + # Ensure the shape matches + self.assertEqual(retval.shape, expected_tensor.shape, + f"Shape mismatch for input: {data_str} (Expected {expected_tensor.shape}, got {retval.shape})") + + # Ensure values match + torch.testing.assert_close(retval, expected_tensor, + msg=f"Value mismatch for input: {data_str}") + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pytorch_wrapper/pt_from_numpy_test.py b/tests/pytorch_wrapper/pt_from_numpy_test.py new file mode 100644 index 0000000..49e6642 --- /dev/null +++ b/tests/pytorch_wrapper/pt_from_numpy_test.py @@ -0,0 +1,65 @@ +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_from_numpy import PtFromNumpy + + +class TestPtFromNumpy(unittest.TestCase): + def setUp(self): + """Set up test data.""" + self.node = PtFromNumpy() + + # Test cases covering rank 0 (scalar) to rank 3 tensors + self.test_cases = [ + # Rank 0 (scalar) int32 + (np.array(12345, dtype=np.int32), torch.tensor(12345, dtype=torch.int32)), + # Rank 0 (scalar) float32 + (np.array(12345.6, dtype=np.float32), torch.tensor(12345.6, dtype=torch.float32)), + + # Rank 1 (1D array) int32 + (np.array([1, 2, 3], dtype=np.int32), torch.tensor([1, 2, 3], dtype=torch.int32)), + # Rank 1 (1D array) float32 + (np.array([1.1, 2.2, 3.3], dtype=np.float32), torch.tensor([1.1, 2.2, 3.3], dtype=torch.float32)), + + # Rank 2 (2D matrix) int32 + (np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32), torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.int32)), + # Rank 2 (2D matrix) float32 + (np.array([[1.1, 2.2], [3.3, 4.4]], dtype=np.float32), torch.tensor([[1.1, 2.2], [3.3, 4.4]], dtype=torch.float32)), + + # Rank 3 (3D tensor) int32 + (np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], dtype=np.int32), + torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], dtype=torch.int32)), + # Rank 3 (3D tensor) float32 + (np.array([[[1.1, 2.2], [3.3, 4.4]], [[5.5, 6.6], [7.7, 8.8]]], dtype=np.float32), + torch.tensor([[[1.1, 2.2], [3.3, 4.4]], [[5.5, 6.6], [7.7, 8.8]]], dtype=torch.float32)), + ] + + def test_convert_numpy_to_tensor(self): + """Test conversion of NumPy ndarray to PyTorch tensor.""" + for np_array, expected_tensor in self.test_cases: + with self.subTest(data=np_array): + retval = self.node.f(np_array)[0] + + # Ensure the returned value is a PyTorch tensor + self.assertTrue(isinstance(retval, torch.Tensor), f"Expected tensor, got {type(retval)}") + + # Ensure the dtype matches + self.assertEqual(retval.dtype, expected_tensor.dtype, f"Dtype mismatch for input: {np_array}") + + # Ensure the shape matches + self.assertEqual(retval.shape, expected_tensor.shape, + f"Shape mismatch for input: {np_array} (Expected {expected_tensor.shape}, got {retval.shape})") + + # Ensure values match + torch.testing.assert_close(retval, expected_tensor, + msg=f"Value mismatch for input: {np_array}") + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pytorch_wrapper/pt_int_create_test.py b/tests/pytorch_wrapper/pt_int_create_test.py new file mode 100644 index 0000000..d77df2e --- /dev/null +++ b/tests/pytorch_wrapper/pt_int_create_test.py @@ -0,0 +1,62 @@ +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_int_create import PtIntCreate + +class TestPtIntCreate(unittest.TestCase): + def setUp(self): + """Set up test data.""" + self.node = PtIntCreate() + + # Test cases covering rank 0 (scalar) to rank 5 tensors + self.test_cases = [ + # Rank 0 (scalar) + ("12345", torch.tensor(12345, dtype=torch.int32)), + + # Rank 1 (1D array) + ("[1, 2, 3]", torch.tensor([1, 2, 3], dtype=torch.int32)), + + # Rank 2 (2D matrix) + ("[[1, 2, 3], [4, 5, 6]]", torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.int32)), + + # Rank 3 (3D tensor) + ("[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]", + torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], dtype=torch.int32)), + + # Rank 4 (4D tensor) + ("[[[[1, 2], [3, 4]]], [[[5, 6], [7, 8]]]]", + torch.tensor([[[[1, 2], [3, 4]]], [[[5, 6], [7, 8]]]], dtype=torch.int32)), + + # Rank 5 (5D tensor) + ("[[[[[1], [2]], [[3], [4]]]], [[[[5], [6]], [[7], [8]]]]]", + torch.tensor([[[[[1], [2]], [[3], [4]]]], [[[[5], [6]], [[7], [8]]]]], dtype=torch.int32)), + ] + + def test_create_tensor(self): + """Test PyTorch tensor creation from various structured inputs.""" + for data_str, expected_tensor in self.test_cases: + with self.subTest(data=data_str): + retval = self.node.f(data_str)[0] + + # Ensure the returned value is a PyTorch tensor + self.assertTrue(isinstance(retval, torch.Tensor), f"Expected tensor, got {type(retval)}") + + # Ensure the dtype is int32 + self.assertEqual(retval.dtype, torch.int32, f"Dtype mismatch for input: {data_str}") + + # Ensure the shape matches + self.assertEqual(retval.shape, expected_tensor.shape, + f"Shape mismatch for input: {data_str} (Expected {expected_tensor.shape}, got {retval.shape})") + + # Ensure values match + torch.testing.assert_close(retval, expected_tensor, + msg=f"Value mismatch for input: {data_str}") + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pytorch_wrapper/pt_to_numpy_test.py b/tests/pytorch_wrapper/pt_to_numpy_test.py new file mode 100644 index 0000000..be55c92 --- /dev/null +++ b/tests/pytorch_wrapper/pt_to_numpy_test.py @@ -0,0 +1,64 @@ +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_to_numpy import PtToNumpy + +class TestPtToNumpy(unittest.TestCase): + def setUp(self): + """Set up test data.""" + self.node = PtToNumpy() + + # Test cases covering rank 0 (scalar) to rank 5 tensors + self.test_cases = [ + # Rank 0 (scalar) int32 + (torch.tensor(12345, dtype=torch.int32), np.array(12345, dtype=np.int32)), + # Rank 0 (scalar) float32 + (torch.tensor(12345.6, dtype=torch.float32), np.array(12345.6, dtype=np.float32)), + + # Rank 1 (1D array) int32 + (torch.tensor([1, 2, 3], dtype=torch.int32), np.array([1, 2, 3], dtype=np.int32)), + # Rank 1 (1D array) float32 + (torch.tensor([1.1, 2.2, 3.3], dtype=torch.float32), np.array([1.1, 2.2, 3.3], dtype=np.float32)), + + # Rank 2 (2D matrix) int32 + (torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.int32), np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)), + # Rank 2 (2D matrix) float32 + (torch.tensor([[1.1, 2.2], [3.3, 4.4]], dtype=torch.float32), np.array([[1.1, 2.2], [3.3, 4.4]], dtype=np.float32)), + + # Rank 3 (3D tensor) int32 + (torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], dtype=torch.int32), + np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], dtype=np.int32)), + # Rank 3 (3D tensor) float32 + (torch.tensor([[[1.1, 2.2], [3.3, 4.4]], [[5.5, 6.6], [7.7, 8.8]]], dtype=torch.float32), + np.array([[[1.1, 2.2], [3.3, 4.4]], [[5.5, 6.6], [7.7, 8.8]]], dtype=np.float32)), + ] + + def test_convert_tensor_to_numpy(self): + """Test conversion of PyTorch tensor to NumPy ndarray.""" + for torch_tensor, expected_array in self.test_cases: + with self.subTest(data=torch_tensor): + retval = self.node.f(torch_tensor)[0] + + # Ensure the returned value is a NumPy array + self.assertTrue(isinstance(retval, np.ndarray), f"Expected ndarray, got {type(retval)}") + + # Ensure the dtype matches + self.assertEqual(retval.dtype, expected_array.dtype, f"Dtype mismatch for input: {torch_tensor}") + + # Ensure the shape matches + self.assertEqual(retval.shape, expected_array.shape, + f"Shape mismatch for input: {torch_tensor} (Expected {expected_array.shape}, got {retval.shape})") + + # Ensure values match + np.testing.assert_array_equal(retval, expected_array, + f"Value mismatch for input: {torch_tensor}") + +if __name__ == "__main__": + unittest.main()