From 60692bf9c2f25733edc8057da40d0d68aeac4df4 Mon Sep 17 00:00:00 2001 From: HowToSD Date: Mon, 10 Mar 2025 15:54:51 -0700 Subject: [PATCH] Removed PyTorch wrapper nodes. They were moved to ComfyUI-Py-Wrapper extension. --- README.md | 15 +- __init__.py | 318 ------------------ docs/dog_cat_classification_model_training.md | 137 -------- docs/images/conv_eval.png | Bin 180926 -> 0 bytes docs/images/conv_train.png | Bin 198292 -> 0 bytes docs/images/conv_train2.png | Bin 70717 -> 0 bytes docs/images/fashion_mnist_eval.png | Bin 121822 -> 0 bytes docs/images/fashion_mnist_train.png | Bin 169022 -> 0 bytes docs/model_training.md | 49 --- docs/reference/node_reference.md | 148 -------- docs/reference/pt_abs.md | 17 - docs/reference/pt_acos.md | 17 - docs/reference/pt_add.md | 18 - docs/reference/pt_arange.md | 23 -- docs/reference/pt_argmax.md | 24 -- docs/reference/pt_argmin.md | 24 -- docs/reference/pt_asin.md | 17 - docs/reference/pt_atan.md | 17 - docs/reference/pt_bitwise_and.md | 18 - docs/reference/pt_bitwise_left_shift.md | 18 - docs/reference/pt_bitwise_not.md | 17 - docs/reference/pt_bitwise_or.md | 18 - docs/reference/pt_bitwise_right_shift.md | 18 - docs/reference/pt_bitwise_xor.md | 18 - docs/reference/pt_bmm.md | 18 - docs/reference/pt_bool_create.md | 22 -- docs/reference/pt_cos.md | 17 - docs/reference/pt_cosh.md | 17 - docs/reference/pt_crop.md | 23 -- docs/reference/pt_data_loader.md | 29 -- docs/reference/pt_div.md | 18 - docs/reference/pt_einsum.md | 26 -- docs/reference/pt_eq.md | 18 - .../pt_evaluate_classification_model.md | 23 -- docs/reference/pt_exp.md | 17 - docs/reference/pt_flatten.md | 17 - docs/reference/pt_float_create.md | 18 - docs/reference/pt_floor_divide.md | 18 - docs/reference/pt_from_image.md | 17 - docs/reference/pt_from_image_transpose.md | 17 - docs/reference/pt_from_latent.md | 17 - docs/reference/pt_from_numpy.md | 17 - docs/reference/pt_full.md | 21 -- docs/reference/pt_gather.md | 59 ---- docs/reference/pt_ge.md | 18 - docs/reference/pt_gt.md | 18 - docs/reference/pt_index_select.md | 41 --- docs/reference/pt_int_create.md | 18 - .../pt_interpolate_by_scale_factor.md | 23 -- docs/reference/pt_interpolate_to_size.md | 24 -- docs/reference/pt_le.md | 18 - docs/reference/pt_linspace.md | 23 -- docs/reference/pt_load_model.md | 18 - docs/reference/pt_log.md | 17 - docs/reference/pt_logical_and.md | 18 - docs/reference/pt_logical_not.md | 17 - docs/reference/pt_logical_or.md | 18 - docs/reference/pt_logical_xor.md | 18 - docs/reference/pt_lt.md | 18 - docs/reference/pt_masked_select.md | 34 -- docs/reference/pt_matmul.md | 18 - docs/reference/pt_max.md | 24 -- docs/reference/pt_mean.md | 26 -- docs/reference/pt_median.md | 29 -- docs/reference/pt_min.md | 24 -- docs/reference/pt_mm.md | 18 - docs/reference/pt_mul.md | 18 - docs/reference/pt_ne.md | 18 - docs/reference/pt_neg.md | 17 - docs/reference/pt_ones.md | 20 -- docs/reference/pt_pad.md | 23 -- docs/reference/pt_permute.md | 25 -- docs/reference/pt_pow.md | 18 - .../pt_predict_classification_model.md | 22 -- docs/reference/pt_prod.md | 28 -- docs/reference/pt_rand.md | 21 -- docs/reference/pt_rand_int.md | 22 -- docs/reference/pt_randn.md | 21 -- docs/reference/pt_remainder.md | 18 - docs/reference/pt_reshape.md | 27 -- docs/reference/pt_save_model.md | 18 - docs/reference/pt_scatter.md | 24 -- docs/reference/pt_show_size.md | 17 - docs/reference/pt_show_text.md | 18 - docs/reference/pt_sin.md | 17 - docs/reference/pt_sinh.md | 17 - docs/reference/pt_size.md | 17 - docs/reference/pt_size_create.md | 17 - docs/reference/pt_size_to_numpy.md | 17 - docs/reference/pt_size_to_string.md | 17 - docs/reference/pt_sqrt.md | 17 - docs/reference/pt_squeeze.md | 20 -- docs/reference/pt_std.md | 28 -- docs/reference/pt_sub.md | 18 - docs/reference/pt_sum.md | 24 -- docs/reference/pt_tan.md | 17 - docs/reference/pt_tanh.md | 17 - docs/reference/pt_to_image.md | 17 - docs/reference/pt_to_image_transpose.md | 17 - docs/reference/pt_to_latent.md | 17 - docs/reference/pt_to_numpy.md | 17 - docs/reference/pt_to_rgb_tensors.md | 19 -- .../pt_train_classification_model.md | 24 -- docs/reference/pt_unsqueeze.md | 19 -- docs/reference/pt_var.md | 28 -- docs/reference/pt_view.md | 27 -- docs/reference/pt_where.md | 23 -- docs/reference/pt_zeros.md | 20 -- docs/reference/ptn_conv_model.md | 45 --- docs/reference/ptn_linear_model.md | 35 -- docs/reference/pto_adam.md | 20 -- docs/reference/ptv_dataset.md | 51 --- docs/reference/ptv_dataset_loader.md | 60 ---- docs/reference/ptv_image_folder_dataset.md | 28 -- docs/reference/ptv_transforms_resize.md | 18 - docs/reference/ptv_transforms_to_tensor.md | 12 - .../dog_cat_classification_eval_model.json | 1 - .../dog_cat_classification_prediction.json | 1 - .../dog_cat_classification_train_model.json | 1 - examples/workflows/fashion_mnist_eval.json | 1 - .../workflows/fashion_mnist_eval_conv.json | 1 - examples/workflows/fashion_mnist_train.json | 1 - .../workflows/fashion_mnist_train_conv.json | 1 - modules/pytorch_wrapper/pt_abs.py | 39 --- modules/pytorch_wrapper/pt_acos.py | 39 --- modules/pytorch_wrapper/pt_add.py | 41 --- modules/pytorch_wrapper/pt_arange.py | 62 ---- modules/pytorch_wrapper/pt_argmax.py | 59 ---- modules/pytorch_wrapper/pt_argmin.py | 59 ---- modules/pytorch_wrapper/pt_asin.py | 39 --- modules/pytorch_wrapper/pt_atan.py | 39 --- modules/pytorch_wrapper/pt_bitwise_and.py | 41 --- .../pytorch_wrapper/pt_bitwise_left_shift.py | 42 --- modules/pytorch_wrapper/pt_bitwise_not.py | 39 --- modules/pytorch_wrapper/pt_bitwise_or.py | 41 --- .../pytorch_wrapper/pt_bitwise_right_shift.py | 42 --- modules/pytorch_wrapper/pt_bitwise_xor.py | 41 --- modules/pytorch_wrapper/pt_bmm.py | 44 --- modules/pytorch_wrapper/pt_bool_create.py | 51 --- modules/pytorch_wrapper/pt_cos.py | 39 --- modules/pytorch_wrapper/pt_cosh.py | 39 --- modules/pytorch_wrapper/pt_crop.py | 65 ---- modules/pytorch_wrapper/pt_data_loader.py | 81 ----- modules/pytorch_wrapper/pt_div.py | 41 --- modules/pytorch_wrapper/pt_einsum.py | 67 ---- modules/pytorch_wrapper/pt_eq.py | 41 --- .../pt_evaluate_classification_model.py | 71 ---- modules/pytorch_wrapper/pt_exp.py | 39 --- modules/pytorch_wrapper/pt_flatten.py | 40 --- modules/pytorch_wrapper/pt_float_create.py | 47 --- modules/pytorch_wrapper/pt_floor_divide.py | 41 --- modules/pytorch_wrapper/pt_from_image.py | 40 --- .../pt_from_image_transpose.py | 47 --- modules/pytorch_wrapper/pt_from_latent.py | 40 --- modules/pytorch_wrapper/pt_from_numpy.py | 42 --- modules/pytorch_wrapper/pt_full.py | 66 ---- modules/pytorch_wrapper/pt_gather.py | 94 ------ modules/pytorch_wrapper/pt_ge.py | 41 --- modules/pytorch_wrapper/pt_gt.py | 41 --- modules/pytorch_wrapper/pt_index_select.py | 78 ----- modules/pytorch_wrapper/pt_int_create.py | 47 --- .../pt_interpolate_by_scale_factor.py | 56 --- .../pytorch_wrapper/pt_interpolate_to_size.py | 60 ---- modules/pytorch_wrapper/pt_le.py | 41 --- modules/pytorch_wrapper/pt_linspace.py | 62 ---- modules/pytorch_wrapper/pt_load_model.py | 41 --- modules/pytorch_wrapper/pt_log.py | 39 --- modules/pytorch_wrapper/pt_logical_and.py | 41 --- modules/pytorch_wrapper/pt_logical_not.py | 39 --- modules/pytorch_wrapper/pt_logical_or.py | 41 --- modules/pytorch_wrapper/pt_logical_xor.py | 41 --- modules/pytorch_wrapper/pt_lt.py | 41 --- modules/pytorch_wrapper/pt_masked_select.py | 62 ---- modules/pytorch_wrapper/pt_matmul.py | 41 --- modules/pytorch_wrapper/pt_max.py | 59 ---- modules/pytorch_wrapper/pt_mean.py | 61 ---- modules/pytorch_wrapper/pt_median.py | 66 ---- modules/pytorch_wrapper/pt_min.py | 59 ---- modules/pytorch_wrapper/pt_mm.py | 44 --- modules/pytorch_wrapper/pt_mul.py | 41 --- modules/pytorch_wrapper/pt_ne.py | 41 --- modules/pytorch_wrapper/pt_neg.py | 39 --- modules/pytorch_wrapper/pt_ones.py | 53 --- modules/pytorch_wrapper/pt_pad.py | 72 ---- modules/pytorch_wrapper/pt_permute.py | 60 ---- modules/pytorch_wrapper/pt_pow.py | 41 --- .../pt_predict_classification_model.py | 66 ---- modules/pytorch_wrapper/pt_prod.py | 65 ---- modules/pytorch_wrapper/pt_rand.py | 57 ---- modules/pytorch_wrapper/pt_rand_int.py | 72 ---- modules/pytorch_wrapper/pt_randn.py | 57 ---- modules/pytorch_wrapper/pt_remainder.py | 41 --- modules/pytorch_wrapper/pt_reshape.py | 62 ---- modules/pytorch_wrapper/pt_save_model.py | 36 -- modules/pytorch_wrapper/pt_scatter.py | 69 ---- modules/pytorch_wrapper/pt_show_size.py | 60 ---- modules/pytorch_wrapper/pt_show_text.py | 62 ---- modules/pytorch_wrapper/pt_sin.py | 39 --- modules/pytorch_wrapper/pt_sinh.py | 39 --- modules/pytorch_wrapper/pt_size.py | 41 --- modules/pytorch_wrapper/pt_size_create.py | 46 --- modules/pytorch_wrapper/pt_size_to_numpy.py | 41 --- modules/pytorch_wrapper/pt_size_to_string.py | 40 --- modules/pytorch_wrapper/pt_sqrt.py | 39 --- modules/pytorch_wrapper/pt_squeeze.py | 45 --- modules/pytorch_wrapper/pt_std.py | 68 ---- modules/pytorch_wrapper/pt_sub.py | 41 --- modules/pytorch_wrapper/pt_sum.py | 59 ---- modules/pytorch_wrapper/pt_tan.py | 39 --- modules/pytorch_wrapper/pt_tanh.py | 39 --- modules/pytorch_wrapper/pt_to_image.py | 41 --- .../pytorch_wrapper/pt_to_image_transpose.py | 47 --- modules/pytorch_wrapper/pt_to_latent.py | 40 --- modules/pytorch_wrapper/pt_to_numpy.py | 41 --- modules/pytorch_wrapper/pt_to_rgb_tensors.py | 50 --- .../pt_train_classification_model.py | 122 ------- modules/pytorch_wrapper/pt_unsqueeze.py | 44 --- modules/pytorch_wrapper/pt_var.py | 68 ---- modules/pytorch_wrapper/pt_view.py | 62 ---- modules/pytorch_wrapper/pt_where.py | 61 ---- modules/pytorch_wrapper/pt_zeros.py | 52 --- modules/pytorch_wrapper/ptn_conv_model.py | 99 ------ modules/pytorch_wrapper/ptn_conv_model_def.py | 151 --------- modules/pytorch_wrapper/ptn_linear_model.py | 87 ----- .../pytorch_wrapper/ptn_linear_model_def.py | 99 ------ modules/pytorch_wrapper/pto_adam.py | 46 --- modules/pytorch_wrapper/ptv_dataset.py | 124 ------- modules/pytorch_wrapper/ptv_dataset_loader.py | 148 -------- .../ptv_image_folder_dataset.py | 73 ---- .../pytorch_wrapper/ptv_transforms_resize.py | 48 --- .../ptv_transforms_to_tensor.py | 37 -- modules/pytorch_wrapper/utils.py | 106 ------ pyproject.toml | 2 +- tests/pytorch_wrapper/__init__.py | 0 tests/pytorch_wrapper/pt_abs_test.py | 40 --- tests/pytorch_wrapper/pt_acos_test.py | 39 --- tests/pytorch_wrapper/pt_add_test.py | 39 --- tests/pytorch_wrapper/pt_arange_test.py | 44 --- tests/pytorch_wrapper/pt_argmax_test.py | 39 --- tests/pytorch_wrapper/pt_argmin_test.py | 39 --- tests/pytorch_wrapper/pt_asin_test.py | 39 --- tests/pytorch_wrapper/pt_atan_test.py | 39 --- tests/pytorch_wrapper/pt_bitwise_and_test.py | 40 --- .../pt_bitwise_left_shift_test.py | 40 --- tests/pytorch_wrapper/pt_bitwise_not_test.py | 40 --- tests/pytorch_wrapper/pt_bitwise_or_test.py | 54 --- .../pt_bitwise_right_shift_test.py | 40 --- tests/pytorch_wrapper/pt_bitwise_xor_test.py | 54 --- tests/pytorch_wrapper/pt_bmm_test.py | 60 ---- tests/pytorch_wrapper/pt_bool_create_test.py | 58 ---- tests/pytorch_wrapper/pt_cos_test.py | 39 --- tests/pytorch_wrapper/pt_cosh_test.py | 39 --- tests/pytorch_wrapper/pt_crop_test.py | 49 --- tests/pytorch_wrapper/pt_data_loader_test.py | 43 --- tests/pytorch_wrapper/pt_div_test.py | 39 --- tests/pytorch_wrapper/pt_einsum_test.py | 99 ------ tests/pytorch_wrapper/pt_eq_test.py | 40 --- ...evaluate_classification_conv_model_test.py | 83 ----- ...valuate_classification_conv_model_test2.py | 83 ----- .../pt_evaluate_classification_model_test.py | 79 ----- tests/pytorch_wrapper/pt_exp_test.py | 39 --- tests/pytorch_wrapper/pt_flatten_test.py | 39 --- tests/pytorch_wrapper/pt_float_create_test.py | 65 ---- tests/pytorch_wrapper/pt_floor_divide_test.py | 39 --- .../pt_from_image_transpose_test.py | 57 ---- tests/pytorch_wrapper/pt_from_numpy_test.py | 65 ---- tests/pytorch_wrapper/pt_full_test.py | 71 ---- tests/pytorch_wrapper/pt_gather_test.py | 48 --- tests/pytorch_wrapper/pt_ge_test.py | 40 --- tests/pytorch_wrapper/pt_gt_test.py | 40 --- tests/pytorch_wrapper/pt_index_select_test.py | 48 --- tests/pytorch_wrapper/pt_int_create_test.py | 62 ---- .../pt_interpolate_by_scale_factor_test.py | 60 ---- .../pt_interpolate_to_size_test.py | 61 ---- tests/pytorch_wrapper/pt_le_test.py | 40 --- tests/pytorch_wrapper/pt_linspace_test.py | 43 --- tests/pytorch_wrapper/pt_log_test.py | 39 --- tests/pytorch_wrapper/pt_logical_and_test.py | 40 --- tests/pytorch_wrapper/pt_logical_not_test.py | 40 --- tests/pytorch_wrapper/pt_logical_or_test.py | 40 --- tests/pytorch_wrapper/pt_logical_xor_test.py | 40 --- tests/pytorch_wrapper/pt_lt_test.py | 40 --- .../pytorch_wrapper/pt_masked_select_test.py | 40 --- tests/pytorch_wrapper/pt_matmul_test.py | 63 ---- tests/pytorch_wrapper/pt_max_test.py | 61 ---- tests/pytorch_wrapper/pt_mean_test.py | 87 ----- tests/pytorch_wrapper/pt_median_test.py | 66 ---- tests/pytorch_wrapper/pt_min_test.py | 61 ---- tests/pytorch_wrapper/pt_mm_test.py | 58 ---- tests/pytorch_wrapper/pt_mul_test.py | 39 --- tests/pytorch_wrapper/pt_ne_test.py | 40 --- tests/pytorch_wrapper/pt_neg_test.py | 40 --- tests/pytorch_wrapper/pt_ones_test.py | 44 --- tests/pytorch_wrapper/pt_pad_test.py | 50 --- tests/pytorch_wrapper/pt_permute_test.py | 59 ---- tests/pytorch_wrapper/pt_pow_test.py | 39 --- ..._predict_classification_conv_model_test.py | 68 ---- tests/pytorch_wrapper/pt_prod_test.py | 59 ---- tests/pytorch_wrapper/pt_rand_int_test.py | 40 --- tests/pytorch_wrapper/pt_rand_test.py | 46 --- tests/pytorch_wrapper/pt_randn_test.py | 61 ---- tests/pytorch_wrapper/pt_remainder_test.py | 39 --- tests/pytorch_wrapper/pt_reshape_test.py | 60 ---- tests/pytorch_wrapper/pt_scatter_test.py | 51 --- tests/pytorch_wrapper/pt_sin_test.py | 39 --- tests/pytorch_wrapper/pt_sinh_test.py | 39 --- tests/pytorch_wrapper/pt_size_create_test.py | 46 --- tests/pytorch_wrapper/pt_size_test.py | 44 --- .../pytorch_wrapper/pt_size_to_numpy_test.py | 55 --- .../pytorch_wrapper/pt_size_to_string_test.py | 46 --- tests/pytorch_wrapper/pt_sqrt_test.py | 39 --- tests/pytorch_wrapper/pt_squeeze_test.py | 57 ---- tests/pytorch_wrapper/pt_std_test.py | 84 ----- tests/pytorch_wrapper/pt_sub_test.py | 39 --- tests/pytorch_wrapper/pt_sum_test.py | 71 ---- tests/pytorch_wrapper/pt_tan_test.py | 39 --- tests/pytorch_wrapper/pt_tanh_test.py | 39 --- .../pt_to_image_transpose_test.py | 54 --- tests/pytorch_wrapper/pt_to_numpy_test.py | 64 ---- .../pytorch_wrapper/pt_to_rgb_tensors_test.py | 69 ---- ...pt_train_classification_conv_model_test.py | 63 ---- ...t_train_classification_conv_model_test2.py | 73 ---- .../pt_train_classification_model_test.py | 59 ---- ...fication_model_with_dataset_loader_test.py | 51 --- tests/pytorch_wrapper/pt_unsqueeze_test.py | 57 ---- tests/pytorch_wrapper/pt_var_test.py | 83 ----- tests/pytorch_wrapper/pt_view_test.py | 61 ---- tests/pytorch_wrapper/pt_where_test.py | 59 ---- tests/pytorch_wrapper/pt_zeros_test.py | 44 --- .../ptn_conv_model_def_test.py | 158 --------- tests/pytorch_wrapper/ptn_conv_model_test.py | 64 ---- .../pytorch_wrapper/ptn_linear_model_test.py | 29 -- .../ptv_dataset_loader_test.py | 34 -- tests/pytorch_wrapper/ptv_dataset_test.py | 36 -- .../ptv_image_folder_dataset_test.py | 47 --- .../ptv_transforms_resize_test.py | 43 --- tests/pytorch_wrapper/utils_test.py | 54 --- web/js/ShowTextGeneric.js | 2 +- 338 files changed, 6 insertions(+), 14298 deletions(-) delete mode 100644 docs/dog_cat_classification_model_training.md delete mode 100644 docs/images/conv_eval.png delete mode 100644 docs/images/conv_train.png delete mode 100644 docs/images/conv_train2.png delete mode 100644 docs/images/fashion_mnist_eval.png delete mode 100644 docs/images/fashion_mnist_train.png delete mode 100644 docs/model_training.md delete mode 100644 docs/reference/pt_abs.md delete mode 100644 docs/reference/pt_acos.md delete mode 100644 docs/reference/pt_add.md delete mode 100644 docs/reference/pt_arange.md delete mode 100644 docs/reference/pt_argmax.md delete mode 100644 docs/reference/pt_argmin.md delete mode 100644 docs/reference/pt_asin.md delete mode 100644 docs/reference/pt_atan.md delete mode 100644 docs/reference/pt_bitwise_and.md delete mode 100644 docs/reference/pt_bitwise_left_shift.md delete mode 100644 docs/reference/pt_bitwise_not.md delete mode 100644 docs/reference/pt_bitwise_or.md delete mode 100644 docs/reference/pt_bitwise_right_shift.md delete mode 100644 docs/reference/pt_bitwise_xor.md delete mode 100644 docs/reference/pt_bmm.md delete mode 100644 docs/reference/pt_bool_create.md delete mode 100644 docs/reference/pt_cos.md delete mode 100644 docs/reference/pt_cosh.md delete mode 100644 docs/reference/pt_crop.md delete mode 100644 docs/reference/pt_data_loader.md delete mode 100644 docs/reference/pt_div.md delete mode 100644 docs/reference/pt_einsum.md delete mode 100644 docs/reference/pt_eq.md delete mode 100644 docs/reference/pt_evaluate_classification_model.md delete mode 100644 docs/reference/pt_exp.md delete mode 100644 docs/reference/pt_flatten.md delete mode 100644 docs/reference/pt_float_create.md delete mode 100644 docs/reference/pt_floor_divide.md delete mode 100644 docs/reference/pt_from_image.md delete mode 100644 docs/reference/pt_from_image_transpose.md delete mode 100644 docs/reference/pt_from_latent.md delete mode 100644 docs/reference/pt_from_numpy.md delete mode 100644 docs/reference/pt_full.md delete mode 100644 docs/reference/pt_gather.md delete mode 100644 docs/reference/pt_ge.md delete mode 100644 docs/reference/pt_gt.md delete mode 100644 docs/reference/pt_index_select.md delete mode 100644 docs/reference/pt_int_create.md delete mode 100644 docs/reference/pt_interpolate_by_scale_factor.md delete mode 100644 docs/reference/pt_interpolate_to_size.md delete mode 100644 docs/reference/pt_le.md delete mode 100644 docs/reference/pt_linspace.md delete mode 100644 docs/reference/pt_load_model.md delete mode 100644 docs/reference/pt_log.md delete mode 100644 docs/reference/pt_logical_and.md delete mode 100644 docs/reference/pt_logical_not.md delete mode 100644 docs/reference/pt_logical_or.md delete mode 100644 docs/reference/pt_logical_xor.md delete mode 100644 docs/reference/pt_lt.md delete mode 100644 docs/reference/pt_masked_select.md delete mode 100644 docs/reference/pt_matmul.md delete mode 100644 docs/reference/pt_max.md delete mode 100644 docs/reference/pt_mean.md delete mode 100644 docs/reference/pt_median.md delete mode 100644 docs/reference/pt_min.md delete mode 100644 docs/reference/pt_mm.md delete mode 100644 docs/reference/pt_mul.md delete mode 100644 docs/reference/pt_ne.md delete mode 100644 docs/reference/pt_neg.md delete mode 100644 docs/reference/pt_ones.md delete mode 100644 docs/reference/pt_pad.md delete mode 100644 docs/reference/pt_permute.md delete mode 100644 docs/reference/pt_pow.md delete mode 100644 docs/reference/pt_predict_classification_model.md delete mode 100644 docs/reference/pt_prod.md delete mode 100644 docs/reference/pt_rand.md delete mode 100644 docs/reference/pt_rand_int.md delete mode 100644 docs/reference/pt_randn.md delete mode 100644 docs/reference/pt_remainder.md delete mode 100644 docs/reference/pt_reshape.md delete mode 100644 docs/reference/pt_save_model.md delete mode 100644 docs/reference/pt_scatter.md delete mode 100644 docs/reference/pt_show_size.md delete mode 100644 docs/reference/pt_show_text.md delete mode 100644 docs/reference/pt_sin.md delete mode 100644 docs/reference/pt_sinh.md delete mode 100644 docs/reference/pt_size.md delete mode 100644 docs/reference/pt_size_create.md delete mode 100644 docs/reference/pt_size_to_numpy.md delete mode 100644 docs/reference/pt_size_to_string.md delete mode 100644 docs/reference/pt_sqrt.md delete mode 100644 docs/reference/pt_squeeze.md delete mode 100644 docs/reference/pt_std.md delete mode 100644 docs/reference/pt_sub.md delete mode 100644 docs/reference/pt_sum.md delete mode 100644 docs/reference/pt_tan.md delete mode 100644 docs/reference/pt_tanh.md delete mode 100644 docs/reference/pt_to_image.md delete mode 100644 docs/reference/pt_to_image_transpose.md delete mode 100644 docs/reference/pt_to_latent.md delete mode 100644 docs/reference/pt_to_numpy.md delete mode 100644 docs/reference/pt_to_rgb_tensors.md delete mode 100644 docs/reference/pt_train_classification_model.md delete mode 100644 docs/reference/pt_unsqueeze.md delete mode 100644 docs/reference/pt_var.md delete mode 100644 docs/reference/pt_view.md delete mode 100644 docs/reference/pt_where.md delete mode 100644 docs/reference/pt_zeros.md delete mode 100644 docs/reference/ptn_conv_model.md delete mode 100644 docs/reference/ptn_linear_model.md delete mode 100644 docs/reference/pto_adam.md delete mode 100644 docs/reference/ptv_dataset.md delete mode 100644 docs/reference/ptv_dataset_loader.md delete mode 100644 docs/reference/ptv_image_folder_dataset.md delete mode 100644 docs/reference/ptv_transforms_resize.md delete mode 100644 docs/reference/ptv_transforms_to_tensor.md delete mode 100644 examples/workflows/dog_cat_classification_eval_model.json delete mode 100644 examples/workflows/dog_cat_classification_prediction.json delete mode 100644 examples/workflows/dog_cat_classification_train_model.json delete mode 100644 examples/workflows/fashion_mnist_eval.json delete mode 100644 examples/workflows/fashion_mnist_eval_conv.json delete mode 100644 examples/workflows/fashion_mnist_train.json delete mode 100644 examples/workflows/fashion_mnist_train_conv.json delete mode 100644 modules/pytorch_wrapper/pt_abs.py delete mode 100644 modules/pytorch_wrapper/pt_acos.py delete mode 100644 modules/pytorch_wrapper/pt_add.py delete mode 100644 modules/pytorch_wrapper/pt_arange.py delete mode 100644 modules/pytorch_wrapper/pt_argmax.py delete mode 100644 modules/pytorch_wrapper/pt_argmin.py delete mode 100644 modules/pytorch_wrapper/pt_asin.py delete mode 100644 modules/pytorch_wrapper/pt_atan.py delete mode 100644 modules/pytorch_wrapper/pt_bitwise_and.py delete mode 100644 modules/pytorch_wrapper/pt_bitwise_left_shift.py delete mode 100644 modules/pytorch_wrapper/pt_bitwise_not.py delete mode 100644 modules/pytorch_wrapper/pt_bitwise_or.py delete mode 100644 modules/pytorch_wrapper/pt_bitwise_right_shift.py delete mode 100644 modules/pytorch_wrapper/pt_bitwise_xor.py delete mode 100644 modules/pytorch_wrapper/pt_bmm.py delete mode 100644 modules/pytorch_wrapper/pt_bool_create.py delete mode 100644 modules/pytorch_wrapper/pt_cos.py delete mode 100644 modules/pytorch_wrapper/pt_cosh.py delete mode 100644 modules/pytorch_wrapper/pt_crop.py delete mode 100644 modules/pytorch_wrapper/pt_data_loader.py delete mode 100644 modules/pytorch_wrapper/pt_div.py delete mode 100644 modules/pytorch_wrapper/pt_einsum.py delete mode 100644 modules/pytorch_wrapper/pt_eq.py delete mode 100644 modules/pytorch_wrapper/pt_evaluate_classification_model.py delete mode 100644 modules/pytorch_wrapper/pt_exp.py delete mode 100644 modules/pytorch_wrapper/pt_flatten.py delete mode 100644 modules/pytorch_wrapper/pt_float_create.py delete mode 100644 modules/pytorch_wrapper/pt_floor_divide.py delete mode 100644 modules/pytorch_wrapper/pt_from_image.py delete mode 100644 modules/pytorch_wrapper/pt_from_image_transpose.py delete mode 100644 modules/pytorch_wrapper/pt_from_latent.py delete mode 100644 modules/pytorch_wrapper/pt_from_numpy.py delete mode 100644 modules/pytorch_wrapper/pt_full.py delete mode 100644 modules/pytorch_wrapper/pt_gather.py delete mode 100644 modules/pytorch_wrapper/pt_ge.py delete mode 100644 modules/pytorch_wrapper/pt_gt.py delete mode 100644 modules/pytorch_wrapper/pt_index_select.py delete mode 100644 modules/pytorch_wrapper/pt_int_create.py delete mode 100644 modules/pytorch_wrapper/pt_interpolate_by_scale_factor.py delete mode 100644 modules/pytorch_wrapper/pt_interpolate_to_size.py delete mode 100644 modules/pytorch_wrapper/pt_le.py delete mode 100644 modules/pytorch_wrapper/pt_linspace.py delete mode 100644 modules/pytorch_wrapper/pt_load_model.py delete mode 100644 modules/pytorch_wrapper/pt_log.py delete mode 100644 modules/pytorch_wrapper/pt_logical_and.py delete mode 100644 modules/pytorch_wrapper/pt_logical_not.py delete mode 100644 modules/pytorch_wrapper/pt_logical_or.py delete mode 100644 modules/pytorch_wrapper/pt_logical_xor.py delete mode 100644 modules/pytorch_wrapper/pt_lt.py delete mode 100644 modules/pytorch_wrapper/pt_masked_select.py delete mode 100644 modules/pytorch_wrapper/pt_matmul.py delete mode 100644 modules/pytorch_wrapper/pt_max.py delete mode 100644 modules/pytorch_wrapper/pt_mean.py delete mode 100644 modules/pytorch_wrapper/pt_median.py delete mode 100644 modules/pytorch_wrapper/pt_min.py delete mode 100644 modules/pytorch_wrapper/pt_mm.py delete mode 100644 modules/pytorch_wrapper/pt_mul.py delete mode 100644 modules/pytorch_wrapper/pt_ne.py delete mode 100644 modules/pytorch_wrapper/pt_neg.py delete mode 100644 modules/pytorch_wrapper/pt_ones.py delete mode 100644 modules/pytorch_wrapper/pt_pad.py delete mode 100644 modules/pytorch_wrapper/pt_permute.py delete mode 100644 modules/pytorch_wrapper/pt_pow.py delete mode 100644 modules/pytorch_wrapper/pt_predict_classification_model.py delete mode 100644 modules/pytorch_wrapper/pt_prod.py delete mode 100644 modules/pytorch_wrapper/pt_rand.py delete mode 100644 modules/pytorch_wrapper/pt_rand_int.py delete mode 100644 modules/pytorch_wrapper/pt_randn.py delete mode 100644 modules/pytorch_wrapper/pt_remainder.py delete mode 100644 modules/pytorch_wrapper/pt_reshape.py delete mode 100644 modules/pytorch_wrapper/pt_save_model.py delete mode 100644 modules/pytorch_wrapper/pt_scatter.py delete mode 100644 modules/pytorch_wrapper/pt_show_size.py delete mode 100644 modules/pytorch_wrapper/pt_show_text.py delete mode 100644 modules/pytorch_wrapper/pt_sin.py delete mode 100644 modules/pytorch_wrapper/pt_sinh.py delete mode 100644 modules/pytorch_wrapper/pt_size.py delete mode 100644 modules/pytorch_wrapper/pt_size_create.py delete mode 100644 modules/pytorch_wrapper/pt_size_to_numpy.py delete mode 100644 modules/pytorch_wrapper/pt_size_to_string.py delete mode 100644 modules/pytorch_wrapper/pt_sqrt.py delete mode 100644 modules/pytorch_wrapper/pt_squeeze.py delete mode 100644 modules/pytorch_wrapper/pt_std.py delete mode 100644 modules/pytorch_wrapper/pt_sub.py delete mode 100644 modules/pytorch_wrapper/pt_sum.py delete mode 100644 modules/pytorch_wrapper/pt_tan.py delete mode 100644 modules/pytorch_wrapper/pt_tanh.py delete mode 100644 modules/pytorch_wrapper/pt_to_image.py delete mode 100644 modules/pytorch_wrapper/pt_to_image_transpose.py delete mode 100644 modules/pytorch_wrapper/pt_to_latent.py delete mode 100644 modules/pytorch_wrapper/pt_to_numpy.py delete mode 100644 modules/pytorch_wrapper/pt_to_rgb_tensors.py delete mode 100644 modules/pytorch_wrapper/pt_train_classification_model.py delete mode 100644 modules/pytorch_wrapper/pt_unsqueeze.py delete mode 100644 modules/pytorch_wrapper/pt_var.py delete mode 100644 modules/pytorch_wrapper/pt_view.py delete mode 100644 modules/pytorch_wrapper/pt_where.py delete mode 100644 modules/pytorch_wrapper/pt_zeros.py delete mode 100644 modules/pytorch_wrapper/ptn_conv_model.py delete mode 100644 modules/pytorch_wrapper/ptn_conv_model_def.py delete mode 100644 modules/pytorch_wrapper/ptn_linear_model.py delete mode 100644 modules/pytorch_wrapper/ptn_linear_model_def.py delete mode 100644 modules/pytorch_wrapper/pto_adam.py delete mode 100644 modules/pytorch_wrapper/ptv_dataset.py delete mode 100644 modules/pytorch_wrapper/ptv_dataset_loader.py delete mode 100644 modules/pytorch_wrapper/ptv_image_folder_dataset.py delete mode 100644 modules/pytorch_wrapper/ptv_transforms_resize.py delete mode 100644 modules/pytorch_wrapper/ptv_transforms_to_tensor.py delete mode 100644 modules/pytorch_wrapper/utils.py delete mode 100644 tests/pytorch_wrapper/__init__.py delete mode 100644 tests/pytorch_wrapper/pt_abs_test.py delete mode 100644 tests/pytorch_wrapper/pt_acos_test.py delete mode 100644 tests/pytorch_wrapper/pt_add_test.py delete mode 100644 tests/pytorch_wrapper/pt_arange_test.py delete mode 100644 tests/pytorch_wrapper/pt_argmax_test.py delete mode 100644 tests/pytorch_wrapper/pt_argmin_test.py delete mode 100644 tests/pytorch_wrapper/pt_asin_test.py delete mode 100644 tests/pytorch_wrapper/pt_atan_test.py delete mode 100644 tests/pytorch_wrapper/pt_bitwise_and_test.py delete mode 100644 tests/pytorch_wrapper/pt_bitwise_left_shift_test.py delete mode 100644 tests/pytorch_wrapper/pt_bitwise_not_test.py delete mode 100644 tests/pytorch_wrapper/pt_bitwise_or_test.py delete mode 100644 tests/pytorch_wrapper/pt_bitwise_right_shift_test.py delete mode 100644 tests/pytorch_wrapper/pt_bitwise_xor_test.py delete mode 100644 tests/pytorch_wrapper/pt_bmm_test.py delete mode 100644 tests/pytorch_wrapper/pt_bool_create_test.py delete mode 100644 tests/pytorch_wrapper/pt_cos_test.py delete mode 100644 tests/pytorch_wrapper/pt_cosh_test.py delete mode 100644 tests/pytorch_wrapper/pt_crop_test.py delete mode 100644 tests/pytorch_wrapper/pt_data_loader_test.py delete mode 100644 tests/pytorch_wrapper/pt_div_test.py delete mode 100644 tests/pytorch_wrapper/pt_einsum_test.py delete mode 100644 tests/pytorch_wrapper/pt_eq_test.py delete mode 100644 tests/pytorch_wrapper/pt_evaluate_classification_conv_model_test.py delete mode 100644 tests/pytorch_wrapper/pt_evaluate_classification_conv_model_test2.py delete mode 100644 tests/pytorch_wrapper/pt_evaluate_classification_model_test.py delete mode 100644 tests/pytorch_wrapper/pt_exp_test.py delete mode 100644 tests/pytorch_wrapper/pt_flatten_test.py delete mode 100644 tests/pytorch_wrapper/pt_float_create_test.py delete mode 100644 tests/pytorch_wrapper/pt_floor_divide_test.py delete mode 100644 tests/pytorch_wrapper/pt_from_image_transpose_test.py delete mode 100644 tests/pytorch_wrapper/pt_from_numpy_test.py delete mode 100644 tests/pytorch_wrapper/pt_full_test.py delete mode 100644 tests/pytorch_wrapper/pt_gather_test.py delete mode 100644 tests/pytorch_wrapper/pt_ge_test.py delete mode 100644 tests/pytorch_wrapper/pt_gt_test.py delete mode 100644 tests/pytorch_wrapper/pt_index_select_test.py delete mode 100644 tests/pytorch_wrapper/pt_int_create_test.py delete mode 100644 tests/pytorch_wrapper/pt_interpolate_by_scale_factor_test.py delete mode 100644 tests/pytorch_wrapper/pt_interpolate_to_size_test.py delete mode 100644 tests/pytorch_wrapper/pt_le_test.py delete mode 100644 tests/pytorch_wrapper/pt_linspace_test.py delete mode 100644 tests/pytorch_wrapper/pt_log_test.py delete mode 100644 tests/pytorch_wrapper/pt_logical_and_test.py delete mode 100644 tests/pytorch_wrapper/pt_logical_not_test.py delete mode 100644 tests/pytorch_wrapper/pt_logical_or_test.py delete mode 100644 tests/pytorch_wrapper/pt_logical_xor_test.py delete mode 100644 tests/pytorch_wrapper/pt_lt_test.py delete mode 100644 tests/pytorch_wrapper/pt_masked_select_test.py delete mode 100644 tests/pytorch_wrapper/pt_matmul_test.py delete mode 100644 tests/pytorch_wrapper/pt_max_test.py delete mode 100644 tests/pytorch_wrapper/pt_mean_test.py delete mode 100644 tests/pytorch_wrapper/pt_median_test.py delete mode 100644 tests/pytorch_wrapper/pt_min_test.py delete mode 100644 tests/pytorch_wrapper/pt_mm_test.py delete mode 100644 tests/pytorch_wrapper/pt_mul_test.py delete mode 100644 tests/pytorch_wrapper/pt_ne_test.py delete mode 100644 tests/pytorch_wrapper/pt_neg_test.py delete mode 100644 tests/pytorch_wrapper/pt_ones_test.py delete mode 100644 tests/pytorch_wrapper/pt_pad_test.py delete mode 100644 tests/pytorch_wrapper/pt_permute_test.py delete mode 100644 tests/pytorch_wrapper/pt_pow_test.py delete mode 100644 tests/pytorch_wrapper/pt_predict_classification_conv_model_test.py delete mode 100644 tests/pytorch_wrapper/pt_prod_test.py delete mode 100644 tests/pytorch_wrapper/pt_rand_int_test.py delete mode 100644 tests/pytorch_wrapper/pt_rand_test.py delete mode 100644 tests/pytorch_wrapper/pt_randn_test.py delete mode 100644 tests/pytorch_wrapper/pt_remainder_test.py delete mode 100644 tests/pytorch_wrapper/pt_reshape_test.py delete mode 100644 tests/pytorch_wrapper/pt_scatter_test.py delete mode 100644 tests/pytorch_wrapper/pt_sin_test.py delete mode 100644 tests/pytorch_wrapper/pt_sinh_test.py delete mode 100644 tests/pytorch_wrapper/pt_size_create_test.py delete mode 100644 tests/pytorch_wrapper/pt_size_test.py delete mode 100644 tests/pytorch_wrapper/pt_size_to_numpy_test.py delete mode 100644 tests/pytorch_wrapper/pt_size_to_string_test.py delete mode 100644 tests/pytorch_wrapper/pt_sqrt_test.py delete mode 100644 tests/pytorch_wrapper/pt_squeeze_test.py delete mode 100644 tests/pytorch_wrapper/pt_std_test.py delete mode 100644 tests/pytorch_wrapper/pt_sub_test.py delete mode 100644 tests/pytorch_wrapper/pt_sum_test.py delete mode 100644 tests/pytorch_wrapper/pt_tan_test.py delete mode 100644 tests/pytorch_wrapper/pt_tanh_test.py delete mode 100644 tests/pytorch_wrapper/pt_to_image_transpose_test.py delete mode 100644 tests/pytorch_wrapper/pt_to_numpy_test.py delete mode 100644 tests/pytorch_wrapper/pt_to_rgb_tensors_test.py delete mode 100644 tests/pytorch_wrapper/pt_train_classification_conv_model_test.py delete mode 100644 tests/pytorch_wrapper/pt_train_classification_conv_model_test2.py delete mode 100644 tests/pytorch_wrapper/pt_train_classification_model_test.py delete mode 100644 tests/pytorch_wrapper/pt_train_classification_model_with_dataset_loader_test.py delete mode 100644 tests/pytorch_wrapper/pt_unsqueeze_test.py delete mode 100644 tests/pytorch_wrapper/pt_var_test.py delete mode 100644 tests/pytorch_wrapper/pt_view_test.py delete mode 100644 tests/pytorch_wrapper/pt_where_test.py delete mode 100644 tests/pytorch_wrapper/pt_zeros_test.py delete mode 100644 tests/pytorch_wrapper/ptn_conv_model_def_test.py delete mode 100644 tests/pytorch_wrapper/ptn_conv_model_test.py delete mode 100644 tests/pytorch_wrapper/ptn_linear_model_test.py delete mode 100644 tests/pytorch_wrapper/ptv_dataset_loader_test.py delete mode 100644 tests/pytorch_wrapper/ptv_dataset_test.py delete mode 100644 tests/pytorch_wrapper/ptv_image_folder_dataset_test.py delete mode 100644 tests/pytorch_wrapper/ptv_transforms_resize_test.py delete mode 100644 tests/pytorch_wrapper/utils_test.py diff --git a/README.md b/README.md index 113a323..cdddffd 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ # ComfyUI-Data-Analysis -## 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. - -Check out [the new tutorial to classify dogs and cats](docs/dog_cat_classification_model_training.md) - +## Update (March 10, 2025) +All PyTorch nodes have been moved to a separate extension. If you're using PyTorch wrappers, install the [ComfyUI-Pt-Wrapper extension](https://github.com/HowToSD/ComfyUI-Pt-Wrapper). --- ![Baseball tutorial](docs/images/baseball_question.jpg) ## How Can We Figure Out Which MLB Player Had the Most Hits Per Year? @@ -22,15 +19,11 @@ I hope you find it useful and engaging for data exploration. ![Baseball tutorial](docs/images/baseball_final.png) **ComfyUI-Data-Analysis** is an extension for ComfyUI that provides custom nodes for data analysis using Pandas, Seaborn, and Matplotlib within ComfyUI. - -Originally designed for structural data analysis, this extension is for you if you: -- Want access to PyTorch's low-level methods (e.g., `add`, `div`, `max`, `where`, `scatter`, `gather`) to manipulate PyTorch tensors or extract information. -- Are comfortable with ComfyUI and want to learn foundational PyTorch methods **without writing a single line of code**. -- Want to analyze structural data (tabular data) using ComfyUI's node system. +Especially, this is suited if you want to analyze structural data (tabular data) using ComfyUI's node system. ## Features -**ComfyUI-Data-Analysis** includes numerous custom nodes that wrap Pandas and PyTorch. +**ComfyUI-Data-Analysis** includes numerous custom nodes that wrap Pandas, Matplotlib and Seaborn. For a complete list, see the [Node Reference](docs/reference/node_reference.md). These nodes allow you to conduct complex analyses in an intuitive, graphical way. You can also combine them with other ComfyUI custom nodes to fit your workflow needs (e.g., integrating images). diff --git a/__init__.py b/__init__.py index 2ddc986..3e295b0 100644 --- a/__init__.py +++ b/__init__.py @@ -171,112 +171,6 @@ 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_abs import PtAbs -from .modules.pytorch_wrapper.pt_acos import PtAcos -from .modules.pytorch_wrapper.pt_add import PtAdd -from .modules.pytorch_wrapper.pt_arange import PtArange -from .modules.pytorch_wrapper.pt_argmax import PtArgmax -from .modules.pytorch_wrapper.pt_argmin import PtArgmin -from .modules.pytorch_wrapper.pt_asin import PtAsin -from .modules.pytorch_wrapper.pt_atan import PtAtan -from .modules.pytorch_wrapper.pt_bitwise_and import PtBitwiseAnd -from .modules.pytorch_wrapper.pt_bitwise_left_shift import PtBitwiseLeftShift -from .modules.pytorch_wrapper.pt_bitwise_not import PtBitwiseNot -from .modules.pytorch_wrapper.pt_bitwise_or import PtBitwiseOr -from .modules.pytorch_wrapper.pt_bitwise_right_shift import PtBitwiseRightShift -from .modules.pytorch_wrapper.pt_bitwise_xor import PtBitwiseXor -from .modules.pytorch_wrapper.pt_bmm import PtBmm -from .modules.pytorch_wrapper.pt_bool_create import PtBoolCreate -from .modules.pytorch_wrapper.pt_cos import PtCos -from .modules.pytorch_wrapper.pt_cosh import PtCosh -from .modules.pytorch_wrapper.pt_crop import PtCrop -from .modules.pytorch_wrapper.pt_data_loader import PtDataLoader -from .modules.pytorch_wrapper.pt_div import PtDiv -from .modules.pytorch_wrapper.pt_einsum import PtEinsum -from .modules.pytorch_wrapper.pt_eq import PtEq -from .modules.pytorch_wrapper.pt_evaluate_classification_model import PtEvaluateClassificationModel -from .modules.pytorch_wrapper.pt_exp import PtExp -from .modules.pytorch_wrapper.pt_flatten import PtFlatten -from .modules.pytorch_wrapper.pt_float_create import PtFloatCreate -from .modules.pytorch_wrapper.pt_floor_divide import PtFloorDiv -from .modules.pytorch_wrapper.pt_from_image import PtFromImage -from .modules.pytorch_wrapper.pt_from_image_transpose import PtFromImageTranspose -from .modules.pytorch_wrapper.pt_from_latent import PtFromLatent -from .modules.pytorch_wrapper.pt_from_numpy import PtFromNumpy -from .modules.pytorch_wrapper.pt_full import PtFull -from .modules.pytorch_wrapper.pt_gather import PtGather -from .modules.pytorch_wrapper.pt_ge import PtGe -from .modules.pytorch_wrapper.pt_gt import PtGt -from .modules.pytorch_wrapper.pt_index_select import PtIndexSelect -from .modules.pytorch_wrapper.pt_int_create import PtIntCreate -from .modules.pytorch_wrapper.pt_interpolate_by_scale_factor import PtInterpolateByScaleFactor -from .modules.pytorch_wrapper.pt_interpolate_to_size import PtInterpolateToSize -from .modules.pytorch_wrapper.pt_le import PtLe -from .modules.pytorch_wrapper.pt_linspace import PtLinspace -from .modules.pytorch_wrapper.pt_load_model import PtLoadModel -from .modules.pytorch_wrapper.pt_log import PtLog -from .modules.pytorch_wrapper.pt_logical_and import PtLogicalAnd -from .modules.pytorch_wrapper.pt_logical_not import PtLogicalNot -from .modules.pytorch_wrapper.pt_logical_or import PtLogicalOr -from .modules.pytorch_wrapper.pt_logical_xor import PtLogicalXor -from .modules.pytorch_wrapper.pt_lt import PtLt -from .modules.pytorch_wrapper.pt_masked_select import PtMaskedSelect -from .modules.pytorch_wrapper.pt_matmul import PtMatMul -from .modules.pytorch_wrapper.pt_max import PtMax -from .modules.pytorch_wrapper.pt_mean import PtMean -from .modules.pytorch_wrapper.pt_median import PtMedian -from .modules.pytorch_wrapper.pt_min import PtMin -from .modules.pytorch_wrapper.pt_mm import PtMm -from .modules.pytorch_wrapper.pt_mul import PtMul -from .modules.pytorch_wrapper.pt_ne import PtNe -from .modules.pytorch_wrapper.pt_neg import PtNeg -from .modules.pytorch_wrapper.pt_ones import PtOnes -from .modules.pytorch_wrapper.pt_pad import PtPad -from .modules.pytorch_wrapper.pt_permute import PtPermute -from .modules.pytorch_wrapper.pt_pow import PtPow -from .modules.pytorch_wrapper.pt_predict_classification_model import PtPredictClassificationModel -from .modules.pytorch_wrapper.pt_prod import PtProd -from .modules.pytorch_wrapper.pt_rand import PtRand -from .modules.pytorch_wrapper.pt_rand_int import PtRandInt -from .modules.pytorch_wrapper.pt_randn import PtRandn -from .modules.pytorch_wrapper.pt_remainder import PtRemainder -from .modules.pytorch_wrapper.pt_reshape import PtReshape -from .modules.pytorch_wrapper.pt_save_model import PtSaveModel -from .modules.pytorch_wrapper.pt_scatter import PtScatter -from .modules.pytorch_wrapper.pt_show_size import PtShowSize -from .modules.pytorch_wrapper.pt_show_text import PtShowText -from .modules.pytorch_wrapper.pt_sin import PtSin -from .modules.pytorch_wrapper.pt_sinh import PtSinh -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_sqrt import PtSqrt -from .modules.pytorch_wrapper.pt_squeeze import PtSqueeze -from .modules.pytorch_wrapper.pt_std import PtStd -from .modules.pytorch_wrapper.pt_sub import PtSub -from .modules.pytorch_wrapper.pt_sum import PtSum -from .modules.pytorch_wrapper.pt_tan import PtTan -from .modules.pytorch_wrapper.pt_tanh import PtTanh -from .modules.pytorch_wrapper.pt_to_image import PtToImage -from .modules.pytorch_wrapper.pt_to_image_transpose import PtToImageTranspose -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.pytorch_wrapper.pt_train_classification_model import PtTrainClassificationModel -from .modules.pytorch_wrapper.pt_unsqueeze import PtUnsqueeze -from .modules.pytorch_wrapper.pt_var import PtVar -from .modules.pytorch_wrapper.pt_view import PtView -from .modules.pytorch_wrapper.pt_where import PtWhere -from .modules.pytorch_wrapper.pt_zeros import PtZeros -from .modules.pytorch_wrapper.ptn_conv_model import PtnConvModel -from .modules.pytorch_wrapper.ptn_linear_model import PtnLinearModel -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 from .modules.seaborn_wrapper.sns_heatmap import SNSHeatmap @@ -453,112 +347,6 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = { "PandasVerticalConcat": PandasVerticalConcat, "PandasVerticalSplit": PandasVerticalSplit, "PandasXs": PandasXs, - "PtAbs": PtAbs, - "PtAcos": PtAcos, - "PtAdd": PtAdd, - "PtArange": PtArange, - "PtArgmax": PtArgmax, - "PtArgmin": PtArgmin, - "PtAsin": PtAsin, - "PtAtan": PtAtan, - "PtBitwiseAnd": PtBitwiseAnd, - "PtBitwiseLeftShift": PtBitwiseLeftShift, - "PtBitwiseNot": PtBitwiseNot, - "PtBitwiseOr": PtBitwiseOr, - "PtBitwiseRightShift": PtBitwiseRightShift, - "PtBitwiseXor": PtBitwiseXor, - "PtBmm": PtBmm, - "PtBoolCreate": PtBoolCreate, - "PtCos": PtCos, - "PtCosh": PtCosh, - "PtCrop": PtCrop, - "PtDataLoader": PtDataLoader, - "PtDiv": PtDiv, - "PtEinsum": PtEinsum, - "PtEq": PtEq, - "PtEvaluateClassificationModel": PtEvaluateClassificationModel, - "PtExp": PtExp, - "PtFlatten": PtFlatten, - "PtFloatCreate": PtFloatCreate, - "PtFloorDiv": PtFloorDiv, - "PtFromImage": PtFromImage, - "PtFromImageTranspose": PtFromImageTranspose, - "PtFromLatent": PtFromLatent, - "PtFromNumpy": PtFromNumpy, - "PtFull": PtFull, - "PtGather": PtGather, - "PtGe": PtGe, - "PtGt": PtGt, - "PtIndexSelect": PtIndexSelect, - "PtIntCreate": PtIntCreate, - "PtInterpolateByScaleFactor": PtInterpolateByScaleFactor, - "PtInterpolateToSize": PtInterpolateToSize, - "PtLe": PtLe, - "PtLinspace": PtLinspace, - "PtLoadModel": PtLoadModel, - "PtLog": PtLog, - "PtLogicalAnd": PtLogicalAnd, - "PtLogicalNot": PtLogicalNot, - "PtLogicalOr": PtLogicalOr, - "PtLogicalXor": PtLogicalXor, - "PtLt": PtLt, - "PtMaskedSelect": PtMaskedSelect, - "PtMatMul": PtMatMul, - "PtMax": PtMax, - "PtMean": PtMean, - "PtMedian": PtMedian, - "PtMin": PtMin, - "PtMm": PtMm, - "PtMul": PtMul, - "PtNe": PtNe, - "PtNeg": PtNeg, - "PtOnes": PtOnes, - "PtPad": PtPad, - "PtPermute": PtPermute, - "PtPow": PtPow, - "PtPredictClassificationModel": PtPredictClassificationModel, - "PtProd": PtProd, - "PtRand": PtRand, - "PtRandInt": PtRandInt, - "PtRandn": PtRandn, - "PtRemainder": PtRemainder, - "PtReshape": PtReshape, - "PtSaveModel": PtSaveModel, - "PtScatter": PtScatter, - "PtShowSize": PtShowSize, - "PtShowText": PtShowText, - "PtSin": PtSin, - "PtSinh": PtSinh, - "PtSize": PtSize, - "PtSizeCreate": PtSizeCreate, - "PtSizeToNumpy": PtSizeToNumpy, - "PtSizeToString": PtSizeToString, - "PtSqrt": PtSqrt, - "PtSqueeze": PtSqueeze, - "PtStd": PtStd, - "PtSub": PtSub, - "PtSum": PtSum, - "PtTan": PtTan, - "PtTanh": PtTanh, - "PtToImage": PtToImage, - "PtToImageTranspose": PtToImageTranspose, - "PtToLatent": PtToLatent, - "PtToNumpy": PtToNumpy, - "PtToRgbTensors": PtToRgbTensors, - "PtTrainClassificationModel": PtTrainClassificationModel, - "PtUnsqueeze": PtUnsqueeze, - "PtVar": PtVar, - "PtView": PtView, - "PtWhere": PtWhere, - "PtZeros": PtZeros, - "PtnConvModel": PtnConvModel, - "PtnLinearModel": PtnLinearModel, - "PtoAdam": PtoAdam, - "PtvDataset": PtvDataset, - "PtvDatasetLoader": PtvDatasetLoader, - "PtvImageFolderDataset": PtvImageFolderDataset, - "PtvTransformsResize": PtvTransformsResize, - "PtvTransformsToTensor": PtvTransformsToTensor, "PyDatetimeToString": PyDatetimeToString, "PyFloatToString": PyFloatToString, "PyIntToString": PyIntToString, @@ -746,112 +534,6 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = { "PandasVerticalConcat": "Pandas Vertical Concat", "PandasVerticalSplit": "Pandas Vertical Split", "PandasXs": "Pandas Xs", - "PtAbs": "Pt Abs", - "PtAcos": "Pt Acos", - "PtAdd": "Pt Add", - "PtArange": "Pt Arange", - "PtArgmax": "Pt Argmax", - "PtArgmin": "Pt Argmin", - "PtAsin": "Pt Asin", - "PtAtan": "Pt Atan", - "PtBitwiseAnd": "Pt Bitwise And", - "PtBitwiseLeftShift": "Pt Bitwise Left Shift", - "PtBitwiseNot": "Pt Bitwise Not", - "PtBitwiseOr": "Pt Bitwise Or", - "PtBitwiseRightShift": "Pt Bitwise Right Shift", - "PtBitwiseXor": "Pt Bitwise Xor", - "PtBmm": "Pt Bmm", - "PtBoolCreate": "Pt Bool Create", - "PtCos": "Pt Cos", - "PtCosh": "Pt Cosh", - "PtCrop": "Pt Crop", - "PtDataLoader": "Pt Data Loader", - "PtDiv": "Pt Div", - "PtEinsum": "Pt Einsum", - "PtEq": "Pt Eq", - "PtEvaluateClassificationModel": "Pt Evaluate Classification Model", - "PtExp": "Pt Exp", - "PtFlatten": "Pt Flatten", - "PtFloatCreate": "Pt Float Create", - "PtFloorDiv": "Pt Floor Div", - "PtFromImage": "Pt From Image", - "PtFromImageTranspose": "Pt From Image Transpose", - "PtFromLatent": "Pt From Latent", - "PtFromNumpy": "Pt From Numpy", - "PtFull": "Pt Full", - "PtGather": "Pt Gather", - "PtGe": "Pt Ge", - "PtGt": "Pt Gt", - "PtIndexSelect": "Pt Index Select", - "PtIntCreate": "Pt Int Create", - "PtInterpolateByScaleFactor": "Pt Interpolate By Scale Factor", - "PtInterpolateToSize": "Pt Interpolate To Size", - "PtLe": "Pt Le", - "PtLinspace": "Pt Linspace", - "PtLoadModel": "Pt Load Model", - "PtLog": "Pt Log", - "PtLogicalAnd": "Pt Logical And", - "PtLogicalNot": "Pt Logical Not", - "PtLogicalOr": "Pt Logical Or", - "PtLogicalXor": "Pt Logical Xor", - "PtLt": "Pt Lt", - "PtMaskedSelect": "Pt Masked Select", - "PtMatMul": "Pt Mat Mul", - "PtMax": "Pt Max", - "PtMean": "Pt Mean", - "PtMedian": "Pt Median", - "PtMin": "Pt Min", - "PtMm": "Pt Mm", - "PtMul": "Pt Mul", - "PtNe": "Pt Ne", - "PtNeg": "Pt Neg", - "PtOnes": "Pt Ones", - "PtPad": "Pt Pad", - "PtPermute": "Pt Permute", - "PtPow": "Pt Pow", - "PtPredictClassificationModel": "Pt Predict Classification Model", - "PtProd": "Pt Prod", - "PtRand": "Pt Rand", - "PtRandInt": "Pt Rand Int", - "PtRandn": "Pt Randn", - "PtRemainder": "Pt Remainder", - "PtReshape": "Pt Reshape", - "PtSaveModel": "Pt Save Model", - "PtScatter": "Pt Scatter", - "PtShowSize": "Pt Show Size", - "PtShowText": "Pt Show Text", - "PtSin": "Pt Sin", - "PtSinh": "Pt Sinh", - "PtSize": "Pt Size", - "PtSizeCreate": "Pt Size Create", - "PtSizeToNumpy": "Pt Size To Numpy", - "PtSizeToString": "Pt Size To String", - "PtSqrt": "Pt Sqrt", - "PtSqueeze": "Pt Squeeze", - "PtStd": "Pt Std", - "PtSub": "Pt Sub", - "PtSum": "Pt Sum", - "PtTan": "Pt Tan", - "PtTanh": "Pt Tanh", - "PtToImage": "Pt To Image", - "PtToImageTranspose": "Pt To Image Transpose", - "PtToLatent": "Pt To Latent", - "PtToNumpy": "Pt To Numpy", - "PtToRgbTensors": "Pt To Rgb Tensors", - "PtTrainClassificationModel": "Pt Train Classification Model", - "PtUnsqueeze": "Pt Unsqueeze", - "PtVar": "Pt Var", - "PtView": "Pt View", - "PtWhere": "Pt Where", - "PtZeros": "Pt Zeros", - "PtnConvModel": "Ptn Conv Model", - "PtnLinearModel": "Ptn Linear Model", - "PtoAdam": "Pto Adam", - "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", "PyIntToString": "Py Int To String", diff --git a/docs/dog_cat_classification_model_training.md b/docs/dog_cat_classification_model_training.md deleted file mode 100644 index 68f3c13..0000000 --- a/docs/dog_cat_classification_model_training.md +++ /dev/null @@ -1,137 +0,0 @@ -# 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. \ No newline at end of file diff --git a/docs/images/conv_eval.png b/docs/images/conv_eval.png deleted file mode 100644 index 03ae6b9232e76ebec06561d3d94a59c9e16a0d87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 180926 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 diff --git a/docs/images/conv_train.png b/docs/images/conv_train.png deleted file mode 100644 index 442c4f396d1fb75a7548727e2149f378e0c99499..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 198292 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ƙ^vBZk$EO33hrYkV}^ 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 diff --git a/docs/images/fashion_mnist_train.png b/docs/images/fashion_mnist_train.png deleted file mode 100644 index c74a788a36f3d64c7a297b18f06446288b6da187..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 169022 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 diff --git a/docs/model_training.md b/docs/model_training.md deleted file mode 100644 index dbfc555..0000000 --- a/docs/model_training.md +++ /dev/null @@ -1,49 +0,0 @@ -# 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** -![Train](images/fashion_mnist_train.png) - -**Conv model** -![Train](images/conv_train.png) - -*Close up of the conv node* -![Train](images/conv_train2.png) - -### Eval workflow -**Linear model** -![Eval](images/fashion_mnist_eval.png) - -**Conv model** -![Eval](images/conv_eval.png) diff --git a/docs/reference/node_reference.md b/docs/reference/node_reference.md index cc5b145..3a1e5ae 100644 --- a/docs/reference/node_reference.md +++ b/docs/reference/node_reference.md @@ -99,8 +99,6 @@ | [Pandas Show Series](pandas_show_series.md) | Displays a Pandas Series as text. | | [Pandas Show Text](pandas_show_text.md) | Displays a string. | | [Pandas To String](pandas_to_string.md) | Converts a pandas DataFrame to a string representation. | -| [Pt Show Size](pt_show_size.md) | Displays PyTorch Size object as a string. | -| [Pt Show Text](pt_show_text.md) | Displays PyTorch tensor as a string. Note that the tensor is partially printed out when | ## IO | Node | Description | | --- | --- | @@ -127,7 +125,6 @@ | [Pandas Load JSON](pandas_load_json.md) | Loads JSON files into a pandas DataFrame. | | [Pandas Save CSV](pandas_save_csv.md) | Saves a PandasDataFrame to a CSV file. | | [Pandas Save JSON](pandas_save_json.md) | Saves a PandasDataFrame to a JSON file. | -| [Pt Save Model](pt_save_model.md) | A wrapper class for saving a PyTorch model. | ## Logical operations | Node | Description | | --- | --- | @@ -178,151 +175,6 @@ | [SNS Line Plot](sns_line.md) | Generates a line plot from a pandas DataFrame using Seaborn. | | [SNS Pairplot](sns_pairplot.md) | Generates a pairplot from a Pandas DataFrame using Seaborn. | | [SNS Scatter Plot](sns_scatter.md) | Generates a scatter plot from a pandas DataFrame using Seaborn. | -## PyTorch wrapper - Arithmetic operations -| Node | Description | -| --- | --- | -| [Pt Add](pt_add.md) | Adds two PyTorch tensors. | -| [Pt Div](pt_div.md) | Divides one PyTorch tensor by another element-wise. | -| [Pt Floor Div](pt_floor_divide.md) | Performs element-wise floor division on two PyTorch tensors. | -| [Pt Mul](pt_mul.md) | Multiplies two PyTorch tensors element-wise. | -| [Pt Pow](pt_pow.md) | Raises one PyTorch tensor to the power of another element-wise. | -| [Pt Remainder](pt_remainder.md) | Computes the element-wise remainder of division between two PyTorch tensors. | -| [Pt Sub](pt_sub.md) | Subtracts one PyTorch tensor from another. | -## PyTorch wrapper - Bitwise operations -| Node | Description | -| --- | --- | -| [Pt Bitwise And](pt_bitwise_and.md) | Performs a bitwise AND operation on two PyTorch tensors element-wise. | -| [Pt Bitwise Left Shift](pt_bitwise_left_shift.md) | Performs a bitwise left shift operation on two PyTorch tensors element-wise. | -| [Pt Bitwise Not](pt_bitwise_not.md) | Performs a bitwise NOT operation on a PyTorch tensor element-wise. | -| [Pt Bitwise Or](pt_bitwise_or.md) | Performs a bitwise OR operation on two PyTorch tensors element-wise. | -| [Pt Bitwise Right Shift](pt_bitwise_right_shift.md) | Performs a bitwise right shift operation on two PyTorch tensors element-wise. | -| [Pt Bitwise Xor](pt_bitwise_xor.md) | Performs a bitwise XOR operation on two PyTorch tensors element-wise. | -## PyTorch wrapper - Comparison operations -| Node | Description | -| --- | --- | -| [Pt Eq](pt_eq.md) | Tests whether two PyTorch tensors are equal element-wise. | -| [Pt Ge](pt_ge.md) | Tests whether elements in the first PyTorch tensor are greater than or equal to the corresponding elements in the second tensor. | -| [Pt Gt](pt_gt.md) | Tests whether elements in the first PyTorch tensor are greater than the corresponding elements in the second tensor. | -| [Pt Le](pt_le.md) | Tests whether elements in the first PyTorch tensor are less than or equal to the corresponding elements in the second tensor. | -| [Pt Lt](pt_lt.md) | Tests whether elements in the first PyTorch tensor are less than the corresponding elements in the second tensor. | -| [Pt Ne](pt_ne.md) | Tests whether two PyTorch tensors are not equal element-wise. | -## PyTorch wrapper - Image processing -| Node | Description | -| --- | --- | -| [Pt Crop](pt_crop.md) | Crops a PyTorch tensor to the specified size. The input tensor must have a shape of (c, h, w) or (b, c, h, w). | -| [Pt From Image](pt_from_image.md) | Casts an Image tensor as a PyTorch tensor. | -| [Pt From Image Transpose](pt_from_image_transpose.md) | Casts an image tensor to a PyTorch tensor and transposes it from (H, W, C) to (C, H, W). For rank-4 inputs, the batch axis remains unchanged. | -| [Pt Interpolate By Scale Factor](pt_interpolate_by_scale_factor.md) | Resizes a PyTorch tensor using interpolation by scale factor. The input tensor must have a shape of (c, h, w) or (b, c, h, w). | -| [Pt Interpolate To Size](pt_interpolate_to_size.md) | Resizes a PyTorch tensor using interpolation. The input tensor must have a shape of (c, h, w) or (b, c, h, w). | -| [Pt Pad](pt_pad.md) | Pads a PyTorch tensor to the specified size. Padded area will be black. The input tensor must have a shape of (c, h, w) or (b, c, h, w). | -| [Pt To Image](pt_to_image.md) | Casts a PyTorch tensor as an Image tensor. | -| [Pt To Image Transpose](pt_to_image_transpose.md) | Casts a PyTorch tensor as an Image tensor and transposes it from (C, H, W) to (H, W, C). For rank-4 inputs, the batch axis remains unchanged. | -## PyTorch wrapper - Indexing and Slicing Operations -| Node | Description | -| --- | --- | -| [Pt Gather](pt_gather.md) | Generates a tensor based on the index tensor using PyTorch's `gather` function. | -| [Pt Index Select](pt_index_select.md) | Extracts elements from the input tensor along a specified dimension using an index tensor. | -| [Pt Masked Select](pt_masked_select.md) | Extracts elements from the input tensor whose corresponding value in `masked_tens` is `True`. | -| [Pt Scatter](pt_scatter.md) | Generates a new tensor by replacing values at specified positions using an index tensor. | -| [Pt Where](pt_where.md) | Generates a new tensor by selecting values based on a condition tensor. | -## PyTorch wrapper - Logical operations -| Node | Description | -| --- | --- | -| [Pt Logical And](pt_logical_and.md) | Performs a logical AND operation on two PyTorch tensors element-wise. | -| [Pt Logical Not](pt_logical_not.md) | Performs a logical NOT operation on a PyTorch tensor element-wise. | -| [Pt Logical Or](pt_logical_or.md) | Performs a logical OR operation on two PyTorch tensors element-wise. | -| [Pt Logical Xor](pt_logical_xor.md) | Performs a logical XOR operation on two PyTorch tensors element-wise. | -## PyTorch wrapper - Math operations -| Node | Description | -| --- | --- | -| [Pt Abs](pt_abs.md) | Computes the absolute value of each element in a PyTorch tensor. | -| [Pt Acos](pt_acos.md) | Computes the arccosine (inverse cosine) of a PyTorch tensor element-wise. | -| [Pt Asin](pt_asin.md) | Computes the arcsine (inverse sine) of a PyTorch tensor element-wise. | -| [Pt Atan](pt_atan.md) | Computes the arc tangent (inverse tangent) of a PyTorch tensor element-wise. | -| [Pt Cos](pt_cos.md) | Computes the cosine of a PyTorch tensor element-wise. | -| [Pt Cosh](pt_cosh.md) | Computes the hyperbolic cosine of a PyTorch tensor element-wise. | -| [Pt Exp](pt_exp.md) | Performs an exponential operation on a PyTorch tensor element-wise. | -| [Pt Log](pt_log.md) | Computes the natural logarithm (log base e) of a PyTorch tensor element-wise. | -| [Pt Neg](pt_neg.md) | Computes the negation of each element in a PyTorch tensor. | -| [Pt Sin](pt_sin.md) | Computes the sine of a PyTorch tensor element-wise. | -| [Pt Sinh](pt_sinh.md) | Computes the hyperbolic sine of a PyTorch tensor element-wise. | -| [Pt Sqrt](pt_sqrt.md) | Computes the square root of each element in a PyTorch tensor. | -| [Pt Tan](pt_tan.md) | Computes the tangent of a PyTorch tensor element-wise. | -| [Pt Tanh](pt_tanh.md) | Computes the hyperbolic tangent of a PyTorch tensor element-wise. | -## PyTorch wrapper - Matrix operations -| Node | Description | -| --- | --- | -| [Pt Bmm](pt_bmm.md) | Performs batched matrix multiplication on two 3D PyTorch tensors. | -| [Pt Einsum](pt_einsum.md) | Performs Tensor operations specified in the Einstein summation equation. | -| [Pt Mat Mul](pt_matmul.md) | Performs matrix multiplication on two PyTorch tensors. | -| [Pt Mm](pt_mm.md) | Performs 2D matrix multiplication on two PyTorch tensors. | -## PyTorch wrapper - Reduction operation & Summary statistics -| Node | Description | -| --- | --- | -| [Pt Argmax](pt_argmax.md) | Computes the indices of the maximum values of a PyTorch tensor along the specified dimension(s). | -| [Pt Argmin](pt_argmin.md) | Computes the indices of the minimum values of a PyTorch tensor along the specified dimension(s). | -| [Pt Max](pt_max.md) | Computes the maximum values of a PyTorch tensor along the specified dimension(s). | -| [Pt Mean](pt_mean.md) | Computes the mean of a PyTorch tensor along the specified dimension(s). | -| [Pt Median](pt_median.md) | Computes the median of a PyTorch tensor along the specified dimension(s). | -| [Pt Min](pt_min.md) | Computes the minimum values of a PyTorch tensor along the specified dimension(s). | -| [Pt Prod](pt_prod.md) | Computes the product of a PyTorch tensor along the specified dimension(s). | -| [Pt Std](pt_std.md) | Computes the standard deviation of a PyTorch tensor along the specified dimension(s). | -| [Pt Sum](pt_sum.md) | Computes the sum of a PyTorch tensor along the specified dimension(s). | -| [Pt Var](pt_var.md) | Computes the variance of a PyTorch tensor along the specified dimension(s). | -## PyTorch wrapper - Size object support -| Node | Description | -| --- | --- | -| [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. | -## PyTorch wrapper - Tensor creation -| Node | Description | -| --- | --- | -| [Pt Arange](pt_arange.md) | Creates a PyTorch tensor using `torch.arange` with the specified start, end, and step values. | -| [Pt Bool Create](pt_bool_create.md) | Creates a PyTorch tensor of dtype bool from True or False values entered as a list in the text field. | -| [Pt Float Create](pt_float_create.md) | Creates a PyTorch tensor with 32-bit floating point precision | -| [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 Full](pt_full.md) | Creates a PyTorch tensor filled with a specified value using the size entered in the text field. | -| [Pt Int Create](pt_int_create.md) | Creates a PyTorch tensor with 32-bit integer | -| [Pt Linspace](pt_linspace.md) | Creates a PyTorch tensor using `torch.linspace` with the specified start, end, and steps values. | -| [Pt Ones](pt_ones.md) | Creates a PyTorch tensor of ones using the size entered in the text field. | -| [Pt Rand](pt_rand.md) | Creates a PyTorch tensor with values sampled from a uniform distribution | -| [Pt Rand Int](pt_rand_int.md) | Creates a PyTorch tensor filled with random integers within a specified range using the size entered in the text field. | -| [Pt Randn](pt_randn.md) | Creates a PyTorch tensor with values sampled from a standard normal distribution (mean=0, std=1) | -| [Pt Zeros](pt_zeros.md) | Creates a PyTorch tensor of zeros using the size entered in the text field. | -## PyTorch wrapper - Tensor data conversion -| Node | Description | -| --- | --- | -| [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. | -| [Pt To Rgb Tensors](pt_to_rgb_tensors.md) | Splits a PyTorch tensor into R, G, and B tensors. | -## PyTorch wrapper - Training -| Node | Description | -| --- | --- | -| [Pt Data Loader](pt_data_loader.md) | Loads data from a dataset node and creates a PyTorch DataLoader. | -| [Pt Evaluate Classification Model](pt_evaluate_classification_model.md) | Performs inference on test data and computes evaluation metrics. | -| [Pt Load Model](pt_load_model.md) | A wrapper class for saving a PyTorch model. | -| [Pt Predict Classification Model](pt_predict_classification_model.md) | Performs inference on input data. | -| [Pt Train Classification Model](pt_train_classification_model.md) | Trains a classification model using a given dataset, optimizer, and number of epochs. | -| [Ptn Conv Model](ptn_conv_model.md) | A convolutional model consisting of multiple convolutional layers. | -| [Ptn Linear Model](ptn_linear_model.md) | A linear model consisting of dense layers. | -| [Pto Adam](pto_adam.md) | Instantiates the Adam optimizer. | -| [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 | -| --- | --- | -| [Pt Flatten](pt_flatten.md) | Flattens a PyTorch tensor into a 1D tensor. | -| [Pt Permute](pt_permute.md) | Permutes the dimensions of a PyTorch tensor according to the specified order. | -| [Pt Reshape](pt_reshape.md) | Reshapes a PyTorch tensor into a specified shape using `torch.reshape()`. | -| [Pt Squeeze](pt_squeeze.md) | Removes a dimension at the specified position in the input tensor if it is of size 1. | -| [Pt Unsqueeze](pt_unsqueeze.md) | Adds a singleton dimension at the specified position in the input tensor. | -| [Pt View](pt_view.md) | Reshapes a PyTorch tensor into a specified shape using `torch.view()`. | ## Python wrapper | Node | Description | | --- | --- | diff --git a/docs/reference/pt_abs.md b/docs/reference/pt_abs.md deleted file mode 100644 index 2565442..0000000 --- a/docs/reference/pt_abs.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Abs -Computes the absolute value of each element in a PyTorch tensor. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -


-Category: PyTorch wrapper - Math operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_acos.md b/docs/reference/pt_acos.md deleted file mode 100644 index 6df37ca..0000000 --- a/docs/reference/pt_acos.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Acos -Computes the arccosine (inverse cosine) of a PyTorch tensor element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Math operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_add.md b/docs/reference/pt_add.md deleted file mode 100644 index 070bfd0..0000000 --- a/docs/reference/pt_add.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Add -Adds two PyTorch tensors. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Arithmetic operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_arange.md b/docs/reference/pt_arange.md deleted file mode 100644 index 3a5e325..0000000 --- a/docs/reference/pt_arange.md +++ /dev/null @@ -1,23 +0,0 @@ -# Pt Arange -Creates a PyTorch tensor using `torch.arange` with the specified start, end, and step values. - -The values are parsed from string inputs and converted to numeric types. This allows users to specify -tensor range values in text fields. The function also ensures proper dtype selection. - -## Input -| Name | Data type | -|---|---| -| start | String | -| end | String | -| step | String | -| data_type | | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Tensor creation - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_argmax.md b/docs/reference/pt_argmax.md deleted file mode 100644 index 239bffc..0000000 --- a/docs/reference/pt_argmax.md +++ /dev/null @@ -1,24 +0,0 @@ -# Pt Argmax -Computes the indices of the maximum values of a PyTorch tensor along the specified dimension(s). - -Specify the dimension in the `dim` field using an integer as shown below: -``` -0 -``` - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| dim | String | -| keepdim | Boolean | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Reduction operation & Summary statistics - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_argmin.md b/docs/reference/pt_argmin.md deleted file mode 100644 index d08c285..0000000 --- a/docs/reference/pt_argmin.md +++ /dev/null @@ -1,24 +0,0 @@ -# Pt Argmin -Computes the indices of the minimum values of a PyTorch tensor along the specified dimension(s). - -Specify the dimension in the `dim` field using an integer as shown below: -``` -0 -``` - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| dim | String | -| keepdim | Boolean | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Reduction operation & Summary statistics - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_asin.md b/docs/reference/pt_asin.md deleted file mode 100644 index 8700137..0000000 --- a/docs/reference/pt_asin.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Asin -Computes the arcsine (inverse sine) of a PyTorch tensor element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Math operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_atan.md b/docs/reference/pt_atan.md deleted file mode 100644 index d745a09..0000000 --- a/docs/reference/pt_atan.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Atan -Computes the arc tangent (inverse tangent) of a PyTorch tensor element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Math operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_bitwise_and.md b/docs/reference/pt_bitwise_and.md deleted file mode 100644 index 3678455..0000000 --- a/docs/reference/pt_bitwise_and.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Bitwise And -Performs a bitwise AND operation on two PyTorch tensors element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Bitwise operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_bitwise_left_shift.md b/docs/reference/pt_bitwise_left_shift.md deleted file mode 100644 index 17ef621..0000000 --- a/docs/reference/pt_bitwise_left_shift.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Bitwise Left Shift -Performs a bitwise left shift operation on two PyTorch tensors element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Bitwise operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_bitwise_not.md b/docs/reference/pt_bitwise_not.md deleted file mode 100644 index fd0080f..0000000 --- a/docs/reference/pt_bitwise_not.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Bitwise Not -Performs a bitwise NOT operation on a PyTorch tensor element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Bitwise operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_bitwise_or.md b/docs/reference/pt_bitwise_or.md deleted file mode 100644 index 2bea79d..0000000 --- a/docs/reference/pt_bitwise_or.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Bitwise Or -Performs a bitwise OR operation on two PyTorch tensors element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Bitwise operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_bitwise_right_shift.md b/docs/reference/pt_bitwise_right_shift.md deleted file mode 100644 index b206a8f..0000000 --- a/docs/reference/pt_bitwise_right_shift.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Bitwise Right Shift -Performs a bitwise right shift operation on two PyTorch tensors element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Bitwise operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_bitwise_xor.md b/docs/reference/pt_bitwise_xor.md deleted file mode 100644 index 4074bb6..0000000 --- a/docs/reference/pt_bitwise_xor.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Bitwise Xor -Performs a bitwise XOR operation on two PyTorch tensors element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Bitwise operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_bmm.md b/docs/reference/pt_bmm.md deleted file mode 100644 index 151f974..0000000 --- a/docs/reference/pt_bmm.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Bmm -Performs batched matrix multiplication on two 3D PyTorch tensors. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Matrix operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_bool_create.md b/docs/reference/pt_bool_create.md deleted file mode 100644 index e342555..0000000 --- a/docs/reference/pt_bool_create.md +++ /dev/null @@ -1,22 +0,0 @@ -# Pt Bool Create -Creates a PyTorch tensor of dtype bool from True or False values entered as a list in the text field. -For example, entering: -``` -[True, False, False] -``` -will create a 1D boolean tensor. - -## Input -| Name | Data type | -|---|---| -| data | String | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Tensor creation - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_cos.md b/docs/reference/pt_cos.md deleted file mode 100644 index d180b87..0000000 --- a/docs/reference/pt_cos.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Cos -Computes the cosine of a PyTorch tensor element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Math operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_cosh.md b/docs/reference/pt_cosh.md deleted file mode 100644 index 9e3b309..0000000 --- a/docs/reference/pt_cosh.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Cosh -Computes the hyperbolic cosine of a PyTorch tensor element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Math operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_crop.md b/docs/reference/pt_crop.md deleted file mode 100644 index 44ed92b..0000000 --- a/docs/reference/pt_crop.md +++ /dev/null @@ -1,23 +0,0 @@ -# Pt Crop -Crops a PyTorch tensor to the specified size. The input tensor must have a shape of (c, h, w) or (b, c, h, w). - -In ComfyUI Analysis, the tensor data type is "TENSOR," while in ComfyUI, the type "IMAGE" is used. To pass image data from ComfyUI to a ComfyUI Analysis node (e.g., this interpolation node), first use the "Pt From Image" node to convert it to a tensor, then transpose the axes from (b, h, w, c) to (b, c, h, w) before passing it to this node. - -This transposition can be performed using the "Pt Permute" node with (0, 2, 3, 1). To convert the output back to an image, apply "Pt Permute" with (0, 3, 1, 2) and then use the "Pt To Image" node. - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| height | Int | -| width | Int | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Image processing - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_data_loader.md b/docs/reference/pt_data_loader.md deleted file mode 100644 index 2ae51c8..0000000 --- a/docs/reference/pt_data_loader.md +++ /dev/null @@ -1,29 +0,0 @@ -# Pt Data Loader -Loads data from a dataset node and creates a PyTorch DataLoader. - -**dataset** -Connect a PtvDataset node that provides the dataset. - -**batch size** -Specifies the number of samples per batch. - -**shuffle** -If True, shuffles the dataset before each epoch. - -## Input -| Name | Data type | -|---|---| -| dataset | Ptvdataset | -| batch_size | Int | -| shuffle | Boolean | -| parameters | String | - -## Output -| Data type | -|---| -| Ptdataloader | - -
-Category: PyTorch wrapper - Training - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_div.md b/docs/reference/pt_div.md deleted file mode 100644 index 585cdb7..0000000 --- a/docs/reference/pt_div.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Div -Divides one PyTorch tensor by another element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Arithmetic operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_einsum.md b/docs/reference/pt_einsum.md deleted file mode 100644 index fa1fcda..0000000 --- a/docs/reference/pt_einsum.md +++ /dev/null @@ -1,26 +0,0 @@ -# Pt Einsum -Performs Tensor operations specified in the Einstein summation equation. - -Specify Einstein summation notation in the first socket. -Specify two tensors in tens_a, tens_b. If you specify the third tensor, specify in tens_c. -Otherwise, leave the socket unconnected. - -Sublist format is not supported. - -## Input -| Name | Data type | -|---|---| -| equation | String | -| tens_a | Tensor | -| tens_b | Tensor | -| tens_c | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Matrix operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_eq.md b/docs/reference/pt_eq.md deleted file mode 100644 index 9228fa7..0000000 --- a/docs/reference/pt_eq.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Eq -Tests whether two PyTorch tensors are equal element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Comparison operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_evaluate_classification_model.md b/docs/reference/pt_evaluate_classification_model.md deleted file mode 100644 index c78bce9..0000000 --- a/docs/reference/pt_evaluate_classification_model.md +++ /dev/null @@ -1,23 +0,0 @@ -# Pt Evaluate Classification Model -Performs inference on test data and computes evaluation metrics. - -## Input -| Name | Data type | -|---|---| -| model | Ptmodel | -| data_loader | Ptdataloader | -| use_gpu | Boolean | - -## Output -| Data type | -|---| -| Float | -| Float | -| Float | -| Float | -| Int | - -
-Category: PyTorch wrapper - Training - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_exp.md b/docs/reference/pt_exp.md deleted file mode 100644 index 8c4ffe0..0000000 --- a/docs/reference/pt_exp.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Exp -Performs an exponential operation on a PyTorch tensor element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Math operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_flatten.md b/docs/reference/pt_flatten.md deleted file mode 100644 index 9c32483..0000000 --- a/docs/reference/pt_flatten.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Flatten -Flattens a PyTorch tensor into a 1D tensor. - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Transform - -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 deleted file mode 100644 index d84a389..0000000 --- a/docs/reference/pt_float_create.md +++ /dev/null @@ -1,18 +0,0 @@ -# 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 - Tensor creation - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_floor_divide.md b/docs/reference/pt_floor_divide.md deleted file mode 100644 index 8f52b4f..0000000 --- a/docs/reference/pt_floor_divide.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Floor Div -Performs element-wise floor division on two PyTorch tensors. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Arithmetic operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_from_image.md b/docs/reference/pt_from_image.md deleted file mode 100644 index 75c6409..0000000 --- a/docs/reference/pt_from_image.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt From Image -Casts an Image tensor as a PyTorch tensor. - -## Input -| Name | Data type | -|---|---| -| image | Image | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Image processing - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_from_image_transpose.md b/docs/reference/pt_from_image_transpose.md deleted file mode 100644 index 767e5b9..0000000 --- a/docs/reference/pt_from_image_transpose.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt From Image Transpose -Casts an image tensor to a PyTorch tensor and transposes it from (H, W, C) to (C, H, W). For rank-4 inputs, the batch axis remains unchanged. - -## Input -| Name | Data type | -|---|---| -| image | Image | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Image processing - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_from_latent.md b/docs/reference/pt_from_latent.md deleted file mode 100644 index 5ecb5b3..0000000 --- a/docs/reference/pt_from_latent.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt From Latent -Casts a latent tensor as a PyTorch tensor. - -## Input -| Name | Data type | -|---|---| -| latent | Latent | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Tensor creation - -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 deleted file mode 100644 index 105284d..0000000 --- a/docs/reference/pt_from_numpy.md +++ /dev/null @@ -1,17 +0,0 @@ -# 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 - Tensor creation - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_full.md b/docs/reference/pt_full.md deleted file mode 100644 index df0a41b..0000000 --- a/docs/reference/pt_full.md +++ /dev/null @@ -1,21 +0,0 @@ -# Pt Full -Creates a PyTorch tensor filled with a specified value using the size entered in the text field. -For example, if you want to create a 4D tensor of batch size=2, channel=3, height=96, width=32 filled with 7, enter: -[2,3,96,32] for size and 7 for value. - -## Input -| Name | Data type | -|---|---| -| value | String | -| size | String | -| data_type | | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Tensor creation - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_gather.md b/docs/reference/pt_gather.md deleted file mode 100644 index 9da9410..0000000 --- a/docs/reference/pt_gather.md +++ /dev/null @@ -1,59 +0,0 @@ -# Pt Gather -Generates a tensor based on the index tensor using PyTorch's `gather` function. - -Specify the `dim` field using an integer to indicate the axis along which to gather elements. -Each input tensor element is selected based on the index tensor. - -For example, consider the following input tensor: -``` -[ [10, 20, 30, 40, 50], - [100, 200, 300, 400, 500]] -``` - -and the corresponding index tensor: - -``` -[ [0, 4, 3, 2, 0], - [4, 3, 0, 0, 0]] -``` - -If `dim = 0`, the function scans row-wise, treating each index value as a row index. -If `dim = 1`, it scans column-wise, interpreting each index value as a column index. - -Let's analyze the case where `dim = 1`: - -* Start at row 0, column 0. -* The index at this position is `index[0, 0] = 0`. -* PyTorch selects the value at position 0 from the array `[10, 20, 30, 40, 50]`, which is `10`. -* The result at this stage is: - ``` - [[10, ?, ?, ?, ?] - [?, ?, ?, ?, ?]] - ``` - -* Moving to column 1: `index[0, 1] = 4`. -* The value at index 4 in `[10, 20, 30, 40, 50]` is `50`. -* The updated result is: - ``` - [[10, 50, ?, ?, ?] - [?, ?, ?, ?, ?]] - ``` - -* Repeating this process for all elements constructs the final gathered tensor. - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| dim | Int | -| index | String | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Indexing and Slicing Operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_ge.md b/docs/reference/pt_ge.md deleted file mode 100644 index bd7b39c..0000000 --- a/docs/reference/pt_ge.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Ge -Tests whether elements in the first PyTorch tensor are greater than or equal to the corresponding elements in the second tensor. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Comparison operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_gt.md b/docs/reference/pt_gt.md deleted file mode 100644 index 5401090..0000000 --- a/docs/reference/pt_gt.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Gt -Tests whether elements in the first PyTorch tensor are greater than the corresponding elements in the second tensor. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Comparison operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_index_select.md b/docs/reference/pt_index_select.md deleted file mode 100644 index 09e6778..0000000 --- a/docs/reference/pt_index_select.md +++ /dev/null @@ -1,41 +0,0 @@ -# Pt Index Select -Extracts elements from the input tensor along a specified dimension using an index tensor. - -`dim` specifies the dimension along which the selection occurs. -* For example, for 2D tensors: -** If `dim=0`, `index` selects rows. -** If `dim=1`, `index` selects columns (for 2D tensors). - -`index` is a list of indices that will be converted into a tensor. - -Example: -``` -Input Tensor: -[[1, 2, 3], - [40, 50, 60], - [700, 800, 900]] - -index = "[0, 2]" -dim = 0 - -Output Tensor: -[[1, 2, 3], - [700, 800, 900]] -``` - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| dim | Int | -| index | String | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Indexing and Slicing Operations - -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 deleted file mode 100644 index 9f3d826..0000000 --- a/docs/reference/pt_int_create.md +++ /dev/null @@ -1,18 +0,0 @@ -# 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 - Tensor creation - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_interpolate_by_scale_factor.md b/docs/reference/pt_interpolate_by_scale_factor.md deleted file mode 100644 index ec781ac..0000000 --- a/docs/reference/pt_interpolate_by_scale_factor.md +++ /dev/null @@ -1,23 +0,0 @@ -# Pt Interpolate By Scale Factor -Resizes a PyTorch tensor using interpolation by scale factor. The input tensor must have a shape of (c, h, w) or (b, c, h, w). - -In ComfyUI Analysis, the tensor data type is "TENSOR," while in ComfyUI, the type "IMAGE" is used. To pass image data from ComfyUI to a ComfyUI Analysis node (e.g., this interpolation node), first use the "Pt From Image" node to convert it to a tensor, then transpose the axes from (b, h, w, c) to (b, c, h, w) before passing it to this node. - -This transposition can be performed using the "Pt Permute" node with (0, 2, 3, 1). To convert the output back to an image, apply "Pt Permute" with (0, 3, 1, 2) and then use the "Pt To Image" node. - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| scale_factor | Float | -| mode | | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Image processing - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_interpolate_to_size.md b/docs/reference/pt_interpolate_to_size.md deleted file mode 100644 index acec38d..0000000 --- a/docs/reference/pt_interpolate_to_size.md +++ /dev/null @@ -1,24 +0,0 @@ -# Pt Interpolate To Size -Resizes a PyTorch tensor using interpolation. The input tensor must have a shape of (c, h, w) or (b, c, h, w). - -In ComfyUI Analysis, the tensor data type is "TENSOR," while in ComfyUI, the type "IMAGE" is used. To pass image data from ComfyUI to a ComfyUI Analysis node (e.g., this interpolation node), first use the "Pt From Image" node to convert it to a tensor, then transpose the axes from (b, h, w, c) to (b, c, h, w) before passing it to this node. - -This transposition can be performed using the "Pt Permute" node with (0, 2, 3, 1). To convert the output back to an image, apply "Pt Permute" with (0, 3, 1, 2) and then use the "Pt To Image" node. - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| height | Int | -| width | Int | -| mode | | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Image processing - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_le.md b/docs/reference/pt_le.md deleted file mode 100644 index aee74d2..0000000 --- a/docs/reference/pt_le.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Le -Tests whether elements in the first PyTorch tensor are less than or equal to the corresponding elements in the second tensor. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Comparison operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_linspace.md b/docs/reference/pt_linspace.md deleted file mode 100644 index df29f03..0000000 --- a/docs/reference/pt_linspace.md +++ /dev/null @@ -1,23 +0,0 @@ -# Pt Linspace -Creates a PyTorch tensor using `torch.linspace` with the specified start, end, and steps values. - -The start and end values are parsed from string inputs and converted to a float. -The steps value is converted to an int. - -## Input -| Name | Data type | -|---|---| -| start | String | -| end | String | -| steps | String | -| data_type | | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Tensor creation - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_load_model.md b/docs/reference/pt_load_model.md deleted file mode 100644 index 0466004..0000000 --- a/docs/reference/pt_load_model.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Load Model -A wrapper class for saving a PyTorch model. - -## Input -| Name | Data type | -|---|---| -| model | Ptmodel | -| model_path | String | - -## Output -| Data type | -|---| -| Ptmodel | - -
-Category: PyTorch wrapper - Training - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_log.md b/docs/reference/pt_log.md deleted file mode 100644 index f15a101..0000000 --- a/docs/reference/pt_log.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Log -Computes the natural logarithm (log base e) of a PyTorch tensor element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Math operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_logical_and.md b/docs/reference/pt_logical_and.md deleted file mode 100644 index 10255b1..0000000 --- a/docs/reference/pt_logical_and.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Logical And -Performs a logical AND operation on two PyTorch tensors element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Logical operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_logical_not.md b/docs/reference/pt_logical_not.md deleted file mode 100644 index 66616e3..0000000 --- a/docs/reference/pt_logical_not.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Logical Not -Performs a logical NOT operation on a PyTorch tensor element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Logical operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_logical_or.md b/docs/reference/pt_logical_or.md deleted file mode 100644 index 3519a38..0000000 --- a/docs/reference/pt_logical_or.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Logical Or -Performs a logical OR operation on two PyTorch tensors element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Logical operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_logical_xor.md b/docs/reference/pt_logical_xor.md deleted file mode 100644 index c67fe55..0000000 --- a/docs/reference/pt_logical_xor.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Logical Xor -Performs a logical XOR operation on two PyTorch tensors element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Logical operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_lt.md b/docs/reference/pt_lt.md deleted file mode 100644 index b44c48b..0000000 --- a/docs/reference/pt_lt.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Lt -Tests whether elements in the first PyTorch tensor are less than the corresponding elements in the second tensor. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Comparison operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_masked_select.md b/docs/reference/pt_masked_select.md deleted file mode 100644 index 80b9e15..0000000 --- a/docs/reference/pt_masked_select.md +++ /dev/null @@ -1,34 +0,0 @@ -# Pt Masked Select -Extracts elements from the input tensor whose corresponding value in `masked_tens` is `True`. - -Example: -``` -Input Tensor: -[[1, 2, 3], - [40, 50, 60], - [700, 800, 900]] - -Masked Tensor: -[[True, False, False], - [False, True, False], - [False, False, True]] - -Output Tensor: -[1, 50, 900] -``` - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| masked_tens | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Indexing and Slicing Operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_matmul.md b/docs/reference/pt_matmul.md deleted file mode 100644 index 8536132..0000000 --- a/docs/reference/pt_matmul.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Mat Mul -Performs matrix multiplication on two PyTorch tensors. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Matrix operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_max.md b/docs/reference/pt_max.md deleted file mode 100644 index 1f8b497..0000000 --- a/docs/reference/pt_max.md +++ /dev/null @@ -1,24 +0,0 @@ -# Pt Max -Computes the maximum values of a PyTorch tensor along the specified dimension(s). - -Specify the dimension(s) in the `dim` field using an integer, a list, or a tuple, as shown below: -``` -0, [0], or (1, 2) -``` - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| dim | String | -| keepdim | Boolean | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Reduction operation & Summary statistics - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_mean.md b/docs/reference/pt_mean.md deleted file mode 100644 index e202099..0000000 --- a/docs/reference/pt_mean.md +++ /dev/null @@ -1,26 +0,0 @@ -# Pt Mean -Computes the mean of a PyTorch tensor along the specified dimension(s). - -This node supports only floating-point or complex tensors as input. - -Specify the dimension(s) in the `dim` field using an integer, a list, or a tuple, as shown below: -``` -0, [0], or (1, 2) -``` - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| dim | String | -| keepdim | Boolean | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Reduction operation & Summary statistics - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_median.md b/docs/reference/pt_median.md deleted file mode 100644 index 1d698d9..0000000 --- a/docs/reference/pt_median.md +++ /dev/null @@ -1,29 +0,0 @@ -# Pt Median -Computes the median of a PyTorch tensor along the specified dimension(s). - -Specify the dimension(s) in the `dim` field using an integer as shown below: -``` -0 -``` -or -``` -1 -``` -Note that PtMedian calls torch.median(), which returns the lower of the two numbers when the true median falls between them. - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| dim | String | -| keepdim | Boolean | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Reduction operation & Summary statistics - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_min.md b/docs/reference/pt_min.md deleted file mode 100644 index f0dc408..0000000 --- a/docs/reference/pt_min.md +++ /dev/null @@ -1,24 +0,0 @@ -# Pt Min -Computes the minimum values of a PyTorch tensor along the specified dimension(s). - -Specify the dimension(s) in the `dim` field using an integer, a list, or a tuple, as shown below: -``` -0, [0], or (1, 2) -``` - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| dim | String | -| keepdim | Boolean | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Reduction operation & Summary statistics - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_mm.md b/docs/reference/pt_mm.md deleted file mode 100644 index c6524b9..0000000 --- a/docs/reference/pt_mm.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Mm -Performs 2D matrix multiplication on two PyTorch tensors. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Matrix operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_mul.md b/docs/reference/pt_mul.md deleted file mode 100644 index cda34b4..0000000 --- a/docs/reference/pt_mul.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Mul -Multiplies two PyTorch tensors element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Arithmetic operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_ne.md b/docs/reference/pt_ne.md deleted file mode 100644 index 37e88c7..0000000 --- a/docs/reference/pt_ne.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Ne -Tests whether two PyTorch tensors are not equal element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Comparison operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_neg.md b/docs/reference/pt_neg.md deleted file mode 100644 index 23d9114..0000000 --- a/docs/reference/pt_neg.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Neg -Computes the negation of each element in a PyTorch tensor. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Math operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_ones.md b/docs/reference/pt_ones.md deleted file mode 100644 index 5f42dc7..0000000 --- a/docs/reference/pt_ones.md +++ /dev/null @@ -1,20 +0,0 @@ -# Pt Ones -Creates a PyTorch tensor of ones using the size entered in the text field. -For example, if you want to create a 4D tensor of batch size=2, channel=3, height=96, width=32, enter: -[2,3,96,32] - -## Input -| Name | Data type | -|---|---| -| size | String | -| data_type | | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Tensor creation - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_pad.md b/docs/reference/pt_pad.md deleted file mode 100644 index 2353f26..0000000 --- a/docs/reference/pt_pad.md +++ /dev/null @@ -1,23 +0,0 @@ -# Pt Pad -Pads a PyTorch tensor to the specified size. Padded area will be black. The input tensor must have a shape of (c, h, w) or (b, c, h, w). - -In ComfyUI Analysis, the tensor data type is "TENSOR," while in ComfyUI, the type "IMAGE" is used. To pass image data from ComfyUI to a ComfyUI Analysis node (e.g., this interpolation node), first use the "Pt From Image" node to convert it to a tensor, then transpose the axes from (b, h, w, c) to (b, c, h, w) before passing it to this node. - -This transposition can be performed using the "Pt Permute" node with (0, 2, 3, 1). To convert the output back to an image, apply "Pt Permute" with (0, 3, 1, 2) and then use the "Pt To Image" node. - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| height | Int | -| width | Int | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Image processing - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_permute.md b/docs/reference/pt_permute.md deleted file mode 100644 index 8fa187f..0000000 --- a/docs/reference/pt_permute.md +++ /dev/null @@ -1,25 +0,0 @@ -# Pt Permute -Permutes the dimensions of a PyTorch tensor according to the specified order. -For example, if a tensor has shape (2, 3, 96, 32) and `new_axes="[0, 3, 1, 2]"`, -the output will have shape (2, 32, 3, 96). - -Specify the new axis order in the `new_axes` field using brackets, as shown below: -``` -[0, 3, 1, 2] -``` - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| new_axes | String | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Transform - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_pow.md b/docs/reference/pt_pow.md deleted file mode 100644 index 1d717bf..0000000 --- a/docs/reference/pt_pow.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Pow -Raises one PyTorch tensor to the power of another element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Arithmetic operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_predict_classification_model.md b/docs/reference/pt_predict_classification_model.md deleted file mode 100644 index 217f61a..0000000 --- a/docs/reference/pt_predict_classification_model.md +++ /dev/null @@ -1,22 +0,0 @@ -# Pt Predict Classification Model -Performs inference on input data. - -## Input -| Name | Data type | -|---|---| -| model | Ptmodel | -| inputs | Tensor | -| class_id_to_name_map | Pydict | -| use_gpu | Boolean | - -## Output -| Data type | -|---| -| String | -| Int | -| Float | - -
-Category: PyTorch wrapper - Training - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_prod.md b/docs/reference/pt_prod.md deleted file mode 100644 index f7d2f37..0000000 --- a/docs/reference/pt_prod.md +++ /dev/null @@ -1,28 +0,0 @@ -# Pt Prod -Computes the product of a PyTorch tensor along the specified dimension(s). - -Specify the dimension(s) in the `dim` field using an integer as shown below: -``` -0 -``` -or -``` -1 -``` - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| dim | String | -| keepdim | Boolean | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Reduction operation & Summary statistics - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_rand.md b/docs/reference/pt_rand.md deleted file mode 100644 index 34ccd58..0000000 --- a/docs/reference/pt_rand.md +++ /dev/null @@ -1,21 +0,0 @@ -# Pt Rand -Creates a PyTorch tensor with values sampled from a uniform distribution -using the size entered in the text field. -For example, if you want to create a 4D tensor of batch size=2, channel=3, height=96, width=32, enter: -[2,3,96,32] - -## Input -| Name | Data type | -|---|---| -| size | String | -| data_type | | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Tensor creation - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_rand_int.md b/docs/reference/pt_rand_int.md deleted file mode 100644 index 16552cd..0000000 --- a/docs/reference/pt_rand_int.md +++ /dev/null @@ -1,22 +0,0 @@ -# Pt Rand Int -Creates a PyTorch tensor filled with random integers within a specified range using the size entered in the text field. -For example, if you want to create a 4D tensor of batch size=2, channel=3, height=96, width=32 with random integers in the range [0, 10), -enter [2,3,96,32] for size, 0 for min_value, and 10 for max_value. - -## Input -| Name | Data type | -|---|---| -| min_value | Int | -| max_value | Int | -| size | String | -| data_type | | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Tensor creation - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_randn.md b/docs/reference/pt_randn.md deleted file mode 100644 index 8c2057b..0000000 --- a/docs/reference/pt_randn.md +++ /dev/null @@ -1,21 +0,0 @@ -# Pt Randn -Creates a PyTorch tensor with values sampled from a standard normal distribution (mean=0, std=1) -using the size entered in the text field. -For example, if you want to create a 4D tensor of batch size=2, channel=3, height=96, width=32, enter: -[2,3,96,32] - -## Input -| Name | Data type | -|---|---| -| size | String | -| data_type | | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Tensor creation - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_remainder.md b/docs/reference/pt_remainder.md deleted file mode 100644 index e4f68e6..0000000 --- a/docs/reference/pt_remainder.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Remainder -Computes the element-wise remainder of division between two PyTorch tensors. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Arithmetic operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_reshape.md b/docs/reference/pt_reshape.md deleted file mode 100644 index fecad29..0000000 --- a/docs/reference/pt_reshape.md +++ /dev/null @@ -1,27 +0,0 @@ -# Pt Reshape -Reshapes a PyTorch tensor into a specified shape using `torch.reshape()`. -The total number of elements must remain unchanged. - -For example, if a tensor has shape (2, 3, 4), and `new_shape="[6, 4]"`, -the output will have shape (6, 4). - -Use -1 to automatically infer a dimension, e.g.: -``` -[2, -1] -``` - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| new_shape | String | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Transform - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_save_model.md b/docs/reference/pt_save_model.md deleted file mode 100644 index da2d30c..0000000 --- a/docs/reference/pt_save_model.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Save Model -A wrapper class for saving a PyTorch model. - -## Input -| Name | Data type | -|---|---| -| model | Ptmodel | -| model_path | String | - -## Output -| Data type | -|---| -| String | - -
-Category: IO - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_scatter.md b/docs/reference/pt_scatter.md deleted file mode 100644 index 719ea0c..0000000 --- a/docs/reference/pt_scatter.md +++ /dev/null @@ -1,24 +0,0 @@ -# Pt Scatter -Generates a new tensor by replacing values at specified positions using an index tensor. - -* Target tensor (tens): Some of whose values are replaced. -* Source tensor (src): Values from this tensor replace a part or whole of the target tensor. -* Index tensor (index): Specifies the positions in the target tensor for replacement. - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| dim | Int | -| index | String | -| src | String | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Indexing and Slicing Operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_show_size.md b/docs/reference/pt_show_size.md deleted file mode 100644 index 8830fb1..0000000 --- a/docs/reference/pt_show_size.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Show Size -Displays PyTorch Size object as a string. - -## Input -| Name | Data type | -|---|---| -| size | Ptsize | - -## Output -| Data type | -|---| -| String | - -
-Category: Display data - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_show_text.md b/docs/reference/pt_show_text.md deleted file mode 100644 index 40e397d..0000000 --- a/docs/reference/pt_show_text.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Show Text -Displays PyTorch tensor as a string. Note that the tensor is partially printed out when -the tensor contains more elements than the threshold set internally. - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | - -## Output -| Data type | -|---| -| String | - -
-Category: Display data - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_sin.md b/docs/reference/pt_sin.md deleted file mode 100644 index 158065b..0000000 --- a/docs/reference/pt_sin.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Sin -Computes the sine of a PyTorch tensor element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Math operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_sinh.md b/docs/reference/pt_sinh.md deleted file mode 100644 index 755828f..0000000 --- a/docs/reference/pt_sinh.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Sinh -Computes the hyperbolic sine of a PyTorch tensor element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Math operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_size.md b/docs/reference/pt_size.md deleted file mode 100644 index a879754..0000000 --- a/docs/reference/pt_size.md +++ /dev/null @@ -1,17 +0,0 @@ -# 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 | - -
-Category: PyTorch wrapper - Size object support - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_size_create.md b/docs/reference/pt_size_create.md deleted file mode 100644 index 8aee4ab..0000000 --- a/docs/reference/pt_size_create.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Size Create -Creates a PyTorch Size using values entered in the text field. - -## Input -| Name | Data type | -|---|---| -| data | String | - -## Output -| Data type | -|---| -| Ptsize | - -
-Category: PyTorch wrapper - Size object support - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_size_to_numpy.md b/docs/reference/pt_size_to_numpy.md deleted file mode 100644 index 784063d..0000000 --- a/docs/reference/pt_size_to_numpy.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Size To Numpy -Converts PyTorch Size object to NumPy ndarray. - -## Input -| Name | Data type | -|---|---| -| sz | Ptsize | - -## Output -| Data type | -|---| -| Ndarray | - -
-Category: PyTorch wrapper - Size object support - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_size_to_string.md b/docs/reference/pt_size_to_string.md deleted file mode 100644 index 2e94aaa..0000000 --- a/docs/reference/pt_size_to_string.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Size To String -Converts PyTorch Size object to a Python string. - -## Input -| Name | Data type | -|---|---| -| sz | Ptsize | - -## Output -| Data type | -|---| -| String | - -
-Category: PyTorch wrapper - Size object support - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_sqrt.md b/docs/reference/pt_sqrt.md deleted file mode 100644 index 0b6165c..0000000 --- a/docs/reference/pt_sqrt.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Sqrt -Computes the square root of each element in a PyTorch tensor. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Math operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_squeeze.md b/docs/reference/pt_squeeze.md deleted file mode 100644 index b11c993..0000000 --- a/docs/reference/pt_squeeze.md +++ /dev/null @@ -1,20 +0,0 @@ -# Pt Squeeze -Removes a dimension at the specified position in the input tensor if it is of size 1. - -Specifying a tuple of dimensions is not supported yet. - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| dim | Int | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Transform - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_std.md b/docs/reference/pt_std.md deleted file mode 100644 index 16ebc9b..0000000 --- a/docs/reference/pt_std.md +++ /dev/null @@ -1,28 +0,0 @@ -# Pt Std -Computes the standard deviation of a PyTorch tensor along the specified dimension(s). - -Specify 1 to use Bessel's correction (N-1) for sample standard deviation. -Specify 0 to compute population standard deviation. - -Specify the dimension(s) in the `dim` field using an integer, a list, or a tuple, as shown below: -``` -0, [0], or (1, 2) -``` - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| correction | Int | -| dim | String | -| keepdim | Boolean | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Reduction operation & Summary statistics - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_sub.md b/docs/reference/pt_sub.md deleted file mode 100644 index 827a938..0000000 --- a/docs/reference/pt_sub.md +++ /dev/null @@ -1,18 +0,0 @@ -# Pt Sub -Subtracts one PyTorch tensor from another. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | -| tens_b | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Arithmetic operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_sum.md b/docs/reference/pt_sum.md deleted file mode 100644 index 99608c8..0000000 --- a/docs/reference/pt_sum.md +++ /dev/null @@ -1,24 +0,0 @@ -# Pt Sum -Computes the sum of a PyTorch tensor along the specified dimension(s). - -Specify the dimension(s) in the `dim` field using an integer, a list, or a tuple, as shown below: -``` -0, [0], or (1, 2) -``` - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| dim | String | -| keepdim | Boolean | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Reduction operation & Summary statistics - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_tan.md b/docs/reference/pt_tan.md deleted file mode 100644 index a05b3a5..0000000 --- a/docs/reference/pt_tan.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Tan -Computes the tangent of a PyTorch tensor element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Math operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_tanh.md b/docs/reference/pt_tanh.md deleted file mode 100644 index eff2b90..0000000 --- a/docs/reference/pt_tanh.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt Tanh -Computes the hyperbolic tangent of a PyTorch tensor element-wise. - -## Input -| Name | Data type | -|---|---| -| tens_a | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Math operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_to_image.md b/docs/reference/pt_to_image.md deleted file mode 100644 index a5f86e8..0000000 --- a/docs/reference/pt_to_image.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt To Image -Casts a PyTorch tensor as an Image tensor. - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | - -## Output -| Data type | -|---| -| Image | - -
-Category: PyTorch wrapper - Image processing - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_to_image_transpose.md b/docs/reference/pt_to_image_transpose.md deleted file mode 100644 index 21af352..0000000 --- a/docs/reference/pt_to_image_transpose.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt To Image Transpose -Casts a PyTorch tensor as an Image tensor and transposes it from (C, H, W) to (H, W, C). For rank-4 inputs, the batch axis remains unchanged. - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | - -## Output -| Data type | -|---| -| Image | - -
-Category: PyTorch wrapper - Image processing - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_to_latent.md b/docs/reference/pt_to_latent.md deleted file mode 100644 index 9a1278c..0000000 --- a/docs/reference/pt_to_latent.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt To Latent -Casts a PyTorch tensor as a latent tensor. - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | - -## Output -| Data type | -|---| -| Latent | - -
-Category: PyTorch wrapper - Tensor data conversion - -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 deleted file mode 100644 index 47821d3..0000000 --- a/docs/reference/pt_to_numpy.md +++ /dev/null @@ -1,17 +0,0 @@ -# Pt To Numpy -Converts PyTorch tensor to NumPy ndarray. - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | - -## Output -| Data type | -|---| -| Ndarray | - -
-Category: PyTorch wrapper - Tensor data conversion - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_to_rgb_tensors.md b/docs/reference/pt_to_rgb_tensors.md deleted file mode 100644 index aff0b37..0000000 --- a/docs/reference/pt_to_rgb_tensors.md +++ /dev/null @@ -1,19 +0,0 @@ -# Pt To Rgb Tensors -Splits a PyTorch tensor into R, G, and B tensors. - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | - -## Output -| Data type | -|---| -| Tensor | -| Tensor | -| Tensor | - -
-Category: PyTorch wrapper - Tensor data conversion - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_train_classification_model.md b/docs/reference/pt_train_classification_model.md deleted file mode 100644 index 1ceaac6..0000000 --- a/docs/reference/pt_train_classification_model.md +++ /dev/null @@ -1,24 +0,0 @@ -# Pt Train Classification Model -Trains a classification model using a given dataset, optimizer, and number of epochs. - -## Input -| Name | Data type | -|---|---| -| model | Ptmodel | -| train_loader | Ptdataloader | -| optimizer | Ptoptimizer | -| epochs | Int | -| use_gpu | Boolean | -| val_loader | Ptdataloader | - -## Output -| Data type | -|---| -| Ptmodel | -| Tensor | -| Tensor | - -
-Category: PyTorch wrapper - Training - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_unsqueeze.md b/docs/reference/pt_unsqueeze.md deleted file mode 100644 index 96bc5b1..0000000 --- a/docs/reference/pt_unsqueeze.md +++ /dev/null @@ -1,19 +0,0 @@ -# Pt Unsqueeze -Adds a singleton dimension at the specified position in the input tensor. -For example, if a tensor has shape (3, 4) and `dim=1`, the output will have shape (3, 1, 4). - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| dim | Int | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Transform - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_var.md b/docs/reference/pt_var.md deleted file mode 100644 index 383af73..0000000 --- a/docs/reference/pt_var.md +++ /dev/null @@ -1,28 +0,0 @@ -# Pt Var -Computes the variance of a PyTorch tensor along the specified dimension(s). - -Specify 1 to use Bessel's correction (N-1) for sample variance. -Specify 0 to compute population variance. - -Specify the dimension(s) in the `dim` field using an integer, a list, or a tuple, as shown below: -``` -0, [0], or (1, 2) -``` - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| correction | Int | -| dim | String | -| keepdim | Boolean | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Reduction operation & Summary statistics - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_view.md b/docs/reference/pt_view.md deleted file mode 100644 index d366f3e..0000000 --- a/docs/reference/pt_view.md +++ /dev/null @@ -1,27 +0,0 @@ -# Pt View -Reshapes a PyTorch tensor into a specified shape using `torch.view()`. -The total number of elements must remain unchanged. - -For example, if a tensor has shape (2, 3, 4), and `new_shape="[6, 4]"`, -the output will have shape (6, 4). - -Use -1 to automatically infer a dimension, e.g.: -``` -[2, -1] -``` - -## Input -| Name | Data type | -|---|---| -| tens | Tensor | -| new_shape | String | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Transform - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_where.md b/docs/reference/pt_where.md deleted file mode 100644 index b10ead9..0000000 --- a/docs/reference/pt_where.md +++ /dev/null @@ -1,23 +0,0 @@ -# Pt Where -Generates a new tensor by selecting values based on a condition tensor. - -* `condition_tens` (torch.Tensor): Boolean tensor that determines which values are selected. -* `true_tens` (torch.Tensor): Values to use where the condition is `True`. -* `false_tens` (torch.Tensor): Values to use where the condition is `False`. - -## Input -| Name | Data type | -|---|---| -| condition_tens | Tensor | -| true_tens | Tensor | -| false_tens | Tensor | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Indexing and Slicing Operations - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pt_zeros.md b/docs/reference/pt_zeros.md deleted file mode 100644 index 7019740..0000000 --- a/docs/reference/pt_zeros.md +++ /dev/null @@ -1,20 +0,0 @@ -# Pt Zeros -Creates a PyTorch tensor of zeros using the size entered in the text field. -For example, if you want to create a 4D tensor of batch size=2, channel=3, height=96, width=32, enter: -[2,3,96,32] - -## Input -| Name | Data type | -|---|---| -| size | String | -| data_type | | - -## Output -| Data type | -|---| -| Tensor | - -
-Category: PyTorch wrapper - Tensor creation - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptn_conv_model.md b/docs/reference/ptn_conv_model.md deleted file mode 100644 index d881ea0..0000000 --- a/docs/reference/ptn_conv_model.md +++ /dev/null @@ -1,45 +0,0 @@ -# Ptn Conv Model -A convolutional model consisting of multiple convolutional layers. - -### Fields: -- `input_dim`: A string representing the input dimensions in the format "(C,H,W)". - Example: `"(3,28,28)"` for a 3-channel 28x28 image. - -- `penultimate_dim`: An integer specifying the number of features before the output layer. If you specify 0, the number is computed internally. - Default: `0` - -- `output_dim`: An integer specifying the number of output features, which should match the number of target classes in classification. - Default: `10`, Min: `1`, Max: `1e6`. - -- `channel_list`: A string representing a Python list of integers specifying the number of channels per layer excluding the channel for the input (e.g. 3 for the color image). - Example: `"[32,64,128,256,512]"`. - -- `kernel_size_list`: A string representing a Python list of kernel sizes per layer. - Example: `"[3,3,3,3,1]"`. - -- `padding_list`: A string representing a Python list of padding values per layer. - Example: `"[1,1,1,1,0]"`. - -- `downsample_list`: A string representing a Python list of boolean values indicating whether to downsample at each layer. - Example: `"[True,True,True,True,False]"`. - -## Input -| Name | Data type | -|---|---| -| input_dim | String | -| penultimate_dim | Int | -| output_dim | Int | -| channel_list | String | -| kernel_size_list | String | -| padding_list | String | -| downsample_list | String | - -## Output -| Data type | -|---| -| Ptmodel | - -
-Category: PyTorch wrapper - Training - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptn_linear_model.md b/docs/reference/ptn_linear_model.md deleted file mode 100644 index a9788fa..0000000 --- a/docs/reference/ptn_linear_model.md +++ /dev/null @@ -1,35 +0,0 @@ -# Ptn Linear Model -A linear model consisting of dense layers. - -### Fields: -- `dim_list`: A string representing a Python list of layer dimensions. - It should contain the input dimension for the first layer and the output dimensions - for all layers. - - Example for a two-layer network: - - Input data: 784 - - Layer 1: 784 → 128 - - Layer 2: 128 → 10 - - Specify `"[784,128,10]"` - -- `bias_list`: A string representing a Python list of bias usage (True/False) per layer. - Example: "[True,False]" - -- `num_layers`: The number of layers in the model. - -## Input -| Name | Data type | -|---|---| -| dim_list | String | -| bias_list | String | -| num_layers | Int | - -## Output -| Data type | -|---| -| Ptmodel | - -
-Category: PyTorch wrapper - Training - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/pto_adam.md b/docs/reference/pto_adam.md deleted file mode 100644 index 257420e..0000000 --- a/docs/reference/pto_adam.md +++ /dev/null @@ -1,20 +0,0 @@ -# Pto Adam -Instantiates the Adam optimizer. - -## Input -| Name | Data type | -|---|---| -| model | Ptmodel | -| learning_rate | Float | -| beta1 | Float | -| beta2 | Float | - -## Output -| Data type | -|---| -| Ptoptimizer | - -
-Category: PyTorch wrapper - Training - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptv_dataset.md b/docs/reference/ptv_dataset.md deleted file mode 100644 index 1258c76..0000000 --- a/docs/reference/ptv_dataset.md +++ /dev/null @@ -1,51 +0,0 @@ -# Ptv Dataset -A Torchvision Dataset class wrapper. - -Parameters - -**name** - Specify a dataset class name such as MNIST, FashionMNIST in string. - This will be converted to a dataset class internally. - -**download** - Set to True if you want to download. - -**root** - Specify the root directory of the downloaded dataset relative to the - dataset directory of this extension, or specify the absolute path. - If you leave as a blank, the dataset will be downloaded under the `datasets` directory - of this extension. - -**transform** - Data transforms e.g. transform to PyTorch tensor, normalize image. - Plug in the PTV Transforms node that contains Torchvision Transforms functionality. - -**parameters** - Specify other parameters in Python dict format. - For example, if you want to set Train parameter to True to download the train set, specify: - ``` - {"train": True} - ``` - or if you want to download the test set, specify: - ``` - {"train": False} - ``` - -## Input -| Name | Data type | -|---|---| -| name | String | -| download | Boolean | -| root | String | -| transform | Ptvtransform | -| parameters | String | - -## Output -| Data type | -|---| -| Ptvdataset | - -
-Category: PyTorch wrapper - Training - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptv_dataset_loader.md b/docs/reference/ptv_dataset_loader.md deleted file mode 100644 index ee13ac5..0000000 --- a/docs/reference/ptv_dataset_loader.md +++ /dev/null @@ -1,60 +0,0 @@ -# Ptv Dataset Loader -A node to combine the dataset and data loader into a single node. - -Transform is internally called to convert input data to PyTorch tensors. - -Parameters - -**name** - Specify the dataset class name such as MNIST, FashionMNIST in string. - This will be converted to a dataset class internally. - -**download** - Set to True if you want to download. - -**root** - Specify the root directory of the downloaded dataset relative to the - dataset directory of this extension, or specify the absolute path. - If you leave as a blank, the dataset will be downloaded under the `datasets` directory - of this extension. - -**batch_size** - The number of samples per batch. - -**shuffle** - If True, shuffles the dataset before each epoch. - -**dataset parameters** - Specify other parameters in Python dict format. - For example, if you want to set Train parameter to True to download the train set, specify: - ``` - {"train": True} - ``` - or if you want to download the test set, specify: - ``` - {"train": False} - ``` - -**load parameters** - Additional parameters in Python dictionary format. - -## Input -| Name | Data type | -|---|---| -| name | String | -| download | Boolean | -| root | String | -| batch_size | Int | -| shuffle | Boolean | -| dataset_parameters | String | -| load_parameters | String | - -## Output -| Data type | -|---| -| Ptdataloader | - -
-Category: PyTorch wrapper - Training - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptv_image_folder_dataset.md b/docs/reference/ptv_image_folder_dataset.md deleted file mode 100644 index b973b55..0000000 --- a/docs/reference/ptv_image_folder_dataset.md +++ /dev/null @@ -1,28 +0,0 @@ -# Ptv Image Folder Dataset -A Torchvision ImageFolder Dataset class wrapper. - -Parameters - -**root** - Specify the root directory that contains subdirectories which of which contains images for each class. - The subdirectory name needs to be named after each class. - -**transform** - Data transforms e.g. transform to PyTorch tensor, normalize image. - Plug in the PTV Transforms node that contains Torchvision Transforms functionality. - -## Input -| Name | Data type | -|---|---| -| root | String | -| transform | Ptvtransform | - -## Output -| Data type | -|---| -| Ptvdataset | - -
-Category: PyTorch wrapper - Training - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptv_transforms_resize.md b/docs/reference/ptv_transforms_resize.md deleted file mode 100644 index 159626f..0000000 --- a/docs/reference/ptv_transforms_resize.md +++ /dev/null @@ -1,18 +0,0 @@ -# Ptv Transforms Resize -Resizes and transforms elements of dataset to PyTorch tensors. - -## Input -| Name | Data type | -|---|---| -| height | Int | -| width | Int | - -## Output -| Data type | -|---| -| Ptvtransform | - -
-Category: PyTorch wrapper - Training - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/docs/reference/ptv_transforms_to_tensor.md b/docs/reference/ptv_transforms_to_tensor.md deleted file mode 100644 index e1ca095..0000000 --- a/docs/reference/ptv_transforms_to_tensor.md +++ /dev/null @@ -1,12 +0,0 @@ -# Ptv Transforms To Tensor -Transforms elements of dataset to PyTorch tensors. - -## Output -| Data type | -|---| -| Ptvtransform | - -
-Category: PyTorch wrapper - Training - -ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/examples/workflows/dog_cat_classification_eval_model.json b/examples/workflows/dog_cat_classification_eval_model.json deleted file mode 100644 index aff7eff..0000000 --- a/examples/workflows/dog_cat_classification_eval_model.json +++ /dev/null @@ -1 +0,0 @@ -{"last_node_id":54,"last_link_id":67,"nodes":[{"id":49,"type":"PtDataLoader","pos":[2664.0126953125,2004.834716796875],"size":[308.1214599609375,135.3099822998047],"flags":{},"order":5,"mode":0,"inputs":[{"name":"dataset","type":"PTVDATASET","link":64}],"outputs":[{"name":"PTDATALOADER","type":"PTDATALOADER","links":[65],"slot_index":0}],"properties":{"Node name for S&R":"PtDataLoader"},"widgets_values":[8,false,"{\"num_workers\":1}"]},{"id":50,"type":"PtvImageFolderDataset","pos":[2324.53857421875,2006.695068359375],"size":[315,58],"flags":{},"order":3,"mode":0,"inputs":[{"name":"transform","type":"PTVTRANSFORM","link":67,"shape":7}],"outputs":[{"name":"PTVDATASET","type":"PTVDATASET","links":[64],"slot_index":0}],"properties":{"Node name for S&R":"PtvImageFolderDataset"},"widgets_values":["/container/transfer/datasets/dog_and_cat/dog_and_cat/val/"]},{"id":53,"type":"PtvTransformsResize","pos":[1959.0155029296875,2007.09521484375],"size":[315,82],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"PTVTRANSFORM","type":"PTVTRANSFORM","links":[67],"slot_index":0}],"properties":{"Node name for S&R":"PtvTransformsResize"},"widgets_values":[256,256]},{"id":48,"type":"PtnConvModel","pos":[2666.497314453125,1718.6485595703125],"size":[315,202],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[{"name":"PTMODEL","type":"PTMODEL","links":[62],"slot_index":0}],"properties":{"Node name for S&R":"PtnConvModel"},"widgets_values":["(3,256,256)",0,2,"[32,64,128,256,512]","[3,3,3,3,3]","[1,1,1,1,1]","[True,True,True,True,True]"]},{"id":47,"type":"PtEvaluateClassificationModel","pos":[3073.28466796875,1998.2191162109375],"size":[268.79998779296875,148.22767639160156],"flags":{},"order":6,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":55},{"name":"data_loader","type":"PTDATALOADER","link":65}],"outputs":[{"name":"accuracy","type":"FLOAT","links":[66],"slot_index":0},{"name":"precision","type":"FLOAT","links":[58],"slot_index":1},{"name":"recall","type":"FLOAT","links":[59],"slot_index":2},{"name":"f1","type":"FLOAT","links":[60],"slot_index":3},{"name":"num_samples","type":"INT","links":[61],"slot_index":4}],"properties":{"Node name for S&R":"PtEvaluateClassificationModel"},"widgets_values":[true]},{"id":33,"type":"CDAShowInt","pos":[3507.768310546875,2136.57861328125],"size":[210,79.09933471679688],"flags":{},"order":11,"mode":0,"inputs":[{"name":"int_scalar","type":"INT","link":61,"widget":{"name":"int_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Num Samples","properties":{"Node name for S&R":"CDAShowInt"},"widgets_values":[0,"300"]},{"id":30,"type":"CDAShowFloat","pos":[3507.768310546875,1986.105712890625],"size":[210,76],"flags":{},"order":9,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":59,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Recall","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.9"]},{"id":31,"type":"CDAShowFloat","pos":[3768.4755859375,1980.9920654296875],"size":[210,76],"flags":{},"order":10,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":60,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"F1","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.8997817469155049"]},{"id":29,"type":"CDAShowFloat","pos":[3768.4755859375,1834.9833984375],"size":[210,76],"flags":{},"order":8,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":58,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Precision","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.9035150645624104"]},{"id":52,"type":"CDAShowFloat","pos":[3507.768310546875,1834.7109375],"size":[210,76],"flags":{},"order":7,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":66,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Accuracy","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.9"]},{"id":23,"type":"PtLoadModel","pos":[3075.703369140625,1723.319580078125],"size":[261.13433837890625,74.19380950927734],"flags":{},"order":4,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":62}],"outputs":[{"name":"model","type":"PTMODEL","links":[55],"slot_index":0}],"properties":{"Node name for S&R":"PtLoadModel"},"widgets_values":["dog_cat.pt"]},{"id":54,"type":"MarkdownNote","pos":[2328.715576171875,1839.947509765625],"size":[313.0150451660156,99.62860870361328],"flags":{},"order":2,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Load val data\nYou need set the root field to point to the **val** directory of the dataset you downloaded."],"color":"#322","bgcolor":"#533"}],"links":[[55,23,0,47,0,"PTMODEL"],[58,47,1,29,0,"FLOAT"],[59,47,2,30,0,"FLOAT"],[60,47,3,31,0,"FLOAT"],[61,47,4,33,0,"INT"],[62,48,0,23,0,"PTMODEL"],[64,50,0,49,0,"PTVDATASET"],[65,49,0,47,1,"PTDATALOADER"],[66,47,0,52,0,"FLOAT"],[67,53,0,50,0,"PTVTRANSFORM"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.34289106510190753,"offset":[-1247.259007394664,-447.540790015054]},"node_versions":{"data-analysis":"db92cda5f5a1e611e15ca6f2531ff8585a5607a5"},"ue_links":[]},"version":0.4} \ No newline at end of file diff --git a/examples/workflows/dog_cat_classification_prediction.json b/examples/workflows/dog_cat_classification_prediction.json deleted file mode 100644 index 187c32a..0000000 --- a/examples/workflows/dog_cat_classification_prediction.json +++ /dev/null @@ -1 +0,0 @@ -{"last_node_id":74,"last_link_id":97,"nodes":[{"id":73,"type":"PtFromImageTranspose","pos":[1488.2567138671875,1628.712646484375],"size":[289.79998779296875,26],"flags":{},"order":4,"mode":0,"inputs":[{"name":"image","type":"IMAGE","link":95}],"outputs":[{"name":"TENSOR","type":"TENSOR","links":[96],"slot_index":0}],"properties":{"Node name for S&R":"PtFromImageTranspose"}},{"id":74,"type":"PtInterpolateToSize","pos":[1801.049560546875,1628.712646484375],"size":[315,106],"flags":{},"order":5,"mode":0,"inputs":[{"name":"tens","type":"TENSOR","link":96}],"outputs":[{"name":"TENSOR","type":"TENSOR","links":[97],"slot_index":0}],"properties":{"Node name for S&R":"PtInterpolateToSize"},"widgets_values":[256,256,"nearest-exact"]},{"id":69,"type":"PtSqueeze","pos":[2206.266357421875,1639.8197021484375],"size":[210,60.5162467956543],"flags":{},"order":6,"mode":0,"inputs":[{"name":"tens","type":"TENSOR","link":97}],"outputs":[{"name":"TENSOR","type":"TENSOR","links":[88],"slot_index":0}],"properties":{"Node name for S&R":"PtSqueeze"},"widgets_values":[0]},{"id":55,"type":"PtPredictClassificationModel","pos":[2482.278564453125,1626.3341064453125],"size":[405.5999755859375,98],"flags":{},"order":7,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":70},{"name":"inputs","type":"TENSOR","link":88},{"name":"class_id_to_name_map","type":"PYDICT","link":69}],"outputs":[{"name":"class_name","type":"STRING","links":[74],"slot_index":0},{"name":"class_id","type":"INT","links":null},{"name":"probability","type":"FLOAT","links":[93],"slot_index":2}],"properties":{"Node name for S&R":"PtPredictClassificationModel"},"widgets_values":[true]},{"id":56,"type":"PyStringToDict","pos":[2206.266357421875,1774.539306640625],"size":[210,81.30076599121094],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"PYDICT","type":"PYDICT","links":[69],"slot_index":0}],"properties":{"Node name for S&R":"PyStringToDict"},"widgets_values":["{0:\"cat\", 1:\"dog\"}"]},{"id":60,"type":"PandasShowText","pos":[2970.7451171875,1629.65576171875],"size":[221.17684936523438,76],"flags":{},"order":8,"mode":0,"inputs":[{"name":"text","type":"STRING","link":74,"widget":{"name":"text"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"PandasShowText"},"widgets_values":["","cat"]},{"id":72,"type":"CDAShowFloat","pos":[2976.763916015625,1770.6387939453125],"size":[210,76],"flags":{},"order":9,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":93,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.9118371605873108"]},{"id":23,"type":"PtLoadModel","pos":[2206.266357421875,1482.663330078125],"size":[210,75.45188903808594],"flags":{},"order":3,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":62}],"outputs":[{"name":"model","type":"PTMODEL","links":[70],"slot_index":0}],"properties":{"Node name for S&R":"PtLoadModel"},"widgets_values":["dog_cat.pt"]},{"id":57,"type":"LoadImage","pos":[1135.1787109375,1628.712646484375],"size":[315,314],"flags":{},"order":2,"mode":0,"inputs":[],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[95],"slot_index":0},{"name":"MASK","type":"MASK","links":null}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["cat.jpg","image"]},{"id":48,"type":"PtnConvModel","pos":[1801.049560546875,1306.3406982421875],"size":[315,202],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[{"name":"PTMODEL","type":"PTMODEL","links":[62],"slot_index":0}],"properties":{"Node name for S&R":"PtnConvModel"},"widgets_values":["(3,256,256)",0,2,"[32,64,128,256,512]","[3,3,3,3,3]","[1,1,1,1,1]","[True,True,True,True,True]"]}],"links":[[62,48,0,23,0,"PTMODEL"],[69,56,0,55,2,"PYDICT"],[70,23,0,55,0,"PTMODEL"],[74,55,0,60,0,"STRING"],[88,69,0,55,1,"TENSOR"],[93,55,2,72,0,"FLOAT"],[95,57,0,73,0,"IMAGE"],[96,73,0,74,0,"TENSOR"],[97,74,0,69,0,"TENSOR"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.6339486400897979,"offset":[-879.509850098028,-1085.7559202042573]},"node_versions":{"data-analysis":"fd6fa94b931cd094ce5766852f5914d7291cb464","comfy-core":"v0.3.10-40-gd0f3752"},"ue_links":[]},"version":0.4} \ No newline at end of file diff --git a/examples/workflows/dog_cat_classification_train_model.json b/examples/workflows/dog_cat_classification_train_model.json deleted file mode 100644 index 706b774..0000000 --- a/examples/workflows/dog_cat_classification_train_model.json +++ /dev/null @@ -1 +0,0 @@ -{"last_node_id":86,"last_link_id":95,"nodes":[{"id":11,"type":"PtoAdam","pos":[3106.311767578125,1425.00830078125],"size":[265.8851013183594,108.32855224609375],"flags":{},"order":16,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":89}],"outputs":[{"name":"PTOPTIMIZER","type":"PTOPTIMIZER","links":[68],"slot_index":0}],"properties":{"Node name for S&R":"PtoAdam"},"widgets_values":[0.0001,0.9,0.999]},{"id":72,"type":"PtDataLoader","pos":[2583.5888671875,1626.235595703125],"size":[400,200],"flags":{},"order":20,"mode":0,"inputs":[{"name":"dataset","type":"PTVDATASET","link":86}],"outputs":[{"name":"PTDATALOADER","type":"PTDATALOADER","links":[87],"slot_index":0}],"properties":{"Node name for S&R":"PtDataLoader"},"widgets_values":[8,true,"{\"num_workers\":1}"]},{"id":19,"type":"PtSaveModel","pos":[3464.489501953125,1694.661376953125],"size":[280.30267333984375,68.31287384033203],"flags":{},"order":23,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":71}],"outputs":[{"name":"model full path","type":"STRING","links":[70],"slot_index":0}],"properties":{"Node name for S&R":"PtSaveModel"},"widgets_values":["dog_cat.pt"]},{"id":74,"type":"PtvImageFolderDataset","pos":[2228.7578125,1923.7598876953125],"size":[315,58],"flags":{},"order":18,"mode":0,"inputs":[{"name":"transform","type":"PTVTRANSFORM","link":95,"shape":7}],"outputs":[{"name":"PTVDATASET","type":"PTVDATASET","links":[91],"slot_index":0}],"properties":{"Node name for S&R":"PtvImageFolderDataset"},"widgets_values":["/container/transfer/datasets/dog_and_cat/dog_and_cat/val/"]},{"id":71,"type":"PtnConvModel","pos":[2607.428955078125,1271.409912109375],"size":[315,202],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"PTMODEL","type":"PTMODEL","links":[88,89],"slot_index":0}],"properties":{"Node name for S&R":"PtnConvModel"},"widgets_values":["(3,256,256)",0,2,"[32,64,128,256,512]","[3,3,3,3,3]","[1,1,1,1,1]","[True,True,True,True,True]"]},{"id":53,"type":"MarkdownNote","pos":[2219.2607421875,1485.9119873046875],"size":[310.71795654296875,94.28345489501953],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Load train data\nYou need set the root field to point to the **train** directory of the dataset you downloaded."],"color":"#322","bgcolor":"#533"},{"id":76,"type":"MarkdownNote","pos":[2014.2506103515625,926.4641723632812],"size":[436.95391845703125,69.52714538574219],"flags":{},"order":2,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Dog Cat Classification Model Training"],"color":"#432","bgcolor":"#653"},{"id":78,"type":"PtvTransformsResize","pos":[1820.2008056640625,1781.384521484375],"size":[315,82],"flags":{},"order":3,"mode":0,"inputs":[],"outputs":[{"name":"PTVTRANSFORM","type":"PTVTRANSFORM","links":[94,95],"slot_index":0}],"properties":{"Node name for S&R":"PtvTransformsResize"},"widgets_values":[256,256]},{"id":52,"type":"MarkdownNote","pos":[2605.098876953125,1119.3177490234375],"size":[308.6709289550781,100.76033782958984],"flags":{},"order":4,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Model set up\n\nNo need to edit this node unless you are familiar with machine learning"],"color":"#232","bgcolor":"#353"},{"id":79,"type":"MarkdownNote","pos":[1816.8997802734375,1627.8802490234375],"size":[317.4327697753906,98.5698471069336],"flags":{},"order":5,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Dataset transformation\n\nNo need to edit this node unless you are familiar with machine learning. This resizes input data to 256px x 256px."],"color":"#232","bgcolor":"#353"},{"id":75,"type":"PtDataLoader","pos":[2588.29736328125,1922.6170654296875],"size":[400,200],"flags":{},"order":21,"mode":0,"inputs":[{"name":"dataset","type":"PTVDATASET","link":91}],"outputs":[{"name":"PTDATALOADER","type":"PTDATALOADER","links":[93],"slot_index":0}],"properties":{"Node name for S&R":"PtDataLoader"},"widgets_values":[8,false,"{\"num_workers\":1}"]},{"id":51,"type":"PandasShowText","pos":[3845.79296875,1686.58056640625],"size":[499.0397033691406,76],"flags":{},"order":26,"mode":0,"inputs":[{"name":"text","type":"STRING","link":70,"widget":{"name":"text"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"PandasShowText"},"widgets_values":["","/home/dockeruser/comfy/ComfyUI/custom_nodes/data-analysis/models/dog_cat.pt"]},{"id":58,"type":"PandasCreateFromTensor","pos":[3487.738037109375,2425.4267578125],"size":[315,26],"flags":{},"order":25,"mode":0,"inputs":[{"name":"tens","type":"TENSOR","link":73}],"outputs":[{"name":"DATAFRAME","type":"DATAFRAME","links":[75],"slot_index":0}],"properties":{"Node name for S&R":"PandasCreateFromTensor"},"widgets_values":[]},{"id":64,"type":"PandasCreateFromTensor","pos":[3844.351806640625,2508.688720703125],"size":[315,26],"flags":{},"order":19,"mode":0,"inputs":[{"name":"tens","type":"TENSOR","link":78}],"outputs":[{"name":"DATAFRAME","type":"DATAFRAME","links":[77],"slot_index":0}],"properties":{"Node name for S&R":"PandasCreateFromTensor"},"widgets_values":[]},{"id":65,"type":"PandasHorizontalConcat","pos":[4218.2119140625,2380.0498046875],"size":[315,78],"flags":{},"order":28,"mode":0,"inputs":[{"name":"a_dataframe","type":"DATAFRAME","link":79},{"name":"b_dataframe","type":"DATAFRAME","link":77}],"outputs":[{"name":"DATAFRAME","type":"DATAFRAME","links":[80,81],"slot_index":0}],"properties":{"Node name for S&R":"PandasHorizontalConcat"},"widgets_values":[true]},{"id":61,"type":"PandasRenameAdvanced","pos":[4608.04443359375,2380.0498046875],"size":[238.48175048828125,131.16506958007812],"flags":{},"order":30,"mode":0,"inputs":[{"name":"dataframe","type":"DATAFRAME","link":81}],"outputs":[{"name":"DATAFRAME","type":"DATAFRAME","links":[82],"slot_index":0}],"properties":{"Node name for S&R":"PandasRenameAdvanced"},"widgets_values":["columns","{0:\"train\",1:\"val\",2:\"epoch\"}"]},{"id":60,"type":"SNSLine","pos":[4918.4609375,2380.0498046875],"size":[315,250],"flags":{},"order":31,"mode":0,"inputs":[{"name":"dataframe","type":"DATAFRAME","link":82}],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[84],"slot_index":0}],"properties":{"Node name for S&R":"SNSLine"},"widgets_values":["epoch","train,val","Loss","Epoch","Cross entropy loss","train,val",true,"",""]},{"id":68,"type":"SaveImage","pos":[5326.560546875,2380.0498046875],"size":[315,270],"flags":{},"order":32,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":84}],"outputs":[],"properties":{"Node name for S&R":"SaveImage"},"widgets_values":["dog_cat_train"]},{"id":57,"type":"PandasCreateFromTensor","pos":[3483.9892578125,2346.20166015625],"size":[315,26],"flags":{},"order":24,"mode":0,"inputs":[{"name":"tens","type":"TENSOR","link":72}],"outputs":[{"name":"DATAFRAME","type":"DATAFRAME","links":[74],"slot_index":0}],"properties":{"Node name for S&R":"PandasCreateFromTensor"},"widgets_values":[]},{"id":83,"type":"MarkdownNote","pos":[2943.528564453125,925.8107299804688],"size":[336.614990234375,70.34814453125],"flags":{},"order":6,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["## Yellow nodes may require adjustment"],"color":"#432","bgcolor":"#653"},{"id":80,"type":"MarkdownNote","pos":[3106.311767578125,1250.9371337890625],"size":[257.6202392578125,106.56401062011719],"flags":{},"order":7,"mode":2,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Optimizer\nYou may need to adjust the learning rate but starts with the number below."],"color":"#432","bgcolor":"#653"},{"id":50,"type":"PtTrainClassificationModel","pos":[3106.311767578125,1663.041748046875],"size":[265.619873046875,145.1302490234375],"flags":{},"order":22,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":88},{"name":"train_loader","type":"PTDATALOADER","link":87},{"name":"optimizer","type":"PTOPTIMIZER","link":68},{"name":"val_loader","type":"PTDATALOADER","link":93,"shape":7}],"outputs":[{"name":"Model","type":"PTMODEL","links":[71],"slot_index":0},{"name":"train loss","type":"TENSOR","links":[72],"slot_index":1},{"name":"val loss","type":"TENSOR","links":[73],"slot_index":2}],"properties":{"Node name for S&R":"PtTrainClassificationModel"},"widgets_values":[20,true]},{"id":84,"type":"MarkdownNote","pos":[2596.4189453125,2210.809326171875],"size":[387.0498352050781,100.52718353271484],"flags":{},"order":8,"mode":2,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Data loading\nIf GPU throws OOM, make the batch_size smaller."],"color":"#432","bgcolor":"#653"},{"id":82,"type":"MarkdownNote","pos":[2944.823486328125,805.5132446289062],"size":[326.2669677734375,67.76112365722656],"flags":{},"order":9,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["## Red nodes require editing"],"color":"#322","bgcolor":"#533"},{"id":77,"type":"MarkdownNote","pos":[2230.19091796875,2034.1849365234375],"size":[313.0150451660156,99.62860870361328],"flags":{},"order":10,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Load val data\nYou need set the root field to point to the **val** directory of the dataset you downloaded."],"color":"#322","bgcolor":"#533"},{"id":62,"type":"PtArange","pos":[3481.524169921875,2515.711669921875],"size":[315,130],"flags":{},"order":11,"mode":0,"inputs":[],"outputs":[{"name":"TENSOR","type":"TENSOR","links":[78],"slot_index":0}],"properties":{"Node name for S&R":"PtArange"},"widgets_values":["0","20","","float32"],"color":"#432","bgcolor":"#653"},{"id":55,"type":"MarkdownNote","pos":[3106.311767578125,1861.1396484375],"size":[265.81390380859375,103.27005767822266],"flags":{},"order":12,"mode":2,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Train\nYou may need to adjust epochs but start with 20."],"color":"#432","bgcolor":"#653"},{"id":85,"type":"MarkdownNote","pos":[3485.391357421875,2688.060791015625],"size":[304.5123596191406,100.34938049316406],"flags":{},"order":13,"mode":2,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Update \"end\" field\nIf you change the epochs, change the end field to match."],"color":"#432","bgcolor":"#653"},{"id":66,"type":"PandasShowDataFrame","pos":[4215.47265625,2533.793701171875],"size":[315.237060546875,414.686279296875],"flags":{},"order":29,"mode":0,"inputs":[{"name":"dataframe","type":"DATAFRAME","link":80}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"PandasShowDataFrame"},"widgets_values":[" 0 1 2\n0 0.686489 0.680127 0.0\n1 0.621646 0.571015 1.0\n2 0.499781 0.471638 2.0\n3 0.382664 0.404523 3.0\n4 0.289659 0.329657 4.0\n5 0.224341 0.292451 5.0\n6 0.160335 0.291596 6.0\n7 0.110199 0.321644 7.0\n8 0.091624 0.270642 8.0\n9 0.052231 0.221975 9.0\n10 0.048100 0.281967 10.0\n11 0.022782 0.289861 11.0\n12 0.013045 0.305584 12.0\n13 0.013724 0.307031 13.0\n14 0.020037 0.271379 14.0\n15 0.031278 0.310788 15.0\n16 0.012516 0.338837 16.0\n17 0.017392 0.298860 17.0\n18 0.001674 0.316514 18.0\n19 0.000855 0.358205 19.0"]},{"id":59,"type":"PandasHorizontalConcat","pos":[3849.372802734375,2380.0498046875],"size":[315,78],"flags":{},"order":27,"mode":0,"inputs":[{"name":"a_dataframe","type":"DATAFRAME","link":74},{"name":"b_dataframe","type":"DATAFRAME","link":75}],"outputs":[{"name":"DATAFRAME","type":"DATAFRAME","links":[79],"slot_index":0}],"properties":{"Node name for S&R":"PandasHorizontalConcat"},"widgets_values":[true]},{"id":86,"type":"MarkdownNote","pos":[3453.1630859375,2150.53857421875],"size":[1160.256103515625,64.83271026611328],"flags":{},"order":14,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# This area is to show how training went. Initially, it's OK if you don't understand the meaning."],"color":"#232","bgcolor":"#353"},{"id":69,"type":"PtvImageFolderDataset","pos":[2220.0927734375,1629.250732421875],"size":[315,58],"flags":{},"order":17,"mode":0,"inputs":[{"name":"transform","type":"PTVTRANSFORM","link":94,"shape":7}],"outputs":[{"name":"PTVDATASET","type":"PTVDATASET","links":[86],"slot_index":0}],"properties":{"Node name for S&R":"PtvImageFolderDataset"},"widgets_values":["/container/transfer/datasets/dog_and_cat/dog_and_cat/train/"]},{"id":56,"type":"MarkdownNote","pos":[3464.170166015625,1519.1202392578125],"size":[285.6708679199219,95.45172882080078],"flags":{},"order":15,"mode":2,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Save model\nUpdate the model path if needed.\nThis is saved under custom_nodes/data-analysis/models"],"color":"#432","bgcolor":"#653"}],"links":[[68,11,0,50,2,"PTOPTIMIZER"],[70,19,0,51,0,"STRING"],[71,50,0,19,0,"PTMODEL"],[72,50,1,57,0,"TENSOR"],[73,50,2,58,0,"TENSOR"],[74,57,0,59,0,"DATAFRAME"],[75,58,0,59,1,"DATAFRAME"],[77,64,0,65,1,"DATAFRAME"],[78,62,0,64,0,"TENSOR"],[79,59,0,65,0,"DATAFRAME"],[80,65,0,66,0,"DATAFRAME"],[81,65,0,61,0,"DATAFRAME"],[82,61,0,60,0,"DATAFRAME"],[84,60,0,68,0,"IMAGE"],[86,69,0,72,0,"PTVDATASET"],[87,72,0,50,1,"PTDATALOADER"],[88,71,0,50,0,"PTMODEL"],[89,71,0,11,0,"PTMODEL"],[91,74,0,75,0,"PTVDATASET"],[93,75,0,50,3,"PTDATALOADER"],[94,78,0,69,0,"PTVTRANSFORM"],[95,78,0,74,0,"PTVTRANSFORM"]],"groups":[{"id":1,"title":"Training metrics","bounding":[3447.499755859375,2242.806884765625,2284.0107421875,790.1612548828125],"color":"#8A8","font_size":24,"flags":{}}],"config":{},"extra":{"ds":{"scale":0.3736662716119983,"offset":[-975.7677617863449,-688.047741909445]},"node_versions":{"data-analysis":"db92cda5f5a1e611e15ca6f2531ff8585a5607a5","comfy-core":"v0.3.10-40-gd0f3752"},"ue_links":[]},"version":0.4} \ No newline at end of file diff --git a/examples/workflows/fashion_mnist_eval.json b/examples/workflows/fashion_mnist_eval.json deleted file mode 100644 index c9e452e..0000000 --- a/examples/workflows/fashion_mnist_eval.json +++ /dev/null @@ -1 +0,0 @@ -{"last_node_id":47,"last_link_id":61,"nodes":[{"id":47,"type":"PtEvaluateClassificationModel","pos":[3243.2138671875,1887.3746337890625],"size":[403.1999816894531,138],"flags":{},"order":3,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":55},{"name":"data_loader","type":"PTDATALOADER","link":56}],"outputs":[{"name":"accuracy","type":"FLOAT","links":[57],"slot_index":0},{"name":"precision","type":"FLOAT","links":[58],"slot_index":1},{"name":"recall","type":"FLOAT","links":[59],"slot_index":2},{"name":"f1","type":"FLOAT","links":[60],"slot_index":3},{"name":"num_samples","type":"INT","links":[61],"slot_index":4}],"properties":{"Node name for S&R":"PtEvaluateClassificationModel"},"widgets_values":[true]},{"id":22,"type":"PtnLinearModel","pos":[2886.657958984375,1718.1610107421875],"size":[264.3730163574219,109.75016021728516],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"PTMODEL","type":"PTMODEL","links":[24],"slot_index":0}],"properties":{"Node name for S&R":"PtnLinearModel"},"widgets_values":["[784,180,42,10]","[False,False,False]",3]},{"id":33,"type":"CDAShowInt","pos":[3716.05078125,2073.464111328125],"size":[210,79.09933471679688],"flags":{},"order":8,"mode":0,"inputs":[{"name":"int_scalar","type":"INT","link":61,"widget":{"name":"int_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Num Samples","properties":{"Node name for S&R":"CDAShowInt"},"widgets_values":[0,"10000"]},{"id":31,"type":"CDAShowFloat","pos":[3937.552490234375,1932.3660888671875],"size":[210,76],"flags":{},"order":7,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":60,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"F1","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.8705378412983575"]},{"id":29,"type":"CDAShowFloat","pos":[3937.552490234375,1808.5174560546875],"size":[210,76],"flags":{},"order":5,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":58,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Precision","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.8721685257742706"]},{"id":30,"type":"CDAShowFloat","pos":[3716.05078125,1932.3660888671875],"size":[210,76],"flags":{},"order":6,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":59,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Recall","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.8715999999999999"]},{"id":28,"type":"CDAShowFloat","pos":[3716.05078125,1808.5174560546875],"size":[210,76],"flags":{},"order":4,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":57,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Accuracy","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.8716"]},{"id":46,"type":"PtvDatasetLoader","pos":[2884.05078125,1908.8636474609375],"size":[266.8697814941406,246],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[{"name":"PTDATALOADER","type":"PTDATALOADER","links":[56],"slot_index":0}],"properties":{"Node name for S&R":"PtvDatasetLoader"},"widgets_values":["FashionMNIST",true,"",128,false,"{\"train\":False}","{\"num_workers\":1}"]},{"id":23,"type":"PtLoadModel","pos":[3243.927978515625,1718.1610107421875],"size":[397.5032653808594,67.37537384033203],"flags":{},"order":2,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":24}],"outputs":[{"name":"model","type":"PTMODEL","links":[55],"slot_index":0}],"properties":{"Node name for S&R":"PtLoadModel"},"widgets_values":["fashion_mnist_ep5.pt"]}],"links":[[24,22,0,23,0,"PTMODEL"],[55,23,0,47,0,"PTMODEL"],[56,46,0,47,1,"PTDATALOADER"],[57,47,0,28,0,"FLOAT"],[58,47,1,29,0,"FLOAT"],[59,47,2,30,0,"FLOAT"],[60,47,3,31,0,"FLOAT"],[61,47,4,33,0,"INT"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.8888542618071794,"offset":[-2627.7528215343027,-1537.849355050907]},"node_versions":{"data-analysis":"dab52eaa403a5e9cdc546aca7f7d95e41ea0a943"},"ue_links":[]},"version":0.4} \ No newline at end of file diff --git a/examples/workflows/fashion_mnist_eval_conv.json b/examples/workflows/fashion_mnist_eval_conv.json deleted file mode 100644 index fc3883d..0000000 --- a/examples/workflows/fashion_mnist_eval_conv.json +++ /dev/null @@ -1 +0,0 @@ -{"last_node_id":48,"last_link_id":62,"nodes":[{"id":47,"type":"PtEvaluateClassificationModel","pos":[3243.2138671875,1887.3746337890625],"size":[403.1999816894531,138],"flags":{},"order":3,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":55},{"name":"data_loader","type":"PTDATALOADER","link":56}],"outputs":[{"name":"accuracy","type":"FLOAT","links":[57],"slot_index":0},{"name":"precision","type":"FLOAT","links":[58],"slot_index":1},{"name":"recall","type":"FLOAT","links":[59],"slot_index":2},{"name":"f1","type":"FLOAT","links":[60],"slot_index":3},{"name":"num_samples","type":"INT","links":[61],"slot_index":4}],"properties":{"Node name for S&R":"PtEvaluateClassificationModel"},"widgets_values":[true]},{"id":33,"type":"CDAShowInt","pos":[3716.05078125,2073.464111328125],"size":[210,79.09933471679688],"flags":{},"order":8,"mode":0,"inputs":[{"name":"int_scalar","type":"INT","link":61,"widget":{"name":"int_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Num Samples","properties":{"Node name for S&R":"CDAShowInt"},"widgets_values":[0,"10000"]},{"id":31,"type":"CDAShowFloat","pos":[3937.552490234375,1932.3660888671875],"size":[210,76],"flags":{},"order":7,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":60,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"F1","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.9085886169153918"]},{"id":29,"type":"CDAShowFloat","pos":[3937.552490234375,1808.5174560546875],"size":[210,76],"flags":{},"order":5,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":58,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Precision","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.9113484312788449"]},{"id":30,"type":"CDAShowFloat","pos":[3716.05078125,1932.3660888671875],"size":[210,76],"flags":{},"order":6,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":59,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Recall","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.908"]},{"id":28,"type":"CDAShowFloat","pos":[3716.05078125,1808.5174560546875],"size":[210,76],"flags":{},"order":4,"mode":0,"inputs":[{"name":"float_scalar","type":"FLOAT","link":57,"widget":{"name":"float_scalar"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"title":"Accuracy","properties":{"Node name for S&R":"CDAShowFloat"},"widgets_values":[0,"0.908"]},{"id":46,"type":"PtvDatasetLoader","pos":[2884.05078125,1908.8636474609375],"size":[266.8697814941406,246],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"PTDATALOADER","type":"PTDATALOADER","links":[56],"slot_index":0}],"properties":{"Node name for S&R":"PtvDatasetLoader"},"widgets_values":["FashionMNIST",true,"",128,false,"{\"train\":False}","{\"num_workers\":1}"]},{"id":48,"type":"PtnConvModel","pos":[2846.147216796875,1629.3045654296875],"size":[315,202],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[{"name":"PTMODEL","type":"PTMODEL","links":[62],"slot_index":0}],"properties":{"Node name for S&R":"PtnConvModel"},"widgets_values":["(1,28,28)",0,10,"[32,64,128,256,512]","[3,3,3,3,1]","[1,1,1,1,0]","[True,True,True,True,False]"]},{"id":23,"type":"PtLoadModel","pos":[3243.927978515625,1718.1610107421875],"size":[397.5032653808594,67.37537384033203],"flags":{},"order":2,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":62}],"outputs":[{"name":"model","type":"PTMODEL","links":[55],"slot_index":0}],"properties":{"Node name for S&R":"PtLoadModel"},"widgets_values":["fashion_mnist_conv_ep5.pt"]}],"links":[[55,23,0,47,0,"PTMODEL"],[56,46,0,47,1,"PTDATALOADER"],[57,47,0,28,0,"FLOAT"],[58,47,1,29,0,"FLOAT"],[59,47,2,30,0,"FLOAT"],[60,47,3,31,0,"FLOAT"],[61,47,4,33,0,"INT"],[62,48,0,23,0,"PTMODEL"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.9652303192711613,"offset":[-2706.3348752184625,-1469.581964047439]},"node_versions":{"data-analysis":"11335e88b00ed3b4a5129f92d3d134854b6b4a92"},"ue_links":[]},"version":0.4} \ No newline at end of file diff --git a/examples/workflows/fashion_mnist_train.json b/examples/workflows/fashion_mnist_train.json deleted file mode 100644 index c693a15..0000000 --- a/examples/workflows/fashion_mnist_train.json +++ /dev/null @@ -1 +0,0 @@ -{"last_node_id":56,"last_link_id":71,"nodes":[{"id":22,"type":"PtnLinearModel","pos":[2830.718017578125,1445.9212646484375],"size":[222.090087890625,128.31336975097656],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"PTMODEL","type":"PTMODEL","links":[22,65],"slot_index":0}],"properties":{"Node name for S&R":"PtnLinearModel"},"widgets_values":["[784,180,42,10]","[False,False,False]",3]},{"id":47,"type":"PtvDatasetLoader","pos":[2830.718017578125,1652.3197021484375],"size":[239.68125915527344,246],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[{"name":"PTDATALOADER","type":"PTDATALOADER","links":[66],"slot_index":0}],"properties":{"Node name for S&R":"PtvDatasetLoader"},"widgets_values":["FashionMNIST",true,"",128,true,"{\"train\":True}","{\"num_workers\":1}"]},{"id":48,"type":"PtvDatasetLoader","pos":[2830.718017578125,1957.211181640625],"size":[239.68125915527344,246],"flags":{},"order":2,"mode":0,"inputs":[],"outputs":[{"name":"PTDATALOADER","type":"PTDATALOADER","links":[67],"slot_index":0}],"properties":{"Node name for S&R":"PtvDatasetLoader"},"widgets_values":["FashionMNIST",true,"",128,false,"{\"train\":False}","{\"num_workers\":1}"]},{"id":50,"type":"PtTrainClassificationModel","pos":[3170.615478515625,1736.97705078125],"size":[243.70773315429688,142],"flags":{},"order":9,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":65},{"name":"train_loader","type":"PTDATALOADER","link":66},{"name":"optimizer","type":"PTOPTIMIZER","link":68},{"name":"val_loader","type":"PTDATALOADER","link":67,"shape":7}],"outputs":[{"name":"Model","type":"PTMODEL","links":[71],"slot_index":0},{"name":"train loss","type":"TENSOR","links":null},{"name":"val loss","type":"TENSOR","links":null}],"properties":{"Node name for S&R":"PtTrainClassificationModel"},"widgets_values":[5,true]},{"id":19,"type":"PtSaveModel","pos":[3464.489501953125,1694.661376953125],"size":[280.30267333984375,68.31287384033203],"flags":{},"order":10,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":71}],"outputs":[{"name":"model full path","type":"STRING","links":[70],"slot_index":0}],"properties":{"Node name for S&R":"PtSaveModel"},"widgets_values":["fashion_mnist_ep5.pt"]},{"id":54,"type":"MarkdownNote","pos":[2582.973388671875,1955.99560546875],"size":[210,58.94380569458008],"flags":{},"order":3,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Load val data"],"color":"#432","bgcolor":"#653"},{"id":53,"type":"MarkdownNote","pos":[2582.973388671875,1652.8607177734375],"size":[210,58.94380569458008],"flags":{},"order":4,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Load train data"],"color":"#432","bgcolor":"#653"},{"id":11,"type":"PtoAdam","pos":[3170.615478515625,1445.9212646484375],"size":[265.8851013183594,108.32855224609375],"flags":{},"order":8,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":22}],"outputs":[{"name":"PTOPTIMIZER","type":"PTOPTIMIZER","links":[68],"slot_index":0}],"properties":{"Node name for S&R":"PtoAdam"},"widgets_values":[0.001,0.9,0.999]},{"id":52,"type":"MarkdownNote","pos":[2582.973388671875,1445.9212646484375],"size":[210,58.94380569458008],"flags":{},"order":5,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Define model"],"color":"#432","bgcolor":"#653"},{"id":55,"type":"MarkdownNote","pos":[3170.615478515625,1953.1151123046875],"size":[210,58.94380569458008],"flags":{},"order":6,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Train"],"color":"#432","bgcolor":"#653"},{"id":56,"type":"MarkdownNote","pos":[3468.277099609375,1806.20458984375],"size":[210,58.94380569458008],"flags":{},"order":7,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Save model"],"color":"#432","bgcolor":"#653"},{"id":51,"type":"PandasShowText","pos":[3469.39404296875,1921.830078125],"size":[576.9476318359375,81.15640258789062],"flags":{},"order":11,"mode":0,"inputs":[{"name":"text","type":"STRING","link":70,"widget":{"name":"text"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"PandasShowText"},"widgets_values":["","/home/dockeruser/comfy/ComfyUI/custom_nodes/data-analysis/models/fashion_mnist_ep5.pt"]}],"links":[[22,22,0,11,0,"PTMODEL"],[65,22,0,50,0,"PTMODEL"],[66,47,0,50,1,"PTDATALOADER"],[67,48,0,50,3,"PTDATALOADER"],[68,11,0,50,2,"PTOPTIMIZER"],[70,19,0,51,0,"STRING"],[71,50,0,19,0,"PTMODEL"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.8678754334266201,"offset":[-2457.1876923760833,-1312.0857787454647]},"node_versions":{"data-analysis":"dab52eaa403a5e9cdc546aca7f7d95e41ea0a943"},"ue_links":[]},"version":0.4} \ No newline at end of file diff --git a/examples/workflows/fashion_mnist_train_conv.json b/examples/workflows/fashion_mnist_train_conv.json deleted file mode 100644 index c34d729..0000000 --- a/examples/workflows/fashion_mnist_train_conv.json +++ /dev/null @@ -1 +0,0 @@ -{"last_node_id":57,"last_link_id":73,"nodes":[{"id":50,"type":"PtTrainClassificationModel","pos":[3170.615478515625,1736.97705078125],"size":[243.70773315429688,142],"flags":{},"order":9,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":73},{"name":"train_loader","type":"PTDATALOADER","link":66},{"name":"optimizer","type":"PTOPTIMIZER","link":68},{"name":"val_loader","type":"PTDATALOADER","link":67,"shape":7}],"outputs":[{"name":"Model","type":"PTMODEL","links":[71],"slot_index":0},{"name":"train loss","type":"TENSOR","links":null},{"name":"val loss","type":"TENSOR","links":null}],"properties":{"Node name for S&R":"PtTrainClassificationModel"},"widgets_values":[5,true]},{"id":11,"type":"PtoAdam","pos":[3170.615478515625,1445.9212646484375],"size":[265.8851013183594,108.32855224609375],"flags":{},"order":8,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":72}],"outputs":[{"name":"PTOPTIMIZER","type":"PTOPTIMIZER","links":[68],"slot_index":0}],"properties":{"Node name for S&R":"PtoAdam"},"widgets_values":[0.001,0.9,0.999]},{"id":55,"type":"MarkdownNote","pos":[3170.615478515625,1953.1151123046875],"size":[210,58.94380569458008],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Train"],"color":"#432","bgcolor":"#653"},{"id":56,"type":"MarkdownNote","pos":[3468.277099609375,1806.20458984375],"size":[210,58.94380569458008],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Save model"],"color":"#432","bgcolor":"#653"},{"id":51,"type":"PandasShowText","pos":[3469.39404296875,1921.830078125],"size":[576.9476318359375,81.15640258789062],"flags":{},"order":11,"mode":0,"inputs":[{"name":"text","type":"STRING","link":70,"widget":{"name":"text"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"PandasShowText"},"widgets_values":["","/home/dockeruser/comfy/ComfyUI/custom_nodes/data-analysis/models/fashion_mnist_conv_ep5.pt"]},{"id":53,"type":"MarkdownNote","pos":[2456.569091796875,1652.8607177734375],"size":[210,58.94380569458008],"flags":{},"order":2,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Load train data"],"color":"#432","bgcolor":"#653"},{"id":54,"type":"MarkdownNote","pos":[2456.569091796875,1961.218994140625],"size":[210,58.94380569458008],"flags":{},"order":3,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Load val data"],"color":"#432","bgcolor":"#653"},{"id":52,"type":"MarkdownNote","pos":[2456.569091796875,1384.5592041015625],"size":[210,58.94380569458008],"flags":{},"order":4,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["# Define model"],"color":"#432","bgcolor":"#653"},{"id":48,"type":"PtvDatasetLoader","pos":[2770.126708984375,1962.4345703125],"size":[309.6732482910156,246.00001525878906],"flags":{},"order":5,"mode":0,"inputs":[],"outputs":[{"name":"PTDATALOADER","type":"PTDATALOADER","links":[67],"slot_index":0}],"properties":{"Node name for S&R":"PtvDatasetLoader"},"widgets_values":["FashionMNIST",true,"",128,false,"{\"train\":False}","{\"num_workers\":1}"]},{"id":47,"type":"PtvDatasetLoader","pos":[2770.126708984375,1649.1856689453125],"size":[314.89654541015625,246],"flags":{},"order":6,"mode":0,"inputs":[],"outputs":[{"name":"PTDATALOADER","type":"PTDATALOADER","links":[66],"slot_index":0}],"properties":{"Node name for S&R":"PtvDatasetLoader"},"widgets_values":["FashionMNIST",true,"",128,true,"{\"train\":True}","{\"num_workers\":1}"]},{"id":19,"type":"PtSaveModel","pos":[3464.489501953125,1694.661376953125],"size":[280.30267333984375,68.31287384033203],"flags":{},"order":10,"mode":0,"inputs":[{"name":"model","type":"PTMODEL","link":71}],"outputs":[{"name":"model full path","type":"STRING","links":[70],"slot_index":0}],"properties":{"Node name for S&R":"PtSaveModel"},"widgets_values":["fashion_mnist_conv_ep5.pt"]},{"id":57,"type":"PtnConvModel","pos":[2770.126708984375,1380.5203857421875],"size":[315,202],"flags":{},"order":7,"mode":0,"inputs":[],"outputs":[{"name":"PTMODEL","type":"PTMODEL","links":[72,73],"slot_index":0}],"properties":{"Node name for S&R":"PtnConvModel"},"widgets_values":["(1,28,28)",0,10,"[32,64,128,256,512]","[3,3,3,3,1]","[1,1,1,1,0]","[True,True,True,True,False]"]}],"links":[[66,47,0,50,1,"PTDATALOADER"],[67,48,0,50,3,"PTDATALOADER"],[68,11,0,50,2,"PTOPTIMIZER"],[70,19,0,51,0,"STRING"],[71,50,0,19,0,"PTMODEL"],[72,57,0,11,0,"PTMODEL"],[73,57,0,50,0,"PTMODEL"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.7251918251473778,"offset":[-2209.359108864266,-1203.1203019096897]},"node_versions":{"data-analysis":"11335e88b00ed3b4a5129f92d3d134854b6b4a92"},"ue_links":[]},"version":0.4} \ No newline at end of file diff --git a/modules/pytorch_wrapper/pt_abs.py b/modules/pytorch_wrapper/pt_abs.py deleted file mode 100644 index 3453cee..0000000 --- a/modules/pytorch_wrapper/pt_abs.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtAbs: - """ - Computes the absolute value of each element in a PyTorch tensor. - - category: PyTorch wrapper - Math operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Computes the absolute value of a PyTorch tensor element-wise. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of numerical type. - - Returns: - tuple: A tuple containing the resultant tensor after applying the absolute function. - """ - return (torch.abs(tens_a),) diff --git a/modules/pytorch_wrapper/pt_acos.py b/modules/pytorch_wrapper/pt_acos.py deleted file mode 100644 index 1d68c2a..0000000 --- a/modules/pytorch_wrapper/pt_acos.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtAcos: - """ - Computes the arccosine (inverse cosine) of a PyTorch tensor element-wise. - - category: PyTorch wrapper - Math operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Computes the element-wise arccosine (inverse cosine) of a PyTorch tensor. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of numerical type. Values must be in the range [-1, 1]. - - Returns: - tuple: A tuple containing the resultant tensor after applying the arccosine function. - """ - return (torch.acos(tens_a),) diff --git a/modules/pytorch_wrapper/pt_add.py b/modules/pytorch_wrapper/pt_add.py deleted file mode 100644 index 4220656..0000000 --- a/modules/pytorch_wrapper/pt_add.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtAdd: - """ - Adds two PyTorch tensors. - - category: PyTorch wrapper - Arithmetic operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Adds two PyTorch tensors. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of any shape. - tens_b (torch.Tensor): A PyTorch tensor of shape that supports addition with `tens_a`. - - Returns: - tuple: A tuple containing the resultant tensor. - """ - return (torch.add(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_arange.py b/modules/pytorch_wrapper/pt_arange.py deleted file mode 100644 index 8d33fb1..0000000 --- a/modules/pytorch_wrapper/pt_arange.py +++ /dev/null @@ -1,62 +0,0 @@ -import os -import sys -from typing import Any, Dict, Tuple -import torch -from .utils import DTYPE_MAPPING - -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 cda.utils import str_to_number, str_to_number_with_default - - -class PtArange: - """ - Creates a PyTorch tensor using `torch.arange` with the specified start, end, and step values. - - The values are parsed from string inputs and converted to numeric types. This allows users to specify - tensor range values in text fields. The function also ensures proper dtype selection. - - category: PyTorch wrapper - Tensor creation - """ - - @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": { - "start": ("STRING", {"default":"", "multiline": False}), - "end": ("STRING", {"default":"", "multiline": False}), - "step": ("STRING", {"default":"", "multiline": False}), - "data_type": (("float32", "float16", "bfloat16", "float64", "uint8", "int8", "int16", "int32", "int64"),) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - - def f(self, start: str, end: str, step: str, data_type: str) -> Tuple[torch.Tensor]: - """ - Creates a PyTorch tensor using `torch.arange` with the given start, stop, and step values. - - Args: - start (str): The starting value of the sequence. If empty, defaults to 0. - end (str): The end value of the sequence (exclusive). - step (str): The step size for generating the sequence. If empty, defaults to 1. - data_type (str): The desired PyTorch dtype for the resulting tensor. - - Returns: - Tuple[torch.Tensor]: A tuple containing the generated PyTorch tensor. - """ - start_value = str_to_number_with_default(start, 0) - end_value = str_to_number(end) - step_value = str_to_number_with_default(step, 1) - tens = torch.arange(start_value, end_value, step_value, dtype=DTYPE_MAPPING[data_type]) - return (tens,) diff --git a/modules/pytorch_wrapper/pt_argmax.py b/modules/pytorch_wrapper/pt_argmax.py deleted file mode 100644 index 1e78222..0000000 --- a/modules/pytorch_wrapper/pt_argmax.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import Any, Dict -import torch -from .utils import str_to_dim - - -class PtArgmax: - """ - Computes the indices of the maximum values of a PyTorch tensor along the specified dimension(s). - - Specify the dimension in the `dim` field using an integer as shown below: - ``` - 0 - ``` - category: PyTorch wrapper - Reduction operation & Summary statistics - """ - - @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", {}), - "dim": ("STRING", {"multiline": True}), - "keepdim": ("BOOLEAN", {"default": False}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, dim: str, keepdim: bool) -> tuple: - """ - Computes the indices of the maximum values along the specified dimension(s). - - Args: - tens (torch.Tensor): The input tensor. - dim (str): A string representation of the dimension(s) along which to compute argmax, - e.g., "0" or "". - keepdim (bool): Whether to retain reduced dimensions. - - Returns: - tuple: A tuple containing the tensor with indices of maximum values. - """ - dim = str_to_dim(dim) - - # Ensure keepdim is a boolean - if not isinstance(keepdim, bool): - raise TypeError(f"Invalid type for keepdim: {keepdim}. Must be a boolean.") - - # Compute the argmax along the specified dimension(s) - tens = torch.argmax(tens, dim=dim, keepdim=keepdim) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_argmin.py b/modules/pytorch_wrapper/pt_argmin.py deleted file mode 100644 index 0f4e30a..0000000 --- a/modules/pytorch_wrapper/pt_argmin.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import Any, Dict -import torch -from .utils import str_to_dim - - -class PtArgmin: - """ - Computes the indices of the minimum values of a PyTorch tensor along the specified dimension(s). - - Specify the dimension in the `dim` field using an integer as shown below: - ``` - 0 - ``` - category: PyTorch wrapper - Reduction operation & Summary statistics - """ - - @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", {}), - "dim": ("STRING", {"multiline": True}), - "keepdim": ("BOOLEAN", {"default": False}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, dim: str, keepdim: bool) -> tuple: - """ - Computes the indices of the minimum values along the specified dimension(s). - - Args: - tens (torch.Tensor): The input tensor. - dim (str): A string representation of the dimension(s) along which to compute argmin, - e.g., "0" or "". - keepdim (bool): Whether to retain reduced dimensions. - - Returns: - tuple: A tuple containing the tensor with indices of minimum values. - """ - dim = str_to_dim(dim) - - # Ensure keepdim is a boolean - if not isinstance(keepdim, bool): - raise TypeError(f"Invalid type for keepdim: {keepdim}. Must be a boolean.") - - # Compute the argmin along the specified dimension(s) - tens = torch.argmin(tens, dim=dim, keepdim=keepdim) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_asin.py b/modules/pytorch_wrapper/pt_asin.py deleted file mode 100644 index ef9b6dd..0000000 --- a/modules/pytorch_wrapper/pt_asin.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtAsin: - """ - Computes the arcsine (inverse sine) of a PyTorch tensor element-wise. - - category: PyTorch wrapper - Math operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Computes the element-wise arcsine (inverse sine) of a PyTorch tensor. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of numerical type. Values must be in the range [-1, 1]. - - Returns: - tuple: A tuple containing the resultant tensor after applying the arcsine function. - """ - return (torch.asin(tens_a),) diff --git a/modules/pytorch_wrapper/pt_atan.py b/modules/pytorch_wrapper/pt_atan.py deleted file mode 100644 index 93e8c68..0000000 --- a/modules/pytorch_wrapper/pt_atan.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtAtan: - """ - Computes the arc tangent (inverse tangent) of a PyTorch tensor element-wise. - - category: PyTorch wrapper - Math operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Computes the element-wise arc tangent (inverse tangent) of a PyTorch tensor. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of numerical type. - - Returns: - tuple: A tuple containing the resultant tensor after applying the arc tangent function. - """ - return (torch.atan(tens_a),) diff --git a/modules/pytorch_wrapper/pt_bitwise_and.py b/modules/pytorch_wrapper/pt_bitwise_and.py deleted file mode 100644 index ca130ef..0000000 --- a/modules/pytorch_wrapper/pt_bitwise_and.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtBitwiseAnd: - """ - Performs a bitwise AND operation on two PyTorch tensors element-wise. - - category: PyTorch wrapper - Bitwise operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Performs a bitwise AND operation on two PyTorch tensors. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of integer type. - tens_b (torch.Tensor): A PyTorch tensor of the same shape and integer type as `tens_a`. - - Returns: - tuple: A tuple containing the resultant tensor after applying bitwise AND. - """ - return (torch.bitwise_and(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_bitwise_left_shift.py b/modules/pytorch_wrapper/pt_bitwise_left_shift.py deleted file mode 100644 index 19cea42..0000000 --- a/modules/pytorch_wrapper/pt_bitwise_left_shift.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import Any, Dict -import torch - -class PtBitwiseLeftShift: - """ - Performs a bitwise left shift operation on two PyTorch tensors element-wise. - - category: PyTorch wrapper - Bitwise operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Performs a bitwise left shift operation on two PyTorch tensors. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of integer type. - tens_b (torch.Tensor): A PyTorch tensor of the same shape and integer type as `tens_a`, - representing the number of positions to shift. - - Returns: - tuple: A tuple containing the resultant tensor after applying bitwise left shift. - """ - return (torch.bitwise_left_shift(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_bitwise_not.py b/modules/pytorch_wrapper/pt_bitwise_not.py deleted file mode 100644 index 7fb05c0..0000000 --- a/modules/pytorch_wrapper/pt_bitwise_not.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtBitwiseNot: - """ - Performs a bitwise NOT operation on a PyTorch tensor element-wise. - - category: PyTorch wrapper - Bitwise operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Performs a bitwise NOT operation on a PyTorch tensor. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of integer type. - - Returns: - tuple: A tuple containing the resultant tensor after applying bitwise NOT. - """ - return (torch.bitwise_not(tens_a),) diff --git a/modules/pytorch_wrapper/pt_bitwise_or.py b/modules/pytorch_wrapper/pt_bitwise_or.py deleted file mode 100644 index b5d86f8..0000000 --- a/modules/pytorch_wrapper/pt_bitwise_or.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtBitwiseOr: - """ - Performs a bitwise OR operation on two PyTorch tensors element-wise. - - category: PyTorch wrapper - Bitwise operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Performs a bitwise OR operation on two PyTorch tensors. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of integer type. - tens_b (torch.Tensor): A PyTorch tensor of the same shape and integer type as `tens_a`. - - Returns: - tuple: A tuple containing the resultant tensor after applying bitwise OR. - """ - return (torch.bitwise_or(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_bitwise_right_shift.py b/modules/pytorch_wrapper/pt_bitwise_right_shift.py deleted file mode 100644 index fafde91..0000000 --- a/modules/pytorch_wrapper/pt_bitwise_right_shift.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import Any, Dict -import torch - -class PtBitwiseRightShift: - """ - Performs a bitwise right shift operation on two PyTorch tensors element-wise. - - category: PyTorch wrapper - Bitwise operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Performs a bitwise right shift operation on two PyTorch tensors. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of integer type. - tens_b (torch.Tensor): A PyTorch tensor of the same shape and integer type as `tens_a`, - representing the number of positions to shift. - - Returns: - tuple: A tuple containing the resultant tensor after applying bitwise right shift. - """ - return (torch.bitwise_right_shift(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_bitwise_xor.py b/modules/pytorch_wrapper/pt_bitwise_xor.py deleted file mode 100644 index bf910e0..0000000 --- a/modules/pytorch_wrapper/pt_bitwise_xor.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtBitwiseXor: - """ - Performs a bitwise XOR operation on two PyTorch tensors element-wise. - - category: PyTorch wrapper - Bitwise operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Performs a bitwise XOR operation on two PyTorch tensors. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of integer type. - tens_b (torch.Tensor): A PyTorch tensor of the same shape and integer type as `tens_a`. - - Returns: - tuple: A tuple containing the resultant tensor after applying bitwise XOR. - """ - return (torch.bitwise_xor(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_bmm.py b/modules/pytorch_wrapper/pt_bmm.py deleted file mode 100644 index 9bebbc6..0000000 --- a/modules/pytorch_wrapper/pt_bmm.py +++ /dev/null @@ -1,44 +0,0 @@ -from typing import Any, Dict -import torch - -class PtBmm: - """ - Performs batched matrix multiplication on two 3D PyTorch tensors. - - category: PyTorch wrapper - Matrix operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Performs batched matrix multiplication on two 3D PyTorch tensors. - - Args: - tens_a (torch.Tensor): A 3D PyTorch tensor (batch, m, n). - tens_b (torch.Tensor): A 3D PyTorch tensor (batch, n, p) that supports matrix multiplication with `tens_a`. - - Returns: - tuple: A tuple containing the resultant 3D tensor. - """ - if tens_a.dim() != 3 or tens_b.dim() != 3: - raise ValueError("torch.bmm() only supports batch matrix multiplication for 3D tensors.") - - return (torch.bmm(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_bool_create.py b/modules/pytorch_wrapper/pt_bool_create.py deleted file mode 100644 index 016e34a..0000000 --- a/modules/pytorch_wrapper/pt_bool_create.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import Any, Dict -import torch -import ast - - -class PtBoolCreate: - """ - Creates a PyTorch tensor of dtype bool from True or False values entered as a list in the text field. - For example, entering: - ``` - [True, False, False] - ``` - will create a 1D boolean tensor. - - category: PyTorch wrapper - Tensor creation - """ - - @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 = "Data Analysis" - - def f(self, data: str) -> tuple: - """ - Creates a PyTorch tensor with bool data type using True or False string 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.bool) - return (tensor,) diff --git a/modules/pytorch_wrapper/pt_cos.py b/modules/pytorch_wrapper/pt_cos.py deleted file mode 100644 index 7a66956..0000000 --- a/modules/pytorch_wrapper/pt_cos.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtCos: - """ - Computes the cosine of a PyTorch tensor element-wise. - - category: PyTorch wrapper - Math operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Computes the element-wise cosine of a PyTorch tensor. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of numerical type, representing angles in radians. - - Returns: - tuple: A tuple containing the resultant tensor after applying the cosine function. - """ - return (torch.cos(tens_a),) diff --git a/modules/pytorch_wrapper/pt_cosh.py b/modules/pytorch_wrapper/pt_cosh.py deleted file mode 100644 index b168937..0000000 --- a/modules/pytorch_wrapper/pt_cosh.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtCosh: - """ - Computes the hyperbolic cosine of a PyTorch tensor element-wise. - - category: PyTorch wrapper - Math operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Computes the element-wise hyperbolic cosine of a PyTorch tensor. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of numerical type. - - Returns: - tuple: A tuple containing the resultant tensor after applying the hyperbolic cosine function. - """ - return (torch.cosh(tens_a),) diff --git a/modules/pytorch_wrapper/pt_crop.py b/modules/pytorch_wrapper/pt_crop.py deleted file mode 100644 index 45c1fc3..0000000 --- a/modules/pytorch_wrapper/pt_crop.py +++ /dev/null @@ -1,65 +0,0 @@ -from typing import Any, Dict -import torch - -class PtCrop: - """ - Crops a PyTorch tensor to the specified size. The input tensor must have a shape of (c, h, w) or (b, c, h, w). - - In ComfyUI Analysis, the tensor data type is "TENSOR," while in ComfyUI, the type "IMAGE" is used. To pass image data from ComfyUI to a ComfyUI Analysis node (e.g., this interpolation node), first use the "Pt From Image" node to convert it to a tensor, then transpose the axes from (b, h, w, c) to (b, c, h, w) before passing it to this node. - - This transposition can be performed using the "Pt Permute" node with (0, 2, 3, 1). To convert the output back to an image, apply "Pt Permute" with (0, 3, 1, 2) and then use the "Pt To Image" node. - - category: PyTorch wrapper - Image processing - """ - - @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", {}), - "height": ("INT", {"default":0, "min":1, "max": 16384}), - "width": ("INT", {"default":0, "min":1, "max": 16384}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, height: int, width: int) -> tuple: - """ - Crops a PyTorch tensor. - - Args: - tens (torch.Tensor): A PyTorch tensor with the shape of (c, h, w) or (b, c, h, w). - height (int): New image height. - width (int): New image width. - - Returns: - tuple: A tuple containing a transformed tensor`. - """ - dim = tens.dim() - if dim == 3: # c, h, w - image_height = tens.size(1) - image_width = tens.size(2) - elif dim == 4: # b, c, h, w - image_height = tens.size(2) - image_width = tens.size(3) - else: - raise ValueError("Only rank 3 or 4 tensors are supported for crop.") - - if height > image_height or width > image_width: - raise ValueError("Specified crop size exceeds tensor dimensions.") - - # Compute starting position for cropping - top = max(int((height - image_height) / 2), 0) - left = max(int((width - image_width) / 2), 0) - - tens2 = tens[..., top:top+height, left:left+width] - return (tens2,) \ No newline at end of file diff --git a/modules/pytorch_wrapper/pt_data_loader.py b/modules/pytorch_wrapper/pt_data_loader.py deleted file mode 100644 index 7ec163b..0000000 --- a/modules/pytorch_wrapper/pt_data_loader.py +++ /dev/null @@ -1,81 +0,0 @@ -import inspect -from typing import Any, Dict -import ast -import torch -from torch.utils.data import DataLoader - - -class PtDataLoader: - """ - Loads data from a dataset node and creates a PyTorch DataLoader. - - **dataset** - Connect a PtvDataset node that provides the dataset. - - **batch size** - Specifies the number of samples per batch. - - **shuffle** - If True, shuffles the dataset before each epoch. - - category: PyTorch wrapper - Training - """ - - @classmethod - def INPUT_TYPES(cls) -> Dict[str, Any]: - """ - Defines the input types required for data loading. - - Returns: - Dict[str, Any]: A dictionary specifying required and optional input types. - """ - return { - "required": { - "dataset": ("PTVDATASET", {}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 1e6}), - "shuffle": ("BOOLEAN", {"default": False}), - }, - "optional": { - "parameters": ("STRING", {"default": "", "multiline": True}) - } - } - - RETURN_TYPES: tuple = ("PTDATALOADER",) - FUNCTION: str = "f" - CATEGORY: str = "Training" - - @classmethod - def IS_CHANGED(cls, **kw): - return float("NaN") - - def f(self, - dataset: Any, # One of the classes defined in torchvision.datasets - batch_size: int, - shuffle: bool, - parameters: str) -> tuple: - """ - Creates a PyTorch DataLoader for the given dataset. - - Args: - dataset (torchvision.datasets.{MNIST, ...}): An instance of a dataset from torchvision.datasets. - batch_size (int): The number of samples per batch. - shuffle (bool): If True, shuffles the dataset before each epoch. - parameters (str): Additional parameters in Python dictionary format. - - Returns: - tuple: A tuple containing the created DataLoader instance. - """ - with torch.inference_mode(False): - param_dict = ast.literal_eval(parameters) - - # Get valid parameters for the DataLoader class - valid_params = inspect.signature(DataLoader).parameters - - # Filter only parameters that match the dataset's constructor - filtered_params = {k: v for k, v in param_dict.items() if k in valid_params} - - dl = DataLoader(dataset=dataset, - batch_size=batch_size, - shuffle=shuffle, - **filtered_params) - return (dl,) diff --git a/modules/pytorch_wrapper/pt_div.py b/modules/pytorch_wrapper/pt_div.py deleted file mode 100644 index db87bc0..0000000 --- a/modules/pytorch_wrapper/pt_div.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtDiv: - """ - Divides one PyTorch tensor by another element-wise. - - category: PyTorch wrapper - Arithmetic operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Divides one PyTorch tensor by another element-wise. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of any shape. - tens_b (torch.Tensor): A PyTorch tensor of shape that supports element-wise division with `tens_a`. - - Returns: - tuple: A tuple containing the resultant tensor. - """ - return (torch.div(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_einsum.py b/modules/pytorch_wrapper/pt_einsum.py deleted file mode 100644 index ddd4dc8..0000000 --- a/modules/pytorch_wrapper/pt_einsum.py +++ /dev/null @@ -1,67 +0,0 @@ -from typing import Any, Dict -import torch - - -class PtEinsum: - """ - Performs Tensor operations specified in the Einstein summation equation. - - Specify Einstein summation notation in the first socket. - Specify two tensors in tens_a, tens_b. If you specify the third tensor, specify in tens_c. - Otherwise, leave the socket unconnected. - - Sublist format is not supported. - - category: PyTorch wrapper - Matrix operations - """ - - @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": { - "equation": ("STRING", {"default": ""}), - "tens_a": ("TENSOR", {}) - }, - "optional":{ - "tens_b": ("TENSOR", {}), - "tens_c": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, - equation: str, - tens_a: torch.Tensor, - tens_b: torch.Tensor = None, - tens_c: torch.Tensor = None) -> tuple: - """ - Performs Tensor operations specified in the Einstein summation equation. - - Args: - equation (str): Einstein summation equation. - tens_a (torch.Tensor): A PyTorch tensor. - tens_b (torch.Tensor): A PyTorch tensor. - tens_c (torch.Tensor): A PyTorch tensor. - - Returns: - tuple: A tuple containing the resultant tensor. - """ - if tens_a is None: - raise ValueError("tens_a is None") - - if tens_b is None and tens_c is None: - tens_ret_val = torch.einsum(equation, tens_a) - elif tens_c is None: - tens_ret_val = torch.einsum(equation, tens_a, tens_b) - else: - tens_ret_val = torch.einsum(equation, tens_a, tens_b, tens_c) - return (tens_ret_val,) diff --git a/modules/pytorch_wrapper/pt_eq.py b/modules/pytorch_wrapper/pt_eq.py deleted file mode 100644 index ffadbce..0000000 --- a/modules/pytorch_wrapper/pt_eq.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtEq: - """ - Tests whether two PyTorch tensors are equal element-wise. - - category: PyTorch wrapper - Comparison operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Compares two PyTorch tensors for element-wise equality. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of any shape. - tens_b (torch.Tensor): A PyTorch tensor of the same shape as `tens_a`. - - Returns: - tuple: A tuple containing a boolean tensor indicating element-wise equality. - """ - return (torch.eq(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_evaluate_classification_model.py b/modules/pytorch_wrapper/pt_evaluate_classification_model.py deleted file mode 100644 index f915c32..0000000 --- a/modules/pytorch_wrapper/pt_evaluate_classification_model.py +++ /dev/null @@ -1,71 +0,0 @@ -from typing import Any, Dict, Tuple -import torch -import torch.nn as nn -import torch.nn.functional as F -from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score - -class PtEvaluateClassificationModel: - """ - Performs inference on test data and computes evaluation metrics. - - category: PyTorch wrapper - Training - """ - - @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": { - "model": ("PTMODEL", {}), - "data_loader": ("PTDATALOADER", {}), - "use_gpu": ("BOOLEAN", {"default":False}) - } - } - - RETURN_NAMES: Tuple[str, ...] = ("accuracy", "precision", "recall", "f1", "num_samples") - RETURN_TYPES: Tuple[str, ...] = ("FLOAT", "FLOAT", "FLOAT", "FLOAT", "INT") - FUNCTION: str = "f" - CATEGORY: str = "Training" - - def f(self, model: nn.Module, data_loader: torch.utils.data.DataLoader, use_gpu: bool) -> Tuple[float, float, float, float, int]: - """ - Performs inference on test data and computes evaluation metrics. - - Args: - model (torch.nn.Module): The PyTorch model to evaluate. - data_loader (torch.utils.data.DataLoader): DataLoader for the test dataset. - use_gpu (bool): Whether to use GPU for inference. - - Returns: - Tuple[float, float, float, float, int]: A tuple containing accuracy, precision, recall, - F1-score, and the number of samples. - """ - - with torch.inference_mode(): - if use_gpu: - model.to("cuda") - model.train(False) # TODO: Change to model.*e*v*a*l() once Comfy's security checker is fixed. - y_hat = [] - y = [] - - for images, labels in data_loader: - if use_gpu: - images = images.to("cuda") - labels = labels.to("cuda") - outputs = model(images) - preds = outputs.argmax(dim=1) - - y_hat.extend(preds.cpu().numpy()) - y.extend(labels.cpu().numpy()) - - accuracy = accuracy_score(y, y_hat) - precision = precision_score(y, y_hat, average='macro') - recall = recall_score(y, y_hat, average='macro') - f1 = f1_score(y, y_hat, average='macro') - num_samples = len(y) - return (accuracy, precision, recall, f1, num_samples) diff --git a/modules/pytorch_wrapper/pt_exp.py b/modules/pytorch_wrapper/pt_exp.py deleted file mode 100644 index 3773009..0000000 --- a/modules/pytorch_wrapper/pt_exp.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtExp: - """ - Performs an exponential operation on a PyTorch tensor element-wise. - - category: PyTorch wrapper - Math operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Computes the element-wise exponential of a PyTorch tensor. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of numerical type. - - Returns: - tuple: A tuple containing the resultant tensor after applying the exponential function. - """ - return (torch.exp(tens_a),) diff --git a/modules/pytorch_wrapper/pt_flatten.py b/modules/pytorch_wrapper/pt_flatten.py deleted file mode 100644 index ac56d3a..0000000 --- a/modules/pytorch_wrapper/pt_flatten.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Any, Dict -import torch - - -class PtFlatten: - """ - Flattens a PyTorch tensor into a 1D tensor. - - category: PyTorch wrapper - Transform - """ - - @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 = "Data Analysis" - - 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(),) diff --git a/modules/pytorch_wrapper/pt_float_create.py b/modules/pytorch_wrapper/pt_float_create.py deleted file mode 100644 index 264b986..0000000 --- a/modules/pytorch_wrapper/pt_float_create.py +++ /dev/null @@ -1,47 +0,0 @@ -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 - Tensor creation - """ - - @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 = "Data Analysis" - - 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_floor_divide.py b/modules/pytorch_wrapper/pt_floor_divide.py deleted file mode 100644 index f579d98..0000000 --- a/modules/pytorch_wrapper/pt_floor_divide.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtFloorDiv: - """ - Performs element-wise floor division on two PyTorch tensors. - - category: PyTorch wrapper - Arithmetic operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Performs element-wise floor division on two PyTorch tensors. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of any shape. - tens_b (torch.Tensor): A PyTorch tensor of shape that supports element-wise floor division with `tens_a`. - - Returns: - tuple: A tuple containing the resultant tensor. - """ - return (torch.floor_divide(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_from_image.py b/modules/pytorch_wrapper/pt_from_image.py deleted file mode 100644 index 8ee0d5e..0000000 --- a/modules/pytorch_wrapper/pt_from_image.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Any, Dict -import torch - - -class PtFromImage: - """ - Casts an Image tensor as a PyTorch tensor. - - category: PyTorch wrapper - Image processing - """ - - @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": { - "image": ("IMAGE", {}), - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, image: torch.Tensor) -> tuple: - """ - Casts an Image tensor as a PyTorch tensor. - - Args: - image (torch.Tensor): Image tensor - - Returns: - tuple: A tuple containing the PyTorch tensor. - """ - return (image,) diff --git a/modules/pytorch_wrapper/pt_from_image_transpose.py b/modules/pytorch_wrapper/pt_from_image_transpose.py deleted file mode 100644 index 4243728..0000000 --- a/modules/pytorch_wrapper/pt_from_image_transpose.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import Any, Dict -import torch - - -class PtFromImageTranspose: - """ - Casts an image tensor to a PyTorch tensor and transposes it from (H, W, C) to (C, H, W). For rank-4 inputs, the batch axis remains unchanged. - - category: PyTorch wrapper - Image processing - """ - - @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": { - "image": ("IMAGE", {}), - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, image: torch.Tensor) -> tuple: - """ - Casts an Image tensor as a PyTorch tensor. - - Args: - image (torch.Tensor): Image tensor - - Returns: - tuple: A tuple containing the PyTorch tensor. - """ - if image.dim() == 3: - tens = image.permute(2, 0, 1) - elif image.dim() == 4: - tens = image.permute(0, 3, 1, 2) - else: - raise ValueError("Only rank 3 or 4 tensors are supported.") - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_from_latent.py b/modules/pytorch_wrapper/pt_from_latent.py deleted file mode 100644 index bfb581b..0000000 --- a/modules/pytorch_wrapper/pt_from_latent.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Any, Dict -import torch - - -class PtFromLatent: - """ - Casts a latent tensor as a PyTorch tensor. - - category: PyTorch wrapper - Tensor creation - """ - - @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": { - "latent": ("LATENT", {}), - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, latent: torch.Tensor) -> tuple: - """ - Casts a latent tensor as a PyTorch tensor. - - Args: - latent (torch.Tensor): Latent tensor - - Returns: - tuple: A tuple containing the PyTorch tensor. - """ - return (latent["samples"],) diff --git a/modules/pytorch_wrapper/pt_from_numpy.py b/modules/pytorch_wrapper/pt_from_numpy.py deleted file mode 100644 index 0c068d8..0000000 --- a/modules/pytorch_wrapper/pt_from_numpy.py +++ /dev/null @@ -1,42 +0,0 @@ -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 - Tensor creation - """ - - @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 = "Data Analysis" - - 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_full.py b/modules/pytorch_wrapper/pt_full.py deleted file mode 100644 index 8ce80a8..0000000 --- a/modules/pytorch_wrapper/pt_full.py +++ /dev/null @@ -1,66 +0,0 @@ -from typing import Any, Dict -import torch -import ast -from .utils import DTYPE_MAPPING - -class PtFull: - """ - Creates a PyTorch tensor filled with a specified value using the size entered in the text field. - For example, if you want to create a 4D tensor of batch size=2, channel=3, height=96, width=32 filled with 7, enter: - [2,3,96,32] for size and 7 for value. - - category: PyTorch wrapper - Tensor creation - """ - - @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": { - "value": ("STRING", {"multiline": False}), - "size": ("STRING", {"multiline": True}), - "data_type": (("float32", "float16", "bfloat16", "float64", "uint8", "int8", "int16", "int32", "int64", "bool"),) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, value: str, size: str, data_type: str) -> tuple: - """ - Creates a PyTorch tensor filled with a specified value. - - Args: - value (str): String representing the value to fill the tensor. - size (str): String representation of the tensor size. - data_type (str): Desired data type of the tensor. - - Returns: - tuple: A tuple containing the PyTorch tensor. - """ - # Convert string to Python list - list_size = ast.literal_eval(size) - - # Convert the value to the appropriate type - dtype = DTYPE_MAPPING[data_type] - - if dtype in {torch.float32, torch.float16, torch.bfloat16, torch.float64}: - fill_value = float(value) - elif dtype in {torch.uint8, torch.int8, torch.int16, torch.int32, torch.int64}: - fill_value = int(value) - elif dtype == torch.bool: - fill_value = value.lower() in {"true", "1"} - else: - raise ValueError(f"Unsupported data type: {data_type}") - - # Convert list to PyTorch Size - sz = torch.Size(list_size) - tens = torch.full(sz, fill_value, dtype=dtype) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_gather.py b/modules/pytorch_wrapper/pt_gather.py deleted file mode 100644 index 82c301b..0000000 --- a/modules/pytorch_wrapper/pt_gather.py +++ /dev/null @@ -1,94 +0,0 @@ -from typing import Any, Dict -import torch -import ast - - -class PtGather: - """ - Generates a tensor based on the index tensor using PyTorch's `gather` function. - - Specify the `dim` field using an integer to indicate the axis along which to gather elements. - Each input tensor element is selected based on the index tensor. - - For example, consider the following input tensor: - ``` - [ [10, 20, 30, 40, 50], - [100, 200, 300, 400, 500]] - ``` - - and the corresponding index tensor: - - ``` - [ [0, 4, 3, 2, 0], - [4, 3, 0, 0, 0]] - ``` - - If `dim = 0`, the function scans row-wise, treating each index value as a row index. - If `dim = 1`, it scans column-wise, interpreting each index value as a column index. - - Let's analyze the case where `dim = 1`: - - * Start at row 0, column 0. - * The index at this position is `index[0, 0] = 0`. - * PyTorch selects the value at position 0 from the array `[10, 20, 30, 40, 50]`, which is `10`. - * The result at this stage is: - ``` - [[10, ?, ?, ?, ?] - [?, ?, ?, ?, ?]] - ``` - - * Moving to column 1: `index[0, 1] = 4`. - * The value at index 4 in `[10, 20, 30, 40, 50]` is `50`. - * The updated result is: - ``` - [[10, 50, ?, ?, ?] - [?, ?, ?, ?, ?]] - ``` - - * Repeating this process for all elements constructs the final gathered tensor. - - category: PyTorch wrapper - Indexing and Slicing Operations - """ - - @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", {}), - "dim": ("INT", {"default": 0, "min": -10, "max": 10}), - "index": ("STRING", {"multiline": True}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, dim: int, index: str) -> tuple: - """ - Gathers elements along the specified dimension using an index tensor. - - Args: - tens (torch.Tensor): The input tensor. - dim (int): The dimension along which to gather values. - index (str): A string representation of the index tensor, provided in list format. - - Returns: - tuple: A tuple containing the gathered tensor. - """ - # Parse the index string to a Python list - index = ast.literal_eval(index) - - # Convert the parsed list to a tensor - index_tensor = torch.tensor(index, dtype=torch.long, device=tens.device) - - # Perform the gather operation - gathered_tens = torch.gather(tens, dim=dim, index=index_tensor) - - return (gathered_tens,) diff --git a/modules/pytorch_wrapper/pt_ge.py b/modules/pytorch_wrapper/pt_ge.py deleted file mode 100644 index 9fc7798..0000000 --- a/modules/pytorch_wrapper/pt_ge.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtGe: - """ - Tests whether elements in the first PyTorch tensor are greater than or equal to the corresponding elements in the second tensor. - - category: PyTorch wrapper - Comparison operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Compares two PyTorch tensors for element-wise greater-than-or-equal-to condition. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of any shape. - tens_b (torch.Tensor): A PyTorch tensor of the same shape as `tens_a`. - - Returns: - tuple: A tuple containing a boolean tensor indicating where `tens_a >= tens_b`. - """ - return (torch.ge(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_gt.py b/modules/pytorch_wrapper/pt_gt.py deleted file mode 100644 index f30d6f5..0000000 --- a/modules/pytorch_wrapper/pt_gt.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtGt: - """ - Tests whether elements in the first PyTorch tensor are greater than the corresponding elements in the second tensor. - - category: PyTorch wrapper - Comparison operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Compares two PyTorch tensors for element-wise greater-than condition. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of any shape. - tens_b (torch.Tensor): A PyTorch tensor of the same shape as `tens_a`. - - Returns: - tuple: A tuple containing a boolean tensor indicating where `tens_a > tens_b`. - """ - return (torch.gt(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_index_select.py b/modules/pytorch_wrapper/pt_index_select.py deleted file mode 100644 index 8903e68..0000000 --- a/modules/pytorch_wrapper/pt_index_select.py +++ /dev/null @@ -1,78 +0,0 @@ -from typing import Any, Dict -import torch -import ast - - -class PtIndexSelect: - """ - Extracts elements from the input tensor along a specified dimension using an index tensor. - - `dim` specifies the dimension along which the selection occurs. - * For example, for 2D tensors: - ** If `dim=0`, `index` selects rows. - ** If `dim=1`, `index` selects columns (for 2D tensors). - - `index` is a list of indices that will be converted into a tensor. - - Example: - ``` - Input Tensor: - [[1, 2, 3], - [40, 50, 60], - [700, 800, 900]] - - index = "[0, 2]" - dim = 0 - - Output Tensor: - [[1, 2, 3], - [700, 800, 900]] - ``` - - category: PyTorch wrapper - Indexing and Slicing Operations - """ - - @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", {}), - "dim": ("INT", {"default": 0, "min": -10, "max": 10}), - "index": ("STRING", {"multiline": True}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, dim: int, index: str) -> tuple: - """ - Extracts elements from the input tensor along the specified dimension. - - Args: - tens (torch.Tensor): The input tensor. - dim (int): The dimension along which elements are selected. - index (str): A string representation of a list of indices, e.g., "[0, 2]". - - Returns: - tuple: A tuple containing the selected tensor. - """ - # Convert the string index to a Python list - index_list = ast.literal_eval(index) - if not isinstance(index_list, list): - raise ValueError("Index must be a list of integers.") - - # Convert the index list to a tensor - index_tensor = torch.tensor(index_list, dtype=torch.long) - - # Perform index selection - result = torch.index_select(tens, dim=dim, index=index_tensor) - - return (result,) diff --git a/modules/pytorch_wrapper/pt_int_create.py b/modules/pytorch_wrapper/pt_int_create.py deleted file mode 100644 index a1bdb9e..0000000 --- a/modules/pytorch_wrapper/pt_int_create.py +++ /dev/null @@ -1,47 +0,0 @@ -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 - Tensor creation - """ - - @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 = "Data Analysis" - - 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_interpolate_by_scale_factor.py b/modules/pytorch_wrapper/pt_interpolate_by_scale_factor.py deleted file mode 100644 index f1a5dc7..0000000 --- a/modules/pytorch_wrapper/pt_interpolate_by_scale_factor.py +++ /dev/null @@ -1,56 +0,0 @@ -from typing import Any, Dict -import torch - -class PtInterpolateByScaleFactor: - """ - Resizes a PyTorch tensor using interpolation by scale factor. The input tensor must have a shape of (c, h, w) or (b, c, h, w). - - In ComfyUI Analysis, the tensor data type is "TENSOR," while in ComfyUI, the type "IMAGE" is used. To pass image data from ComfyUI to a ComfyUI Analysis node (e.g., this interpolation node), first use the "Pt From Image" node to convert it to a tensor, then transpose the axes from (b, h, w, c) to (b, c, h, w) before passing it to this node. - - This transposition can be performed using the "Pt Permute" node with (0, 2, 3, 1). To convert the output back to an image, apply "Pt Permute" with (0, 3, 1, 2) and then use the "Pt To Image" node. - - category: PyTorch wrapper - Image processing - """ - - @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", {}), - "scale_factor": ("FLOAT", {"default":2.0, "min":0.01, "max": 100.0, "step":0.01}), - "mode": (("nearest-exact", "bilinear", "bicubic", "nearest"),) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, scale_factor: float, mode: str) -> tuple: - """ - Resizes a PyTorch tensor using interpolation. - - Args: - tens (torch.Tensor): A PyTorch tensor with the shape of (c, h, w) or (b, c, h, w). - scale_factor (float): Scale factor. - mode (string): Interpolation mode. - - Returns: - tuple: A tuple containing a transformed tensor`. - """ - if tens.dim() == 3: - rank3 = True - tens_adjusted = torch.unsqueeze(tens, 0) - else: - rank3 = False - tens_adjusted = tens - tens2 = torch.nn.functional.interpolate(tens_adjusted, scale_factor=scale_factor, mode=mode) - if rank3: - tens2 = torch.squeeze(tens2, 0) - return (tens2,) \ No newline at end of file diff --git a/modules/pytorch_wrapper/pt_interpolate_to_size.py b/modules/pytorch_wrapper/pt_interpolate_to_size.py deleted file mode 100644 index b2183da..0000000 --- a/modules/pytorch_wrapper/pt_interpolate_to_size.py +++ /dev/null @@ -1,60 +0,0 @@ -from typing import Any, Dict -import torch - -class PtInterpolateToSize: - """ - Resizes a PyTorch tensor using interpolation. The input tensor must have a shape of (c, h, w) or (b, c, h, w). - - In ComfyUI Analysis, the tensor data type is "TENSOR," while in ComfyUI, the type "IMAGE" is used. To pass image data from ComfyUI to a ComfyUI Analysis node (e.g., this interpolation node), first use the "Pt From Image" node to convert it to a tensor, then transpose the axes from (b, h, w, c) to (b, c, h, w) before passing it to this node. - - This transposition can be performed using the "Pt Permute" node with (0, 2, 3, 1). To convert the output back to an image, apply "Pt Permute" with (0, 3, 1, 2) and then use the "Pt To Image" node. - - category: PyTorch wrapper - Image processing - """ - - @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", {}), - "height": ("INT", {"default":0, "min":1, "max": 16384}), - "width": ("INT", {"default":0, "min":1, "max": 16384}), - "mode": (("nearest-exact", "bilinear", "bicubic", "nearest"),) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, height: int, width: int, mode: str) -> tuple: - """ - Resizes a PyTorch tensor using interpolation. - - Args: - tens (torch.Tensor): A PyTorch tensor with the shape of (c, h, w) or (b, c, h, w). - height (int): New image height. - width (int): New image width. - mode (string): Interpolation mode. - - Returns: - tuple: A tuple containing a transformed tensor`. - """ - if tens.dim() == 3: - rank3 = True - tens_adjusted = torch.unsqueeze(tens, 0) - else: - rank3 = False - tens_adjusted = tens - - tens2 = torch.nn.functional.interpolate(tens_adjusted, size=(height, width), mode=mode) - - if rank3: - tens2 = torch.squeeze(tens2, 0) - return (tens2,) \ No newline at end of file diff --git a/modules/pytorch_wrapper/pt_le.py b/modules/pytorch_wrapper/pt_le.py deleted file mode 100644 index 641cf79..0000000 --- a/modules/pytorch_wrapper/pt_le.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtLe: - """ - Tests whether elements in the first PyTorch tensor are less than or equal to the corresponding elements in the second tensor. - - category: PyTorch wrapper - Comparison operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Compares two PyTorch tensors for element-wise less-than-or-equal-to condition. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of any shape. - tens_b (torch.Tensor): A PyTorch tensor of the same shape as `tens_a`. - - Returns: - tuple: A tuple containing a boolean tensor indicating where `tens_a <= tens_b`. - """ - return (torch.le(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_linspace.py b/modules/pytorch_wrapper/pt_linspace.py deleted file mode 100644 index 731b1bc..0000000 --- a/modules/pytorch_wrapper/pt_linspace.py +++ /dev/null @@ -1,62 +0,0 @@ -import os -import sys -from typing import Any, Dict, Tuple -import torch -from .utils import DTYPE_MAPPING - -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 cda.utils import str_to_number, str_to_number_with_default - - -class PtLinspace: - """ - Creates a PyTorch tensor using `torch.linspace` with the specified start, end, and steps values. - - The start and end values are parsed from string inputs and converted to a float. - The steps value is converted to an int. - - category: PyTorch wrapper - Tensor creation - """ - - @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": { - "start": ("STRING", {"default":"", "multiline": False}), - "end": ("STRING", {"default":"", "multiline": False}), - "steps": ("STRING", {"default":"", "multiline": False}), - "data_type": (("float32", "float16", "bfloat16", "float64", "uint8", "int8", "int16", "int32", "int64"),) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - - def f(self, start: str, end: str, steps: str, data_type: str) -> Tuple[torch.Tensor]: - """ - Creates a PyTorch tensor using `torch.arange` with the given start, end, and step values. - - Args: - start (str): The starting value of the sequence. - end (str): The end value of the sequence (inclusive). - steps (str): The number of steps - data_type (str): The desired PyTorch dtype for the resulting tensor. - - Returns: - Tuple[torch.Tensor]: A tuple containing the generated PyTorch tensor. - """ - start_value = float(start) - end_value = float(end) - steps_value = int(steps) - tens = torch.linspace(start_value, end_value, steps_value, dtype=DTYPE_MAPPING[data_type]) - return (tens,) diff --git a/modules/pytorch_wrapper/pt_load_model.py b/modules/pytorch_wrapper/pt_load_model.py deleted file mode 100644 index eb65601..0000000 --- a/modules/pytorch_wrapper/pt_load_model.py +++ /dev/null @@ -1,41 +0,0 @@ -import os -from typing import Any, Dict -import torch -from .utils import get_model_full_path - -class PtLoadModel: - """ - A wrapper class for saving a PyTorch model. - - category: PyTorch wrapper - Training - """ - - @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": { - "model": ("PTMODEL", {}), - "model_path": ("STRING", {"default": "model.pkl"}) - } - } - - RETURN_NAMES: tuple = ("model",) - RETURN_TYPES: tuple = ("PTMODEL",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - @classmethod - def IS_CHANGED(cls, **kw): - return float("NaN") - - def f(self, model:Any, model_path: str): - model_full_path = get_model_full_path(model_path) - sd = torch.load(model_full_path, weights_only=True) - model.load_state_dict(sd) - return (model,) \ No newline at end of file diff --git a/modules/pytorch_wrapper/pt_log.py b/modules/pytorch_wrapper/pt_log.py deleted file mode 100644 index bebd1d7..0000000 --- a/modules/pytorch_wrapper/pt_log.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtLog: - """ - Computes the natural logarithm (log base e) of a PyTorch tensor element-wise. - - category: PyTorch wrapper - Math operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Computes the element-wise natural logarithm (log base e) of a PyTorch tensor. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of numerical type. Must contain only positive values. - - Returns: - tuple: A tuple containing the resultant tensor after applying the natural logarithm. - """ - return (torch.log(tens_a),) diff --git a/modules/pytorch_wrapper/pt_logical_and.py b/modules/pytorch_wrapper/pt_logical_and.py deleted file mode 100644 index 0b19d48..0000000 --- a/modules/pytorch_wrapper/pt_logical_and.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtLogicalAnd: - """ - Performs a logical AND operation on two PyTorch tensors element-wise. - - category: PyTorch wrapper - Logical operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Performs a logical AND operation on two PyTorch tensors. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of boolean or numerical type. - tens_b (torch.Tensor): A PyTorch tensor of the same shape and type as `tens_a`. - - Returns: - tuple: A tuple containing a boolean tensor where `tens_a AND tens_b` is applied element-wise. - """ - return (torch.logical_and(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_logical_not.py b/modules/pytorch_wrapper/pt_logical_not.py deleted file mode 100644 index 60c83dd..0000000 --- a/modules/pytorch_wrapper/pt_logical_not.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtLogicalNot: - """ - Performs a logical NOT operation on a PyTorch tensor element-wise. - - category: PyTorch wrapper - Logical operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Performs a logical NOT operation on a PyTorch tensor. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of boolean or numerical type. - - Returns: - tuple: A tuple containing the resultant tensor after applying logical NOT. - """ - return (torch.logical_not(tens_a),) diff --git a/modules/pytorch_wrapper/pt_logical_or.py b/modules/pytorch_wrapper/pt_logical_or.py deleted file mode 100644 index 8a6d1f7..0000000 --- a/modules/pytorch_wrapper/pt_logical_or.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtLogicalOr: - """ - Performs a logical OR operation on two PyTorch tensors element-wise. - - category: PyTorch wrapper - Logical operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Performs a logical OR operation on two PyTorch tensors. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of boolean or numerical type. - tens_b (torch.Tensor): A PyTorch tensor of the same shape and type as `tens_a`. - - Returns: - tuple: A tuple containing a boolean tensor where `tens_a OR tens_b` is applied element-wise. - """ - return (torch.logical_or(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_logical_xor.py b/modules/pytorch_wrapper/pt_logical_xor.py deleted file mode 100644 index 4b162cb..0000000 --- a/modules/pytorch_wrapper/pt_logical_xor.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtLogicalXor: - """ - Performs a logical XOR operation on two PyTorch tensors element-wise. - - category: PyTorch wrapper - Logical operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Performs a logical XOR operation on two PyTorch tensors. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of boolean or numerical type. - tens_b (torch.Tensor): A PyTorch tensor of the same shape and type as `tens_a`. - - Returns: - tuple: A tuple containing a boolean tensor where `tens_a XOR tens_b` is applied element-wise. - """ - return (torch.logical_xor(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_lt.py b/modules/pytorch_wrapper/pt_lt.py deleted file mode 100644 index 2fafb48..0000000 --- a/modules/pytorch_wrapper/pt_lt.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtLt: - """ - Tests whether elements in the first PyTorch tensor are less than the corresponding elements in the second tensor. - - category: PyTorch wrapper - Comparison operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Compares two PyTorch tensors for element-wise less-than condition. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of any shape. - tens_b (torch.Tensor): A PyTorch tensor of the same shape as `tens_a`. - - Returns: - tuple: A tuple containing a boolean tensor indicating where `tens_a < tens_b`. - """ - return (torch.lt(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_masked_select.py b/modules/pytorch_wrapper/pt_masked_select.py deleted file mode 100644 index 3c825e7..0000000 --- a/modules/pytorch_wrapper/pt_masked_select.py +++ /dev/null @@ -1,62 +0,0 @@ -from typing import Any, Dict -import torch - - -class PtMaskedSelect: - """ - Extracts elements from the input tensor whose corresponding value in `masked_tens` is `True`. - - Example: - ``` - Input Tensor: - [[1, 2, 3], - [40, 50, 60], - [700, 800, 900]] - - Masked Tensor: - [[True, False, False], - [False, True, False], - [False, False, True]] - - Output Tensor: - [1, 50, 900] - ``` - - category: PyTorch wrapper - Indexing and Slicing Operations - """ - - @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", {}), - "masked_tens": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, masked_tens: torch.Tensor) -> tuple: - """ - Extracts elements from `tens` where `masked_tens` is `True`. - - Args: - tens (torch.Tensor): The input tensor. - masked_tens (torch.Tensor): A boolean tensor specifying which elements to select. - - Returns: - tuple: A tuple containing the selected tensor. - """ - if masked_tens.dtype != torch.bool: - raise ValueError("masked_tens must be a boolean tensor (dtype=torch.bool)") - - result = torch.masked_select(tens, masked_tens) - return (result,) diff --git a/modules/pytorch_wrapper/pt_matmul.py b/modules/pytorch_wrapper/pt_matmul.py deleted file mode 100644 index 6401bfe..0000000 --- a/modules/pytorch_wrapper/pt_matmul.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtMatMul: - """ - Performs matrix multiplication on two PyTorch tensors. - - category: PyTorch wrapper - Matrix operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Performs matrix multiplication on two PyTorch tensors. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of shape compatible for matrix multiplication. - tens_b (torch.Tensor): A PyTorch tensor of shape that supports matrix multiplication with `tens_a`. - - Returns: - tuple: A tuple containing the resultant tensor. - """ - return (torch.matmul(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_max.py b/modules/pytorch_wrapper/pt_max.py deleted file mode 100644 index ce3ec28..0000000 --- a/modules/pytorch_wrapper/pt_max.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import Any, Dict -import torch -import ast -from .utils import str_to_dim - -class PtMax: - """ - Computes the maximum values of a PyTorch tensor along the specified dimension(s). - - Specify the dimension(s) in the `dim` field using an integer, a list, or a tuple, as shown below: - ``` - 0, [0], or (1, 2) - ``` - category: PyTorch wrapper - Reduction operation & Summary statistics - """ - - @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", {}), - "dim": ("STRING", {"multiline": True}), - "keepdim": ("BOOLEAN", {"default": False}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, dim: str, keepdim: bool) -> tuple: - """ - Computes the maximum values along the specified dimension(s). - - Args: - tens (torch.Tensor): The input tensor. - dim (str): A string representation of the dimension(s) along which to compute the maximum, - e.g., "0", "[0]", or "(1, 2)". - keepdim (bool): Whether to retain reduced dimensions. - - Returns: - tuple: A tuple containing the tensor with maximum values. - """ - dim = str_to_dim(dim) - - # Ensure keepdim is a boolean - if not isinstance(keepdim, bool): - raise TypeError(f"Invalid type for keepdim: {keepdim}. Must be a boolean.") - - # Compute the max along the specified dimension(s) - tens = torch.amax(tens, dim=dim, keepdim=keepdim) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_mean.py b/modules/pytorch_wrapper/pt_mean.py deleted file mode 100644 index 1745853..0000000 --- a/modules/pytorch_wrapper/pt_mean.py +++ /dev/null @@ -1,61 +0,0 @@ -from typing import Any, Dict -import torch -from .utils import str_to_dim - - -class PtMean: - """ - Computes the mean of a PyTorch tensor along the specified dimension(s). - - This node supports only floating-point or complex tensors as input. - - Specify the dimension(s) in the `dim` field using an integer, a list, or a tuple, as shown below: - ``` - 0, [0], or (1, 2) - ``` - category: PyTorch wrapper - Reduction operation & Summary statistics - """ - - @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", {}), - "dim": ("STRING", {"multiline": True}), - "keepdim": ("BOOLEAN", {"default": False}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, dim: str, keepdim: bool) -> tuple: - """ - Computes the mean along the specified dimension(s). - - Args: - tens (torch.Tensor): The input tensor. - dim (str): A string representation of the dimension(s) along which to compute the mean, - e.g., "0", "[0]", or "(1, 2)". - keepdim (bool): Whether to retain reduced dimensions. - - Returns: - tuple: A tuple containing the mean tensor. - """ - dim = str_to_dim(dim) - - # Ensure keepdim is a boolean - if not isinstance(keepdim, bool): - raise TypeError(f"Invalid type for keepdim: {keepdim}. Must be a boolean.") - - # Compute the mean along the specified dimension(s) - tens = torch.mean(tens, dim=dim, keepdim=keepdim) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_median.py b/modules/pytorch_wrapper/pt_median.py deleted file mode 100644 index 14971e5..0000000 --- a/modules/pytorch_wrapper/pt_median.py +++ /dev/null @@ -1,66 +0,0 @@ -from typing import Any, Dict -import torch -from .utils import str_to_dim - - -class PtMedian: - """ - Computes the median of a PyTorch tensor along the specified dimension(s). - - Specify the dimension(s) in the `dim` field using an integer as shown below: - ``` - 0 - ``` - or - ``` - 1 - ``` - Note that PtMedian calls torch.median(), which returns the lower of the two numbers when the true median falls between them. - - category: PyTorch wrapper - Reduction operation & Summary statistics - """ - - @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", {}), - "dim": ("STRING", {"multiline": True}), - "keepdim": ("BOOLEAN", {"default": False}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, dim: str, keepdim: bool) -> tuple: - """ - Computes the median along the specified dimension(s). - - Args: - tens (torch.Tensor): The input tensor. - dim (str): A string representation of the dimension(s) along which to compute the median, - e.g., "0", or "1". - keepdim (bool): Whether to retain reduced dimensions. - - Returns: - tuple: A tuple containing the median tensor. - """ - dim = str_to_dim(dim) - if not isinstance(dim, int): - raise TypeError(f"Invalid type for dim: {dim}. Must be a integer.") - # Ensure keepdim is a boolean - if not isinstance(keepdim, bool): - raise TypeError(f"Invalid type for keepdim: {keepdim}. Must be a boolean.") - - # Compute the median along the specified dimension(s) - tens, _ = torch.median(tens, dim=dim, keepdim=keepdim) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_min.py b/modules/pytorch_wrapper/pt_min.py deleted file mode 100644 index 87b3dd5..0000000 --- a/modules/pytorch_wrapper/pt_min.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import Any, Dict -import torch -from .utils import str_to_dim - - -class PtMin: - """ - Computes the minimum values of a PyTorch tensor along the specified dimension(s). - - Specify the dimension(s) in the `dim` field using an integer, a list, or a tuple, as shown below: - ``` - 0, [0], or (1, 2) - ``` - category: PyTorch wrapper - Reduction operation & Summary statistics - """ - - @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", {}), - "dim": ("STRING", {"multiline": True}), - "keepdim": ("BOOLEAN", {"default": False}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, dim: str, keepdim: bool) -> tuple: - """ - Computes the minimum values along the specified dimension(s). - - Args: - tens (torch.Tensor): The input tensor. - dim (str): A string representation of the dimension(s) along which to compute the minimum, - e.g., "0", "[0]", or "(1, 2)". - keepdim (bool): Whether to retain reduced dimensions. - - Returns: - tuple: A tuple containing the tensor with minimum values. - """ - dim = str_to_dim(dim) - - # Ensure keepdim is a boolean - if not isinstance(keepdim, bool): - raise TypeError(f"Invalid type for keepdim: {keepdim}. Must be a boolean.") - - # Compute the min along the specified dimension(s) - tens = torch.amin(tens, dim=dim, keepdim=keepdim) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_mm.py b/modules/pytorch_wrapper/pt_mm.py deleted file mode 100644 index 16f33b1..0000000 --- a/modules/pytorch_wrapper/pt_mm.py +++ /dev/null @@ -1,44 +0,0 @@ -from typing import Any, Dict -import torch - -class PtMm: - """ - Performs 2D matrix multiplication on two PyTorch tensors. - - category: PyTorch wrapper - Matrix operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Performs 2D matrix multiplication on two PyTorch tensors. - - Args: - tens_a (torch.Tensor): A 2D PyTorch tensor. - tens_b (torch.Tensor): A 2D PyTorch tensor that supports matrix multiplication with `tens_a`. - - Returns: - tuple: A tuple containing the resultant tensor. - """ - if tens_a.dim() != 2 or tens_b.dim() != 2: - raise ValueError("torch.mm() only supports 2D tensors.") - - return (torch.mm(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_mul.py b/modules/pytorch_wrapper/pt_mul.py deleted file mode 100644 index 1a4a1c1..0000000 --- a/modules/pytorch_wrapper/pt_mul.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtMul: - """ - Multiplies two PyTorch tensors element-wise. - - category: PyTorch wrapper - Arithmetic operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Multiplies two PyTorch tensors element-wise. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of any shape. - tens_b (torch.Tensor): A PyTorch tensor of shape that supports element-wise multiplication with `tens_a`. - - Returns: - tuple: A tuple containing the resultant tensor. - """ - return (torch.mul(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_ne.py b/modules/pytorch_wrapper/pt_ne.py deleted file mode 100644 index cd8aab9..0000000 --- a/modules/pytorch_wrapper/pt_ne.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtNe: - """ - Tests whether two PyTorch tensors are not equal element-wise. - - category: PyTorch wrapper - Comparison operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Compares two PyTorch tensors for element-wise inequality. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of any shape. - tens_b (torch.Tensor): A PyTorch tensor of the same shape as `tens_a`. - - Returns: - tuple: A tuple containing a boolean tensor indicating element-wise inequality. - """ - return (torch.ne(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_neg.py b/modules/pytorch_wrapper/pt_neg.py deleted file mode 100644 index c0dd79c..0000000 --- a/modules/pytorch_wrapper/pt_neg.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtNeg: - """ - Computes the negation of each element in a PyTorch tensor. - - category: PyTorch wrapper - Math operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Computes the negation of a PyTorch tensor element-wise. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of numerical type. - - Returns: - tuple: A tuple containing the resultant tensor after applying negation. - """ - return (torch.neg(tens_a),) diff --git a/modules/pytorch_wrapper/pt_ones.py b/modules/pytorch_wrapper/pt_ones.py deleted file mode 100644 index 7d6131e..0000000 --- a/modules/pytorch_wrapper/pt_ones.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Any, Dict -import torch -import ast -from .utils import DTYPE_MAPPING - -class PtOnes: - """ - Creates a PyTorch tensor of ones using the size entered in the text field. - For example, if you want to create a 4D tensor of batch size=2, channel=3, height=96, width=32, enter: - [2,3,96,32] - - category: PyTorch wrapper - Tensor creation - """ - - @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": { - "size": ("STRING", {"multiline": True}), - "data_type": (("float32", "float16", "bfloat16", "float64", "uint8", "int8", "int16", "int32", "int64", "bool"),) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, size: str, data_type: str) -> tuple: - """ - Creates a PyTorch tensor of ones using the size entered in the text field. - - Args: - size (str): String value in the text field. - data_type (str): Desired data type of the tensor. - - Returns: - tuple: A tuple containing the PyTorch tensor. - """ - # Convert string to Python list - list_size = ast.literal_eval(size) - - # Convert list to PyTorch Size - sz = torch.Size(list_size) - - tens = torch.ones(sz, dtype=DTYPE_MAPPING[data_type]) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_pad.py b/modules/pytorch_wrapper/pt_pad.py deleted file mode 100644 index 5300460..0000000 --- a/modules/pytorch_wrapper/pt_pad.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import Any, Dict -import torch - -class PtPad: - """ - Pads a PyTorch tensor to the specified size. Padded area will be black. The input tensor must have a shape of (c, h, w) or (b, c, h, w). - - In ComfyUI Analysis, the tensor data type is "TENSOR," while in ComfyUI, the type "IMAGE" is used. To pass image data from ComfyUI to a ComfyUI Analysis node (e.g., this interpolation node), first use the "Pt From Image" node to convert it to a tensor, then transpose the axes from (b, h, w, c) to (b, c, h, w) before passing it to this node. - - This transposition can be performed using the "Pt Permute" node with (0, 2, 3, 1). To convert the output back to an image, apply "Pt Permute" with (0, 3, 1, 2) and then use the "Pt To Image" node. - - category: PyTorch wrapper - Image processing - """ - - @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", {}), - "height": ("INT", {"default":0, "min":1, "max": 16384}), - "width": ("INT", {"default":0, "min":1, "max": 16384}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, height: int, width: int) -> tuple: - """ - Pads a PyTorch tensor. - - Args: - tens (torch.Tensor): A PyTorch tensor with the shape of (c, h, w) or (b, c, h, w). - height (int): New image height. - width (int): New image width. - - Returns: - tuple: A tuple containing a transformed tensor. - """ - dim = tens.dim() - if dim == 3: # c, h, w - image_height = tens.size(1) - image_width = tens.size(2) - elif dim == 4: # b, c, h, w - image_height = tens.size(2) - image_width = tens.size(3) - else: - raise ValueError("Only rank 3 or 4 tensors are supported for padding.") - - if height < image_height or width < image_width: - raise ValueError("Specified dimensions must be greater than or equal to tensor dimensions.") - - # Compute starting position for padding - top = max((height - image_height) // 2, 0) - left = max((width - image_width) // 2, 0) - - if dim == 3: - tens2 = torch.zeros(tens.size(0), height, width, - dtype=tens.dtype, device=tens.device) - else: - tens2 = torch.zeros(tens.size(0), tens.size(1), height, width, - dtype=tens.dtype, device=tens.device) - - tens2[..., top:top+image_height, left:left+image_width] = tens - return (tens2,) \ No newline at end of file diff --git a/modules/pytorch_wrapper/pt_permute.py b/modules/pytorch_wrapper/pt_permute.py deleted file mode 100644 index 86ea0f8..0000000 --- a/modules/pytorch_wrapper/pt_permute.py +++ /dev/null @@ -1,60 +0,0 @@ -from typing import Any, Dict -import torch -import ast - - -class PtPermute: - """ - Permutes the dimensions of a PyTorch tensor according to the specified order. - For example, if a tensor has shape (2, 3, 96, 32) and `new_axes="[0, 3, 1, 2]"`, - the output will have shape (2, 32, 3, 96). - - Specify the new axis order in the `new_axes` field using brackets, as shown below: - ``` - [0, 3, 1, 2] - ``` - category: PyTorch wrapper - Transform - """ - - @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", {}), - "new_axes": ("STRING", {"multiline": True}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, new_axes: str) -> tuple: - """ - Permutes the dimensions of the input tensor based on the specified order. - - Args: - tens (torch.Tensor): The input tensor. - new_axes (str): A string representation of the desired dimension order, - e.g., "[0, 3, 1, 2]" or "(0, 3, 1, 2)". - - Returns: - tuple: A tuple containing the permuted PyTorch tensor. - """ - # Convert string to a tuple of integers - permute_order = tuple(ast.literal_eval(new_axes)) - - # Ensure the parsed order is valid - if not isinstance(permute_order, tuple) or not all(isinstance(i, int) for i in permute_order): - raise ValueError(f"Invalid format for new_axes: {new_axes}. Must be a tuple or list of integers.") - - # Apply permutation - tens = tens.permute(*permute_order) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_pow.py b/modules/pytorch_wrapper/pt_pow.py deleted file mode 100644 index 52f3cb6..0000000 --- a/modules/pytorch_wrapper/pt_pow.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtPow: - """ - Raises one PyTorch tensor to the power of another element-wise. - - category: PyTorch wrapper - Arithmetic operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Raises one PyTorch tensor to the power of another element-wise. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of any shape. - tens_b (torch.Tensor): A PyTorch tensor of shape that supports element-wise exponentiation with `tens_a`. - - Returns: - tuple: A tuple containing the resultant tensor. - """ - return (torch.pow(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_predict_classification_model.py b/modules/pytorch_wrapper/pt_predict_classification_model.py deleted file mode 100644 index e9e1e70..0000000 --- a/modules/pytorch_wrapper/pt_predict_classification_model.py +++ /dev/null @@ -1,66 +0,0 @@ -from typing import Any, Dict, Tuple -import torch -import torch.nn as nn -import torch.nn.functional as F -from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score - -class PtPredictClassificationModel: - """ - Performs inference on input data. - - category: PyTorch wrapper - Training - """ - - @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": { - "model": ("PTMODEL", {}), - "inputs": ("TENSOR", {}), - "class_id_to_name_map": ("PYDICT", {}), - "use_gpu": ("BOOLEAN", {"default":False}) - } - } - - RETURN_NAMES: Tuple[str, ...] = ("class_name", "class_id", "probability") - RETURN_TYPES: Tuple[str, ...] = ("STRING", "INT", "FLOAT") - FUNCTION: str = "f" - CATEGORY: str = "Training" - - def f(self, model: nn.Module, - inputs: torch.Tensor, - class_id_to_name_map: Dict, - use_gpu: bool) -> Tuple[str, int, float]: - """ - Performs inference on test data and computes evaluation metrics. - - Args: - model (torch.nn.Module): The PyTorch model to evaluate. - inputs (torch.Tensor): Input image to predict the class for. - class_id_to_name_map (Dict): A Python dictionary contains the mapping of - class ID to class name. - use_gpu (bool): Whether to use GPU for inference. - - Returns: - Tuple[str, int, float]: A tuple containing "class_name", "class_id", "probability". - """ - - with torch.inference_mode(): - if use_gpu: - model.to("cuda") - model.train(False) # TODO: Change to model.*e*v*a*l() once Comfy's security checker is fixed. - x = torch.unsqueeze(inputs, 0) - if use_gpu: - x = x.to("cuda") - outputs = model(x) - class_id = outputs.argmax(dim=1)[0].detach().cpu().item() - prob = F.softmax(outputs, dim=1)[0][class_id].detach().cpu().item() - class_name = class_id_to_name_map[class_id] - - return (class_name, class_id, prob) diff --git a/modules/pytorch_wrapper/pt_prod.py b/modules/pytorch_wrapper/pt_prod.py deleted file mode 100644 index c40316a..0000000 --- a/modules/pytorch_wrapper/pt_prod.py +++ /dev/null @@ -1,65 +0,0 @@ -from typing import Any, Dict -import torch -from .utils import str_to_dim - - -class PtProd: - """ - Computes the product of a PyTorch tensor along the specified dimension(s). - - Specify the dimension(s) in the `dim` field using an integer as shown below: - ``` - 0 - ``` - or - ``` - 1 - ``` - - category: PyTorch wrapper - Reduction operation & Summary statistics - """ - - @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", {}), - "dim": ("STRING", {"multiline": True}), - "keepdim": ("BOOLEAN", {"default": False}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, dim: str, keepdim: bool) -> tuple: - """ - Computes the product along the specified dimension(s). - - Args: - tens (torch.Tensor): The input tensor. - dim (str): A string representation of the dimension(s) along which to compute the product, - e.g., "0" or "1". - keepdim (bool): Whether to retain reduced dimensions. - - Returns: - tuple: A tuple containing the product tensor. - """ - dim = str_to_dim(dim) - if not isinstance(dim, int): - raise TypeError(f"Invalid type for dim: {dim}. Must be a integer.") - # Ensure keepdim is a boolean - if not isinstance(keepdim, bool): - raise TypeError(f"Invalid type for keepdim: {keepdim}. Must be a boolean.") - - # Compute the product along the specified dimension(s) - tens = torch.prod(tens, dim=dim, keepdim=keepdim) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_rand.py b/modules/pytorch_wrapper/pt_rand.py deleted file mode 100644 index 3d967ea..0000000 --- a/modules/pytorch_wrapper/pt_rand.py +++ /dev/null @@ -1,57 +0,0 @@ -from typing import Any, Dict -import torch -import ast -from .utils import DTYPE_MAPPING - -class PtRand: - """ - Creates a PyTorch tensor with values sampled from a uniform distribution - using the size entered in the text field. - For example, if you want to create a 4D tensor of batch size=2, channel=3, height=96, width=32, enter: - [2,3,96,32] - - category: PyTorch wrapper - Tensor creation - """ - - @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": { - "size": ("STRING", {"multiline": True}), - "data_type": (("float32", "float16", "bfloat16", "float64"),) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - @classmethod - def IS_CHANGED(cls, **kw): - return float("NaN") - - def f(self, size: str, data_type: str) -> tuple: - """ - Creates a PyTorch tensor with values sampled from a uniform distribution. - - Args: - size (str): String value in the text field. - data_type (str): Desired floating-point data type of the tensor. - - Returns: - tuple: A tuple containing the PyTorch tensor. - """ - # Convert string to Python list - list_size = ast.literal_eval(size) - - # Convert list to PyTorch Size - sz = torch.Size(list_size) - tens = torch.rand(sz, dtype=DTYPE_MAPPING[data_type]) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_rand_int.py b/modules/pytorch_wrapper/pt_rand_int.py deleted file mode 100644 index 071ea48..0000000 --- a/modules/pytorch_wrapper/pt_rand_int.py +++ /dev/null @@ -1,72 +0,0 @@ -import time -from typing import Any, Dict -import torch -import ast -import copy -from .utils import DTYPE_MAPPING - -class PtRandInt: - """ - Creates a PyTorch tensor filled with random integers within a specified range using the size entered in the text field. - For example, if you want to create a 4D tensor of batch size=2, channel=3, height=96, width=32 with random integers in the range [0, 10), - enter [2,3,96,32] for size, 0 for min_value, and 10 for max_value. - - category: PyTorch wrapper - Tensor creation - """ - - @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": { - "min_value": ("INT", {"default": 0, "min": -2**31, "max": 2**31}), - "max_value": ("INT", {"default": 1, "min": -2**31, "max": 2**31}), - "size": ("STRING", {"multiline": True}), - "data_type": (("uint8", "int8", "int16", "int32", "int64"),) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - @classmethod - def IS_CHANGED(cls, **kw): - return float("NaN") - - def __init__(self): - # Initialize a separate generator with a different seed - self.gen = torch.Generator() - self.gen.manual_seed(int(time.time() * 1000000) % (2**32)) # Unique seed - - def f(self, min_value: int, max_value: int, size: str, data_type: str) -> tuple: - """ - Creates a PyTorch tensor filled with random integers in the range [min_value, max_value). - - Args: - min_value (int): Minimum value (inclusive). - max_value (int): Maximum value (exclusive). - size (str): String representation of the tensor size. - data_type (str): Desired integer data type of the tensor. - - Returns: - tuple: A tuple containing the PyTorch tensor. - """ - # Convert string to Python list - list_size = ast.literal_eval(size) - - # Convert list to PyTorch Size - sz = torch.Size(list_size) - - # Ensure max_value is greater than min_value - if max_value <= min_value: - raise ValueError("max_value must be greater than min_value.") - - # Generate random integers within the specified range - tens = torch.randint(min_value, max_value, sz, dtype=DTYPE_MAPPING[data_type], generator=self.gen) - return (tens,) diff --git a/modules/pytorch_wrapper/pt_randn.py b/modules/pytorch_wrapper/pt_randn.py deleted file mode 100644 index 9084769..0000000 --- a/modules/pytorch_wrapper/pt_randn.py +++ /dev/null @@ -1,57 +0,0 @@ -from typing import Any, Dict -import torch -import ast -from .utils import DTYPE_MAPPING - -class PtRandn: - """ - Creates a PyTorch tensor with values sampled from a standard normal distribution (mean=0, std=1) - using the size entered in the text field. - For example, if you want to create a 4D tensor of batch size=2, channel=3, height=96, width=32, enter: - [2,3,96,32] - - category: PyTorch wrapper - Tensor creation - """ - - @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": { - "size": ("STRING", {"multiline": True}), - "data_type": (("float32", "float16", "bfloat16", "float64"),) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - @classmethod - def IS_CHANGED(cls, **kw): - return float("NaN") - - def f(self, size: str, data_type: str) -> tuple: - """ - Creates a PyTorch tensor with values sampled from a standard normal distribution. - - Args: - size (str): String value in the text field. - data_type (str): Desired floating-point data type of the tensor. - - Returns: - tuple: A tuple containing the PyTorch tensor. - """ - # Convert string to Python list - list_size = ast.literal_eval(size) - - # Convert list to PyTorch Size - sz = torch.Size(list_size) - tens = torch.randn(sz, dtype=DTYPE_MAPPING[data_type]) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_remainder.py b/modules/pytorch_wrapper/pt_remainder.py deleted file mode 100644 index 25c9591..0000000 --- a/modules/pytorch_wrapper/pt_remainder.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtRemainder: - """ - Computes the element-wise remainder of division between two PyTorch tensors. - - category: PyTorch wrapper - Arithmetic operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Computes the element-wise remainder of division between two PyTorch tensors. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of any shape. - tens_b (torch.Tensor): A PyTorch tensor of shape that supports element-wise remainder computation with `tens_a`. - - Returns: - tuple: A tuple containing the resultant tensor. - """ - return (torch.remainder(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_reshape.py b/modules/pytorch_wrapper/pt_reshape.py deleted file mode 100644 index a132f5d..0000000 --- a/modules/pytorch_wrapper/pt_reshape.py +++ /dev/null @@ -1,62 +0,0 @@ -from typing import Any, Dict -import torch -import ast - - -class PtReshape: - """ - Reshapes a PyTorch tensor into a specified shape using `torch.reshape()`. - The total number of elements must remain unchanged. - - For example, if a tensor has shape (2, 3, 4), and `new_shape="[6, 4]"`, - the output will have shape (6, 4). - - Use -1 to automatically infer a dimension, e.g.: - ``` - [2, -1] - ``` - category: PyTorch wrapper - Transform - """ - - @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", {}), - "new_shape": ("STRING", {"multiline": True}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, new_shape: str) -> tuple: - """ - Reshapes the input tensor based on the specified shape. - - Args: - tens (torch.Tensor): The input tensor. - new_shape (str): A string representation of the desired shape, - e.g., "[2, -1]" or "(6, 4)". - - Returns: - tuple: A tuple containing the reshaped PyTorch tensor. - """ - # Convert string to a tuple of integers - shape_order = tuple(ast.literal_eval(new_shape)) - - # Ensure the parsed shape is valid - if not isinstance(shape_order, tuple) or not all(isinstance(i, int) for i in shape_order): - raise ValueError(f"Invalid format for new_shape: {new_shape}. Must be a tuple or list of integers.") - - # Apply reshape operation - tens = tens.reshape(*shape_order) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_save_model.py b/modules/pytorch_wrapper/pt_save_model.py deleted file mode 100644 index 993c221..0000000 --- a/modules/pytorch_wrapper/pt_save_model.py +++ /dev/null @@ -1,36 +0,0 @@ -import os -from typing import Any, Dict -import torch -from .utils import get_model_full_path - -class PtSaveModel: - """ - A wrapper class for saving a PyTorch model. - - category: IO - """ - - @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": { - "model": ("PTMODEL", {}), - "model_path": ("STRING", {"default": "model.pkl"}) - } - } - - RETURN_NAMES: tuple = ("model full path",) - RETURN_TYPES: tuple = ("STRING",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, model:Any, model_path: str): - model_full_path = get_model_full_path(model_path) - torch.save(model.state_dict(), model_full_path) - return (os.path.realpath(model_full_path),) \ No newline at end of file diff --git a/modules/pytorch_wrapper/pt_scatter.py b/modules/pytorch_wrapper/pt_scatter.py deleted file mode 100644 index a629089..0000000 --- a/modules/pytorch_wrapper/pt_scatter.py +++ /dev/null @@ -1,69 +0,0 @@ -from typing import Any, Dict -import torch -import ast - -class PtScatter: - """ - Generates a new tensor by replacing values at specified positions using an index tensor. - - * Target tensor (tens): Some of whose values are replaced. - * Source tensor (src): Values from this tensor replace a part or whole of the target tensor. - * Index tensor (index): Specifies the positions in the target tensor for replacement. - - category: PyTorch wrapper - Indexing and Slicing Operations - """ - - @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", {}), - "dim": ("INT", {"default": 0, "min": -10, "max": 10}), - "index": ("STRING", {"multiline": True}), - "src": ("STRING", {"multiline": True}), - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, - tens: torch.Tensor, - dim: int, - index: str, - src: str) -> tuple: - """ - Replaces elements in the target tensor along the specified dimension using an index tensor. - - Args: - tens (torch.Tensor): The target tensor whose values will be replaced. - dim (int): The dimension along which values are replaced. - index (str): A string representation of the index tensor (list format). - src (str): A string representation of the source tensor (list format). - - Returns: - tuple: A tuple containing the modified tensor. - """ - # Parse the input index and source tensors from string representations - index_list = ast.literal_eval(index) - src_list = ast.literal_eval(src) - - # Convert to tensors - index_tensor = torch.tensor(index_list, dtype=torch.long, device=tens.device) - src_tensor = torch.tensor(src_list, dtype=tens.dtype, device=tens.device) - - # Ensure index and src shapes match - if index_tensor.shape != src_tensor.shape: - raise ValueError(f"Shape mismatch: index tensor {index_tensor.shape} and src tensor {src_tensor.shape} must match.") - - # Perform the scatter operation - updated_tens = torch.scatter(tens.clone(), dim=dim, index=index_tensor, src=src_tensor) - - return (updated_tens,) diff --git a/modules/pytorch_wrapper/pt_show_size.py b/modules/pytorch_wrapper/pt_show_size.py deleted file mode 100644 index d8b154a..0000000 --- a/modules/pytorch_wrapper/pt_show_size.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -Almost all of this code was taken from -https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/py/show_text.py - -See credit/credit.md for the full license. -""" -from typing import List -import torch - - -class PtShowSize: - """ - Displays PyTorch Size object as a string. - - category: Display data - """ - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "size": ("PTSIZE", {}), - }, - "hidden": { - "unique_id": "UNIQUE_ID", - "extra_pnginfo": "EXTRA_PNGINFO", - }, - } - - INPUT_IS_LIST = True - RETURN_TYPES = ("STRING",) - FUNCTION = "notify" - OUTPUT_NODE = True - OUTPUT_IS_LIST = (True,) - - CATEGORY = "Data Analysis" - - MAX_NUM_ELEMENTS_TO_DISPLAY = 100 - - def notify(self, size: torch.Size, unique_id=None, extra_pnginfo=None): - text = str(size[0]) - - if unique_id is not None and extra_pnginfo is not None: - if not isinstance(extra_pnginfo, list): - print("Error: extra_pnginfo is not a list") - elif ( - not isinstance(extra_pnginfo[0], dict) - or "workflow" not in extra_pnginfo[0] - ): - print("Error: extra_pnginfo[0] is not a dict or missing 'workflow' key") - else: - workflow = extra_pnginfo[0]["workflow"] - node = next( - (x for x in workflow["nodes"] if str(x["id"]) == str(unique_id[0])), - None, - ) - if node: - node["widgets_values"] = [text] - else: - print("unique_id or extra_pnginfo is None.") - return {"ui": {"text": [text]}, "result": ([text],)} diff --git a/modules/pytorch_wrapper/pt_show_text.py b/modules/pytorch_wrapper/pt_show_text.py deleted file mode 100644 index 152db36..0000000 --- a/modules/pytorch_wrapper/pt_show_text.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -Almost all of this code was taken from -https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/py/show_text.py - -See credit/credit.md for the full license. -""" -from typing import List -import torch - - -class PtShowText: - """ - Displays PyTorch tensor as a string. Note that the tensor is partially printed out when - the tensor contains more elements than the threshold set internally. - - category: Display data - """ - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "tens": ("TENSOR", {}), - }, - "hidden": { - "unique_id": "UNIQUE_ID", - "extra_pnginfo": "EXTRA_PNGINFO", - }, - } - - INPUT_IS_LIST = True - RETURN_TYPES = ("STRING",) - FUNCTION = "notify" - OUTPUT_NODE = True - OUTPUT_IS_LIST = (True,) - - CATEGORY = "Data Analysis" - - MAX_NUM_ELEMENTS_TO_DISPLAY = 100 - - def notify(self, tens: torch.Tensor, unique_id=None, extra_pnginfo=None): - torch.set_printoptions(threshold=PtShowText.MAX_NUM_ELEMENTS_TO_DISPLAY) - text = str(tens[0]) - - if unique_id is not None and extra_pnginfo is not None: - if not isinstance(extra_pnginfo, list): - print("Error: extra_pnginfo is not a list") - elif ( - not isinstance(extra_pnginfo[0], dict) - or "workflow" not in extra_pnginfo[0] - ): - print("Error: extra_pnginfo[0] is not a dict or missing 'workflow' key") - else: - workflow = extra_pnginfo[0]["workflow"] - node = next( - (x for x in workflow["nodes"] if str(x["id"]) == str(unique_id[0])), - None, - ) - if node: - node["widgets_values"] = [text] - else: - print("unique_id or extra_pnginfo is None.") - return {"ui": {"text": [text]}, "result": ([text],)} diff --git a/modules/pytorch_wrapper/pt_sin.py b/modules/pytorch_wrapper/pt_sin.py deleted file mode 100644 index fb25085..0000000 --- a/modules/pytorch_wrapper/pt_sin.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtSin: - """ - Computes the sine of a PyTorch tensor element-wise. - - category: PyTorch wrapper - Math operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Computes the element-wise sine of a PyTorch tensor. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of numerical type, representing angles in radians. - - Returns: - tuple: A tuple containing the resultant tensor after applying the sine function. - """ - return (torch.sin(tens_a),) diff --git a/modules/pytorch_wrapper/pt_sinh.py b/modules/pytorch_wrapper/pt_sinh.py deleted file mode 100644 index 5707082..0000000 --- a/modules/pytorch_wrapper/pt_sinh.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtSinh: - """ - Computes the hyperbolic sine of a PyTorch tensor element-wise. - - category: PyTorch wrapper - Math operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Computes the element-wise hyperbolic sine of a PyTorch tensor. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of numerical type. - - Returns: - tuple: A tuple containing the resultant tensor after applying the hyperbolic sine function. - """ - return (torch.sinh(tens_a),) diff --git a/modules/pytorch_wrapper/pt_size.py b/modules/pytorch_wrapper/pt_size.py deleted file mode 100644 index dd3be36..0000000 --- a/modules/pytorch_wrapper/pt_size.py +++ /dev/null @@ -1,41 +0,0 @@ -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 - Size object support - """ - - @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 = "Data Analysis" - - 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(),) diff --git a/modules/pytorch_wrapper/pt_size_create.py b/modules/pytorch_wrapper/pt_size_create.py deleted file mode 100644 index c19b877..0000000 --- a/modules/pytorch_wrapper/pt_size_create.py +++ /dev/null @@ -1,46 +0,0 @@ -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 - Size object support - """ - - @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 = "Data Analysis" - - 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,) diff --git a/modules/pytorch_wrapper/pt_size_to_numpy.py b/modules/pytorch_wrapper/pt_size_to_numpy.py deleted file mode 100644 index eb8ce05..0000000 --- a/modules/pytorch_wrapper/pt_size_to_numpy.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch -import numpy as np - -class PtSizeToNumpy: - """ - Converts PyTorch Size object to NumPy ndarray. - - category: PyTorch wrapper - Size object support - """ - - @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 = "Data Analysis" - - 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,) diff --git a/modules/pytorch_wrapper/pt_size_to_string.py b/modules/pytorch_wrapper/pt_size_to_string.py deleted file mode 100644 index 8dc7992..0000000 --- a/modules/pytorch_wrapper/pt_size_to_string.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Any, Dict -import torch - - -class PtSizeToString: - """ - Converts PyTorch Size object to a Python string. - - category: PyTorch wrapper - Size object support - """ - - @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 = "Data Analysis" - - 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),) diff --git a/modules/pytorch_wrapper/pt_sqrt.py b/modules/pytorch_wrapper/pt_sqrt.py deleted file mode 100644 index b6b4514..0000000 --- a/modules/pytorch_wrapper/pt_sqrt.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtSqrt: - """ - Computes the square root of each element in a PyTorch tensor. - - category: PyTorch wrapper - Math operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Computes the square root of a PyTorch tensor element-wise. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of numerical type. - - Returns: - tuple: A tuple containing the resultant tensor after applying the square root function. - """ - return (torch.sqrt(tens_a),) diff --git a/modules/pytorch_wrapper/pt_squeeze.py b/modules/pytorch_wrapper/pt_squeeze.py deleted file mode 100644 index c86f3ce..0000000 --- a/modules/pytorch_wrapper/pt_squeeze.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import Any, Dict -import torch -from .utils import DTYPE_MAPPING - -class PtSqueeze: - """ - Removes a dimension at the specified position in the input tensor if it is of size 1. - - Specifying a tuple of dimensions is not supported yet. - - category: PyTorch wrapper - Transform - """ - - @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", {}), - "dim": ("INT", {"default": -1, "min": -1, "max": 10}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, dim: int) -> tuple: - """ - Removes a dimension at the specified position in the input tensor if it is of size 1. - - Args: - tens (torch.Tensor): Input tensor. - dim (int): Dimension to remove if it is of size 1. - - Returns: - tuple: A tuple containing the transformed PyTorch tensor. - """ - tens = torch.squeeze(tens, dim) - return (tens,) diff --git a/modules/pytorch_wrapper/pt_std.py b/modules/pytorch_wrapper/pt_std.py deleted file mode 100644 index d6c78c9..0000000 --- a/modules/pytorch_wrapper/pt_std.py +++ /dev/null @@ -1,68 +0,0 @@ -from typing import Any, Dict -import torch -from .utils import str_to_dim - - -class PtStd: - """ - Computes the standard deviation of a PyTorch tensor along the specified dimension(s). - - Specify 1 to use Bessel's correction (N-1) for sample standard deviation. - Specify 0 to compute population standard deviation. - - Specify the dimension(s) in the `dim` field using an integer, a list, or a tuple, as shown below: - ``` - 0, [0], or (1, 2) - ``` - category: PyTorch wrapper - Reduction operation & Summary statistics - """ - - @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", {}), - "correction": ("INT", {"default": 1, "min": 0, "max": 1}), - "dim": ("STRING", {"multiline": True}), - "keepdim": ("BOOLEAN", {"default": False}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, dim: str, correction: int, keepdim: bool) -> tuple: - """ - Computes the standard deviation along the specified dimension(s). - - Args: - tens (torch.Tensor): The input tensor. - dim (str): A string representation of the dimension(s) along which to compute the standard deviation, - e.g., "0", "[0]", or "(1, 2)". - correction (int): 0 for population standard deviation, 1 for sample standard deviation (Bessel's correction). - keepdim (bool): Whether to retain reduced dimensions. - - Returns: - tuple: A tuple containing the standard deviation tensor. - """ - dim = str_to_dim(dim) - - # Ensure correction is valid (0 or 1) - if correction not in (0, 1): - raise ValueError(f"Invalid correction value: {correction}. Must be 0 (population) or 1 (sample).") - - # Ensure keepdim is a boolean - if not isinstance(keepdim, bool): - raise TypeError(f"Invalid type for keepdim: {keepdim}. Must be a boolean.") - - # Compute the standard deviation along the specified dimension(s) - tens = torch.std(tens, dim=dim, correction=correction, keepdim=keepdim) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_sub.py b/modules/pytorch_wrapper/pt_sub.py deleted file mode 100644 index 026b0b1..0000000 --- a/modules/pytorch_wrapper/pt_sub.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - -class PtSub: - """ - Subtracts one PyTorch tensor from another. - - category: PyTorch wrapper - Arithmetic operations - """ - - @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_a": ("TENSOR", {}), - "tens_b": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor, tens_b: torch.Tensor) -> tuple: - """ - Subtracts one PyTorch tensor from another. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of any shape. - tens_b (torch.Tensor): A PyTorch tensor of shape that supports subtraction from `tens_a`. - - Returns: - tuple: A tuple containing the resultant tensor. - """ - return (torch.sub(tens_a, tens_b),) diff --git a/modules/pytorch_wrapper/pt_sum.py b/modules/pytorch_wrapper/pt_sum.py deleted file mode 100644 index 9b43c1e..0000000 --- a/modules/pytorch_wrapper/pt_sum.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import Any, Dict -import torch -from .utils import str_to_dim - - -class PtSum: - """ - Computes the sum of a PyTorch tensor along the specified dimension(s). - - Specify the dimension(s) in the `dim` field using an integer, a list, or a tuple, as shown below: - ``` - 0, [0], or (1, 2) - ``` - category: PyTorch wrapper - Reduction operation & Summary statistics - """ - - @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", {}), - "dim": ("STRING", {"multiline": True}), - "keepdim": ("BOOLEAN", {"default": False}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, dim: str, keepdim: bool) -> tuple: - """ - Computes the sum along the specified dimension(s). - - Args: - tens (torch.Tensor): The input tensor. - dim (str): A string representation of the dimension(s) along which to compute the sum, - e.g., "0", "[0]", or "(1, 2)". - keepdim (bool): Whether to retain reduced dimensions. - - Returns: - tuple: A tuple containing the summed tensor. - """ - dim = str_to_dim(dim) - - # Ensure keepdim is a boolean - if not isinstance(keepdim, bool): - raise TypeError(f"Invalid type for keepdim: {keepdim}. Must be a boolean.") - - # Compute the sum along the specified dimension(s) - tens = torch.sum(tens, dim=dim, keepdim=keepdim) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_tan.py b/modules/pytorch_wrapper/pt_tan.py deleted file mode 100644 index 0f38135..0000000 --- a/modules/pytorch_wrapper/pt_tan.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtTan: - """ - Computes the tangent of a PyTorch tensor element-wise. - - category: PyTorch wrapper - Math operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Computes the element-wise tangent of a PyTorch tensor. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of numerical type, representing angles in radians. - - Returns: - tuple: A tuple containing the resultant tensor after applying the tangent function. - """ - return (torch.tan(tens_a),) diff --git a/modules/pytorch_wrapper/pt_tanh.py b/modules/pytorch_wrapper/pt_tanh.py deleted file mode 100644 index 9e08aca..0000000 --- a/modules/pytorch_wrapper/pt_tanh.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Any, Dict -import torch - -class PtTanh: - """ - Computes the hyperbolic tangent of a PyTorch tensor element-wise. - - category: PyTorch wrapper - Math operations - """ - - @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_a": ("TENSOR", {}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens_a: torch.Tensor) -> tuple: - """ - Computes the element-wise hyperbolic tangent of a PyTorch tensor. - - Args: - tens_a (torch.Tensor): A PyTorch tensor of numerical type. - - Returns: - tuple: A tuple containing the resultant tensor after applying the hyperbolic tangent function. - """ - return (torch.tanh(tens_a),) diff --git a/modules/pytorch_wrapper/pt_to_image.py b/modules/pytorch_wrapper/pt_to_image.py deleted file mode 100644 index cd3ddb4..0000000 --- a/modules/pytorch_wrapper/pt_to_image.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - - -class PtToImage: - """ - Casts a PyTorch tensor as an Image tensor. - - category: PyTorch wrapper - Image processing - """ - - @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_NAMES: tuple = ("image",) - RETURN_TYPES: tuple = ("IMAGE",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor) -> tuple: - """ - Casts a PyTorch tensor as an Image tensor. - - Args: - tens (torch.Tensor): PyTorch Tensor - - Returns: - tuple: A tuple containing the image tensor. - """ - return (tens,) diff --git a/modules/pytorch_wrapper/pt_to_image_transpose.py b/modules/pytorch_wrapper/pt_to_image_transpose.py deleted file mode 100644 index 3218dbc..0000000 --- a/modules/pytorch_wrapper/pt_to_image_transpose.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import Any, Dict -import torch - - -class PtToImageTranspose: - """ - Casts a PyTorch tensor as an Image tensor and transposes it from (C, H, W) to (H, W, C). For rank-4 inputs, the batch axis remains unchanged. - - category: PyTorch wrapper - Image processing - """ - - @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_NAMES: tuple = ("image",) - RETURN_TYPES: tuple = ("IMAGE",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor) -> tuple: - """ - Casts a PyTorch tensor as an Image tensor. - - Args: - tens (torch.Tensor): PyTorch Tensor - - Returns: - tuple: A tuple containing the image tensor. - """ - if tens.dim() == 3: - image = tens.permute(1, 2, 0) - elif tens.dim() == 4: - image = tens.permute(0, 2, 3, 1) - else: - raise ValueError("Only rank 3 or 4 tensors are supported.") - return (image,) diff --git a/modules/pytorch_wrapper/pt_to_latent.py b/modules/pytorch_wrapper/pt_to_latent.py deleted file mode 100644 index b9a7fb2..0000000 --- a/modules/pytorch_wrapper/pt_to_latent.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Any, Dict -import torch - - -class PtToLatent: - """ - Casts a PyTorch tensor as a latent tensor. - - category: PyTorch wrapper - Tensor data 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": { - "tens": ("TENSOR", {}), - } - } - - RETURN_TYPES: tuple = ("LATENT",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor) -> tuple: - """ - Casts a PyTorch tensor as a latent tensor. - - Args: - tens (torch.Tensor): PyTorch Tensor - - Returns: - tuple: A tuple containing the latent tensor. - """ - return ({"samples":tens},) diff --git a/modules/pytorch_wrapper/pt_to_numpy.py b/modules/pytorch_wrapper/pt_to_numpy.py deleted file mode 100644 index 4b2b9b7..0000000 --- a/modules/pytorch_wrapper/pt_to_numpy.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Dict -import torch - - -class PtToNumpy: - """ - Converts PyTorch tensor to NumPy ndarray. - - category: PyTorch wrapper - Tensor data 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": { - "tens": ("TENSOR", {}), - } - } - - RETURN_TYPES: tuple = ("NDARRAY",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - 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/modules/pytorch_wrapper/pt_to_rgb_tensors.py b/modules/pytorch_wrapper/pt_to_rgb_tensors.py deleted file mode 100644 index a162129..0000000 --- a/modules/pytorch_wrapper/pt_to_rgb_tensors.py +++ /dev/null @@ -1,50 +0,0 @@ -from typing import Any, Dict -import torch - - -class PtToRgbTensors: - """ - Splits a PyTorch tensor into R, G, and B tensors. - - category: PyTorch wrapper - Tensor data conversion - """ - - @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 = "Data Analysis" - - 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) diff --git a/modules/pytorch_wrapper/pt_train_classification_model.py b/modules/pytorch_wrapper/pt_train_classification_model.py deleted file mode 100644 index 75700c9..0000000 --- a/modules/pytorch_wrapper/pt_train_classification_model.py +++ /dev/null @@ -1,122 +0,0 @@ -from typing import Any, Dict, Optional, Tuple -import torch -import torch.nn as nn -import torch.nn.functional as F -import numpy as np - -class PtTrainClassificationModel: - """ - Trains a classification model using a given dataset, optimizer, and number of epochs. - - category: PyTorch wrapper - Training - """ - - @classmethod - def INPUT_TYPES(cls) -> Dict[str, Any]: - """ - Defines the input types required for training the model. - - Returns: - Dict[str, Any]: A dictionary specifying required and optional input types. - """ - return { - "required": { - "model": ("PTMODEL", {}), - "train_loader": ("PTDATALOADER", {}), - "optimizer": ("PTOPTIMIZER", {}), - "epochs": ("INT", {"default":1, "min":1, "max":1e6}), - "use_gpu": ("BOOLEAN", {"default":False}) - }, - "optional": { - "val_loader": ("PTDATALOADER", {}), - } - } - - RETURN_NAMES: tuple = ("Model", "train loss", "val loss") - RETURN_TYPES: tuple = ("PTMODEL","TENSOR","TENSOR") - FUNCTION: str = "f" - CATEGORY: str = "Training" - - @classmethod - def IS_CHANGED(cls, **kw): - return float("NaN") - - def f(self, model: nn.Module, - train_loader: torch.utils.data.DataLoader, - optimizer: torch.optim.Optimizer, - epochs: int, - use_gpu: bool, - val_loader: Optional[torch.utils.data.DataLoader] = None) -> Tuple[nn.Module, torch.Tensor, torch.Tensor]: - """ - Trains a classification model. - - Args: - model (torch.nn.Module): The PyTorch model to be trained. - train_loader (torch.utils.data.DataLoader): DataLoader containing the training dataset. - optimizer (torch.optim.Optimizer): Optimizer used for training. - epochs (int): Number of training epochs. - use_gpu (bool): Whether to use GPU for training. - val_loader (Optional[torch.utils.data.DataLoader]): DataLoader for validation dataset (optional). - - Returns: - Tuple[torch.nn.Module, torch.Tensor, torch.Tensor]: A tuple containing the trained model, - training loss history, and validation loss history. - """ - - with torch.inference_mode(False): - train_loss = [] - val_loss = [] - if use_gpu: - model.to("cuda") - - loss_function = nn.CrossEntropyLoss() # mean loss - - # Train the model - for epoch in range(epochs): - model.train() - total_loss = 0 - total_samples = len(train_loader.dataset) - for images, labels in train_loader: - if use_gpu: - images = images.to("cuda") - labels = labels.to("cuda") - optimizer.zero_grad() - outputs = model(images) - loss = loss_function(outputs, labels) - loss.backward() - optimizer.step() - - batch_size = labels.size(0) - batch_loss = loss.item() * batch_size - total_loss += batch_loss - - epoch_loss = total_loss / total_samples - - print(f"Epoch (train) {epoch+1}/{epochs}, Loss: {epoch_loss:.4f}") - train_loss.append(epoch_loss) - - if val_loader: - with torch.no_grad(): - model.train(False) # TODO: Change to model.*e*v*a*l() once Comfy's security checker is fixed. - total_loss = 0 - total_samples = len(val_loader.dataset) - for images, labels in val_loader: - if use_gpu: - images = images.to("cuda") - labels = labels.to("cuda") - outputs = model(images) - loss = loss_function(outputs, labels) - batch_size = labels.size(0) - batch_loss = loss.item() * batch_size - total_loss += batch_loss - - epoch_loss = total_loss / total_samples - print(f"Epoch (val) {epoch+1}/{epochs}, Loss: {epoch_loss:.4f}") - val_loss.append(epoch_loss) - - if use_gpu: - model.to("cpu") - - return (model, - torch.tensor(train_loss, dtype=torch.float32), - torch.tensor(val_loss, dtype=torch.float32)) \ No newline at end of file diff --git a/modules/pytorch_wrapper/pt_unsqueeze.py b/modules/pytorch_wrapper/pt_unsqueeze.py deleted file mode 100644 index 7505b95..0000000 --- a/modules/pytorch_wrapper/pt_unsqueeze.py +++ /dev/null @@ -1,44 +0,0 @@ -from typing import Any, Dict -import torch - - -class PtUnsqueeze: - """ - Adds a singleton dimension at the specified position in the input tensor. - For example, if a tensor has shape (3, 4) and `dim=1`, the output will have shape (3, 1, 4). - - category: PyTorch wrapper - Transform - """ - - @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", {}), - "dim": ("INT", {"default": -1, "min": -10, "max": 10}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, dim: int) -> tuple: - """ - Adds a singleton dimension at the specified position. - - Args: - tens (torch.Tensor): Input tensor. - dim (int): Dimension at which to insert the new singleton dimension. - - Returns: - tuple: A tuple containing the transformed PyTorch tensor. - """ - tens = torch.unsqueeze(tens, dim) - return (tens,) diff --git a/modules/pytorch_wrapper/pt_var.py b/modules/pytorch_wrapper/pt_var.py deleted file mode 100644 index 56829c0..0000000 --- a/modules/pytorch_wrapper/pt_var.py +++ /dev/null @@ -1,68 +0,0 @@ -from typing import Any, Dict -import torch -from .utils import str_to_dim - - -class PtVar: - """ - Computes the variance of a PyTorch tensor along the specified dimension(s). - - Specify 1 to use Bessel's correction (N-1) for sample variance. - Specify 0 to compute population variance. - - Specify the dimension(s) in the `dim` field using an integer, a list, or a tuple, as shown below: - ``` - 0, [0], or (1, 2) - ``` - category: PyTorch wrapper - Reduction operation & Summary statistics - """ - - @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", {}), - "correction": ("INT", {"default": 1, "min": 0, "max": 1}), - "dim": ("STRING", {"multiline": True}), - "keepdim": ("BOOLEAN", {"default": False}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, dim: str, correction: int, keepdim: bool) -> tuple: - """ - Computes the variance along the specified dimension(s). - - Args: - tens (torch.Tensor): The input tensor. - dim (str): A string representation of the dimension(s) along which to compute the variance, - e.g., "0", "[0]", or "(1, 2)". - correction (int): 0 for population variance, 1 for sample variance (Bessel's correction). - keepdim (bool): Whether to retain reduced dimensions. - - Returns: - tuple: A tuple containing the variance tensor. - """ - dim = str_to_dim(dim) - - # Ensure correction is valid (0 or 1) - if correction not in (0, 1): - raise ValueError(f"Invalid correction value: {correction}. Must be 0 (population) or 1 (sample).") - - # Ensure keepdim is a boolean - if not isinstance(keepdim, bool): - raise TypeError(f"Invalid type for keepdim: {keepdim}. Must be a boolean.") - - # Compute the variance along the specified dimension(s) - tens = torch.var(tens, dim=dim, correction=correction, keepdim=keepdim) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_view.py b/modules/pytorch_wrapper/pt_view.py deleted file mode 100644 index 3d7de7d..0000000 --- a/modules/pytorch_wrapper/pt_view.py +++ /dev/null @@ -1,62 +0,0 @@ -from typing import Any, Dict -import torch -import ast - - -class PtView: - """ - Reshapes a PyTorch tensor into a specified shape using `torch.view()`. - The total number of elements must remain unchanged. - - For example, if a tensor has shape (2, 3, 4), and `new_shape="[6, 4]"`, - the output will have shape (6, 4). - - Use -1 to automatically infer a dimension, e.g.: - ``` - [2, -1] - ``` - category: PyTorch wrapper - Transform - """ - - @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", {}), - "new_shape": ("STRING", {"multiline": True}) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, tens: torch.Tensor, new_shape: str) -> tuple: - """ - Reshapes the input tensor based on the specified shape. - - Args: - tens (torch.Tensor): The input tensor. - new_shape (str): A string representation of the desired shape, - e.g., "[2, -1]" or "(6, 4)". - - Returns: - tuple: A tuple containing the reshaped PyTorch tensor. - """ - # Convert string to a tuple of integers - shape_order = tuple(ast.literal_eval(new_shape)) - - # Ensure the parsed shape is valid - if not isinstance(shape_order, tuple) or not all(isinstance(i, int) for i in shape_order): - raise ValueError(f"Invalid format for new_shape: {new_shape}. Must be a tuple or list of integers.") - - # Apply view operation - tens = tens.view(*shape_order) - - return (tens,) diff --git a/modules/pytorch_wrapper/pt_where.py b/modules/pytorch_wrapper/pt_where.py deleted file mode 100644 index 7635af4..0000000 --- a/modules/pytorch_wrapper/pt_where.py +++ /dev/null @@ -1,61 +0,0 @@ -from typing import Any, Dict -import torch - -class PtWhere: - """ - Generates a new tensor by selecting values based on a condition tensor. - - * `condition_tens` (torch.Tensor): Boolean tensor that determines which values are selected. - * `true_tens` (torch.Tensor): Values to use where the condition is `True`. - * `false_tens` (torch.Tensor): Values to use where the condition is `False`. - - category: PyTorch wrapper - Indexing and Slicing Operations - """ - - @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": { - "condition_tens": ("TENSOR", {}), - "true_tens": ("TENSOR", {}), - "false_tens": ("TENSOR", {}), - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, - condition_tens: torch.Tensor, - true_tens: torch.Tensor, - false_tens: torch.Tensor) -> tuple: - """ - Selects values from `true_tens` or `false_tens` based on `condition_tens`. - - Args: - condition_tens (torch.Tensor): Boolean tensor indicating selection conditions. - true_tens (torch.Tensor): Values used where condition is True. - false_tens (torch.Tensor): Values used where condition is False. - - Returns: - tuple: A tuple containing the modified tensor. - """ - # Ensure condition_tens is a boolean tensor - if condition_tens.dtype != torch.bool: - raise TypeError(f"Expected `condition_tens` to be a boolean tensor, got {condition_tens.dtype}") - - # Ensure all tensors are compatible in shape - if not torch.broadcast_shapes(condition_tens.shape, true_tens.shape, false_tens.shape): - raise ValueError(f"Shape mismatch: condition {condition_tens.shape}, true {true_tens.shape}, false {false_tens.shape}") - - # Perform the conditional selection - tens_out = torch.where(condition_tens, true_tens, false_tens) - - return (tens_out,) diff --git a/modules/pytorch_wrapper/pt_zeros.py b/modules/pytorch_wrapper/pt_zeros.py deleted file mode 100644 index 0eeacc0..0000000 --- a/modules/pytorch_wrapper/pt_zeros.py +++ /dev/null @@ -1,52 +0,0 @@ -from typing import Any, Dict -import torch -import ast -from .utils import DTYPE_MAPPING - -class PtZeros: - """ - Creates a PyTorch tensor of zeros using the size entered in the text field. - For example, if you want to create a 4D tensor of batch size=2, channel=3, height=96, width=32, enter: - [2,3,96,32] - - category: PyTorch wrapper - Tensor creation - """ - - @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": { - "size": ("STRING", {"multiline": True}), - "data_type": (("float32", "float16", "bfloat16", "float64", "uint8", "int8", "int16", "int32", "int64", "bool"),) - } - } - - RETURN_TYPES: tuple = ("TENSOR",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, size: str, data_type: str) -> tuple: - """ - Creates a PyTorch tensor of zeros using the size entered in the text field. - - Args: - size (str): String value in the text field. - data_type (str): Desired data type of the tensor. - - Returns: - tuple: A tuple containing the PyTorch tensor. - """ - # Convert string to Python list - list_size = ast.literal_eval(size) - - # Convert list to PyTorch Size - sz = torch.Size(list_size) - tens = torch.zeros(sz, dtype=DTYPE_MAPPING[data_type]) - - return (tens,) diff --git a/modules/pytorch_wrapper/ptn_conv_model.py b/modules/pytorch_wrapper/ptn_conv_model.py deleted file mode 100644 index a31fe36..0000000 --- a/modules/pytorch_wrapper/ptn_conv_model.py +++ /dev/null @@ -1,99 +0,0 @@ -from typing import Any, Dict, Tuple -import ast -import torch -from .ptn_conv_model_def import ConvModel - - -class PtnConvModel: - """ - A convolutional model consisting of multiple convolutional layers. - - ### Fields: - - `input_dim`: A string representing the input dimensions in the format "(C,H,W)". - Example: `"(3,28,28)"` for a 3-channel 28x28 image. - - - `penultimate_dim`: An integer specifying the number of features before the output layer. If you specify 0, the number is computed internally. - Default: `0` - - - `output_dim`: An integer specifying the number of output features, which should match the number of target classes in classification. - Default: `10`, Min: `1`, Max: `1e6`. - - - `channel_list`: A string representing a Python list of integers specifying the number of channels per layer excluding the channel for the input (e.g. 3 for the color image). - Example: `"[32,64,128,256,512]"`. - - - `kernel_size_list`: A string representing a Python list of kernel sizes per layer. - Example: `"[3,3,3,3,1]"`. - - - `padding_list`: A string representing a Python list of padding values per layer. - Example: `"[1,1,1,1,0]"`. - - - `downsample_list`: A string representing a Python list of boolean values indicating whether to downsample at each layer. - Example: `"[True,True,True,True,False]"`. - - category: PyTorch wrapper - Training - """ - - @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": { - "input_dim": ("STRING", {"default": "(3,28,28)"}), - "penultimate_dim": ("INT", {"default": 0, "min": 0, "max": 1e6}), - "output_dim": ("INT", {"default": 10, "min": 1, "max": 1e6}), - "channel_list": ("STRING", {"default": "[32,64,128,256,512]"}), - "kernel_size_list": ("STRING", {"default": "[3,3,3,3,1]"}), - "padding_list": ("STRING", {"default": "[1,1,1,1,0]"}), - "downsample_list": ("STRING", {"default": "[True,True,True,True,False]"}) - } - } - - RETURN_TYPES: Tuple[str] = ("PTMODEL",) - FUNCTION: str = "f" - CATEGORY: str = "Training" - - @classmethod - def IS_CHANGED(cls, **kw): - return float("NaN") - - def f(self, - input_dim:str, - penultimate_dim: int, - output_dim: int, - channel_list: str, - kernel_size_list: str, - padding_list: str, - downsample_list: str) -> None: - """ - Constructs a convolutional neural network model. - - Args: - TODO:Update - - Returns: - Tuple[nn.Module]: A tuple containing the instantiated PyTorch model. - - Raises: - TODO:Update - """ - input_dim_parsed = tuple(ast.literal_eval(input_dim)) - channel_list_parsed = ast.literal_eval(channel_list) - kernel_size_list_parsed = ast.literal_eval(kernel_size_list) - padding_list_parsed = ast.literal_eval(padding_list) - downsample_list_parsed = ast.literal_eval(downsample_list) - - with torch.inference_mode(False): - model = ConvModel( - input_dim_parsed, - penultimate_dim, - output_dim, - channel_list_parsed, - kernel_size_list_parsed, - padding_list_parsed, - downsample_list_parsed) - return (model,) diff --git a/modules/pytorch_wrapper/ptn_conv_model_def.py b/modules/pytorch_wrapper/ptn_conv_model_def.py deleted file mode 100644 index 74f71c6..0000000 --- a/modules/pytorch_wrapper/ptn_conv_model_def.py +++ /dev/null @@ -1,151 +0,0 @@ -from typing import List, Dict, Any, Tuple -import torch -import torch.nn as nn - - -class ConvSingleLayer(nn.Module): - """A single convolutional layer followed by an optional ReLU activation and optional downsampling. - - Shape is tensor of shape `(batch_size, in_channels, height, width)`. - - Args: - in_channels (int): Number of input channels. - out_channels (int): Number of output channels. - kernel_size (int): Size of the convolutional kernel. - padding (int): Padding size for convolution. - downsample (bool): If True, applies a 2x2 MaxPooling layer. - apply_activation (bool, optional): If True, applies ReLU activation. Defaults to True. - - pragma: skip_doc - """ - - @classmethod # TODO: Dummy method to suppress ComfyUI warning. DO NOT REMOVE - def INPUT_TYPES(cls) -> Dict[str, Any]: - return {} - - def __init__(self, - in_channels: int, - out_channels: int, - kernel_size: int, - padding: int, - downsample: bool, - apply_activation: bool = True) -> None: - super().__init__() - self.conv = nn.Conv2d(in_channels=in_channels, - out_channels=out_channels, - kernel_size=kernel_size, - padding=padding) - self.apply_activation = apply_activation - self.activation = nn.ReLU() - self.downsample = downsample - self.maxpool = nn.MaxPool2d(kernel_size=2, stride=2) - - def forward(self, inputs: torch.Tensor) -> torch.Tensor: - """Forward pass of the convolutional layer. - - Args: - inputs (torch.Tensor): Input tensor of shape `(batch_size, in_channels, height, width)`. - - Returns: - torch.Tensor: Output tensor after convolution, optional activation, and optional pooling. - """ - x = self.conv(inputs) - if self.downsample: - x = self.maxpool(x) - if self.apply_activation: - x = self.activation(x) - return x - - -class ConvModel(nn.Module): - """A multi-layer convolutional neural network with ReLU activation after each convolutional layer, except for the final dense layer. - - Args: - input_dim (Tuple[int, int, int]): Shape of a single input sample (C, H, W). - penultimate_dim (int): Number of features before the final dense layer. If 0 is specified, this value is automatically computed internally. - output_dim (int): Number of output features. - channel_list (List[int]): List of channel sizes for each layer. This should not include the inputs channel. - Input channel is taken from the first element of input_dim. - kernel_size_list (List[int]): List of kernel sizes for each convolutional layer. - padding_list (List[int]): List of padding values for each convolutional layer. - downsample_list (List[bool]): List indicating whether downsampling should be applied after each layer. - - pragma: skip_doc - """ - - @classmethod # TODO: Dummy method to suppress ComfyUI warning - def INPUT_TYPES(cls) -> Dict[str, Any]: - return {} - - def __init__(self, - input_dim: Tuple[int, int, int], - penultimate_dim: int, - output_dim: int, - channel_list: List[int], - kernel_size_list: List[int], - padding_list: List[int], - downsample_list: List[bool]) -> None: - super().__init__() - - # Validate list lengths - if (len(channel_list) != len(kernel_size_list) or - len(kernel_size_list) != len(padding_list) or - len(padding_list) != len(downsample_list)): - raise ValueError("channel_list, kernel_size_list, padding_list, and downsample_list must have the same length.") - - self.channel_list = [input_dim[0]] + channel_list - - # Initialize convolutional layers - layers = [] - for i in range(len(self.channel_list) - 1): - layers.append( - ConvSingleLayer( - in_channels=self.channel_list[i], - out_channels=self.channel_list[i + 1], - kernel_size=kernel_size_list[i], - padding=padding_list[i], - downsample=downsample_list[i] - ) - ) - - self.conv_layers = nn.ModuleList(layers) - - if penultimate_dim == 0: - # Try to auto compute penultimate_dim by processing the dummy input - with torch.no_grad(): - dummy_input = torch.randn(1, *input_dim) # B, C, H, W - x = dummy_input - for layer in self.conv_layers: - x = layer(x) - checked_penultimate_dim = x.numel() # Number of elements - else: - checked_penultimate_dim = penultimate_dim - - self.final_dense = nn.Linear(checked_penultimate_dim, output_dim, bias=True) - - def forward(self, inputs: torch.Tensor) -> torch.Tensor: - """Forward pass of the model. - - Args: - inputs (torch.Tensor): Input tensor of shape `(batch_size, C, H, W)`. - - Returns: - torch.Tensor: Output tensor of shape `(batch_size, output_dim)`. - - Raises: - ValueError: If inputs is not a rank 4 tensor. - """ - if inputs.dim() not in [3, 4]: - raise ValueError("Inputs is not a rank 3 or 4 tensor.") - - if inputs.dim() == 3: - x = torch.unsqueeze(inputs, 1) - else: - x = inputs - - for layer in self.conv_layers: - x = layer(x) - - x = torch.flatten(x, start_dim=1) # Flatten except batch-axis - x = self.final_dense(x) - return x diff --git a/modules/pytorch_wrapper/ptn_linear_model.py b/modules/pytorch_wrapper/ptn_linear_model.py deleted file mode 100644 index bd2108e..0000000 --- a/modules/pytorch_wrapper/ptn_linear_model.py +++ /dev/null @@ -1,87 +0,0 @@ -from typing import Any, Dict, Tuple -import ast -import torch -import torch.nn as nn -from .ptn_linear_model_def import DenseModel - - -class PtnLinearModel: - """ - A linear model consisting of dense layers. - - ### Fields: - - `dim_list`: A string representing a Python list of layer dimensions. - It should contain the input dimension for the first layer and the output dimensions - for all layers. - - Example for a two-layer network: - - Input data: 784 - - Layer 1: 784 → 128 - - Layer 2: 128 → 10 - - Specify `"[784,128,10]"` - - - `bias_list`: A string representing a Python list of bias usage (True/False) per layer. - Example: "[True,False]" - - - `num_layers`: The number of layers in the model. - - category: PyTorch wrapper - Training - """ - - @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": { - "dim_list": ("STRING", {"default": "784,10"}), - "bias_list": ("STRING", {"default": "{True}"}), - "num_layers": ("INT", {"default": 1, "min": 1, "max": 2000}), - } - } - - RETURN_TYPES: Tuple[str] = ("PTMODEL",) - FUNCTION: str = "f" - CATEGORY: str = "Training" - - @classmethod - def IS_CHANGED(cls, **kw): - return float("NaN") - - def f(self, dim_list: str, bias_list: str, num_layers: int) -> Tuple[nn.Module]: - """ - Constructs a dense neural network model. - - Args: - dim_list (str): A comma-separated string representing layer dimensions. - bias_list (str): A comma-separated string representing bias usage (True/False) per layer. - num_layers (int): The number of layers in the model. - - Returns: - Tuple[nn.Module]: A tuple containing the instantiated PyTorch model. - - Raises: - ValueError: If `dim_list` length does not match `num_layers + 1`. - ValueError: If `bias_list` length does not match `num_layers`. - """ - dims = ast.literal_eval(dim_list) - biases = ast.literal_eval(bias_list) - - if len(dims) - 1 != num_layers: - raise ValueError( - "dim_list and num_layers do not match. dim_list should contain " - "the input dimension for the first layer and output dimensions for all layers. " - "This list should be one element longer than the number of layers." - ) - - if len(biases) != num_layers: - raise ValueError("bias_list and num_layers do not match.") - - with torch.inference_mode(False): - model = DenseModel(dims, biases, num_layers) - - return (model,) diff --git a/modules/pytorch_wrapper/ptn_linear_model_def.py b/modules/pytorch_wrapper/ptn_linear_model_def.py deleted file mode 100644 index 28a10e6..0000000 --- a/modules/pytorch_wrapper/ptn_linear_model_def.py +++ /dev/null @@ -1,99 +0,0 @@ -from typing import List, Dict, Any -import torch -import torch.nn as nn - - -class DenseSingleLayer(nn.Module): - """A single dense layer followed by a ReLU activation. - - Args: - in_features (int): Number of input features. - out_features (int): Number of output features. - bias (bool): If True, adds a learnable bias to the layer. - - pragma: skip_doc - """ - - @classmethod # TODO: Dummy method to suppress ComfyUI warning - def INPUT_TYPES(cls) -> Dict[str, Any]: - return { - } - - def __init__(self, in_features: int, out_features: int, bias: bool) -> None: - super().__init__() - self.fc = nn.Linear(in_features, out_features, bias) - self.activation = nn.ReLU() - - def forward(self, inputs: torch.Tensor) -> torch.Tensor: - """Forward pass of the dense layer with ReLU activation. - - Args: - inputs (torch.Tensor): Input tensor of shape `(batch_size, in_features)`. - - Returns: - torch.Tensor: Output tensor of shape `(batch_size, out_features)`. - """ - x = self.fc(inputs) - x = self.activation(x) - return x - - -class DenseModel(nn.Module): - """A multi-layer dense neural network with ReLU activation after each dense layer, except for the final layer. - - Args: - dim_list (List[int]): List of feature dimensions. Should be of length `num_layers + 1`. - bias_list (List[bool]): List indicating whether each layer should have a bias. - num_layers (int): Number of layers in the model. - - Example: - For a three-layer network: - - Layer 1: input -> output1 - - Layer 2: output1 -> output2 - - Layer 3: output2 -> output3 - - `dim_list = [input_dim, output1_dim, output2_dim, output3_dim]` - - pragma: skip_doc - """ - - @classmethod # TODO: Dummy method to suppress ComfyUI warning - def INPUT_TYPES(cls) -> Dict[str, Any]: - return { - } - - def __init__(self, dim_list: List[int], bias_list: List[bool], num_layers: int) -> None: - super().__init__() - - self.no_activation_layers = nn.ModuleList([ - DenseSingleLayer(dim_list[i], dim_list[i + 1], bias_list[i]) - for i in range(num_layers - 1) - ]) - - self.dim_list = dim_list - self.bias_list = bias_list - self.num_layers = num_layers - - self.final_dense = nn.Linear( - dim_list[num_layers - 1], # in_features - dim_list[num_layers], # out_features - bias_list[num_layers - 1] - ) - - def forward(self, inputs: torch.Tensor) -> torch.Tensor: - """Forward pass of the dense model. - - Args: - inputs (torch.Tensor): Input tensor of shape `(batch_size, dim_list[0])`. - - Returns: - torch.Tensor: Output tensor of shape `(batch_size, dim_list[-1])`. - """ - x = inputs.view(-1, self.dim_list[0]) # Ensure correct input shape - - for i in range(self.num_layers - 1): - x = self.no_activation_layers[i](x) - - x = self.final_dense(x) - - return x diff --git a/modules/pytorch_wrapper/pto_adam.py b/modules/pytorch_wrapper/pto_adam.py deleted file mode 100644 index ddc1c1a..0000000 --- a/modules/pytorch_wrapper/pto_adam.py +++ /dev/null @@ -1,46 +0,0 @@ -from typing import Any, Dict -import torch -import torch.nn as nn -import torch.nn.functional as F - -class PtoAdam: - """ - Instantiates the Adam optimizer. - - category: PyTorch wrapper - Training - """ - - @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": { - "model": ("PTMODEL", {}), - "learning_rate": ("FLOAT", {"default":0.001, "min":1e-10, "max":1, "step":0.0000000001}), - "beta1": ("FLOAT", {"default":0.9, "min":1e-10, "max":1, "step":0.0000000001}), - "beta2": ("FLOAT", {"default":0.999, "min":1e-10, "max":1, "step":0.0000000001}), - } - } - - RETURN_TYPES: tuple = ("PTOPTIMIZER",) - FUNCTION: str = "f" - CATEGORY: str = "Data Analysis" - - def f(self, model, learning_rate, beta1, beta2) -> tuple: - """ - Converts PyTorch tensor to NumPy ndarray. - - Args: - tens (torch.Tensor): PyTorch Tensor - - Returns: - tuple: A tuple containing the ndarray. - """ - with torch.inference_mode(False): - opt = torch.optim.Adam(model.parameters(), lr=learning_rate, betas=(beta1, beta2)) - return(opt,) \ No newline at end of file diff --git a/modules/pytorch_wrapper/ptv_dataset.py b/modules/pytorch_wrapper/ptv_dataset.py deleted file mode 100644 index 29984fd..0000000 --- a/modules/pytorch_wrapper/ptv_dataset.py +++ /dev/null @@ -1,124 +0,0 @@ -import inspect -from typing import Any, Dict, Optional, Callable, Tuple -import torch -import torchvision.datasets as datasets -import ast - -from .utils import get_dataset_full_path - -class PtvDataset: - """ - A Torchvision Dataset class wrapper. - - Parameters - - **name** - Specify a dataset class name such as MNIST, FashionMNIST in string. - This will be converted to a dataset class internally. - - **download** - Set to True if you want to download. - - **root** - Specify the root directory of the downloaded dataset relative to the - dataset directory of this extension, or specify the absolute path. - If you leave as a blank, the dataset will be downloaded under the `datasets` directory - of this extension. - - **transform** - Data transforms e.g. transform to PyTorch tensor, normalize image. - Plug in the PTV Transforms node that contains Torchvision Transforms functionality. - - **parameters** - Specify other parameters in Python dict format. - For example, if you want to set Train parameter to True to download the train set, specify: - ``` - {"train": True} - ``` - or if you want to download the test set, specify: - ``` - {"train": False} - ``` - - category: PyTorch wrapper - Training - """ - - @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": { - "name": ("STRING", {"default": "", "multiline": False}), - "download": ("BOOLEAN", {"default": True}), - "root": ("STRING", {"default": "", "multiline": False}), - }, - "optional": { - "transform": ("PTVTRANSFORM", {}), - "parameters": ("STRING", {"default": "", "multiline": True}) - } - } - - RETURN_TYPES: Tuple[str] = ("PTVDATASET",) - FUNCTION: str = "f" - CATEGORY: str = "Training" - - @classmethod - def IS_CHANGED(cls, **kw): - return float("NaN") - - def f(self, - name: str, - download: bool, - root: str, - parameters: str, - transform: Optional[Callable] = None) -> Tuple: - """ - Loads a dataset from Torchvision with specified parameters. - - Args: - name (str): Name of the dataset class (e.g., MNIST, FashionMNIST). - download (bool): Whether to download the dataset. - root (str): Root directory for storing the dataset. - parameters (str): Additional dataset parameters in stringified dictionary format. - transform (Optional[Callable]): Transformations to apply to the dataset. - - Returns: - Tuple: A tuple containing the dataset instance. - """ - - with torch.inference_mode(False): - param_dict = ast.literal_eval(parameters) - - # Converts the datasets class name to dataset class itself - try: - dataset_class = getattr(datasets, name) - except AttributeError: - raise ValueError(f"Dataset '{name}' is not available in torchvision.datasets") - - # Get valid parameters for the dataset class - valid_params = inspect.signature(dataset_class).parameters - - # Filter only parameters that match the dataset's constructor - filtered_params = {k: v for k, v in param_dict.items() if k in valid_params} - - if root: - dataset_path = get_dataset_full_path(root) - else: # Use default directory - dataset_path = get_dataset_full_path("") - - if transform: - dc = dataset_class(download=download, - root=dataset_path, - transform=transform, - **filtered_params) - else: - dc = dataset_class(download=download, - root=dataset_path, - **filtered_params) - - return (dc,) diff --git a/modules/pytorch_wrapper/ptv_dataset_loader.py b/modules/pytorch_wrapper/ptv_dataset_loader.py deleted file mode 100644 index 40ad553..0000000 --- a/modules/pytorch_wrapper/ptv_dataset_loader.py +++ /dev/null @@ -1,148 +0,0 @@ -import ast -import inspect -from typing import Any, Dict, Tuple -import torch -import torchvision.datasets as datasets -from torch.utils.data import DataLoader -import torchvision.transforms as transforms -from .utils import get_dataset_full_path - - -class PtvDatasetLoader: - """ - A node to combine the dataset and data loader into a single node. - - Transform is internally called to convert input data to PyTorch tensors. - - Parameters - - **name** - Specify the dataset class name such as MNIST, FashionMNIST in string. - This will be converted to a dataset class internally. - - **download** - Set to True if you want to download. - - **root** - Specify the root directory of the downloaded dataset relative to the - dataset directory of this extension, or specify the absolute path. - If you leave as a blank, the dataset will be downloaded under the `datasets` directory - of this extension. - - **batch_size** - The number of samples per batch. - - **shuffle** - If True, shuffles the dataset before each epoch. - - **dataset parameters** - Specify other parameters in Python dict format. - For example, if you want to set Train parameter to True to download the train set, specify: - ``` - {"train": True} - ``` - or if you want to download the test set, specify: - ``` - {"train": False} - ``` - - **load parameters** - Additional parameters in Python dictionary format. - - category: PyTorch wrapper - Training - """ - - @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": { - "name": ("STRING", {"default": "", "multiline": False}), - "download": ("BOOLEAN", {"default": True}), - "root": ("STRING", {"default": "", "multiline": False}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 1e6}), - "shuffle": ("BOOLEAN", {"default": False}), - }, - "optional": { - "dataset_parameters": ("STRING", {"default": '{"train":True}', "multiline": True}), - "load_parameters": ("STRING", {"default": '{"num_workers:1}', "multiline": True}) - } - } - - RETURN_TYPES: Tuple[str] = ("PTDATALOADER",) - FUNCTION: str = "f" - CATEGORY: str = "Training" - - @classmethod - def IS_CHANGED(cls, **kw): - return float("NaN") - - def f(self, - name: str, - download: bool, - root: str, - batch_size: int, - shuffle: bool, - dataset_parameters: str, - load_parameters: str) -> Tuple: - """ - Loads a dataset from Torchvision with specified parameters. - - Args: - name (str): Name of the dataset class (e.g., MNIST, FashionMNIST). - download (bool): Whether to download the dataset. - root (str): Root directory for storing the dataset. - batch_size (int): The number of samples per batch. - shuffle (bool): If True, shuffles the dataset before each epoch. - dataset_parameters (str): Additional dataset parameters in stringified dictionary format. - load_parameters (str): Additional parameters for data load in Python dictionary format. - - Returns: - Tuple: A tuple containing the dataset instance. - """ - - with torch.inference_mode(False): - dataset_param_dict = ast.literal_eval(dataset_parameters) - - # Converts the datasets class name to dataset class itself - try: - dataset_class = getattr(datasets, name) - except AttributeError: - raise ValueError(f"Dataset '{name}' is not available in torchvision.datasets") - - # Get valid parameters for the dataset class - valid_params = inspect.signature(dataset_class).parameters - - # Filter only parameters that match the dataset's constructor - filtered_params = {k: v for k, v in dataset_param_dict.items() if k in valid_params} - - if root: - dataset_path = get_dataset_full_path(root) - else: # Use default directory - dataset_path = get_dataset_full_path("") - - transform = transforms.Compose([transforms.ToTensor()]) - dc = dataset_class(download=download, - root=dataset_path, - transform=transform, - **filtered_params) - - - load_param_dict = ast.literal_eval(load_parameters) - - # Get valid parameters for the DataLoader class - valid_params = inspect.signature(DataLoader).parameters - - # Filter only parameters that match the dataset's constructor - filtered_params = {k: v for k, v in load_param_dict.items() if k in valid_params} - - dl = DataLoader(dataset=dc, - batch_size=batch_size, - shuffle=shuffle, - **filtered_params) - return (dl,) \ No newline at end of file diff --git a/modules/pytorch_wrapper/ptv_image_folder_dataset.py b/modules/pytorch_wrapper/ptv_image_folder_dataset.py deleted file mode 100644 index 44ac685..0000000 --- a/modules/pytorch_wrapper/ptv_image_folder_dataset.py +++ /dev/null @@ -1,73 +0,0 @@ -from typing import Any, Dict, Optional, Callable, Tuple -import torch -from torchvision.datasets import ImageFolder - -from .utils import get_dataset_full_path - -class PtvImageFolderDataset: - """ - A Torchvision ImageFolder Dataset class wrapper. - - Parameters - - **root** - Specify the root directory that contains subdirectories which of which contains images for each class. - The subdirectory name needs to be named after each class. - - **transform** - Data transforms e.g. transform to PyTorch tensor, normalize image. - Plug in the PTV Transforms node that contains Torchvision Transforms functionality. - - category: PyTorch wrapper - Training - """ - - @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": { - "root": ("STRING", {"default": "", "multiline": False}) - }, - "optional": { - "transform": ("PTVTRANSFORM", {}) - } - } - - RETURN_TYPES: Tuple[str] = ("PTVDATASET",) - FUNCTION: str = "f" - CATEGORY: str = "Training" - - @classmethod - def IS_CHANGED(cls, **kw): - return float("NaN") - - def f(self, - root: str, - transform: Optional[Callable] = None) -> Tuple: - """ - Loads a dataset from Torchvision with specified parameters. - - Args: - root (str): Root directory for storing the dataset. - transform (Optional[Callable]): Transformations to apply to the dataset. - - Returns: - Tuple: A tuple containing the dataset instance. - """ - - with torch.inference_mode(False): - - if root: - dataset_path = get_dataset_full_path(root) - else: # Use default directory - dataset_path = get_dataset_full_path("") - - dc = ImageFolder(root=dataset_path, - transform=transform - ) - return (dc,) diff --git a/modules/pytorch_wrapper/ptv_transforms_resize.py b/modules/pytorch_wrapper/ptv_transforms_resize.py deleted file mode 100644 index 39b5eb7..0000000 --- a/modules/pytorch_wrapper/ptv_transforms_resize.py +++ /dev/null @@ -1,48 +0,0 @@ -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,) - - diff --git a/modules/pytorch_wrapper/ptv_transforms_to_tensor.py b/modules/pytorch_wrapper/ptv_transforms_to_tensor.py deleted file mode 100644 index 89fdded..0000000 --- a/modules/pytorch_wrapper/ptv_transforms_to_tensor.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Any, Dict, Optional, Callable -import torchvision.transforms as transforms - - -class PtvTransformsToTensor: - """ - 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 { - } - - RETURN_TYPES: tuple = ("PTVTRANSFORM",) - FUNCTION: str = "f" - CATEGORY: str = "Training" - - def f(self) -> tuple: - """ - Transforms elements of dataset to PyTorch tensors. - - Returns: - tuple: A tuple containing the transform callable. - """ - c = transforms.Compose([transforms.ToTensor()]) - return(c,) - - diff --git a/modules/pytorch_wrapper/utils.py b/modules/pytorch_wrapper/utils.py deleted file mode 100644 index f9e9cb6..0000000 --- a/modules/pytorch_wrapper/utils.py +++ /dev/null @@ -1,106 +0,0 @@ -import os -import ast -from typing import Optional, Tuple, Union -import torch - -DTYPE_MAPPING = { - "float32": torch.float32, - "float16": torch.float16, - "bfloat16": torch.bfloat16, - "float64": torch.float64, - "uint8": torch.uint8, - "int8": torch.int8, - "int16": torch.int16, - "int32": torch.int32, - "int64": torch.int64, - "bool": torch.bool -} - - -def get_resource_full_path(resource_name: str, resource_path: str) -> str: - """ - Resolves the absolute path to a resource file within the "resources" directory and - ensures that the directory exists. - - Args: - resource_path (str): The path to the resource file. If the path is a relative path, - the path is considered relative to the `resource` directory of this extension. - - Returns: - str: The absolute path to the resource file. - """ - if os.path.isabs(resource_path): - resource_full_path = resource_path - else: - resource_full_path = os.path.realpath( - os.path.join(os.path.dirname(__file__), "..", "..", resource_name, resource_path)) - resource_dir = os.path.dirname(resource_full_path) - if os.path.exists(resource_dir) is False: - os.makedirs(resource_dir) - return resource_full_path - - -def get_model_full_path(model_path: str) -> str: - """ - Resolves the absolute path to a model file within the "models" directory and - ensures that the directory exists. - - Args: - model_path (str): The path to the model file. If the path is a relative path, - the path is considered relative to the `models` directory of this extension. - - Returns: - str: The absolute path to the model file. - """ - return get_resource_full_path("models", model_path) - - -def get_dataset_full_path(dataset_path: str) -> str: - """ - Resolves the absolute path to the datasete within the "datasets" directory and - ensures that the directory exists. - - Args: - dataset_path (str): The path to the dataset file. If the path is a relative path, - the path is considered relative to the `datasets` directory of this extension. - - Returns: - str: The absolute path to the dataset file. - """ - return get_resource_full_path("datasets", dataset_path) - - -def str_to_dim(dim: str) -> Optional[Union[int, Tuple[int, ...]]]: - """ - Converts a string representation of a dimension into an integer or a tuple of integers. - - Args: - dim (str): A string representing an integer or a tuple of integers. - - Returns: - Optional[Union[int, Tuple[int, ...]]]: - - An integer if the input represents a single integer. - - A tuple of integers if the input represents a list or tuple. - - None if the input is empty. - - Raises: - ValueError: If the parsed value is not an integer or a tuple of integers. - """ - if dim: - # Convert string to an integer, list, or tuple of integers - parsed_dim = ast.literal_eval(dim) - - # Convert list to tuple - if isinstance(parsed_dim, list): - parsed_dim = tuple(parsed_dim) - - # Ensure the parsed dim is valid - if not isinstance(parsed_dim, (int, tuple)) or ( - isinstance(parsed_dim, tuple) and not all(isinstance(i, int) for i in parsed_dim) - ): - raise ValueError(f"Invalid format for dim: {parsed_dim}. Must be an integer or a tuple of integers.") - - return parsed_dim - - return None - diff --git a/pyproject.toml b/pyproject.toml index 438106d..e855d93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.11" +version = "3.0.3" license = {file = "LICENSE"} [project.urls] diff --git a/tests/pytorch_wrapper/__init__.py b/tests/pytorch_wrapper/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/pytorch_wrapper/pt_abs_test.py b/tests/pytorch_wrapper/pt_abs_test.py deleted file mode 100644 index 046e88a..0000000 --- a/tests/pytorch_wrapper/pt_abs_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_abs import PtAbs # Updated import to PtAbs - - -class TestPtAbs(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtAbs() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([-1, 2, -3, 4], dtype=torch.float32), torch.tensor([1, 2, 3, 4], dtype=torch.float32)), # Mixed positive/negative - (torch.tensor([0, -5, 10, -15], dtype=torch.int32), torch.tensor([0, 5, 10, 15], dtype=torch.int32)), # Integer case - (torch.tensor([[-1.5, 2.3], [-3.7, 4.9]], dtype=torch.float32), torch.tensor([[1.5, 2.3], [3.7, 4.9]], dtype=torch.float32)), # 2D case - (torch.tensor([[-100, 0, 50], [-25, -75, 100]], dtype=torch.int64), torch.tensor([[100, 0, 50], [25, 75, 100]], dtype=torch.int64)), # Large numbers - (torch.full((2, 2), -7.7, dtype=torch.float32), torch.full((2, 2), 7.7, dtype=torch.float32)), # Constant matrix - ] - - def test_absolute_value(self): - """Test tensor element-wise absolute value using torch.abs().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected absolute value operation - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_acos_test.py b/tests/pytorch_wrapper/pt_acos_test.py deleted file mode 100644 index 528acf8..0000000 --- a/tests/pytorch_wrapper/pt_acos_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_acos import PtAcos # Updated import to PtAcos - - -class TestPtAcos(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtAcos() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([1.0, 0.0, -1.0], dtype=torch.float32), torch.acos(torch.tensor([1.0, 0.0, -1.0], dtype=torch.float32))), # Edge cases - (torch.tensor([0.5, -0.5], dtype=torch.float32), torch.acos(torch.tensor([0.5, -0.5], dtype=torch.float32))), # Common values - (torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32), torch.acos(torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32))), # 2D case - (torch.full((2, 2), 0.6, dtype=torch.float32), torch.acos(torch.full((2, 2), 0.6, dtype=torch.float32))), # Constant matrix - ] - - def test_acos(self): - """Test tensor element-wise arc cosine using torch.acos().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected acos values - self.assertTrue(torch.allclose(result, expected, atol=1e-6), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_add_test.py b/tests/pytorch_wrapper/pt_add_test.py deleted file mode 100644 index a46d181..0000000 --- a/tests/pytorch_wrapper/pt_add_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_add import PtAdd - - -class TestPtAdd(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtAdd() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([1.0, 2.0, 3.0]), torch.tensor([4.0, 5.0, 6.0]), torch.tensor([5.0, 7.0, 9.0])), - (torch.ones(3, 3), torch.ones(3, 3), torch.full((3, 3), 2.0)), - (torch.zeros(2, 2), torch.ones(2, 2), torch.ones(2, 2)), - (torch.tensor([[1.0], [2.0]]), torch.tensor([[3.0], [4.0]]), torch.tensor([[4.0], [6.0]])), - ] - - def test_addition(self): - """Test tensor addition.""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected sum - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_arange_test.py b/tests/pytorch_wrapper/pt_arange_test.py deleted file mode 100644 index d80f56c..0000000 --- a/tests/pytorch_wrapper/pt_arange_test.py +++ /dev/null @@ -1,44 +0,0 @@ -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_arange import PtArange - - -class TestPtArange(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtArange() - - # Test cases: (start, end, step, dtype, expected_tensor, expected_dtype) - self.test_cases = [ - ("0", "5", "1", "int32", torch.tensor([0, 1, 2, 3, 4], dtype=torch.int32), torch.int32), # Simple range - ("-3", "3", "1", "float32", torch.tensor([-3, -2, -1, 0, 1, 2], dtype=torch.float32), torch.float32), # Negative start - ("1.5", "4.5", "1", "float64", torch.tensor([1.5, 2.5, 3.5], dtype=torch.float64), torch.float64), # Floating point step - ("10", "0", "-2", "int64", torch.tensor([10, 8, 6, 4, 2], dtype=torch.int64), torch.int64), # Decreasing range - ("", "5", "1", "int8", torch.tensor([0, 1, 2, 3, 4], dtype=torch.int8), torch.int8), # Default start (0) - ("0", "10", "", "uint8", torch.tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=torch.uint8), torch.uint8), # Default step (1) - ] - - def test_arange_tensor_creation(self): - """Test tensor generation using torch.arange(), including values and dtype.""" - for start, end, step, dtype, expected, expected_dtype in self.test_cases: - with self.subTest(start=start, end=end, step=step, dtype=dtype): - result, = self.node.f(start, end, step, dtype) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected tensor values - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - - # Ensure result has the correct dtype - self.assertEqual(result.dtype, expected_dtype, f"Expected dtype {expected_dtype}, got {result.dtype}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_argmax_test.py b/tests/pytorch_wrapper/pt_argmax_test.py deleted file mode 100644 index 2be354b..0000000 --- a/tests/pytorch_wrapper/pt_argmax_test.py +++ /dev/null @@ -1,39 +0,0 @@ -import os -import sys -import unittest -import torch -import ast - -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_argmax import PtArgmax - - -class TestPtArgmax(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtArgmax() - - # Test cases: (Tensor, Dim (as string), keepdim, Expected result) - self.test_cases = [ - (torch.tensor([[1, 3, 2], [4, 0, 5]]), "0", False, torch.tensor([1, 0, 1])), - (torch.tensor([[1, 3, 2], [4, 0, 5]]), "1", True, torch.tensor([[1], [2]])), - (torch.tensor([[10, 3, 2], [123, 0, 5]]), "", False, torch.tensor(3)), - ] - - def test_argmax(self): - """Test torch.argmax behavior.""" - for tens, dim, keepdim, expected in self.test_cases: - with self.subTest(tens=tens, dim=dim, keepdim=keepdim): - result, = self.node.f(tens, dim, keepdim) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected indices - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_argmin_test.py b/tests/pytorch_wrapper/pt_argmin_test.py deleted file mode 100644 index 89753c9..0000000 --- a/tests/pytorch_wrapper/pt_argmin_test.py +++ /dev/null @@ -1,39 +0,0 @@ -import os -import sys -import unittest -import torch -import ast - -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_argmin import PtArgmin - - -class TestPtArgmin(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtArgmin() - - # Test cases: (Tensor, Dim (as string), keepdim, Expected result) - self.test_cases = [ - (torch.tensor([[1, 3, 2], [4, 0, 5]]), "0", False, torch.tensor([0, 1, 0])), - (torch.tensor([[1, 3, 2], [4, 0, 5]]), "1", True, torch.tensor([[0], [1]])), - (torch.tensor([[1, 3, 2], [4, 0, 5]]), "", False, torch.tensor(4)), - ] - - def test_argmin(self): - """Test torch.argmin behavior.""" - for tens, dim, keepdim, expected in self.test_cases: - with self.subTest(tens=tens, dim=dim, keepdim=keepdim): - result, = self.node.f(tens, dim, keepdim) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected indices - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_asin_test.py b/tests/pytorch_wrapper/pt_asin_test.py deleted file mode 100644 index 92f8905..0000000 --- a/tests/pytorch_wrapper/pt_asin_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_asin import PtAsin # Updated import to PtAsin - - -class TestPtAsin(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtAsin() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([1.0, 0.0, -1.0], dtype=torch.float32), torch.asin(torch.tensor([1.0, 0.0, -1.0], dtype=torch.float32))), # Edge cases - (torch.tensor([0.5, -0.5], dtype=torch.float32), torch.asin(torch.tensor([0.5, -0.5], dtype=torch.float32))), # Common values - (torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32), torch.asin(torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32))), # 2D case - (torch.full((2, 2), 0.6, dtype=torch.float32), torch.asin(torch.full((2, 2), 0.6, dtype=torch.float32))), # Constant matrix - ] - - def test_asin(self): - """Test tensor element-wise arc sine using torch.asin().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected asin values - self.assertTrue(torch.allclose(result, expected, atol=1e-6), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_atan_test.py b/tests/pytorch_wrapper/pt_atan_test.py deleted file mode 100644 index 551d7a5..0000000 --- a/tests/pytorch_wrapper/pt_atan_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_atan import PtAtan # Updated import to PtAtan - - -class TestPtAtan(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtAtan() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([1.0, 0.0, -1.0], dtype=torch.float32), torch.atan(torch.tensor([1.0, 0.0, -1.0], dtype=torch.float32))), # Edge cases - (torch.tensor([0.5, -0.5], dtype=torch.float32), torch.atan(torch.tensor([0.5, -0.5], dtype=torch.float32))), # Common values - (torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32), torch.atan(torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32))), # 2D case - (torch.full((2, 2), 0.6, dtype=torch.float32), torch.atan(torch.full((2, 2), 0.6, dtype=torch.float32))), # Constant matrix - ] - - def test_atan(self): - """Test tensor element-wise arc tangent using torch.atan().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected atan values - self.assertTrue(torch.allclose(result, expected, atol=1e-6), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_bitwise_and_test.py b/tests/pytorch_wrapper/pt_bitwise_and_test.py deleted file mode 100644 index 630d459..0000000 --- a/tests/pytorch_wrapper/pt_bitwise_and_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_bitwise_and import PtBitwiseAnd # Updated import to PtBitwiseAnd - - -class TestPtBitwiseAnd(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtBitwiseAnd() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([0b0001, 0b0011, 0b0101]), torch.tensor([0b0001, 0b0010, 0b0110]), torch.tensor([0b0001, 0b0010, 0b0100])), - (torch.tensor([0b1111, 0b1010, 0b1100]), torch.tensor([0b0101, 0b1100, 0b1010]), torch.tensor([0b0101, 0b1000, 0b1000])), - (torch.tensor([1, 2, 3]), torch.tensor([3, 2, 1]), torch.tensor([1, 2, 1])), - (torch.ones(2, 2, dtype=torch.int32), torch.zeros(2, 2, dtype=torch.int32), torch.zeros(2, 2, dtype=torch.int32)), - (torch.full((2, 2), 0b1010, dtype=torch.int32), torch.full((2, 2), 0b1100, dtype=torch.int32), torch.full((2, 2), 0b1000, dtype=torch.int32)), - ] - - def test_bitwise_and(self): - """Test tensor element-wise bitwise AND using torch.bitwise_and().""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected bitwise AND operation - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_bitwise_left_shift_test.py b/tests/pytorch_wrapper/pt_bitwise_left_shift_test.py deleted file mode 100644 index 124b8bf..0000000 --- a/tests/pytorch_wrapper/pt_bitwise_left_shift_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_bitwise_left_shift import PtBitwiseLeftShift # Updated import to PtBitwiseLeftShift - - -class TestPtBitwiseLeftShift(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtBitwiseLeftShift() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([1, 2, 4], dtype=torch.int64), torch.tensor([1, 2, 3], dtype=torch.int64), torch.tensor([2, 8, 32], dtype=torch.int64)), # 1<<1, 2<<2, 4<<3 - (torch.tensor([4, 8, 16], dtype=torch.int64), torch.tensor([2, 1, 3], dtype=torch.int64), torch.tensor([16, 16, 128], dtype=torch.int64)), # 4<<2, 8<<1, 16<<3 - (torch.tensor([0b0001, 0b0010, 0b0100], dtype=torch.int64), torch.tensor([1, 2, 3], dtype=torch.int64), torch.tensor([0b0010, 0b1000, 0b100000], dtype=torch.int64)), # Bitwise shift - (torch.ones(3, 3, dtype=torch.int64), torch.full((3, 3), 2, dtype=torch.int64), torch.full((3, 3), 4, dtype=torch.int64)), # 1<<2 = 4 - (torch.full((2, 2), 5, dtype=torch.int64), torch.full((2, 2), 3, dtype=torch.int64), torch.full((2, 2), 40, dtype=torch.int64)), # 5<<3 = 40 - ] - - def test_bitwise_left_shift(self): - """Test tensor element-wise bitwise left shift using torch.bitwise_left_shift().""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected bitwise left shift operation - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_bitwise_not_test.py b/tests/pytorch_wrapper/pt_bitwise_not_test.py deleted file mode 100644 index a7d87d8..0000000 --- a/tests/pytorch_wrapper/pt_bitwise_not_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_bitwise_not import PtBitwiseNot # Updated import to PtBitwiseNot - - -class TestPtBitwiseNot(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtBitwiseNot() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([0, 1, 2, 3], dtype=torch.int32), torch.tensor([-1, -2, -3, -4], dtype=torch.int32)), # Bitwise NOT - (torch.tensor([0b0000, 0b0001, 0b0010], dtype=torch.int32), torch.tensor([-1, -2, -3], dtype=torch.int32)), # Binary representation - (torch.tensor([[5, 10], [15, 20]], dtype=torch.int32), torch.tensor([[-6, -11], [-16, -21]], dtype=torch.int32)), # Matrix case - (torch.full((2, 2), 0, dtype=torch.int32), torch.full((2, 2), -1, dtype=torch.int32)), # NOT 0 -> -1 - (torch.full((3, 3), -1, dtype=torch.int32), torch.full((3, 3), 0, dtype=torch.int32)), # NOT -1 -> 0 - ] - - def test_bitwise_not(self): - """Test tensor element-wise bitwise NOT using torch.bitwise_not().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected bitwise NOT operation - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_bitwise_or_test.py b/tests/pytorch_wrapper/pt_bitwise_or_test.py deleted file mode 100644 index 9b606fd..0000000 --- a/tests/pytorch_wrapper/pt_bitwise_or_test.py +++ /dev/null @@ -1,54 +0,0 @@ -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_bitwise_or import PtBitwiseOr # Updated import to PtBitwiseOr - - -class TestPtBitwiseOr(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtBitwiseOr() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([0b0001, 0b0011, 0b0101], dtype=torch.int32), - torch.tensor([0b0010, 0b0100, 0b0110], dtype=torch.int32), - torch.tensor([0b0011, 0b0111, 0b0111], dtype=torch.int32)), # Binary OR - - (torch.tensor([1, 2, 4], dtype=torch.int32), - torch.tensor([8, 16, 32], dtype=torch.int32), - torch.tensor([9, 18, 36], dtype=torch.int32)), # Decimal OR - - (torch.tensor([5, 10, 15], dtype=torch.int32), - torch.tensor([3, 6, 12], dtype=torch.int32), - torch.tensor([7, 14, 15], dtype=torch.int32)), # Mixed values - - (torch.zeros(3, 3, dtype=torch.int32), - torch.ones(3, 3, dtype=torch.int32), - torch.ones(3, 3, dtype=torch.int32)), # OR with all ones - - (torch.full((2, 2), 0b1010, dtype=torch.int32), - torch.full((2, 2), 0b1100, dtype=torch.int32), - torch.full((2, 2), 0b1110, dtype=torch.int32)), # OR with constant matrices - ] - - def test_bitwise_or(self): - """Test tensor element-wise bitwise OR using torch.bitwise_or().""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected bitwise OR operation - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_bitwise_right_shift_test.py b/tests/pytorch_wrapper/pt_bitwise_right_shift_test.py deleted file mode 100644 index 69a9b9b..0000000 --- a/tests/pytorch_wrapper/pt_bitwise_right_shift_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_bitwise_right_shift import PtBitwiseRightShift # Updated import to PtBitwiseRightShift - - -class TestPtBitwiseRightShift(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtBitwiseRightShift() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([4, 8, 16], dtype=torch.int64), torch.tensor([1, 2, 3], dtype=torch.int64), torch.tensor([2, 2, 2], dtype=torch.int64)), # 4>>1, 8>>2, 16>>3 - (torch.tensor([32, 64, 128], dtype=torch.int64), torch.tensor([2, 3, 4], dtype=torch.int64), torch.tensor([8, 8, 8], dtype=torch.int64)), # 32>>2, 64>>3, 128>>4 - (torch.tensor([0b1000, 0b10000, 0b100000], dtype=torch.int64), torch.tensor([1, 2, 3], dtype=torch.int64), torch.tensor([0b0100, 0b00100, 0b000100], dtype=torch.int64)), # Bitwise shift - (torch.full((3, 3), 16, dtype=torch.int64), torch.full((3, 3), 2, dtype=torch.int64), torch.full((3, 3), 4, dtype=torch.int64)), # 16>>2 = 4 - (torch.full((2, 2), 40, dtype=torch.int64), torch.full((2, 2), 3, dtype=torch.int64), torch.full((2, 2), 5, dtype=torch.int64)), # 40>>3 = 5 - ] - - def test_bitwise_right_shift(self): - """Test tensor element-wise bitwise right shift using torch.bitwise_right_shift().""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected bitwise right shift operation - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_bitwise_xor_test.py b/tests/pytorch_wrapper/pt_bitwise_xor_test.py deleted file mode 100644 index 24ce242..0000000 --- a/tests/pytorch_wrapper/pt_bitwise_xor_test.py +++ /dev/null @@ -1,54 +0,0 @@ -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_bitwise_xor import PtBitwiseXor # Updated import to PtBitwiseXor - - -class TestPtBitwiseXor(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtBitwiseXor() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([0b0001, 0b0011, 0b0101], dtype=torch.int32), - torch.tensor([0b0010, 0b0100, 0b0110], dtype=torch.int32), - torch.tensor([0b0011, 0b0111, 0b0011], dtype=torch.int32)), # Binary XOR - - (torch.tensor([1, 2, 4], dtype=torch.int32), - torch.tensor([8, 16, 32], dtype=torch.int32), - torch.tensor([9, 18, 36], dtype=torch.int32)), # Decimal XOR - - (torch.tensor([5, 10, 15], dtype=torch.int32), - torch.tensor([3, 6, 12], dtype=torch.int32), - torch.tensor([6, 12, 3], dtype=torch.int32)), # Mixed values - - (torch.zeros(3, 3, dtype=torch.int32), - torch.ones(3, 3, dtype=torch.int32), - torch.ones(3, 3, dtype=torch.int32)), # XOR with all ones - - (torch.full((2, 2), 0b1010, dtype=torch.int32), - torch.full((2, 2), 0b1100, dtype=torch.int32), - torch.full((2, 2), 0b0110, dtype=torch.int32)), # XOR with constant matrices - ] - - def test_bitwise_xor(self): - """Test tensor element-wise bitwise XOR using torch.bitwise_xor().""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected bitwise XOR operation - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_bmm_test.py b/tests/pytorch_wrapper/pt_bmm_test.py deleted file mode 100644 index e1e1a87..0000000 --- a/tests/pytorch_wrapper/pt_bmm_test.py +++ /dev/null @@ -1,60 +0,0 @@ -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_bmm import PtBmm # Updated import to PtBmm - - -class TestPtBmm(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtBmm() - - # Valid test cases: (Tensor A, Tensor B, Expected result) - self.valid_test_cases = [ - # Case 1: Batch of 2 matrices (3x4) * (4x5) → (2, 3, 5) - (torch.ones(2, 3, 4), torch.ones(2, 4, 5), torch.full((2, 3, 5), 4.0)), - - # Case 2: Batch of 3 matrices (2x3) * (3x2) → (3, 2, 2) - (torch.tensor([[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]]), - torch.tensor([[[7.0, 8.0], [9.0, 10.0], [11.0, 12.0]]]), - torch.tensor([[[58.0, 64.0], [139.0, 154.0]]])), - - # Case 3: Identity matrix batch multiplication (should remain the same) - (torch.eye(3).expand(4, -1, -1), torch.eye(3).expand(4, -1, -1), torch.eye(3).expand(4, -1, -1)), - ] - - # Invalid test cases (should raise errors) - self.invalid_test_cases = [ - (torch.ones(3, 4), torch.ones(4, 5)), # 2D tensors (not allowed) - (torch.ones(2, 3, 4), torch.ones(3, 4, 5)), # Mismatched batch size - (torch.ones(2, 3, 4, 5), torch.ones(2, 5, 6)), # 4D tensors (not allowed) - (torch.ones(2, 3, 4), torch.ones(2, 5, 6)), # Matmul shape mismatch within batch - ] - - def test_batched_matrix_multiplication(self): - """Test valid batched matrix multiplication cases.""" - for tens_a, tens_b, expected in self.valid_test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected batch matrix multiplication - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - - def test_invalid_multiplication(self): - """Test invalid cases where dimensions do not match.""" - for tens_a, tens_b in self.invalid_test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - with self.assertRaises(Exception, msg=f"torch.bmm() should fail for shapes {tens_a.shape} and {tens_b.shape}"): - self.node.f(tens_a, tens_b) - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_bool_create_test.py b/tests/pytorch_wrapper/pt_bool_create_test.py deleted file mode 100644 index 8e3ce6e..0000000 --- a/tests/pytorch_wrapper/pt_bool_create_test.py +++ /dev/null @@ -1,58 +0,0 @@ -import os -import sys -import unittest -import torch -import ast - -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_bool_create import PtBoolCreate - -class TestPtBoolCreate(unittest.TestCase): - def setUp(self): - """Set up test data.""" - self.node = PtBoolCreate() - - # Test cases covering various ranks - self.test_cases = [ - # Rank 0 (scalar) - ("True", torch.tensor(True, dtype=torch.bool)), - ("False", torch.tensor(False, dtype=torch.bool)), - - # Rank 1 (1D array) - ("[True, False, True]", torch.tensor([True, False, True], dtype=torch.bool)), - - # Rank 2 (2D matrix) - ("[[True, False], [False, True]]", torch.tensor([[True, False], [False, True]], dtype=torch.bool)), - - # Rank 3 (3D tensor) - ( - "[[[True, False], [False, True]], [[False, True], [True, False]]]", - torch.tensor([[[True, False], [False, True]], [[False, True], [True, False]]], dtype=torch.bool), - ), - ] - - def test_create_tensor(self): - """Test PyTorch boolean 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 bool - self.assertEqual(retval.dtype, torch.bool, 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_cos_test.py b/tests/pytorch_wrapper/pt_cos_test.py deleted file mode 100644 index 9d03217..0000000 --- a/tests/pytorch_wrapper/pt_cos_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_cos import PtCos # Updated import to PtCos - - -class TestPtCos(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtCos() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([0.0, torch.pi / 2, torch.pi], dtype=torch.float32), torch.cos(torch.tensor([0.0, torch.pi / 2, torch.pi], dtype=torch.float32))), # Edge cases - (torch.tensor([0.5, -0.5], dtype=torch.float32), torch.cos(torch.tensor([0.5, -0.5], dtype=torch.float32))), # Common values - (torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32), torch.cos(torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32))), # 2D case - (torch.full((2, 2), 0.6, dtype=torch.float32), torch.cos(torch.full((2, 2), 0.6, dtype=torch.float32))), # Constant matrix - ] - - def test_cos(self): - """Test tensor element-wise cosine using torch.cos().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected cos values - self.assertTrue(torch.allclose(result, expected, atol=1e-6), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_cosh_test.py b/tests/pytorch_wrapper/pt_cosh_test.py deleted file mode 100644 index 299fec0..0000000 --- a/tests/pytorch_wrapper/pt_cosh_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_cosh import PtCosh # Updated import to PtCosh - - -class TestPtCosh(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtCosh() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([0.0, 1.0, -1.0], dtype=torch.float32), torch.cosh(torch.tensor([0.0, 1.0, -1.0], dtype=torch.float32))), # Edge cases - (torch.tensor([0.5, -0.5], dtype=torch.float32), torch.cosh(torch.tensor([0.5, -0.5], dtype=torch.float32))), # Common values - (torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32), torch.cosh(torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32))), # 2D case - (torch.full((2, 2), 0.6, dtype=torch.float32), torch.cosh(torch.full((2, 2), 0.6, dtype=torch.float32))), # Constant matrix - ] - - def test_cosh(self): - """Test tensor element-wise hyperbolic cosine using torch.cosh().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected cosh values - self.assertTrue(torch.allclose(result, expected, atol=1e-6), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_crop_test.py b/tests/pytorch_wrapper/pt_crop_test.py deleted file mode 100644 index 8ebe074..0000000 --- a/tests/pytorch_wrapper/pt_crop_test.py +++ /dev/null @@ -1,49 +0,0 @@ -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_crop import PtCrop - - -class TestPtCrop(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtCrop() - - # Test cases: (Input tensor, Height, Width, Expected shape) - self.test_cases = [ - (torch.rand(1, 3, 16, 16), 8, 8, (1, 3, 8, 8)), # Crop 4D tensor - (torch.rand(3, 16, 16), 8, 8, (3, 8, 8)), # Crop 3D tensor - (torch.rand(1, 3, 16, 16), 16, 16, (1, 3, 16, 16)), # No crop, same size - ] - - def test_crop(self): - """Test tensor cropping to specific sizes.""" - for tens, height, width, expected_shape in self.test_cases: - with self.subTest(height=height, width=width): - result, = self.node.f(tens, height, width) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result shape matches expected shape - self.assertEqual(result.shape, expected_shape, f"Expected shape {expected_shape}, got {result.shape}") - - def test_invalid_tensor_rank(self): - """Test that an invalid tensor rank raises an error.""" - tens = torch.rand(16, 16) # 2D tensor (invalid) - with self.assertRaises(ValueError, msg="Only rank 3 or 4 tensors are supported for crop."): - self.node.f(tens, 8, 8) - - def test_crop_exceeds_dimensions(self): - """Test that cropping beyond tensor dimensions raises an error.""" - tens = torch.rand(1, 3, 16, 16) - with self.assertRaises(ValueError, msg="Specified crop size exceeds tensor dimensions."): - self.node.f(tens, 32, 32) - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_data_loader_test.py b/tests/pytorch_wrapper/pt_data_loader_test.py deleted file mode 100644 index 6b59a75..0000000 --- a/tests/pytorch_wrapper/pt_data_loader_test.py +++ /dev/null @@ -1,43 +0,0 @@ -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.ptv_dataset import PtvDataset -from pytorch_wrapper.ptv_transforms_to_tensor import PtvTransformsToTensor -from pytorch_wrapper.pt_data_loader import PtDataLoader - -class TestPtDataLoader(unittest.TestCase): - def setUp(self): - """Set up test data.""" - self.dataset_node = PtvDataset() - self.compose_node = PtvTransformsToTensor() - self.data_loader_node = PtDataLoader() - - def test_with_transform(self): - """Tests with transform""" - bs = 2 - transform = self.compose_node.f()[0] - dataset = self.dataset_node.f("MNIST", - True, # download - "", # root - '{"train": True}', - transform=transform)[0] - train_data = self.data_loader_node.f( - dataset=dataset, - batch_size=bs, - shuffle=True, - parameters='{"num_workers":1}' - )[0] - - x, y = next(iter(train_data)) - self.assertEqual(x.size(), torch.Size([bs, 1, 28, 28])) - self.assertEqual(y.size(), torch.Size([bs])) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_div_test.py b/tests/pytorch_wrapper/pt_div_test.py deleted file mode 100644 index 7e6e022..0000000 --- a/tests/pytorch_wrapper/pt_div_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_div import PtDiv # Updated import to PtDiv - - -class TestPtDiv(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtDiv() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([4.0, 10.0, 18.0]), torch.tensor([2.0, 5.0, 6.0]), torch.tensor([2.0, 2.0, 3.0])), - (torch.ones(3, 3), torch.ones(3, 3), torch.ones(3, 3)), # 1 / 1 = 1 - (torch.full((2, 2), 4.0), torch.full((2, 2), 2.0), torch.full((2, 2), 2.0)), # 4 / 2 = 2 - (torch.tensor([[6.0], [12.0]]), torch.tensor([[3.0], [4.0]]), torch.tensor([[2.0], [3.0]])), # Element-wise division - ] - - def test_division(self): - """Test tensor division.""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected quotient - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_einsum_test.py b/tests/pytorch_wrapper/pt_einsum_test.py deleted file mode 100644 index ef93fcb..0000000 --- a/tests/pytorch_wrapper/pt_einsum_test.py +++ /dev/null @@ -1,99 +0,0 @@ -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_einsum import PtEinsum # Updated import to PtEinsum - - -class TestPtEinsum(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtEinsum() - - def test_trace_extraction(self): - """Extract the diagonal (1,2,3,4,5) and sum it up (trace).""" - matrix = torch.tensor([[1, 0, 0, 0, 0], - [0, 2, 0, 0, 0], - [0, 0, 3, 0, 0], - [0, 0, 0, 4, 0], - [0, 0, 0, 0, 5]], dtype=torch.float32) - expected = torch.tensor(15.0) # Sum of diagonal - result, = self.node.f("ii", matrix) # Only requires `tens_a` - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - - def test_diagonal_extraction(self): - """Extract diagonal elements (1,2,3,4,5).""" - matrix = torch.tensor([[1, 0, 0, 0, 0], - [0, 2, 0, 0, 0], - [0, 0, 3, 0, 0], - [0, 0, 0, 4, 0], - [0, 0, 0, 0, 5]], dtype=torch.float32) - expected = torch.tensor([1, 2, 3, 4, 5], dtype=torch.float32) - result, = self.node.f("ii->i", matrix) # Only requires `tens_a` - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - - def test_hadamard_product(self): - """Compute Hadamard product (element-wise multiplication).""" - a = torch.tensor([[1, 2, 3, 4], [5, 6, 7, 8]], dtype=torch.float32) - b = torch.tensor([[9, 10, 11, 12], [13, 14, 15, 16]], dtype=torch.float32) - expected = a * b - result, = self.node.f("ij,ij->ij", a, b) - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - - def test_matrix_multiplication(self): - """Test standard matrix multiplication.""" - a = torch.tensor([[1, 2, 3, 4], [5, 6, 7, 8]], dtype=torch.float32) - b = torch.tensor([[9, 10], [11, 12], [13, 14], [15, 16]], dtype=torch.float32) - expected = torch.mm(a, b) - result, = self.node.f("ij,jk->ik", a, b) - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - - def test_3d_tensor_multiplication(self): - """Test matrix multiplication for rank-3 tensors.""" - a = torch.ones(2, 3, 4, dtype=torch.float32) - b = torch.ones(2, 4, 5, dtype=torch.float32) - expected = torch.matmul(a, b) - result, = self.node.f("bij,bjk->bik", a, b) - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - - def test_4d_tensor_multiplication(self): - """Test matrix multiplication for rank-4 tensors.""" - a = torch.ones(2, 3, 4, 6, dtype=torch.float32) # (batch, groups, rows, cols) - b = torch.ones(2, 3, 6, 5, dtype=torch.float32) # (batch, groups, cols, out_features) - - expected = torch.matmul(a, b) # Standard 4D matrix multiplication - result, = self.node.f("bijk,bikl->bijl", a, b) # Correct einsum notation - - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - - def test_bilinear_transformation(self): - """Test bilinear transformation: a vector, W matrix, and b vector.""" - a = torch.tensor([1, 2, 3, 4, 5], dtype=torch.float32) - W = torch.tensor([[6, 7, 8, 9, 10], - [11, 12, 13, 14, 15], - [16, 17, 18, 19, 20], - [21, 22, 23, 24, 25], - [26, 27, 28, 29, 30]], dtype=torch.float32) - b = torch.tensor([31, 32, 33, 34, 35], dtype=torch.float32) - - expected = torch.dot(a, torch.matmul(W, b)) - result, = self.node.f("i,ij,j->", a, W, b) - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - - def test_tensor_format_conversion(self): - """Test conversion from (B, C, H, W) format to (B, H, W, C) format.""" - a = torch.randn(2, 3, 64, 64, dtype=torch.float32) # (Batch, Channels, Height, Width) - - expected = a.permute(0, 2, 3, 1) # Convert to (Batch, Height, Width, Channels) - result, = self.node.f("bchw->bhwc", a) # Correct einsum notation for format swap - - self.assertTrue(torch.equal(result, expected), f"Expected {expected.shape}, got {result.shape}") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_eq_test.py b/tests/pytorch_wrapper/pt_eq_test.py deleted file mode 100644 index 0943a8f..0000000 --- a/tests/pytorch_wrapper/pt_eq_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_eq import PtEq # Updated import to PtEq - - -class TestPtEq(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtEq() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([1, 2, 3]), torch.tensor([1, 2, 3]), torch.tensor([True, True, True])), - (torch.tensor([1, 2, 3]), torch.tensor([3, 2, 1]), torch.tensor([False, True, False])), - (torch.ones(3, 3), torch.ones(3, 3), torch.full((3, 3), True, dtype=torch.bool)), - (torch.zeros(2, 2), torch.ones(2, 2), torch.full((2, 2), False, dtype=torch.bool)), - (torch.tensor([[1], [2]]), torch.tensor([[1], [3]]), torch.tensor([[True], [False]])), - ] - - def test_equality(self): - """Test tensor element-wise equality using torch.eq().""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected element-wise equality - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_evaluate_classification_conv_model_test.py b/tests/pytorch_wrapper/pt_evaluate_classification_conv_model_test.py deleted file mode 100644 index 78e2509..0000000 --- a/tests/pytorch_wrapper/pt_evaluate_classification_conv_model_test.py +++ /dev/null @@ -1,83 +0,0 @@ -import os -import sys -import unittest -import torch -import torch.nn as nn - -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.ptn_conv_model import PtnConvModel -from pytorch_wrapper.ptv_dataset import PtvDataset -from pytorch_wrapper.ptv_transforms_to_tensor import PtvTransformsToTensor -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): - def setUp(self): - """Set up test instance.""" - # Transform - compose_node = PtvTransformsToTensor() - transform = compose_node.f()[0] - - # Dataset - dataset_node = PtvDataset() - dataset = dataset_node.f("FashionMNIST", - True, # download - "", # root - '{"train": False}', - transform=transform)[0] - - # Data loader - data_loader_node = PtDataLoader() - self.data_loader = data_loader_node.f( - dataset=dataset, - batch_size=32, - shuffle=True, - parameters='{"num_workers":1}' - )[0] - - # Model - self.model_node = PtnConvModel() - self.model = self.model_node.f( - input_dim="(1, 28, 28)", - penultimate_dim=0, - output_dim=10, - channel_list="[32,64,128,256]", - kernel_size_list="[3,3,3,3]", - padding_list="[1,1,1,1]", - downsample_list="[True,True,True,True]" - )[0] - - # Load weights - self.load_model = PtLoadModel() - epochs = 1 - self.loaded_model = self.load_model.f(self.model, f"fashion_mnist_{epochs}_epochs_conv.pt") - - # Instantiates evaluation node - self.evaluate = PtEvaluateClassificationModel() - - def test_evaluation(self): - """ - Tests evaluation. - - Note that this test requires a trained model file on the file system. - Unit test will fail if you run this for the first time. - Run the unit test for training to generate the weight, and re-run this unit test. - """ - accuracy, precision, recall, f1, num_samples = \ - self.evaluate.f(self.model, self.data_loader, use_gpu=True) - print(f""" - accuracy: {accuracy} - precision: {precision} - recall: {recall} - f1: {f1} - Number of samples: {num_samples} - """) - self.assertTrue(accuracy > 0.8) - self.assertEqual(num_samples, 10000) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_evaluate_classification_conv_model_test2.py b/tests/pytorch_wrapper/pt_evaluate_classification_conv_model_test2.py deleted file mode 100644 index cf6419e..0000000 --- a/tests/pytorch_wrapper/pt_evaluate_classification_conv_model_test2.py +++ /dev/null @@ -1,83 +0,0 @@ -import os -import sys -import unittest -import torch -import torch.nn as nn - -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.ptn_conv_model import PtnConvModel -from pytorch_wrapper.ptv_image_folder_dataset import PtvImageFolderDataset -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 = PtvTransformsResize() - transform = compose_node.f(256, 256)[0] - - # Dataset - self.node = PtvImageFolderDataset() - dataset = self.node.f("dog_and_cat/val", - transform=transform)[0] - - # Data loader - data_loader_node = PtDataLoader() - self.data_loader = data_loader_node.f( - dataset=dataset, - batch_size=32, - shuffle=True, - parameters='{"num_workers":1}' - )[0] - - # Model - self.model_node = PtnConvModel() - self.model = self.model_node.f( - input_dim="(3, 256, 256)", - penultimate_dim=0, - output_dim=2, - 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]" - )[0] - - # Load weights - self.load_model = PtLoadModel() - 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. - - Note that this test requires a trained model file on the file system. - Unit test will fail if you run this for the first time. - Run the unit test for training to generate the weight, and re-run this unit test. - """ - accuracy, precision, recall, f1, num_samples = \ - self.evaluate.f(self.model, self.data_loader, use_gpu=True) - print(f""" - accuracy: {accuracy} - precision: {precision} - recall: {recall} - f1: {f1} - Number of samples: {num_samples} - """) - self.assertTrue(accuracy > 0.7) - self.assertEqual(num_samples, 300) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_evaluate_classification_model_test.py b/tests/pytorch_wrapper/pt_evaluate_classification_model_test.py deleted file mode 100644 index 262e13a..0000000 --- a/tests/pytorch_wrapper/pt_evaluate_classification_model_test.py +++ /dev/null @@ -1,79 +0,0 @@ -import os -import sys -import unittest -import torch -import torch.nn as nn - -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.ptn_linear_model import PtnLinearModel -from pytorch_wrapper.ptv_dataset import PtvDataset -from pytorch_wrapper.ptv_transforms_to_tensor import PtvTransformsToTensor -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): - def setUp(self): - """Set up test instance.""" - # Transform - compose_node = PtvTransformsToTensor() - transform = compose_node.f()[0] - - # Dataset - dataset_node = PtvDataset() - dataset = dataset_node.f("FashionMNIST", - True, # download - "", # root - '{"train": False}', - transform=transform)[0] - - # Data loader - data_loader_node = PtDataLoader() - self.data_loader = data_loader_node.f( - dataset=dataset, - batch_size=32, - shuffle=True, - parameters='{"num_workers":1}' - )[0] - - # Model - self.model_node = PtnLinearModel() - self.model = self.model_node.f( - "784,180,42,10", # input dim for first layer, output dim for each layer - "True,True,True", # bias - 3 # num layers - )[0] - - # Load weights - self.load_model = PtLoadModel() - epochs = 1 - self.loaded_model = self.load_model.f(self.model, f"fashion_mnist_{epochs}_epochs.pt") - - # Instntiates evaluation node - self.evaluate = PtEvaluateClassificationModel() - - def test_evaluation(self): - """ - Tests evaluation. - - Note that this test requires a trained model file on the file system. - Unit test will fail if you run this for the first time. - Run the unit test for training to generate the weight, and re-run this unit test. - """ - accuracy, precision, recall, f1, num_samples = \ - self.evaluate.f(self.model, self.data_loader, use_gpu=True) - print(f""" - accuracy: {accuracy} - precision: {precision} - recall: {recall} - f1: {f1} - Number of samples: {num_samples} - """) - self.assertTrue(accuracy > 0.8) - self.assertEqual(num_samples, 10000) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_exp_test.py b/tests/pytorch_wrapper/pt_exp_test.py deleted file mode 100644 index b88551c..0000000 --- a/tests/pytorch_wrapper/pt_exp_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_exp import PtExp # Updated import to PtExp - - -class TestPtExp(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtExp() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([0.0, 1.0, 2.0], dtype=torch.float32), torch.exp(torch.tensor([0.0, 1.0, 2.0], dtype=torch.float32))), # Edge cases - (torch.tensor([-1.0, -2.0, 0.5], dtype=torch.float32), torch.exp(torch.tensor([-1.0, -2.0, 0.5], dtype=torch.float32))), # Common values - (torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32), torch.exp(torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32))), # 2D case - (torch.full((2, 2), 0.6, dtype=torch.float32), torch.exp(torch.full((2, 2), 0.6, dtype=torch.float32))), # Constant matrix - ] - - def test_exp(self): - """Test tensor element-wise exponential function using torch.exp().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected exp values - self.assertTrue(torch.allclose(result, expected, atol=1e-6), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_flatten_test.py b/tests/pytorch_wrapper/pt_flatten_test.py deleted file mode 100644 index 5a66695..0000000 --- a/tests/pytorch_wrapper/pt_flatten_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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() diff --git a/tests/pytorch_wrapper/pt_float_create_test.py b/tests/pytorch_wrapper/pt_float_create_test.py deleted file mode 100644 index f5acb08..0000000 --- a/tests/pytorch_wrapper/pt_float_create_test.py +++ /dev/null @@ -1,65 +0,0 @@ -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_floor_divide_test.py b/tests/pytorch_wrapper/pt_floor_divide_test.py deleted file mode 100644 index 9347727..0000000 --- a/tests/pytorch_wrapper/pt_floor_divide_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_floor_divide import PtFloorDiv - - -class TestPtFloorDiv(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtFloorDiv() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([4.0, 10.0, 18.0]), torch.tensor([3.0, 4.0, 5.0]), torch.tensor([1.0, 2.0, 3.0])), - (torch.ones(3, 3) * 7, torch.ones(3, 3) * 3, torch.ones(3, 3) * 2), # 7 // 3 = 2 - (torch.full((2, 2), 9.0), torch.full((2, 2), 2.0), torch.full((2, 2), 4.0)), # 9 // 2 = 4 - (torch.tensor([[7.0], [15.0]]), torch.tensor([[2.0], [4.0]]), torch.tensor([[3.0], [3.0]])), # Element-wise floor division - ] - - def test_floor_division(self): - """Test tensor floor division.""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected floor quotient - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_from_image_transpose_test.py b/tests/pytorch_wrapper/pt_from_image_transpose_test.py deleted file mode 100644 index 6a2b74a..0000000 --- a/tests/pytorch_wrapper/pt_from_image_transpose_test.py +++ /dev/null @@ -1,57 +0,0 @@ -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_from_image_transpose import PtFromImageTranspose - - -class TestPtFromImageTranspose(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtFromImageTranspose() - - # Define test cases: (input_tensor, expected_shape) - self.test_cases = [ - # Case 1: 3D tensor (H, W, C) -> (C, H, W) - (torch.ones(64, 128, 3), (3, 64, 128)), # (64,128,3) -> (3,64,128) - - # Case 2: 4D tensor (N, H, W, C) -> (N, C, H, W) - (torch.ones(10, 64, 128, 3), (10, 3, 64, 128)), # (10,64,128,3) -> (10,3,64,128) - - # Case 3: Invalid input (2D tensor) - (torch.ones(64, 128), ValueError), # 2D tensors should raise an error - - # Case 4: Invalid input (5D tensor) - (torch.ones(10, 64, 128, 3, 2), ValueError), # 5D tensors should raise an error - ] - - def test_transpose(self): - """Test image transposition operation with valid and invalid cases.""" - for input_tensor, expected in self.test_cases: - with self.subTest(input_shape=input_tensor.shape): - if isinstance(expected, tuple): - # Valid cases - result, = self.node.f(input_tensor) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result has correct shape - self.assertEqual(result.shape, torch.Size(expected), f"Expected shape {expected}, got {result.shape}") - - # Ensure result has the same dtype - self.assertEqual(result.dtype, input_tensor.dtype, "Dtype mismatch after transpose operation") - else: - # Invalid cases should raise an error - with self.assertRaises(expected): - self.node.f(input_tensor) - - -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 deleted file mode 100644 index 49e6642..0000000 --- a/tests/pytorch_wrapper/pt_from_numpy_test.py +++ /dev/null @@ -1,65 +0,0 @@ -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_full_test.py b/tests/pytorch_wrapper/pt_full_test.py deleted file mode 100644 index 77f35de..0000000 --- a/tests/pytorch_wrapper/pt_full_test.py +++ /dev/null @@ -1,71 +0,0 @@ -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_full import PtFull -from pytorch_wrapper.utils import DTYPE_MAPPING - - -class TestPtFull(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtFull() - - # Tensor size to test - self.size_str = "[2, 3]" # Using a small 2D tensor size for all tests - self.expected_shape = (2, 3) - - # Values to test (using representative values for each type) - self.test_values = { - "float32": "1.5", - "float16": "-2.3", - "bfloat16": "3.7", - "float64": "4.9", - "uint8": "255", # Max value for uint8 - "int8": "-128", # Min value for int8 - "int16": "32767", # Max value for int16 - "int32": "-2147483648", # Min value for int32 - "int64": "9223372036854775807", # Max value for int64 - "bool": "True" - } - - def test_full_tensor_all_dtypes(self): - """Test tensor creation with a specified value for all supported data types.""" - for data_type, torch_dtype in DTYPE_MAPPING.items(): - value_str = self.test_values[data_type] - - with self.subTest(data_type=data_type, value=value_str): - result, = self.node.f(value_str, self.size_str, data_type) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result has correct shape - self.assertEqual(result.shape, torch.Size(self.expected_shape), f"Expected shape {self.expected_shape}, got {result.shape}") - - # Ensure result has correct dtype - self.assertEqual(result.dtype, torch_dtype, f"Expected dtype {torch_dtype}, got {result.dtype}") - - # Convert value_str to correct type - if torch_dtype in {torch.float32, torch.float16, torch.bfloat16, torch.float64}: - expected_value = float(value_str) - elif torch_dtype in {torch.uint8, torch.int8, torch.int16, torch.int32, torch.int64}: - expected_value = int(value_str) - elif torch_dtype == torch.bool: - expected_value = value_str.lower() == "true" - else: - raise ValueError(f"Unsupported data type: {data_type}") - - # Ensure all elements match the expected value - expected = torch.full(self.expected_shape, expected_value, dtype=torch_dtype) - self.assertTrue(torch.equal(result, expected), f"Tensor values do not match expected tensor for dtype {data_type}") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_gather_test.py b/tests/pytorch_wrapper/pt_gather_test.py deleted file mode 100644 index df09c79..0000000 --- a/tests/pytorch_wrapper/pt_gather_test.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -import sys -import unittest -import torch -import ast - -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_gather import PtGather - - -class TestPtGather(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtGather() - - # Test cases: (Tensor, Dim, Index (as string), Expected result) - self.test_cases = [ - ( - torch.tensor([[10, 20, 30, 40, 50], [100, 200, 300, 400, 500]]), - 1, - "[[0, 4, 3, 2, 0], [4, 3, 0, 0, 0]]", - torch.tensor([[10, 50, 40, 30, 10], [500, 400, 100, 100, 100]]) - ), - ( - torch.tensor([[1, 2], [3, 4]]), - 0, - "[[1, 0], [0, 1]]", - torch.tensor([[3, 2], [1, 4]]) - ), - ] - - def test_gather(self): - """Test torch.gather behavior.""" - for tens, dim, index, expected in self.test_cases: - with self.subTest(tens=tens, dim=dim, index=index): - result, = self.node.f(tens, dim, index) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected gathered values - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_ge_test.py b/tests/pytorch_wrapper/pt_ge_test.py deleted file mode 100644 index 92ab581..0000000 --- a/tests/pytorch_wrapper/pt_ge_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_ge import PtGe # Updated import to PtGe - - -class TestPtGe(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtGe() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([1, 2, 3]), torch.tensor([0, 2, 4]), torch.tensor([True, True, False])), - (torch.tensor([3, 5, 7]), torch.tensor([1, 5, 10]), torch.tensor([True, True, False])), - (torch.ones(3, 3), torch.ones(3, 3), torch.full((3, 3), True, dtype=torch.bool)), - (torch.zeros(2, 2), torch.ones(2, 2), torch.full((2, 2), False, dtype=torch.bool)), - (torch.tensor([[5], [10]]), torch.tensor([[5], [10]]), torch.tensor([[True], [True]])), - ] - - def test_greater_or_equal(self): - """Test tensor element-wise greater-than-or-equal condition using torch.ge().""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected greater-than-or-equal comparison - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_gt_test.py b/tests/pytorch_wrapper/pt_gt_test.py deleted file mode 100644 index 29603bf..0000000 --- a/tests/pytorch_wrapper/pt_gt_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_gt import PtGt # Updated import to PtGt - - -class TestPtGt(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtGt() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([1, 2, 3]), torch.tensor([0, 2, 4]), torch.tensor([True, False, False])), - (torch.tensor([3, 5, 7]), torch.tensor([1, 5, 10]), torch.tensor([True, False, False])), - (torch.ones(3, 3), torch.zeros(3, 3), torch.full((3, 3), True, dtype=torch.bool)), - (torch.zeros(2, 2), torch.ones(2, 2), torch.full((2, 2), False, dtype=torch.bool)), - (torch.tensor([[5], [10]]), torch.tensor([[3], [10]]), torch.tensor([[True], [False]])), - ] - - def test_greater_than(self): - """Test tensor element-wise greater-than condition using torch.gt().""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected greater-than comparison - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_index_select_test.py b/tests/pytorch_wrapper/pt_index_select_test.py deleted file mode 100644 index 8751214..0000000 --- a/tests/pytorch_wrapper/pt_index_select_test.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -import sys -import unittest -import torch -import ast - -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_index_select import PtIndexSelect - - -class TestPtIndexSelect(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtIndexSelect() - - # Test cases: (Tensor, Dim, Index (as string), Expected result) - self.test_cases = [ - ( - torch.tensor([[1, 2, 3], [40, 50, 60], [700, 800, 900]]), - 0, - "[0, 2]", - torch.tensor([[1, 2, 3], [700, 800, 900]]) - ), - ( - torch.tensor([[1, 2, 3], [40, 50, 60], [700, 800, 900]]), - 1, - "[1, 2]", - torch.tensor([[2, 3], [50, 60], [800, 900]]) - ), - ] - - def test_index_select(self): - """Test torch.index_select behavior.""" - for tens, dim, index, expected in self.test_cases: - with self.subTest(tens=tens, dim=dim, index=index): - result, = self.node.f(tens, dim, index) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected selected values - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -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 deleted file mode 100644 index d77df2e..0000000 --- a/tests/pytorch_wrapper/pt_int_create_test.py +++ /dev/null @@ -1,62 +0,0 @@ -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_interpolate_by_scale_factor_test.py b/tests/pytorch_wrapper/pt_interpolate_by_scale_factor_test.py deleted file mode 100644 index 07422d0..0000000 --- a/tests/pytorch_wrapper/pt_interpolate_by_scale_factor_test.py +++ /dev/null @@ -1,60 +0,0 @@ -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_interpolate_by_scale_factor import PtInterpolateByScaleFactor - - -class TestPtInterpolateByScaleFactor(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtInterpolateByScaleFactor() - - # Test cases 3d: (Input tensor, Scale factor, Mode, Expected shape) - self.test_cases_3d = [ - (torch.rand(3, 16, 16), 2.0, "nearest", (3, 32, 32)), - (torch.rand(3, 16, 16), 2.0, "nearest-exact", (3, 32, 32)), - (torch.rand(3, 16, 16), 0.5, "bilinear", (3, 8, 8)), - (torch.rand(3, 16, 16), 1.5, "bicubic", (3, 24, 24)), - ] - - # Test cases: (Input tensor, Scale factor, Mode, Expected shape) - self.test_cases_4d = [ - (torch.rand(1, 3, 16, 16), 2.0, "nearest", (1, 3, 32, 32)), # Upscale 4D tensor - (torch.rand(1, 3, 16, 16), 2.0, "nearest-exact", (1, 3, 32, 32)), # Upscale 4D tensor - (torch.rand(1, 3, 16, 16), 0.5, "bilinear", (1, 3, 8, 8)), # Downscale 4D tensor - (torch.rand(1, 3, 16, 16), 1.5, "bicubic", (1, 3, 24, 24)), # Bicubic mode with 4D tensor - ] - - def test_interpolation_3d(self): - """Test tensor interpolation with different scale factors and modes.""" - for tens, scale_factor, mode, expected_shape in self.test_cases_3d: - with self.subTest(scale_factor=scale_factor, mode=mode): - result, = self.node.f(tens, scale_factor, mode) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result shape matches expected shape - self.assertEqual(result.shape, expected_shape, f"Expected shape {expected_shape}, got {result.shape}") - - - def test_interpolation_4d(self): - """Test tensor interpolation with different scale factors and modes.""" - for tens, scale_factor, mode, expected_shape in self.test_cases_4d: - with self.subTest(scale_factor=scale_factor, mode=mode): - result, = self.node.f(tens, scale_factor, mode) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result shape matches expected shape - self.assertEqual(result.shape, expected_shape, f"Expected shape {expected_shape}, got {result.shape}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_interpolate_to_size_test.py b/tests/pytorch_wrapper/pt_interpolate_to_size_test.py deleted file mode 100644 index 36baf26..0000000 --- a/tests/pytorch_wrapper/pt_interpolate_to_size_test.py +++ /dev/null @@ -1,61 +0,0 @@ -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_interpolate_to_size import PtInterpolateToSize - - -class TestPtInterpolateToSize(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtInterpolateToSize() - - # Test cases 3d: (Input tensor, Height, Width, Mode, Expected shape) - self.test_cases_3d = [ - (torch.rand(3, 16, 16), 32, 32, "nearest", (3, 32, 32)), # Upscale 3D tensor - (torch.rand(3, 16, 16), 32, 32, "nearest-exact", (3, 32, 32)), # Upscale 3D tensor - (torch.rand(3, 16, 16), 8, 8, "bilinear", (3, 8, 8)), # Downscale 3D tensor - (torch.rand(3, 16, 16), 24, 24, "bicubic", (3, 24, 24)), # Bicubic mode with 3D tensor - ] - - # Test cases 4d: (Input tensor, Height, Width, Mode, Expected shape) - self.test_cases_4d = [ - (torch.rand(1, 3, 16, 16), 32, 32, "nearest", (1, 3, 32, 32)), # Upscale 4D tensor - (torch.rand(1, 3, 16, 16), 32, 32, "nearest-exact", (1, 3, 32, 32)), # Upscale 4D tensor - (torch.rand(1, 3, 16, 16), 8, 8, "bilinear", (1, 3, 8, 8)), # Downscale 4D tensor - (torch.rand(1, 3, 16, 16), 24, 24, "bicubic", (1, 3, 24, 24)), # Bicubic mode with 4D tensor - ] - - def test_interpolation_3d(self): - """Test tensor interpolation with different target sizes and modes.""" - for tens, height, width, mode, expected_shape in self.test_cases_3d: - with self.subTest(height=height, width=width, mode=mode): - result, = self.node.f(tens, height, width, mode) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result shape matches expected shape - self.assertEqual(result.shape, expected_shape, f"Expected shape {expected_shape}, got {result.shape}") - - - def test_interpolation_4d(self): - """Test tensor interpolation with different target sizes and modes.""" - for tens, height, width, mode, expected_shape in self.test_cases_4d: - with self.subTest(height=height, width=width, mode=mode): - result, = self.node.f(tens, height, width, mode) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result shape matches expected shape - self.assertEqual(result.shape, expected_shape, f"Expected shape {expected_shape}, got {result.shape}") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_le_test.py b/tests/pytorch_wrapper/pt_le_test.py deleted file mode 100644 index 4cb062d..0000000 --- a/tests/pytorch_wrapper/pt_le_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_le import PtLe # Updated import to PtLe - - -class TestPtLe(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtLe() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([1, 2, 3]), torch.tensor([0, 2, 4]), torch.tensor([False, True, True])), - (torch.tensor([3, 5, 7]), torch.tensor([1, 5, 10]), torch.tensor([False, True, True])), - (torch.ones(3, 3), torch.ones(3, 3), torch.full((3, 3), True, dtype=torch.bool)), - (torch.zeros(2, 2), torch.ones(2, 2), torch.full((2, 2), True, dtype=torch.bool)), - (torch.tensor([[5], [10]]), torch.tensor([[5], [10]]), torch.tensor([[True], [True]])), - ] - - def test_less_or_equal(self): - """Test tensor element-wise less-than-or-equal condition using torch.le().""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected less-than-or-equal comparison - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_linspace_test.py b/tests/pytorch_wrapper/pt_linspace_test.py deleted file mode 100644 index 3678a1f..0000000 --- a/tests/pytorch_wrapper/pt_linspace_test.py +++ /dev/null @@ -1,43 +0,0 @@ -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_linspace import PtLinspace - - -class TestPtLinspace(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtLinspace() - - # Test cases: (start, end, steps, dtype, expected_tensor, expected_dtype) - self.test_cases = [ - ("0", "10", "5", "float32", torch.tensor([0, 2.5, 5, 7.5, 10], dtype=torch.float32), torch.float32), # Basic float32 case - ("-5", "5", "6", "float64", torch.tensor([-5, -3, -1, 1, 3, 5], dtype=torch.float64), torch.float64), # Negative to positive - ("1", "2", "3", "float16", torch.tensor([1, 1.5, 2], dtype=torch.float16), torch.float16), # Small float16 case - ("100", "200", "5", "int32", torch.tensor([100, 125, 150, 175, 200], dtype=torch.int32), torch.int32), # Large int32 case - ("0", "1", "2", "bfloat16", torch.tensor([0, 1], dtype=torch.bfloat16), torch.bfloat16), # Simple two-step bfloat16 - ] - - def test_linspace_tensor_creation(self): - """Test tensor generation using torch.linspace(), including values and dtype.""" - for start, end, steps, dtype, expected, expected_dtype in self.test_cases: - with self.subTest(start=start, end=end, steps=steps, dtype=dtype): - result, = self.node.f(start, end, steps, dtype) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected tensor values - self.assertTrue(torch.allclose(result, expected, atol=1e-5), f"Expected {expected}, got {result}") - - # Ensure result has the correct dtype - self.assertEqual(result.dtype, expected_dtype, f"Expected dtype {expected_dtype}, got {result.dtype}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_log_test.py b/tests/pytorch_wrapper/pt_log_test.py deleted file mode 100644 index a2b644a..0000000 --- a/tests/pytorch_wrapper/pt_log_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_log import PtLog # Updated import to PtLog - - -class TestPtLog(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtLog() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([1.0, torch.e, 10.0], dtype=torch.float32), torch.log(torch.tensor([1.0, torch.e, 10.0], dtype=torch.float32))), # Edge cases - (torch.tensor([0.5, 2.0, 3.0], dtype=torch.float32), torch.log(torch.tensor([0.5, 2.0, 3.0], dtype=torch.float32))), # Common values - (torch.tensor([[1.5, 3.2], [4.7, 5.9]], dtype=torch.float32), torch.log(torch.tensor([[1.5, 3.2], [4.7, 5.9]], dtype=torch.float32))), # 2D case - (torch.full((2, 2), 2.5, dtype=torch.float32), torch.log(torch.full((2, 2), 2.5, dtype=torch.float32))), # Constant matrix - ] - - def test_log(self): - """Test tensor element-wise natural logarithm using torch.log().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected log values - self.assertTrue(torch.allclose(result, expected, atol=1e-6), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_logical_and_test.py b/tests/pytorch_wrapper/pt_logical_and_test.py deleted file mode 100644 index c5227fb..0000000 --- a/tests/pytorch_wrapper/pt_logical_and_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_logical_and import PtLogicalAnd # Updated import to PtLogicalAnd - - -class TestPtLogicalAnd(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtLogicalAnd() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([True, False, True]), torch.tensor([False, False, True]), torch.tensor([False, False, True])), - (torch.tensor([[True, True], [False, False]]), torch.tensor([[True, False], [True, False]]), torch.tensor([[True, False], [False, False]])), - (torch.ones(3, 3, dtype=torch.bool), torch.zeros(3, 3, dtype=torch.bool), torch.zeros(3, 3, dtype=torch.bool)), # All true AND all false -> all false - (torch.ones(2, 2, dtype=torch.bool), torch.ones(2, 2, dtype=torch.bool), torch.ones(2, 2, dtype=torch.bool)), # All true AND all true -> all true - (torch.tensor([1, 0, 1, 0], dtype=torch.bool), torch.tensor([1, 1, 0, 0], dtype=torch.bool), torch.tensor([True, False, False, False])), # Mixed cases - ] - - def test_logical_and(self): - """Test tensor element-wise logical AND using torch.logical_and().""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected logical AND operation - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_logical_not_test.py b/tests/pytorch_wrapper/pt_logical_not_test.py deleted file mode 100644 index de18c94..0000000 --- a/tests/pytorch_wrapper/pt_logical_not_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_logical_not import PtLogicalNot # Updated import to PtLogicalNot - - -class TestPtLogicalNot(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtLogicalNot() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([True, False, True]), torch.tensor([False, True, False])), - (torch.tensor([[True, True], [False, False]]), torch.tensor([[False, False], [True, True]])), - (torch.ones(3, 3, dtype=torch.bool), torch.zeros(3, 3, dtype=torch.bool)), # NOT all true -> all false - (torch.zeros(2, 2, dtype=torch.bool), torch.ones(2, 2, dtype=torch.bool)), # NOT all false -> all true - (torch.tensor([1, 0, 1, 0], dtype=torch.bool), torch.tensor([False, True, False, True])), # Mixed cases - ] - - def test_logical_not(self): - """Test tensor element-wise logical NOT using torch.logical_not().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected logical NOT operation - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_logical_or_test.py b/tests/pytorch_wrapper/pt_logical_or_test.py deleted file mode 100644 index 97ae602..0000000 --- a/tests/pytorch_wrapper/pt_logical_or_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_logical_or import PtLogicalOr # Updated import to PtLogicalOr - - -class TestPtLogicalOr(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtLogicalOr() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([True, False, True]), torch.tensor([False, False, True]), torch.tensor([True, False, True])), - (torch.tensor([[True, True], [False, False]]), torch.tensor([[False, True], [True, False]]), torch.tensor([[True, True], [True, False]])), - (torch.ones(3, 3, dtype=torch.bool), torch.zeros(3, 3, dtype=torch.bool), torch.ones(3, 3, dtype=torch.bool)), # All true OR all false -> all true - (torch.zeros(2, 2, dtype=torch.bool), torch.zeros(2, 2, dtype=torch.bool), torch.zeros(2, 2, dtype=torch.bool)), # All false OR all false -> all false - (torch.tensor([1, 0, 1, 0], dtype=torch.bool), torch.tensor([1, 1, 0, 0], dtype=torch.bool), torch.tensor([True, True, True, False])), # Mixed cases - ] - - def test_logical_or(self): - """Test tensor element-wise logical OR using torch.logical_or().""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected logical OR operation - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_logical_xor_test.py b/tests/pytorch_wrapper/pt_logical_xor_test.py deleted file mode 100644 index 7756330..0000000 --- a/tests/pytorch_wrapper/pt_logical_xor_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_logical_xor import PtLogicalXor # Updated import to PtLogicalXor - - -class TestPtLogicalXor(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtLogicalXor() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([True, False, True]), torch.tensor([False, False, True]), torch.tensor([True, False, False])), - (torch.tensor([[True, True], [False, False]]), torch.tensor([[False, True], [True, False]]), torch.tensor([[True, False], [True, False]])), - (torch.ones(3, 3, dtype=torch.bool), torch.zeros(3, 3, dtype=torch.bool), torch.ones(3, 3, dtype=torch.bool)), # True XOR False -> True - (torch.zeros(2, 2, dtype=torch.bool), torch.zeros(2, 2, dtype=torch.bool), torch.zeros(2, 2, dtype=torch.bool)), # False XOR False -> False - (torch.tensor([1, 0, 1, 0], dtype=torch.bool), torch.tensor([1, 1, 0, 0], dtype=torch.bool), torch.tensor([False, True, True, False])), # Mixed cases - ] - - def test_logical_xor(self): - """Test tensor element-wise logical XOR using torch.logical_xor().""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected logical XOR operation - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_lt_test.py b/tests/pytorch_wrapper/pt_lt_test.py deleted file mode 100644 index 027c478..0000000 --- a/tests/pytorch_wrapper/pt_lt_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_lt import PtLt # Updated import to PtLt - - -class TestPtLt(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtLt() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([1, 2, 3]), torch.tensor([0, 2, 4]), torch.tensor([False, False, True])), - (torch.tensor([3, 5, 7]), torch.tensor([1, 5, 10]), torch.tensor([False, False, True])), - (torch.ones(3, 3), torch.ones(3, 3), torch.full((3, 3), False, dtype=torch.bool)), - (torch.zeros(2, 2), torch.ones(2, 2), torch.full((2, 2), True, dtype=torch.bool)), - (torch.tensor([[5], [10]]), torch.tensor([[5], [10]]), torch.tensor([[False], [False]])), - ] - - def test_less_than(self): - """Test tensor element-wise less-than condition using torch.lt().""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected less-than comparison - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_masked_select_test.py b/tests/pytorch_wrapper/pt_masked_select_test.py deleted file mode 100644 index 7ff2303..0000000 --- a/tests/pytorch_wrapper/pt_masked_select_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_masked_select import PtMaskedSelect - - -class TestPtMaskedSelect(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtMaskedSelect() - - # Test cases: (Tensor, Masked Tensor, Expected result) - self.test_cases = [ - ( - torch.tensor([[1, 2, 3], [40, 50, 60], [700, 800, 900]]), - torch.tensor([[True, False, False], [False, True, False], [False, False, True]]), - torch.tensor([1, 50, 900]) - ), - ] - - def test_masked_select(self): - """Test torch.masked_select behavior.""" - for tens, masked_tens, expected in self.test_cases: - with self.subTest(tens=tens, masked_tens=masked_tens): - result, = self.node.f(tens, masked_tens) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected selected values - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_matmul_test.py b/tests/pytorch_wrapper/pt_matmul_test.py deleted file mode 100644 index d3f5245..0000000 --- a/tests/pytorch_wrapper/pt_matmul_test.py +++ /dev/null @@ -1,63 +0,0 @@ -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_matmul import PtMatMul # Updated import to PtMatMul - - -class TestPtMatMul(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtMatMul() - - # Valid test cases: (Tensor A, Tensor B, Expected result) - self.valid_test_cases = [ - # Case 1: 1D x 1D (dot product) - (torch.tensor([1.0, 2.0, 3.0]), torch.tensor([4.0, 5.0, 6.0]), torch.tensor(32.0)), - - # Case 2: 1D x 2D (vector-matrix multiplication) - (torch.tensor([1.0, 2.0]), torch.tensor([[3.0, 4.0], [5.0, 6.0]]), torch.tensor([13.0, 16.0])), - - # Case 3: 2D x 2D (matrix-matrix multiplication) - (torch.tensor([[1.0, 2.0], [3.0, 4.0]]), torch.tensor([[5.0, 6.0], [7.0, 8.0]]), torch.tensor([[19.0, 22.0], [43.0, 50.0]])), - - # Case 4: 3D x 2D (batched matrix-matrix multiplication) - (torch.ones(2, 3, 4), torch.ones(4, 5), torch.full((2, 3, 5), 4.0)), # (2,3,4) * (4,5) → (2,3,5) - - # Case 5: 3D x 3D (batched matrix multiplication with broadcasting) - (torch.ones(2, 3, 4), torch.ones(2, 4, 5), torch.full((2, 3, 5), 4.0)), # (2,3,4) * (2,4,5) → (2,3,5) - ] - - # Invalid test cases (should raise errors) - self.invalid_test_cases = [ - (torch.ones(2, 3), torch.ones(4, 2)), # (2,3) * (4,2) is invalid - (torch.tensor([1.0, 2.0, 3.0]), torch.tensor([4.0, 5.0])), # Vector size mismatch - (torch.ones(2, 3, 4), torch.ones(3, 5)), # (2,3,4) * (3,5) should fail due to misalignment - ] - - def test_matrix_multiplication(self): - """Test valid matrix multiplication cases.""" - for tens_a, tens_b, expected in self.valid_test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected matrix multiplication - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - - def test_invalid_multiplication(self): - """Test invalid cases where dimensions do not match.""" - for tens_a, tens_b in self.invalid_test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - with self.assertRaises(RuntimeError, msg=f"MatMul should fail for shapes {tens_a.shape} and {tens_b.shape}"): - self.node.f(tens_a, tens_b) - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_max_test.py b/tests/pytorch_wrapper/pt_max_test.py deleted file mode 100644 index d5d2a8a..0000000 --- a/tests/pytorch_wrapper/pt_max_test.py +++ /dev/null @@ -1,61 +0,0 @@ -import os -import sys -import unittest -import torch -import ast - -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_max import PtMax - - -class TestPtMax(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtMax() - - # Test cases: (Tensor, Dim (as string), keepdim, Expected result) - self.test_cases = [ - ( - torch.tensor([[1, 3, 2], [4, 0, 5]]), - "0", - False, - torch.tensor([4, 3, 5]) - ), - ( - torch.tensor([[1, 3, 2], [4, 0, 5]]), - "1", - True, - torch.tensor([[3], [5]]) - ), - ( - torch.tensor([[[1, 5], [2, 8]], [[3, 4], [6, 7]]]), - "(0, 1)", - False, - torch.tensor([6, 8]) - ), - ( - # Reduce across all axes - torch.tensor([[1, 3, 2], [4, 0, 5]]), - "", # Passing empty string to reduce across all dimensions - False, - torch.tensor(5) - ) - ] - - def test_max(self): - """Test torch.max behavior.""" - for tens, dim, keepdim, expected in self.test_cases: - with self.subTest(tens=tens, dim=dim, keepdim=keepdim): - result, = self.node.f(tens, dim, keepdim) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected max values - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_mean_test.py b/tests/pytorch_wrapper/pt_mean_test.py deleted file mode 100644 index 4f5f2c1..0000000 --- a/tests/pytorch_wrapper/pt_mean_test.py +++ /dev/null @@ -1,87 +0,0 @@ -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_mean import PtMean - - -class TestPtMean(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtMean() - - # Test cases: (Tensor, Dim (as string), keepdim, Expected result) - self.test_cases = [ - # Float tensor cases - ( - torch.tensor([[1.0, 3.0, 2.0], [4.0, 0.0, 5.0]], dtype=torch.float), - "0", - False, - torch.tensor([2.5, 1.5, 3.5], dtype=torch.float) - ), - ( - torch.tensor([[1.0, 3.0, 2.0], [4.0, 0.0, 5.0]], dtype=torch.float), - "1", - True, - torch.tensor([[2.], [3.]], dtype=torch.float) - ), - ( - torch.tensor([[[1.0, 5.0], [2.0, 8.0]], [[3.0, 4.0], [6.0, 7.0]]], dtype=torch.float), - "(0, 1)", - False, - torch.tensor([3.0, 6.0], dtype=torch.float) - ), - ( - # Reduce across all axes - torch.tensor([[1.0, 3.0, 2.0], [4.0, 0.0, 5.0]], dtype=torch.float), - "", # Passing empty string to reduce across all dimensions - False, - torch.tensor(2.5, dtype=torch.float) - ), - # Complex tensor cases - ( - torch.tensor([[1 + 1j, 3 + 3j, 2 + 2j], [4 + 4j, 0 + 0j, 5 + 5j]], dtype=torch.complex64), - "0", - False, - torch.tensor([2.5 + 2.5j, 1.5 + 1.5j, 3.5 + 3.5j], dtype=torch.complex64) - ), - ( - torch.tensor([[1 + 1j, 3 + 3j, 2 + 2j], [4 + 4j, 0 + 0j, 5 + 5j]], dtype=torch.complex64), - "1", - True, - torch.tensor([[2. + 2j], [3. + 3j]], dtype=torch.complex64) - ), - ( - torch.tensor([[[1 + 1j, 5 + 5j], [2 + 2j, 8 + 8j]], [[3 + 3j, 4 + 4j], [6 + 6j, 7 + 7j]]], dtype=torch.complex64), - "(0, 1)", - False, - torch.tensor([3. + 3j, 6. + 6j], dtype=torch.complex64) - ), - ( - # Reduce across all axes - torch.tensor([[1 + 1j, 3 + 3j, 2 + 2j], [4 + 4j, 0 + 0j, 5 + 5j]], dtype=torch.complex64), - "", # Passing empty string to reduce across all dimensions - False, - torch.tensor(2.5 + 2.5j, dtype=torch.complex64) - ) - ] - - def test_mean(self): - """Test torch.mean behavior.""" - for tens, dim, keepdim, expected in self.test_cases: - with self.subTest(tens=tens, dim=dim, keepdim=keepdim): - result, = self.node.f(tens, dim, keepdim) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected mean values - self.assertTrue(torch.allclose(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_median_test.py b/tests/pytorch_wrapper/pt_median_test.py deleted file mode 100644 index 15283c5..0000000 --- a/tests/pytorch_wrapper/pt_median_test.py +++ /dev/null @@ -1,66 +0,0 @@ -import os -import sys -import unittest -import torch -import ast - -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_median import PtMedian - - -class TestPtMedian(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtMedian() - - # Test cases: (Tensor, Dim (as string), keepdim, Expected result) - self.test_cases = [ - ( - torch.tensor([[1, 3, 2], [2, 1, 3], [4, 0, 5]]), - "0", - False, - torch.tensor([2, 1, 3]) - ), - ( - torch.tensor([[1, 3, 2], [4, 0, 5]]), - "0", - False, - torch.tensor([1, 0, 2]) # Fix: torch.median returns the lower value when between two numbers - ), - ( - torch.tensor([[1, 3, 2], [4, 0, 5]]), - "1", - True, - torch.tensor([[2], [4]]) - ), - ( - torch.tensor([[1, 3, 2], [4, 0, 5], [7, 8, 6], [9, 2, 10]]), - "0", - False, - torch.tensor([4, 2, 5]) - ), - ( - torch.tensor([[1, 3, 2, 8], [4, 0, 5, 6], [7, 8, 6, 9]]), - "1", - False, - torch.tensor([2, 4, 7]) - ) - ] - - def test_median(self): - """Test torch.median behavior.""" - for tens, dim, keepdim, expected in self.test_cases: - with self.subTest(tens=tens, dim=dim, keepdim=keepdim): - result, = self.node.f(tens, dim, keepdim) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected median values - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_min_test.py b/tests/pytorch_wrapper/pt_min_test.py deleted file mode 100644 index f70194f..0000000 --- a/tests/pytorch_wrapper/pt_min_test.py +++ /dev/null @@ -1,61 +0,0 @@ -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_min import PtMin - - -class TestPtMin(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtMin() - - # Test cases: (Tensor, Dim (as string), keepdim, Expected result) - self.test_cases = [ - ( - torch.tensor([[1, 3, 2], [4, 0, 5]]), - "0", - False, - torch.tensor([1, 0, 2]) - ), - ( - torch.tensor([[1, 3, 2], [4, 0, 5]]), - "1", - True, - torch.tensor([[1], [0]]) - ), - ( - torch.tensor([[[1, 5], [2, 8]], [[3, 4], [6, 7]]]), - "(0, 1)", - False, - torch.tensor([1, 4]) - ), - ( - # Reduce across all axes - torch.tensor([[1, 3, 2], [4, 0, 5]]), - "", # Passing empty string to reduce across all dimensions - False, - torch.tensor(0) - ) - ] - - def test_amin(self): - """Test torch.amin behavior.""" - for tens, dim, keepdim, expected in self.test_cases: - with self.subTest(tens=tens, dim=dim, keepdim=keepdim): - result, = self.node.f(tens, dim, keepdim) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected min values - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_mm_test.py b/tests/pytorch_wrapper/pt_mm_test.py deleted file mode 100644 index 33fb78f..0000000 --- a/tests/pytorch_wrapper/pt_mm_test.py +++ /dev/null @@ -1,58 +0,0 @@ -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_mm import PtMm # Updated import to PtMm - - -class TestPtMm(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtMm() - - # Valid test cases: (Tensor A, Tensor B, Expected result) - self.valid_test_cases = [ - # Case 1: 2D x 2D (matrix-matrix multiplication) - (torch.tensor([[1.0, 2.0], [3.0, 4.0]]), torch.tensor([[5.0, 6.0], [7.0, 8.0]]), torch.tensor([[19.0, 22.0], [43.0, 50.0]])), - - # Case 2: 2D identity matrix multiplication (should remain the same) - (torch.eye(3), torch.eye(3), torch.eye(3)), - - # Case 3: Rectangular 2D matrix multiplication - (torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), torch.tensor([[7.0, 8.0], [9.0, 10.0], [11.0, 12.0]]), torch.tensor([[58.0, 64.0], [139.0, 154.0]])), - ] - - # Invalid test cases (should raise errors) - self.invalid_test_cases = [ - (torch.tensor([1.0, 2.0, 3.0]), torch.tensor([4.0, 5.0, 6.0])), # 1D x 1D (dot product not supported by mm) - (torch.tensor([[1.0, 2.0]]), torch.tensor([3.0, 4.0])), # 2D x 1D mismatch - (torch.ones(2, 3, 4), torch.ones(4, 5)), # 3D x 2D is invalid for mm - (torch.ones(2, 3, 4), torch.ones(2, 4, 5)), # 3D x 3D is invalid for mm - ] - - def test_matrix_multiplication(self): - """Test valid matrix multiplication cases.""" - for tens_a, tens_b, expected in self.valid_test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected matrix multiplication - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - - def test_invalid_multiplication(self): - """Test invalid cases where dimensions do not match.""" - for tens_a, tens_b in self.invalid_test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - with self.assertRaises(ValueError, msg=f"torch.mm() should fail for shapes {tens_a.shape} and {tens_b.shape}"): - self.node.f(tens_a, tens_b) - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_mul_test.py b/tests/pytorch_wrapper/pt_mul_test.py deleted file mode 100644 index 8bff3f9..0000000 --- a/tests/pytorch_wrapper/pt_mul_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_mul import PtMul # Updated import to PtMul - - -class TestPtMul(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtMul() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([1.0, 2.0, 3.0]), torch.tensor([4.0, 5.0, 6.0]), torch.tensor([4.0, 10.0, 18.0])), - (torch.ones(3, 3), torch.ones(3, 3), torch.ones(3, 3)), # 1 * 1 = 1 - (torch.zeros(2, 2), torch.ones(2, 2), torch.zeros(2, 2)), # 0 * 1 = 0 - (torch.tensor([[2.0], [3.0]]), torch.tensor([[3.0], [4.0]]), torch.tensor([[6.0], [12.0]])), # Element-wise multiplication - ] - - def test_multiplication(self): - """Test tensor multiplication.""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected product - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_ne_test.py b/tests/pytorch_wrapper/pt_ne_test.py deleted file mode 100644 index 4816b21..0000000 --- a/tests/pytorch_wrapper/pt_ne_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_ne import PtNe # Updated import to PtNe - - -class TestPtNe(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtNe() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([1, 2, 3]), torch.tensor([1, 2, 3]), torch.tensor([False, False, False])), - (torch.tensor([1, 2, 3]), torch.tensor([3, 2, 1]), torch.tensor([True, False, True])), - (torch.ones(3, 3), torch.ones(3, 3), torch.full((3, 3), False, dtype=torch.bool)), - (torch.zeros(2, 2), torch.ones(2, 2), torch.full((2, 2), True, dtype=torch.bool)), - (torch.tensor([[1], [2]]), torch.tensor([[1], [3]]), torch.tensor([[False], [True]])), - ] - - def test_inequality(self): - """Test tensor element-wise inequality using torch.ne().""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected element-wise inequality - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_neg_test.py b/tests/pytorch_wrapper/pt_neg_test.py deleted file mode 100644 index 57c66e1..0000000 --- a/tests/pytorch_wrapper/pt_neg_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_neg import PtNeg # Updated import to PtNeg - - -class TestPtNeg(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtNeg() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([1, -2, 3]), torch.tensor([-1, 2, -3])), - (torch.tensor([0, 5, -7]), torch.tensor([0, -5, 7])), - (torch.ones(3, 3), torch.full((3, 3), -1.0)), # Negation of ones - (torch.zeros(2, 2), torch.zeros(2, 2)), # Negation of zeros - (torch.tensor([[5], [-10]]), torch.tensor([[-5], [10]])), # Column vector case - ] - - def test_neg(self): - """Test tensor element-wise negation using torch.neg().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected negation - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_ones_test.py b/tests/pytorch_wrapper/pt_ones_test.py deleted file mode 100644 index fc893af..0000000 --- a/tests/pytorch_wrapper/pt_ones_test.py +++ /dev/null @@ -1,44 +0,0 @@ -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_ones import PtOnes # Importing the PtOnes class -from pytorch_wrapper.utils import DTYPE_MAPPING # Importing the dtype mapping - - -class TestPtOnes(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtOnes() - - # Tensor size to test - self.size_str = "[2, 3]" # Using a small 2D tensor size for all tests - self.expected_shape = (2, 3) - - def test_ones_tensor_all_dtypes(self): - """Test tensor creation with ones for all supported data types.""" - for data_type, torch_dtype in DTYPE_MAPPING.items(): - with self.subTest(data_type=data_type): - result, = self.node.f(self.size_str, data_type) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result has correct shape - self.assertEqual(result.shape, torch.Size(self.expected_shape), f"Expected shape {self.expected_shape}, got {result.shape}") - - # Ensure result has correct dtype - self.assertEqual(result.dtype, torch_dtype, f"Expected dtype {torch_dtype}, got {result.dtype}") - - # Ensure all elements are ones - expected = torch.ones(self.expected_shape, dtype=torch_dtype) - self.assertTrue(torch.equal(result, expected), f"Tensor values do not match expected ones tensor for dtype {data_type}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_pad_test.py b/tests/pytorch_wrapper/pt_pad_test.py deleted file mode 100644 index 5198658..0000000 --- a/tests/pytorch_wrapper/pt_pad_test.py +++ /dev/null @@ -1,50 +0,0 @@ -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_pad import PtPad - - -class TestPtPad(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtPad() - - # Test cases: (Input tensor, Height, Width, Expected shape) - self.test_cases = [ - (torch.rand(1, 3, 16, 16), 32, 32, (1, 3, 32, 32)), # Pad 4D tensor - (torch.rand(3, 16, 16), 32, 32, (3, 32, 32)), # Pad 3D tensor - (torch.rand(1, 3, 16, 16), 16, 16, (1, 3, 16, 16)), # No padding, same size - ] - - def test_padding(self): - """Test tensor padding to specific sizes.""" - for tens, height, width, expected_shape in self.test_cases: - with self.subTest(height=height, width=width): - result, = self.node.f(tens, height, width) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result shape matches expected shape - self.assertEqual(result.shape, expected_shape, f"Expected shape {expected_shape}, got {result.shape}") - - def test_invalid_tensor_rank(self): - """Test that an invalid tensor rank raises an error.""" - tens = torch.rand(16, 16) # 2D tensor (invalid) - with self.assertRaises(ValueError, msg="Only rank 3 or 4 tensors are supported for padding."): - self.node.f(tens, 32, 32) - - def test_padding_smaller_than_tensor(self): - """Test that padding to a smaller size raises an error.""" - tens = torch.rand(1, 3, 16, 16) - with self.assertRaises(ValueError, msg="Specified dimensions must be greater than or equal to tensor dimensions."): - self.node.f(tens, 8, 8) - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_permute_test.py b/tests/pytorch_wrapper/pt_permute_test.py deleted file mode 100644 index 04a8e74..0000000 --- a/tests/pytorch_wrapper/pt_permute_test.py +++ /dev/null @@ -1,59 +0,0 @@ -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_permute import PtPermute - - -class TestPtPermute(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtPermute() - - # Define test cases: (input_tensor, new_axes, expected_shape) - self.test_cases = [ - # Case 1: Basic 2D permutation - (torch.ones(2, 3), "[1, 0]", (3, 2)), # (2,3) -> (3,2) - - # Case 2: Permute dimensions of a 3D tensor - (torch.ones(2, 3, 4), "[2, 0, 1]", (4, 2, 3)), # (2,3,4) -> (4,2,3) - - # Case 3: Identity permutation (no change) - (torch.ones(2, 3, 4), "[0, 1, 2]", (2, 3, 4)), # (2,3,4) -> (2,3,4) - - # Case 4: Permute 4D tensor (like a channel-first transformation) - (torch.ones(2, 3, 96, 32), "[0, 3, 1, 2]", (2, 32, 3, 96)), # (2,3,96,32) -> (2,32,3,96) - - # Case 5: Swapping first and last dimensions - (torch.ones(5, 10, 15), "[2, 1, 0]", (15, 10, 5)), # (5,10,15) -> (15,10,5) - - # Case 6: Edge case with a single-dimension tensor - (torch.ones(10,), "[0]", (10,)), # (10,) -> (10,) (No change) - - # Case 7: Larger 5D tensor permutation - (torch.ones(2, 3, 4, 5, 6), "[4, 3, 2, 1, 0]", (6, 5, 4, 3, 2)), # Reverse all dimensions - ] - - def test_permute(self): - """Test tensor permutation operation with various dimension orders.""" - for input_tensor, new_axes, expected_shape in self.test_cases: - with self.subTest(input_shape=input_tensor.shape, new_axes=new_axes): - result, = self.node.f(input_tensor, new_axes) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result has correct shape - self.assertEqual(result.shape, torch.Size(expected_shape), f"Expected shape {expected_shape}, got {result.shape}") - - # Ensure result has the same dtype - self.assertEqual(result.dtype, input_tensor.dtype, "Dtype mismatch after permute operation") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_pow_test.py b/tests/pytorch_wrapper/pt_pow_test.py deleted file mode 100644 index b6bde5f..0000000 --- a/tests/pytorch_wrapper/pt_pow_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_pow import PtPow - - -class TestPtPow(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtPow() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([2.0, 3.0, 4.0]), torch.tensor([3.0, 2.0, 1.0]), torch.tensor([8.0, 9.0, 4.0])), # 2^3, 3^2, 4^1 - (torch.ones(3, 3) * 4, torch.ones(3, 3) * 2, torch.ones(3, 3) * 16), # 4^2 = 16 - (torch.full((2, 2), 5.0), torch.full((2, 2), 3.0), torch.full((2, 2), 125.0)), # 5^3 = 125 - (torch.tensor([[2.0], [3.0]]), torch.tensor([[4.0], [5.0]]), torch.tensor([[16.0], [243.0]])), # 2^4 = 16, 3^5 = 243 - ] - - def test_power(self): - """Test tensor exponentiation.""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected power computation - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_predict_classification_conv_model_test.py b/tests/pytorch_wrapper/pt_predict_classification_conv_model_test.py deleted file mode 100644 index c99eda4..0000000 --- a/tests/pytorch_wrapper/pt_predict_classification_conv_model_test.py +++ /dev/null @@ -1,68 +0,0 @@ -import os -import sys -import unittest -import torch -import torch.nn as nn - -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.ptn_conv_model import PtnConvModel -from pytorch_wrapper.ptv_image_folder_dataset import PtvImageFolderDataset -from pytorch_wrapper.ptv_transforms_resize import PtvTransformsResize -from pytorch_wrapper.pt_data_loader import PtDataLoader -from pytorch_wrapper.pt_predict_classification_model import PtPredictClassificationModel -from pytorch_wrapper.pt_load_model import PtLoadModel - -class TestPtPrediction(unittest.TestCase): - - def setUp(self): - """Set up test instance.""" - # Transform - compose_node = PtvTransformsResize() - transform = compose_node.f(256, 256)[0] - - # Dataset - self.node = PtvImageFolderDataset() - self.dataset = self.node.f("dog_and_cat/val", - transform=transform)[0] - - - # Model - self.model_node = PtnConvModel() - self.model = self.model_node.f( - input_dim="(3, 256, 256)", - penultimate_dim=0, - output_dim=2, - 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]" - )[0] - - # Load weights - epochs = 20 - self.load_model = PtLoadModel() - self.loaded_model = self.load_model.f(self.model,f"dog_cat_{epochs}_epochs_conv.pt") - - # Instantiates evaluation node - self.predict = PtPredictClassificationModel() - - def test_prediction(self): - """ - Tests prediction. - - Note that this test requires a trained model file on the file system. - Unit test will fail if you run this for the first time. - Run the unit test for training to generate the weight, and re-run this unit test. - """ - inputs, y = next(iter(self.dataset)) - class_id_to_name_map = {0:"cat", 1:"dog"} - class_name, class_id, prob = \ - self.predict.f(self.model, inputs, class_id_to_name_map, use_gpu=True) - self.assertTrue(prob > 0.8) - self.assertEqual(class_id, 0) - self.assertEqual(class_name, "cat") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_prod_test.py b/tests/pytorch_wrapper/pt_prod_test.py deleted file mode 100644 index 29fd1ec..0000000 --- a/tests/pytorch_wrapper/pt_prod_test.py +++ /dev/null @@ -1,59 +0,0 @@ -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_prod import PtProd - - -class TestPtProd(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtProd() - - # Test cases: (Tensor, Dim (as string), keepdim, Expected result) - self.test_cases = [ - ( - torch.tensor([[1, 3, 2], [2, 1, 3]]), - "0", - False, - torch.tensor([2, 3, 6]) - ), - ( - torch.tensor([[1, 3, 2], [4, 0, 5]]), - "1", - True, - torch.tensor([[6], [0]]) - ), - ( - torch.tensor([[1, 3, 2], [4, 5, 6]]), - "0", - False, - torch.tensor([4, 15, 12]) - ), - ( - torch.tensor([[1, 3, 2], [4, 5, 6], [7, 8, 9]]), - "1", - False, - torch.tensor([6, 120, 504]) - ) - ] - - def test_prod(self): - """Test torch.prod behavior.""" - for tens, dim, keepdim, expected in self.test_cases: - with self.subTest(tens=tens, dim=dim, keepdim=keepdim): - result, = self.node.f(tens, dim, keepdim) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected product values - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_rand_int_test.py b/tests/pytorch_wrapper/pt_rand_int_test.py deleted file mode 100644 index 57c66e1..0000000 --- a/tests/pytorch_wrapper/pt_rand_int_test.py +++ /dev/null @@ -1,40 +0,0 @@ -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_neg import PtNeg # Updated import to PtNeg - - -class TestPtNeg(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtNeg() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([1, -2, 3]), torch.tensor([-1, 2, -3])), - (torch.tensor([0, 5, -7]), torch.tensor([0, -5, 7])), - (torch.ones(3, 3), torch.full((3, 3), -1.0)), # Negation of ones - (torch.zeros(2, 2), torch.zeros(2, 2)), # Negation of zeros - (torch.tensor([[5], [-10]]), torch.tensor([[-5], [10]])), # Column vector case - ] - - def test_neg(self): - """Test tensor element-wise negation using torch.neg().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected negation - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_rand_test.py b/tests/pytorch_wrapper/pt_rand_test.py deleted file mode 100644 index c3ef56c..0000000 --- a/tests/pytorch_wrapper/pt_rand_test.py +++ /dev/null @@ -1,46 +0,0 @@ -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_rand import PtRand # Importing the PtRand class -from pytorch_wrapper.utils import DTYPE_MAPPING # Importing the dtype mapping - - -class TestPtRand(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtRand() - - # Tensor size to test - self.size_str = "[2, 3]" # Using a small 2D tensor size for all tests - self.expected_shape = (2, 3) - - # Only floating-point types are valid for torch.rand() - self.valid_dtypes = {k: v for k, v in DTYPE_MAPPING.items() if v in {torch.float32, torch.float16, torch.bfloat16, torch.float64}} - - def test_rand_tensor_all_floating_dtypes(self): - """Test random tensor creation for all supported floating-point data types.""" - for data_type, torch_dtype in self.valid_dtypes.items(): - with self.subTest(data_type=data_type): - result, = self.node.f(self.size_str, data_type) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result has correct shape - self.assertEqual(result.shape, torch.Size(self.expected_shape), f"Expected shape {self.expected_shape}, got {result.shape}") - - # Ensure result has correct dtype - self.assertEqual(result.dtype, torch_dtype, f"Expected dtype {torch_dtype}, got {result.dtype}") - - # Ensure all elements are within range [0,1) - self.assertTrue(torch.all(result >= 0) and torch.all(result < 1), f"Tensor values are not in the range [0,1) for dtype {data_type}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_randn_test.py b/tests/pytorch_wrapper/pt_randn_test.py deleted file mode 100644 index 9022c1d..0000000 --- a/tests/pytorch_wrapper/pt_randn_test.py +++ /dev/null @@ -1,61 +0,0 @@ -import os -import sys -import unittest -import torch -import ast - - -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_rand_int import PtRandInt # Importing the PtRandInt class -from pytorch_wrapper.utils import DTYPE_MAPPING # Importing the dtype mapping - - -class TestPtRandInt(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtRandInt() - - # Tensor size to test - self.size_str = "[2, 3]" # Using a small 2D tensor size for all tests - self.expected_shape = (2, 3) - - # Only integer types are valid for torch.randint() - self.valid_dtypes = { - k: v for k, v in DTYPE_MAPPING.items() if v in {torch.int8, torch.int16, torch.int32, torch.int64, torch.uint8} - } - - # Define min and max values for each integer type - self.test_ranges = { - "int8": (-128, 127), - "int16": (-32768, 32767), - "int32": (-2147483648, 2147483647), - "int64": (-9223372036854775808, 9223372036854775807), - "uint8": (0, 255), - } - - def test_randint_tensor_all_integer_dtypes(self): - """Test random integer tensor creation for all supported integer data types.""" - for data_type, torch_dtype in self.valid_dtypes.items(): - min_value, max_value = self.test_ranges[data_type] - - with self.subTest(data_type=data_type, min_value=min_value, max_value=max_value): - result, = self.node.f(min_value, max_value, self.size_str, data_type) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result has correct shape - self.assertEqual(result.shape, torch.Size(self.expected_shape), f"Expected shape {self.expected_shape}, got {result.shape}") - - # Ensure result has correct dtype - self.assertEqual(result.dtype, torch_dtype, f"Expected dtype {torch_dtype}, got {result.dtype}") - - # Ensure all elements are within range [min_value, max_value) - self.assertTrue(torch.all(result >= min_value), f"Tensor contains values less than min_value for dtype {data_type}") - self.assertTrue(torch.all(result < max_value), f"Tensor contains values greater than or equal to max_value for dtype {data_type}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_remainder_test.py b/tests/pytorch_wrapper/pt_remainder_test.py deleted file mode 100644 index fbd17d0..0000000 --- a/tests/pytorch_wrapper/pt_remainder_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_remainder import PtRemainder - - -class TestPtRemainder(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtRemainder() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([4.0, 10.0, 18.0]), torch.tensor([3.0, 4.0, 5.0]), torch.tensor([1.0, 2.0, 3.0])), - (torch.ones(3, 3) * 7, torch.ones(3, 3) * 3, torch.ones(3, 3) * 1), # 7 % 3 = 1 - (torch.full((2, 2), 9.0), torch.full((2, 2), 2.0), torch.full((2, 2), 1.0)), # 9 % 2 = 1 - (torch.tensor([[7.0], [15.0]]), torch.tensor([[2.0], [4.0]]), torch.tensor([[1.0], [3.0]])), # Element-wise remainder - ] - - def test_remainder(self): - """Test tensor remainder computation.""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected remainder - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_reshape_test.py b/tests/pytorch_wrapper/pt_reshape_test.py deleted file mode 100644 index 5cbcdf0..0000000 --- a/tests/pytorch_wrapper/pt_reshape_test.py +++ /dev/null @@ -1,60 +0,0 @@ -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_reshape import PtReshape # Importing the PtReshape class - - -class TestPtReshape(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtReshape() - - # Define test cases: (input_tensor, new_shape, expected_shape) - self.test_cases = [ - # Case 1: Reshape a 2D tensor - (torch.ones(2, 3), "[3, 2]", (3, 2)), # (2,3) -> (3,2) - - # Case 2: Reshape a 3D tensor - (torch.ones(2, 3, 4), "[6, 4]", (6, 4)), # (2,3,4) -> (6,4) - - # Case 3: Using -1 for automatic inference - (torch.ones(2, 3, 4), "[2, -1]", (2, 12)), # (2,3,4) -> (2,12) - (torch.ones(6, 4), "[-1, 4]", (6, 4)), # (6,4) -> (6,4) - - # Case 4: Flattening a multi-dimensional tensor - (torch.ones(2, 3, 4), "[-1]", (24,)), # (2,3,4) -> (24,) - - # Case 5: Expanding dimensions - (torch.ones(6,), "[2, 3]", (2, 3)), # (6,) -> (2,3) - - # Case 6: Reshaping a 4D tensor - (torch.ones(2, 3, 4, 5), "[6, -1, 5]", (6, 4, 5)), # (2,3,4,5) -> (6,4,5) - - # Case 7: Edge case with 1D tensor - (torch.ones(10,), "[10, 1]", (10, 1)), # (10,) -> (10,1) - ] - - def test_reshape(self): - """Test tensor reshape operation with various shape orders.""" - for input_tensor, new_shape, expected_shape in self.test_cases: - with self.subTest(input_shape=input_tensor.shape, new_shape=new_shape): - result, = self.node.f(input_tensor, new_shape) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result has correct shape - self.assertEqual(result.shape, torch.Size(expected_shape), f"Expected shape {expected_shape}, got {result.shape}") - - # Ensure result has the same dtype - self.assertEqual(result.dtype, input_tensor.dtype, "Dtype mismatch after reshape operation") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_scatter_test.py b/tests/pytorch_wrapper/pt_scatter_test.py deleted file mode 100644 index 8aa2e15..0000000 --- a/tests/pytorch_wrapper/pt_scatter_test.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -import sys -import unittest -import torch -import ast - -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_scatter import PtScatter - - -class TestPtScatter(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtScatter() - - # Test cases: (Target Tensor, Dim, Index Tensor (as string), Source Tensor (as string), Expected Output) - self.test_cases = [ - ( - torch.tensor([ - [10, 11, 12], - [21, 22, 23], - [31, 32, 33], - ]), - 0, - "[[0, 1, 0], [1, 1, 2]]", - "[[2, 4, 6], [4, 4, 2]]", - torch.tensor([ - [2, 11, 6], - [4, 4, 23], - [31, 32, 2], - ]) - ) - ] - - def test_scatter(self): - """Test torch.scatter behavior.""" - for tens, dim, index, src, expected in self.test_cases: - with self.subTest(tens=tens, dim=dim, index=index, src=src): - result, = self.node.f(tens, dim, index, src) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected values - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_sin_test.py b/tests/pytorch_wrapper/pt_sin_test.py deleted file mode 100644 index 25d0b3f..0000000 --- a/tests/pytorch_wrapper/pt_sin_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_sin import PtSin # Updated import to PtSin - - -class TestPtSin(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtSin() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([0.0, torch.pi / 2, torch.pi], dtype=torch.float32), torch.sin(torch.tensor([0.0, torch.pi / 2, torch.pi], dtype=torch.float32))), # Edge cases - (torch.tensor([0.5, -0.5], dtype=torch.float32), torch.sin(torch.tensor([0.5, -0.5], dtype=torch.float32))), # Common values - (torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32), torch.sin(torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32))), # 2D case - (torch.full((2, 2), 0.6, dtype=torch.float32), torch.sin(torch.full((2, 2), 0.6, dtype=torch.float32))), # Constant matrix - ] - - def test_sin(self): - """Test tensor element-wise sine using torch.sin().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected sin values - self.assertTrue(torch.allclose(result, expected, atol=1e-6), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_sinh_test.py b/tests/pytorch_wrapper/pt_sinh_test.py deleted file mode 100644 index 9e9a360..0000000 --- a/tests/pytorch_wrapper/pt_sinh_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_sinh import PtSinh # Updated import to PtSinh - - -class TestPtSinh(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtSinh() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([0.0, 1.0, -1.0], dtype=torch.float32), torch.sinh(torch.tensor([0.0, 1.0, -1.0], dtype=torch.float32))), # Edge cases - (torch.tensor([0.5, -0.5], dtype=torch.float32), torch.sinh(torch.tensor([0.5, -0.5], dtype=torch.float32))), # Common values - (torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32), torch.sinh(torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32))), # 2D case - (torch.full((2, 2), 0.6, dtype=torch.float32), torch.sinh(torch.full((2, 2), 0.6, dtype=torch.float32))), # Constant matrix - ] - - def test_sinh(self): - """Test tensor element-wise hyperbolic sine using torch.sinh().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected sinh values - self.assertTrue(torch.allclose(result, expected, atol=1e-6), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_size_create_test.py b/tests/pytorch_wrapper/pt_size_create_test.py deleted file mode 100644 index a229ac8..0000000 --- a/tests/pytorch_wrapper/pt_size_create_test.py +++ /dev/null @@ -1,46 +0,0 @@ -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() diff --git a/tests/pytorch_wrapper/pt_size_test.py b/tests/pytorch_wrapper/pt_size_test.py deleted file mode 100644 index c268f48..0000000 --- a/tests/pytorch_wrapper/pt_size_test.py +++ /dev/null @@ -1,44 +0,0 @@ -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() diff --git a/tests/pytorch_wrapper/pt_size_to_numpy_test.py b/tests/pytorch_wrapper/pt_size_to_numpy_test.py deleted file mode 100644 index feca5ef..0000000 --- a/tests/pytorch_wrapper/pt_size_to_numpy_test.py +++ /dev/null @@ -1,55 +0,0 @@ -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() diff --git a/tests/pytorch_wrapper/pt_size_to_string_test.py b/tests/pytorch_wrapper/pt_size_to_string_test.py deleted file mode 100644 index 69f9b54..0000000 --- a/tests/pytorch_wrapper/pt_size_to_string_test.py +++ /dev/null @@ -1,46 +0,0 @@ -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() diff --git a/tests/pytorch_wrapper/pt_sqrt_test.py b/tests/pytorch_wrapper/pt_sqrt_test.py deleted file mode 100644 index 1254d07..0000000 --- a/tests/pytorch_wrapper/pt_sqrt_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_sqrt import PtSqrt # Updated import to PtSqrt - - -class TestPtSqrt(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtSqrt() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([1.0, 4.0, 9.0], dtype=torch.float32), torch.sqrt(torch.tensor([1.0, 4.0, 9.0], dtype=torch.float32))), # Perfect squares - (torch.tensor([0.5, 2.0, 3.0], dtype=torch.float32), torch.sqrt(torch.tensor([0.5, 2.0, 3.0], dtype=torch.float32))), # Common values - (torch.tensor([[1.5, 3.2], [4.7, 5.9]], dtype=torch.float32), torch.sqrt(torch.tensor([[1.5, 3.2], [4.7, 5.9]], dtype=torch.float32))), # 2D case - (torch.full((2, 2), 2.5, dtype=torch.float32), torch.sqrt(torch.full((2, 2), 2.5, dtype=torch.float32))), # Constant matrix - ] - - def test_sqrt(self): - """Test tensor element-wise square root using torch.sqrt().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected sqrt values - self.assertTrue(torch.allclose(result, expected, atol=1e-6), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_squeeze_test.py b/tests/pytorch_wrapper/pt_squeeze_test.py deleted file mode 100644 index a3d79f8..0000000 --- a/tests/pytorch_wrapper/pt_squeeze_test.py +++ /dev/null @@ -1,57 +0,0 @@ -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_squeeze import PtSqueeze # Importing the PtSqueeze class - - -class TestPtSqueeze(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtSqueeze() - - # Define test cases: (input_tensor, dim, expected_shape) - self.test_cases = [ - # Case 1: Squeezing a singleton dimension - (torch.ones(1, 3, 4), 0, (3, 4)), # (1,3,4) -> (3,4) - (torch.ones(2, 1, 4), 1, (2, 4)), # (2,1,4) -> (2,4) - - # Case 2: No change if dim is not a singleton - (torch.ones(2, 3, 4), 1, (2, 3, 4)), # (2,3,4) -> (2,3,4) - - # Case 3: Multiple singleton dimensions, squeezing one - (torch.ones(1, 2, 1, 4), 2, (1, 2, 4)), # (1,2,1,4) -> (1,2,4) - - # Case 4: Squeezing a negative index - (torch.ones(2, 3, 1), -1, (2, 3)), # (2,3,1) -> (2,3) - - # Case 5: Squeezing all size 1 dimensions - (torch.ones(1, 1, 1), 0, (1, 1)), # (1,1,1) -> (1,1) - (torch.ones(1, 1, 1), 1, (1, 1)), # (1,1,1) -> (1,1) - (torch.ones(1, 1, 1), 2, (1, 1)), # (1,1,1) -> (1,1) - ] - - def test_squeeze(self): - """Test tensor squeeze operation on various dimensions.""" - for input_tensor, dim, expected_shape in self.test_cases: - with self.subTest(input_shape=input_tensor.shape, dim=dim): - result, = self.node.f(input_tensor, dim) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result has correct shape - self.assertEqual(result.shape, torch.Size(expected_shape), f"Expected shape {expected_shape}, got {result.shape}") - - # Ensure result has the same dtype - self.assertEqual(result.dtype, input_tensor.dtype, "Dtype mismatch after squeeze operation") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_std_test.py b/tests/pytorch_wrapper/pt_std_test.py deleted file mode 100644 index 3e46908..0000000 --- a/tests/pytorch_wrapper/pt_std_test.py +++ /dev/null @@ -1,84 +0,0 @@ -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_std import PtStd - - -class TestPtStd(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtStd() - - # Define test cases with string-based dimensions - self.test_cases = [ - # Sample standard deviation (Bessel's correction) along dim=0 - ( - torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), - "0", - 1, - False, - torch.std(torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), dim=0, correction=1, keepdim=False) - ), - # Sample standard deviation along dim=1, with keepdim=True - ( - torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), - "1", - 1, - True, - torch.std(torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), dim=1, correction=1, keepdim=True) - ), - # Sample standard deviation across both axes (dim=(0,1)) - ( - torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), - "(0, 1)", - 1, - False, - torch.std(torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), dim=(0, 1), correction=1, keepdim=False) - ), - # Sample standard deviation across all elements (dim=None) - Entire tensor - ( - torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), - "", - 1, - False, - torch.std(torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), correction=1, keepdim=False) - ), - # Population standard deviation along dim=0 (correction=0) - ( - torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), - "0", - 0, - False, - torch.std(torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), dim=0, correction=0, keepdim=False) - ), - # Population standard deviation along dim=1 with keepdim=True (correction=0) - ( - torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), - "1", - 0, - True, - torch.std(torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), dim=1, correction=0, keepdim=True) - ) - ] - - def test_std(self): - """Test torch.std behavior.""" - for tens, dim, correction, keepdim, expected in self.test_cases: - with self.subTest(tens=tens, dim=dim, correction=correction, keepdim=keepdim): - result, = self.node.f(tens, dim, correction, keepdim) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected standard deviation values - self.assertTrue(torch.allclose(result, expected, atol=1e-6), f"Expected {expected}, got {result}") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_sub_test.py b/tests/pytorch_wrapper/pt_sub_test.py deleted file mode 100644 index 4894c0b..0000000 --- a/tests/pytorch_wrapper/pt_sub_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_sub import PtSub - - -class TestPtSub(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtSub() - - # Test cases: (Tensor A, Tensor B, Expected result) - self.test_cases = [ - (torch.tensor([5.0, 7.0, 9.0]), torch.tensor([4.0, 5.0, 6.0]), torch.tensor([1.0, 2.0, 3.0])), - (torch.full((3, 3), 2.0), torch.ones(3, 3), torch.ones(3, 3)), - (torch.ones(2, 2), torch.ones(2, 2), torch.zeros(2, 2)), - (torch.tensor([[4.0], [6.0]]), torch.tensor([[3.0], [4.0]]), torch.tensor([[1.0], [2.0]])), - ] - - def test_subtraction(self): - """Test tensor subtraction.""" - for tens_a, tens_b, expected in self.test_cases: - with self.subTest(tens_a=tens_a, tens_b=tens_b): - result, = self.node.f(tens_a, tens_b) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected difference - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_sum_test.py b/tests/pytorch_wrapper/pt_sum_test.py deleted file mode 100644 index dd528ec..0000000 --- a/tests/pytorch_wrapper/pt_sum_test.py +++ /dev/null @@ -1,71 +0,0 @@ -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_sum import PtSum - - -class TestPtSum(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtSum() - - # Test cases: (Tensor, Dim (as string), keepdim, Expected result) - self.test_cases = [ - ( - torch.tensor([[1, 3, 2], [2, 1, 3]]), - "0", - False, - torch.tensor([3, 4, 5]) - ), - ( - torch.tensor([[1, 3, 2], [4, 0, 5]]), - "1", - True, - torch.tensor([[6], [9]]) - ), - ( - torch.tensor([[1, 3, 2], [4, 5, 6]]), - "0", - False, - torch.tensor([5, 8, 8]) - ), - ( - torch.tensor([[1, 3, 2], [4, 5, 6], [7, 8, 9]]), - "1", - False, - torch.tensor([6, 15, 24]) - ), - ( - torch.tensor([[1, 2], [3, 4]]), - "", - False, - torch.tensor(10) # Sum of all elements - ), - ( - torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]), - "(0, 1)", - False, - torch.tensor([16, 20]) # Sum over dimensions (0, 1) - ) - ] - - def test_sum(self): - """Test torch.sum behavior.""" - for tens, dim, keepdim, expected in self.test_cases: - with self.subTest(tens=tens, dim=dim, keepdim=keepdim): - result, = self.node.f(tens, dim, keepdim) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected sum values - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_tan_test.py b/tests/pytorch_wrapper/pt_tan_test.py deleted file mode 100644 index 1346872..0000000 --- a/tests/pytorch_wrapper/pt_tan_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_tan import PtTan # Updated import to PtTan - - -class TestPtTan(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtTan() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([0.0, torch.pi / 4, -torch.pi / 4], dtype=torch.float32), torch.tan(torch.tensor([0.0, torch.pi / 4, -torch.pi / 4], dtype=torch.float32))), # Common angles - (torch.tensor([0.5, -0.5], dtype=torch.float32), torch.tan(torch.tensor([0.5, -0.5], dtype=torch.float32))), # General values - (torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32), torch.tan(torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32))), # 2D case - (torch.full((2, 2), 0.6, dtype=torch.float32), torch.tan(torch.full((2, 2), 0.6, dtype=torch.float32))), # Constant matrix - ] - - def test_tan(self): - """Test tensor element-wise tangent using torch.tan().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected tan values - self.assertTrue(torch.allclose(result, expected, atol=1e-6), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_tanh_test.py b/tests/pytorch_wrapper/pt_tanh_test.py deleted file mode 100644 index e2f0fc8..0000000 --- a/tests/pytorch_wrapper/pt_tanh_test.py +++ /dev/null @@ -1,39 +0,0 @@ -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_tanh import PtTanh # Updated import to PtTanh - - -class TestPtTanh(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtTanh() - - # Test cases: (Tensor A, Expected result) - self.test_cases = [ - (torch.tensor([0.0, 1.0, -1.0], dtype=torch.float32), torch.tanh(torch.tensor([0.0, 1.0, -1.0], dtype=torch.float32))), # Common values - (torch.tensor([0.5, -0.5], dtype=torch.float32), torch.tanh(torch.tensor([0.5, -0.5], dtype=torch.float32))), # General cases - (torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32), torch.tanh(torch.tensor([[0.1, -0.3], [0.7, -0.9]], dtype=torch.float32))), # 2D case - (torch.full((2, 2), 0.6, dtype=torch.float32), torch.tanh(torch.full((2, 2), 0.6, dtype=torch.float32))), # Constant matrix - ] - - def test_tanh(self): - """Test tensor element-wise hyperbolic tangent using torch.tanh().""" - for tens_a, expected in self.test_cases: - with self.subTest(tens_a=tens_a): - result, = self.node.f(tens_a) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected tanh values - self.assertTrue(torch.allclose(result, expected, atol=1e-6), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_to_image_transpose_test.py b/tests/pytorch_wrapper/pt_to_image_transpose_test.py deleted file mode 100644 index f6ad4f3..0000000 --- a/tests/pytorch_wrapper/pt_to_image_transpose_test.py +++ /dev/null @@ -1,54 +0,0 @@ -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_image_transpose import PtToImageTranspose - - -class TestPtToImageTranspose(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtToImageTranspose() - - # Define test cases: (input_tensor, expected_shape) - self.test_cases = [ - # Case 1: 3D tensor (C, H, W) -> (H, W, C) - (torch.ones(3, 64, 128), (64, 128, 3)), # (3,64,128) -> (64,128,3) - - # Case 2: 4D tensor (N, C, H, W) -> (N, H, W, C) - (torch.ones(10, 3, 64, 128), (10, 64, 128, 3)), # (10,3,64,128) -> (10,64,128,3) - - # Case 3: Invalid input (2D tensor) - (torch.ones(64, 128), ValueError), # 2D tensors should raise an error - - # Case 4: Invalid input (5D tensor) - (torch.ones(10, 3, 64, 128, 2), ValueError), # 5D tensors should raise an error - ] - - def test_transpose(self): - """Test tensor to image transposition with valid and invalid cases.""" - for input_tensor, expected in self.test_cases: - with self.subTest(input_shape=input_tensor.shape): - if isinstance(expected, tuple): - # Valid cases - result, = self.node.f(input_tensor) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result has correct shape - self.assertEqual(result.shape, torch.Size(expected), f"Expected shape {expected}, got {result.shape}") - - # Ensure result has the same dtype - self.assertEqual(result.dtype, input_tensor.dtype, "Dtype mismatch after transpose operation") - else: - # Invalid cases should raise an error - with self.assertRaises(expected): - self.node.f(input_tensor) - - -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 deleted file mode 100644 index be55c92..0000000 --- a/tests/pytorch_wrapper/pt_to_numpy_test.py +++ /dev/null @@ -1,64 +0,0 @@ -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() diff --git a/tests/pytorch_wrapper/pt_to_rgb_tensors_test.py b/tests/pytorch_wrapper/pt_to_rgb_tensors_test.py deleted file mode 100644 index 69f411a..0000000 --- a/tests/pytorch_wrapper/pt_to_rgb_tensors_test.py +++ /dev/null @@ -1,69 +0,0 @@ -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() diff --git a/tests/pytorch_wrapper/pt_train_classification_conv_model_test.py b/tests/pytorch_wrapper/pt_train_classification_conv_model_test.py deleted file mode 100644 index 295753a..0000000 --- a/tests/pytorch_wrapper/pt_train_classification_conv_model_test.py +++ /dev/null @@ -1,63 +0,0 @@ -import os -import sys -import unittest -import torch -import torch.nn as nn - -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.ptn_conv_model import PtnConvModel -from pytorch_wrapper.pto_adam import PtoAdam -from pytorch_wrapper.ptv_dataset import PtvDataset -from pytorch_wrapper.ptv_transforms_to_tensor import PtvTransformsToTensor -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 TestPtTrainConvModel(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - compose_node = PtvTransformsToTensor() - transform = compose_node.f()[0] - dataset_node = PtvDataset() - dataset = dataset_node.f("FashionMNIST", - True, # download - "", # root - '{"train": True}', - transform=transform)[0] - data_loader_node = PtDataLoader() - self.train_loader = data_loader_node.f( - dataset=dataset, - batch_size=32, - shuffle=True, - parameters='{"num_workers":1}' - )[0] - - self.model_node = PtnConvModel() - self.model = self.model_node.f( - input_dim="(1, 28, 28)", - penultimate_dim=0, - output_dim=10, - channel_list="[32,64,128,256]", - kernel_size_list="[3,3,3,3]", - padding_list="[1,1,1,1]", - downsample_list="[True,True,True,True]" - )[0] - - self.optimizer = PtoAdam().f(self.model, 0.001, 0.9, 0.999)[0] - self.trainer = PtTrainClassificationModel() - self.save_model = PtSaveModel() - - def test_1(self): - epochs = 1 - trained_model = self.trainer.f(self.model, self.train_loader, self.optimizer, - epochs, # epochs - use_gpu=True)[0] - self.save_model.f(trained_model, f"fashion_mnist_{epochs}_epochs_conv.pt") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_train_classification_conv_model_test2.py b/tests/pytorch_wrapper/pt_train_classification_conv_model_test2.py deleted file mode 100644 index 1c38ad6..0000000 --- a/tests/pytorch_wrapper/pt_train_classification_conv_model_test2.py +++ /dev/null @@ -1,73 +0,0 @@ -import os -import sys -import unittest -import torch -import torch.nn as nn - -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.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_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 = PtvTransformsResize() - transform = compose_node.f(256, 256)[0] - self.node = PtvImageFolderDataset() - train_dataset = self.node.f("dog_and_cat/train", - transform=transform)[0] - 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}' - )[0] - - self.model_node = PtnConvModel() - self.model = self.model_node.f( - input_dim="(3, 256, 256)", - penultimate_dim=0, - output_dim=2, - 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]" - )[0] - - self.optimizer = PtoAdam().f(self.model, 0.0001, 0.9, 0.999)[0] - self.trainer = PtTrainClassificationModel() - self.save_model = PtSaveModel() - - @unittest.skipIf(os.getenv("RUN_SKIPPED_TESTS") != "1", "Skipping unless explicitly enabled") - def test_1(self): - epochs = 20 - trained_model = self.trainer.f(self.model, self.train_loader, self.optimizer, - epochs, # epochs - use_gpu=True, - val_loader=self.val_loader)[0] - self.save_model.f(trained_model, f"dog_cat_{epochs}_epochs_conv.pt") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_train_classification_model_test.py b/tests/pytorch_wrapper/pt_train_classification_model_test.py deleted file mode 100644 index 9edb822..0000000 --- a/tests/pytorch_wrapper/pt_train_classification_model_test.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -import sys -import unittest -import torch -import torch.nn as nn - -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.ptn_linear_model import PtnLinearModel -from pytorch_wrapper.pto_adam import PtoAdam -from pytorch_wrapper.ptv_dataset import PtvDataset -from pytorch_wrapper.ptv_transforms_to_tensor import PtvTransformsToTensor -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 TestPtTrainModel(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - compose_node = PtvTransformsToTensor() - transform = compose_node.f()[0] - dataset_node = PtvDataset() - dataset = dataset_node.f("FashionMNIST", - True, # download - "", # root - '{"train": True}', - transform=transform)[0] - data_loader_node = PtDataLoader() - self.train_loader = data_loader_node.f( - dataset=dataset, - batch_size=32, - shuffle=True, - parameters='{"num_workers":1}' - )[0] - - self.model_node = PtnLinearModel() - self.model = self.model_node.f( - "784,180,42,10", # input dim for first layer, output dim for each layer - "True,True,True", # bias - 3 # num layers - )[0] - - self.optimizer = PtoAdam().f(self.model, 0.001, 0.9, 0.999)[0] - self.trainer = PtTrainClassificationModel() - self.save_model = PtSaveModel() - - def test_1(self): - epochs = 1 - trained_model = self.trainer.f(self.model, self.train_loader, self.optimizer, - epochs, # epochs - use_gpu=True)[0] - self.save_model.f(trained_model, f"fashion_mnist_{epochs}_epochs.pt") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_train_classification_model_with_dataset_loader_test.py b/tests/pytorch_wrapper/pt_train_classification_model_with_dataset_loader_test.py deleted file mode 100644 index 43fead0..0000000 --- a/tests/pytorch_wrapper/pt_train_classification_model_with_dataset_loader_test.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -import sys -import unittest -import torch -import torch.nn as nn - -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.ptn_linear_model import PtnLinearModel -from pytorch_wrapper.pto_adam import PtoAdam -from pytorch_wrapper.ptv_dataset_loader import PtvDatasetLoader -from pytorch_wrapper.pt_train_classification_model import PtTrainClassificationModel -from pytorch_wrapper.pt_save_model import PtSaveModel - - -class TestPtTrainModelWithDatasetLoader(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.dataset_loader_node = PtvDatasetLoader() - self.train_loader = self.dataset_loader_node.f( - "MNIST", - True, # download - "", # root - batch_size=128, - shuffle=True, - dataset_parameters='{"train": True}', - load_parameters='{"num_workers":1}' - )[0] - self.model_node = PtnLinearModel() - self.model = self.model_node.f( - "784,180,42,10", # input dim for first layer, output dim for each layer - "True,True,True", # bias - 3 # num layers - )[0] - - self.optimizer = PtoAdam().f(self.model, 0.001, 0.9, 0.999)[0] - self.trainer = PtTrainClassificationModel() - self.save_model = PtSaveModel() - - def test_1(self): - epochs = 2 - trained_model = self.trainer.f(self.model, self.train_loader, self.optimizer, - epochs, # epochs - use_gpu=True)[0] - self.save_model.f(trained_model, f"mnist_{epochs}_epochs.pt") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_unsqueeze_test.py b/tests/pytorch_wrapper/pt_unsqueeze_test.py deleted file mode 100644 index d9b97a7..0000000 --- a/tests/pytorch_wrapper/pt_unsqueeze_test.py +++ /dev/null @@ -1,57 +0,0 @@ -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_unsqueeze import PtUnsqueeze # Importing the PtUnsqueeze class - - -class TestPtUnsqueeze(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtUnsqueeze() - - # Define test cases: (input_tensor, dim, expected_shape) - self.test_cases = [ - # Case 1: Adding a singleton dimension at different positions - (torch.ones(3, 4), 0, (1, 3, 4)), # (3,4) -> (1,3,4) - (torch.ones(3, 4), 1, (3, 1, 4)), # (3,4) -> (3,1,4) - (torch.ones(3, 4), 2, (3, 4, 1)), # (3,4) -> (3,4,1) - - # Case 2: Adding a singleton dimension at negative indices - (torch.ones(3, 4), -1, (3, 4, 1)), # (3,4) -> (3,4,1) - (torch.ones(3, 4), -2, (3, 1, 4)), # (3,4) -> (3,1,4) - (torch.ones(3, 4), -3, (1, 3, 4)), # (3,4) -> (1,3,4) - - # Case 3: Adding multiple dimensions one by one - (torch.ones(3,), 0, (1, 3)), # (3,) -> (1,3) - (torch.ones(3,), 1, (3, 1)), # (3,) -> (3,1) - (torch.ones(3,), -1, (3, 1)), # (3,) -> (3,1) - - # Case 4: Expanding an already multi-dimensional tensor - (torch.ones(2, 3, 4), 1, (2, 1, 3, 4)), # (2,3,4) -> (2,1,3,4) - (torch.ones(2, 3, 4), -1, (2, 3, 4, 1)), # (2,3,4) -> (2,3,4,1) - ] - - def test_unsqueeze(self): - """Test tensor unsqueeze operation on various dimensions.""" - for input_tensor, dim, expected_shape in self.test_cases: - with self.subTest(input_shape=input_tensor.shape, dim=dim): - result, = self.node.f(input_tensor, dim) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result has correct shape - self.assertEqual(result.shape, torch.Size(expected_shape), f"Expected shape {expected_shape}, got {result.shape}") - - # Ensure result has the same dtype - self.assertEqual(result.dtype, input_tensor.dtype, "Dtype mismatch after unsqueeze operation") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_var_test.py b/tests/pytorch_wrapper/pt_var_test.py deleted file mode 100644 index 2908cbd..0000000 --- a/tests/pytorch_wrapper/pt_var_test.py +++ /dev/null @@ -1,83 +0,0 @@ -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_var import PtVar - - -class TestPtVar(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtVar() - - # Define test cases with string-based dimensions - self.test_cases = [ - # Sample variance (Bessel's correction) along dim=0 - ( - torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), - "0", - 1, - False, - torch.var(torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), dim=0, correction=1, keepdim=False) - ), - # Sample variance along dim=1, with keepdim=True - ( - torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), - "1", - 1, - True, - torch.var(torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), dim=1, correction=1, keepdim=True) - ), - # Sample variance across both axes (dim=(0,1)) - ( - torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), - "(0, 1)", - 1, - False, - torch.var(torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), dim=(0, 1), correction=1, keepdim=False) - ), - # Sample variance across all elements (dim=None) - Entire tensor - ( - torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), - "", - 1, - False, - torch.var(torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), correction=1, keepdim=False) - ), - # Population variance along dim=0 (correction=0) - ( - torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), - "0", - 0, - False, - torch.var(torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), dim=0, correction=0, keepdim=False) - ), - # Population variance along dim=1 with keepdim=True (correction=0) - ( - torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), - "1", - 0, - True, - torch.var(torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), dim=1, correction=0, keepdim=True) - ) - ] - - def test_var(self): - """Test torch.var behavior.""" - for tens, dim, correction, keepdim, expected in self.test_cases: - with self.subTest(tens=tens, dim=dim, correction=correction, keepdim=keepdim): - result, = self.node.f(tens, dim, correction, keepdim) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected variance values - self.assertTrue(torch.allclose(result, expected, atol=1e-6), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_view_test.py b/tests/pytorch_wrapper/pt_view_test.py deleted file mode 100644 index a35dc48..0000000 --- a/tests/pytorch_wrapper/pt_view_test.py +++ /dev/null @@ -1,61 +0,0 @@ -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_view import PtView # Importing the PtView class - - -class TestPtView(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtView() - - # Define test cases: (input_tensor, new_shape, expected_shape) - self.test_cases = [ - # Case 1: Reshape a 2D tensor - (torch.ones(2, 3), "[3, 2]", (3, 2)), # (2,3) -> (3,2) - - # Case 2: Reshape a 3D tensor - (torch.ones(2, 3, 4), "[6, 4]", (6, 4)), # (2,3,4) -> (6,4) - - # Case 3: Using -1 for automatic inference - (torch.ones(2, 3, 4), "[2, -1]", (2, 12)), # (2,3,4) -> (2,12) - (torch.ones(6, 4), "[-1, 4]", (6, 4)), # (6,4) -> (6,4) - - # Case 4: Flattening a multi-dimensional tensor - (torch.ones(2, 3, 4), "[-1]", (24,)), # (2,3,4) -> (24,) - - # Case 5: Expanding dimensions - (torch.ones(6,), "[2, 3]", (2, 3)), # (6,) -> (2,3) - - # Case 6: Reshaping a 4D tensor - (torch.ones(2, 3, 4, 5), "[6, -1, 5]", (6, 4, 5)), # (2,3,4,5) -> (6,4,5) - - # Case 7: Edge case with 1D tensor - (torch.ones(10,), "[10, 1]", (10, 1)), # (10,) -> (10,1) - ] - - def test_view(self): - """Test tensor reshape operation with various shape orders.""" - for input_tensor, new_shape, expected_shape in self.test_cases: - with self.subTest(input_shape=input_tensor.shape, new_shape=new_shape): - result, = self.node.f(input_tensor, new_shape) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result has correct shape - self.assertEqual(result.shape, torch.Size(expected_shape), f"Expected shape {expected_shape}, got {result.shape}") - - # Ensure result has the same dtype - self.assertEqual(result.dtype, input_tensor.dtype, "Dtype mismatch after view operation") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_where_test.py b/tests/pytorch_wrapper/pt_where_test.py deleted file mode 100644 index 3f343fe..0000000 --- a/tests/pytorch_wrapper/pt_where_test.py +++ /dev/null @@ -1,59 +0,0 @@ -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_where import PtWhere - - -class TestPtWhere(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtWhere() - - # Test cases: (Condition Tensor, True Tensor, False Tensor, Expected Output) - self.test_cases = [ - ( - torch.tensor([[True, False], [False, True]]), - torch.tensor([[1, 2], [3, 4]]), - torch.tensor([[5, 6], [7, 8]]), - torch.tensor([[1, 6], [7, 4]]) - ), - ( - torch.tensor([True, False, True]), - torch.tensor([10, 20, 30]), - torch.tensor([100, 200, 300]), - torch.tensor([10, 200, 30]) - ), - ( - torch.tensor([[False, False], [True, True]]), - torch.tensor([[1.5, 2.5], [3.5, 4.5]]), - torch.tensor([[5.5, 6.5], [7.5, 8.5]]), - torch.tensor([[5.5, 6.5], [3.5, 4.5]]) - ), - ( - torch.tensor([[[True, False], [False, True]], [[False, True], [True, False]]]), - torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]), - torch.tensor([[[9, 10], [11, 12]], [[13, 14], [15, 16]]]), - torch.tensor([[[1, 10], [11, 4]], [[13, 6], [7, 16]]]) - ), - ] - - def test_where(self): - """Test torch.where behavior.""" - for condition_tens, true_tens, false_tens, expected in self.test_cases: - with self.subTest(condition_tens=condition_tens, true_tens=true_tens, false_tens=false_tens): - result, = self.node.f(condition_tens, true_tens, false_tens) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result matches expected values - self.assertTrue(torch.equal(result, expected), f"Expected {expected}, got {result}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/pt_zeros_test.py b/tests/pytorch_wrapper/pt_zeros_test.py deleted file mode 100644 index 3cfda36..0000000 --- a/tests/pytorch_wrapper/pt_zeros_test.py +++ /dev/null @@ -1,44 +0,0 @@ -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_zeros import PtZeros # Importing the PtZeros class -from pytorch_wrapper.utils import DTYPE_MAPPING # Importing the dtype mapping - - -class TestPtZeros(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.node = PtZeros() - - # Tensor size to test - self.size_str = "[2, 3]" # Using a small 2D tensor size for all tests - self.expected_shape = (2, 3) - - def test_zeros_tensor_all_dtypes(self): - """Test tensor creation with zeros for all supported data types.""" - for data_type, torch_dtype in DTYPE_MAPPING.items(): - with self.subTest(data_type=data_type): - result, = self.node.f(self.size_str, data_type) - - # Ensure return value is a tensor - self.assertTrue(isinstance(result, torch.Tensor), "Output is not a tensor") - - # Ensure result has correct shape - self.assertEqual(result.shape, torch.Size(self.expected_shape), f"Expected shape {self.expected_shape}, got {result.shape}") - - # Ensure result has correct dtype - self.assertEqual(result.dtype, torch_dtype, f"Expected dtype {torch_dtype}, got {result.dtype}") - - # Ensure all elements are zeros - expected = torch.zeros(self.expected_shape, dtype=torch_dtype) - self.assertTrue(torch.equal(result, expected), f"Tensor values do not match expected zeros tensor for dtype {data_type}") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/ptn_conv_model_def_test.py b/tests/pytorch_wrapper/ptn_conv_model_def_test.py deleted file mode 100644 index 2b3b812..0000000 --- a/tests/pytorch_wrapper/ptn_conv_model_def_test.py +++ /dev/null @@ -1,158 +0,0 @@ -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.ptn_conv_model_def import ConvModel, ConvSingleLayer - - -class TestPtnConvModelDef(unittest.TestCase): - def setUp(self): - """Setup method for initializing test conditions.""" - pass - - def test_single_layer_1_with_padding(self): - """Test ConvSingleLayer with padding=1, no downsampling.""" - model = ConvSingleLayer( - in_channels=4, - out_channels=32, - kernel_size=3, - padding=1, - downsample=False, - apply_activation=True) - x = torch.ones(1, 4, 28, 28) - out = model(x) - self.assertEqual(out.size(), torch.Size([1, 32, 28, 28])) - - def test_single_layer_no_padding(self): - """Test ConvSingleLayer with padding=0, no downsampling.""" - model = ConvSingleLayer( - in_channels=4, - out_channels=32, - kernel_size=3, - padding=0, - downsample=False, - apply_activation=True) - x = torch.ones(1, 4, 28, 28) - out = model(x) - self.assertEqual(out.size(), torch.Size([1, 32, 26, 26])) - - def test_single_layer_3_no_padding_downsample(self): - """Test ConvSingleLayer with padding=0 and downsampling enabled.""" - model = ConvSingleLayer( - in_channels=4, - out_channels=32, - kernel_size=3, - padding=0, - downsample=True, - apply_activation=True) - x = torch.ones(1, 4, 28, 28) - out = model(x) - self.assertEqual(out.size(), torch.Size([1, 32, 13, 13])) - - def test_1(self): - """Test ConvModel with a single convolutional layer, checking output size.""" - model = ConvModel( - input_dim=(4, 28, 28), - penultimate_dim=0, - output_dim=10, - channel_list=[32], - kernel_size_list=[3], - padding_list=[1], - downsample_list=[False]) - - x = torch.ones(1, 4, 28, 28) - out = model(x) - self.assertEqual(out.size(), torch.Size([1, 10])) - - def test_2(self): - """Test ConvModel intermediate convolution layers' output size.""" - model = ConvModel( - input_dim=(4, 28, 28), - penultimate_dim=0, - output_dim=10, - channel_list=[32], - kernel_size_list=[3], - padding_list=[1], - downsample_list=[False]) - - x = torch.ones(1, 4, 28, 28) - for c in model.conv_layers: - x = c(x) - out = x - self.assertEqual(out.size(), torch.Size([1, 32, 28, 28])) - - def test_3(self): - """Test ConvModel with two convolutional layers and downsampling enabled.""" - model = ConvModel( - input_dim=(4, 28, 28), - penultimate_dim=0, - output_dim=10, - channel_list=[32,64], - kernel_size_list=[3,3], - padding_list=[1,1], - downsample_list=[True,True]) - - x = torch.ones(1, 4, 28, 28) - for c in model.conv_layers: - x = c(x) - out = x - self.assertEqual(out.size(), torch.Size([1, 64, 7, 7])) - - def test_4(self): - """Test ConvModel with three convolutional layers and downsampling enabled.""" - model = ConvModel( - input_dim=(4, 28, 28), - penultimate_dim=0, - output_dim=10, - channel_list=[32,64,128], - kernel_size_list=[3,3,3], - padding_list=[1,1,1], - downsample_list=[True,True,True]) - - x = torch.ones(1, 4, 28, 28) - for c in model.conv_layers: - x = c(x) - out = x - self.assertEqual(out.size(), torch.Size([1, 128, 3, 3])) - - def test_5(self): - """Test ConvModel with four convolutional layers and downsampling enabled.""" - model = ConvModel( - input_dim=(4, 28, 28), - penultimate_dim=0, - output_dim=10, - channel_list=[32,64,128,256], - kernel_size_list=[3,3,3,3], - padding_list=[1,1,1,1], - downsample_list=[True,True,True,True]) - - x = torch.ones(1, 4, 28, 28) - for c in model.conv_layers: - x = c(x) - out = x - self.assertEqual(out.size(), torch.Size([1, 256, 1, 1])) - - def test_6(self): - """Test ConvModel with five convolutional layers, last layer without downsampling.""" - model = ConvModel( - input_dim=(4, 28, 28), - penultimate_dim=0, - output_dim=10, - channel_list=[32,64,128,256,512], - kernel_size_list=[3,3,3,3,1], - padding_list=[1,1,1,1,0], - downsample_list=[True,True,True,True,False]) - - x = torch.ones(1, 4, 28, 28) - for c in model.conv_layers: - x = c(x) - out = x - self.assertEqual(out.size(), torch.Size([1, 512, 1, 1])) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/ptn_conv_model_test.py b/tests/pytorch_wrapper/ptn_conv_model_test.py deleted file mode 100644 index 9e6564e..0000000 --- a/tests/pytorch_wrapper/ptn_conv_model_test.py +++ /dev/null @@ -1,64 +0,0 @@ -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.ptn_conv_model import PtnConvModel - - -class TestPtnConvModel(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.model_node = PtnConvModel() - - def test_forward(self): - model = self.model_node.f( - input_dim="(4, 28, 28)", - penultimate_dim=0, - output_dim=10, - channel_list="[32,64,128,256]", - kernel_size_list="[3,3,3,3]", - padding_list="[1,1,1,1]", - downsample_list="[True,True,True,True]" - )[0] - bs = 2 - x = torch.ones(bs, 4, 28, 28) - out = model(x) - self.assertEqual(out.size(), torch.Size([bs, 10])) - - def test_rank3_input(self): - model = self.model_node.f( - input_dim="(1, 28, 28)", - penultimate_dim=0, - output_dim=10, - channel_list="[32,64,128,256]", - kernel_size_list="[3,3,3,3]", - padding_list="[1,1,1,1]", - downsample_list="[True,True,True,True]" - )[0] - bs = 2 - x = torch.ones(bs, 28, 28) - out = model(x) - self.assertEqual(out.size(), torch.Size([bs, 10])) - - def test_rank2_input(self): - model = self.model_node.f( - input_dim="(1, 28, 28)", - penultimate_dim=0, - output_dim=10, - channel_list="[32,64,128,256]", - kernel_size_list="[3,3,3,3]", - padding_list="[1,1,1,1]", - downsample_list="[True,True,True,True]" - )[0] - bs = 2 - x = torch.ones(bs, 28) - with self.assertRaises(ValueError) as context: - model(x) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/ptn_linear_model_test.py b/tests/pytorch_wrapper/ptn_linear_model_test.py deleted file mode 100644 index 124a15a..0000000 --- a/tests/pytorch_wrapper/ptn_linear_model_test.py +++ /dev/null @@ -1,29 +0,0 @@ -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.ptn_linear_model import PtnLinearModel # Updated import to PtnLinear - - -class TestPtnLinearModel(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - self.model_node = PtnLinearModel() - self.model = self.model_node.f( - "784,180,42,10", # input dim for first layer, output dim for each layer - "True,True,True", # bias - 3 # num layers - )[0] # HINADA note: Using roughly 0.23x ratio for reduction - - def test_forward(self): - x = torch.ones(4, 28, 28) - out = self.model(x) - self.assertEqual(out.size(), torch.Size([4, 10])) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/ptv_dataset_loader_test.py b/tests/pytorch_wrapper/ptv_dataset_loader_test.py deleted file mode 100644 index 0d01690..0000000 --- a/tests/pytorch_wrapper/ptv_dataset_loader_test.py +++ /dev/null @@ -1,34 +0,0 @@ -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.ptv_dataset_loader import PtvDatasetLoader - -class TestPtDataLoader(unittest.TestCase): - def setUp(self): - """Set up test data.""" - self.dataset_loader_node = PtvDatasetLoader() - - def test1(self): - """Tests downloading data and checks dimension""" - bs = 2 - train_data = self.dataset_loader_node.f( - "MNIST", - True, # download - "", # root - batch_size=bs, - shuffle=True, - dataset_parameters='{"train": True}', - load_parameters='{"num_workers":1}' - )[0] - - x, y = next(iter(train_data)) - self.assertEqual(x.size(), torch.Size([bs, 1, 28, 28])) - self.assertEqual(y.size(), torch.Size([bs])) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/ptv_dataset_test.py b/tests/pytorch_wrapper/ptv_dataset_test.py deleted file mode 100644 index 3eeffba..0000000 --- a/tests/pytorch_wrapper/ptv_dataset_test.py +++ /dev/null @@ -1,36 +0,0 @@ -import os -import sys -import unittest - -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_dataset import PtvDataset -from pytorch_wrapper.ptv_transforms_to_tensor import PtvTransformsToTensor - -class TestPtvDataset(unittest.TestCase): - def setUp(self): - """Set up test data.""" - self.node = PtvDataset() - self.compose_node = PtvTransformsToTensor() - - def test_no_transform(self): - """Tests downloading without transform""" - self.node.f("MNIST", - True, # download - "", # root - '{"train": True}', - transform=None) - - def test_with_transform(self): - """Tests downloading with transform""" - transform = self.compose_node.f()[0] - self.node.f("MNIST", - True, # download - "", # root - '{"train": False}', - transform=transform) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/ptv_image_folder_dataset_test.py b/tests/pytorch_wrapper/ptv_image_folder_dataset_test.py deleted file mode 100644 index bc9de3b..0000000 --- a/tests/pytorch_wrapper/ptv_image_folder_dataset_test.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -This unit test requires the dog_and_cat dataset to be set up under -project_root/datasets/. -""" -import os -import sys -import unittest - -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_image_folder_dataset import PtvImageFolderDataset -from pytorch_wrapper.ptv_transforms_to_tensor import PtvTransformsToTensor - - -class TestPtvImageDataset(unittest.TestCase): - def setUp(self): - """Set up test data.""" - self.node = PtvImageFolderDataset() - self.compose_node = PtvTransformsToTensor() - - def test_with_tensor_transform(self): - """Tests downloading with transform""" - transform = self.compose_node.f()[0] - dc = self.node.f("dog_and_cat/train", - transform=transform)[0] - expected_classes = ["cats", "dogs"] - expected_dict = [("cats", 0), ("dogs", 1)] - for i, c in enumerate(dc.classes): - self.assertEqual(c, expected_classes[i]) - - for i, (k, w) in enumerate(dc.class_to_idx.items()): - self.assertEqual(k, expected_dict[i][0]) - self.assertEqual(w, expected_dict[i][1]) - - # Read data - data = next(iter(dc)) - - # Check data shape - self.assertEqual(data[0].size(), (3, 512, 512)) - - # Check class - self.assertTrue(data[1] == 0) # cat - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/pytorch_wrapper/ptv_transforms_resize_test.py b/tests/pytorch_wrapper/ptv_transforms_resize_test.py deleted file mode 100644 index ca6d386..0000000 --- a/tests/pytorch_wrapper/ptv_transforms_resize_test.py +++ /dev/null @@ -1,43 +0,0 @@ -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() diff --git a/tests/pytorch_wrapper/utils_test.py b/tests/pytorch_wrapper/utils_test.py deleted file mode 100644 index 7101006..0000000 --- a/tests/pytorch_wrapper/utils_test.py +++ /dev/null @@ -1,54 +0,0 @@ -import os -import sys -import unittest - -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.utils import str_to_dim - - -class TestStrToDim(unittest.TestCase): - def setUp(self): - """Set up test instance.""" - pass - - def test_valid_integer(self): - """Test parsing a valid integer string.""" - self.assertEqual(str_to_dim("5"), 5) - - def test_valid_tuple(self): - """Test parsing a valid tuple string.""" - self.assertEqual(str_to_dim("(1, 2, 3)"), (1, 2, 3)) - - def test_valid_list(self): - """Test parsing a valid list string (should be converted to tuple).""" - self.assertEqual(str_to_dim("[4, 5, 6]"), (4, 5, 6)) - - def test_empty_string(self): - """Test empty string input should return None.""" - self.assertIsNone(str_to_dim("")) - - def test_invalid_string(self): - """Test invalid string input should raise ValueError.""" - with self.assertRaises(ValueError): - str_to_dim("invalid") - - def test_mixed_type_list(self): - """Test a list with mixed types should raise ValueError.""" - with self.assertRaises(ValueError): - str_to_dim("[1, 'a', 3]") - - def test_nested_tuple(self): - """Test a nested tuple should raise ValueError.""" - with self.assertRaises(ValueError): - str_to_dim("((1, 2), 3)") - - def test_dict_input(self): - """Test a dictionary input should raise ValueError.""" - with self.assertRaises(ValueError): - str_to_dim("{'a': 1, 'b': 2}") - -if __name__ == "__main__": - unittest.main() diff --git a/web/js/ShowTextGeneric.js b/web/js/ShowTextGeneric.js index 1a68f01..e56aa46 100644 --- a/web/js/ShowTextGeneric.js +++ b/web/js/ShowTextGeneric.js @@ -12,7 +12,7 @@ import { ComfyWidgets } from "../../../scripts/widgets.js"; app.registerExtension({ name: "HowToSD.ShowTextGeneric", async beforeRegisterNodeDef(nodeType, nodeData, app) { - if (["NumpyShow", "PtShowText", "PtShowSize", "PandasShowSeries"].includes(nodeData.name)) { // This needs to match NODE_CLASS_MAPPINGS in Python + if (["NumpyShow", "PandasShowSeries"].includes(nodeData.name)) { // This needs to match NODE_CLASS_MAPPINGS in Python function populate(text) { if (this.widgets) { // Clear all widgets on the node