Added Ptv Transforms Resize node to resize image data. Added workflows and PyTorch model training tutorials.
This commit is contained in:
@@ -1,45 +1,10 @@
|
||||
# ComfyUI-Data-Analysis
|
||||
## Update (March 5, 2025)
|
||||
Training workflow trains the FashionMNIST model from scratch with a single click of a button.
|
||||
## Update (March 7, 2025)
|
||||
You can now use newly added nodes to train a machine learning model for image classification with your own image data **without any coding** from scratch.
|
||||
|
||||
Experimental PyTorch model training has been added (documentation coming soon).
|
||||
No coding required—training is as simple as a single click!
|
||||
|
||||
It is designed to be modular. For example, to switch from a linear model to a conv model, you can just replace the model node (see screenshot below).
|
||||
|
||||
Or you can just drag and drop below workflows to ComfyUI to check it out. Model is downloaded automatically:
|
||||
|
||||
Linear model
|
||||
* Training: examples/fashion_mnist_train.json
|
||||
* Evaluation: examples/fashion_mnist_eval.json
|
||||
|
||||
Convolutional model
|
||||
* Training: examples/fashion_mnist_train_conv.json
|
||||
* Evaluation: examples/fashion_mnist_eval_conv.json
|
||||
|
||||
These training workflows train a FashionMNIST model from scratch effortlessly.
|
||||
|
||||
### Training workflow
|
||||
**Linear model**
|
||||

|
||||
|
||||
**Conv model**
|
||||

|
||||
|
||||
*Close up of the conv node*
|
||||

|
||||
|
||||
### Eval workflow
|
||||
**Linear model**
|
||||

|
||||
|
||||
**Conv model**
|
||||

|
||||
|
||||
Note that the training feature is a proof of concept and currently supports only the linear and conv models. Additional models may be added based on demand.
|
||||
|
||||
<hr>
|
||||
Check out [the new tutorial to classify dogs and cats](docs/dog_cat_classification_model_training.md)
|
||||
|
||||
---
|
||||

|
||||
## How Can We Figure Out Which MLB Player Had the Most Hits Per Year?
|
||||
|
||||
|
||||
@@ -243,6 +243,7 @@ from .modules.pytorch_wrapper.pto_adam import PtoAdam
|
||||
from .modules.pytorch_wrapper.ptv_dataset import PtvDataset
|
||||
from .modules.pytorch_wrapper.ptv_dataset_loader import PtvDatasetLoader
|
||||
from .modules.pytorch_wrapper.ptv_image_folder_dataset import PtvImageFolderDataset
|
||||
from .modules.pytorch_wrapper.ptv_transforms_resize import PtvTransformsResize
|
||||
from .modules.pytorch_wrapper.ptv_transforms_to_tensor import PtvTransformsToTensor
|
||||
from .modules.seaborn_wrapper.sns_bar import SNSBar
|
||||
from .modules.seaborn_wrapper.sns_boxplot import SNSBoxplot
|
||||
@@ -494,6 +495,7 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = {
|
||||
"PtvDataset": PtvDataset,
|
||||
"PtvDatasetLoader": PtvDatasetLoader,
|
||||
"PtvImageFolderDataset": PtvImageFolderDataset,
|
||||
"PtvTransformsResize": PtvTransformsResize,
|
||||
"PtvTransformsToTensor": PtvTransformsToTensor,
|
||||
"PyDatetimeToString": PyDatetimeToString,
|
||||
"PyFloatToString": PyFloatToString,
|
||||
@@ -754,6 +756,7 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = {
|
||||
"PtvDataset": "Ptv Dataset",
|
||||
"PtvDatasetLoader": "Ptv Dataset Loader",
|
||||
"PtvImageFolderDataset": "Ptv Image Folder Dataset",
|
||||
"PtvTransformsResize": "Ptv Transforms Resize",
|
||||
"PtvTransformsToTensor": "Ptv Transforms To Tensor",
|
||||
"PyDatetimeToString": "Py Datetime To String",
|
||||
"PyFloatToString": "Py Float To String",
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
# the tests in the directory will be skipped.
|
||||
export PYTHONPATH=modules:$PYTHONPATH
|
||||
echo $PYTHONPATH
|
||||
python -m unittest discover -s tests -p "*_test.py" -t .
|
||||
RUN_SKIPPED_TESTS=0 python -m unittest discover -s tests -p "*_test.py" -t .
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
# Dog cat classification model training tutorial
|
||||
This tutorial covers how to use **ComfyUI Data Analysis** to train an image classification model with your own image dataset.
|
||||
We'll use a **dog and cat image dataset** created specifically for this tutorial, which is available publicly on [my GitHub repo](https://github.com/HowToSD/dog_and_cat_dataset).
|
||||
|
||||
Once you complete the tutorial, you can experiment with your own images.
|
||||
|
||||
**Disclaimer:** The model architecture is a traditional convolutional neural network (CNN) and is **not state-of-the-art**. This choice keeps the model small, enabling faster training and easier explanation.
|
||||
|
||||
---
|
||||
|
||||
## Overview of Steps
|
||||
|
||||
1. Downloading the dog and cat dataset from the GitHub repo
|
||||
2. Edit the training workflow
|
||||
3. Run the training workflow
|
||||
4. Edit the evaluation workflow
|
||||
5. Run the evaluation workflow
|
||||
|
||||
---
|
||||
|
||||
## 1. Download the Dog & Cat Dataset
|
||||
|
||||
Visit [the GitHub repo](https://github.com/HowToSD/dog_and_cat_dataset) and go to the **Releases** page.
|
||||
Click **"Source code (zip)"** to download the zip that contains all the images.
|
||||
Extract the ZIP file using an unarchiver.
|
||||
|
||||
The dataset follows this directory structure:
|
||||
|
||||
```
|
||||
dog_and_cat
|
||||
train
|
||||
cats
|
||||
dogs
|
||||
|
||||
val
|
||||
cats
|
||||
dogs
|
||||
```
|
||||
|
||||
|
||||
Take note of the **full paths** to the `train` and `val` directories, as you'll need them in the next step.
|
||||
|
||||
---
|
||||
|
||||
## 2. Editing the Training Workflow
|
||||
|
||||
Drag and drop the below file into ComfyUI:
|
||||
```
|
||||
examples/workflow/dog_cat_classification_train_model.json
|
||||
```
|
||||
Some nodes will be marked red, indicating they require edits.
|
||||
The only required changes are the **train** and **val** dataset paths.
|
||||
Replace them with the paths noted earlier.
|
||||
|
||||
---
|
||||
|
||||
## 3. Running the Training Workflow
|
||||
|
||||
Click **Queue** to start training.
|
||||
|
||||
Monitor the console for errors. If training runs smoothly, you should see output like this:
|
||||
|
||||
```
|
||||
Epoch (train) 1/20, Loss: 0.6865
|
||||
Epoch (val) 1/20, Loss: 0.6801
|
||||
Epoch (train) 2/20, Loss: 0.6216
|
||||
Epoch (val) 2/20, Loss: 0.5710
|
||||
Epoch (train) 3/20, Loss: 0.4998
|
||||
Epoch (val) 3/20, Loss: 0.4716
|
||||
Epoch (train) 4/20, Loss: 0.3827
|
||||
Epoch (val) 4/20, Loss: 0.4045
|
||||
Epoch (train) 5/20, Loss: 0.2897
|
||||
Epoch (val) 5/20, Loss: 0.3297
|
||||
Epoch (train) 6/20, Loss: 0.2243
|
||||
Epoch (val) 6/20, Loss: 0.2925
|
||||
Epoch (train) 7/20, Loss: 0.1603
|
||||
Epoch (val) 7/20, Loss: 0.2916
|
||||
Epoch (train) 8/20, Loss: 0.1102
|
||||
Epoch (val) 8/20, Loss: 0.3216
|
||||
Epoch (train) 9/20, Loss: 0.0916
|
||||
Epoch (val) 9/20, Loss: 0.2706
|
||||
Epoch (train) 10/20, Loss: 0.0522
|
||||
Epoch (val) 10/20, Loss: 0.2220
|
||||
Epoch (train) 11/20, Loss: 0.0481
|
||||
Epoch (val) 11/20, Loss: 0.2820
|
||||
Epoch (train) 12/20, Loss: 0.0228
|
||||
Epoch (val) 12/20, Loss: 0.2899
|
||||
Epoch (train) 13/20, Loss: 0.0130
|
||||
Epoch (val) 13/20, Loss: 0.3056
|
||||
Epoch (train) 14/20, Loss: 0.0137
|
||||
Epoch (val) 14/20, Loss: 0.3070
|
||||
Epoch (train) 15/20, Loss: 0.0200
|
||||
Epoch (val) 15/20, Loss: 0.2714
|
||||
Epoch (train) 16/20, Loss: 0.0313
|
||||
Epoch (val) 16/20, Loss: 0.3108
|
||||
Epoch (train) 17/20, Loss: 0.0125
|
||||
Epoch (val) 17/20, Loss: 0.3388
|
||||
Epoch (train) 18/20, Loss: 0.0174
|
||||
Epoch (val) 18/20, Loss: 0.2989
|
||||
Epoch (train) 19/20, Loss: 0.0017
|
||||
Epoch (val) 19/20, Loss: 0.3165
|
||||
Epoch (train) 20/20, Loss: 0.0009
|
||||
Epoch (val) 20/20, Loss: 0.3582
|
||||
Prompt executed in 253.64 seconds
|
||||
```
|
||||
|
||||
If you encounter an error:
|
||||
- Ensure dataset paths are correct.
|
||||
- Reduce **batch size** if your GPU runs out of memory (OOM error).
|
||||
|
||||
Once training completes, a **line chart** displaying training progress will appear at the bottom right of the canvas. You don’t need to interpret it initially.
|
||||
|
||||
---
|
||||
|
||||
## 4. Editing the Evaluation Workflow
|
||||
Now, let's verify if the model was trained correctly. In this step, we'll feed the validation data into the trained model and compute metrics to assess its performance.
|
||||
|
||||
Drag and drop the below file into ComfyUI:
|
||||
```
|
||||
examples/workflow/dog_cat_classification_eval_model.json
|
||||
```
|
||||
|
||||
into **ComfyUI**.
|
||||
|
||||
Update the **val dataset path**.
|
||||
|
||||
---
|
||||
|
||||
## 5. Running the Evaluation Workflow
|
||||
|
||||
Click **Queue**. The evaluation process runs much faster than training.
|
||||
|
||||
Check the **Accuracy** node—it should display a value around **0.9**, confirming successful model training.
|
||||
|
||||
Congratulations! You’ve successfully trained a machine learning model from scratch without writing a single line of code.
|
||||
|
||||
Now, you can use your own images to train the model in the same way.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Model Training
|
||||
|
||||
Experimental PyTorch model training has been added.
|
||||
No coding is required. Training is as simple as a single click!
|
||||
Currently, only classification is supported.
|
||||
|
||||
The system is designed to be modular. For example, to switch from a linear model to a convolutional model, you only need to replace the model node (see screenshot below).
|
||||
|
||||
You can choose to:
|
||||
* Use your own images
|
||||
* Use a public dataset
|
||||
|
||||
## Using Your Own Images
|
||||
|
||||
Check out [the dog and cat classification tutorial](dog_cat_classification_model_training.md).
|
||||
|
||||
## Public Datasets
|
||||
|
||||
Currently, only Fashion MNIST has been tested. Other public datasets have not been verified yet, though they may work.
|
||||
If you try one and it doesn't work, please create an issue so I can investigate adding support.
|
||||
|
||||
To use the Fashion MNIST dataset, simply drag and drop the workflow files below into ComfyUI. The model will be downloaded automatically.
|
||||
|
||||
Linear model
|
||||
* Training: examples/fashion_mnist_train.json
|
||||
* Evaluation: examples/fashion_mnist_eval.json
|
||||
|
||||
Convolutional model
|
||||
* Training: examples/fashion_mnist_train_conv.json
|
||||
* Evaluation: examples/fashion_mnist_eval_conv.json
|
||||
|
||||
These training workflows train a FashionMNIST model from scratch effortlessly.
|
||||
|
||||
### Training workflow
|
||||
**Linear model**
|
||||

|
||||
|
||||
**Conv model**
|
||||

|
||||
|
||||
*Close up of the conv node*
|
||||

|
||||
|
||||
### Eval workflow
|
||||
**Linear model**
|
||||

|
||||
|
||||
**Conv model**
|
||||

|
||||
@@ -276,6 +276,7 @@
|
||||
| [Ptv Dataset](ptv_dataset.md) | A Torchvision Dataset class wrapper. |
|
||||
| [Ptv Dataset Loader](ptv_dataset_loader.md) | A node to combine the dataset and data loader into a single node. |
|
||||
| [Ptv Image Folder Dataset](ptv_image_folder_dataset.md) | A Torchvision ImageFolder Dataset class wrapper. |
|
||||
| [Ptv Transforms Resize](ptv_transforms_resize.md) | Resizes and transforms elements of dataset to PyTorch tensors. |
|
||||
| [Ptv Transforms To Tensor](ptv_transforms_to_tensor.md) | Transforms elements of dataset to PyTorch tensors. |
|
||||
## PyTorch wrapper - Transform
|
||||
| Node | Description |
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Ptv Transforms Resize
|
||||
Resizes and transforms elements of dataset to PyTorch tensors.
|
||||
|
||||
## Input
|
||||
| Name | Data type |
|
||||
|---|---|
|
||||
| height | Int |
|
||||
| width | Int |
|
||||
|
||||
## Output
|
||||
| Data type |
|
||||
|---|
|
||||
| Ptvtransform |
|
||||
|
||||
<HR>
|
||||
Category: PyTorch wrapper - Training
|
||||
|
||||
ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,48 @@
|
||||
from typing import Any, Dict
|
||||
import torchvision.transforms as transforms
|
||||
|
||||
|
||||
class PtvTransformsResize:
|
||||
"""
|
||||
Resizes and transforms elements of dataset to PyTorch tensors.
|
||||
|
||||
category: PyTorch wrapper - Training
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> Dict[str, Any]:
|
||||
"""
|
||||
Defines the required input types.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: An empty dictionary.
|
||||
"""
|
||||
return {
|
||||
"required": {
|
||||
"height": ("INT", {"default": 256, "min": 1, "max": 32768}),
|
||||
"width": ("INT", {"default": 256, "min": 1, "max": 32768})
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES: tuple = ("PTVTRANSFORM",)
|
||||
FUNCTION: str = "f"
|
||||
CATEGORY: str = "Training"
|
||||
|
||||
def f(self, height: int, width: int) -> tuple:
|
||||
"""
|
||||
Resizes and transforms elements of dataset to PyTorch tensors.
|
||||
|
||||
Args:
|
||||
height (int): Target height of the input image after transform
|
||||
width (int): Target width of the inpt image after transform
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing the transform callable.
|
||||
"""
|
||||
c = transforms.Compose([
|
||||
transforms.Resize((height, width)),
|
||||
transforms.ToTensor()
|
||||
])
|
||||
return(c,)
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "data-analysis"
|
||||
description = "Data analysis custom modules for ComfyUI – Integrates Pandas, Seaborn and Matplotlib into ComfyUI, featuring extensive PyTorch wrapper nodes for seamless tensor operations. Experimental PyTorch model training was added in v2.0."
|
||||
version = "2.0.7"
|
||||
version = "2.0.8"
|
||||
license = {file = "LICENSE"}
|
||||
|
||||
[project.urls]
|
||||
|
||||
@@ -9,17 +9,19 @@ MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules")
|
||||
sys.path.append(MODULE_ROOT)
|
||||
from pytorch_wrapper.ptn_conv_model import PtnConvModel
|
||||
from pytorch_wrapper.ptv_image_folder_dataset import PtvImageFolderDataset
|
||||
from pytorch_wrapper.ptv_transforms_to_tensor import PtvTransformsToTensor
|
||||
from pytorch_wrapper.ptv_transforms_resize import PtvTransformsResize
|
||||
from pytorch_wrapper.pt_data_loader import PtDataLoader
|
||||
from pytorch_wrapper.pt_evaluate_classification_model import PtEvaluateClassificationModel
|
||||
from pytorch_wrapper.pt_load_model import PtLoadModel
|
||||
|
||||
class TestPtEvaluate(unittest.TestCase):
|
||||
|
||||
@unittest.skipIf(os.getenv("RUN_SKIPPED_TESTS") != "1", "Skipping unless explicitly enabled")
|
||||
def setUp(self):
|
||||
"""Set up test instance."""
|
||||
# Transform
|
||||
compose_node = PtvTransformsToTensor()
|
||||
transform = compose_node.f()[0]
|
||||
compose_node = PtvTransformsResize()
|
||||
transform = compose_node.f(256, 256)[0]
|
||||
|
||||
# Dataset
|
||||
self.node = PtvImageFolderDataset()
|
||||
@@ -38,10 +40,10 @@ class TestPtEvaluate(unittest.TestCase):
|
||||
# Model
|
||||
self.model_node = PtnConvModel()
|
||||
self.model = self.model_node.f(
|
||||
input_dim="(3, 512, 512)",
|
||||
input_dim="(3, 256, 256)",
|
||||
penultimate_dim=0,
|
||||
output_dim=2,
|
||||
channel_list="[32,64,128,256,512]", # To 256, 128, 64, 32, 16
|
||||
channel_list="[32,64,128,256,512]", # To 128, 64, 32, 16, 8
|
||||
kernel_size_list="[3,3,3,3,3]",
|
||||
padding_list="[1,1,1,1,1]",
|
||||
downsample_list="[True,True,True,True,True]"
|
||||
@@ -49,12 +51,13 @@ class TestPtEvaluate(unittest.TestCase):
|
||||
|
||||
# Load weights
|
||||
self.load_model = PtLoadModel()
|
||||
epochs = 7
|
||||
epochs = 20
|
||||
self.loaded_model = self.load_model.f(self.model,f"dog_cat_{epochs}_epochs_conv.pt")
|
||||
|
||||
# Instantiates evaluation node
|
||||
self.evaluate = PtEvaluateClassificationModel()
|
||||
|
||||
@unittest.skipIf(os.getenv("RUN_SKIPPED_TESTS") != "1", "Skipping unless explicitly enabled")
|
||||
def test_evaluation(self):
|
||||
"""
|
||||
Tests evaluation.
|
||||
|
||||
@@ -11,23 +11,34 @@ sys.path.append(MODULE_ROOT)
|
||||
from pytorch_wrapper.ptn_conv_model import PtnConvModel
|
||||
from pytorch_wrapper.pto_adam import PtoAdam
|
||||
from pytorch_wrapper.ptv_image_folder_dataset import PtvImageFolderDataset
|
||||
from pytorch_wrapper.ptv_transforms_to_tensor import PtvTransformsToTensor
|
||||
from pytorch_wrapper.ptv_transforms_resize import PtvTransformsResize
|
||||
from pytorch_wrapper.pt_data_loader import PtDataLoader
|
||||
from pytorch_wrapper.pt_train_classification_model import PtTrainClassificationModel
|
||||
from pytorch_wrapper.pt_save_model import PtSaveModel
|
||||
|
||||
|
||||
class TestPtTrainConvModel2(unittest.TestCase):
|
||||
|
||||
@unittest.skipIf(os.getenv("RUN_SKIPPED_TESTS") != "1", "Skipping unless explicitly enabled")
|
||||
def setUp(self):
|
||||
"""Set up test instance."""
|
||||
compose_node = PtvTransformsToTensor()
|
||||
transform = compose_node.f()[0]
|
||||
compose_node = PtvTransformsResize()
|
||||
transform = compose_node.f(256, 256)[0]
|
||||
self.node = PtvImageFolderDataset()
|
||||
dataset = self.node.f("dog_and_cat/train",
|
||||
train_dataset = self.node.f("dog_and_cat/train",
|
||||
transform=transform)[0]
|
||||
data_loader_node = PtDataLoader()
|
||||
self.train_loader = data_loader_node.f(
|
||||
dataset=dataset,
|
||||
train_data_loader_node = PtDataLoader()
|
||||
val_dataset = self.node.f("dog_and_cat/val",
|
||||
transform=transform)[0]
|
||||
val_data_loader_node = PtDataLoader()
|
||||
self.train_loader = train_data_loader_node.f(
|
||||
dataset=train_dataset,
|
||||
batch_size=32,
|
||||
shuffle=True,
|
||||
parameters='{"num_workers":1}'
|
||||
)[0]
|
||||
self.val_loader = val_data_loader_node.f(
|
||||
dataset=val_dataset,
|
||||
batch_size=32,
|
||||
shuffle=True,
|
||||
parameters='{"num_workers":1}'
|
||||
@@ -35,10 +46,10 @@ class TestPtTrainConvModel2(unittest.TestCase):
|
||||
|
||||
self.model_node = PtnConvModel()
|
||||
self.model = self.model_node.f(
|
||||
input_dim="(3, 512, 512)",
|
||||
input_dim="(3, 256, 256)",
|
||||
penultimate_dim=0,
|
||||
output_dim=2,
|
||||
channel_list="[32,64,128,256,512]", # To 256, 128, 64, 32, 16
|
||||
channel_list="[32,64,128,256,512]", # To 128, 64, 32, 16, 8
|
||||
kernel_size_list="[3,3,3,3,3]",
|
||||
padding_list="[1,1,1,1,1]",
|
||||
downsample_list="[True,True,True,True,True]"
|
||||
@@ -48,11 +59,13 @@ class TestPtTrainConvModel2(unittest.TestCase):
|
||||
self.trainer = PtTrainClassificationModel()
|
||||
self.save_model = PtSaveModel()
|
||||
|
||||
@unittest.skipIf(os.getenv("RUN_SKIPPED_TESTS") != "1", "Skipping unless explicitly enabled")
|
||||
def test_1(self):
|
||||
epochs = 7
|
||||
epochs = 20
|
||||
trained_model = self.trainer.f(self.model, self.train_loader, self.optimizer,
|
||||
epochs, # epochs
|
||||
use_gpu=True)[0]
|
||||
use_gpu=True,
|
||||
val_loader=self.val_loader)[0]
|
||||
self.save_model.f(trained_model, f"dog_cat_{epochs}_epochs_conv.pt")
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import torch
|
||||
from PIL import Image
|
||||
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.ptv_transforms_resize import PtvTransformsResize
|
||||
|
||||
|
||||
class TestPtvTransformsResize(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test data."""
|
||||
self.compose_node = PtvTransformsResize()
|
||||
self.height = 128
|
||||
self.width = 128
|
||||
|
||||
def test_with_transform(self):
|
||||
"""Tests the transformation output."""
|
||||
transform = self.compose_node.f(self.height, self.width)[0]
|
||||
self.assertTrue(callable(transform), "Transform should be callable.")
|
||||
|
||||
# Create a dummy image
|
||||
img = Image.fromarray(np.random.randint(0, 256, (256, 256, 3), dtype=np.uint8))
|
||||
transformed_img = transform(img)
|
||||
|
||||
# Check output type
|
||||
self.assertIsInstance(transformed_img, torch.Tensor, "Output should be a PyTorch tensor.")
|
||||
|
||||
# Check output shape
|
||||
self.assertEqual(transformed_img.shape, (3, self.height, self.width), "Output shape should match target dimensions.")
|
||||
|
||||
def test_default_parameters(self):
|
||||
"""Tests default transform parameters."""
|
||||
transform = self.compose_node.f(256, 256)[0]
|
||||
self.assertTrue(callable(transform), "Default transform should be callable.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user