From ac3c501d56b4ce50065c160462f12b6db023b6e5 Mon Sep 17 00:00:00 2001 From: lihyin Date: Sun, 13 Sep 2020 21:54:14 -0600 Subject: [PATCH 01/65] Add Pytorch installation instructions for potential issues in Windows --- Readme.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Readme.md b/Readme.md index c614482..d77db9d 100644 --- a/Readme.md +++ b/Readme.md @@ -20,6 +20,14 @@ Install packages with: $ pip install -r requirements.txt ``` +Or install with for Windows as per [PyTorch official site](https://pytorch.org/get-started/locally/): + +``` +$ pip install torch===1.6.0 torchvision===0.7.0 -f https://download.pytorch.org/whl/torch_s +table.html +$ pip install -r requirements.txt +``` + ## Configure and Run All configurations concerning data, model, training, visualization etc. can be made in _config.py_. The default configuration will run a training with paper-given parameters on the provided dummy dataset. This dataset contains images of 4 squares as normal examples and 4 circles as anomaly. From 6104c35ba9a885a5423270d51b4ca4d6cac9bfdd Mon Sep 17 00:00:00 2001 From: lihyin Date: Sun, 13 Sep 2020 23:02:18 -0600 Subject: [PATCH 02/65] Add the Readme tips to resolve GPU Out of Memory issue and more specific instructions how to run the training --- Readme.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index d77db9d..4122315 100644 --- a/Readme.md +++ b/Readme.md @@ -32,9 +32,18 @@ $ pip install -r requirements.txt All configurations concerning data, model, training, visualization etc. can be made in _config.py_. The default configuration will run a training with paper-given parameters on the provided dummy dataset. This dataset contains images of 4 squares as normal examples and 4 circles as anomaly. -To start the training, just run _main.py_! If training on the dummy data does not lead to an AUROC of 1.0, something seems to be wrong. +If you encounter GPU Out of Memory issue, you can reduce the neuron numbers in _config.py_ +``` +fc_internal = 1536 # number of neurons in hidden layers of s-t-networks +``` + +To start the training, just run _main.py_ as follows! If training on the dummy data does not lead to an AUROC of 1.0, something seems to be wrong. Please report us if you have issues when using the code. +``` +$ python main.py +``` + ## Data The given dummy dataset shows how the implementation expects the construction of a dataset. Coincidentally, the [MVTec AD dataset](https://www.mvtec.com/de/unternehmen/forschung/datasets/mvtec-ad/) is constructed in this way. From 16d69d6d2a27a2902b87a6ea5816eeb1f4508164 Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Mon, 14 Sep 2020 13:39:12 -0600 Subject: [PATCH 03/65] train without test: save model and weight --- main.py | 2 +- train.py | 53 +++++++++++++++++++++++++++-------------------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/main.py b/main.py index 9fac6ca..41986ca 100644 --- a/main.py +++ b/main.py @@ -9,4 +9,4 @@ train_set, test_set = load_datasets(c.dataset_path, c.class_name) train_loader, test_loader = make_dataloaders(train_set, test_set) -model = train(train_loader, test_loader) +model = train(train_loader, None) diff --git a/train.py b/train.py index f3ebf73..59d44fe 100644 --- a/train.py +++ b/train.py @@ -62,35 +62,36 @@ def train(train_loader, test_loader): if c.verbose: print('Epoch: {:d}.{:d} \t train loss: {:.4f}'.format(epoch, sub_epoch, mean_train_loss)) - # evaluate - model.eval() - if c.verbose: - print('\nCompute loss and scores on test set:') - test_loss = list() - test_z = list() - test_labels = list() - with torch.no_grad(): - for i, data in enumerate(tqdm(test_loader, disable=c.hide_tqdm_bar)): - inputs, labels = preprocess_batch(data) - z = model(inputs) - loss = get_loss(z, model.nf.jacobian(run_forward=False)) - test_z.append(z) - test_loss.append(t2np(loss)) - test_labels.append(t2np(labels)) - - test_loss = np.mean(np.array(test_loss)) - if c.verbose: - print('Epoch: {:d} \t test_loss: {:.4f}'.format(epoch, test_loss)) + if not (test_loader is None): + # evaluate + model.eval() + if c.verbose: + print('\nCompute loss and scores on test set:') + test_loss = list() + test_z = list() + test_labels = list() + with torch.no_grad(): + for i, data in enumerate(tqdm(test_loader, disable=c.hide_tqdm_bar)): + inputs, labels = preprocess_batch(data) + z = model(inputs) + loss = get_loss(z, model.nf.jacobian(run_forward=False)) + test_z.append(z) + test_loss.append(t2np(loss)) + test_labels.append(t2np(labels)) + + test_loss = np.mean(np.array(test_loss)) + if c.verbose: + print('Epoch: {:d} \t test_loss: {:.4f}'.format(epoch, test_loss)) - test_labels = np.concatenate(test_labels) - is_anomaly = np.array([0 if l == 0 else 1 for l in test_labels]) + test_labels = np.concatenate(test_labels) + is_anomaly = np.array([0 if l == 0 else 1 for l in test_labels]) - z_grouped = torch.cat(test_z, dim=0).view(-1, c.n_transforms_test, c.n_feat) - anomaly_score = t2np(torch.mean(z_grouped ** 2, dim=(-2, -1))) - score_obs.update(roc_auc_score(is_anomaly, anomaly_score), epoch, - print_score=c.verbose or epoch == c.meta_epochs - 1) + z_grouped = torch.cat(test_z, dim=0).view(-1, c.n_transforms_test, c.n_feat) + anomaly_score = t2np(torch.mean(z_grouped ** 2, dim=(-2, -1))) + score_obs.update(roc_auc_score(is_anomaly, anomaly_score), epoch, + print_score=c.verbose or epoch == c.meta_epochs - 1) - if c.grad_map_viz: + if c.grad_map_viz and not (test_loader is None): export_gradient_maps(model, test_loader, optimizer, -1) if c.save_model: From 9ab1a3ef33be6325c0f98fe31c2fe9af57e87e9c Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Mon, 14 Sep 2020 13:45:51 -0600 Subject: [PATCH 04/65] Add timing --- main.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main.py b/main.py index 41986ca..2c3b654 100644 --- a/main.py +++ b/main.py @@ -6,7 +6,12 @@ import config as c from train import train from utils import load_datasets, make_dataloaders +import time train_set, test_set = load_datasets(c.dataset_path, c.class_name) train_loader, test_loader = make_dataloaders(train_set, test_set) +time_start = time.time() model = train(train_loader, None) +time_end = time.time() +time_c = time_end - time_start # 运行所花时间 +print("time cost: {:f} s".format(time_c)) \ No newline at end of file From 2ada233d21897965bd8643c0dcd9fcadfe776ed5 Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Mon, 14 Sep 2020 14:15:36 -0600 Subject: [PATCH 05/65] runTest.py not working --- main.py | 3 ++- runTest.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ train.py | 4 ++-- 3 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 runTest.py diff --git a/main.py b/main.py index 2c3b654..c770e95 100644 --- a/main.py +++ b/main.py @@ -11,7 +11,8 @@ train_set, test_set = load_datasets(c.dataset_path, c.class_name) train_loader, test_loader = make_dataloaders(train_set, test_set) time_start = time.time() -model = train(train_loader, None) +model = train(train_loader, test_loader) +# model = train(train_loader, None) time_end = time.time() time_c = time_end - time_start # 运行所花时间 print("time cost: {:f} s".format(time_c)) \ No newline at end of file diff --git a/runTest.py b/runTest.py new file mode 100644 index 0000000..b4ea204 --- /dev/null +++ b/runTest.py @@ -0,0 +1,62 @@ +'''This is the repo which contains the original code to the WACV 2021 paper +"Same Same But DifferNet: Semi-Supervised Defect Detection with Normalizing Flows" +by Marco Rudolph, Bastian Wandt and Bodo Rosenhahn. +For further information contact Marco Rudolph (rudolph@tnt.uni-hannover.de)''' + +import config as c +from train import train +from utils import load_datasets, make_dataloaders +from model import load_model, load_weights +import numpy as np +import torch +from train import Score_Observer +from sklearn.metrics import roc_auc_score +from tqdm import tqdm +import time +from utils import * +from localization import export_gradient_maps + +def test(model, test_loader): + print("Running test") + score_obs = Score_Observer('AUROC') + # evaluate + model.eval() + if c.verbose: + print('\nCompute loss and scores on test set:') + test_loss = list() + test_z = list() + test_labels = list() + with torch.no_grad(): + for i, data in enumerate(tqdm(test_loader, disable=c.hide_tqdm_bar)): + inputs, labels = preprocess_batch(data) + z = model(inputs) + loss = get_loss(z, model.nf.jacobian(run_forward=False)) + test_z.append(z) + test_loss.append(t2np(loss)) + test_labels.append(t2np(labels)) + + test_loss = np.mean(np.array(test_loss)) + if c.verbose: + print('{:d} \t test_loss: {:.4f}'.format(test_loss)) + + test_labels = np.concatenate(test_labels) + is_anomaly = np.array([0 if l == 0 else 1 for l in test_labels]) + + z_grouped = torch.cat(test_z, dim=0).view(-1, c.n_transforms_test, c.n_feat) + anomaly_score = t2np(torch.mean(z_grouped ** 2, dim=(-2, -1))) + score_obs.update(roc_auc_score(is_anomaly, anomaly_score), epoch, + print_score=c.verbose or epoch == c.meta_epochs - 1) + + # if c.grad_map_viz: + # export_gradient_maps(model, test_loader, optimizer, -1) + +train_set, test_set = load_datasets(c.dataset_path, c.class_name) +_, test_loader = make_dataloaders(train_set, test_set) +time_start = time.time() +model = load_model(c.modelname) +load_weights(model, c.modelname) +test(model, test_loader) +time_end = time.time() +time_c = time_end - time_start # 运行所花时间 +print("time cost: {:f} s".format(time_c)) + diff --git a/train.py b/train.py index 59d44fe..9f3529f 100644 --- a/train.py +++ b/train.py @@ -91,8 +91,8 @@ def train(train_loader, test_loader): score_obs.update(roc_auc_score(is_anomaly, anomaly_score), epoch, print_score=c.verbose or epoch == c.meta_epochs - 1) - if c.grad_map_viz and not (test_loader is None): - export_gradient_maps(model, test_loader, optimizer, -1) +# if c.grad_map_viz and not (test_loader is None): +# export_gradient_maps(model, test_loader, optimizer, -1) if c.save_model: model.to('cpu') From a505a2f4b06170118a874a4b4a863ad8e777ea79 Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Mon, 14 Sep 2020 14:23:09 -0600 Subject: [PATCH 06/65] runTest.py working --- runTest.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/runTest.py b/runTest.py index b4ea204..fe02615 100644 --- a/runTest.py +++ b/runTest.py @@ -18,9 +18,12 @@ def test(model, test_loader): print("Running test") + optimizer = torch.optim.Adam(model.nf.parameters(), lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, weight_decay=1e-5) score_obs = Score_Observer('AUROC') # evaluate + model.to(c.device) model.eval() + epoch = 0 if c.verbose: print('\nCompute loss and scores on test set:') test_loss = list() @@ -37,7 +40,7 @@ def test(model, test_loader): test_loss = np.mean(np.array(test_loss)) if c.verbose: - print('{:d} \t test_loss: {:.4f}'.format(test_loss)) + print('Epoch: {:d} \t test_loss: {:.4f}'.format(epoch, test_loss)) test_labels = np.concatenate(test_labels) is_anomaly = np.array([0 if l == 0 else 1 for l in test_labels]) @@ -47,9 +50,10 @@ def test(model, test_loader): score_obs.update(roc_auc_score(is_anomaly, anomaly_score), epoch, print_score=c.verbose or epoch == c.meta_epochs - 1) - # if c.grad_map_viz: - # export_gradient_maps(model, test_loader, optimizer, -1) + if c.grad_map_viz: + export_gradient_maps(model, test_loader, optimizer, -1) +########################## Main #################### train_set, test_set = load_datasets(c.dataset_path, c.class_name) _, test_loader = make_dataloaders(train_set, test_set) time_start = time.time() From 7c8b3f4e0aa10f5fb88c99d1f1d8e1a5f84bff89 Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Tue, 15 Sep 2020 12:29:25 -0600 Subject: [PATCH 07/65] to use the model: no need to load the weights --- config.py | 11 ++++++----- main.py | 4 ++-- runTest.py | 1 - 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/config.py b/config.py index a650dba..737a71f 100644 --- a/config.py +++ b/config.py @@ -7,11 +7,12 @@ torch.cuda.set_device(0) # data settings -dataset_path = "dummy_dataset" -class_name = "dummy_class" -modelname = "dummy_test" +dataset_path = "zerobox_dataset" +class_name = "zerobox_class" +modelname = "zerobox_test" -img_size = (448, 448) +# img_size = (448, 448) +img_size = (480, 270) img_dims = [3] + list(img_size) add_img_noise = 0.01 @@ -39,7 +40,7 @@ # total epochs = meta_epochs * sub_epochs # evaluation after epochs -meta_epochs = 1 +meta_epochs = 10 sub_epochs = 8 # output settings diff --git a/main.py b/main.py index c770e95..d78c368 100644 --- a/main.py +++ b/main.py @@ -11,8 +11,8 @@ train_set, test_set = load_datasets(c.dataset_path, c.class_name) train_loader, test_loader = make_dataloaders(train_set, test_set) time_start = time.time() -model = train(train_loader, test_loader) -# model = train(train_loader, None) +#model = train(train_loader, test_loader) +model = train(train_loader, None) time_end = time.time() time_c = time_end - time_start # 运行所花时间 print("time cost: {:f} s".format(time_c)) \ No newline at end of file diff --git a/runTest.py b/runTest.py index fe02615..9f9dc37 100644 --- a/runTest.py +++ b/runTest.py @@ -58,7 +58,6 @@ def test(model, test_loader): _, test_loader = make_dataloaders(train_set, test_set) time_start = time.time() model = load_model(c.modelname) -load_weights(model, c.modelname) test(model, test_loader) time_end = time.time() time_c = time_end - time_start # 运行所花时间 From 02011561eafe229562cc9bda8151a7e15c7891ae Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Tue, 15 Sep 2020 12:34:31 -0600 Subject: [PATCH 08/65] load the model file directly --- runTest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runTest.py b/runTest.py index 9f9dc37..b1a4c4b 100644 --- a/runTest.py +++ b/runTest.py @@ -57,7 +57,8 @@ def test(model, test_loader): train_set, test_set = load_datasets(c.dataset_path, c.class_name) _, test_loader = make_dataloaders(train_set, test_set) time_start = time.time() -model = load_model(c.modelname) +# model = load_model(c.modelname) +model = torch.load("model_zerobox_test") test(model, test_loader) time_end = time.time() time_c = time_end - time_start # 运行所花时间 From ac0e6026e38d03b2eb5e4be16707a9d32ff54834 Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Tue, 15 Sep 2020 12:51:24 -0600 Subject: [PATCH 09/65] run test without loading training data --- runTest.py | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/runTest.py b/runTest.py index b1a4c4b..437d3d6 100644 --- a/runTest.py +++ b/runTest.py @@ -53,12 +53,51 @@ def test(model, test_loader): if c.grad_map_viz: export_gradient_maps(model, test_loader, optimizer, -1) +def load_testloader(data_dir_test): + def target_transform(target): + return class_perm[target] + + classes = os.listdir(data_dir_test) + if 'good' not in classes: + print('There should exist a subdirectory "good". Read the doc of this function for further information.') + exit() + classes.sort() + class_perm = list() + class_idx = 1 + for cl in classes: + if cl == 'good': + class_perm.append(0) + else: + class_perm.append(class_idx) + class_idx += 1 + + augmentative_transforms = [] + if c.transf_rotations: + augmentative_transforms += [transforms.RandomRotation(180)] + if c.transf_brightness > 0.0 or c.transf_contrast > 0.0 or c.transf_saturation > 0.0: + augmentative_transforms += [transforms.ColorJitter(brightness=c.transf_brightness, contrast=c.transf_contrast, + saturation=c.transf_saturation)] + + tfs = [transforms.Resize(c.img_size)] + augmentative_transforms + [transforms.ToTensor(), + transforms.Normalize(c.norm_mean, c.norm_std)] + + transform_train = transforms.Compose(tfs) + testset = ImageFolderMultiTransform(data_dir_test, transform=transform_train, target_transform=target_transform, + n_transforms=c.n_transforms_test) + testloader = torch.utils.data.DataLoader(testset, pin_memory=True, batch_size=c.batch_size_test, shuffle=True, + drop_last=False) + return testloader + ########################## Main #################### -train_set, test_set = load_datasets(c.dataset_path, c.class_name) -_, test_loader = make_dataloaders(train_set, test_set) +# train_set, test_set = load_datasets(c.dataset_path, c.class_name) +# _, test_loader = make_dataloaders(train_set, test_set) + +test_loader = load_testloader("zerobox_dataset/zerobox_class/test") +model = torch.load("model_zerobox_test") + +print("starting to run tests after loaded model and test dataset") time_start = time.time() # model = load_model(c.modelname) -model = torch.load("model_zerobox_test") test(model, test_loader) time_end = time.time() time_c = time_end - time_start # 运行所花时间 From f4d31e1b00c81307e7996f14eb19bb6ed6492154 Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Tue, 15 Sep 2020 13:09:21 -0600 Subject: [PATCH 10/65] Problem with Linux --- config.py | 5 +++-- runTest.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/config.py b/config.py index 737a71f..020472f 100644 --- a/config.py +++ b/config.py @@ -2,9 +2,10 @@ research purposes. Don't try this code if you are a software engineer.''' # device settings -device = 'cuda' # or 'cpu' +# device = 'cuda' # or 'cpu' +device = 'cpu' # or 'cuda' import torch -torch.cuda.set_device(0) +#torch.cuda.set_device(0) # data settings dataset_path = "zerobox_dataset" diff --git a/runTest.py b/runTest.py index 437d3d6..d5318b5 100644 --- a/runTest.py +++ b/runTest.py @@ -47,6 +47,7 @@ def test(model, test_loader): z_grouped = torch.cat(test_z, dim=0).view(-1, c.n_transforms_test, c.n_feat) anomaly_score = t2np(torch.mean(z_grouped ** 2, dim=(-2, -1))) + print(f"test_labels={test_labels}, is_anomaly={is_anomaly},anomaly_score={anomaly_score}") score_obs.update(roc_auc_score(is_anomaly, anomaly_score), epoch, print_score=c.verbose or epoch == c.meta_epochs - 1) @@ -93,7 +94,7 @@ def target_transform(target): # _, test_loader = make_dataloaders(train_set, test_set) test_loader = load_testloader("zerobox_dataset/zerobox_class/test") -model = torch.load("model_zerobox_test") +model = torch.load("model_zerobox_test", map_location=torch.device('cpu')) print("starting to run tests after loaded model and test dataset") time_start = time.time() From 4cbcef3450df83313db356c2bee4d5bac64bdcbe Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Wed, 16 Sep 2020 08:10:03 -0600 Subject: [PATCH 11/65] Enable LFS: add model_zerobox_test --- .gitattributes | 1 + model_zerobox_test | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 .gitattributes create mode 100644 model_zerobox_test diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5689b70 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +model_zerobox_test filter=lfs diff=lfs merge=lfs -text diff --git a/model_zerobox_test b/model_zerobox_test new file mode 100644 index 0000000..c8907b3 --- /dev/null +++ b/model_zerobox_test @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ffdc4a17f435b31ceb6ec2b61cfd0cf1b4dc77693443998150eeeb78698307a +size 934360903 From 710f323280a83ac77d08f400e029a0312f64c149 Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Sun, 20 Sep 2020 09:49:30 -0600 Subject: [PATCH 12/65] stop using git-lfs --- config.py | 4 ++-- localization.py | 20 +++++++++++++++----- model_zerobox_test | 3 --- runTest.py | 6 ++++-- utils.py | 3 +++ 5 files changed, 24 insertions(+), 12 deletions(-) delete mode 100644 model_zerobox_test diff --git a/config.py b/config.py index 020472f..5c26f3e 100644 --- a/config.py +++ b/config.py @@ -36,8 +36,8 @@ # dataloader parameters n_transforms = 4 # number of transformations per sample in training n_transforms_test = 64 # number of transformations per sample in testing -batch_size = 24 # actual batch size is this value multiplied by n_transforms(_test) -batch_size_test = batch_size * n_transforms // n_transforms_test +batch_size = 10 # actual batch size is this value multiplied by n_transforms(_test) +batch_size_test = 1 # batch_size * n_transforms // n_transforms_test # total epochs = meta_epochs * sub_epochs # evaluation after epochs diff --git a/localization.py b/localization.py index 17e5778..34b7eb5 100644 --- a/localization.py +++ b/localization.py @@ -21,14 +21,24 @@ def save_imgs(inputs, grad, cnt): os.makedirs(export_dir) for g in range(grad.shape[0]): - normed_grad = (grad[g] - np.min(grad[g])) / ( - np.max(grad[g]) - np.min(grad[g])) + normed_grad = (grad[g] - np.min(grad[g])) / (np.max(grad[g]) - np.min(grad[g])) + # normed_grad = grad[g] + print("{:d}: minGrad={:.2e}, maxGrad={:.2e}, max/min={:.2e}".format( + cnt,np.min(grad[g]), np.max(grad[g]),np.max(grad[g])/np.min(grad[g]))) orig_image = inputs[g] - for image, file_suffix in [(normed_grad, '_gradient_map.png'), (orig_image, '_orig.png')]: + for image, file_suffix in [ + (normed_grad, "_gradient_map.png"), + (orig_image, "_orig.png"), + ]: plt.clf() plt.imshow(image) - plt.axis('off') - plt.savefig(os.path.join(export_dir, str(cnt) + file_suffix), bbox_inches='tight', pad_inches=0) + #plt.imshow(image, vmin=0, vmax=1e13) + plt.axis("off") + plt.savefig( + os.path.join(export_dir, str(cnt) + file_suffix), + bbox_inches="tight", + pad_inches=0, + ) cnt += 1 return cnt diff --git a/model_zerobox_test b/model_zerobox_test deleted file mode 100644 index c8907b3..0000000 --- a/model_zerobox_test +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ffdc4a17f435b31ceb6ec2b61cfd0cf1b4dc77693443998150eeeb78698307a -size 934360903 diff --git a/runTest.py b/runTest.py index d5318b5..9da001a 100644 --- a/runTest.py +++ b/runTest.py @@ -30,8 +30,9 @@ def test(model, test_loader): test_z = list() test_labels = list() with torch.no_grad(): - for i, data in enumerate(tqdm(test_loader, disable=c.hide_tqdm_bar)): + for i, data in enumerate(tqdm(test_loader, disable=c.hide_tqdm_bar)): inputs, labels = preprocess_batch(data) + print(f"i={i}: labels={labels}, size of inputs={inputs.size()}") z = model(inputs) loss = get_loss(z, model.nf.jacobian(run_forward=False)) test_z.append(z) @@ -94,7 +95,8 @@ def target_transform(target): # _, test_loader = make_dataloaders(train_set, test_set) test_loader = load_testloader("zerobox_dataset/zerobox_class/test") -model = torch.load("model_zerobox_test", map_location=torch.device('cpu')) +# model = torch.load("../zerobox-v2/zerobox_differnet_model.pt", map_location=torch.device('cpu')) +model = torch.load("models/zerobox_test.pt", map_location=torch.device('cpu')) print("starting to run tests after loaded model and test dataset") time_start = time.time() diff --git a/utils.py b/utils.py index af3881c..6412f57 100644 --- a/utils.py +++ b/utils.py @@ -100,6 +100,9 @@ def make_dataloaders(trainset, testset): def preprocess_batch(data): '''move data to device and reshape image''' inputs, labels = data + print(f"begin: size of inputs={inputs.size()}") inputs, labels = inputs.to(c.device), labels.to(c.device) + print(f"to: size of inputs={inputs.size()}") inputs = inputs.view(-1, *inputs.shape[-3:]) + print(f"view: size of inputs={inputs.size()}") return inputs, labels From 4116214313d7f6cc5ea8deee2f0e372dd24f7bb1 Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Sun, 20 Sep 2020 12:49:27 -0600 Subject: [PATCH 13/65] we don't need score_obs at testing: without good images (still need folder) --- multi_transform_loader.py | 3 +++ runTest.py | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/multi_transform_loader.py b/multi_transform_loader.py index edfb5b8..f576987 100644 --- a/multi_transform_loader.py +++ b/multi_transform_loader.py @@ -46,11 +46,14 @@ def __getitem__(self, index): samples = list() for i in range(self.n_transforms): if self.get_fixed: + print(f"i={i}: calling fixed_rotation({sample}, {self.fixed_degrees[i]}))") samples.append(fixed_rotation(self, sample, self.fixed_degrees[i])) else: + print(f"i={i}: calling transform({sample})") samples.append(self.transform(sample)) samples = torch.stack(samples, dim=0) if self.target_transform is not None: + print(f"calling target_transform({target})") target = self.target_transform(target) return samples, target diff --git a/runTest.py b/runTest.py index 9da001a..68692ac 100644 --- a/runTest.py +++ b/runTest.py @@ -19,7 +19,7 @@ def test(model, test_loader): print("Running test") optimizer = torch.optim.Adam(model.nf.parameters(), lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, weight_decay=1e-5) - score_obs = Score_Observer('AUROC') + # score_obs = Score_Observer('AUROC') # evaluate model.to(c.device) model.eval() @@ -49,10 +49,11 @@ def test(model, test_loader): z_grouped = torch.cat(test_z, dim=0).view(-1, c.n_transforms_test, c.n_feat) anomaly_score = t2np(torch.mean(z_grouped ** 2, dim=(-2, -1))) print(f"test_labels={test_labels}, is_anomaly={is_anomaly},anomaly_score={anomaly_score}") - score_obs.update(roc_auc_score(is_anomaly, anomaly_score), epoch, - print_score=c.verbose or epoch == c.meta_epochs - 1) + # score_obs.update(roc_auc_score(is_anomaly, anomaly_score), epoch, + # print_score=c.verbose or epoch == c.meta_epochs - 1) if c.grad_map_viz: + print("saving gradient maps...") export_gradient_maps(model, test_loader, optimizer, -1) def load_testloader(data_dir_test): From 394ba7e988dad17a0ed701d28e1bd9440053874d Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Sun, 20 Sep 2020 12:53:46 -0600 Subject: [PATCH 14/65] cleanup lfs --- .gitattributes | 1 - runTest.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.gitattributes b/.gitattributes index 5689b70..e69de29 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +0,0 @@ -model_zerobox_test filter=lfs diff=lfs merge=lfs -text diff --git a/runTest.py b/runTest.py index 68692ac..b12553a 100644 --- a/runTest.py +++ b/runTest.py @@ -52,9 +52,9 @@ def test(model, test_loader): # score_obs.update(roc_auc_score(is_anomaly, anomaly_score), epoch, # print_score=c.verbose or epoch == c.meta_epochs - 1) - if c.grad_map_viz: - print("saving gradient maps...") - export_gradient_maps(model, test_loader, optimizer, -1) + # if c.grad_map_viz: + # print("saving gradient maps...") + # export_gradient_maps(model, test_loader, optimizer, -1) def load_testloader(data_dir_test): def target_transform(target): From c484fac12e75be3f0d471efd444a5ea05f3a56c8 Mon Sep 17 00:00:00 2001 From: Shi Jin Date: Mon, 21 Sep 2020 15:04:09 -0600 Subject: [PATCH 15/65] consistent results with zerobox test_differnet.py --- config.py | 9 +++++---- localization.py | 27 ++++++++++++++++++++++++--- multi_transform_loader.py | 9 +++++---- runTest.py | 35 ++++++++++++++++++++--------------- 4 files changed, 54 insertions(+), 26 deletions(-) diff --git a/config.py b/config.py index 5c26f3e..4e866a2 100644 --- a/config.py +++ b/config.py @@ -1,10 +1,11 @@ '''This file configures the training procedure because handling arguments in every single function is so exhaustive for research purposes. Don't try this code if you are a software engineer.''' +import torch # device settings # device = 'cuda' # or 'cpu' device = 'cpu' # or 'cuda' -import torch + #torch.cuda.set_device(0) # data settings @@ -18,7 +19,7 @@ add_img_noise = 0.01 # transformation settings -transf_rotations = True +transf_rotations = False transf_brightness = 0.0 transf_contrast = 0.0 transf_saturation = 0.0 @@ -34,9 +35,9 @@ n_feat = 256 * n_scales # do not change except you change the feature extractor # dataloader parameters -n_transforms = 4 # number of transformations per sample in training +n_transforms = 1 # number of transformations per sample in training n_transforms_test = 64 # number of transformations per sample in testing -batch_size = 10 # actual batch size is this value multiplied by n_transforms(_test) +batch_size = 1 # actual batch size is this value multiplied by n_transforms(_test) batch_size_test = 1 # batch_size * n_transforms // n_transforms_test # total epochs = meta_epochs * sub_epochs diff --git a/localization.py b/localization.py index 34b7eb5..b4b6f36 100644 --- a/localization.py +++ b/localization.py @@ -16,6 +16,7 @@ def save_imgs(inputs, grad, cnt): + print(f"calling save_image(input={inputs.shape}, grad={grad.shape})") export_dir = os.path.join(GRADIENT_MAP_DIR, c.modelname) if not os.path.exists(export_dir): os.makedirs(export_dir) @@ -23,8 +24,8 @@ def save_imgs(inputs, grad, cnt): for g in range(grad.shape[0]): normed_grad = (grad[g] - np.min(grad[g])) / (np.max(grad[g]) - np.min(grad[g])) # normed_grad = grad[g] - print("{:d}: minGrad={:.2e}, maxGrad={:.2e}, max/min={:.2e}".format( - cnt,np.min(grad[g]), np.max(grad[g]),np.max(grad[g])/np.min(grad[g]))) + print("cnt={:d}/g={:d}: minGrad={:.2e}, maxGrad={:.2e}, max/min={:.2e}".format( + cnt, g, np.min(grad[g]), np.max(grad[g]), np.max(grad[g])/np.min(grad[g]))) orig_image = inputs[g] for image, file_suffix in [ (normed_grad, "_gradient_map.png"), @@ -35,7 +36,7 @@ def save_imgs(inputs, grad, cnt): #plt.imshow(image, vmin=0, vmax=1e13) plt.axis("off") plt.savefig( - os.path.join(export_dir, str(cnt) + file_suffix), + os.path.join(export_dir, f"{cnt}_{g}" + file_suffix), bbox_inches="tight", pad_inches=0, ) @@ -53,6 +54,7 @@ def export_gradient_maps(model, testloader, optimizer, n_batches=1): for i, data in enumerate(tqdm(testloader, disable=c.hide_tqdm_bar)): optimizer.zero_grad() inputs, labels = preprocess_batch(data) + print(f"i={i}: inputs={inputs.shape}, labels={labels}") inputs = Variable(inputs, requires_grad=True) emb = model(inputs) @@ -65,10 +67,19 @@ def export_gradient_maps(model, testloader, optimizer, n_batches=1): continue grad = t2np(grad) + print(f"origina: shape of inputs={inputs.shape}") + allInputs = inputs.view(c.n_transforms_test, *inputs.shape[-3:]) inputs = inputs.view(-1, c.n_transforms_test, *inputs.shape[-3:])[:, 0] + + print(f"view: shape of inputs={inputs.shape}, allInputs={allInputs.shape}") inputs = np.transpose(t2np(inputs[labels > 0]), [0, 2, 3, 1]) + allInputs = np.transpose(t2np(allInputs), [0, 2, 3, 1]) + + print(f"transpose: shape of inputs={inputs.shape}, allInputs={allInputs.shape}") inputs_unnormed = np.clip(inputs * c.norm_std + c.norm_mean, 0, 1) + print(f"shape of inputs={inputs.shape},inputs_unnormed={inputs_unnormed.shape}") + images = np.zeros([c.n_transforms_test,480, 270, 3]) for i_item in range(c.n_transforms_test): old_shape = grad[:, i_item].shape img = np.reshape(grad[:, i_item], [-1, *grad.shape[-2:]]) @@ -76,10 +87,20 @@ def export_gradient_maps(model, testloader, optimizer, n_batches=1): img = np.transpose(rotate(img, degrees[i_item], reshape=False), [2, 0, 1]) img = gaussian_filter(img, (0, 3, 3)) grad[:, i_item] = np.reshape(img, old_shape) + # print(f"shape of img={img.shape}, grad={grad.shape}") + # PyTorch tensors assume the color channel is the first dimension + # but matplotlib assumes is the third dimension + images[i_item, :] = img.transpose((1, 2, 0)) + + #save_imgs(allInputs,images,0) + grad = np.reshape(grad, [grad.shape[0], -1, *grad.shape[-2:]]) grad_img = np.mean(np.abs(grad), axis=1) grad_img_sq = grad_img ** 2 + print(f"shape of grad={grad.shape}, grad_img={grad_img.shape}") + # print(f"inputs_unnormed={inputs_unnormed}") + # print(f"grad_img_sq={grad_img_sq}") cnt = save_imgs(inputs_unnormed, grad_img_sq, cnt) diff --git a/multi_transform_loader.py b/multi_transform_loader.py index f576987..a7d18e7 100644 --- a/multi_transform_loader.py +++ b/multi_transform_loader.py @@ -46,11 +46,12 @@ def __getitem__(self, index): samples = list() for i in range(self.n_transforms): if self.get_fixed: - print(f"i={i}: calling fixed_rotation({sample}, {self.fixed_degrees[i]}))") + # print(f"i={i}: calling fixed_rotation({sample}, {self.fixed_degrees[i]}))") samples.append(fixed_rotation(self, sample, self.fixed_degrees[i])) - else: - print(f"i={i}: calling transform({sample})") - samples.append(self.transform(sample)) + else: + new = self.transform(sample) + samples.append(new) + # print(f"i={i}: calling transform({sample})") samples = torch.stack(samples, dim=0) if self.target_transform is not None: print(f"calling target_transform({target})") diff --git a/runTest.py b/runTest.py index b12553a..6ad53ed 100644 --- a/runTest.py +++ b/runTest.py @@ -15,6 +15,7 @@ import time from utils import * from localization import export_gradient_maps +from torch.autograd import Variable def test(model, test_loader): print("Running test") @@ -23,21 +24,25 @@ def test(model, test_loader): # evaluate model.to(c.device) model.eval() + # print(f"model={model}") epoch = 0 if c.verbose: print('\nCompute loss and scores on test set:') test_loss = list() test_z = list() test_labels = list() - with torch.no_grad(): - for i, data in enumerate(tqdm(test_loader, disable=c.hide_tqdm_bar)): - inputs, labels = preprocess_batch(data) - print(f"i={i}: labels={labels}, size of inputs={inputs.size()}") - z = model(inputs) - loss = get_loss(z, model.nf.jacobian(run_forward=False)) - test_z.append(z) - test_loss.append(t2np(loss)) - test_labels.append(t2np(labels)) + #with torch.no_grad(): + for i, data in enumerate(tqdm(test_loader, disable=c.hide_tqdm_bar)): + inputs, labels = preprocess_batch(data) + #inputs = Variable(inputs, requires_grad=True) + print(f"i={i}: labels={labels}, size of inputs={inputs.size()}") + # print(f"inputs={inputs}") + z = model(inputs) + # print(f"z={z}") + loss = get_loss(z, model.nf.jacobian(run_forward=False)) + test_z.append(z) + test_loss.append(t2np(loss)) + test_labels.append(t2np(labels)) test_loss = np.mean(np.array(test_loss)) if c.verbose: @@ -52,9 +57,9 @@ def test(model, test_loader): # score_obs.update(roc_auc_score(is_anomaly, anomaly_score), epoch, # print_score=c.verbose or epoch == c.meta_epochs - 1) - # if c.grad_map_viz: - # print("saving gradient maps...") - # export_gradient_maps(model, test_loader, optimizer, -1) + if c.grad_map_viz: + print("saving gradient maps...") + export_gradient_maps(model, test_loader, optimizer, -1) def load_testloader(data_dir_test): def target_transform(target): @@ -75,8 +80,8 @@ def target_transform(target): class_idx += 1 augmentative_transforms = [] - if c.transf_rotations: - augmentative_transforms += [transforms.RandomRotation(180)] + # if c.transf_rotations: + # augmentative_transforms += [transforms.RandomRotation(180)] if c.transf_brightness > 0.0 or c.transf_contrast > 0.0 or c.transf_saturation > 0.0: augmentative_transforms += [transforms.ColorJitter(brightness=c.transf_brightness, contrast=c.transf_contrast, saturation=c.transf_saturation)] @@ -95,7 +100,7 @@ def target_transform(target): # train_set, test_set = load_datasets(c.dataset_path, c.class_name) # _, test_loader = make_dataloaders(train_set, test_set) -test_loader = load_testloader("zerobox_dataset/zerobox_class/test") +test_loader = load_testloader("group15B.avi/") # model = torch.load("../zerobox-v2/zerobox_differnet_model.pt", map_location=torch.device('cpu')) model = torch.load("models/zerobox_test.pt", map_location=torch.device('cpu')) From 5f88cd58bbde1ae8c9c8d6d84273329cc9ba2a8f Mon Sep 17 00:00:00 2001 From: lihyin Date: Sun, 4 Oct 2020 20:09:36 -0600 Subject: [PATCH 16/65] Separate validation and test datasets; Add test.py for test only --- config.py | 32 +++++++++--------- main.py | 23 +++++++++---- test.py | 26 +++++++++++++++ train.py | 97 +++++++++++++++++++++++++++++++++++++++++++++++++------ utils.py | 39 +++++++++++++++------- 5 files changed, 174 insertions(+), 43 deletions(-) create mode 100644 test.py diff --git a/config.py b/config.py index 4e866a2..6b42fc8 100644 --- a/config.py +++ b/config.py @@ -1,25 +1,22 @@ '''This file configures the training procedure because handling arguments in every single function is so exhaustive for research purposes. Don't try this code if you are a software engineer.''' -import torch # device settings -# device = 'cuda' # or 'cpu' -device = 'cpu' # or 'cuda' - -#torch.cuda.set_device(0) +device = 'cpu' # 'cuda' or 'cpu' +import torch +torch.cuda.set_device(0) # data settings -dataset_path = "zerobox_dataset" -class_name = "zerobox_class" -modelname = "zerobox_test" +dataset_path = "dataset" +class_name = "zerobox-2009-5" +modelname = "zerobox-2009-5" -# img_size = (448, 448) -img_size = (480, 270) +img_size = (448, 448) img_dims = [3] + list(img_size) add_img_noise = 0.01 # transformation settings -transf_rotations = False +transf_rotations = True transf_brightness = 0.0 transf_contrast = 0.0 transf_saturation = 0.0 @@ -29,20 +26,21 @@ n_scales = 3 # number of scales at which features are extracted, img_size is the highest - others are //2, //4,... clamp_alpha = 3 # see paper equation 2 for explanation n_coupling_blocks = 8 -fc_internal = 2048 # number of neurons in hidden layers of s-t-networks +# fc_internal = 2048 # number of neurons in hidden layers of s-t-networks +fc_internal = 1536 # number of neurons in hidden layers of s-t-networks dropout = 0.0 # dropout in s-t-networks lr_init = 2e-4 n_feat = 256 * n_scales # do not change except you change the feature extractor # dataloader parameters -n_transforms = 1 # number of transformations per sample in training -n_transforms_test = 64 # number of transformations per sample in testing -batch_size = 1 # actual batch size is this value multiplied by n_transforms(_test) -batch_size_test = 1 # batch_size * n_transforms // n_transforms_test +n_transforms = 4 # number of transformations per sample in training +n_transforms_test = 16 # number of transformations per sample in testing +batch_size = 4 # actual batch size is this value multiplied by n_transforms(_test) +batch_size_test = batch_size * n_transforms // n_transforms_test # total epochs = meta_epochs * sub_epochs # evaluation after epochs -meta_epochs = 10 +meta_epochs = 1 sub_epochs = 8 # output settings diff --git a/main.py b/main.py index d78c368..d106d0d 100644 --- a/main.py +++ b/main.py @@ -4,15 +4,26 @@ For further information contact Marco Rudolph (rudolph@tnt.uni-hannover.de)''' import config as c -from train import train +from train import * from utils import load_datasets, make_dataloaders import time +import gc + +train_set, validate_set, _ = load_datasets(c.dataset_path, c.class_name) +train_loader, validate_loader, _ = make_dataloaders(train_set, validate_set, None) -train_set, test_set = load_datasets(c.dataset_path, c.class_name) -train_loader, test_loader = make_dataloaders(train_set, test_set) time_start = time.time() -#model = train(train_loader, test_loader) -model = train(train_loader, None) +model, model_parameters = train(train_loader, validate_loader) +#model, model_config = train(train_loader, None) time_end = time.time() time_c = time_end - time_start # 运行所花时间 -print("time cost: {:f} s".format(time_c)) \ No newline at end of file +print("train time cost: {:f} s".format(time_c)) + +# free memory +del train_set +del validate_set +del train_loader +del validate_loader + +gc.collect() +torch.cuda.empty_cache() \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..951be13 --- /dev/null +++ b/test.py @@ -0,0 +1,26 @@ +'''This is the repo which contains the original code to the WACV 2021 paper +"Same Same But DifferNet: Semi-Supervised Defect Detection with Normalizing Flows" +by Marco Rudolph, Bastian Wandt and Bodo Rosenhahn. +For further information contact Marco Rudolph (rudolph@tnt.uni-hannover.de)''' + +import config as c +from train import * +from utils import load_datasets, make_dataloaders +import time +import gc +import json + +_, _, test_set = load_datasets(c.dataset_path, c.class_name, test=True) +_, _, test_loader = make_dataloaders(None, None, test_set, test=True) + +model = torch.load("models/" + c.modelname + "", map_location=torch.device('cpu')) + +with open('models/' + c.modelname + '.json') as jsonfile: + model_parameters = json.load(jsonfile) + +time_start = time.time() +test(model, model_parameters, test_loader) +time_end = time.time() +time_c = time_end - time_start # 运行所花时间 + +print("test time cost: {:f} s".format(time_c)) \ No newline at end of file diff --git a/train.py b/train.py index 9f3529f..f453eec 100644 --- a/train.py +++ b/train.py @@ -1,6 +1,7 @@ import numpy as np import torch from sklearn.metrics import roc_auc_score +from sklearn.metrics import roc_curve from tqdm import tqdm import config as c @@ -8,6 +9,7 @@ from model import DifferNet, save_model, save_weights from utils import * +import json class Score_Observer: '''Keeps an eye on the current and highest score so far''' @@ -31,7 +33,7 @@ def print_score(self): self.max_epoch)) -def train(train_loader, test_loader): +def train(train_loader, validate_loader): model = DifferNet() optimizer = torch.optim.Adam(model.nf.parameters(), lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, weight_decay=1e-5) model.to(c.device) @@ -62,16 +64,16 @@ def train(train_loader, test_loader): if c.verbose: print('Epoch: {:d}.{:d} \t train loss: {:.4f}'.format(epoch, sub_epoch, mean_train_loss)) - if not (test_loader is None): + if not (validate_loader is None): # evaluate model.eval() if c.verbose: - print('\nCompute loss and scores on test set:') + print('\nCompute loss and scores on validate set:') test_loss = list() test_z = list() test_labels = list() with torch.no_grad(): - for i, data in enumerate(tqdm(test_loader, disable=c.hide_tqdm_bar)): + for i, data in enumerate(tqdm(validate_loader, disable=c.hide_tqdm_bar)): inputs, labels = preprocess_batch(data) z = model(inputs) loss = get_loss(z, model.nf.jacobian(run_forward=False)) @@ -80,8 +82,6 @@ def train(train_loader, test_loader): test_labels.append(t2np(labels)) test_loss = np.mean(np.array(test_loss)) - if c.verbose: - print('Epoch: {:d} \t test_loss: {:.4f}'.format(epoch, test_loss)) test_labels = np.concatenate(test_labels) is_anomaly = np.array([0 if l == 0 else 1 for l in test_labels]) @@ -91,11 +91,90 @@ def train(train_loader, test_loader): score_obs.update(roc_auc_score(is_anomaly, anomaly_score), epoch, print_score=c.verbose or epoch == c.meta_epochs - 1) -# if c.grad_map_viz and not (test_loader is None): -# export_gradient_maps(model, test_loader, optimizer, -1) + fpr, tpr, thresholds = roc_curve(is_anomaly, anomaly_score) + model_parameters = {} + model_parameters['fpr'] = fpr.tolist() + model_parameters['tpr'] = tpr.tolist() + model_parameters['thresholds'] = thresholds.tolist() + + with open('models/' + c.modelname + '.json', 'w') as jsonfile: + jsonfile.write(json.dumps(model_parameters)) + + if c.verbose: + print('Epoch: {:d} \t validate_loss: {:.4f}'.format(epoch, test_loss)) + + # compare is_anomaly and anomaly_score + np.set_printoptions(precision=2, suppress=True) + print('is_anomaly: ', is_anomaly) + print('anomaly_score: ', anomaly_score) + print('fpr: ', fpr) + print('tpr: ', tpr) + print('thresholds: ', thresholds) + +# if c.grad_map_viz and not (validate_loader is None): +# export_gradient_maps(model, validate_loader, optimizer, -1) if c.save_model: model.to('cpu') save_model(model, c.modelname) save_weights(model, c.modelname) - return model + + return model, model_parameters + +def test(model, model_parameters, test_loader): + print("Running test") + optimizer = torch.optim.Adam(model.nf.parameters(), lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, + weight_decay=1e-5) + # score_obs = Score_Observer('AUROC') + # evaluate + model.to(c.device) + model.eval() + # print(f"model={model}") + epoch = 0 + if c.verbose: + print('\nCompute loss and scores on test set:') + test_loss = list() + test_z = list() + test_labels = list() + # with torch.no_grad(): + for i, data in enumerate(tqdm(test_loader, disable=c.hide_tqdm_bar)): + inputs, labels = preprocess_batch(data) + # inputs = Variable(inputs, requires_grad=True) + print(f"i={i}: labels={labels}, size of inputs={inputs.size()}") + # print(f"inputs={inputs}") + z = model(inputs) + # print(f"z={z}") + loss = get_loss(z, model.nf.jacobian(run_forward=False)) + test_z.append(z) + test_loss.append(t2np(loss)) + test_labels.append(t2np(labels)) + + test_loss = np.mean(np.array(test_loss)) + if c.verbose: + print('Epoch: {:d} \t test_loss: {:.4f}'.format(epoch, test_loss)) + + test_labels = np.concatenate(test_labels) + is_anomaly = np.array([0 if l == 0 else 1 for l in test_labels]) + + z_grouped = torch.cat(test_z, dim=0).view(-1, c.n_transforms_test, c.n_feat) + anomaly_score = t2np(torch.mean(z_grouped ** 2, dim=(-2, -1))) + # score_obs.update(roc_auc_score(is_anomaly, anomaly_score), epoch, + # print_score=c.verbose or epoch == c.meta_epochs - 1) + + # get the threshold for target true positive rate + for i in range(len(model_parameters['tpr'])): + if model_parameters['tpr'][i] > model_parameters['target_tpr']: + target_threshold = thresholds[i] + + is_anomaly_detected = np.array([0 if l < target_threshold else 1 for l in anomaly_score]) + + # calculate test accuracy + error_count = 0 + for i in range(len(is_anomaly)): + if is_anomaly[i] != is_anomaly_detected[i]: + error_count += 1 + + test_accuracy = 1 - float(error_count) / len(is_anomaly) + + print(f"test_labels={test_labels}, is_anomaly={is_anomaly},anomaly_score={anomaly_score},is_anomaly_detected={is_anomaly_detected}") + print(f"target_tpr={c.target_tpr}, target_threshold={target_threshold}, test_accuracy={test_accuracy}") diff --git a/utils.py b/utils.py index 6412f57..2c90434 100644 --- a/utils.py +++ b/utils.py @@ -17,7 +17,7 @@ def get_loss(z, jac): return torch.mean(0.5 * torch.sum(z ** 2, dim=(1,)) - jac) / z.shape[1] -def load_datasets(dataset_path, class_name): +def load_datasets(dataset_path, class_name, test=False): ''' Expected folder/file format to find anomalies of class from dataset location : @@ -55,9 +55,10 @@ def target_transform(target): return class_perm[target] data_dir_train = os.path.join(dataset_path, class_name, 'train') + data_dir_validate = os.path.join(dataset_path, class_name, 'validate') data_dir_test = os.path.join(dataset_path, class_name, 'test') - classes = os.listdir(data_dir_test) + classes = os.listdir(data_dir_validate) if 'good' not in classes: print('There should exist a subdirectory "good". Read the doc of this function for further information.') exit() @@ -83,18 +84,34 @@ def target_transform(target): transform_train = transforms.Compose(tfs) - trainset = ImageFolderMultiTransform(data_dir_train, transform=transform_train, n_transforms=c.n_transforms) - testset = ImageFolderMultiTransform(data_dir_test, transform=transform_train, target_transform=target_transform, + trainset = None + validateset = None + testset = None + if test == False: + trainset = ImageFolderMultiTransform(data_dir_train, transform=transform_train, n_transforms=c.n_transforms) + validateset = ImageFolderMultiTransform(data_dir_validate, transform=transform_train, target_transform=target_transform, n_transforms=c.n_transforms_test) - return trainset, testset + else: + testset = ImageFolderMultiTransform(data_dir_test, transform=transform_train, target_transform=target_transform, + n_transforms=c.n_transforms_test) + + return trainset, validateset, testset + +def make_dataloaders(trainset, validateset, testset, test=False): + trainloader = None + validateloader = None + testloader = None + if test == False: + trainloader = torch.utils.data.DataLoader(trainset, pin_memory=True, batch_size=c.batch_size, shuffle=True, + drop_last=False) + validateloader = torch.utils.data.DataLoader(validateset, pin_memory=True, batch_size=c.batch_size, shuffle=True, + drop_last=False) + else: + testloader = torch.utils.data.DataLoader(testset, pin_memory=True, batch_size=c.batch_size_test, shuffle=True, + drop_last=False) -def make_dataloaders(trainset, testset): - trainloader = torch.utils.data.DataLoader(trainset, pin_memory=True, batch_size=c.batch_size, shuffle=True, - drop_last=False) - testloader = torch.utils.data.DataLoader(testset, pin_memory=True, batch_size=c.batch_size_test, shuffle=True, - drop_last=False) - return trainloader, testloader + return trainloader, validateloader, testloader def preprocess_batch(data): From 8fccb269f8ee015510b6e7d0dbec966d68715341 Mon Sep 17 00:00:00 2001 From: lihyin Date: Mon, 5 Oct 2020 00:14:26 -0600 Subject: [PATCH 17/65] Fix how to get target_threshold; Add target_tpr in config.py; --- config.py | 2 ++ train.py | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/config.py b/config.py index 6b42fc8..833b1f9 100644 --- a/config.py +++ b/config.py @@ -48,3 +48,5 @@ grad_map_viz = True hide_tqdm_bar = True save_model = True + +target_tpr = 0.85 diff --git a/train.py b/train.py index f453eec..1982918 100644 --- a/train.py +++ b/train.py @@ -163,8 +163,9 @@ def test(model, model_parameters, test_loader): # get the threshold for target true positive rate for i in range(len(model_parameters['tpr'])): - if model_parameters['tpr'][i] > model_parameters['target_tpr']: - target_threshold = thresholds[i] + if model_parameters['tpr'][i] > c.target_tpr: + target_threshold = model_parameters['thresholds'][i] + break is_anomaly_detected = np.array([0 if l < target_threshold else 1 for l in anomaly_score]) From 27c831d6c1deb032548c0c4c73858e004773f7cc Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sun, 25 Oct 2020 00:13:20 -0600 Subject: [PATCH 18/65] added new function differences_as_input. instead of using the input images, we calculate the differences between each set of 2 images, then we feed the differences as our input to the model --- utils.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/utils.py b/utils.py index 2c90434..3acaaca 100644 --- a/utils.py +++ b/utils.py @@ -117,9 +117,34 @@ def make_dataloaders(trainset, validateset, testset, test=False): def preprocess_batch(data): '''move data to device and reshape image''' inputs, labels = data + inputs = differences_as_input(inputs) + print(f"begin: size of inputs={inputs.size()}") inputs, labels = inputs.to(c.device), labels.to(c.device) print(f"to: size of inputs={inputs.size()}") inputs = inputs.view(-1, *inputs.shape[-3:]) print(f"view: size of inputs={inputs.size()}") return inputs, labels + +def differences_as_input(inputs): + # instead of using the input images, we calculate the differences between each set of 2 images + # then we feed the differences as our input to the model + num_batch = inputs.shape[0] + for i in range(num_batch): + diff_list = [] + num_transformed_images = inputs.shape[1] + for j in range(num_transformed_images): + if j % 2 == 0: + # calculate the differences between each set of 2 images + # append the differences into a new list + diff_list.append(inputs[i][j+1]-inputs[0][j]) + + diff_list = torch.stack(diff_list, 0) + diff_list = diff_list.unsqueeze(1).permute(1, 0, 2, 3, 4) + + # cat the differences between images from each batch into the entire batch group + if i == 0: + diff_inputs = diff_list + else: + diff_inputs = torch.cat((diff_inputs, diff_list), 0) + return diff_inputs From 03b70c3872ca2473d46028af8a1bb6bd1d899000 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sun, 25 Oct 2020 01:19:37 -0600 Subject: [PATCH 19/65] updated differences function --- utils.py | 302 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 152 insertions(+), 150 deletions(-) diff --git a/utils.py b/utils.py index 3acaaca..b59761a 100644 --- a/utils.py +++ b/utils.py @@ -1,150 +1,152 @@ -import os -import torch -from torch.utils.data import DataLoader -from torchvision import datasets, transforms - -import config as c -from multi_transform_loader import ImageFolderMultiTransform - - -def t2np(tensor): - '''pytorch tensor -> numpy array''' - return tensor.cpu().data.numpy() if tensor is not None else None - - -def get_loss(z, jac): - '''check equation 4 of the paper why this makes sense - oh and just ignore the scaling here''' - return torch.mean(0.5 * torch.sum(z ** 2, dim=(1,)) - jac) / z.shape[1] - - -def load_datasets(dataset_path, class_name, test=False): - ''' - Expected folder/file format to find anomalies of class from dataset location : - - train data: - - dataset_path/class_name/train/good/any_filename.png - dataset_path/class_name/train/good/another_filename.tif - dataset_path/class_name/train/good/xyz.png - [...] - - test data: - - 'normal data' = non-anomalies - - dataset_path/class_name/test/good/name_the_file_as_you_like_as_long_as_there_is_an_image_extension.webp - dataset_path/class_name/test/good/did_you_know_the_image_extension_webp?.png - dataset_path/class_name/test/good/did_you_know_that_filenames_may_contain_question_marks????.png - dataset_path/class_name/test/good/dont_know_how_it_is_with_windows.png - dataset_path/class_name/test/good/just_dont_use_windows_for_this.png - [...] - - anomalies - assume there are anomaly classes 'crack' and 'curved' - - dataset_path/class_name/test/crack/dat_crack_damn.png - dataset_path/class_name/test/crack/let_it_crack.png - dataset_path/class_name/test/crack/writing_docs_is_fun.png - [...] - - dataset_path/class_name/test/curved/wont_make_a_difference_if_you_put_all_anomalies_in_one_class.png - dataset_path/class_name/test/curved/but_this_code_is_practicable_for_the_mvtec_dataset.png - [...] - ''' - - def target_transform(target): - return class_perm[target] - - data_dir_train = os.path.join(dataset_path, class_name, 'train') - data_dir_validate = os.path.join(dataset_path, class_name, 'validate') - data_dir_test = os.path.join(dataset_path, class_name, 'test') - - classes = os.listdir(data_dir_validate) - if 'good' not in classes: - print('There should exist a subdirectory "good". Read the doc of this function for further information.') - exit() - classes.sort() - class_perm = list() - class_idx = 1 - for cl in classes: - if cl == 'good': - class_perm.append(0) - else: - class_perm.append(class_idx) - class_idx += 1 - - augmentative_transforms = [] - if c.transf_rotations: - augmentative_transforms += [transforms.RandomRotation(180)] - if c.transf_brightness > 0.0 or c.transf_contrast > 0.0 or c.transf_saturation > 0.0: - augmentative_transforms += [transforms.ColorJitter(brightness=c.transf_brightness, contrast=c.transf_contrast, - saturation=c.transf_saturation)] - - tfs = [transforms.Resize(c.img_size)] + augmentative_transforms + [transforms.ToTensor(), - transforms.Normalize(c.norm_mean, c.norm_std)] - - transform_train = transforms.Compose(tfs) - - trainset = None - validateset = None - testset = None - if test == False: - trainset = ImageFolderMultiTransform(data_dir_train, transform=transform_train, n_transforms=c.n_transforms) - validateset = ImageFolderMultiTransform(data_dir_validate, transform=transform_train, target_transform=target_transform, - n_transforms=c.n_transforms_test) - else: - testset = ImageFolderMultiTransform(data_dir_test, transform=transform_train, target_transform=target_transform, - n_transforms=c.n_transforms_test) - - return trainset, validateset, testset - - -def make_dataloaders(trainset, validateset, testset, test=False): - trainloader = None - validateloader = None - testloader = None - if test == False: - trainloader = torch.utils.data.DataLoader(trainset, pin_memory=True, batch_size=c.batch_size, shuffle=True, - drop_last=False) - validateloader = torch.utils.data.DataLoader(validateset, pin_memory=True, batch_size=c.batch_size, shuffle=True, - drop_last=False) - else: - testloader = torch.utils.data.DataLoader(testset, pin_memory=True, batch_size=c.batch_size_test, shuffle=True, - drop_last=False) - - return trainloader, validateloader, testloader - - -def preprocess_batch(data): - '''move data to device and reshape image''' - inputs, labels = data - inputs = differences_as_input(inputs) - - print(f"begin: size of inputs={inputs.size()}") - inputs, labels = inputs.to(c.device), labels.to(c.device) - print(f"to: size of inputs={inputs.size()}") - inputs = inputs.view(-1, *inputs.shape[-3:]) - print(f"view: size of inputs={inputs.size()}") - return inputs, labels - -def differences_as_input(inputs): - # instead of using the input images, we calculate the differences between each set of 2 images - # then we feed the differences as our input to the model - num_batch = inputs.shape[0] - for i in range(num_batch): - diff_list = [] - num_transformed_images = inputs.shape[1] - for j in range(num_transformed_images): - if j % 2 == 0: - # calculate the differences between each set of 2 images - # append the differences into a new list - diff_list.append(inputs[i][j+1]-inputs[0][j]) - - diff_list = torch.stack(diff_list, 0) - diff_list = diff_list.unsqueeze(1).permute(1, 0, 2, 3, 4) - - # cat the differences between images from each batch into the entire batch group - if i == 0: - diff_inputs = diff_list - else: - diff_inputs = torch.cat((diff_inputs, diff_list), 0) - return diff_inputs +import os +import torch +from torch.utils.data import DataLoader +from torchvision import datasets, transforms + +import config as c +from multi_transform_loader import ImageFolderMultiTransform + + +def t2np(tensor): + '''pytorch tensor -> numpy array''' + return tensor.cpu().data.numpy() if tensor is not None else None + + +def get_loss(z, jac): + '''check equation 4 of the paper why this makes sense - oh and just ignore the scaling here''' + return torch.mean(0.5 * torch.sum(z ** 2, dim=(1,)) - jac) / z.shape[1] + + +def load_datasets(dataset_path, class_name, test=False): + ''' + Expected folder/file format to find anomalies of class from dataset location : + + train data: + + dataset_path/class_name/train/good/any_filename.png + dataset_path/class_name/train/good/another_filename.tif + dataset_path/class_name/train/good/xyz.png + [...] + + test data: + + 'normal data' = non-anomalies + + dataset_path/class_name/test/good/name_the_file_as_you_like_as_long_as_there_is_an_image_extension.webp + dataset_path/class_name/test/good/did_you_know_the_image_extension_webp?.png + dataset_path/class_name/test/good/did_you_know_that_filenames_may_contain_question_marks????.png + dataset_path/class_name/test/good/dont_know_how_it_is_with_windows.png + dataset_path/class_name/test/good/just_dont_use_windows_for_this.png + [...] + + anomalies - assume there are anomaly classes 'crack' and 'curved' + + dataset_path/class_name/test/crack/dat_crack_damn.png + dataset_path/class_name/test/crack/let_it_crack.png + dataset_path/class_name/test/crack/writing_docs_is_fun.png + [...] + + dataset_path/class_name/test/curved/wont_make_a_difference_if_you_put_all_anomalies_in_one_class.png + dataset_path/class_name/test/curved/but_this_code_is_practicable_for_the_mvtec_dataset.png + [...] + ''' + + def target_transform(target): + return class_perm[target] + + data_dir_train = os.path.join(dataset_path, class_name, 'train') + data_dir_validate = os.path.join(dataset_path, class_name, 'validate') + data_dir_test = os.path.join(dataset_path, class_name, 'test') + + classes = os.listdir(data_dir_validate) + if 'good' not in classes: + print('There should exist a subdirectory "good". Read the doc of this function for further information.') + exit() + classes.sort() + class_perm = list() + class_idx = 1 + for cl in classes: + if cl == 'good': + class_perm.append(0) + else: + class_perm.append(class_idx) + class_idx += 1 + + augmentative_transforms = [] + if c.transf_rotations: + augmentative_transforms += [transforms.RandomRotation(180)] + if c.transf_brightness > 0.0 or c.transf_contrast > 0.0 or c.transf_saturation > 0.0: + augmentative_transforms += [transforms.ColorJitter(brightness=c.transf_brightness, contrast=c.transf_contrast, + saturation=c.transf_saturation)] + + tfs = [transforms.Resize(c.img_size)] + augmentative_transforms + [transforms.ToTensor(), + transforms.Normalize(c.norm_mean, c.norm_std)] + + transform_train = transforms.Compose(tfs) + + trainset = None + validateset = None + testset = None + if test == False: + trainset = ImageFolderMultiTransform(data_dir_train, transform=transform_train, n_transforms=c.n_transforms) + validateset = ImageFolderMultiTransform(data_dir_validate, transform=transform_train, target_transform=target_transform, + n_transforms=c.n_transforms_test) + else: + testset = ImageFolderMultiTransform(data_dir_test, transform=transform_train, target_transform=target_transform, + n_transforms=c.n_transforms_test) + + return trainset, validateset, testset + + +def make_dataloaders(trainset, validateset, testset, test=False): + trainloader = None + validateloader = None + testloader = None + if test == False: + trainloader = torch.utils.data.DataLoader(trainset, pin_memory=True, batch_size=c.batch_size, shuffle=True, + drop_last=False) + validateloader = torch.utils.data.DataLoader(validateset, pin_memory=True, batch_size=c.batch_size, shuffle=True, + drop_last=False) + else: + testloader = torch.utils.data.DataLoader(testset, pin_memory=True, batch_size=c.batch_size_test, shuffle=True, + drop_last=False) + + return trainloader, validateloader, testloader + + +def preprocess_batch(data): + '''move data to device and reshape image''' + inputs, labels = data + inputs = differences_as_input(inputs) + + print(f"begin: size of inputs={inputs.size()}") + inputs, labels = inputs.to(c.device), labels.to(c.device) + print(f"to: size of inputs={inputs.size()}") + inputs = inputs.view(-1, *inputs.shape[-3:]) + print(f"view: size of inputs={inputs.size()}") + return inputs, labels + +def differences_as_input(inputs): + ''' + instead of using the input images, we calculate the differences between each set of 2 images + # then we feed the differences as our input to the model + ''' + num_batch = inputs.shape[0] + for i in range(num_batch): + diff_list = [] + num_transformed_images = inputs.shape[1] + for j in range(num_transformed_images): + if j % 2 == 0: + # calculate the differences between each set of 2 images + # append the differences into a new list + diff_list.append(inputs[i][j+1]-inputs[0][j]) + + diff_list = torch.stack(diff_list, 0) + diff_list = diff_list.unsqueeze(1).permute(1, 0, 2, 3, 4) + + # cat the differences between images from each batch into the entire batch group + if i == 0: + diff_inputs = diff_list + else: + diff_inputs = torch.cat((diff_inputs, diff_list), 0) + return torch.cat((diff_inputs, diff_inputs), 1) From 2b03c72bfca7bb262f87486ec6764467c0da98ff Mon Sep 17 00:00:00 2001 From: txrxrxr Date: Sun, 25 Oct 2020 13:45:06 -0600 Subject: [PATCH 20/65] Added function to randomly shrink image size of width and lenth --- utils.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/utils.py b/utils.py index 2c90434..be93727 100644 --- a/utils.py +++ b/utils.py @@ -6,6 +6,17 @@ import config as c from multi_transform_loader import ImageFolderMultiTransform +import random + +def random_shrink(): + w, h = c.img_size + w_shrink = w + int(random.uniform(-0.5, 0.17) * w) + h_shrink = h + int(random.uniform(-0.5, 0.17) * h) + img_size = (w_shrink, h_shrink) + if w_shrink < h_shrink: + img_size = (h_shrink, w_shrink) + print('shrinked image size: ', img_size) + return img_size def t2np(tensor): '''pytorch tensor -> numpy array''' @@ -79,8 +90,8 @@ def target_transform(target): augmentative_transforms += [transforms.ColorJitter(brightness=c.transf_brightness, contrast=c.transf_contrast, saturation=c.transf_saturation)] - tfs = [transforms.Resize(c.img_size)] + augmentative_transforms + [transforms.ToTensor(), - transforms.Normalize(c.norm_mean, c.norm_std)] + tfs = [transforms.Resize(random_shrink())] + augmentative_transforms + [transforms.ToTensor(), + transforms.Normalize(c.norm_mean, c.norm_std)] transform_train = transforms.Compose(tfs) From ffdd7e42b38cfcface9d4ef02dccb08ace5847a2 Mon Sep 17 00:00:00 2001 From: txrxrxr Date: Sun, 1 Nov 2020 19:55:55 -0700 Subject: [PATCH 21/65] Added new random shrink --- utils.py | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/utils.py b/utils.py index be93727..e95592e 100644 --- a/utils.py +++ b/utils.py @@ -7,16 +7,37 @@ from multi_transform_loader import ImageFolderMultiTransform import random - -def random_shrink(): - w, h = c.img_size - w_shrink = w + int(random.uniform(-0.5, 0.17) * w) - h_shrink = h + int(random.uniform(-0.5, 0.17) * h) - img_size = (w_shrink, h_shrink) +import cv2 +import numpy as np + +def TransformShow(name="img", wait=100): + def transform_show(img): + cv2.imshow(name, np.array(img)) + cv2.waitKey(wait) + return img + + return transform_show + +def randomCrop(): + def random_crop(img): + new_size = random_shrink(img.size) + rs = transforms.RandomCrop(new_size, padding=None, pad_if_needed=True, fill=0, padding_mode='edge') + return rs(img) + + return random_crop + +def random_shrink(img_size): + w, h = img_size + shrinkW = random.randint(0,1) + shrinkH = 1-shrinkW + shrink_scale = random.uniform(0.04, 0.1) + w_shrink = w - int(shrinkW * shrink_scale * w) + h_shrink = h - int(shrinkH * shrink_scale * h) + new_img_size = (w_shrink, h_shrink) if w_shrink < h_shrink: - img_size = (h_shrink, w_shrink) - print('shrinked image size: ', img_size) - return img_size + new_img_size = (h_shrink, w_shrink) + print('shrinked image size: ', new_img_size, img_size) + return new_img_size def t2np(tensor): '''pytorch tensor -> numpy array''' @@ -90,8 +111,8 @@ def target_transform(target): augmentative_transforms += [transforms.ColorJitter(brightness=c.transf_brightness, contrast=c.transf_contrast, saturation=c.transf_saturation)] - tfs = [transforms.Resize(random_shrink())] + augmentative_transforms + [transforms.ToTensor(), - transforms.Normalize(c.norm_mean, c.norm_std)] + tfs = [randomCrop(), transforms.Resize(c.img_size)] \ + + augmentative_transforms + [ TransformShow("", 200), transforms.ToTensor(), transforms.Normalize(c.norm_mean, c.norm_std)] transform_train = transforms.Compose(tfs) From 672c7c53ec7ba1553025d65348d6902b5a9d53e6 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sun, 8 Nov 2020 17:37:42 -0700 Subject: [PATCH 22/65] Added new annotation and data extratction method. --- data_extraction.py | 48 +++++++++++++++ dataset/data-generation/annotations.xml | 80 +++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 data_extraction.py create mode 100644 dataset/data-generation/annotations.xml diff --git a/data_extraction.py b/data_extraction.py new file mode 100644 index 0000000..5e6cd8f --- /dev/null +++ b/data_extraction.py @@ -0,0 +1,48 @@ +import cv2 +from xml.dom import minidom + +# Read annotations +annotation = minidom.parse('dataset/data-generation/annotations.xml') +boxes = annotation.getElementsByTagName('box') +boxesList = [] +frameList = [] + +for i in range(boxes.length): + frame = int(boxes[i].attributes['frame'].value) + frameList.append(frame) + + ytl = int(float(boxes[i].attributes['ytl'].value)) + ybr = int(float(boxes[i].attributes['ybr'].value)) + xtl = int(float(boxes[i].attributes['xtl'].value)) + xbr = int(float(boxes[i].attributes['xbr'].value)) + + boxesList.append([ytl, ybr, xtl, xbr]) + + +# one specific item attribute +print('Box #1 frame:') +print(boxes[0].attributes['frame'].value) + +# Opens the Video file +cap = cv2.VideoCapture('dataset/data-generation/FileOutput0_2019-07-06_17-11-17-01-01 black jar short.avi') +shrink_percentage = 0.02 +j = 0 +while(cap.isOpened()): + ret, frame = cap.read() + if(frame is not None and j in frameList): + ytl = boxesList[frameList.index(j)][0] + ybr = boxesList[frameList.index(j)][1] + xtl = boxesList[frameList.index(j)][2] + xbr = boxesList[frameList.index(j)][3] + + crop_frame = frame[int(ytl*(1+shrink_percentage)):int(ybr*(1-shrink_percentage)), + int(xtl*(1+shrink_percentage)):int(xbr*(1-shrink_percentage))] + print('frame', j) + cv2.imwrite('dataset/data-generation/frame' + str(j) + '.jpg', crop_frame) + + if ret == False: + break + j += 1 + +cap.release() +cv2.destroyAllWindows() diff --git a/dataset/data-generation/annotations.xml b/dataset/data-generation/annotations.xml new file mode 100644 index 0000000..e56c857 --- /dev/null +++ b/dataset/data-generation/annotations.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 57a4fa93812067ed00cd54426d3468fd9ae52a72 Mon Sep 17 00:00:00 2001 From: txrxrxr Date: Sun, 8 Nov 2020 17:48:00 -0700 Subject: [PATCH 23/65] Added function to randomly shrink image size of width and length --- utils.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/utils.py b/utils.py index e95592e..ceaea95 100644 --- a/utils.py +++ b/utils.py @@ -9,6 +9,7 @@ import random import cv2 import numpy as np +from datetime import datetime def TransformShow(name="img", wait=100): def transform_show(img): @@ -20,24 +21,30 @@ def transform_show(img): def randomCrop(): def random_crop(img): - new_size = random_shrink(img.size) - rs = transforms.RandomCrop(new_size, padding=None, pad_if_needed=True, fill=0, padding_mode='edge') - return rs(img) + x,y,w,h = random_shrink2(img.size) + rs = transforms.functional.crop(img,y,x,h,w) + # path = r'C:\Users\fiona\Desktop\differnet\transform\\' + # now = datetime.now() + # dt_string = now.strftime("%d%m%Y%H%M%S") + # cv2.imwrite(path + 'transform_' + dt_string + '.jpg', np.array(rs)) + return rs return random_crop -def random_shrink(img_size): - w, h = img_size - shrinkW = random.randint(0,1) - shrinkH = 1-shrinkW - shrink_scale = random.uniform(0.04, 0.1) - w_shrink = w - int(shrinkW * shrink_scale * w) - h_shrink = h - int(shrinkH * shrink_scale * h) - new_img_size = (w_shrink, h_shrink) - if w_shrink < h_shrink: - new_img_size = (h_shrink, w_shrink) - print('shrinked image size: ', new_img_size, img_size) - return new_img_size +def random_shrink2(img_size): + width, height = img_size + center_x = int(width / 2) + center_y = int(height / 2) + shrink_scaleW = random.uniform(0.05, 0.15) + shrink_scaleH = random.uniform(0.1, 0.2) + new_width = int(width * (1 - shrink_scaleW)) + new_height = int(height * (1 - shrink_scaleH)) + new_ul_x = int(center_x - new_width / 2) + new_ul_y = int(center_y - new_height / 2) + print( + f"shrinking ({0, 0, width, height}) to ({new_ul_x, new_ul_y, new_width, new_height}) by {shrink_scaleW, shrink_scaleH}" + ) + return new_ul_x, new_ul_y, new_width, new_height def t2np(tensor): '''pytorch tensor -> numpy array''' From 3c892565ea186208463f3fe4cd386b55cb5cf267 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sun, 8 Nov 2020 21:11:19 -0700 Subject: [PATCH 24/65] Updated the data extraction method --- data_extraction.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data_extraction.py b/data_extraction.py index 5e6cd8f..ac205f5 100644 --- a/data_extraction.py +++ b/data_extraction.py @@ -7,6 +7,7 @@ boxesList = [] frameList = [] +# Store the bounding box info along with frame number info into list for i in range(boxes.length): frame = int(boxes[i].attributes['frame'].value) frameList.append(frame) @@ -18,13 +19,11 @@ boxesList.append([ytl, ybr, xtl, xbr]) - -# one specific item attribute -print('Box #1 frame:') -print(boxes[0].attributes['frame'].value) - # Opens the Video file cap = cv2.VideoCapture('dataset/data-generation/FileOutput0_2019-07-06_17-11-17-01-01 black jar short.avi') + + +# Set up shrink percentage shrink_percentage = 0.02 j = 0 while(cap.isOpened()): @@ -35,6 +34,7 @@ xtl = boxesList[frameList.index(j)][2] xbr = boxesList[frameList.index(j)][3] + # Crop the frames with the bounding box position info crop_frame = frame[int(ytl*(1+shrink_percentage)):int(ybr*(1-shrink_percentage)), int(xtl*(1+shrink_percentage)):int(xbr*(1-shrink_percentage))] print('frame', j) From 96ea14b266c13eeb52b3666881bcab9b510a6006 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sun, 8 Nov 2020 21:56:54 -0700 Subject: [PATCH 25/65] Updated the utils not using the differences as input --- utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.py b/utils.py index b59761a..8eb1708 100644 --- a/utils.py +++ b/utils.py @@ -117,7 +117,7 @@ def make_dataloaders(trainset, validateset, testset, test=False): def preprocess_batch(data): '''move data to device and reshape image''' inputs, labels = data - inputs = differences_as_input(inputs) + #inputs = differences_as_input(inputs) print(f"begin: size of inputs={inputs.size()}") inputs, labels = inputs.to(c.device), labels.to(c.device) From 6b9c57eb990690635e6205143b49010f84e5078d Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sun, 8 Nov 2020 23:55:33 -0700 Subject: [PATCH 26/65] Updated the data_extraction.py structure --- data_extraction.py | 102 +++--- dataset/data-generation/1.xml | 438 ++++++++++++++++++++++++ dataset/data-generation/annotations.xml | 80 ----- 3 files changed, 495 insertions(+), 125 deletions(-) create mode 100644 dataset/data-generation/1.xml delete mode 100644 dataset/data-generation/annotations.xml diff --git a/data_extraction.py b/data_extraction.py index ac205f5..6086434 100644 --- a/data_extraction.py +++ b/data_extraction.py @@ -1,48 +1,60 @@ import cv2 from xml.dom import minidom -# Read annotations -annotation = minidom.parse('dataset/data-generation/annotations.xml') -boxes = annotation.getElementsByTagName('box') -boxesList = [] -frameList = [] - -# Store the bounding box info along with frame number info into list -for i in range(boxes.length): - frame = int(boxes[i].attributes['frame'].value) - frameList.append(frame) - - ytl = int(float(boxes[i].attributes['ytl'].value)) - ybr = int(float(boxes[i].attributes['ybr'].value)) - xtl = int(float(boxes[i].attributes['xtl'].value)) - xbr = int(float(boxes[i].attributes['xbr'].value)) - - boxesList.append([ytl, ybr, xtl, xbr]) - -# Opens the Video file -cap = cv2.VideoCapture('dataset/data-generation/FileOutput0_2019-07-06_17-11-17-01-01 black jar short.avi') - - -# Set up shrink percentage -shrink_percentage = 0.02 -j = 0 -while(cap.isOpened()): - ret, frame = cap.read() - if(frame is not None and j in frameList): - ytl = boxesList[frameList.index(j)][0] - ybr = boxesList[frameList.index(j)][1] - xtl = boxesList[frameList.index(j)][2] - xbr = boxesList[frameList.index(j)][3] - - # Crop the frames with the bounding box position info - crop_frame = frame[int(ytl*(1+shrink_percentage)):int(ybr*(1-shrink_percentage)), - int(xtl*(1+shrink_percentage)):int(xbr*(1-shrink_percentage))] - print('frame', j) - cv2.imwrite('dataset/data-generation/frame' + str(j) + '.jpg', crop_frame) - - if ret == False: - break - j += 1 - -cap.release() -cv2.destroyAllWindows() +# Load videos one by one +for i in range(4): + print('Data generation on video-' + str(i+1)) + + filename = str(i+1) + # Opens the Video file + cap = cv2.VideoCapture('dataset/data-generation/' + filename + '.avi') + + # Read annotations + annotation = minidom.parse('dataset/data-generation/' + filename + '.xml') + boxes = annotation.getElementsByTagName('box') + + frameList = [] + labelList = [] + boxesList = [] + + # Store the bounding box info along with frame number info into list + for i in range(boxes.length): + if (boxes[i].attributes['outside'].value != '1'): + frame = int(boxes[i].attributes['frame'].value) + frameList.append(frame) + + labelList.append(boxes[i].parentNode.attributes['label'].value) + + ytl = int(float(boxes[i].attributes['ytl'].value)) + ybr = int(float(boxes[i].attributes['ybr'].value)) + xtl = int(float(boxes[i].attributes['xtl'].value)) + xbr = int(float(boxes[i].attributes['xbr'].value)) + boxesList.append([ytl, ybr, xtl, xbr]) + + # Set up shrink percentage + shrink_percentage = 0.02 + j = 0 + while(cap.isOpened()): + ret, frame = cap.read() + if(frame is not None and j in frameList): + ytl = boxesList[frameList.index(j)][0] + ybr = boxesList[frameList.index(j)][1] + xtl = boxesList[frameList.index(j)][2] + xbr = boxesList[frameList.index(j)][3] + label = 'good' if labelList[frameList.index(j)] == 'bottle' else 'defect' + # Crop the frames with the bounding box position info + crop_frame = frame[int(ytl*(1+shrink_percentage)):int(ybr*(1-shrink_percentage)), + int(xtl*(1+shrink_percentage)):int(xbr*(1-shrink_percentage))] + + # output file formatting example "video1-frame4-defect.jpg" + print('Successfully generated: dataset/data-generation/' + label + '/video-' + filename + '-frame' + str(j) + + '-' + label + '.jpg') + cv2.imwrite('dataset/data-generation/' + label + '/video-' + filename + '-frame' + str(j) + '-' + label + '.jpg', + crop_frame) + + if ret == False: + break + j += 1 + + cap.release() + cv2.destroyAllWindows() diff --git a/dataset/data-generation/1.xml b/dataset/data-generation/1.xml new file mode 100644 index 0000000..cee4ba3 --- /dev/null +++ b/dataset/data-generation/1.xml @@ -0,0 +1,438 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dataset/data-generation/annotations.xml b/dataset/data-generation/annotations.xml deleted file mode 100644 index e56c857..0000000 --- a/dataset/data-generation/annotations.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 39fe3a1e603a9afa8019178350077856def903e8 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Mon, 9 Nov 2020 00:18:56 -0700 Subject: [PATCH 27/65] Updated the data_extraction structure --- data_extraction.py | 14 ++++++++------ dataset/data-generation/{ => annotations}/1.xml | 0 2 files changed, 8 insertions(+), 6 deletions(-) rename dataset/data-generation/{ => annotations}/1.xml (100%) diff --git a/data_extraction.py b/data_extraction.py index 6086434..b12e303 100644 --- a/data_extraction.py +++ b/data_extraction.py @@ -2,15 +2,15 @@ from xml.dom import minidom # Load videos one by one -for i in range(4): +for i in range(8): print('Data generation on video-' + str(i+1)) - filename = str(i+1) + # Opens the Video file - cap = cv2.VideoCapture('dataset/data-generation/' + filename + '.avi') + cap = cv2.VideoCapture('dataset/data-generation/videos/' + filename + '.avi') # Read annotations - annotation = minidom.parse('dataset/data-generation/' + filename + '.xml') + annotation = minidom.parse('dataset/data-generation/annotations/' + filename + '.xml') boxes = annotation.getElementsByTagName('box') frameList = [] @@ -19,6 +19,8 @@ # Store the bounding box info along with frame number info into list for i in range(boxes.length): + + # make sure not select the bounding box that outside the frame if (boxes[i].attributes['outside'].value != '1'): frame = int(boxes[i].attributes['frame'].value) frameList.append(frame) @@ -47,9 +49,9 @@ int(xtl*(1+shrink_percentage)):int(xbr*(1-shrink_percentage))] # output file formatting example "video1-frame4-defect.jpg" - print('Successfully generated: dataset/data-generation/' + label + '/video-' + filename + '-frame' + str(j) + + print('Successfully generated: dataset/zerobox-2010-1/' + label + '/video-' + filename + '-frame' + str(j) + '-' + label + '.jpg') - cv2.imwrite('dataset/data-generation/' + label + '/video-' + filename + '-frame' + str(j) + '-' + label + '.jpg', + cv2.imwrite('dataset/zerobox-2010-1/' + label + '/video-' + filename + '-frame' + str(j) + '-' + label + '.jpg', crop_frame) if ret == False: diff --git a/dataset/data-generation/1.xml b/dataset/data-generation/annotations/1.xml similarity index 100% rename from dataset/data-generation/1.xml rename to dataset/data-generation/annotations/1.xml From c6561154e603e1dd5a1e580cedc3c7cff1010487 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Mon, 9 Nov 2020 19:05:13 -0700 Subject: [PATCH 28/65] Revert back the Utils, and remove the part that apply the concept of ResNet and train on the differences between samples and the averaged sample. #158 --- data_extraction.py | 2 +- utils.py | 37 +------------------------------------ 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/data_extraction.py b/data_extraction.py index b12e303..ed65c8e 100644 --- a/data_extraction.py +++ b/data_extraction.py @@ -2,7 +2,7 @@ from xml.dom import minidom # Load videos one by one -for i in range(8): +for i in range(21): print('Data generation on video-' + str(i+1)) filename = str(i+1) diff --git a/utils.py b/utils.py index 8eb1708..3ca6714 100644 --- a/utils.py +++ b/utils.py @@ -20,32 +20,24 @@ def get_loss(z, jac): def load_datasets(dataset_path, class_name, test=False): ''' Expected folder/file format to find anomalies of class from dataset location : - train data: - dataset_path/class_name/train/good/any_filename.png dataset_path/class_name/train/good/another_filename.tif dataset_path/class_name/train/good/xyz.png [...] - test data: - 'normal data' = non-anomalies - dataset_path/class_name/test/good/name_the_file_as_you_like_as_long_as_there_is_an_image_extension.webp dataset_path/class_name/test/good/did_you_know_the_image_extension_webp?.png dataset_path/class_name/test/good/did_you_know_that_filenames_may_contain_question_marks????.png dataset_path/class_name/test/good/dont_know_how_it_is_with_windows.png dataset_path/class_name/test/good/just_dont_use_windows_for_this.png [...] - anomalies - assume there are anomaly classes 'crack' and 'curved' - dataset_path/class_name/test/crack/dat_crack_damn.png dataset_path/class_name/test/crack/let_it_crack.png dataset_path/class_name/test/crack/writing_docs_is_fun.png [...] - dataset_path/class_name/test/curved/wont_make_a_difference_if_you_put_all_anomalies_in_one_class.png dataset_path/class_name/test/curved/but_this_code_is_practicable_for_the_mvtec_dataset.png [...] @@ -117,36 +109,9 @@ def make_dataloaders(trainset, validateset, testset, test=False): def preprocess_batch(data): '''move data to device and reshape image''' inputs, labels = data - #inputs = differences_as_input(inputs) - print(f"begin: size of inputs={inputs.size()}") inputs, labels = inputs.to(c.device), labels.to(c.device) print(f"to: size of inputs={inputs.size()}") inputs = inputs.view(-1, *inputs.shape[-3:]) print(f"view: size of inputs={inputs.size()}") - return inputs, labels - -def differences_as_input(inputs): - ''' - instead of using the input images, we calculate the differences between each set of 2 images - # then we feed the differences as our input to the model - ''' - num_batch = inputs.shape[0] - for i in range(num_batch): - diff_list = [] - num_transformed_images = inputs.shape[1] - for j in range(num_transformed_images): - if j % 2 == 0: - # calculate the differences between each set of 2 images - # append the differences into a new list - diff_list.append(inputs[i][j+1]-inputs[0][j]) - - diff_list = torch.stack(diff_list, 0) - diff_list = diff_list.unsqueeze(1).permute(1, 0, 2, 3, 4) - - # cat the differences between images from each batch into the entire batch group - if i == 0: - diff_inputs = diff_list - else: - diff_inputs = torch.cat((diff_inputs, diff_list), 0) - return torch.cat((diff_inputs, diff_inputs), 1) + return inputs, labels \ No newline at end of file From a43d71f684c3979a8e276a8b74628cc2959a9eae Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Mon, 9 Nov 2020 19:17:25 -0700 Subject: [PATCH 29/65] Revert back the Utils, and remove the part that apply the concept of ResNet and train on the differences between samples and the averaged sample. #158 --- utils.py | 242 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 125 insertions(+), 117 deletions(-) diff --git a/utils.py b/utils.py index 3ca6714..2c90434 100644 --- a/utils.py +++ b/utils.py @@ -1,117 +1,125 @@ -import os -import torch -from torch.utils.data import DataLoader -from torchvision import datasets, transforms - -import config as c -from multi_transform_loader import ImageFolderMultiTransform - - -def t2np(tensor): - '''pytorch tensor -> numpy array''' - return tensor.cpu().data.numpy() if tensor is not None else None - - -def get_loss(z, jac): - '''check equation 4 of the paper why this makes sense - oh and just ignore the scaling here''' - return torch.mean(0.5 * torch.sum(z ** 2, dim=(1,)) - jac) / z.shape[1] - - -def load_datasets(dataset_path, class_name, test=False): - ''' - Expected folder/file format to find anomalies of class from dataset location : - train data: - dataset_path/class_name/train/good/any_filename.png - dataset_path/class_name/train/good/another_filename.tif - dataset_path/class_name/train/good/xyz.png - [...] - test data: - 'normal data' = non-anomalies - dataset_path/class_name/test/good/name_the_file_as_you_like_as_long_as_there_is_an_image_extension.webp - dataset_path/class_name/test/good/did_you_know_the_image_extension_webp?.png - dataset_path/class_name/test/good/did_you_know_that_filenames_may_contain_question_marks????.png - dataset_path/class_name/test/good/dont_know_how_it_is_with_windows.png - dataset_path/class_name/test/good/just_dont_use_windows_for_this.png - [...] - anomalies - assume there are anomaly classes 'crack' and 'curved' - dataset_path/class_name/test/crack/dat_crack_damn.png - dataset_path/class_name/test/crack/let_it_crack.png - dataset_path/class_name/test/crack/writing_docs_is_fun.png - [...] - dataset_path/class_name/test/curved/wont_make_a_difference_if_you_put_all_anomalies_in_one_class.png - dataset_path/class_name/test/curved/but_this_code_is_practicable_for_the_mvtec_dataset.png - [...] - ''' - - def target_transform(target): - return class_perm[target] - - data_dir_train = os.path.join(dataset_path, class_name, 'train') - data_dir_validate = os.path.join(dataset_path, class_name, 'validate') - data_dir_test = os.path.join(dataset_path, class_name, 'test') - - classes = os.listdir(data_dir_validate) - if 'good' not in classes: - print('There should exist a subdirectory "good". Read the doc of this function for further information.') - exit() - classes.sort() - class_perm = list() - class_idx = 1 - for cl in classes: - if cl == 'good': - class_perm.append(0) - else: - class_perm.append(class_idx) - class_idx += 1 - - augmentative_transforms = [] - if c.transf_rotations: - augmentative_transforms += [transforms.RandomRotation(180)] - if c.transf_brightness > 0.0 or c.transf_contrast > 0.0 or c.transf_saturation > 0.0: - augmentative_transforms += [transforms.ColorJitter(brightness=c.transf_brightness, contrast=c.transf_contrast, - saturation=c.transf_saturation)] - - tfs = [transforms.Resize(c.img_size)] + augmentative_transforms + [transforms.ToTensor(), - transforms.Normalize(c.norm_mean, c.norm_std)] - - transform_train = transforms.Compose(tfs) - - trainset = None - validateset = None - testset = None - if test == False: - trainset = ImageFolderMultiTransform(data_dir_train, transform=transform_train, n_transforms=c.n_transforms) - validateset = ImageFolderMultiTransform(data_dir_validate, transform=transform_train, target_transform=target_transform, - n_transforms=c.n_transforms_test) - else: - testset = ImageFolderMultiTransform(data_dir_test, transform=transform_train, target_transform=target_transform, - n_transforms=c.n_transforms_test) - - return trainset, validateset, testset - - -def make_dataloaders(trainset, validateset, testset, test=False): - trainloader = None - validateloader = None - testloader = None - if test == False: - trainloader = torch.utils.data.DataLoader(trainset, pin_memory=True, batch_size=c.batch_size, shuffle=True, - drop_last=False) - validateloader = torch.utils.data.DataLoader(validateset, pin_memory=True, batch_size=c.batch_size, shuffle=True, - drop_last=False) - else: - testloader = torch.utils.data.DataLoader(testset, pin_memory=True, batch_size=c.batch_size_test, shuffle=True, - drop_last=False) - - return trainloader, validateloader, testloader - - -def preprocess_batch(data): - '''move data to device and reshape image''' - inputs, labels = data - print(f"begin: size of inputs={inputs.size()}") - inputs, labels = inputs.to(c.device), labels.to(c.device) - print(f"to: size of inputs={inputs.size()}") - inputs = inputs.view(-1, *inputs.shape[-3:]) - print(f"view: size of inputs={inputs.size()}") - return inputs, labels \ No newline at end of file +import os +import torch +from torch.utils.data import DataLoader +from torchvision import datasets, transforms + +import config as c +from multi_transform_loader import ImageFolderMultiTransform + + +def t2np(tensor): + '''pytorch tensor -> numpy array''' + return tensor.cpu().data.numpy() if tensor is not None else None + + +def get_loss(z, jac): + '''check equation 4 of the paper why this makes sense - oh and just ignore the scaling here''' + return torch.mean(0.5 * torch.sum(z ** 2, dim=(1,)) - jac) / z.shape[1] + + +def load_datasets(dataset_path, class_name, test=False): + ''' + Expected folder/file format to find anomalies of class from dataset location : + + train data: + + dataset_path/class_name/train/good/any_filename.png + dataset_path/class_name/train/good/another_filename.tif + dataset_path/class_name/train/good/xyz.png + [...] + + test data: + + 'normal data' = non-anomalies + + dataset_path/class_name/test/good/name_the_file_as_you_like_as_long_as_there_is_an_image_extension.webp + dataset_path/class_name/test/good/did_you_know_the_image_extension_webp?.png + dataset_path/class_name/test/good/did_you_know_that_filenames_may_contain_question_marks????.png + dataset_path/class_name/test/good/dont_know_how_it_is_with_windows.png + dataset_path/class_name/test/good/just_dont_use_windows_for_this.png + [...] + + anomalies - assume there are anomaly classes 'crack' and 'curved' + + dataset_path/class_name/test/crack/dat_crack_damn.png + dataset_path/class_name/test/crack/let_it_crack.png + dataset_path/class_name/test/crack/writing_docs_is_fun.png + [...] + + dataset_path/class_name/test/curved/wont_make_a_difference_if_you_put_all_anomalies_in_one_class.png + dataset_path/class_name/test/curved/but_this_code_is_practicable_for_the_mvtec_dataset.png + [...] + ''' + + def target_transform(target): + return class_perm[target] + + data_dir_train = os.path.join(dataset_path, class_name, 'train') + data_dir_validate = os.path.join(dataset_path, class_name, 'validate') + data_dir_test = os.path.join(dataset_path, class_name, 'test') + + classes = os.listdir(data_dir_validate) + if 'good' not in classes: + print('There should exist a subdirectory "good". Read the doc of this function for further information.') + exit() + classes.sort() + class_perm = list() + class_idx = 1 + for cl in classes: + if cl == 'good': + class_perm.append(0) + else: + class_perm.append(class_idx) + class_idx += 1 + + augmentative_transforms = [] + if c.transf_rotations: + augmentative_transforms += [transforms.RandomRotation(180)] + if c.transf_brightness > 0.0 or c.transf_contrast > 0.0 or c.transf_saturation > 0.0: + augmentative_transforms += [transforms.ColorJitter(brightness=c.transf_brightness, contrast=c.transf_contrast, + saturation=c.transf_saturation)] + + tfs = [transforms.Resize(c.img_size)] + augmentative_transforms + [transforms.ToTensor(), + transforms.Normalize(c.norm_mean, c.norm_std)] + + transform_train = transforms.Compose(tfs) + + trainset = None + validateset = None + testset = None + if test == False: + trainset = ImageFolderMultiTransform(data_dir_train, transform=transform_train, n_transforms=c.n_transforms) + validateset = ImageFolderMultiTransform(data_dir_validate, transform=transform_train, target_transform=target_transform, + n_transforms=c.n_transforms_test) + else: + testset = ImageFolderMultiTransform(data_dir_test, transform=transform_train, target_transform=target_transform, + n_transforms=c.n_transforms_test) + + return trainset, validateset, testset + + +def make_dataloaders(trainset, validateset, testset, test=False): + trainloader = None + validateloader = None + testloader = None + if test == False: + trainloader = torch.utils.data.DataLoader(trainset, pin_memory=True, batch_size=c.batch_size, shuffle=True, + drop_last=False) + validateloader = torch.utils.data.DataLoader(validateset, pin_memory=True, batch_size=c.batch_size, shuffle=True, + drop_last=False) + else: + testloader = torch.utils.data.DataLoader(testset, pin_memory=True, batch_size=c.batch_size_test, shuffle=True, + drop_last=False) + + return trainloader, validateloader, testloader + + +def preprocess_batch(data): + '''move data to device and reshape image''' + inputs, labels = data + print(f"begin: size of inputs={inputs.size()}") + inputs, labels = inputs.to(c.device), labels.to(c.device) + print(f"to: size of inputs={inputs.size()}") + inputs = inputs.view(-1, *inputs.shape[-3:]) + print(f"view: size of inputs={inputs.size()}") + return inputs, labels From 9b02a0379a85f0acd01900ade1f2f7186191ec95 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Tue, 17 Nov 2020 15:14:03 -0700 Subject: [PATCH 30/65] save original frame image --- data_extraction.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data_extraction.py b/data_extraction.py index ed65c8e..bd63364 100644 --- a/data_extraction.py +++ b/data_extraction.py @@ -44,6 +44,7 @@ xtl = boxesList[frameList.index(j)][2] xbr = boxesList[frameList.index(j)][3] label = 'good' if labelList[frameList.index(j)] == 'bottle' else 'defect' + # Crop the frames with the bounding box position info crop_frame = frame[int(ytl*(1+shrink_percentage)):int(ybr*(1-shrink_percentage)), int(xtl*(1+shrink_percentage)):int(xbr*(1-shrink_percentage))] @@ -51,6 +52,8 @@ # output file formatting example "video1-frame4-defect.jpg" print('Successfully generated: dataset/zerobox-2010-1/' + label + '/video-' + filename + '-frame' + str(j) + '-' + label + '.jpg') + cv2.imwrite('dataset/zerobox-2010-1-original/' + label + '/original-video-' + filename + '-frame' + str(j) + '-' + label + '.jpg', + frame) cv2.imwrite('dataset/zerobox-2010-1/' + label + '/video-' + filename + '-frame' + str(j) + '-' + label + '.jpg', crop_frame) From 1c68c6be6656d4af205755877dd2ac1defeaac28 Mon Sep 17 00:00:00 2001 From: Zijian Kuang Date: Tue, 17 Nov 2020 17:11:51 -0700 Subject: [PATCH 31/65] Update Readme.md --- Readme.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Readme.md b/Readme.md index 4122315..ffb8958 100644 --- a/Readme.md +++ b/Readme.md @@ -45,6 +45,19 @@ $ python main.py ``` ## Data +How to use Data extraction tool to extract data from video clips: + 1. Create folder structure like the example shows in the picture below. + + 2. Dump the videos and annotations (rename them use 1.xml, 1.avi as one pair annotation and video) into the folders under data-generation folder. + + 3. Modify the annotation files: Since the annotation uses label "defect" to indicate the defect area, while, both good and defective bottles are labeled as "bottle" which is confusing. To indicate which "bottle" is defective, we need to find the frames that labeled with defect, and then manully update the group's label from "bottle" to "defective" for the groups that falling in to those frames. + + - For example: in the example image above, the frame 15 and 16 are labeled as "defect" which indicates those 2 frames has defect areas on the bottles. So we need to find the group that contains frame 15 and 16, and then manully update the label from "bottle" to "defective". and then delete the whole group that labeled as "defect" (since we don't care about the defect area in data extraction). + + 4. Modify the config.py, fill in appropriate value for num_videos, save_cropped_image_to and save_original_image_to + + 5. run the data extraction: python data_extraction.py + The given dummy dataset shows how the implementation expects the construction of a dataset. Coincidentally, the [MVTec AD dataset](https://www.mvtec.com/de/unternehmen/forschung/datasets/mvtec-ad/) is constructed in this way. From c84d9bacee7db74377d11ebd27bb1b0d7cee6645 Mon Sep 17 00:00:00 2001 From: Zijian Kuang Date: Tue, 17 Nov 2020 17:15:56 -0700 Subject: [PATCH 32/65] Update Readme.md --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index ffb8958..c5a6171 100644 --- a/Readme.md +++ b/Readme.md @@ -52,7 +52,7 @@ How to use Data extraction tool to extract data from video clips: 3. Modify the annotation files: Since the annotation uses label "defect" to indicate the defect area, while, both good and defective bottles are labeled as "bottle" which is confusing. To indicate which "bottle" is defective, we need to find the frames that labeled with defect, and then manully update the group's label from "bottle" to "defective" for the groups that falling in to those frames. - - For example: in the example image above, the frame 15 and 16 are labeled as "defect" which indicates those 2 frames has defect areas on the bottles. So we need to find the group that contains frame 15 and 16, and then manully update the label from "bottle" to "defective". and then delete the whole group that labeled as "defect" (since we don't care about the defect area in data extraction). + - For example: in the example image above, the frame 15 and 16 are labeled as "defect" which indicates those 2 frames has defect areas on the bottles. So we need to find the group that contains frame 15 and 16, and then manully update the label from "bottle" to "defective". and then delete the whole \ group that labeled as "defect" (since we don't care about the defect area in data extraction). 4. Modify the config.py, fill in appropriate value for num_videos, save_cropped_image_to and save_original_image_to From 5460ea675af614369fff27a55e07be688bcce9fa Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Tue, 17 Nov 2020 17:16:56 -0700 Subject: [PATCH 33/65] included examples --- config.py | 5 +++++ data_extraction.py | 9 +++++---- .../data-generation/annotations/structure1.png | Bin 0 -> 10305 bytes .../data-generation/annotations/structure2.png | Bin 0 -> 18484 bytes .../data-generation/annotations/structure3.png | Bin 0 -> 346186 bytes 5 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 dataset/data-generation/annotations/structure1.png create mode 100644 dataset/data-generation/annotations/structure2.png create mode 100644 dataset/data-generation/annotations/structure3.png diff --git a/config.py b/config.py index 833b1f9..4f1d743 100644 --- a/config.py +++ b/config.py @@ -1,6 +1,11 @@ '''This file configures the training procedure because handling arguments in every single function is so exhaustive for research purposes. Don't try this code if you are a software engineer.''' +# data extraction settings +num_videos = 21 +save_cropped_image_to = "dataset/zerobox-2010-1/" +save_original_image_to = "dataset/zerobox-2010-1-original/" + # device settings device = 'cpu' # 'cuda' or 'cpu' import torch diff --git a/data_extraction.py b/data_extraction.py index bd63364..d78b9c9 100644 --- a/data_extraction.py +++ b/data_extraction.py @@ -1,8 +1,9 @@ import cv2 from xml.dom import minidom +import config as c # Load videos one by one -for i in range(21): +for i in range(c.num_videos): print('Data generation on video-' + str(i+1)) filename = str(i+1) @@ -50,11 +51,11 @@ int(xtl*(1+shrink_percentage)):int(xbr*(1-shrink_percentage))] # output file formatting example "video1-frame4-defect.jpg" - print('Successfully generated: dataset/zerobox-2010-1/' + label + '/video-' + filename + '-frame' + str(j) + + print('Successfully generated: ' +c.save_cropped_image_to + label + '/video-' + filename + '-frame' + str(j) + '-' + label + '.jpg') - cv2.imwrite('dataset/zerobox-2010-1-original/' + label + '/original-video-' + filename + '-frame' + str(j) + '-' + label + '.jpg', + cv2.imwrite(c.save_original_image_to + label + '/original-video-' + filename + '-frame' + str(j) + '-' + label + '.jpg', frame) - cv2.imwrite('dataset/zerobox-2010-1/' + label + '/video-' + filename + '-frame' + str(j) + '-' + label + '.jpg', + cv2.imwrite(c.save_cropped_image_to + label + '/video-' + filename + '-frame' + str(j) + '-' + label + '.jpg', crop_frame) if ret == False: diff --git a/dataset/data-generation/annotations/structure1.png b/dataset/data-generation/annotations/structure1.png new file mode 100644 index 0000000000000000000000000000000000000000..18c72f665a5905e70e232a500bfb2696a15e5af6 GIT binary patch literal 10305 zcmb`NcUTkK+V+P4(sUyol_tF-h|)t;q$o{#2Px7-q<3NzbOQp?q(hJn0!jxdiuB&u zKmaMB7pZ}Sz?UHIv)^;x^Zxa5UAf4tnOV%N^{nUqJ@+C+Q(fs2`BicN04}L0D`)`# zfhO*Ai}V7nRKyfeA3hsc&_Kw;e+^I;4%c$;mc21GKq#ie7ZJ}k^|{y}ZATN;wO-%5Jpwvy4E9S5b?Pow4r zx#PH;9?|AszdtaTRBr9snsXugqLPWv>knL$8Sl3*wy{KWB!P%cNZR#DcL+S?n?abF zXB#Q2;Hlg~js2q9DU|yD1}lsN+l`v zWD!ys#Y4rR$>L%*<0sTu<5uFGdNJ-1vz&&Fg7htz15!<#qMUXnqp(plb8;)`o<^^F z(55JGgJSuZTW!E+q2rUL^_3P)!HOuP6PpXZFaG|vw3GMa=$_QM3Q_sf5p!dNYiRau zBl9xr0+6Y{mkjB6YfIc%VEaW7F^0gD6J5B*THtY&_MuaGEtXveF3ttT_7on-6TDxb z=zzPd%XOxf_WWr4V}Z%qELYjF`^aoYpM>~Wp?l%10Fu@-^(YA1SB zbdBqmr>AB54UJ>!DR&QldF=ZSuY6A3$;1haaC=-!5t3v{cF# zZ}HI>*HFt@k}nVVBwHij8csR;V^_qdZ(cKX^a4OeWK^^k2Ql^8EP){Jb4nSxE7%1a zj?&nI+k=d#@@_srMrDsqAIQKUA(=dVi87WC?)K%Yh7o@y);?s>nAK*6-5zNn)1k6y z197RRVIR6VZ{6cruAwJ+Eaz*l2-e=O7?W!(Ku~FqPyh8F-@GI+}ZQG4%`K+i;0YDLvDc9o960ziP|ha>A=oJdTp9zPP+gPVpIF{ebyg@{d5MZ){wA}EFz5& z9xNFzVBtDJT2ARrJh8}54FH!f^AeE*3Gcp<56AJ~z5%)~iG4Tav3|Q=>8DGr{wL{I zc1u{tU$GDXDrNB{&{OA=;G-kfQwd!F&U^>N<#UpGFbe>_O&p73(7pS*APd)*L}--w zj*dJn05LyolMB&mdbP4EwOb`|>GY~fAn7TF;`E#B?g(Ll2LWL3ve^i&iEcEyn7|Z` zWW{dBJNj?RpB}00O4KNn?WSGPU^dW^0RdSmK0R|(6)eL?oiL?WKPmPol)6h!4oQDfDh!a+SBIAl7vzMFHT+_hv!y6kgS6m7(#D9Y$mzDmf1}u zjT9X#0>DL82D%iLNV*4YK&93Ap^6y_;?yNh#2OFc0)cF8+l?+?w)L(}KGiO?1D#$r{@&iV@~lRBHN=>7#p-1{n! zi)R({1y1~IuPTCU@b(?eeHL^Ku~iB>p<0tHpSPdPrNnylJj3<9(^oaoJN2w{#&FIr zfG;rOCJnF4Y{i%WDBDS>_YHU33ZcPz>buBg@imU*C#KyP zt;WNl6F;%*j%U{jW6<69!AS-ECD;|G{Ep^+?GXP4FN`aZXa8W z^HgL|v7)Gs)1o=n?uIBf$5xdfP5%4In_+8~e zIblVCnXSIV6n~LQ|$~iZ9tlR8vX!A^%Dp;}v9Fw{2 zE~*J=_-38IJR;B_T98euU{`H6SrJYVX1jrnGD#ft-1aZGDQ4=O_c^U$w1KJx40s-r z>iCOFT?z7kikS8kin<^0NVp*j;k9j3r5$J*i)b|7T#Jdk-#U5Hq8yr=(-2V8x8eEY zWTmF4OeJAUJ~9StvyqDJvg*YMC^UM>?n$-JQ~32Y&Db8Xh$8eqZdalIN_OC9gVs zhHV*Io%*mAFga}9!^-Pz-dij|NyY})Z$+WxrM+b0wLE3fYgXku*b^puca_1|OpV2& zha!=zuvYTDuU3)C5>W4Pu*1HZo$JMK8@Z0K2L!GcX$1On8vYa}e(;mwZQAbXU|mnU zi1|=F|K3A~E)XWm=_=o&rk%{mG)%0HIWBSp0($h|8)#c^2R`{mmo373Z5c@C_J*r7 zjX7i2CvIWiblM@~z)2DJ^2;`oBT6zNstQ3pIUBOsRU8Aw#1j|J21r>&NRUNTzw!Z} z8hgKMQp4lk1=jwior2U-rAWv%boA#K(qZ-V=0BU?4=@O@*3gO0g}vYB%6WdtoL}Po zz~mv#mrAuZ?~~zayH2Qy@Qz3DhWXPl>-kfi)slR4y4pcQ2P$KB|4*lBufP0qP$E&M z9hcR%UInd|A72{Rr=RybE!aR-9)q7a?YQ=V_ZtpSZh}M(xj&u9?J)?7*dI|T)><%X zLGWf+pHC}!dA^8od^$(mhUsBgvo=Rl;J__d=;={dX}#q5`$iMP;{=g7>|m(9rRb$t zZ6#aiJi5DaLH54L1RsWfwR!Do6|_nOd)Rp(HXaJr*dPNtOPuUWPEMn`XI?An-Tx^2 zhi$Abl(iFVVtwKp`MpeJ1~axNedt!5Z`CR0&_6eq0+7>?Qan{!p5V_hum_TehD-NA}WfeLVs8n1_o4gKmk-1vW-08~Pu$ zA5=R=P8ifS4#I3IGyuy)-IHFLdRjX%lwN0WyKAP09C9Lcr21o>eYru1@pN8Cztcm} z@elncU;LFhd*WUExfZL2*?81P&*BlGwO=*ZFtgTF(vU z{`4eI>$*jgt^`k$tYKZPN-5A+$|fQruZ4>oB~xFvBQi2~MGJ|rcZ5MQi!PN3E57$1 z;7tx(a~ZL(ul|-2_Z0gfYCR_M(hEj(bR(arG7JQ){$rwwh->{$35dwQGQIfMbV1+% zK(Ql{mQS{~qVGun$bpWo^^HX|+Nzs143W0rPIpV;RG&=~ctL!0jS`11pKx8`C41>I zF*?RPP-}LTYm0avv+z!D?{Tbnfw*qvRlbk(#FhmVS7wvzDd~FFD)g)q4b^f9SOP% z0-AFq@iL2g>?Pwmr1;nYgc=aLO0` z2b<{cmaLOwTZPyfr)LQCM_vjap<+#&ndWe>ogL9OqPb<`lbo+`Vsv-mm&N%W)wDh3 zNuw0XvsCm+!Qf(BNzC&HFrE2QSADSt$*SUyW2mJ{Q{SbGIO?j$qS`)c#c8*bhOLv0 zFh-_69}`szJtjoA10j+DT{EL`D&DSK1Eu~E&^?2F@)P9r3I`L2 zz|v-vYQDWuQkQUyirHr8cN!klckxY;61Uu`3T(*6kJ}j~_gB712iuZiCI@c+X=Qgb zNqpIk0{6$@`&_w|J0H|Or3WKaV= zbVks3pE1Sha$EmS6$P87=9LlqeGA63D26d(w}*`iNxlO|*EAeqog<0c1bc*1alJV$ zi~%50cj|u&(!?Z)jTFEZfKN&d`6=rcSpfk1k0Aa#f!TrT>Q5d8$9a_`X7Z>PdRHoeJR#`ta_MA=?peAm`iJll9a0i+Rh)$UL#xS6b}IeLtLVS=?Yc~)juVV14u{gbAbmB=sGy&q%TwLr)UJPb1*P0K&N$Q<7M?DC9_t%*8)7yb;rjKWBD#Bl% zVP5akKWD0)t(P<@LW-W!dL#=dwT_Pa${>X?Cgy>H&e}7PUz~+{=?X?qtLKdjWG#!I zj!eGr#IIEVKo zZT7$_x6-(9@v=&HCm+PpY7N16`{AIy#C8KA#;fv5G_T@^sT=2gr_K^4Ts&KI?O_7= z%sA~zk# z{0Vf^;4ZmwE^WVan^T=- zvg4Ni{6VJVvPiQXGCn@uNg2-gTWTPr6CyqD^ennK`suSgH8|O6IH$Dkr8LGym-i*-{M;YNnr!7U;&bYk zfBnAEwBHC`dea|>$>*fls&?`ZHS76|?IK2}AaS`DFR`>b?sv zZ*QkF%T`$z@kyoU*iynfIqTZQ^h*zv_T`_R`TdevYAsbfP6NS{y z>@uS}0ic+n_+zz@ZxrI$Zt$JDG#P2mk9JIttzC3GT;!8XLTg`p?xh4NViuf?xg%7} zst1A?1T>$;?5Bo*;_I!_2%(ioNNHGbeo#(tXMPqqW}(f`1YVXQg@`&oB012T){oF0xsDTt#^^Uo2Y*)HlS4>#yTNfxP&j#P?`!aE|8t}sVj6B+@?DWs7lQ)W*;uBT{OM+d} z)N$O*SM+PlAF(_fdc1irVz}IM(>+D;Pc^Z5`>L$Vp5Ra}evEEsnUzyo!A-HzjccmC z_yvuhlGo?f$8YRC&kZcUk=F>mV=WH`0Q4DLg+tN|)k0s|k@$V*?`Somh!tkPqzjinLh{fR2Yzx?FJAo&WSu?01t~mZjbyKzcE#9*;KUS zwSq$%2IRvnS)Hz$U(6#AS;S{{Z;en@9Upr*s2<9twD=$Nm2{!(^77!d^rQ-}(i7xK z=~5!10Ez0En2NeGfg<)$$fesGm}+E;U!x$(r%!<|3KzN)R+u{z*|=N=za`|&3bSK!Z~0*JkF*e*Ul zRIx@AYnYPJlZ9_qhH*_7WR_j40t<90Fu8nZrfSe0)#uD9pC4duGyFV-9cH3@7fWlT zmJq>adK4Zu_5$)H+xaLN^ooA@X31t!q{L01K@KNJ7w{RO+*iI%yVIo<$w^DCswYeo z@60e*uGOss^I@jYEZnWiod3~wM;lVb8Q+4(+>Xajr}I!$d3X&L(vih$e*oCz^>} zGBp&=PH3TVArip>1w?(pv>Y9{1S#OB&R5rC1{wFKKm-Z@4t9(-&|v_|xtq!PyAwTSvEA93k*2 zu`?3*Rp9-O*?$AD-&tClCQpzA$fCrT8B;py?+JlHlK;%*m;Zla7?+`etmCMX@nYv} z+psU*JUlt^FRyU;L+Ezf%Ojg}IQ8c8n>*NWa+~!lNmOEm-VJxU`^)mX40|HVQPv=v zD0z?T(xC16o5AOam&0E{meS7IG~r+d7V&+9C&qAyHt?7h z!TN^pxDGFfAX0IHv*pnO9a0fDRp8HA360j-q_PGYL4_+* zTyg?5vPy+xEd5j0V=BKLAiG|KnNZ{dqrJ2^G68T z@D_;?{9am9?jNabOgQoe#9_D!ougms<Rtx#Nb{`W@xc?zqb#%G|9gpcsvvnN;`m~1E-ralc&Lgj5wC%`1 z{^gu07MhaTg8>G%{v(_Rh4H65`KLed9CO|9=Wv`wl(qJKGs|LW=`Sv8Wu>akR1yfP zSs9yv`<4%6Sf*T3=q9H$dbR--m5h;~71F`DnQHG#Rg|pr)2x9a=5m-X>)6f~?nx)} z)ABrlY5k+)+CcQ3v}sFx!U$nv6=qciJ*;d<~7?ki-$ z)HekfUUpSc@coq)-%&3y(nFj#ax^*M^3%%TLyX{IwHDR#;n+$;JQ{PaO`>2}cb;5o z&_$q4$2mqWUKFD?vm-%#K}d6MedHAfhB9`_d&wB&?8I5S7X0 z@Mx?*A}xm0F1^mFt65YvGpKzspgt()h|DBTY_Jy57^N_5}NFk>wUg#d|bV#bZ z8mF}l-lyJt75u|-NxiK|vRS!1c(ukk=M4N70-aLjDhrO2=5CdP#arv;r=476twh-Q z0)G10qx?`AH884W#DEc8un9J)2SF`%gFIR?%NjOi7i_{%PU)?(2L3<8U7lx3! z2mVbS@a|;!x{s9uXVE^^G^<;dC^A)9Q)(2I{P9}&?d}!AF28-`R=tFNM4rpBRY-6P z$6}}6mD95L!VRTslD3n!=kEofSqhAROsyPd#1+XAJOOzqg}P721Q`mTT5Wc$JH!}a zy_CNNp(U|NN>j#_mf>XkHdH!tGjiKBvl``K4^*SI$d=f2aQcLQwxrNW}>d=8Usx$`7eVy8kt(HTc`{nLC|owVB$dsT}}dA ztLEJ4aT`b4KJnPl90+O_F2Gm5X$zGU8ef1jJ>y52SYI1?9{KX@v&&IIABw@&B& zPQt~7i_FP%y-0CGCmh$rZ90rDJ^K}8#WkgzJ%Robg=f9pz!q*N->p!P>| zKzmfuXm4u7kn*P$cIpwg8P)6S{#6NR3wc%0b)SgokpNrTG?{pdhisw` z!?L&;Yx5=D0(a6HF6 z&i}-=K9N|=Sr4K^@pq1Wi(RwRb7p5?eApKpl%(h2EPddca*|Z%((6*a@98JPP1D$@ z0chn`fz)Q1@|YimP?^O%y}LHiM!p?!Exeoc382=1RV1_GiATv&2ExBF&@ko~nXQH13->7hb;I`U`m< z9>rnRU>B>_xL@75MxWWMTDl*mSgHZO4WPfW5le50o63R!cM>9LTWf7^6yk~0Y8i(d z(u5OWVv$>g0k0PaWSzPdv3U5l6h E4}@g)EdT%j literal 0 HcmV?d00001 diff --git a/dataset/data-generation/annotations/structure2.png b/dataset/data-generation/annotations/structure2.png new file mode 100644 index 0000000000000000000000000000000000000000..ec5ae622dcefffbf75e32f3880e3cfe1513c6f12 GIT binary patch literal 18484 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%0D%qR{+}( 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 p|idQ*yP2T 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

K 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 E2XQh-Q2+n{ literal 0 HcmV?d00001 From 6df1af4b35a3b35b50a797689251749b7912ae3e Mon Sep 17 00:00:00 2001 From: Zijian Kuang Date: Tue, 17 Nov 2020 17:20:16 -0700 Subject: [PATCH 34/65] Update Readme.md --- Readme.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index c5a6171..6c40d22 100644 --- a/Readme.md +++ b/Readme.md @@ -48,10 +48,15 @@ $ python main.py How to use Data extraction tool to extract data from video clips: 1. Create folder structure like the example shows in the picture below. + ![1](https://github.com/zerobox-ai/differnet/blob/zijian/dataset/data-generation/annotations/structure1.png) + 2. Dump the videos and annotations (rename them use 1.xml, 1.avi as one pair annotation and video) into the folders under data-generation folder. + ![2](https://github.com/zerobox-ai/differnet/blob/zijian/dataset/data-generation/annotations/structure2.png) + 3. Modify the annotation files: Since the annotation uses label "defect" to indicate the defect area, while, both good and defective bottles are labeled as "bottle" which is confusing. To indicate which "bottle" is defective, we need to find the frames that labeled with defect, and then manully update the group's label from "bottle" to "defective" for the groups that falling in to those frames. - + ![3](https://github.com/zerobox-ai/differnet/blob/zijian/dataset/data-generation/annotations/structure3.png) + - For example: in the example image above, the frame 15 and 16 are labeled as "defect" which indicates those 2 frames has defect areas on the bottles. So we need to find the group that contains frame 15 and 16, and then manully update the label from "bottle" to "defective". and then delete the whole \ group that labeled as "defect" (since we don't care about the defect area in data extraction). 4. Modify the config.py, fill in appropriate value for num_videos, save_cropped_image_to and save_original_image_to From 537e4612fe596778e260b48d2d57e9c512aff75b Mon Sep 17 00:00:00 2001 From: txrxrxr Date: Mon, 23 Nov 2020 21:43:55 -0700 Subject: [PATCH 35/65] Added new shrink function. Shrink top by 20% and rest by 5%. --- utils.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/utils.py b/utils.py index ceaea95..4525e2a 100644 --- a/utils.py +++ b/utils.py @@ -35,14 +35,17 @@ def random_shrink2(img_size): width, height = img_size center_x = int(width / 2) center_y = int(height / 2) - shrink_scaleW = random.uniform(0.05, 0.15) - shrink_scaleH = random.uniform(0.1, 0.2) - new_width = int(width * (1 - shrink_scaleW)) - new_height = int(height * (1 - shrink_scaleH)) + shrink_scaleT = 0.2 + shrink_scale = 0.05 + top_reduction = shrink_scaleT * height + bot_reduction = shrink_scale * height + lr_reduction = shrink_scale * width + new_height = int(height - top_reduction - bot_reduction) + new_width = int(width - 2*lr_reduction) new_ul_x = int(center_x - new_width / 2) new_ul_y = int(center_y - new_height / 2) print( - f"shrinking ({0, 0, width, height}) to ({new_ul_x, new_ul_y, new_width, new_height}) by {shrink_scaleW, shrink_scaleH}" + f"shrinking ({0, 0, width, height}) to ({new_ul_x, new_ul_y, new_width, new_height})" ) return new_ul_x, new_ul_y, new_width, new_height @@ -119,7 +122,7 @@ def target_transform(target): saturation=c.transf_saturation)] tfs = [randomCrop(), transforms.Resize(c.img_size)] \ - + augmentative_transforms + [ TransformShow("", 200), transforms.ToTensor(), transforms.Normalize(c.norm_mean, c.norm_std)] + + augmentative_transforms + [ TransformShow("", 100), transforms.ToTensor(), transforms.Normalize(c.norm_mean, c.norm_std)] transform_train = transforms.Compose(tfs) From 2f65829b4fe5711eee220c164152aa54dc624c46 Mon Sep 17 00:00:00 2001 From: txrxrxr Date: Wed, 25 Nov 2020 10:39:56 -0700 Subject: [PATCH 36/65] Fixed bugs in shrink function. --- utils.py | 47 +++++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/utils.py b/utils.py index 4525e2a..035da2a 100644 --- a/utils.py +++ b/utils.py @@ -1,51 +1,54 @@ import os import torch -from torch.utils.data import DataLoader from torchvision import datasets, transforms import config as c from multi_transform_loader import ImageFolderMultiTransform -import random import cv2 import numpy as np from datetime import datetime def TransformShow(name="img", wait=100): def transform_show(img): + # path = "transform/" + # now = datetime.now() + # dt_string = now.strftime("%d%m%Y%H%M%S") + # cv2.imwrite(path + 'all_transform_' + dt_string + '.jpg', np.array(img)) cv2.imshow(name, np.array(img)) cv2.waitKey(wait) return img return transform_show -def randomCrop(): - def random_crop(img): - x,y,w,h = random_shrink2(img.size) +def cropImage(): + def crop_image(img): + x,y,w,h = shrinkEdges(img.size) rs = transforms.functional.crop(img,y,x,h,w) - # path = r'C:\Users\fiona\Desktop\differnet\transform\\' + # path = "transform/" # now = datetime.now() # dt_string = now.strftime("%d%m%Y%H%M%S") # cv2.imwrite(path + 'transform_' + dt_string + '.jpg', np.array(rs)) return rs - return random_crop + return crop_image -def random_shrink2(img_size): +def shrinkEdges(img_size): width, height = img_size - center_x = int(width / 2) - center_y = int(height / 2) - shrink_scaleT = 0.2 - shrink_scale = 0.05 - top_reduction = shrink_scaleT * height - bot_reduction = shrink_scale * height - lr_reduction = shrink_scale * width - new_height = int(height - top_reduction - bot_reduction) - new_width = int(width - 2*lr_reduction) - new_ul_x = int(center_x - new_width / 2) - new_ul_y = int(center_y - new_height / 2) + shrink_scale_top = 0.2 + shrink_scale_bot = 0.05 + shrink_scale_left = 0.05 + shrink_scale_right = 0.05 + top_reduction = shrink_scale_top * width + bot_reduction = shrink_scale_bot * width + left_reduction = shrink_scale_left * height + right_reduction = shrink_scale_right * height + new_height = int(height - left_reduction - right_reduction) + new_width = int(width - top_reduction - bot_reduction) + new_ul_x = int(top_reduction) + new_ul_y = int(right_reduction) print( - f"shrinking ({0, 0, width, height}) to ({new_ul_x, new_ul_y, new_width, new_height})" + f"shrinking {0, 0, width, height} to {new_ul_x, new_ul_y, new_width, new_height} by ({new_width/width:.2f}, {new_height/height:.2f})" ) return new_ul_x, new_ul_y, new_width, new_height @@ -121,8 +124,8 @@ def target_transform(target): augmentative_transforms += [transforms.ColorJitter(brightness=c.transf_brightness, contrast=c.transf_contrast, saturation=c.transf_saturation)] - tfs = [randomCrop(), transforms.Resize(c.img_size)] \ - + augmentative_transforms + [ TransformShow("", 100), transforms.ToTensor(), transforms.Normalize(c.norm_mean, c.norm_std)] + tfs = [cropImage(), transforms.Resize(c.img_size)] \ + + augmentative_transforms + [ TransformShow("Transformed Image", 100), transforms.ToTensor(), transforms.Normalize(c.norm_mean, c.norm_std)] transform_train = transforms.Compose(tfs) From d2e34758ed0253ad19c713d1dc1dbe92261d4a34 Mon Sep 17 00:00:00 2001 From: Jason0917 Date: Thu, 26 Nov 2020 16:49:37 +0800 Subject: [PATCH 37/65] Generate and save ROC curve image --- config.py | 8 +- flows.py | 528 ++++++++++++++++++++++++++++++++++++++++++++++ localization.py | 41 +--- logo_detection.py | 60 ++++++ model.py | 2 + train.py | 22 +- utils.py | 12 +- 7 files changed, 625 insertions(+), 48 deletions(-) create mode 100644 flows.py create mode 100644 logo_detection.py diff --git a/config.py b/config.py index 833b1f9..f851344 100644 --- a/config.py +++ b/config.py @@ -2,14 +2,14 @@ research purposes. Don't try this code if you are a software engineer.''' # device settings -device = 'cpu' # 'cuda' or 'cpu' +device = 'cuda' # 'cuda' or 'cpu' import torch torch.cuda.set_device(0) # data settings dataset_path = "dataset" -class_name = "zerobox-2009-5" -modelname = "zerobox-2009-5" +class_name = "zerobox-2010-2-zijian" +modelname = "zerobox-2010-2-zijian" img_size = (448, 448) img_dims = [3] + list(img_size) @@ -49,4 +49,4 @@ hide_tqdm_bar = True save_model = True -target_tpr = 0.85 +target_tpr = 0.85 \ No newline at end of file diff --git a/flows.py b/flows.py new file mode 100644 index 0000000..9af567c --- /dev/null +++ b/flows.py @@ -0,0 +1,528 @@ +import math +import types + +import numpy as np +import scipy as sp +import scipy.linalg +import torch +import torch.nn as nn +import torch.nn.functional as F + + +def get_mask(in_features, out_features, in_flow_features, mask_type=None): + """ + mask_type: input | None | output + + See Figure 1 for a better illustration: + https://arxiv.org/pdf/1502.03509.pdf + """ + if mask_type == 'input': + in_degrees = torch.arange(in_features) % in_flow_features + else: + in_degrees = torch.arange(in_features) % (in_flow_features - 1) + + if mask_type == 'output': + out_degrees = torch.arange(out_features) % in_flow_features - 1 + else: + out_degrees = torch.arange(out_features) % (in_flow_features - 1) + + return (out_degrees.unsqueeze(-1) >= in_degrees.unsqueeze(0)).float() + + +class MaskedLinear(nn.Module): + def __init__(self, + in_features, + out_features, + mask, + cond_in_features=None, + bias=True): + super(MaskedLinear, self).__init__() + self.linear = nn.Linear(in_features, out_features) + if cond_in_features is not None: + self.cond_linear = nn.Linear( + cond_in_features, out_features, bias=False) + + self.register_buffer('mask', mask) + + def forward(self, inputs, cond_inputs=None): + output = F.linear(inputs, self.linear.weight * self.mask, + self.linear.bias) + if cond_inputs is not None: + output += self.cond_linear(cond_inputs) + return output + + +nn.MaskedLinear = MaskedLinear + + +class MADESplit(nn.Module): + """ An implementation of MADE + (https://arxiv.org/abs/1502.03509). + """ + + def __init__(self, + num_inputs, + num_hidden, + num_cond_inputs=None, + s_act='tanh', + t_act='relu', + pre_exp_tanh=False): + super(MADESplit, self).__init__() + + self.pre_exp_tanh = pre_exp_tanh + + activations = {'relu': nn.ReLU, 'sigmoid': nn.Sigmoid, 'tanh': nn.Tanh} + + input_mask = get_mask(num_inputs, num_hidden, num_inputs, + mask_type='input') + hidden_mask = get_mask(num_hidden, num_hidden, num_inputs) + output_mask = get_mask(num_hidden, num_inputs, num_inputs, + mask_type='output') + + act_func = activations[s_act] + self.s_joiner = nn.MaskedLinear(num_inputs, num_hidden, input_mask, + num_cond_inputs) + + self.s_trunk = nn.Sequential(act_func(), + nn.MaskedLinear(num_hidden, num_hidden, + hidden_mask), act_func(), + nn.MaskedLinear(num_hidden, num_inputs, + output_mask)) + + act_func = activations[t_act] + self.t_joiner = nn.MaskedLinear(num_inputs, num_hidden, input_mask, + num_cond_inputs) + + self.t_trunk = nn.Sequential(act_func(), + nn.MaskedLinear(num_hidden, num_hidden, + hidden_mask), act_func(), + nn.MaskedLinear(num_hidden, num_inputs, + output_mask)) + + def forward(self, inputs, cond_inputs=None, rev=False): + if rev == False: + h = self.s_joiner(inputs, cond_inputs) + m = self.s_trunk(h) + + h = self.t_joiner(inputs, cond_inputs) + a = self.t_trunk(h) + + if self.pre_exp_tanh: + a = torch.tanh(a) + + u = (inputs - m) * torch.exp(-a) + return u, -a.sum(-1, keepdim=True) + + else: + x = torch.zeros_like(inputs) + for i_col in range(inputs.shape[1]): + h = self.s_joiner(x, cond_inputs) + m = self.s_trunk(h) + + h = self.t_joiner(x, cond_inputs) + a = self.t_trunk(h) + + if self.pre_exp_tanh: + a = torch.tanh(a) + + x[:, i_col] = inputs[:, i_col] * torch.exp( + a[:, i_col]) + m[:, i_col] + return x, -a.sum(-1, keepdim=True) + +class MADE(nn.Module): + """ An implementation of MADE + (https://arxiv.org/abs/1502.03509). + """ + + def __init__(self, + num_inputs, + num_hidden, + num_cond_inputs=None, + act='relu', + pre_exp_tanh=False): + super(MADE, self).__init__() + + activations = {'relu': nn.ReLU, 'sigmoid': nn.Sigmoid, 'tanh': nn.Tanh} + act_func = activations[act] + + input_mask = get_mask( + num_inputs, num_hidden, num_inputs, mask_type='input') + hidden_mask = get_mask(num_hidden, num_hidden, num_inputs) + output_mask = get_mask( + num_hidden, num_inputs * 2, num_inputs, mask_type='output') + + self.joiner = nn.MaskedLinear(num_inputs, num_hidden, input_mask, + num_cond_inputs) + + self.trunk = nn.Sequential(act_func(), + nn.MaskedLinear(num_hidden, num_hidden, + hidden_mask), act_func(), + nn.MaskedLinear(num_hidden, num_inputs * 2, + output_mask)) + + def forward(self, inputs, cond_inputs=None, rev=False): + if rev == False: + h = self.joiner(inputs, cond_inputs) + m, a = self.trunk(h).chunk(2, 1) + u = (inputs - m) * torch.exp(-a) + return u, -a.sum(-1, keepdim=True) + + else: + x = torch.zeros_like(inputs) + for i_col in range(inputs.shape[1]): + h = self.joiner(x, cond_inputs) + m, a = self.trunk(h).chunk(2, 1) + x[:, i_col] = inputs[:, i_col] * torch.exp( + a[:, i_col]) + m[:, i_col] + return x, -a.sum(-1, keepdim=True) + + +class Sigmoid(nn.Module): + def __init__(self): + super(Sigmoid, self).__init__() + + def forward(self, inputs, cond_inputs=None, rev=False): + if rev == False: + s = torch.sigmoid + return s(inputs), torch.log(s(inputs) * (1 - s(inputs))).sum( + -1, keepdim=True) + else: + return torch.log(inputs / + (1 - inputs)), -torch.log(inputs - inputs**2).sum( + -1, keepdim=True) + + +class Logit(Sigmoid): + def __init__(self): + super(Logit, self).__init__() + + def forward(self, inputs, cond_inputs=None, rev=False): + if rev == False: + return super(Logit, self).forward(inputs, 'inverse') + else: + return super(Logit, self).forward(inputs, 'direct') + + + +class BatchNormFlow(nn.Module): + """ An implementation of a batch normalization layer from + Density estimation using Real NVP + (https://arxiv.org/abs/1605.08803). + """ + + def __init__(self, num_inputs, momentum=0.0, eps=1e-5): + super(BatchNormFlow, self).__init__() + + num_inputs = num_inputs[0][0] + self.log_gamma = nn.Parameter(torch.zeros(num_inputs)) + self.beta = nn.Parameter(torch.zeros(num_inputs)) + self.momentum = momentum + self.eps = eps + + self.register_buffer('running_mean', torch.zeros(num_inputs)) + self.register_buffer('running_var', torch.ones(num_inputs)) + + def forward(self, inputs, cond_inputs=None, rev=False): + if rev == False: + if self.training: + inputs = inputs[0] + #inputs = torch.Tensor(inputs) + #inputs = np.array(inputs) + self.batch_mean = inputs.mean(0) + self.batch_var = ( + inputs - self.batch_mean).pow(2).mean(0) + self.eps + + self.running_mean.mul_(self.momentum) + self.running_var.mul_(self.momentum) + + self.running_mean.add_(self.batch_mean.data * + (1 - self.momentum)) + self.running_var.add_(self.batch_var.data * + (1 - self.momentum)) + + mean = self.batch_mean + var = self.batch_var + else: + mean = self.running_mean + var = self.running_var + + x_hat = (inputs - mean) / var.sqrt() + y = torch.exp(self.log_gamma) * x_hat + self.beta + return y, (self.log_gamma - 0.5 * torch.log(var)).sum( + -1, keepdim=True) + else: + if self.training: + mean = self.batch_mean + var = self.batch_var + else: + mean = self.running_mean + var = self.running_var + + x_hat = (inputs - self.beta) / torch.exp(self.log_gamma) + + y = x_hat * var.sqrt() + mean + + return y, (-self.log_gamma + 0.5 * torch.log(var)).sum( + -1, keepdim=True) + + def output_dims(self, input_dims): + assert len(input_dims) == 1, "Can only use 1 input" + return input_dims + +class ActNorm(nn.Module): + """ An implementation of a activation normalization layer + from Glow: Generative Flow with Invertible 1x1 Convolutions + (https://arxiv.org/abs/1807.03039). + """ + + def __init__(self, num_inputs): + super(ActNorm, self).__init__() + num_inputs = num_inputs[0][0] + self.weight = nn.Parameter(torch.ones(num_inputs)) + self.bias = nn.Parameter(torch.zeros(num_inputs)) + self.initialized = False + + def forward(self, inputs, cond_inputs=None, rev=False): + inputs = inputs[0] + if self.initialized == False: + self.weight.data.copy_(torch.log(1.0 / (inputs.std(0) + 1e-12))) + self.bias.data.copy_(inputs.mean(0)) + self.initialized = True + + if rev == False: + return ( + inputs - self.bias) * torch.exp(self.weight), self.weight.sum( + -1, keepdim=True).unsqueeze(0).repeat(inputs.size(0), 1) + else: + return inputs * torch.exp( + -self.weight) + self.bias, -self.weight.sum( + -1, keepdim=True).unsqueeze(0).repeat(inputs.size(0), 1) + + def output_dims(self, input_dims): + assert len(input_dims) == 1, "Can only use 1 input" + return input_dims + + +class InvertibleMM(nn.Module): + """ An implementation of a invertible matrix multiplication + layer from Glow: Generative Flow with Invertible 1x1 Convolutions + (https://arxiv.org/abs/1807.03039). + """ + + def __init__(self, num_inputs): + super(InvertibleMM, self).__init__() + self.W = nn.Parameter(torch.Tensor(num_inputs, num_inputs)) + nn.init.orthogonal_(self.W) + + def forward(self, inputs, cond_inputs=None, rev=False): + if rev == False: + return inputs @ self.W, torch.slogdet( + self.W)[-1].unsqueeze(0).unsqueeze(0).repeat( + inputs.size(0), 1) + else: + return inputs @ torch.inverse(self.W), -torch.slogdet( + self.W)[-1].unsqueeze(0).unsqueeze(0).repeat( + inputs.size(0), 1) + + +class LUInvertibleMM(nn.Module): + """ An implementation of a invertible matrix multiplication + layer from Glow: Generative Flow with Invertible 1x1 Convolutions + (https://arxiv.org/abs/1807.03039). + """ + + def __init__(self, num_inputs): + super(LUInvertibleMM, self).__init__() + num_inputs = num_inputs[0][0] + self.W = torch.Tensor(num_inputs, num_inputs) + nn.init.orthogonal_(self.W) + self.L_mask = torch.tril(torch.ones(self.W.size()), -1) + self.U_mask = self.L_mask.t().clone() + + P, L, U = sp.linalg.lu(self.W.numpy()) + self.P = torch.from_numpy(P) + self.L = nn.Parameter(torch.from_numpy(L)) + self.U = nn.Parameter(torch.from_numpy(U)) + + S = np.diag(U) + sign_S = np.sign(S) + log_S = np.log(abs(S)) + self.sign_S = torch.from_numpy(sign_S) + self.log_S = nn.Parameter(torch.from_numpy(log_S)) + + self.I = torch.eye(self.L.size(0)) + + def forward(self, inputs, cond_inputs=None, rev=False): + if str(self.L_mask.device) != str(self.L.device): + self.L_mask = self.L_mask.to(self.L.device) + self.U_mask = self.U_mask.to(self.L.device) + self.I = self.I.to(self.L.device) + self.P = self.P.to(self.L.device) + self.sign_S = self.sign_S.to(self.L.device) + + L = self.L * self.L_mask + self.I + U = self.U * self.U_mask + torch.diag( + self.sign_S * torch.exp(self.log_S)) + W = self.P @ L @ U + + if rev == False: + return inputs[0] @ W, self.log_S.sum().unsqueeze(0).unsqueeze( + 0).repeat(inputs[0].size(0), 1) + else: + return inputs[0] @ torch.inverse( + W), -self.log_S.sum().unsqueeze(0).unsqueeze(0).repeat( + inputs[0].size(0), 1) + + def output_dims(self, input_dims): + assert len(input_dims) == 1, "Can only use 1 input" + return input_dims + + def jacobian(self, x, rev=False): + return 0. + +class Shuffle(nn.Module): + """ An implementation of a shuffling layer from + Density estimation using Real NVP + (https://arxiv.org/abs/1605.08803). + """ + + def __init__(self, num_inputs): + super(Shuffle, self).__init__() + self.perm = np.random.permutation(num_inputs) + self.inv_perm = np.argsort(self.perm) + + def forward(self, inputs, cond_inputs=None, rev=False): + if rev == False: + return inputs[:, self.perm], torch.zeros( + inputs.size(0), 1, device=inputs.device) + else: + return inputs[:, self.inv_perm], torch.zeros( + inputs.size(0), 1, device=inputs.device) + + +class Reverse(nn.Module): + """ An implementation of a reversing layer from + Density estimation using Real NVP + (https://arxiv.org/abs/1605.08803). + """ + + def __init__(self, num_inputs): + super(Reverse, self).__init__() + self.perm = np.array(np.arange(0, num_inputs)[::-1]) + self.inv_perm = np.argsort(self.perm) + + def forward(self, inputs, cond_inputs=None, rev=False): + if rev == False: + return inputs[:, self.perm], torch.zeros( + inputs.size(0), 1, device=inputs.device) + else: + return inputs[:, self.inv_perm], torch.zeros( + inputs.size(0), 1, device=inputs.device) + + +class CouplingLayer(nn.Module): + """ An implementation of a coupling layer + from RealNVP (https://arxiv.org/abs/1605.08803). + """ + + def __init__(self, + num_inputs, + num_hidden, + mask, + num_cond_inputs=None, + s_act='tanh', + t_act='relu'): + super(CouplingLayer, self).__init__() + self.num_inputs = num_inputs[0][0] + self.mask = mask + + activations = {'relu': nn.ReLU, 'sigmoid': nn.Sigmoid, 'tanh': nn.Tanh} + s_act_func = activations[s_act] + t_act_func = activations[t_act] + + if num_cond_inputs is not None: + total_inputs = num_inputs + num_cond_inputs + else: + total_inputs = num_inputs + + total_inputs = total_inputs[0][0] + self.scale_net = nn.Sequential( + nn.Linear(total_inputs, num_hidden), s_act_func(), + nn.Linear(num_hidden, num_hidden), s_act_func(), + nn.Linear(num_hidden, num_inputs[0][0])) + self.translate_net = nn.Sequential( + nn.Linear(total_inputs, num_hidden), t_act_func(), + nn.Linear(num_hidden, num_hidden), t_act_func(), + nn.Linear(num_hidden, num_inputs[0][0])) + + def init(m): + if isinstance(m, nn.Linear): + m.bias.data.fill_(0) + nn.init.orthogonal_(m.weight.data) + + def forward(self, inputs, cond_inputs=None, rev=False): + mask = self.mask + inputs = inputs[0] + masked_inputs = inputs * mask + if cond_inputs is not None: + masked_inputs = torch.cat([masked_inputs, cond_inputs], -1) + + if rev == False: + log_s = self.scale_net(masked_inputs) * (1 - mask) + t = self.translate_net(masked_inputs) * (1 - mask) + s = torch.exp(log_s) + return inputs * s + t, log_s.sum(-1, keepdim=True) + else: + log_s = self.scale_net(masked_inputs) * (1 - mask) + t = self.translate_net(masked_inputs) * (1 - mask) + s = torch.exp(-log_s) + return (inputs - t) * s, -log_s.sum(-1, keepdim=True) + + def output_dims(self, input_dims): + assert len(input_dims) == 1, "Can only use 1 input" + return input_dims + +class FlowSequential(nn.Sequential): + """ A sequential container for flows. + In addition to a forward pass it implements a backward pass and + computes log jacobians. + """ + + def forward(self, inputs, cond_inputs=None, rev=False, logdets=None): + """ Performs a forward or backward pass for flow modules. + Args: + inputs: a tuple of inputs and logdets + mode: to run direct computation or inverse + """ + self.num_inputs = inputs.size(-1) + + if logdets is None: + logdets = torch.zeros(inputs.size(0), 1, device=inputs.device) + + # assert mode in ['direct', 'inverse'] + if rev == False: + for module in self._modules.values(): + inputs, logdet = module(inputs, cond_inputs, rev) + logdets += logdet + else: + for module in reversed(self._modules.values()): + inputs, logdet = module(inputs, cond_inputs, rev) + logdets += logdet + + return inputs, logdets + + def log_probs(self, inputs, cond_inputs = None): + u, log_jacob = self(inputs, cond_inputs) + log_probs = (-0.5 * u.pow(2) - 0.5 * math.log(2 * math.pi)).sum( + -1, keepdim=True) + return (log_probs + log_jacob).sum(-1, keepdim=True) + + def sample(self, num_samples=None, noise=None, cond_inputs=None): + if noise is None: + noise = torch.Tensor(num_samples, self.num_inputs).normal_() + device = next(self.parameters()).device + noise = noise.to(device) + if cond_inputs is not None: + cond_inputs = cond_inputs.to(device) + samples = self.forward(noise, cond_inputs, mode='inverse')[0] + return samples diff --git a/localization.py b/localization.py index b4b6f36..17e5778 100644 --- a/localization.py +++ b/localization.py @@ -16,30 +16,19 @@ def save_imgs(inputs, grad, cnt): - print(f"calling save_image(input={inputs.shape}, grad={grad.shape})") export_dir = os.path.join(GRADIENT_MAP_DIR, c.modelname) if not os.path.exists(export_dir): os.makedirs(export_dir) for g in range(grad.shape[0]): - normed_grad = (grad[g] - np.min(grad[g])) / (np.max(grad[g]) - np.min(grad[g])) - # normed_grad = grad[g] - print("cnt={:d}/g={:d}: minGrad={:.2e}, maxGrad={:.2e}, max/min={:.2e}".format( - cnt, g, np.min(grad[g]), np.max(grad[g]), np.max(grad[g])/np.min(grad[g]))) + normed_grad = (grad[g] - np.min(grad[g])) / ( + np.max(grad[g]) - np.min(grad[g])) orig_image = inputs[g] - for image, file_suffix in [ - (normed_grad, "_gradient_map.png"), - (orig_image, "_orig.png"), - ]: + for image, file_suffix in [(normed_grad, '_gradient_map.png'), (orig_image, '_orig.png')]: plt.clf() plt.imshow(image) - #plt.imshow(image, vmin=0, vmax=1e13) - plt.axis("off") - plt.savefig( - os.path.join(export_dir, f"{cnt}_{g}" + file_suffix), - bbox_inches="tight", - pad_inches=0, - ) + plt.axis('off') + plt.savefig(os.path.join(export_dir, str(cnt) + file_suffix), bbox_inches='tight', pad_inches=0) cnt += 1 return cnt @@ -54,7 +43,6 @@ def export_gradient_maps(model, testloader, optimizer, n_batches=1): for i, data in enumerate(tqdm(testloader, disable=c.hide_tqdm_bar)): optimizer.zero_grad() inputs, labels = preprocess_batch(data) - print(f"i={i}: inputs={inputs.shape}, labels={labels}") inputs = Variable(inputs, requires_grad=True) emb = model(inputs) @@ -67,19 +55,10 @@ def export_gradient_maps(model, testloader, optimizer, n_batches=1): continue grad = t2np(grad) - print(f"origina: shape of inputs={inputs.shape}") - allInputs = inputs.view(c.n_transforms_test, *inputs.shape[-3:]) inputs = inputs.view(-1, c.n_transforms_test, *inputs.shape[-3:])[:, 0] - - print(f"view: shape of inputs={inputs.shape}, allInputs={allInputs.shape}") inputs = np.transpose(t2np(inputs[labels > 0]), [0, 2, 3, 1]) - allInputs = np.transpose(t2np(allInputs), [0, 2, 3, 1]) - - print(f"transpose: shape of inputs={inputs.shape}, allInputs={allInputs.shape}") inputs_unnormed = np.clip(inputs * c.norm_std + c.norm_mean, 0, 1) - print(f"shape of inputs={inputs.shape},inputs_unnormed={inputs_unnormed.shape}") - images = np.zeros([c.n_transforms_test,480, 270, 3]) for i_item in range(c.n_transforms_test): old_shape = grad[:, i_item].shape img = np.reshape(grad[:, i_item], [-1, *grad.shape[-2:]]) @@ -87,20 +66,10 @@ def export_gradient_maps(model, testloader, optimizer, n_batches=1): img = np.transpose(rotate(img, degrees[i_item], reshape=False), [2, 0, 1]) img = gaussian_filter(img, (0, 3, 3)) grad[:, i_item] = np.reshape(img, old_shape) - # print(f"shape of img={img.shape}, grad={grad.shape}") - # PyTorch tensors assume the color channel is the first dimension - # but matplotlib assumes is the third dimension - images[i_item, :] = img.transpose((1, 2, 0)) - - #save_imgs(allInputs,images,0) - grad = np.reshape(grad, [grad.shape[0], -1, *grad.shape[-2:]]) grad_img = np.mean(np.abs(grad), axis=1) grad_img_sq = grad_img ** 2 - print(f"shape of grad={grad.shape}, grad_img={grad_img.shape}") - # print(f"inputs_unnormed={inputs_unnormed}") - # print(f"grad_img_sq={grad_img_sq}") cnt = save_imgs(inputs_unnormed, grad_img_sq, cnt) diff --git a/logo_detection.py b/logo_detection.py new file mode 100644 index 0000000..e1a304a --- /dev/null +++ b/logo_detection.py @@ -0,0 +1,60 @@ +from skimage import io +import matplotlib.pyplot as plt +import numpy as np +import cv2 + +from skimage.color import rgb2gray +from skimage import feature + +for i in range(12): + break + +I1 = io.imread("bottle_logo_defective1.jpg") +cv2.imwrite("origin.jpg", I1) + +image = cv2.cvtColor(I1, cv2.COLOR_BGR2GRAY) +cv2.imwrite("Gray.jpg", image) + +image = cv2.GaussianBlur(image, (21, 21), 0) + +# seg_img = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 3, 1) +seg_img = cv2.threshold(image, 30, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1] +cv2.imwrite('binary.jpg', seg_img) + +h0, w0 = seg_img.shape + +if seg_img[0][0] == 255: + for i in range(w0): + for j in range(2): + if seg_img[j][i] == 255: + seg_img[j][i] = 0 + for j in range(h0): + for i in range(2): + if seg_img[j][i] == 255: + seg_img[j][i] = 0 + +cv2.imwrite("Threshold.jpg", seg_img) + +num, labels, stats, centroids = cv2.connectedComponentsWithStats(seg_img) + +h, w = seg_img.shape +print(h * w) + +first_stat = 0 +second_stat = 0 + +# Largest area should be the bottle +# Second largest area should be the logo +for istat in stats: + if istat[4] > 2000 and istat[4] > second_stat: + if istat[4] > first_stat: + first_stat = istat[4] + else: + second_stat = istat[4] + logo_stat = istat + +print(logo_stat) +cv2.rectangle(I1, (logo_stat[0], logo_stat[1]), (logo_stat[0] + logo_stat[2], logo_stat[1] + logo_stat[3]), + (255, 0, 255), 2) + +cv2.imwrite("segmented.jpg", I1) diff --git a/model.py b/model.py index 89f7594..9b0bf67 100644 --- a/model.py +++ b/model.py @@ -8,6 +8,7 @@ import config as c from freia_funcs import permute_layer, glow_coupling_layer, F_fully_connected, ReversibleGraphNet, OutputNode, \ InputNode, Node +from flows import LUInvertibleMM WEIGHT_DIR = './weights' MODEL_DIR = './models' @@ -18,6 +19,7 @@ def nf_head(input_dim=c.n_feat): nodes.append(InputNode(input_dim, name='input')) for k in range(c.n_coupling_blocks): nodes.append(Node([nodes[-1].out0], permute_layer, {'seed': k}, name=F'permute_{k}')) + # nodes.append(Node([nodes[-1].out0], LUInvertibleMM, {'seed': k}, name=F'permute_{k}')) nodes.append(Node([nodes[-1].out0], glow_coupling_layer, {'clamp': c.clamp_alpha, 'F_class': F_fully_connected, 'F_args': {'internal_size': c.fc_internal, 'dropout': c.dropout}}, diff --git a/train.py b/train.py index 1982918..e896a21 100644 --- a/train.py +++ b/train.py @@ -9,6 +9,8 @@ from model import DifferNet, save_model, save_weights from utils import * +from datetime import datetime +import matplotlib.pyplot as plt import json class Score_Observer: @@ -97,6 +99,22 @@ def train(train_loader, validate_loader): model_parameters['tpr'] = tpr.tolist() model_parameters['thresholds'] = thresholds.tolist() + plt.figure() + lw = 2 + plt.figure(figsize=(10, 10)) + plt.plot(fpr.tolist(), tpr.tolist(), color='darkorange', + lw=lw, label='ROC curve') + plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--') + plt.xlim([0.0, 1.0]) + plt.ylim([0.0, 1.0]) + plt.xlabel('False Positive Rate') + plt.ylabel('True Positive Rate') + plt.title('ROC Curve') + plt.legend(loc="lower right") + now = datetime.now() + dt_string = now.strftime("%d%m%Y%H%M%S") + plt.savefig('ROC_' + dt_string + '.jpg') + with open('models/' + c.modelname + '.json', 'w') as jsonfile: jsonfile.write(json.dumps(model_parameters)) @@ -111,8 +129,8 @@ def train(train_loader, validate_loader): print('tpr: ', tpr) print('thresholds: ', thresholds) -# if c.grad_map_viz and not (validate_loader is None): -# export_gradient_maps(model, validate_loader, optimizer, -1) + if c.grad_map_viz and not (validate_loader is None): + export_gradient_maps(model, validate_loader, optimizer, 1) if c.save_model: model.to('cpu') diff --git a/utils.py b/utils.py index 4525e2a..9dc4e37 100644 --- a/utils.py +++ b/utils.py @@ -23,10 +23,10 @@ def randomCrop(): def random_crop(img): x,y,w,h = random_shrink2(img.size) rs = transforms.functional.crop(img,y,x,h,w) - # path = r'C:\Users\fiona\Desktop\differnet\transform\\' - # now = datetime.now() - # dt_string = now.strftime("%d%m%Y%H%M%S") - # cv2.imwrite(path + 'transform_' + dt_string + '.jpg', np.array(rs)) + path = 'cropped/' + now = datetime.now() + dt_string = now.strftime("%d%m%Y%H%M%S") + cv2.imwrite(path + 'transform_' + dt_string + '.jpg', np.array(rs)) return rs return random_crop @@ -43,7 +43,7 @@ def random_shrink2(img_size): new_height = int(height - top_reduction - bot_reduction) new_width = int(width - 2*lr_reduction) new_ul_x = int(center_x - new_width / 2) - new_ul_y = int(center_y - new_height / 2) + new_ul_y = int(bot_reduction) print( f"shrinking ({0, 0, width, height}) to ({new_ul_x, new_ul_y, new_width, new_height})" ) @@ -116,7 +116,7 @@ def target_transform(target): augmentative_transforms = [] if c.transf_rotations: - augmentative_transforms += [transforms.RandomRotation(180)] + augmentative_transforms += [transforms.RandomRotation(5)] if c.transf_brightness > 0.0 or c.transf_contrast > 0.0 or c.transf_saturation > 0.0: augmentative_transforms += [transforms.ColorJitter(brightness=c.transf_brightness, contrast=c.transf_contrast, saturation=c.transf_saturation)] From 13a0afcfba6dd8b077b2ec528042238a809e361b Mon Sep 17 00:00:00 2001 From: Lihang Ying Date: Fri, 27 Nov 2020 17:57:31 -0700 Subject: [PATCH 38/65] save files with parameters in file name; add rotation degree config; comment too detailed debug info --- config.py | 120 +++++++++--------- model.py | 150 ++++++++++++----------- multi_transform_loader.py | 2 +- train.py | 17 +-- utils.py | 250 +++++++++++++++++++------------------- 5 files changed, 279 insertions(+), 260 deletions(-) diff --git a/config.py b/config.py index 4f1d743..aded08e 100644 --- a/config.py +++ b/config.py @@ -1,57 +1,63 @@ -'''This file configures the training procedure because handling arguments in every single function is so exhaustive for -research purposes. Don't try this code if you are a software engineer.''' - -# data extraction settings -num_videos = 21 -save_cropped_image_to = "dataset/zerobox-2010-1/" -save_original_image_to = "dataset/zerobox-2010-1-original/" - -# device settings -device = 'cpu' # 'cuda' or 'cpu' -import torch -torch.cuda.set_device(0) - -# data settings -dataset_path = "dataset" -class_name = "zerobox-2009-5" -modelname = "zerobox-2009-5" - -img_size = (448, 448) -img_dims = [3] + list(img_size) -add_img_noise = 0.01 - -# transformation settings -transf_rotations = True -transf_brightness = 0.0 -transf_contrast = 0.0 -transf_saturation = 0.0 -norm_mean, norm_std = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225] - -# network hyperparameters -n_scales = 3 # number of scales at which features are extracted, img_size is the highest - others are //2, //4,... -clamp_alpha = 3 # see paper equation 2 for explanation -n_coupling_blocks = 8 -# fc_internal = 2048 # number of neurons in hidden layers of s-t-networks -fc_internal = 1536 # number of neurons in hidden layers of s-t-networks -dropout = 0.0 # dropout in s-t-networks -lr_init = 2e-4 -n_feat = 256 * n_scales # do not change except you change the feature extractor - -# dataloader parameters -n_transforms = 4 # number of transformations per sample in training -n_transforms_test = 16 # number of transformations per sample in testing -batch_size = 4 # actual batch size is this value multiplied by n_transforms(_test) -batch_size_test = batch_size * n_transforms // n_transforms_test - -# total epochs = meta_epochs * sub_epochs -# evaluation after epochs -meta_epochs = 1 -sub_epochs = 8 - -# output settings -verbose = True -grad_map_viz = True -hide_tqdm_bar = True -save_model = True - -target_tpr = 0.85 +'''This file configures the training procedure because handling arguments in every single function is so exhaustive for +research purposes. Don't try this code if you are a software engineer.''' + +# data extraction settings +num_videos = 21 +save_cropped_image_to = "dataset/zerobox-2010-1/" +save_original_image_to = "dataset/zerobox-2010-1-original/" + +# device settings +device = 'cuda' # 'cuda' or 'cpu' +import torch +torch.cuda.set_device(0) + +# data settings +dataset_path = "dataset" +class_name = "zerobox-2010-2-zijian" +modelname = "zerobox-2010-2-zijian" + +img_size = (448, 448) +img_dims = [3] + list(img_size) +add_img_noise = 0.01 + +# transformation settings +transf_rotations = True +transf_brightness = 0.0 +transf_contrast = 0.0 +transf_saturation = 0.0 +norm_mean, norm_std = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225] + +rotation_degree = 5 +crop_top = 0.0 +crop_left = 0.0 +crop_bottom = 0.0 +crop_right = 0.0 + +# network hyperparameters +n_scales = 3 # number of scales at which features are extracted, img_size is the highest - others are //2, //4,... +clamp_alpha = 3 # see paper equation 2 for explanation +n_coupling_blocks = 8 +# fc_internal = 2048 # number of neurons in hidden layers of s-t-networks +fc_internal = 1536 # number of neurons in hidden layers of s-t-networks +dropout = 0.0 # dropout in s-t-networks +lr_init = 2e-4 +n_feat = 256 * n_scales # do not change except you change the feature extractor + +# dataloader parameters +n_transforms = 4 # number of transformations per sample in training +n_transforms_test = 16 # number of transformations per sample in testing +batch_size = 4 # actual batch size is this value multiplied by n_transforms(_test) +batch_size_test = batch_size * n_transforms // n_transforms_test + +# total epochs = meta_epochs * sub_epochs +# evaluation after epochs +meta_epochs = 2 +sub_epochs = 8 + +# output settings +verbose = True +grad_map_viz = True +hide_tqdm_bar = True +save_model = True + +target_tpr = 0.85 diff --git a/model.py b/model.py index 89f7594..51dbdef 100644 --- a/model.py +++ b/model.py @@ -1,70 +1,80 @@ -import numpy as np -import os -import torch -import torch.nn.functional as F -from torch import nn -from torchvision.models import alexnet - -import config as c -from freia_funcs import permute_layer, glow_coupling_layer, F_fully_connected, ReversibleGraphNet, OutputNode, \ - InputNode, Node - -WEIGHT_DIR = './weights' -MODEL_DIR = './models' - - -def nf_head(input_dim=c.n_feat): - nodes = list() - nodes.append(InputNode(input_dim, name='input')) - for k in range(c.n_coupling_blocks): - nodes.append(Node([nodes[-1].out0], permute_layer, {'seed': k}, name=F'permute_{k}')) - nodes.append(Node([nodes[-1].out0], glow_coupling_layer, - {'clamp': c.clamp_alpha, 'F_class': F_fully_connected, - 'F_args': {'internal_size': c.fc_internal, 'dropout': c.dropout}}, - name=F'fc_{k}')) - nodes.append(OutputNode([nodes[-1].out0], name='output')) - coder = ReversibleGraphNet(nodes) - return coder - - -class DifferNet(nn.Module): - def __init__(self): - super(DifferNet, self).__init__() - self.feature_extractor = alexnet(pretrained=True) - self.nf = nf_head() - - def forward(self, x): - y_cat = list() - - for s in range(c.n_scales): - x_scaled = F.interpolate(x, size=c.img_size[0] // (2 ** s)) if s > 0 else x - feat_s = self.feature_extractor.features(x_scaled) - y_cat.append(torch.mean(feat_s, dim=(2, 3))) - - y = torch.cat(y_cat, dim=1) - z = self.nf(y) - return z - - -def save_model(model, filename): - if not os.path.exists(MODEL_DIR): - os.makedirs(MODEL_DIR) - torch.save(model, os.path.join(MODEL_DIR, filename)) - - -def load_model(filename): - path = os.path.join(MODEL_DIR, filename) - model = torch.load(path) - return model - - -def save_weights(model, filename): - if not os.path.exists(WEIGHT_DIR): - os.makedirs(WEIGHT_DIR) - torch.save(model.state_dict(), os.path.join(WEIGHT_DIR, filename)) - - -def load_weights(model, filename): - path = os.path.join(WEIGHT_DIR, filename) - model.load_state_dict(torch.load(path)) - return model +import numpy as np +import os +import torch +import torch.nn.functional as F +from torch import nn +from torchvision.models import alexnet + +import config as c +from freia_funcs import permute_layer, glow_coupling_layer, F_fully_connected, ReversibleGraphNet, OutputNode, \ + InputNode, Node + +import json + +WEIGHT_DIR = './weights' +MODEL_DIR = './models' + + +def nf_head(input_dim=c.n_feat): + nodes = list() + nodes.append(InputNode(input_dim, name='input')) + for k in range(c.n_coupling_blocks): + nodes.append(Node([nodes[-1].out0], permute_layer, {'seed': k}, name=F'permute_{k}')) + nodes.append(Node([nodes[-1].out0], glow_coupling_layer, + {'clamp': c.clamp_alpha, 'F_class': F_fully_connected, + 'F_args': {'internal_size': c.fc_internal, 'dropout': c.dropout}}, + name=F'fc_{k}')) + nodes.append(OutputNode([nodes[-1].out0], name='output')) + coder = ReversibleGraphNet(nodes) + return coder + + +class DifferNet(nn.Module): + def __init__(self): + super(DifferNet, self).__init__() + self.feature_extractor = alexnet(pretrained=True) + self.nf = nf_head() + + def forward(self, x): + y_cat = list() + + for s in range(c.n_scales): + x_scaled = F.interpolate(x, size=c.img_size[0] // (2 ** s)) if s > 0 else x + feat_s = self.feature_extractor.features(x_scaled) + y_cat.append(torch.mean(feat_s, dim=(2, 3))) + + y = torch.cat(y_cat, dim=1) + z = self.nf(y) + return z + + +def save_model(model, filename): + if not os.path.exists(MODEL_DIR): + os.makedirs(MODEL_DIR) + torch.save(model, os.path.join(MODEL_DIR, filename)) + + +def load_model(filename): + path = os.path.join(MODEL_DIR, filename) + model = torch.load(path) + return model + + +def save_weights(model, filename): + if not os.path.exists(WEIGHT_DIR): + os.makedirs(WEIGHT_DIR) + torch.save(model.state_dict(), os.path.join(WEIGHT_DIR, filename)) + + +def load_weights(model, filename): + path = os.path.join(WEIGHT_DIR, filename) + model.load_state_dict(torch.load(path)) + return model + + +def save_parameters(model_parameters, filename): + if not os.path.exists(MODEL_DIR): + os.makedirs(MODEL_DIR) + + with open(MODEL_DIR + '/' + filename + '.json', 'w') as jsonfile: + jsonfile.write(json.dumps(model_parameters, indent=4)) diff --git a/multi_transform_loader.py b/multi_transform_loader.py index a7d18e7..44c3192 100644 --- a/multi_transform_loader.py +++ b/multi_transform_loader.py @@ -54,7 +54,7 @@ def __getitem__(self, index): # print(f"i={i}: calling transform({sample})") samples = torch.stack(samples, dim=0) if self.target_transform is not None: - print(f"calling target_transform({target})") + # print(f"calling target_transform({target})") target = self.target_transform(target) return samples, target diff --git a/train.py b/train.py index 1982918..63b97bf 100644 --- a/train.py +++ b/train.py @@ -6,10 +6,9 @@ import config as c from localization import export_gradient_maps -from model import DifferNet, save_model, save_weights +from model import DifferNet, save_model, save_parameters, save_weights from utils import * -import json class Score_Observer: '''Keeps an eye on the current and highest score so far''' @@ -38,6 +37,9 @@ def train(train_loader, validate_loader): optimizer = torch.optim.Adam(model.nf.parameters(), lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, weight_decay=1e-5) model.to(c.device) + save_name_pre = '{}_{}_{}_{}_{}_{}'.format(c.modelname, c.rotation_degree, + c.crop_top, c.crop_left, c.crop_bottom, c.crop_right) + score_obs = Score_Observer('AUROC') for epoch in range(c.meta_epochs): @@ -88,7 +90,8 @@ def train(train_loader, validate_loader): z_grouped = torch.cat(test_z, dim=0).view(-1, c.n_transforms_test, c.n_feat) anomaly_score = t2np(torch.mean(z_grouped ** 2, dim=(-2, -1))) - score_obs.update(roc_auc_score(is_anomaly, anomaly_score), epoch, + AUROC = roc_auc_score(is_anomaly, anomaly_score) + score_obs.update(AUROC, epoch, print_score=c.verbose or epoch == c.meta_epochs - 1) fpr, tpr, thresholds = roc_curve(is_anomaly, anomaly_score) @@ -96,9 +99,9 @@ def train(train_loader, validate_loader): model_parameters['fpr'] = fpr.tolist() model_parameters['tpr'] = tpr.tolist() model_parameters['thresholds'] = thresholds.tolist() + model_parameters['AUROC'] = AUROC - with open('models/' + c.modelname + '.json', 'w') as jsonfile: - jsonfile.write(json.dumps(model_parameters)) + save_parameters(model_parameters, save_name_pre + '_epoch-' + str(epoch)) if c.verbose: print('Epoch: {:d} \t validate_loss: {:.4f}'.format(epoch, test_loss)) @@ -116,8 +119,8 @@ def train(train_loader, validate_loader): if c.save_model: model.to('cpu') - save_model(model, c.modelname) - save_weights(model, c.modelname) + save_model(model, save_name_pre + '.pth') + save_weights(model, save_name_pre + '.weights.pth') return model, model_parameters diff --git a/utils.py b/utils.py index 2c90434..11c453e 100644 --- a/utils.py +++ b/utils.py @@ -1,125 +1,125 @@ -import os -import torch -from torch.utils.data import DataLoader -from torchvision import datasets, transforms - -import config as c -from multi_transform_loader import ImageFolderMultiTransform - - -def t2np(tensor): - '''pytorch tensor -> numpy array''' - return tensor.cpu().data.numpy() if tensor is not None else None - - -def get_loss(z, jac): - '''check equation 4 of the paper why this makes sense - oh and just ignore the scaling here''' - return torch.mean(0.5 * torch.sum(z ** 2, dim=(1,)) - jac) / z.shape[1] - - -def load_datasets(dataset_path, class_name, test=False): - ''' - Expected folder/file format to find anomalies of class from dataset location : - - train data: - - dataset_path/class_name/train/good/any_filename.png - dataset_path/class_name/train/good/another_filename.tif - dataset_path/class_name/train/good/xyz.png - [...] - - test data: - - 'normal data' = non-anomalies - - dataset_path/class_name/test/good/name_the_file_as_you_like_as_long_as_there_is_an_image_extension.webp - dataset_path/class_name/test/good/did_you_know_the_image_extension_webp?.png - dataset_path/class_name/test/good/did_you_know_that_filenames_may_contain_question_marks????.png - dataset_path/class_name/test/good/dont_know_how_it_is_with_windows.png - dataset_path/class_name/test/good/just_dont_use_windows_for_this.png - [...] - - anomalies - assume there are anomaly classes 'crack' and 'curved' - - dataset_path/class_name/test/crack/dat_crack_damn.png - dataset_path/class_name/test/crack/let_it_crack.png - dataset_path/class_name/test/crack/writing_docs_is_fun.png - [...] - - dataset_path/class_name/test/curved/wont_make_a_difference_if_you_put_all_anomalies_in_one_class.png - dataset_path/class_name/test/curved/but_this_code_is_practicable_for_the_mvtec_dataset.png - [...] - ''' - - def target_transform(target): - return class_perm[target] - - data_dir_train = os.path.join(dataset_path, class_name, 'train') - data_dir_validate = os.path.join(dataset_path, class_name, 'validate') - data_dir_test = os.path.join(dataset_path, class_name, 'test') - - classes = os.listdir(data_dir_validate) - if 'good' not in classes: - print('There should exist a subdirectory "good". Read the doc of this function for further information.') - exit() - classes.sort() - class_perm = list() - class_idx = 1 - for cl in classes: - if cl == 'good': - class_perm.append(0) - else: - class_perm.append(class_idx) - class_idx += 1 - - augmentative_transforms = [] - if c.transf_rotations: - augmentative_transforms += [transforms.RandomRotation(180)] - if c.transf_brightness > 0.0 or c.transf_contrast > 0.0 or c.transf_saturation > 0.0: - augmentative_transforms += [transforms.ColorJitter(brightness=c.transf_brightness, contrast=c.transf_contrast, - saturation=c.transf_saturation)] - - tfs = [transforms.Resize(c.img_size)] + augmentative_transforms + [transforms.ToTensor(), - transforms.Normalize(c.norm_mean, c.norm_std)] - - transform_train = transforms.Compose(tfs) - - trainset = None - validateset = None - testset = None - if test == False: - trainset = ImageFolderMultiTransform(data_dir_train, transform=transform_train, n_transforms=c.n_transforms) - validateset = ImageFolderMultiTransform(data_dir_validate, transform=transform_train, target_transform=target_transform, - n_transforms=c.n_transforms_test) - else: - testset = ImageFolderMultiTransform(data_dir_test, transform=transform_train, target_transform=target_transform, - n_transforms=c.n_transforms_test) - - return trainset, validateset, testset - - -def make_dataloaders(trainset, validateset, testset, test=False): - trainloader = None - validateloader = None - testloader = None - if test == False: - trainloader = torch.utils.data.DataLoader(trainset, pin_memory=True, batch_size=c.batch_size, shuffle=True, - drop_last=False) - validateloader = torch.utils.data.DataLoader(validateset, pin_memory=True, batch_size=c.batch_size, shuffle=True, - drop_last=False) - else: - testloader = torch.utils.data.DataLoader(testset, pin_memory=True, batch_size=c.batch_size_test, shuffle=True, - drop_last=False) - - return trainloader, validateloader, testloader - - -def preprocess_batch(data): - '''move data to device and reshape image''' - inputs, labels = data - print(f"begin: size of inputs={inputs.size()}") - inputs, labels = inputs.to(c.device), labels.to(c.device) - print(f"to: size of inputs={inputs.size()}") - inputs = inputs.view(-1, *inputs.shape[-3:]) - print(f"view: size of inputs={inputs.size()}") - return inputs, labels +import os +import torch +from torch.utils.data import DataLoader +from torchvision import datasets, transforms + +import config as c +from multi_transform_loader import ImageFolderMultiTransform + + +def t2np(tensor): + '''pytorch tensor -> numpy array''' + return tensor.cpu().data.numpy() if tensor is not None else None + + +def get_loss(z, jac): + '''check equation 4 of the paper why this makes sense - oh and just ignore the scaling here''' + return torch.mean(0.5 * torch.sum(z ** 2, dim=(1,)) - jac) / z.shape[1] + + +def load_datasets(dataset_path, class_name, test=False): + ''' + Expected folder/file format to find anomalies of class from dataset location : + + train data: + + dataset_path/class_name/train/good/any_filename.png + dataset_path/class_name/train/good/another_filename.tif + dataset_path/class_name/train/good/xyz.png + [...] + + test data: + + 'normal data' = non-anomalies + + dataset_path/class_name/test/good/name_the_file_as_you_like_as_long_as_there_is_an_image_extension.webp + dataset_path/class_name/test/good/did_you_know_the_image_extension_webp?.png + dataset_path/class_name/test/good/did_you_know_that_filenames_may_contain_question_marks????.png + dataset_path/class_name/test/good/dont_know_how_it_is_with_windows.png + dataset_path/class_name/test/good/just_dont_use_windows_for_this.png + [...] + + anomalies - assume there are anomaly classes 'crack' and 'curved' + + dataset_path/class_name/test/crack/dat_crack_damn.png + dataset_path/class_name/test/crack/let_it_crack.png + dataset_path/class_name/test/crack/writing_docs_is_fun.png + [...] + + dataset_path/class_name/test/curved/wont_make_a_difference_if_you_put_all_anomalies_in_one_class.png + dataset_path/class_name/test/curved/but_this_code_is_practicable_for_the_mvtec_dataset.png + [...] + ''' + + def target_transform(target): + return class_perm[target] + + data_dir_train = os.path.join(dataset_path, class_name, 'train') + data_dir_validate = os.path.join(dataset_path, class_name, 'validate') + data_dir_test = os.path.join(dataset_path, class_name, 'test') + + classes = os.listdir(data_dir_validate) + if 'good' not in classes: + print('There should exist a subdirectory "good". Read the doc of this function for further information.') + exit() + classes.sort() + class_perm = list() + class_idx = 1 + for cl in classes: + if cl == 'good': + class_perm.append(0) + else: + class_perm.append(class_idx) + class_idx += 1 + + augmentative_transforms = [] + if c.transf_rotations: + augmentative_transforms += [transforms.RandomRotation(c.rotation_degree)] + if c.transf_brightness > 0.0 or c.transf_contrast > 0.0 or c.transf_saturation > 0.0: + augmentative_transforms += [transforms.ColorJitter(brightness=c.transf_brightness, contrast=c.transf_contrast, + saturation=c.transf_saturation)] + + tfs = [transforms.Resize(c.img_size)] + augmentative_transforms + [transforms.ToTensor(), + transforms.Normalize(c.norm_mean, c.norm_std)] + + transform_train = transforms.Compose(tfs) + + trainset = None + validateset = None + testset = None + if test == False: + trainset = ImageFolderMultiTransform(data_dir_train, transform=transform_train, n_transforms=c.n_transforms) + validateset = ImageFolderMultiTransform(data_dir_validate, transform=transform_train, target_transform=target_transform, + n_transforms=c.n_transforms_test) + else: + testset = ImageFolderMultiTransform(data_dir_test, transform=transform_train, target_transform=target_transform, + n_transforms=c.n_transforms_test) + + return trainset, validateset, testset + + +def make_dataloaders(trainset, validateset, testset, test=False): + trainloader = None + validateloader = None + testloader = None + if test == False: + trainloader = torch.utils.data.DataLoader(trainset, pin_memory=True, batch_size=c.batch_size, shuffle=True, + drop_last=False) + validateloader = torch.utils.data.DataLoader(validateset, pin_memory=True, batch_size=c.batch_size, shuffle=True, + drop_last=False) + else: + testloader = torch.utils.data.DataLoader(testset, pin_memory=True, batch_size=c.batch_size_test, shuffle=True, + drop_last=False) + + return trainloader, validateloader, testloader + + +def preprocess_batch(data): + '''move data to device and reshape image''' + inputs, labels = data + #print(f"begin: size of inputs={inputs.size()}") + inputs, labels = inputs.to(c.device), labels.to(c.device) + #print(f"to: size of inputs={inputs.size()}") + inputs = inputs.view(-1, *inputs.shape[-3:]) + #print(f"view: size of inputs={inputs.size()}") + return inputs, labels From 00b6b9c7aefc8a4e614d64bac85f71d55711c571 Mon Sep 17 00:00:00 2001 From: Lihang Ying Date: Fri, 27 Nov 2020 22:15:14 -0700 Subject: [PATCH 39/65] put the shrink percentage into config.py; set crop_bottom = 0.2 --- config.py | 10 +++++----- requirements.txt | 1 + train.py | 3 ++- utils.py | 33 ++++++++++++++++++++------------- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/config.py b/config.py index aded08e..8e6c9fa 100644 --- a/config.py +++ b/config.py @@ -27,11 +27,11 @@ transf_saturation = 0.0 norm_mean, norm_std = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225] -rotation_degree = 5 -crop_top = 0.0 -crop_left = 0.0 -crop_bottom = 0.0 -crop_right = 0.0 +rotation_degree = 10 +crop_top = 0.05 +crop_left = 0.05 +crop_bottom = 0.2 +crop_right = 0.05 # network hyperparameters n_scales = 3 # number of scales at which features are extracted, img_size is the highest - others are //2, //4,... diff --git a/requirements.txt b/requirements.txt index eb100f1..508f397 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ torch>=1.00 torchvision>=0.2.2 matplotlib>=3.0.3 tqdm>=4.40.2 +opencv-python \ No newline at end of file diff --git a/train.py b/train.py index 63b54c2..dee5e26 100644 --- a/train.py +++ b/train.py @@ -105,6 +105,7 @@ def train(train_loader, validate_loader): model_parameters['AUROC'] = AUROC save_parameters(model_parameters, save_name_pre + '_epoch-' + str(epoch)) + plt.figure() lw = 2 plt.figure(figsize=(10, 10)) @@ -119,7 +120,7 @@ def train(train_loader, validate_loader): plt.legend(loc="lower right") now = datetime.now() dt_string = now.strftime("%d%m%Y%H%M%S") - plt.savefig('ROC_' + dt_string + '.jpg') + plt.savefig(save_name_pre + '_AUROC_' + dt_string + '.jpg') with open('models/' + c.modelname + '.json', 'w') as jsonfile: jsonfile.write(json.dumps(model_parameters)) diff --git a/utils.py b/utils.py index e715d2a..2b64ad4 100644 --- a/utils.py +++ b/utils.py @@ -9,14 +9,16 @@ import numpy as np from datetime import datetime +TRANSFORM_DIR = "./transform/" + def TransformShow(name="img", wait=100): def transform_show(img): # path = "transform/" # now = datetime.now() # dt_string = now.strftime("%d%m%Y%H%M%S") # cv2.imwrite(path + 'all_transform_' + dt_string + '.jpg', np.array(img)) - cv2.imshow(name, np.array(img)) - cv2.waitKey(wait) + # cv2.imshow(name, np.array(img)) + # cv2.waitKey(wait) return img return transform_show @@ -25,20 +27,24 @@ def cropImage(): def crop_image(img): x,y,w,h = shrinkEdges(img.size) rs = transforms.functional.crop(img,y,x,h,w) - # path = "transform/" - # now = datetime.now() - # dt_string = now.strftime("%d%m%Y%H%M%S") - # cv2.imwrite(path + 'transform_' + dt_string + '.jpg', np.array(rs)) + + if not os.path.exists(TRANSFORM_DIR): + os.makedirs(TRANSFORM_DIR) + + now = datetime.now() + dt_string = now.strftime("%d%m%Y%H%M%S") + # cv2.imwrite(TRANSFORM_DIR + 'transform_' + dt_string + '.jpg', np.array(rs)) return rs return crop_image + def shrinkEdges(img_size): width, height = img_size - shrink_scale_top = 0.2 - shrink_scale_bot = 0.05 - shrink_scale_left = 0.05 - shrink_scale_right = 0.05 + shrink_scale_top = c.crop_top + shrink_scale_bot = c.crop_bottom + shrink_scale_left = c.crop_left + shrink_scale_right = c.crop_right top_reduction = shrink_scale_top * width bot_reduction = shrink_scale_bot * width left_reduction = shrink_scale_left * height @@ -47,11 +53,12 @@ def shrinkEdges(img_size): new_width = int(width - top_reduction - bot_reduction) new_ul_x = int(top_reduction) new_ul_y = int(right_reduction) - print( - f"shrinking {0, 0, width, height} to {new_ul_x, new_ul_y, new_width, new_height} by ({new_width/width:.2f}, {new_height/height:.2f})" - ) + # print( + # f"shrinking {0, 0, width, height} to {new_ul_x, new_ul_y, new_width, new_height} by ({new_width/width:.2f}, {new_height/height:.2f})" + #) return new_ul_x, new_ul_y, new_width, new_height + def t2np(tensor): '''pytorch tensor -> numpy array''' return tensor.cpu().data.numpy() if tensor is not None else None From aef987c14fbb9a30cdfe35fa2247b7a4c66160d9 Mon Sep 17 00:00:00 2001 From: Lihang Ying Date: Fri, 27 Nov 2020 23:59:48 -0700 Subject: [PATCH 40/65] define roc curve plot as a function and save it into model folder --- config.py | 6 +++--- model.py | 21 ++++++++++++++++++++- train.py | 30 ++++-------------------------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/config.py b/config.py index 8e6c9fa..4dd0b29 100644 --- a/config.py +++ b/config.py @@ -30,7 +30,7 @@ rotation_degree = 10 crop_top = 0.05 crop_left = 0.05 -crop_bottom = 0.2 +crop_bottom = 0.1 crop_right = 0.05 # network hyperparameters @@ -51,13 +51,13 @@ # total epochs = meta_epochs * sub_epochs # evaluation after epochs -meta_epochs = 2 +meta_epochs = 3 sub_epochs = 8 # output settings verbose = True grad_map_viz = True hide_tqdm_bar = True -save_model = True +save_model = False target_tpr = 0.85 diff --git a/model.py b/model.py index 28ac2ca..9251da3 100644 --- a/model.py +++ b/model.py @@ -8,8 +8,9 @@ import config as c from freia_funcs import permute_layer, glow_coupling_layer, F_fully_connected, ReversibleGraphNet, OutputNode, \ InputNode, Node -from flows import LUInvertibleMM +from datetime import datetime +import matplotlib.pyplot as plt import json WEIGHT_DIR = './weights' @@ -80,3 +81,21 @@ def save_parameters(model_parameters, filename): with open(MODEL_DIR + '/' + filename + '.json', 'w') as jsonfile: jsonfile.write(json.dumps(model_parameters, indent=4)) + +def save_roc_plot(fpr, tpr, filename): + plt.figure() + lw = 2 + plt.figure(figsize=(10, 10)) + plt.plot(fpr.tolist(), tpr.tolist(), color='darkorange', + lw=lw, label='ROC curve') + plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--') + plt.xlim([0.0, 1.0]) + plt.ylim([0.0, 1.0]) + plt.xlabel('False Positive Rate') + plt.ylabel('True Positive Rate') + plt.title('ROC Curve') + plt.legend(loc="lower right") + now = datetime.now() + dt_string = now.strftime("%Y%m%d%H%M%S") + # plt.savefig(MODEL_DIR + '/' +filename + '_ROC_' + dt_string + '.jpg') + plt.savefig(MODEL_DIR + '/' + filename + '_ROC.jpg') \ No newline at end of file diff --git a/train.py b/train.py index dee5e26..6905245 100644 --- a/train.py +++ b/train.py @@ -6,13 +6,9 @@ import config as c from localization import export_gradient_maps -from model import DifferNet, save_model, save_parameters, save_weights +from model import DifferNet, save_model, save_weights, save_parameters, save_roc_plot from utils import * -from datetime import datetime -import matplotlib.pyplot as plt -import json - class Score_Observer: '''Keeps an eye on the current and highest score so far''' @@ -40,7 +36,7 @@ def train(train_loader, validate_loader): optimizer = torch.optim.Adam(model.nf.parameters(), lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, weight_decay=1e-5) model.to(c.device) - save_name_pre = '{}_{}_{}_{}_{}_{}'.format(c.modelname, c.rotation_degree, + save_name_pre = '{}_{}_{:.2f}_{:.2f}_{:.2f}_{:.2f}'.format(c.modelname, c.rotation_degree, c.crop_top, c.crop_left, c.crop_bottom, c.crop_right) score_obs = Score_Observer('AUROC') @@ -104,26 +100,8 @@ def train(train_loader, validate_loader): model_parameters['thresholds'] = thresholds.tolist() model_parameters['AUROC'] = AUROC - save_parameters(model_parameters, save_name_pre + '_epoch-' + str(epoch)) - - plt.figure() - lw = 2 - plt.figure(figsize=(10, 10)) - plt.plot(fpr.tolist(), tpr.tolist(), color='darkorange', - lw=lw, label='ROC curve') - plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--') - plt.xlim([0.0, 1.0]) - plt.ylim([0.0, 1.0]) - plt.xlabel('False Positive Rate') - plt.ylabel('True Positive Rate') - plt.title('ROC Curve') - plt.legend(loc="lower right") - now = datetime.now() - dt_string = now.strftime("%d%m%Y%H%M%S") - plt.savefig(save_name_pre + '_AUROC_' + dt_string + '.jpg') - - with open('models/' + c.modelname + '.json', 'w') as jsonfile: - jsonfile.write(json.dumps(model_parameters)) + save_parameters(model_parameters, save_name_pre + "_{:.4f}".format(AUROC)) + save_roc_plot(fpr, tpr, save_name_pre + "_{:.4f}".format(AUROC)) if c.verbose: print('Epoch: {:d} \t validate_loss: {:.4f}'.format(epoch, test_loss)) From 8fce55a1100b6737054019de26f781f9e8eba0c3 Mon Sep 17 00:00:00 2001 From: txrxrxr Date: Sun, 29 Nov 2020 21:46:05 -0700 Subject: [PATCH 41/65] Fixed bugs in shrink function. --- utils.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/utils.py b/utils.py index 2b64ad4..0c79f91 100644 --- a/utils.py +++ b/utils.py @@ -38,24 +38,23 @@ def crop_image(img): return crop_image - def shrinkEdges(img_size): width, height = img_size shrink_scale_top = c.crop_top shrink_scale_bot = c.crop_bottom shrink_scale_left = c.crop_left shrink_scale_right = c.crop_right - top_reduction = shrink_scale_top * width - bot_reduction = shrink_scale_bot * width - left_reduction = shrink_scale_left * height - right_reduction = shrink_scale_right * height - new_height = int(height - left_reduction - right_reduction) - new_width = int(width - top_reduction - bot_reduction) - new_ul_x = int(top_reduction) - new_ul_y = int(right_reduction) + left_reduction = shrink_scale_left * width + right_reduction = shrink_scale_right * width + top_reduction = shrink_scale_top * height + bot_reduction = shrink_scale_bot * height + new_height = int(height - top_reduction - bot_reduction) + new_width = int(width - left_reduction - right_reduction) + new_ul_x = int(left_reduction) + new_ul_y = int(top_reduction) # print( - # f"shrinking {0, 0, width, height} to {new_ul_x, new_ul_y, new_width, new_height} by ({new_width/width:.2f}, {new_height/height:.2f})" - #) + # f"shrinking {0, 0, width, height} to {new_ul_x, new_ul_y, new_width, new_height}" + # ) return new_ul_x, new_ul_y, new_width, new_height @@ -132,7 +131,7 @@ def target_transform(target): saturation=c.transf_saturation)] tfs = [cropImage(), transforms.Resize(c.img_size)] \ - + augmentative_transforms + [ TransformShow("Transformed Image", 100), transforms.ToTensor(), transforms.Normalize(c.norm_mean, c.norm_std)] + + augmentative_transforms + [ TransformShow("Transformed Image", 10), transforms.ToTensor(), transforms.Normalize(c.norm_mean, c.norm_std)] transform_train = transforms.Compose(tfs) From c9bd62f223da9a01c3920626b75fc6a1717625b1 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sat, 5 Dec 2020 01:31:09 -0700 Subject: [PATCH 42/65] updated utils to include save_transformed_image in the config.py --- config.py | 17 +++++++++-------- utils.py | 3 ++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/config.py b/config.py index 4dd0b29..51de3bb 100644 --- a/config.py +++ b/config.py @@ -13,8 +13,8 @@ # data settings dataset_path = "dataset" -class_name = "zerobox-2010-2-zijian" -modelname = "zerobox-2010-2-zijian" +class_name = "Experiment 3.1" +modelname = "Experiment 3.1_0_0.05_0.15_0.05_0.15" img_size = (448, 448) img_dims = [3] + list(img_size) @@ -27,11 +27,11 @@ transf_saturation = 0.0 norm_mean, norm_std = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225] -rotation_degree = 10 +rotation_degree = 0 crop_top = 0.05 -crop_left = 0.05 -crop_bottom = 0.1 -crop_right = 0.05 +crop_left = 0.15 +crop_bottom = 0.05 +crop_right = 0.15 # network hyperparameters n_scales = 3 # number of scales at which features are extracted, img_size is the highest - others are //2, //4,... @@ -56,8 +56,9 @@ # output settings verbose = True -grad_map_viz = True +grad_map_viz = False hide_tqdm_bar = True -save_model = False +save_model = True +save_transformed_image = True target_tpr = 0.85 diff --git a/utils.py b/utils.py index 2b64ad4..33636c0 100644 --- a/utils.py +++ b/utils.py @@ -33,7 +33,8 @@ def crop_image(img): now = datetime.now() dt_string = now.strftime("%d%m%Y%H%M%S") - # cv2.imwrite(TRANSFORM_DIR + 'transform_' + dt_string + '.jpg', np.array(rs)) + if(c.save_transformed_image): + cv2.imwrite(TRANSFORM_DIR + 'transform_' + dt_string + '.jpg', np.array(rs)) return rs return crop_image From f5b056bc992f22b90e08e7c4ef26fe2cb540774f Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sat, 5 Dec 2020 01:33:19 -0700 Subject: [PATCH 43/65] draw ground truth bounding box on saved original frames --- data_extraction.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/data_extraction.py b/data_extraction.py index d78b9c9..948948c 100644 --- a/data_extraction.py +++ b/data_extraction.py @@ -46,6 +46,14 @@ xbr = boxesList[frameList.index(j)][3] label = 'good' if labelList[frameList.index(j)] == 'bottle' else 'defect' + # draw bounding box on original frames + if label != 'defect': + cv2.rectangle(frame, (xtl, ytl), (xbr, ybr), (0, 255, 0), 5) + else: + cv2.rectangle(frame, (xtl, ytl), (xbr, ybr), (255, 0, 0), 5) + #cv2.imshow("Show", frame) + #cv2.waitKey() + #cv2.destroyAllWindows() # Crop the frames with the bounding box position info crop_frame = frame[int(ytl*(1+shrink_percentage)):int(ybr*(1-shrink_percentage)), int(xtl*(1+shrink_percentage)):int(xbr*(1-shrink_percentage))] From 0eb3a4089262c6abcdf0cd381dc557287a16ee8b Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sat, 5 Dec 2020 19:15:45 -0700 Subject: [PATCH 44/65] Included prediction visualization part --- data_extraction.py | 2 +- predict.py | 21 ++++++++ train.py | 129 ++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 137 insertions(+), 15 deletions(-) create mode 100644 predict.py diff --git a/data_extraction.py b/data_extraction.py index 948948c..b0bb99f 100644 --- a/data_extraction.py +++ b/data_extraction.py @@ -50,7 +50,7 @@ if label != 'defect': cv2.rectangle(frame, (xtl, ytl), (xbr, ybr), (0, 255, 0), 5) else: - cv2.rectangle(frame, (xtl, ytl), (xbr, ybr), (255, 0, 0), 5) + cv2.rectangle(frame, (xtl, ytl), (xbr, ybr), (0, 0, 255), 5) #cv2.imshow("Show", frame) #cv2.waitKey() #cv2.destroyAllWindows() diff --git a/predict.py b/predict.py new file mode 100644 index 0000000..7df4c43 --- /dev/null +++ b/predict.py @@ -0,0 +1,21 @@ +import config as c +from train import * +from utils import load_datasets, make_dataloaders +import time +import gc +import json + +_, _, predict_set = load_datasets(c.dataset_path, 'predict', test=True) +_, _, predict_loader = make_dataloaders(None, None, predict_set, test=True) + +model = torch.load("models/" + c.modelname + "", map_location=torch.device('cpu')) + +with open('models/' + c.modelname + '.json') as jsonfile: + model_parameters = json.load(jsonfile) + +time_start = time.time() +predict(model, model_parameters, predict_loader) +time_end = time.time() +time_c = time_end - time_start +print("predicting time cost: {:f} s".format(time_c)) + diff --git a/train.py b/train.py index 6905245..95ef046 100644 --- a/train.py +++ b/train.py @@ -8,6 +8,8 @@ from localization import export_gradient_maps from model import DifferNet, save_model, save_weights, save_parameters, save_roc_plot from utils import * +from operator import itemgetter +import cv2 class Score_Observer: '''Keeps an eye on the current and highest score so far''' @@ -139,20 +141,20 @@ def test(model, model_parameters, test_loader): test_loss = list() test_z = list() test_labels = list() - # with torch.no_grad(): - for i, data in enumerate(tqdm(test_loader, disable=c.hide_tqdm_bar)): - inputs, labels = preprocess_batch(data) - # inputs = Variable(inputs, requires_grad=True) - print(f"i={i}: labels={labels}, size of inputs={inputs.size()}") - # print(f"inputs={inputs}") - z = model(inputs) - # print(f"z={z}") - loss = get_loss(z, model.nf.jacobian(run_forward=False)) - test_z.append(z) - test_loss.append(t2np(loss)) - test_labels.append(t2np(labels)) - - test_loss = np.mean(np.array(test_loss)) + with torch.no_grad(): + for i, data in enumerate(tqdm(test_loader, disable=c.hide_tqdm_bar)): + inputs, labels = preprocess_batch(data) + # inputs = Variable(inputs, requires_grad=True) + print(f"i={i}: labels={labels}, size of inputs={inputs.size()}") + # print(f"inputs={inputs}") + z = model(inputs) + # print(f"z={z}") + loss = get_loss(z, model.nf.jacobian(run_forward=False)) + test_z.append(z) + test_loss.append(t2np(loss)) + test_labels.append(t2np(labels)) + + test_loss = np.mean(np.array(test_loss)) if c.verbose: print('Epoch: {:d} \t test_loss: {:.4f}'.format(epoch, test_loss)) @@ -182,3 +184,102 @@ def test(model, model_parameters, test_loader): print(f"test_labels={test_labels}, is_anomaly={is_anomaly},anomaly_score={anomaly_score},is_anomaly_detected={is_anomaly_detected}") print(f"target_tpr={c.target_tpr}, target_threshold={target_threshold}, test_accuracy={test_accuracy}") + +def predict(model, model_parameters, predict_loader): + print("Predicting") + optimizer = torch.optim.Adam(model.nf.parameters(), lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, weight_decay=1e-5) + model.to(c.device) + model.eval() + if c.verbose: + print('\nCompute loss and scores on test set:') + test_z = list() + test_labels = list() + predictions = [] + with torch.no_grad(): + for i, data in enumerate(predict_loader): + inputs, labels = preprocess_batch(data) + frame = int(predict_loader.dataset.imgs[i][0].split('frame',1)[1].split('-')[0]) + print(f"i={i}: frame#={frame}, labels={labels.cpu().numpy()[0]}, size of inputs={inputs.size()}") + predictions.append([frame, predict_loader.dataset.imgs[i][0], labels.cpu().numpy()[0], 0]) + z = model(inputs) + test_z.append(z) + test_labels.append(t2np(labels)) + + test_labels = np.concatenate(test_labels) + is_anomaly = np.array([0 if l == 0 else 1 for l in test_labels]) + + z_grouped = torch.cat(test_z, dim=0).view(-1, c.n_transforms_test, c.n_feat) + anomaly_score = t2np(torch.mean(z_grouped ** 2, dim=(-2, -1))) + + for i in range(len(model_parameters['tpr'])): + if model_parameters['tpr'][i] > c.target_tpr: + target_threshold = model_parameters['thresholds'][i] + break + + is_anomaly_detected = [] + i = 0 + for l in anomaly_score: + if l < target_threshold: + is_anomaly_detected.append(0) + predictions[i][3] = 0 + else: + is_anomaly_detected.append(1) + predictions[i][3] = 1 + i += 1 + predictions = sorted(predictions, key=itemgetter(0)) + + # calculate test accuracy + error_count = 0 + for i in range(len(is_anomaly)): + if is_anomaly[i] != is_anomaly_detected[i]: + error_count += 1 + + test_accuracy = 1 - float(error_count) / len(is_anomaly) + + # print(f"test_labels={test_labels}, is_anomaly={is_anomaly},anomaly_score={anomaly_score},is_anomaly_detected={is_anomaly_detected}") + print(f"target_tpr={c.target_tpr}, target_threshold={target_threshold}, test_accuracy={test_accuracy}") + if c.grad_map_viz: + print("saving gradient maps...") + export_gradient_maps(model, predict_loader, optimizer, -1) + + # visualize the prediction result + if c.visualization: + for i in range(len(predictions)): + # load file path + file_path = predictions[i][1] + idx = file_path.index('video') + file_path = file_path[:idx] + 'original-' + file_path[idx:] + file_path = file_path.replace("predict\\test", "zerobox-2010-1-original") + + # rotate and resize image + img = cv2.imread(file_path) + img = cv2.rotate(img, cv2.cv2.ROTATE_90_COUNTERCLOCKWISE) + img = cv2.resize(img, (600, 900)) + + # display prediction on each frame + font = cv2.FONT_HERSHEY_DUPLEX + font_size = 0.7 + + if (predictions[i][3] == 1): + img = cv2.putText(img, 'prediction: defective', (300, 810), font, + font_size, (0, 0, 255), 1, cv2.LINE_AA) + else: + img = cv2.putText(img, 'prediction: good', (300, 810), font, + font_size, (0, 255, 0), 1, cv2.LINE_AA) + + if (predictions[i][2] == 1): + img = cv2.putText(img, 'ground truth: defective', (300, 830), font, + font_size, (0, 0, 255), 1, cv2.LINE_AA) + else: + img = cv2.putText(img, 'ground truth: good', (300, 830), font, + font_size, (0, 255, 0), 1, cv2.LINE_AA) + + img = cv2.putText(img, 'frame #: ' + str(predictions[i][0]), (300, 850), font, + font_size, (0, 255, 0), 1, cv2.LINE_AA) + img = cv2.putText(img, 'accuracy: ' + str(test_accuracy), (300, 870), font, + font_size, (0, 255, 0), 1, cv2.LINE_AA) + img = cv2.putText(img, 'threshold: ' + str(target_threshold), (300, 890), font, + font_size, (0, 255, 0), 1, cv2.LINE_AA) + # show results + cv2.imshow('window', img) + cv2.waitKey(220) From 26fe50f54c7866a778d6d46db865c11925afe4cf Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sat, 5 Dec 2020 19:16:11 -0700 Subject: [PATCH 45/65] Updated config.py --- config.py | 6 +++--- utils.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config.py b/config.py index 51de3bb..a028d80 100644 --- a/config.py +++ b/config.py @@ -45,9 +45,9 @@ # dataloader parameters n_transforms = 4 # number of transformations per sample in training -n_transforms_test = 16 # number of transformations per sample in testing +n_transforms_test = 1 # number of transformations per sample in testing batch_size = 4 # actual batch size is this value multiplied by n_transforms(_test) -batch_size_test = batch_size * n_transforms // n_transforms_test +batch_size_test = 1 # total epochs = meta_epochs * sub_epochs # evaluation after epochs @@ -60,5 +60,5 @@ hide_tqdm_bar = True save_model = True save_transformed_image = True - +visualization = True target_tpr = 0.85 diff --git a/utils.py b/utils.py index b0d3063..d792fe8 100644 --- a/utils.py +++ b/utils.py @@ -160,7 +160,7 @@ def make_dataloaders(trainset, validateset, testset, test=False): validateloader = torch.utils.data.DataLoader(validateset, pin_memory=True, batch_size=c.batch_size, shuffle=True, drop_last=False) else: - testloader = torch.utils.data.DataLoader(testset, pin_memory=True, batch_size=c.batch_size_test, shuffle=True, + testloader = torch.utils.data.DataLoader(testset, pin_memory=True, batch_size=c.batch_size_test, shuffle=False, drop_last=False) return trainloader, validateloader, testloader From 8d7b731532a7e1f7431d47ffe6bf486d9f2ff196 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sat, 5 Dec 2020 22:42:35 -0700 Subject: [PATCH 46/65] Updated visualization part --- train.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/train.py b/train.py index 95ef046..14baf59 100644 --- a/train.py +++ b/train.py @@ -200,7 +200,7 @@ def predict(model, model_parameters, predict_loader): inputs, labels = preprocess_batch(data) frame = int(predict_loader.dataset.imgs[i][0].split('frame',1)[1].split('-')[0]) print(f"i={i}: frame#={frame}, labels={labels.cpu().numpy()[0]}, size of inputs={inputs.size()}") - predictions.append([frame, predict_loader.dataset.imgs[i][0], labels.cpu().numpy()[0], 0]) + predictions.append([frame, predict_loader.dataset.imgs[i][0], labels.cpu().numpy()[0], 0, 0]) z = model(inputs) test_z.append(z) test_labels.append(t2np(labels)) @@ -219,6 +219,7 @@ def predict(model, model_parameters, predict_loader): is_anomaly_detected = [] i = 0 for l in anomaly_score: + predictions[i][4] = l if l < target_threshold: is_anomaly_detected.append(0) predictions[i][3] = 0 @@ -258,27 +259,27 @@ def predict(model, model_parameters, predict_loader): # display prediction on each frame font = cv2.FONT_HERSHEY_DUPLEX - font_size = 0.7 - + font_size = 0.65 + pos_x = 330 if (predictions[i][3] == 1): - img = cv2.putText(img, 'prediction: defective', (300, 810), font, + img = cv2.putText(img, 'prediction: defective', (pos_x, 810), font, font_size, (0, 0, 255), 1, cv2.LINE_AA) else: - img = cv2.putText(img, 'prediction: good', (300, 810), font, + img = cv2.putText(img, 'prediction: good', (pos_x, 810), font, font_size, (0, 255, 0), 1, cv2.LINE_AA) if (predictions[i][2] == 1): - img = cv2.putText(img, 'ground truth: defective', (300, 830), font, + img = cv2.putText(img, 'ground truth: defective', (pos_x, 830), font, font_size, (0, 0, 255), 1, cv2.LINE_AA) else: - img = cv2.putText(img, 'ground truth: good', (300, 830), font, + img = cv2.putText(img, 'ground truth: good', (pos_x, 830), font, font_size, (0, 255, 0), 1, cv2.LINE_AA) - img = cv2.putText(img, 'frame #: ' + str(predictions[i][0]), (300, 850), font, + img = cv2.putText(img, 'anomaly score: ' + str(round(predictions[i][4], 4)), (pos_x, 850), font, font_size, (0, 255, 0), 1, cv2.LINE_AA) - img = cv2.putText(img, 'accuracy: ' + str(test_accuracy), (300, 870), font, + img = cv2.putText(img, 'threshold: ' + str(round(target_threshold, 4)), (pos_x, 870), font, font_size, (0, 255, 0), 1, cv2.LINE_AA) - img = cv2.putText(img, 'threshold: ' + str(target_threshold), (300, 890), font, + img = cv2.putText(img, 'accuracy: ' + str(round(test_accuracy * 100, 2)) + '%', (pos_x, 890), font, font_size, (0, 255, 0), 1, cv2.LINE_AA) # show results cv2.imshow('window', img) From 954177450fdb5972010781e1ad6201bdbfbe1671 Mon Sep 17 00:00:00 2001 From: txrxrxr Date: Wed, 9 Dec 2020 20:14:23 -0700 Subject: [PATCH 47/65] Added new model MaskDifferNet. --- model.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/model.py b/model.py index 9251da3..8d74732 100644 --- a/model.py +++ b/model.py @@ -4,6 +4,7 @@ import torch.nn.functional as F from torch import nn from torchvision.models import alexnet +from torch.autograd import Variable import config as c from freia_funcs import permute_layer, glow_coupling_layer, F_fully_connected, ReversibleGraphNet, OutputNode, \ @@ -16,6 +17,60 @@ WEIGHT_DIR = './weights' MODEL_DIR = './models' +# copy from https://github.com/pytorch/examples/blob/master/vae/main.py +class VAE(nn.Module): + def __init__(self): + super(VAE, self).__init__() + + self.fc1 = nn.Linear(784, 400) + self.fc21 = nn.Linear(400, 20) + self.fc22 = nn.Linear(400, 20) + self.fc3 = nn.Linear(20, 400) + self.fc4 = nn.Linear(400, 784) + + def encode(self, x): + h1 = F.relu(self.fc1(x)) + return self.fc21(h1), self.fc22(h1) + + def reparameterize(self, mu, logvar): + std = torch.exp(0.5*logvar) + eps = torch.randn_like(std) + return mu + eps*std + + def decode(self, z): + h3 = F.relu(self.fc3(z)) + return torch.sigmoid(self.fc4(h3)) + + def forward(self, x): + mu, logvar = self.encode(x.view(-1, 784)) + z = self.reparameterize(mu, logvar) + return self.decode(z), mu, logvar + + +class MaskDifferNet(nn.Module): + def __init__(self): + super(MaskDifferNet, self).__init__() + self.differnet = DifferNet() + self.nf = self.differnet.nf + self.vae = VAE() + + def forward(self, x): + y = self.vae(x) + # y_grayscale = y[0].numpy + y[1].numpy + y[2].numpy + + # output a mask. refer to: https://discuss.pytorch.org/t/binary-mask-output-by-network/27458/5 + # x = Variable(x, requires_grad=False) + # loss = loss_function(y[0], x, y[1], y[2]) + mask = torch.relu(torch.sign(torch.sigmoid(y[0]) - 0.5)) + + # apply mask to the input image. + # refer to: https://stackoverflow.com/questions/58521595/masking-tensor-of-same-shape-in-pytorch + mask = mask.view(x.shape) + z = x * mask.int().float() + + output = self.differnet(z) + + return output def nf_head(input_dim=c.n_feat): nodes = list() From bc9177ffc3f5ec974de0c52c0b9aed58b8c814ab Mon Sep 17 00:00:00 2001 From: txrxrxr Date: Wed, 9 Dec 2020 20:56:58 -0700 Subject: [PATCH 48/65] Added visual for MaskDifferNet. --- model.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/model.py b/model.py index 8d74732..8a1fe4b 100644 --- a/model.py +++ b/model.py @@ -13,6 +13,7 @@ from datetime import datetime import matplotlib.pyplot as plt import json +import cv2 WEIGHT_DIR = './weights' MODEL_DIR = './models' @@ -61,12 +62,19 @@ def forward(self, x): # output a mask. refer to: https://discuss.pytorch.org/t/binary-mask-output-by-network/27458/5 # x = Variable(x, requires_grad=False) # loss = loss_function(y[0], x, y[1], y[2]) - mask = torch.relu(torch.sign(torch.sigmoid(y[0]) - 0.5)) + mask = torch.sign(torch.sigmoid(y[0]) - 0.5) # apply mask to the input image. # refer to: https://stackoverflow.com/questions/58521595/masking-tensor-of-same-shape-in-pytorch mask = mask.view(x.shape) + mask_img = np.reshape(np.squeeze(mask.cpu().detach().numpy()), (448, 448, 3)) + cv2.imshow('mask image', mask_img) + cv2.waitKey(100) z = x * mask.int().float() + z_img = np.reshape(np.squeeze(z.cpu().detach().numpy()), (448, 448, 3)) + cv2.imshow('z image', z_img) + cv2.waitKey(100) + output = self.differnet(z) From beb4b1bb5846ca982dd5585cf281dcd698e9167f Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Wed, 9 Dec 2020 22:06:34 -0700 Subject: [PATCH 49/65] Added optimizer parameters for VAE --- model.py | 13 ++++++++++++- train.py | 8 +++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/model.py b/model.py index 8d74732..1cec49f 100644 --- a/model.py +++ b/model.py @@ -13,6 +13,7 @@ from datetime import datetime import matplotlib.pyplot as plt import json +import cv2 WEIGHT_DIR = './weights' MODEL_DIR = './models' @@ -46,6 +47,7 @@ def forward(self, x): z = self.reparameterize(mu, logvar) return self.decode(z), mu, logvar +# todo: VAE + CNN to generate mask, we need to train the CNN's parameter class MaskDifferNet(nn.Module): def __init__(self): @@ -62,11 +64,20 @@ def forward(self, x): # x = Variable(x, requires_grad=False) # loss = loss_function(y[0], x, y[1], y[2]) mask = torch.relu(torch.sign(torch.sigmoid(y[0]) - 0.5)) - + y_img = torch.squeeze(y[0].view(x.shape)).permute(2, 1, 0).cpu().detach().numpy() + cv2.imshow('VAE output', y_img) + cv2.waitKey(1) # apply mask to the input image. # refer to: https://stackoverflow.com/questions/58521595/masking-tensor-of-same-shape-in-pytorch mask = mask.view(x.shape) + x_img = torch.squeeze(x).permute(2, 1, 0).cpu().detach().numpy() + cv2.imshow('original input', x_img) + cv2.waitKey(1) + z = x * mask.int().float() + z_img = torch.squeeze(z).permute(2, 1, 0).cpu().detach().numpy() + cv2.imshow('original + mask', z_img) + cv2.waitKey(1) output = self.differnet(z) diff --git a/train.py b/train.py index 14baf59..9d2210b 100644 --- a/train.py +++ b/train.py @@ -6,7 +6,7 @@ import config as c from localization import export_gradient_maps -from model import DifferNet, save_model, save_weights, save_parameters, save_roc_plot +from model import DifferNet, save_model, save_weights, save_parameters, save_roc_plot, MaskDifferNet from utils import * from operator import itemgetter import cv2 @@ -34,8 +34,10 @@ def print_score(self): def train(train_loader, validate_loader): - model = DifferNet() - optimizer = torch.optim.Adam(model.nf.parameters(), lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, weight_decay=1e-5) + model = MaskDifferNet() + optimizer = torch.optim.Adam([{'params': model.nf.parameters()}, + {'params': model.vae.parameters()} + ], lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, weight_decay=1e-5) model.to(c.device) save_name_pre = '{}_{}_{:.2f}_{:.2f}_{:.2f}_{:.2f}'.format(c.modelname, c.rotation_degree, From fdcf9540d68c14d0838fdc8721b9d2b7b84bdb7d Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Mon, 28 Dec 2020 19:40:23 -0700 Subject: [PATCH 50/65] code clean up --- config.py | 2 +- model.py | 9 ++------- train.py | 7 ++++--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/config.py b/config.py index a028d80..592e220 100644 --- a/config.py +++ b/config.py @@ -14,7 +14,7 @@ # data settings dataset_path = "dataset" class_name = "Experiment 3.1" -modelname = "Experiment 3.1_0_0.05_0.15_0.05_0.15" +modelname = "Experiment 3.2_0_0.05_0.15_0.05_0.15" img_size = (448, 448) img_dims = [3] + list(img_size) diff --git a/model.py b/model.py index 1cec49f..eff26ae 100644 --- a/model.py +++ b/model.py @@ -58,17 +58,12 @@ def __init__(self): def forward(self, x): y = self.vae(x) - # y_grayscale = y[0].numpy + y[1].numpy + y[2].numpy - # output a mask. refer to: https://discuss.pytorch.org/t/binary-mask-output-by-network/27458/5 - # x = Variable(x, requires_grad=False) - # loss = loss_function(y[0], x, y[1], y[2]) mask = torch.relu(torch.sign(torch.sigmoid(y[0]) - 0.5)) y_img = torch.squeeze(y[0].view(x.shape)).permute(2, 1, 0).cpu().detach().numpy() cv2.imshow('VAE output', y_img) cv2.waitKey(1) - # apply mask to the input image. - # refer to: https://stackoverflow.com/questions/58521595/masking-tensor-of-same-shape-in-pytorch + mask = mask.view(x.shape) x_img = torch.squeeze(x).permute(2, 1, 0).cpu().detach().numpy() cv2.imshow('original input', x_img) @@ -79,7 +74,7 @@ def forward(self, x): cv2.imshow('original + mask', z_img) cv2.waitKey(1) - output = self.differnet(z) + output = self.differnet(y[0].view(x.shape)) return output diff --git a/train.py b/train.py index 9d2210b..2de2177 100644 --- a/train.py +++ b/train.py @@ -34,9 +34,10 @@ def print_score(self): def train(train_loader, validate_loader): - model = MaskDifferNet() - optimizer = torch.optim.Adam([{'params': model.nf.parameters()}, - {'params': model.vae.parameters()} + model = DifferNet() + optimizer = torch.optim.Adam([{'params': model.nf.parameters()} + + ], lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, weight_decay=1e-5) model.to(c.device) From d04da3b3866b7758565e9d5764922f4442553bf8 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Thu, 28 Jan 2021 22:27:26 -0700 Subject: [PATCH 51/65] modified prediction class, to check if frame_name_is_given on each image file --- config.py | 3 ++- train.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/config.py b/config.py index 592e220..54eb19a 100644 --- a/config.py +++ b/config.py @@ -60,5 +60,6 @@ hide_tqdm_bar = True save_model = True save_transformed_image = True -visualization = True +visualization = False +frame_name_is_given = False target_tpr = 0.85 diff --git a/train.py b/train.py index 2de2177..6c1ba47 100644 --- a/train.py +++ b/train.py @@ -201,7 +201,9 @@ def predict(model, model_parameters, predict_loader): with torch.no_grad(): for i, data in enumerate(predict_loader): inputs, labels = preprocess_batch(data) - frame = int(predict_loader.dataset.imgs[i][0].split('frame',1)[1].split('-')[0]) + if c.frame_name_is_given: + frame = int(predict_loader.dataset.imgs[i][0].split('frame',1)[1].split('-')[0]) + frame = i print(f"i={i}: frame#={frame}, labels={labels.cpu().numpy()[0]}, size of inputs={inputs.size()}") predictions.append([frame, predict_loader.dataset.imgs[i][0], labels.cpu().numpy()[0], 0, 0]) z = model(inputs) From ce354e1ec63431932f0e606d03cf8f7d1609fbe1 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Thu, 28 Jan 2021 23:43:25 -0700 Subject: [PATCH 52/65] Modify predict.py code for differnet to have better result visualization --- train.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/train.py b/train.py index 6c1ba47..2a7404b 100644 --- a/train.py +++ b/train.py @@ -204,7 +204,7 @@ def predict(model, model_parameters, predict_loader): if c.frame_name_is_given: frame = int(predict_loader.dataset.imgs[i][0].split('frame',1)[1].split('-')[0]) frame = i - print(f"i={i}: frame#={frame}, labels={labels.cpu().numpy()[0]}, size of inputs={inputs.size()}") + #print(f"i={i}: frame#={frame}, labels={labels.cpu().numpy()[0]}, size of inputs={inputs.size()}") predictions.append([frame, predict_loader.dataset.imgs[i][0], labels.cpu().numpy()[0], 0, 0]) z = model(inputs) test_z.append(z) @@ -242,12 +242,32 @@ def predict(model, model_parameters, predict_loader): test_accuracy = 1 - float(error_count) / len(is_anomaly) + for i in range(len(predictions)): + msg = 'frame: ' + str(i) + '. ' + if (predictions[i][3] == 1): + msg += 'prediction: defective. ' + else: + msg += 'prediction: good. ' + + if (predictions[i][2] == 1): + msg += 'ground truth: defective. ' + else: + msg += 'ground truth: good. ' + + msg += 'anomaly score: ' + str(round(predictions[i][4], 4)) + '. ' + msg += 'threshold: ' + str(round(target_threshold, 4)) + '. ' + msg += 'accuracy: ' + str(round(test_accuracy * 100, 2)) + '%' + + print(msg) + # print(f"test_labels={test_labels}, is_anomaly={is_anomaly},anomaly_score={anomaly_score},is_anomaly_detected={is_anomaly_detected}") print(f"target_tpr={c.target_tpr}, target_threshold={target_threshold}, test_accuracy={test_accuracy}") if c.grad_map_viz: print("saving gradient maps...") export_gradient_maps(model, predict_loader, optimizer, -1) + + # visualize the prediction result if c.visualization: for i in range(len(predictions)): From 8d58892763a6a18176a850a498324d9779ef828a Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sat, 30 Jan 2021 19:53:38 -0700 Subject: [PATCH 53/65] updated model naming format --- config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.py b/config.py index 54eb19a..9628d81 100644 --- a/config.py +++ b/config.py @@ -13,8 +13,8 @@ # data settings dataset_path = "dataset" -class_name = "Experiment 3.1" -modelname = "Experiment 3.2_0_0.05_0.15_0.05_0.15" +class_name = "Experiment_3.1" +modelname = "Experiment_3.2_0_0.05_0.15_0.05_0.15" img_size = (448, 448) img_dims = [3] + list(img_size) From ba3856d85a4ceb1b48a6886254c523fffd8dd925 Mon Sep 17 00:00:00 2001 From: txrxrxr Date: Sun, 14 Feb 2021 11:41:44 -0700 Subject: [PATCH 54/65] Changed export gradient map to generate the map for both good and defect samples on the transformed images. --- localization.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/localization.py b/localization.py index 17e5778..1e91dab 100644 --- a/localization.py +++ b/localization.py @@ -35,7 +35,7 @@ def save_imgs(inputs, grad, cnt): def export_gradient_maps(model, testloader, optimizer, n_batches=1): plt.figure(figsize=(10, 10)) - testloader.dataset.get_fixed = True + testloader.dataset.get_fixed = False cnt = 0 degrees = -1 * np.arange(c.n_transforms_test) * 360.0 / c.n_transforms_test @@ -50,13 +50,13 @@ def export_gradient_maps(model, testloader, optimizer, n_batches=1): loss.backward() grad = inputs.grad.view(-1, c.n_transforms_test, *inputs.shape[-3:]) - grad = grad[labels > 0] + grad = grad[labels >= 0] if grad.shape[0] == 0: continue grad = t2np(grad) inputs = inputs.view(-1, c.n_transforms_test, *inputs.shape[-3:])[:, 0] - inputs = np.transpose(t2np(inputs[labels > 0]), [0, 2, 3, 1]) + inputs = np.transpose(t2np(inputs[labels >= 0]), [0, 2, 3, 1]) inputs_unnormed = np.clip(inputs * c.norm_std + c.norm_mean, 0, 1) for i_item in range(c.n_transforms_test): From 3e1351cc615cdc2439acd02c76cc8ef83fdd5401 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Mon, 15 Feb 2021 10:32:25 -0700 Subject: [PATCH 55/65] merge test and predict into one file --- predict.py | 21 -------------- test.py | 4 +-- train.py | 81 ++++++++---------------------------------------------- 3 files changed, 13 insertions(+), 93 deletions(-) delete mode 100644 predict.py diff --git a/predict.py b/predict.py deleted file mode 100644 index 7df4c43..0000000 --- a/predict.py +++ /dev/null @@ -1,21 +0,0 @@ -import config as c -from train import * -from utils import load_datasets, make_dataloaders -import time -import gc -import json - -_, _, predict_set = load_datasets(c.dataset_path, 'predict', test=True) -_, _, predict_loader = make_dataloaders(None, None, predict_set, test=True) - -model = torch.load("models/" + c.modelname + "", map_location=torch.device('cpu')) - -with open('models/' + c.modelname + '.json') as jsonfile: - model_parameters = json.load(jsonfile) - -time_start = time.time() -predict(model, model_parameters, predict_loader) -time_end = time.time() -time_c = time_end - time_start -print("predicting time cost: {:f} s".format(time_c)) - diff --git a/test.py b/test.py index 951be13..51b85ae 100644 --- a/test.py +++ b/test.py @@ -21,6 +21,6 @@ time_start = time.time() test(model, model_parameters, test_loader) time_end = time.time() -time_c = time_end - time_start # 运行所花时间 +time_c = time_end - time_start +print("testing time cost: {:f} s".format(time_c)) -print("test time cost: {:f} s".format(time_c)) \ No newline at end of file diff --git a/train.py b/train.py index 2a7404b..82ff8d4 100644 --- a/train.py +++ b/train.py @@ -131,65 +131,6 @@ def train(train_loader, validate_loader): def test(model, model_parameters, test_loader): print("Running test") - optimizer = torch.optim.Adam(model.nf.parameters(), lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, - weight_decay=1e-5) - # score_obs = Score_Observer('AUROC') - # evaluate - model.to(c.device) - model.eval() - # print(f"model={model}") - epoch = 0 - if c.verbose: - print('\nCompute loss and scores on test set:') - test_loss = list() - test_z = list() - test_labels = list() - with torch.no_grad(): - for i, data in enumerate(tqdm(test_loader, disable=c.hide_tqdm_bar)): - inputs, labels = preprocess_batch(data) - # inputs = Variable(inputs, requires_grad=True) - print(f"i={i}: labels={labels}, size of inputs={inputs.size()}") - # print(f"inputs={inputs}") - z = model(inputs) - # print(f"z={z}") - loss = get_loss(z, model.nf.jacobian(run_forward=False)) - test_z.append(z) - test_loss.append(t2np(loss)) - test_labels.append(t2np(labels)) - - test_loss = np.mean(np.array(test_loss)) - if c.verbose: - print('Epoch: {:d} \t test_loss: {:.4f}'.format(epoch, test_loss)) - - test_labels = np.concatenate(test_labels) - is_anomaly = np.array([0 if l == 0 else 1 for l in test_labels]) - - z_grouped = torch.cat(test_z, dim=0).view(-1, c.n_transforms_test, c.n_feat) - anomaly_score = t2np(torch.mean(z_grouped ** 2, dim=(-2, -1))) - # score_obs.update(roc_auc_score(is_anomaly, anomaly_score), epoch, - # print_score=c.verbose or epoch == c.meta_epochs - 1) - - # get the threshold for target true positive rate - for i in range(len(model_parameters['tpr'])): - if model_parameters['tpr'][i] > c.target_tpr: - target_threshold = model_parameters['thresholds'][i] - break - - is_anomaly_detected = np.array([0 if l < target_threshold else 1 for l in anomaly_score]) - - # calculate test accuracy - error_count = 0 - for i in range(len(is_anomaly)): - if is_anomaly[i] != is_anomaly_detected[i]: - error_count += 1 - - test_accuracy = 1 - float(error_count) / len(is_anomaly) - - print(f"test_labels={test_labels}, is_anomaly={is_anomaly},anomaly_score={anomaly_score},is_anomaly_detected={is_anomaly_detected}") - print(f"target_tpr={c.target_tpr}, target_threshold={target_threshold}, test_accuracy={test_accuracy}") - -def predict(model, model_parameters, predict_loader): - print("Predicting") optimizer = torch.optim.Adam(model.nf.parameters(), lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, weight_decay=1e-5) model.to(c.device) model.eval() @@ -199,13 +140,13 @@ def predict(model, model_parameters, predict_loader): test_labels = list() predictions = [] with torch.no_grad(): - for i, data in enumerate(predict_loader): + for i, data in enumerate(test_loader): inputs, labels = preprocess_batch(data) if c.frame_name_is_given: - frame = int(predict_loader.dataset.imgs[i][0].split('frame',1)[1].split('-')[0]) + frame = int(test_loader.dataset.imgs[i][0].split('frame',1)[1].split('-')[0]) frame = i #print(f"i={i}: frame#={frame}, labels={labels.cpu().numpy()[0]}, size of inputs={inputs.size()}") - predictions.append([frame, predict_loader.dataset.imgs[i][0], labels.cpu().numpy()[0], 0, 0]) + predictions.append([frame, test_loader.dataset.imgs[i][0], labels.cpu().numpy()[0], 0, 0]) z = model(inputs) test_z.append(z) test_labels.append(t2np(labels)) @@ -245,9 +186,9 @@ def predict(model, model_parameters, predict_loader): for i in range(len(predictions)): msg = 'frame: ' + str(i) + '. ' if (predictions[i][3] == 1): - msg += 'prediction: defective. ' + msg += 'testion: defective. ' else: - msg += 'prediction: good. ' + msg += 'testion: good. ' if (predictions[i][2] == 1): msg += 'ground truth: defective. ' @@ -264,33 +205,33 @@ def predict(model, model_parameters, predict_loader): print(f"target_tpr={c.target_tpr}, target_threshold={target_threshold}, test_accuracy={test_accuracy}") if c.grad_map_viz: print("saving gradient maps...") - export_gradient_maps(model, predict_loader, optimizer, -1) + export_gradient_maps(model, test_loader, optimizer, -1) - # visualize the prediction result + # visualize the testion result if c.visualization: for i in range(len(predictions)): # load file path file_path = predictions[i][1] idx = file_path.index('video') file_path = file_path[:idx] + 'original-' + file_path[idx:] - file_path = file_path.replace("predict\\test", "zerobox-2010-1-original") + file_path = file_path.replace("test\\test", "zerobox-2010-1-original") # rotate and resize image img = cv2.imread(file_path) img = cv2.rotate(img, cv2.cv2.ROTATE_90_COUNTERCLOCKWISE) img = cv2.resize(img, (600, 900)) - # display prediction on each frame + # display testion on each frame font = cv2.FONT_HERSHEY_DUPLEX font_size = 0.65 pos_x = 330 if (predictions[i][3] == 1): - img = cv2.putText(img, 'prediction: defective', (pos_x, 810), font, + img = cv2.putText(img, 'testion: defective', (pos_x, 810), font, font_size, (0, 0, 255), 1, cv2.LINE_AA) else: - img = cv2.putText(img, 'prediction: good', (pos_x, 810), font, + img = cv2.putText(img, 'testion: good', (pos_x, 810), font, font_size, (0, 255, 0), 1, cv2.LINE_AA) if (predictions[i][2] == 1): From 76f95363e44f0bf1ad0d0ec11dfc1fbe27956e81 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Mon, 15 Feb 2021 10:39:59 -0700 Subject: [PATCH 56/65] added mask calculation functions and apply calculated mask to input images function --- apply_mask.py | 22 ++++++++++++++++++++++ drawMask.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 apply_mask.py create mode 100644 drawMask.py diff --git a/apply_mask.py b/apply_mask.py new file mode 100644 index 0000000..9bff6bf --- /dev/null +++ b/apply_mask.py @@ -0,0 +1,22 @@ +import cv2 +import os + + +def load_images_from_folder(folder): + images = [] + for filename in os.listdir(folder): + img = cv2.imread(os.path.join(folder, filename)) + if img is not None: + images.append(img) + return images + + +path = 'dataset/Experiment_5.1/validate/defect' +mask = cv2.imread(os.path.join('dataset/Mask/', 'Mask-182.jpg')) +mask = mask / 255 +imgs = load_images_from_folder(path) + +for i, img in enumerate(imgs): + img = cv2.resize(img, (400, 700), interpolation=cv2.INTER_AREA) + masked_img = img * mask + cv2.imwrite('dataset/Experiment_5.5/validate/defect/defect-Masked-' + str(i) + '.jpg', masked_img) diff --git a/drawMask.py b/drawMask.py new file mode 100644 index 0000000..15176a9 --- /dev/null +++ b/drawMask.py @@ -0,0 +1,43 @@ +import cv2 +import os +import numpy as np + + +def load_images_from_folder(folder): + images = [] + for filename in os.listdir(folder): + image = cv2.imread(os.path.join(folder, filename)) + if image is not None: + images.append(image) + return images + + +imgs = load_images_from_folder('dataset/bgm/') + +for i, img in enumerate(imgs): + print('Original Dimensions : ', img.shape) + resized = cv2.resize(img, (400, 700), interpolation=cv2.INTER_AREA) + masked = [] + + for r in resized: + new_c = [] + for c in r: + # check if the pixel value is green mask or original image pixels + if True in (abs([120, 255, 155] - c) > [30, 30, 30]): + new_c.append([0, 0, 0]) + else: + # keep the green mask pixels in the img + new_c.append([1, 1, 1]) + masked.append(new_c) + masked = np.array(masked, dtype=np.uint8) + if i == 0: + mask = masked + else: + mask = np.ceil((mask + masked) / 2) + + # output the results of each iteration after mask addition process + cv2.imwrite('dataset/Mask/Mask-' + str(i) + '.jpg', 255 - (255 * mask)) + print('Resized Dimensions : ', resized.shape) + +# output final mask addition result +cv2.imwrite('dataset/Mask/Mask.jpg', 255 - (255 * mask)) From 2bff3dd9cf312f6adb0edd48e7e7d9cabe9cf9bb Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Mon, 15 Feb 2021 11:32:53 -0700 Subject: [PATCH 57/65] fixed typo --- train.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/train.py b/train.py index 82ff8d4..4eb634d 100644 --- a/train.py +++ b/train.py @@ -186,9 +186,9 @@ def test(model, model_parameters, test_loader): for i in range(len(predictions)): msg = 'frame: ' + str(i) + '. ' if (predictions[i][3] == 1): - msg += 'testion: defective. ' + msg += 'prediction: defective. ' else: - msg += 'testion: good. ' + msg += 'prediction: good. ' if (predictions[i][2] == 1): msg += 'ground truth: defective. ' @@ -209,7 +209,7 @@ def test(model, model_parameters, test_loader): - # visualize the testion result + # visualize the prediction result if c.visualization: for i in range(len(predictions)): # load file path @@ -223,15 +223,15 @@ def test(model, model_parameters, test_loader): img = cv2.rotate(img, cv2.cv2.ROTATE_90_COUNTERCLOCKWISE) img = cv2.resize(img, (600, 900)) - # display testion on each frame + # display prediction on each frame font = cv2.FONT_HERSHEY_DUPLEX font_size = 0.65 pos_x = 330 if (predictions[i][3] == 1): - img = cv2.putText(img, 'testion: defective', (pos_x, 810), font, + img = cv2.putText(img, 'prediction: defective', (pos_x, 810), font, font_size, (0, 0, 255), 1, cv2.LINE_AA) else: - img = cv2.putText(img, 'testion: good', (pos_x, 810), font, + img = cv2.putText(img, 'prediction: good', (pos_x, 810), font, font_size, (0, 255, 0), 1, cv2.LINE_AA) if (predictions[i][2] == 1): From 9aae1db53ba0991416e73cf6e40acbb0d14cb1da Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Mon, 15 Feb 2021 12:33:15 -0700 Subject: [PATCH 58/65] code clean up --- model.py | 63 -------------------------------------------------------- train.py | 12 +++-------- 2 files changed, 3 insertions(+), 72 deletions(-) diff --git a/model.py b/model.py index eff26ae..dc06eaa 100644 --- a/model.py +++ b/model.py @@ -1,10 +1,8 @@ -import numpy as np import os import torch import torch.nn.functional as F from torch import nn from torchvision.models import alexnet -from torch.autograd import Variable import config as c from freia_funcs import permute_layer, glow_coupling_layer, F_fully_connected, ReversibleGraphNet, OutputNode, \ @@ -13,77 +11,16 @@ from datetime import datetime import matplotlib.pyplot as plt import json -import cv2 WEIGHT_DIR = './weights' MODEL_DIR = './models' -# copy from https://github.com/pytorch/examples/blob/master/vae/main.py -class VAE(nn.Module): - def __init__(self): - super(VAE, self).__init__() - - self.fc1 = nn.Linear(784, 400) - self.fc21 = nn.Linear(400, 20) - self.fc22 = nn.Linear(400, 20) - self.fc3 = nn.Linear(20, 400) - self.fc4 = nn.Linear(400, 784) - - def encode(self, x): - h1 = F.relu(self.fc1(x)) - return self.fc21(h1), self.fc22(h1) - - def reparameterize(self, mu, logvar): - std = torch.exp(0.5*logvar) - eps = torch.randn_like(std) - return mu + eps*std - - def decode(self, z): - h3 = F.relu(self.fc3(z)) - return torch.sigmoid(self.fc4(h3)) - - def forward(self, x): - mu, logvar = self.encode(x.view(-1, 784)) - z = self.reparameterize(mu, logvar) - return self.decode(z), mu, logvar - -# todo: VAE + CNN to generate mask, we need to train the CNN's parameter - -class MaskDifferNet(nn.Module): - def __init__(self): - super(MaskDifferNet, self).__init__() - self.differnet = DifferNet() - self.nf = self.differnet.nf - self.vae = VAE() - - def forward(self, x): - y = self.vae(x) - - mask = torch.relu(torch.sign(torch.sigmoid(y[0]) - 0.5)) - y_img = torch.squeeze(y[0].view(x.shape)).permute(2, 1, 0).cpu().detach().numpy() - cv2.imshow('VAE output', y_img) - cv2.waitKey(1) - - mask = mask.view(x.shape) - x_img = torch.squeeze(x).permute(2, 1, 0).cpu().detach().numpy() - cv2.imshow('original input', x_img) - cv2.waitKey(1) - - z = x * mask.int().float() - z_img = torch.squeeze(z).permute(2, 1, 0).cpu().detach().numpy() - cv2.imshow('original + mask', z_img) - cv2.waitKey(1) - - output = self.differnet(y[0].view(x.shape)) - - return output def nf_head(input_dim=c.n_feat): nodes = list() nodes.append(InputNode(input_dim, name='input')) for k in range(c.n_coupling_blocks): nodes.append(Node([nodes[-1].out0], permute_layer, {'seed': k}, name=F'permute_{k}')) - # nodes.append(Node([nodes[-1].out0], LUInvertibleMM, {'seed': k}, name=F'permute_{k}')) nodes.append(Node([nodes[-1].out0], glow_coupling_layer, {'clamp': c.clamp_alpha, 'F_class': F_fully_connected, 'F_args': {'internal_size': c.fc_internal, 'dropout': c.dropout}}, diff --git a/train.py b/train.py index 4eb634d..1a045fb 100644 --- a/train.py +++ b/train.py @@ -1,12 +1,8 @@ -import numpy as np -import torch from sklearn.metrics import roc_auc_score from sklearn.metrics import roc_curve from tqdm import tqdm - -import config as c from localization import export_gradient_maps -from model import DifferNet, save_model, save_weights, save_parameters, save_roc_plot, MaskDifferNet +from model import DifferNet, save_model, save_weights, save_parameters, save_roc_plot from utils import * from operator import itemgetter import cv2 @@ -35,10 +31,8 @@ def print_score(self): def train(train_loader, validate_loader): model = DifferNet() - optimizer = torch.optim.Adam([{'params': model.nf.parameters()} - - - ], lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, weight_decay=1e-5) + optimizer = torch.optim.Adam([{'params': model.nf.parameters()}], lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, + weight_decay=1e-5) model.to(c.device) save_name_pre = '{}_{}_{:.2f}_{:.2f}_{:.2f}_{:.2f}'.format(c.modelname, c.rotation_degree, From 745b0a449cb14bad0ea4d79c0addcafbe7b6c983 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sat, 20 Feb 2021 17:06:23 -0700 Subject: [PATCH 59/65] output json file name fix; model.pth load file name fix --- test.py | 2 +- train.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/test.py b/test.py index 51b85ae..51f9cec 100644 --- a/test.py +++ b/test.py @@ -13,7 +13,7 @@ _, _, test_set = load_datasets(c.dataset_path, c.class_name, test=True) _, _, test_loader = make_dataloaders(None, None, test_set, test=True) -model = torch.load("models/" + c.modelname + "", map_location=torch.device('cpu')) +model = torch.load('models/' + c.modelname + '.pth', map_location=torch.device('cpu')) with open('models/' + c.modelname + '.json') as jsonfile: model_parameters = json.load(jsonfile) diff --git a/train.py b/train.py index 1a045fb..1b4e897 100644 --- a/train.py +++ b/train.py @@ -99,8 +99,9 @@ def train(train_loader, validate_loader): model_parameters['thresholds'] = thresholds.tolist() model_parameters['AUROC'] = AUROC - save_parameters(model_parameters, save_name_pre + "_{:.4f}".format(AUROC)) - save_roc_plot(fpr, tpr, save_name_pre + "_{:.4f}".format(AUROC)) + if epoch == c.meta_epochs - 1: + save_parameters(model_parameters, c.modelname) + save_roc_plot(fpr, tpr, c.modelname + "_{:.4f}".format(AUROC)) if c.verbose: print('Epoch: {:d} \t validate_loss: {:.4f}'.format(epoch, test_loss)) @@ -118,8 +119,8 @@ def train(train_loader, validate_loader): if c.save_model: model.to('cpu') - save_model(model, save_name_pre + '.pth') - save_weights(model, save_name_pre + '.weights.pth') + save_model(model, c.modelname + '.pth') + save_weights(model, c.modelname + '.weights.pth') return model, model_parameters From 983ae9f8d19af94ff23e81004ac3a34105530c09 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sun, 21 Feb 2021 13:39:53 -0700 Subject: [PATCH 60/65] moved test code out of the train.py (into the new test.py); renamed main to run_traning.py --- config.py | 22 ++++---- runTest.py | 114 -------------------------------------- test.py => run_test.py | 9 ++- main.py => run_traning.py | 3 +- train.py | 1 - 5 files changed, 19 insertions(+), 130 deletions(-) delete mode 100644 runTest.py rename test.py => run_test.py (89%) rename main.py => run_traning.py (92%) diff --git a/config.py b/config.py index 9628d81..7e98bf7 100644 --- a/config.py +++ b/config.py @@ -13,8 +13,8 @@ # data settings dataset_path = "dataset" -class_name = "Experiment_3.1" -modelname = "Experiment_3.2_0_0.05_0.15_0.05_0.15" +class_name = "Experiment_3.2" +modelname = "Experiment_3.2_10epoch_239tainingdata_0.5BCS" img_size = (448, 448) img_dims = [3] + list(img_size) @@ -22,16 +22,16 @@ # transformation settings transf_rotations = True -transf_brightness = 0.0 -transf_contrast = 0.0 -transf_saturation = 0.0 +transf_brightness = 0.5 +transf_contrast = 0.5 +transf_saturation = 0.5 norm_mean, norm_std = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225] rotation_degree = 0 -crop_top = 0.05 -crop_left = 0.15 -crop_bottom = 0.05 -crop_right = 0.15 +crop_top = 0.10 +crop_left = 0.10 +crop_bottom = 0.10 +crop_right = 0.10 # network hyperparameters n_scales = 3 # number of scales at which features are extracted, img_size is the highest - others are //2, //4,... @@ -51,12 +51,12 @@ # total epochs = meta_epochs * sub_epochs # evaluation after epochs -meta_epochs = 3 +meta_epochs = 10 sub_epochs = 8 # output settings verbose = True -grad_map_viz = False +grad_map_viz = True hide_tqdm_bar = True save_model = True save_transformed_image = True diff --git a/runTest.py b/runTest.py deleted file mode 100644 index 6ad53ed..0000000 --- a/runTest.py +++ /dev/null @@ -1,114 +0,0 @@ -'''This is the repo which contains the original code to the WACV 2021 paper -"Same Same But DifferNet: Semi-Supervised Defect Detection with Normalizing Flows" -by Marco Rudolph, Bastian Wandt and Bodo Rosenhahn. -For further information contact Marco Rudolph (rudolph@tnt.uni-hannover.de)''' - -import config as c -from train import train -from utils import load_datasets, make_dataloaders -from model import load_model, load_weights -import numpy as np -import torch -from train import Score_Observer -from sklearn.metrics import roc_auc_score -from tqdm import tqdm -import time -from utils import * -from localization import export_gradient_maps -from torch.autograd import Variable - -def test(model, test_loader): - print("Running test") - optimizer = torch.optim.Adam(model.nf.parameters(), lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, weight_decay=1e-5) - # score_obs = Score_Observer('AUROC') - # evaluate - model.to(c.device) - model.eval() - # print(f"model={model}") - epoch = 0 - if c.verbose: - print('\nCompute loss and scores on test set:') - test_loss = list() - test_z = list() - test_labels = list() - #with torch.no_grad(): - for i, data in enumerate(tqdm(test_loader, disable=c.hide_tqdm_bar)): - inputs, labels = preprocess_batch(data) - #inputs = Variable(inputs, requires_grad=True) - print(f"i={i}: labels={labels}, size of inputs={inputs.size()}") - # print(f"inputs={inputs}") - z = model(inputs) - # print(f"z={z}") - loss = get_loss(z, model.nf.jacobian(run_forward=False)) - test_z.append(z) - test_loss.append(t2np(loss)) - test_labels.append(t2np(labels)) - - test_loss = np.mean(np.array(test_loss)) - if c.verbose: - print('Epoch: {:d} \t test_loss: {:.4f}'.format(epoch, test_loss)) - - test_labels = np.concatenate(test_labels) - is_anomaly = np.array([0 if l == 0 else 1 for l in test_labels]) - - z_grouped = torch.cat(test_z, dim=0).view(-1, c.n_transforms_test, c.n_feat) - anomaly_score = t2np(torch.mean(z_grouped ** 2, dim=(-2, -1))) - print(f"test_labels={test_labels}, is_anomaly={is_anomaly},anomaly_score={anomaly_score}") - # score_obs.update(roc_auc_score(is_anomaly, anomaly_score), epoch, - # print_score=c.verbose or epoch == c.meta_epochs - 1) - - if c.grad_map_viz: - print("saving gradient maps...") - export_gradient_maps(model, test_loader, optimizer, -1) - -def load_testloader(data_dir_test): - def target_transform(target): - return class_perm[target] - - classes = os.listdir(data_dir_test) - if 'good' not in classes: - print('There should exist a subdirectory "good". Read the doc of this function for further information.') - exit() - classes.sort() - class_perm = list() - class_idx = 1 - for cl in classes: - if cl == 'good': - class_perm.append(0) - else: - class_perm.append(class_idx) - class_idx += 1 - - augmentative_transforms = [] - # if c.transf_rotations: - # augmentative_transforms += [transforms.RandomRotation(180)] - if c.transf_brightness > 0.0 or c.transf_contrast > 0.0 or c.transf_saturation > 0.0: - augmentative_transforms += [transforms.ColorJitter(brightness=c.transf_brightness, contrast=c.transf_contrast, - saturation=c.transf_saturation)] - - tfs = [transforms.Resize(c.img_size)] + augmentative_transforms + [transforms.ToTensor(), - transforms.Normalize(c.norm_mean, c.norm_std)] - - transform_train = transforms.Compose(tfs) - testset = ImageFolderMultiTransform(data_dir_test, transform=transform_train, target_transform=target_transform, - n_transforms=c.n_transforms_test) - testloader = torch.utils.data.DataLoader(testset, pin_memory=True, batch_size=c.batch_size_test, shuffle=True, - drop_last=False) - return testloader - -########################## Main #################### -# train_set, test_set = load_datasets(c.dataset_path, c.class_name) -# _, test_loader = make_dataloaders(train_set, test_set) - -test_loader = load_testloader("group15B.avi/") -# model = torch.load("../zerobox-v2/zerobox_differnet_model.pt", map_location=torch.device('cpu')) -model = torch.load("models/zerobox_test.pt", map_location=torch.device('cpu')) - -print("starting to run tests after loaded model and test dataset") -time_start = time.time() -# model = load_model(c.modelname) -test(model, test_loader) -time_end = time.time() -time_c = time_end - time_start # 运行所花时间 -print("time cost: {:f} s".format(time_c)) - diff --git a/test.py b/run_test.py similarity index 89% rename from test.py rename to run_test.py index 51f9cec..5048ae7 100644 --- a/test.py +++ b/run_test.py @@ -3,8 +3,7 @@ by Marco Rudolph, Bastian Wandt and Bodo Rosenhahn. For further information contact Marco Rudolph (rudolph@tnt.uni-hannover.de)''' -import config as c -from train import * +from test import * from utils import load_datasets, make_dataloaders import time import gc @@ -24,3 +23,9 @@ time_c = time_end - time_start print("testing time cost: {:f} s".format(time_c)) +# free memory +del test_set +del test_loader + +gc.collect() +torch.cuda.empty_cache() diff --git a/main.py b/run_traning.py similarity index 92% rename from main.py rename to run_traning.py index d106d0d..91e06a3 100644 --- a/main.py +++ b/run_traning.py @@ -3,7 +3,6 @@ by Marco Rudolph, Bastian Wandt and Bodo Rosenhahn. For further information contact Marco Rudolph (rudolph@tnt.uni-hannover.de)''' -import config as c from train import * from utils import load_datasets, make_dataloaders import time @@ -14,7 +13,7 @@ time_start = time.time() model, model_parameters = train(train_loader, validate_loader) -#model, model_config = train(train_loader, None) + time_end = time.time() time_c = time_end - time_start # 运行所花时间 print("train time cost: {:f} s".format(time_c)) diff --git a/train.py b/train.py index 1b4e897..1f00bc1 100644 --- a/train.py +++ b/train.py @@ -28,7 +28,6 @@ def print_score(self): print('{:s}: \t last: {:.4f} \t max: {:.4f} \t epoch_max: {:d}'.format(self.name, self.last, self.max_score, self.max_epoch)) - def train(train_loader, validate_loader): model = DifferNet() optimizer = torch.optim.Adam([{'params': model.nf.parameters()}], lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, From e381c687af650e8f4b8143613a21e16262e419a5 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sun, 21 Feb 2021 22:26:04 -0700 Subject: [PATCH 61/65] Add todo list. --- apply_mask.py | 4 +- drawMask.py | 1 + test.py | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++ train.py | 2 +- 4 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 test.py diff --git a/apply_mask.py b/apply_mask.py index 9bff6bf..b40f41a 100644 --- a/apply_mask.py +++ b/apply_mask.py @@ -11,7 +11,7 @@ def load_images_from_folder(folder): return images -path = 'dataset/Experiment_5.1/validate/defect' +path = 'dataset/Experiment_5.1/test/defect' mask = cv2.imread(os.path.join('dataset/Mask/', 'Mask-182.jpg')) mask = mask / 255 imgs = load_images_from_folder(path) @@ -19,4 +19,4 @@ def load_images_from_folder(folder): for i, img in enumerate(imgs): img = cv2.resize(img, (400, 700), interpolation=cv2.INTER_AREA) masked_img = img * mask - cv2.imwrite('dataset/Experiment_5.5/validate/defect/defect-Masked-' + str(i) + '.jpg', masked_img) + cv2.imwrite('dataset/Experiment_5.5/test/defect/defect-Masked-' + str(i) + '.jpg', masked_img) diff --git a/drawMask.py b/drawMask.py index 15176a9..b34a9a1 100644 --- a/drawMask.py +++ b/drawMask.py @@ -27,6 +27,7 @@ def load_images_from_folder(folder): new_c.append([0, 0, 0]) else: # keep the green mask pixels in the img + # todo: increase mask area. new_c.append([1, 1, 1]) masked.append(new_c) masked = np.array(masked, dtype=np.uint8) diff --git a/test.py b/test.py new file mode 100644 index 0000000..e837741 --- /dev/null +++ b/test.py @@ -0,0 +1,131 @@ +from sklearn.metrics import roc_auc_score +from sklearn.metrics import roc_curve +from tqdm import tqdm +from localization import export_gradient_maps +from model import DifferNet, save_model, save_weights, save_parameters, save_roc_plot +from utils import * +from operator import itemgetter +import cv2 + +def test(model, model_parameters, test_loader): + print("Running test") + optimizer = torch.optim.Adam(model.nf.parameters(), lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, weight_decay=1e-5) + model.to(c.device) + model.eval() + if c.verbose: + print('\nCompute loss and scores on test set:') + test_z = list() + test_labels = list() + predictions = [] + with torch.no_grad(): + for i, data in enumerate(test_loader): + inputs, labels = preprocess_batch(data) + if c.frame_name_is_given: + frame = int(test_loader.dataset.imgs[i][0].split('frame',1)[1].split('-')[0]) + frame = i + #print(f"i={i}: frame#={frame}, labels={labels.cpu().numpy()[0]}, size of inputs={inputs.size()}") + predictions.append([frame, test_loader.dataset.imgs[i][0], labels.cpu().numpy()[0], 0, 0]) + z = model(inputs) + test_z.append(z) + test_labels.append(t2np(labels)) + + test_labels = np.concatenate(test_labels) + is_anomaly = np.array([0 if l == 0 else 1 for l in test_labels]) + + z_grouped = torch.cat(test_z, dim=0).view(-1, c.n_transforms_test, c.n_feat) + anomaly_score = t2np(torch.mean(z_grouped ** 2, dim=(-2, -1))) + + for i in range(len(model_parameters['tpr'])): + if model_parameters['tpr'][i] > c.target_tpr: + target_threshold = model_parameters['thresholds'][i] + break + + is_anomaly_detected = [] + i = 0 + for l in anomaly_score: + predictions[i][4] = l + if l < target_threshold: + is_anomaly_detected.append(0) + predictions[i][3] = 0 + else: + is_anomaly_detected.append(1) + predictions[i][3] = 1 + i += 1 + predictions = sorted(predictions, key=itemgetter(0)) + + # calculate test accuracy + error_count = 0 + for i in range(len(is_anomaly)): + if is_anomaly[i] != is_anomaly_detected[i]: + error_count += 1 + + test_accuracy = 1 - float(error_count) / len(is_anomaly) + + for i in range(len(predictions)): + msg = 'frame: ' + str(i) + '. ' + if (predictions[i][3] == 1): + msg += 'prediction: defective. ' + else: + msg += 'prediction: good. ' + + if (predictions[i][2] == 1): + msg += 'ground truth: defective. ' + else: + msg += 'ground truth: good. ' + + msg += 'anomaly score: ' + str(round(predictions[i][4], 4)) + '. ' + msg += 'threshold: ' + str(round(target_threshold, 4)) + '. ' + msg += 'accuracy: ' + str(round(test_accuracy * 100, 2)) + '%' + + print(msg) + + # print(f"test_labels={test_labels}, is_anomaly={is_anomaly},anomaly_score={anomaly_score},is_anomaly_detected={is_anomaly_detected}") + print(f"target_tpr={c.target_tpr}, target_threshold={target_threshold}, test_accuracy={test_accuracy}") + if c.grad_map_viz: + print("saving gradient maps...") + export_gradient_maps(model, test_loader, optimizer, -1) + + # todo: add ROC curve here. + + + # visualize the prediction result + if c.visualization: + for i in range(len(predictions)): + # load file path + file_path = predictions[i][1] + idx = file_path.index('video') + file_path = file_path[:idx] + 'original-' + file_path[idx:] + file_path = file_path.replace("test\\test", "zerobox-2010-1-original") + + # rotate and resize image + img = cv2.imread(file_path) + img = cv2.rotate(img, cv2.cv2.ROTATE_90_COUNTERCLOCKWISE) + img = cv2.resize(img, (600, 900)) + + # display prediction on each frame + font = cv2.FONT_HERSHEY_DUPLEX + font_size = 0.65 + pos_x = 330 + if (predictions[i][3] == 1): + img = cv2.putText(img, 'prediction: defective', (pos_x, 810), font, + font_size, (0, 0, 255), 1, cv2.LINE_AA) + else: + img = cv2.putText(img, 'prediction: good', (pos_x, 810), font, + font_size, (0, 255, 0), 1, cv2.LINE_AA) + + if (predictions[i][2] == 1): + img = cv2.putText(img, 'ground truth: defective', (pos_x, 830), font, + font_size, (0, 0, 255), 1, cv2.LINE_AA) + else: + img = cv2.putText(img, 'ground truth: good', (pos_x, 830), font, + font_size, (0, 255, 0), 1, cv2.LINE_AA) + + img = cv2.putText(img, 'anomaly score: ' + str(round(predictions[i][4], 4)), (pos_x, 850), font, + font_size, (0, 255, 0), 1, cv2.LINE_AA) + img = cv2.putText(img, 'threshold: ' + str(round(target_threshold, 4)), (pos_x, 870), font, + font_size, (0, 255, 0), 1, cv2.LINE_AA) + img = cv2.putText(img, 'accuracy: ' + str(round(test_accuracy * 100, 2)) + '%', (pos_x, 890), font, + font_size, (0, 255, 0), 1, cv2.LINE_AA) + # show results + cv2.imshow('window', img) + cv2.waitKey(220) \ No newline at end of file diff --git a/train.py b/train.py index 1f00bc1..78bcc78 100644 --- a/train.py +++ b/train.py @@ -36,7 +36,7 @@ def train(train_loader, validate_loader): save_name_pre = '{}_{}_{:.2f}_{:.2f}_{:.2f}_{:.2f}'.format(c.modelname, c.rotation_degree, c.crop_top, c.crop_left, c.crop_bottom, c.crop_right) - + # todo: training rate score_obs = Score_Observer('AUROC') for epoch in range(c.meta_epochs): From aa7ad9dcc2e8532abe22509a208d7447a2a5d148 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sun, 28 Feb 2021 15:49:17 -0700 Subject: [PATCH 62/65] Added ROC curve in Test. --- test.py | 9 ++-- train.py | 129 +------------------------------------------------------ 2 files changed, 5 insertions(+), 133 deletions(-) diff --git a/test.py b/test.py index e837741..628c1b0 100644 --- a/test.py +++ b/test.py @@ -1,8 +1,7 @@ from sklearn.metrics import roc_auc_score from sklearn.metrics import roc_curve -from tqdm import tqdm from localization import export_gradient_maps -from model import DifferNet, save_model, save_weights, save_parameters, save_roc_plot +from model import save_roc_plot from utils import * from operator import itemgetter import cv2 @@ -34,6 +33,9 @@ def test(model, model_parameters, test_loader): z_grouped = torch.cat(test_z, dim=0).view(-1, c.n_transforms_test, c.n_feat) anomaly_score = t2np(torch.mean(z_grouped ** 2, dim=(-2, -1))) + AUROC = roc_auc_score(is_anomaly, anomaly_score) + fpr, tpr, thresholds = roc_curve(is_anomaly, anomaly_score) + save_roc_plot(fpr, tpr, c.modelname + "_{:.4f}_test".format(AUROC)) for i in range(len(model_parameters['tpr'])): if model_parameters['tpr'][i] > c.target_tpr: @@ -85,9 +87,6 @@ def test(model, model_parameters, test_loader): print("saving gradient maps...") export_gradient_maps(model, test_loader, optimizer, -1) - # todo: add ROC curve here. - - # visualize the prediction result if c.visualization: for i in range(len(predictions)): diff --git a/train.py b/train.py index 78bcc78..689763a 100644 --- a/train.py +++ b/train.py @@ -4,8 +4,6 @@ from localization import export_gradient_maps from model import DifferNet, save_model, save_weights, save_parameters, save_roc_plot from utils import * -from operator import itemgetter -import cv2 class Score_Observer: '''Keeps an eye on the current and highest score so far''' @@ -36,7 +34,7 @@ def train(train_loader, validate_loader): save_name_pre = '{}_{}_{:.2f}_{:.2f}_{:.2f}_{:.2f}'.format(c.modelname, c.rotation_degree, c.crop_top, c.crop_left, c.crop_bottom, c.crop_right) - # todo: training rate + # todo: learning rate score_obs = Score_Observer('AUROC') for epoch in range(c.meta_epochs): @@ -50,9 +48,6 @@ def train(train_loader, validate_loader): for i, data in enumerate(tqdm(train_loader, disable=c.hide_tqdm_bar)): optimizer.zero_grad() inputs, labels = preprocess_batch(data) # move to device and reshape - # TODO inspect - # inputs += torch.randn(*inputs.shape).cuda() * c.add_img_noise - z = model(inputs) loss = get_loss(z, model.nf.jacobian(run_forward=False)) train_loss.append(t2np(loss)) @@ -122,125 +117,3 @@ def train(train_loader, validate_loader): save_weights(model, c.modelname + '.weights.pth') return model, model_parameters - -def test(model, model_parameters, test_loader): - print("Running test") - optimizer = torch.optim.Adam(model.nf.parameters(), lr=c.lr_init, betas=(0.8, 0.8), eps=1e-04, weight_decay=1e-5) - model.to(c.device) - model.eval() - if c.verbose: - print('\nCompute loss and scores on test set:') - test_z = list() - test_labels = list() - predictions = [] - with torch.no_grad(): - for i, data in enumerate(test_loader): - inputs, labels = preprocess_batch(data) - if c.frame_name_is_given: - frame = int(test_loader.dataset.imgs[i][0].split('frame',1)[1].split('-')[0]) - frame = i - #print(f"i={i}: frame#={frame}, labels={labels.cpu().numpy()[0]}, size of inputs={inputs.size()}") - predictions.append([frame, test_loader.dataset.imgs[i][0], labels.cpu().numpy()[0], 0, 0]) - z = model(inputs) - test_z.append(z) - test_labels.append(t2np(labels)) - - test_labels = np.concatenate(test_labels) - is_anomaly = np.array([0 if l == 0 else 1 for l in test_labels]) - - z_grouped = torch.cat(test_z, dim=0).view(-1, c.n_transforms_test, c.n_feat) - anomaly_score = t2np(torch.mean(z_grouped ** 2, dim=(-2, -1))) - - for i in range(len(model_parameters['tpr'])): - if model_parameters['tpr'][i] > c.target_tpr: - target_threshold = model_parameters['thresholds'][i] - break - - is_anomaly_detected = [] - i = 0 - for l in anomaly_score: - predictions[i][4] = l - if l < target_threshold: - is_anomaly_detected.append(0) - predictions[i][3] = 0 - else: - is_anomaly_detected.append(1) - predictions[i][3] = 1 - i += 1 - predictions = sorted(predictions, key=itemgetter(0)) - - # calculate test accuracy - error_count = 0 - for i in range(len(is_anomaly)): - if is_anomaly[i] != is_anomaly_detected[i]: - error_count += 1 - - test_accuracy = 1 - float(error_count) / len(is_anomaly) - - for i in range(len(predictions)): - msg = 'frame: ' + str(i) + '. ' - if (predictions[i][3] == 1): - msg += 'prediction: defective. ' - else: - msg += 'prediction: good. ' - - if (predictions[i][2] == 1): - msg += 'ground truth: defective. ' - else: - msg += 'ground truth: good. ' - - msg += 'anomaly score: ' + str(round(predictions[i][4], 4)) + '. ' - msg += 'threshold: ' + str(round(target_threshold, 4)) + '. ' - msg += 'accuracy: ' + str(round(test_accuracy * 100, 2)) + '%' - - print(msg) - - # print(f"test_labels={test_labels}, is_anomaly={is_anomaly},anomaly_score={anomaly_score},is_anomaly_detected={is_anomaly_detected}") - print(f"target_tpr={c.target_tpr}, target_threshold={target_threshold}, test_accuracy={test_accuracy}") - if c.grad_map_viz: - print("saving gradient maps...") - export_gradient_maps(model, test_loader, optimizer, -1) - - - - # visualize the prediction result - if c.visualization: - for i in range(len(predictions)): - # load file path - file_path = predictions[i][1] - idx = file_path.index('video') - file_path = file_path[:idx] + 'original-' + file_path[idx:] - file_path = file_path.replace("test\\test", "zerobox-2010-1-original") - - # rotate and resize image - img = cv2.imread(file_path) - img = cv2.rotate(img, cv2.cv2.ROTATE_90_COUNTERCLOCKWISE) - img = cv2.resize(img, (600, 900)) - - # display prediction on each frame - font = cv2.FONT_HERSHEY_DUPLEX - font_size = 0.65 - pos_x = 330 - if (predictions[i][3] == 1): - img = cv2.putText(img, 'prediction: defective', (pos_x, 810), font, - font_size, (0, 0, 255), 1, cv2.LINE_AA) - else: - img = cv2.putText(img, 'prediction: good', (pos_x, 810), font, - font_size, (0, 255, 0), 1, cv2.LINE_AA) - - if (predictions[i][2] == 1): - img = cv2.putText(img, 'ground truth: defective', (pos_x, 830), font, - font_size, (0, 0, 255), 1, cv2.LINE_AA) - else: - img = cv2.putText(img, 'ground truth: good', (pos_x, 830), font, - font_size, (0, 255, 0), 1, cv2.LINE_AA) - - img = cv2.putText(img, 'anomaly score: ' + str(round(predictions[i][4], 4)), (pos_x, 850), font, - font_size, (0, 255, 0), 1, cv2.LINE_AA) - img = cv2.putText(img, 'threshold: ' + str(round(target_threshold, 4)), (pos_x, 870), font, - font_size, (0, 255, 0), 1, cv2.LINE_AA) - img = cv2.putText(img, 'accuracy: ' + str(round(test_accuracy * 100, 2)) + '%', (pos_x, 890), font, - font_size, (0, 255, 0), 1, cv2.LINE_AA) - # show results - cv2.imshow('window', img) - cv2.waitKey(220) From d52dfb93a7722d32a39e2e027a75c120804c6af6 Mon Sep 17 00:00:00 2001 From: kuangzijian Date: Sun, 21 Mar 2021 12:47:48 -0600 Subject: [PATCH 63/65] Shrink Mask --- apply_mask.py | 8 ++++---- drawMask.py | 26 ++++++++++++++++++++++---- run_test.py | 5 ++++- test.py | 1 + 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/apply_mask.py b/apply_mask.py index b40f41a..8a0362d 100644 --- a/apply_mask.py +++ b/apply_mask.py @@ -11,12 +11,12 @@ def load_images_from_folder(folder): return images -path = 'dataset/Experiment_5.1/test/defect' -mask = cv2.imread(os.path.join('dataset/Mask/', 'Mask-182.jpg')) -mask = mask / 255 +path = 'dataset/Experiment_4.1/validate/good' +mask = cv2.imread(os.path.join('dataset/Mask/', 'Mask_shrink.jpg')) +mask = mask / 255 # make the mask into 0/1 matrix for multiplication imgs = load_images_from_folder(path) for i, img in enumerate(imgs): img = cv2.resize(img, (400, 700), interpolation=cv2.INTER_AREA) masked_img = img * mask - cv2.imwrite('dataset/Experiment_5.5/test/defect/defect-Masked-' + str(i) + '.jpg', masked_img) + cv2.imwrite('dataset/Experiment_5.1/validate/good/good-Masked-' + str(i) + '.jpg', masked_img) diff --git a/drawMask.py b/drawMask.py index b34a9a1..fe1f253 100644 --- a/drawMask.py +++ b/drawMask.py @@ -13,10 +13,12 @@ def load_images_from_folder(folder): imgs = load_images_from_folder('dataset/bgm/') +output_height = 700 +output_width = 400 for i, img in enumerate(imgs): print('Original Dimensions : ', img.shape) - resized = cv2.resize(img, (400, 700), interpolation=cv2.INTER_AREA) + resized = cv2.resize(img, (output_width, output_height), interpolation=cv2.INTER_AREA) masked = [] for r in resized: @@ -27,7 +29,6 @@ def load_images_from_folder(folder): new_c.append([0, 0, 0]) else: # keep the green mask pixels in the img - # todo: increase mask area. new_c.append([1, 1, 1]) masked.append(new_c) masked = np.array(masked, dtype=np.uint8) @@ -37,8 +38,25 @@ def load_images_from_folder(folder): mask = np.ceil((mask + masked) / 2) # output the results of each iteration after mask addition process - cv2.imwrite('dataset/Mask/Mask-' + str(i) + '.jpg', 255 - (255 * mask)) - print('Resized Dimensions : ', resized.shape) + #cv2.imwrite('dataset/Mask/Mask-' + str(i) + '.jpg', 255 - (255 * mask)) + #print('Resized Dimensions : ', resized.shape) # output final mask addition result cv2.imwrite('dataset/Mask/Mask.jpg', 255 - (255 * mask)) + +# shrink the mask area +shrink_percentage = 0.1 # shrink the mask by percentage from 4 orientations (top, bottom, left and right) +shrank_height = output_height * (1 - 2 * shrink_percentage) # shrink top and bottom +shrank_width = output_width * (1 - 2 * shrink_percentage) # shrink left and right +shrank_mask = cv2.resize(mask, (int(shrank_width), int(shrank_height)), interpolation=cv2.INTER_AREA) +# extend the border of the shrank_mask +shrank_mask = cv2.copyMakeBorder( + shrank_mask, + top=int(output_height * shrink_percentage), + bottom=int(output_height * shrink_percentage), + left=int(output_width * shrink_percentage), + right=int(output_width * shrink_percentage), + borderType=cv2.BORDER_CONSTANT, + value=[255, 255, 255] +) +cv2.imwrite('dataset/Mask/Mask_shrink.jpg', 255 - (255 * shrank_mask)) diff --git a/run_test.py b/run_test.py index 5048ae7..f733db1 100644 --- a/run_test.py +++ b/run_test.py @@ -9,9 +9,12 @@ import gc import json +c.transf_brightness = 0.0 +c.transf_contrast = 0.0 +c.transf_saturation = 0.0 + _, _, test_set = load_datasets(c.dataset_path, c.class_name, test=True) _, _, test_loader = make_dataloaders(None, None, test_set, test=True) - model = torch.load('models/' + c.modelname + '.pth', map_location=torch.device('cpu')) with open('models/' + c.modelname + '.json') as jsonfile: diff --git a/test.py b/test.py index 628c1b0..0964ec9 100644 --- a/test.py +++ b/test.py @@ -62,6 +62,7 @@ def test(model, model_parameters, test_loader): error_count += 1 test_accuracy = 1 - float(error_count) / len(is_anomaly) + #todo: tpr/fpr display. for i in range(len(predictions)): msg = 'frame: ' + str(i) + '. ' From 6d649126912aeec52bfc0e22f672b3063a9e0476 Mon Sep 17 00:00:00 2001 From: ChengguiSun Date: Thu, 29 Apr 2021 16:09:50 -0600 Subject: [PATCH 64/65] change datafolder to experiment6.1 --- config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.py b/config.py index 7e98bf7..7dc5654 100644 --- a/config.py +++ b/config.py @@ -13,8 +13,8 @@ # data settings dataset_path = "dataset" -class_name = "Experiment_3.2" -modelname = "Experiment_3.2_10epoch_239tainingdata_0.5BCS" +class_name = "Experiment_6.1" +modelname = "Experiment_6.1_10epoch_239tainingdata_0.5BCS" img_size = (448, 448) img_dims = [3] + list(img_size) From f4fd5dc224b3ab10b2668f027ee242bdffa73bea Mon Sep 17 00:00:00 2001 From: ChengguiSun Date: Thu, 29 Apr 2021 22:29:15 -0600 Subject: [PATCH 65/65] meta 5 sub 8 --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index 7dc5654..d91230f 100644 --- a/config.py +++ b/config.py @@ -51,7 +51,7 @@ # total epochs = meta_epochs * sub_epochs # evaluation after epochs -meta_epochs = 10 +meta_epochs = 5 sub_epochs = 8 # output settings