diff --git a/README.md b/README.md index 10392c7a..1abb1f20 100644 --- a/README.md +++ b/README.md @@ -241,14 +241,9 @@ Environment files... specifically .yml files for mamba/conda environments (these Media used in documentation. -#### archive - -Old code that we didn't quite want to delete, but is basically obsolete. - - #### sandbox -Random things that don't fit in any other directory, but aren't quite deprecated. Mostly postprocessing scripts that were built for a single use case but could potentially be useful in the future. +Random things that don't fit in any other directory, but aren't quite deprecated. #### test_images diff --git a/archive/MLDebugTemplate.tdb b/archive/MLDebugTemplate.tdb deleted file mode 100644 index fa806372..00000000 Binary files a/archive/MLDebugTemplate.tdb and /dev/null differ diff --git a/archive/README.md b/archive/README.md deleted file mode 100644 index 4d1aa220..00000000 --- a/archive/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Overview - -This folder contains obsolete code that we didn't *quite* want to entirely delete from the repo. YMMV with anything in this folder. It's called "archive", don't say you weren't warned. diff --git a/archive/active_learning/.gitignore b/archive/active_learning/.gitignore deleted file mode 100644 index bf433aae..00000000 --- a/archive/active_learning/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -*.png -*.zip -experiment_triplet_confidence/ -finetunedtriplet_resnet50_5000/ -triplet_resnet50_1499/ -missouricameratraps_online_triplet_confidence/ -wpzurbanwoodlandcarnivores_offline_triplet_confidence/ -wpzurbanwoodlandcarnivores_online_triplet_confidence/ diff --git a/archive/active_learning/DL/Engine.py b/archive/active_learning/DL/Engine.py deleted file mode 100644 index 3927752d..00000000 --- a/archive/active_learning/DL/Engine.py +++ /dev/null @@ -1,252 +0,0 @@ -from .utils import * - -import time -import sys - -def log(total,current,t,l,epoch=-1,top1=None,top5=None): - if epoch!=-1: - print('Epoch %d'%(epoch), end =" ") - if top1 is not None and top5 is not None: - print('Batch [%d/%d]\t' - 'Time %.3f %.3f\t' - 'Loss %.4f %.4f\t' - 'Acc@1 %.3f %.3f\t' - 'Acc@5 %.3f %.3f'%(current, total, t.val, t.avg, l.val, l.avg, top1.val, top1.avg, top5.val, top5.avg)) - else: - print('Batch [%d/%d]\t' - 'Time %.3f %.3f\t' - 'Loss %.4f %.4f'%(current, total, t.val, t.avg, l.val, l.avg)) - sys.stdout.flush() - -class Engine(): - """ - Class for training and evaluating a model and using it for prediction on new data. - - Attributes: - model: The model to train. - criterion: The criterion/loss function to optimize the model with. - optimizer: The method by which to optimize the criterion. - verbose: Output and log verbosity. - print_freq: Frequency of verbose outputs. - progressBar: - """ - - def __init__(self, model, criterion, optimizer, verbose=False, print_freq=10, progressBar=None): - self.model = model - self.criterion = criterion - self.optimizer = optimizer - self.verbose = verbose - self.print_freq = print_freq - self.progressBar = progressBar - - def train_one_batch(self, input, target, iter_num, calc_accuracy): - """ - Trains a model on one batch. - - Args: - input: Samples in the batch loaded by a DataLoader for training dataset. - target: Labels for samples in the batch. - iter_num: Which iteration (batch) number this is. - calc_accuracy: Whether to calculate and log top1 and top5 accuracy. - - Returns: - loss: Loss on this batch. - t: Time taken to train on this batch. - acc1: Accuracy of top 1 predictions. - acc5: Accuracy of top 5 predictions. - """ - - start = time.time() - - input = input.cuda()#non_blocking=True) - target = target.cuda()#non_blocking=True) - - # compute output - output, _ = self.model(input) - - # measure accuracy - if calc_accuracy: - acc1, acc5 = accuracy(output, target, topk=(1, 5)) - - # compute loss on this batch - loss = self.criterion(output, target) - - self.optimizer.zero_grad() # zero the parameter gradients - loss.backward() # accumulate gradient for each parameter - self.optimizer.step() # update parameters - - end = time.time() - t = end - start - - if self.progressBar: - self.progressBar.setValue(iter_num) - - if not calc_accuracy: - return loss.item(), t - else: - return loss.item(), t, acc1, acc5 - - - def train_one_epoch(self, train_loader, epoch_num, calc_accuracy): - """ - Trains a model for one epoch. - - Args: - train_loader: DataLoader for training dataset. - epoch_num: Index of the epochs for which the model has been trained. - calc_accuracy: Whether to calculate and log top1 and top5 accuracy. - """ - - batch_time = AverageMeter() - losses = AverageMeter() - if calc_accuracy: - top1 = AverageMeter() - top5 = AverageMeter() - - self.model.train() # switch model to train mode - - for i, batch in enumerate(train_loader): - input = batch[0] - target = batch[1] - - # train on a batch, record loss, and measure accuracy (if calc_accuracy) - if calc_accuracy: - loss, t, acc1, acc5 = self.train_one_batch(input,target,i,True) - top1.update(acc1, input.size(0)) - top5.update(acc5, input.size(0)) - else: - loss, t = self.train_one_batch(input, target, i, False) - losses.update(loss, input.size(0)) - batch_time.update(t) - - if self.verbose and i % self.print_freq == 0: - if calc_accuracy: - log(len(train_loader), i, batch_time, losses, epoch=epoch_num, top1=top1, top5=top5) - else: - log(len(train_loader), i, batch_time, losses, epoch=epoch_num) - - - def validate_one_batch(self, input, target, iter_num, calcAccuracy): - with torch.no_grad(): - start=time.time() - - input = input.cuda(non_blocking=True) - target = target.cuda(non_blocking=True) - - # compute output - output, _ = self.model(input) - - # measure accuracy and record loss - if calcAccuracy: - acc1, acc5 = accuracy(output, target, topk=(1, 5)) - loss = self.criterion(output, target) - - end = time.time() - if self.progressBar: - self.progressBar.setValue(iter_num) - - if not calcAccuracy: - return loss.item(),end-start - else: - return loss.item(),end-start,acc1,acc5 - - def validate(self, val_loader, calcAccuracy): - batch_time = AverageMeter() - losses = AverageMeter() - if calcAccuracy: - top1 = AverageMeter() - top5 = AverageMeter() - - # switch to evaluate mode - self.model.eval() - - for i, batch in enumerate(val_loader): - input= batch[0] - target= batch[1] - if calcAccuracy: - loss,t,acc1,acc5= self.validate_one_batch(input,target,i, True) - top1.update(acc1,input.size(0)) - top5.update(acc5,input.size(0)) - else: - loss,t= self.validate_one_batch(input,target,i, False) - losses.update(loss, input.size(0)) - batch_time.update(t) - - if self.verbose and i % self.print_freq == 0: - if calcAccuracy: - log(len(val_loader), i, batch_time, losses, top1=top1, top5=top5) - else: - log(len(val_loader), i, batch_time, losses) - if calcAccuracy: - log(len(val_loader), i, batch_time, losses, top1=top1, top5=top5) - else: - log(len(val_loader), i, batch_time, losses) - - return losses.avg - - def predict_one_batch(self, input, iter_num): - with torch.no_grad(): - input = input.cuda(non_blocking=True) - # compute output - output, _ = self.model(input) - if self.progressBar: - self.progressBar.setValue(iter_num) - - return output - - def predict(self, dataloader, load_info= False, dim=256): - - # switch to evaluate mode - self.model.eval() - embeddings = np.zeros((len(dataloader.dataset), dim), dtype=np.float32) - labels = np.zeros(len(dataloader.dataset), dtype=np.int64) - if load_info: - paths=[None]*len(dataloader.dataset) - k = 0 - for i, batch in enumerate(dataloader): - images=batch[0] - target= batch[1] - if load_info: - paths[k:k+len(batch[2])]=batch[2] - embedding= self.predict_one_batch(images,i) - embeddings[k:k+len(images)] = embedding.data.cpu().numpy() - labels[k:k+len(images)] = target.numpy() - k += len(images) - if self.verbose and i % self.print_freq == 0: - print("Batch %d"%(i)) - sys.stdout.flush() - if load_info: - return embeddings, labels, paths - else: - return embeddings, labels - - def embedding_one_batch(self, input, iter_num): - """ - Use engine model for evaluation to get imbedding features for a batch of input images. - """ - with torch.no_grad(): - input = input.cuda(non_blocking=True) - _, output = self.model(input) # compute output - if self.progressBar: - self.progressBar.setValue(iter_num) - - return output - - def embedding(self, dataloader, dim=256): - """ - Use Engine model for evaluation to get embedding features for input samples from a dataloader. - """ - - self.model.eval() # switch model to evaluate mode - embeddings = np.zeros((len(dataloader.dataset), dim), dtype=np.float32) - k = 0 - for i, batch in enumerate(dataloader): # load 1 batch of images at a time from dataloader and evaluate - images = batch[0] - embedding = self.embedding_one_batch(images, i) - embeddings[k:k+len(images)] = embedding.data.cpu().numpy() - k += len(images) - if self.verbose and i % self.print_freq == 0: - print("Batch %d"%(i)) - sys.stdout.flush() - - return embeddings diff --git a/archive/active_learning/DL/__init__.py b/archive/active_learning/DL/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/archive/active_learning/DL/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/archive/active_learning/DL/losses.py b/archive/active_learning/DL/losses.py deleted file mode 100644 index b2f2066e..00000000 --- a/archive/active_learning/DL/losses.py +++ /dev/null @@ -1,136 +0,0 @@ -import torch -import torch.nn as nn -import torch.nn.functional as F -from torch.autograd import Variable - -class FocalLoss(nn.Module): - def __init__(self, gamma=0, alpha=None, size_average=True): - super(FocalLoss, self).__init__() - self.gamma = gamma - self.alpha = alpha - if isinstance(alpha,(float,int)): self.alpha = torch.Tensor([alpha,1-alpha]) - if isinstance(alpha,list): self.alpha = torch.Tensor(alpha) - self.size_average = size_average - - def forward(self, input, target): - if input.dim()>2: - input = input.view(input.size(0),input.size(1),-1) # N,C,H,W => N,C,H*W - input = input.transpose(1,2) # N,C,H*W => N,H*W,C - input = input.contiguous().view(-1,input.size(2)) # N,H*W,C => N*H*W,C - target = target.view(-1,1) - - logpt = F.log_softmax(input) - logpt = logpt.gather(1,target) - logpt = logpt.view(-1) - pt = Variable(logpt.data.exp()) - - if self.alpha is not None: - if self.alpha.type()!=input.data.type(): - self.alpha = self.alpha.type_as(input.data) - at = self.alpha.gather(0,target.data.view(-1)) - logpt = logpt * Variable(at) - - loss = -1 * (1-pt)**self.gamma * logpt - if self.size_average: return loss.mean() - else: return loss.sum() - -class CenterLoss(nn.Module): - """Center loss. - - Reference: - Wen et al. A Discriminative Feature Learning Approach for Deep Face Recognition. ECCV 2016. - - Args: - num_classes (int): number of classes. - feat_dim (int): feature dimension. - """ - def __init__(self, num_classes=10, feat_dim=2, coef=1.0): - super(CenterLoss, self).__init__() - self.num_classes = num_classes - self.feat_dim = feat_dim - self.centers = nn.Parameter(torch.randn(self.num_classes, self.feat_dim).cuda()) - self.coef = coef - self.nll= nn.CrossEntropyLoss().cuda() - - def forward(self, embeddings, outputs, target): - """ - Args: - x: feature matrix with shape (batch_size, feat_dim). - labels: ground truth labels with shape (batch_size). - """ - batch_size = embeddings.size(0) - distmat = torch.pow(embeddings, 2).sum(dim=1, keepdim=True).expand(batch_size, self.num_classes) + \ - torch.pow(self.centers, 2).sum(dim=1, keepdim=True).expand(self.num_classes, batch_size).t() - distmat.addmm_(1, -2, embeddings, self.centers.t()) - - classes = torch.arange(self.num_classes).long() - classes = classes.cuda() - labels = target.unsqueeze(1).expand(batch_size, self.num_classes) - mask = labels.eq(classes.expand(batch_size, self.num_classes)) - - dist = [] - for i in range(batch_size): - value = distmat[i][mask[i]] - value = value.clamp(min=1e-12, max=1e+12) # for numerical stability - dist.append(value) - dist = torch.cat(dist) - loss = dist.mean() - loss = loss*self.coef+self.nll(outputs, target) - return loss - -class OnlineContrastiveLoss(nn.Module): - """ - Online Contrastive loss - Takes a batch of embeddings and corresponding labels. - Pairs are generated using pair_selector object that take embeddings and targets and return indices of positive - and negative pairs - """ - - def __init__(self, margin, pair_selector): - super(OnlineContrastiveLoss, self).__init__() - self.margin = margin - self.pair_selector = pair_selector - - def forward(self, embeddings, target): - positive_pairs, negative_pairs = self.pair_selector.get_pairs(embeddings, target) - if embeddings.is_cuda: - positive_pairs = positive_pairs.cuda() - negative_pairs = negative_pairs.cuda() - positive_loss = (embeddings[positive_pairs[:, 0]] - embeddings[positive_pairs[:, 1]]).pow(2).sum(1) - negative_loss = F.relu( - self.margin - (embeddings[negative_pairs[:, 0]] - embeddings[negative_pairs[:, 1]]).pow(2).sum( - 1).sqrt()).pow(2) - loss = torch.cat([positive_loss, negative_loss], dim=0) - return loss.mean() - -class OnlineTripletLoss(nn.Module): - """ - Online Triplets loss - Takes a batch of embeddings and corresponding labels. - Triplets are generated using triplet_selector object that take embeddings and targets and return indices of - triplets - """ - - def __init__(self, margin, triplet_selector): - super(OnlineTripletLoss, self).__init__() - self.margin = margin - self.triplet_selector = triplet_selector - - def forward(self, embeddings, target): - - triplets = self.triplet_selector.get_triplets(embeddings, target) - if embeddings.is_cuda: - triplets = triplets.cuda() - - ap_distances = (embeddings[triplets[:, 0]] - embeddings[triplets[:, 1]]).pow(2).sum(1)#.pow(.5) - an_distances = (embeddings[triplets[:, 0]] - embeddings[triplets[:, 2]]).pow(2).sum(1)#.pow(.5) - #print(ap_distances.size(),an_distances.size()) - #losses = -(((-ap_distances)/128)+1+1e-16).log() - (((-(128-an_distances))/128)+1+1e-16).log() - #import pdb - #pdb.set_trace() - - #losses = ap_distances - an_distances + self.margin - losses = torch.relu(ap_distances - an_distances + self.margin) - #print(losses.size()) - - return losses.mean() diff --git a/archive/active_learning/DL/networks.py b/archive/active_learning/DL/networks.py deleted file mode 100644 index b619ca7d..00000000 --- a/archive/active_learning/DL/networks.py +++ /dev/null @@ -1,104 +0,0 @@ -''' -networks.py - -Specifies architectures for different embedding networks. - -''' - -import torch -import torch.nn as nn -import torchvision.models as models -import torch.nn.functional as F - -class EmbeddingNet(nn.Module): - def __init__(self, architecture, feat_dim, use_pretrained=False): - super(EmbeddingNet, self).__init__() - self.feat_dim = feat_dim - self.inner_model = models.__dict__[architecture](pretrained=use_pretrained) - if architecture.startswith('resnet'): - in_feats= self.inner_model.fc.in_features - self.inner_model.fc = nn.Linear(in_feats, feat_dim) - elif architecture.startswith('inception'): - in_feats= self.inner_model.fc.in_features - self.inner_model.fc = nn.Linear(in_feats, feat_dim) - if architecture.startswith('densenet'): - in_feats= self.inner_model.classifier.in_features - self.inner_model.classifier = nn.Linear(in_feats, feat_dim) - if architecture.startswith('vgg'): - in_feats= self.inner_model.classifier._modules['6'].in_features - self.inner_model.classifier._modules['6'] = nn.Linear(in_feats, feat_dim) - if architecture.startswith('alexnet'): - in_feats= self.inner_model.classifier._modules['6'].in_features - self.inner_model.classifier._modules['6'] = nn.Linear(in_feats, feat_dim) - - def forward(self, x): - return self.inner_model.forward(x) - -class NormalizedEmbeddingNet(EmbeddingNet): - def __init__(self, architecture, feat_dim, use_pretrained=False): - EmbeddingNet.__init__(self, architecture, feat_dim, use_pretrained = use_pretrained) - - def forward(self, x): - embedding = F.normalize(self.inner_model.forward(x))*10.0 - return embedding, embedding - - -class SoftmaxNet(nn.Module): - def __init__(self, architecture, feat_dim, num_classes, use_pretrained = False): - super(SoftmaxNet, self).__init__() - self.embedding = EmbeddingNet(architecture, feat_dim, use_pretrained = use_pretrained) - self.classifier = nn.Linear(feat_dim, num_classes) - - def forward(self, x): - embed = self.embedding(x) - x = F.relu(embed) - x = self.classifier(x) - return x, embed - - -class ClassificationNet(nn.Module): - def __init__(self, feat_dim, num_classes): - super(ClassificationNet, self).__init__() - self.fc1 = nn.Linear(feat_dim, 128) - self.fc12 = nn.Linear(128, 64) - self.bn1 = nn.BatchNorm1d(64) - #self.fc13 = nn.Linear(128, 64) - #self.bn2 = nn.BatchNorm1d(64) - self.fc2 = nn.Linear(64, num_classes) - - def forward(self, x): - x = F.relu(self.fc1(x)) - x = F.dropout(x, p = 0.2, training=self.training) - x = F.relu(self.bn1(self.fc12(x))) - #x = F.relu(self.fc12(x)) - - #x = F.relu(self.bn1(self.fc13(x))) - #x = F.relu(self.fc13(x)) - #x = F.dropout(x, training=self.training) - x = self.fc2(x) - return x - -class CombinedNet(nn.Module): - def __init__(self, embedding_net, classification_net): - super(CombinedNet, self).__init__() - self.embedding_net = embedding_net - self.classification_net = classification_net - - def train(self, mode=True): - self.embedding_net.train() - self.classification_net.train() - - def eval(self, mode=False): - self.embedding_net.eval() - self.classification_net.eval() - - def forward(self, x): - # save features last FC layer - x = self.embedding_net(x) - #x = F.relu(x) - x = self.classification_net(x) - return x - - def get_embedding(self, x): - # save features last FC layer - return self.embedding_net(x) diff --git a/archive/active_learning/DL/sqlite_data_loader.py b/archive/active_learning/DL/sqlite_data_loader.py deleted file mode 100644 index f0397a29..00000000 --- a/archive/active_learning/DL/sqlite_data_loader.py +++ /dev/null @@ -1,257 +0,0 @@ - - -import os, random -import numpy as np -from PIL import Image as PILImage -from PIL import ImageStat -from peewee import * -from Database.DB_models import * -# from UIComponents.DBObjects import * -from .Engine import Engine - -from torch.utils.data import Dataset, DataLoader -from torchvision.transforms import * -from torch.utils.data.sampler import BatchSampler - - -class SQLDataLoader(Dataset): - """ - Class for SQL dataset used for active learning. - - Attributes: - img_base: Path to directory containing image files for detections in the dataset. - query: SQL query on Detection database table to get id, label and kind for samples in the dataset. - samples: Samples returned from the query on the database. - set_indices: Indices of detections of each kind. - kind: Which detection kind to use for current set of samples. - current_set: Subset of samples with specified kind. - model: Embedding model to use on the samples. - - embedding: Whether to return embedding representation (True, used for classifier training) or actual image (False, used for embedding training). - is_training: Whether the samples in current set are being used for (re)training (True) or validating (False) the embedding. - train_transform: Image transforms to apply while training the embedding. - eval_transform: Image transforms to apply while evaluating the embedding. - num_workers: Number of subprocesses to use for data loading. - """ - - def __init__(self, img_base, query=None, is_training=False, embedding_mode=False, kind=DetectionKind.ModelDetection.value, model=None, num_workers=8, raw_size=[256,256], processed_size=[224,224], limit=5000000): - self.img_base = img_base - if query is None: - self.query = Detection.select(Detection.id, Oracle.label, Detection.kind).join(Oracle).order_by(fn.random()).limit(limit) - else: - self.query = query - self.refresh(self.query) - self.set_indices = [[],[],[],[],[]]# indices with ActiveDetection, ModelDetection, ConfirmedDetection, UserDetection, and all user labeled/confirmed detections (ConfirmedDetection U UserDetection) - for i, s in enumerate(self.samples): - self.set_indices[s[2]].append(i) - self.set_indices[4] = list(set(self.set_indices[2]).union(self.set_indices[3])) - print([len(x) for x in self.set_indices]) - self.kind = kind - self.current_set = self.set_indices[kind] - - self.embedding = embedding_mode - self.is_training = is_training - - mean, std = self.get_mean_std() - self.train_transform = transforms.Compose([Resize(raw_size), RandomCrop(processed_size), RandomHorizontalFlip(), ColorJitter(), RandomRotation(20), ToTensor(), Normalize(mean, std)]) - self.eval_transform = transforms.Compose([Resize(raw_size), CenterCrop((processed_size)), ToTensor(), Normalize(mean, std)]) - - self.num_workers = num_workers - - if model is not None: - self.updateEmbedding(model) - - def refresh(self, query): - """Updates samples in SQL dataset by executing query.""" - - print('Reading database to get samples.') - self.samples = list(query.tuples()) - - def embedding_mode(self): - """Sets embedding attribute of the SQL dataset to True.""" - - self.embedding = True - - def image_mode(self): - """Sets embedding attribute of the SQL dataset to False.""" - - self.embedding = False - - def train(self): - """Sets is_training attribute of the SQL dataset to True.""" - - self.is_training = True - - def eval(self): - """Sets is_training attribute of the SQL dataset to False.""" - - self.is_training = False - - def set_kind(self, kind): - """Changes current set of samples to specified kind.""" - - self.current_set = self.set_indices[kind] - # TODO: should this also change self.kind? - - def updateEmbedding(self, model): - """Get embedding features for entire SQL dataset through a given model.""" - - print('Extracting embedding from the provided model ...') - self.model = model - e = Engine(model, None, None, verbose=True, print_freq=10) - temp = self.is_training - # get the embedding representations for all samples (i.e. set current_set to all indices) - self.current_set = list(range(len(self.samples))) - self.eval() # temporarily set is_training = False while generating embedding features - self.em = e.embedding(self.getSingleLoader(batch_size = 1024)) - self.is_training = temp # revert is_training to previous value - self.set_kind(self.kind) # revert current_set to only samples of specified detection kind - print('Embedding extraction is done.') - - def get_mean_std(self): - """Get RGB channel means and stds for image samples in the SQL dataset.""" - - info = Info.get() - if info.RM == -1 and info.RS == -1: - print("Calculating dataset mean and std") - means = np.zeros((3)) - stds = np.zeros((3)) - sample_size= min(len(self.samples), 10000) - for i in range(sample_size): - img = self.loader(random.choice(self.samples)[0]) - stat = ImageStat.Stat(img) - means+= np.array(stat.mean)/255.0 - stds+= np.array(stat.stddev)/255.0 - means = means/sample_size - stds = stds/sample_size - info.RM, info.GM, info.BM = means - info.RS, info.GS, info.BS = stds - info.save() - print("Updated dataset mean and std") - else: - print("Load dataset mean and std from database") - means = [info.RM, info.GM, info.BM] - stds = [info.RS, info.GS, info.BS] - return means, stds - - def __len__(self): - return len(self.current_set) - - def loader(self, path): - """Loads image given path to image file.""" - - return PILImage.open(os.path.join(self.img_base,path+".JPG")).convert('RGB') - # return PILImage.open(os.path.join(self.img_base,path)).convert('RGB') - - def __getitem__(self, idx): - """ - Args: - index (int): Index (position) of sample in current set list. - Returns: - tuple: (sample, target) where target is class_index of the target class. - """ - - index = self.current_set[idx] - path = self.samples[index][0] - target = self.samples[index][1] - if target is None: # to get getSingleLoader to work when using Detection.category (not Oracle) as label, and there are no labels for the current sample set - target = -1 - if not self.embedding: - sample = self.loader(path) - if self.is_training: - sample = self.train_transform(sample) - else: - sample = self.eval_transform(sample) - return sample, target, path - else: - return self.em[index], target, path - - def getpaths(self): - """Returns list of paths to image files in current sample set.""" - - return [os.path.join(self.img_base, self.samples[i][0]+".JPG") for i in self.current_set] - - def getallpaths(self): - """Returns list of paths to image files in entire dataset sample set.""" - - return [os.path.join(self.img_base,i[0]+".JPG") for i in self.samples] - - def getIDs(self): - """Returns list of detection IDs for samples in current sample set.""" - - return [self.samples[i][0] for i in self.current_set] - - def getallIDs(self): - """Returns list of detection IDs for samples in entire dataset sample set.""" - - return [self.samples[i][0] for i in range(len(self.samples))] - - def getlabels(self): - """Returns list of labels for samples in current sample set.""" - - return [self.samples[i][1] for i in self.current_set] - - def getalllabels(self): - """Returns list of labels for samples in entire dataset sample set.""" - - return [self.samples[i][1] for i in range(len(self.samples))] - - def writeback(self): - for i, l in enumerate(self.set_indices): - query = Detection.update(kind = i).where(Detection.id.in_(rList)) - query.execute() - - def getClassesInfo(self): - return list(Category.select()) - - def getBalancedLoader(self, P=14, K=10): - train_batch_sampler = BalancedBatchSampler(self, n_classes=P, n_samples=K) - return DataLoader(self, batch_sampler=train_batch_sampler, num_workers= self.num_workers) - - def getSingleLoader(self, batch_size = -1): - """Data loader for the SQL dataset that returns items from the current sample set.""" - - if batch_size == -1: - batch_size = 128 if self.is_training else 256 - return DataLoader(self, batch_size = batch_size, shuffle = self.is_training, num_workers = self.num_workers) - - -class BalancedBatchSampler(BatchSampler): - """ - BatchSampler - from a MNIST-like dataset, samples n_classes and within these classes samples n_samples. - Returns batches of size n_classes * n_samples - """ - - def __init__(self, underlying_dataset, n_classes, n_samples): - self.labels = [underlying_dataset.samples[i][1] for i in underlying_dataset.current_set] - self.labels_set = set(self.labels) - self.label_to_indices = {label: np.where(np.array(self.labels) == label)[0] - for label in self.labels_set} - for l in self.labels_set: - np.random.shuffle(self.label_to_indices[l]) - self.used_label_indices_count = {label: 0 for label in self.labels_set} - self.count = 0 - self.n_classes = min(n_classes, len(self.labels_set)) - self.n_samples = n_samples - self.dataset = underlying_dataset - self.batch_size = self.n_samples * self.n_classes - - def __iter__(self): - self.count = 0 - while self.count + self.batch_size < (len(self.dataset) * 4): - #print(self.labels_set, self.n_classes) - classes = np.random.choice(list(self.labels_set), self.n_classes, replace=False) - indices = [] - for class_ in classes: - indices.extend(self.label_to_indices[class_][ - self.used_label_indices_count[class_]:self.used_label_indices_count[ - class_] + self.n_samples]) - self.used_label_indices_count[class_] += self.n_samples - if self.used_label_indices_count[class_] + self.n_samples > len(self.label_to_indices[class_]): - np.random.shuffle(self.label_to_indices[class_]) - self.used_label_indices_count[class_] = 0 - yield indices - self.count += self.n_classes * self.n_samples - - def __len__(self): - return (len(self.dataset) // (self.n_samples*self.n_classes)) * 4 diff --git a/archive/active_learning/DL/utils.py b/archive/active_learning/DL/utils.py deleted file mode 100644 index 3bc04cf7..00000000 --- a/archive/active_learning/DL/utils.py +++ /dev/null @@ -1,435 +0,0 @@ -import numpy as np -import sys -import matplotlib.pyplot as plt -plt.switch_backend('agg') -from PIL import Image as PILImage -from matplotlib.offsetbox import OffsetImage, AnnotationBbox -import torch -import matplotlib.patches as mpatches -import shutil -from itertools import combinations -from MulticoreTSNE import MulticoreTSNE as TSNE -#from sklearn.manifold import TSNE -from sklearn.decomposition import PCA - -indexcolors =["#000000", "#FFFF00", "#1CE6FF", "#FF34FF", "#FF4A46", "#008941", "#006FA6", "#A30059", - - "#FFDBE5", "#7A4900", "#0000A6", "#63FFAC", "#B79762", "#004D43", "#8FB0FF", "#997D87", - "#5A0007", "#809693", "#E704C4", "#1B4400", "#4FC601", "#3B5DFF", "#4A3B53", "#FF2F80", - "#61615A", "#BA0900", "#6B7900", "#00C2A0", "#FFAA92", "#FF90C9", "#B903AA", "#D16100", - "#DDEFFF", "#000035", "#7B4F4B", "#A1C299", "#300018", "#0AA6D8", "#013349", "#00846F", - "#372101", "#FFB500", "#C2FFED", "#A079BF", "#CC0744", "#C0B9B2", "#C2FF99", "#001E09", - "#00489C", "#6F0062", "#0CBD66", "#EEC3FF", "#456D75", "#B77B68", "#7A87A1", "#788D66", - "#885578", "#FAD09F", "#FF8A9A", "#D157A0", "#BEC459", "#456648", "#0086ED", "#886F4C", - - "#34362D", "#B4A8BD", "#00A6AA", "#452C2C", "#636375", "#A3C8C9", "#FF913F", "#938A81", - "#575329", "#00FECF", "#B05B6F", "#8CD0FF", "#3B9700", "#04F757", "#C8A1A1", "#1E6E00", - "#7900D7", "#A77500", "#6367A9", "#A05837", "#6B002C", "#772600", "#D790FF", "#9B9700", - "#549E79", "#FFF69F", "#201625", "#72418F", "#BC23FF", "#99ADC0", "#3A2465", "#922329", - "#5B4534", "#FDE8DC", "#404E55", "#0089A3", "#CB7E98", "#A4E804", "#324E72", "#6A3A4C", - "#83AB58", "#001C1E", "#D1F7CE", "#004B28", "#C8D0F6", "#A3A489", "#806C66", "#222800", - "#BF5650", "#E83000", "#66796D", "#DA007C", "#FF1A59", "#8ADBB4", "#1E0200", "#5B4E51", - "#C895C5", "#320033", "#FF6832", "#66E1D3", "#CFCDAC", "#D0AC94", "#7ED379", "#012C58", - - "#7A7BFF", "#D68E01", "#353339", "#78AFA1", "#FEB2C6", "#75797C", "#837393", "#943A4D", - "#B5F4FF", "#D2DCD5", "#9556BD", "#6A714A", "#001325", "#02525F", "#0AA3F7", "#E98176", - "#DBD5DD", "#5EBCD1", "#3D4F44", "#7E6405", "#02684E", "#962B75", "#8D8546", "#9695C5", - "#E773CE", "#D86A78", "#3E89BE", "#CA834E", "#518A87", "#5B113C", "#55813B", "#FEFFE6", - "#00005F", "#A97399", "#4B8160", "#59738A", "#FF5DA7", "#F7C9BF", "#643127", "#513A01", - "#6B94AA", "#51A058", "#A45B02", "#1D1702", "#E20027", "#E7AB63", "#4C6001", "#9C6966", - "#64547B", "#97979E", "#006A66", "#391406", "#F4D749", "#0045D2", "#006C31", "#DDB6D0", - "#7C6571", "#9FB2A4", "#00D891", "#15A08A", "#BC65E9", "#FFFFFE", "#C6DC99", "#203B3C", - - "#671190", "#6B3A64", "#F5E1FF", "#FFA0F2", "#CCAA35", "#374527", "#8BB400", "#797868", - "#C6005A", "#3B000A", "#C86240", "#29607C", "#402334", "#7D5A44", "#CCB87C", "#B88183", - "#AA5199", "#B5D6C3", "#A38469", "#9F94F0", "#A74571", "#B894A6", "#71BB8C", "#00B433", - "#789EC9", "#6D80BA", "#953F00", "#5EFF03", "#E4FFFC", "#1BE177", "#BCB1E5", "#76912F", - "#003109", "#0060CD", "#D20096", "#895563", "#29201D", "#5B3213", "#A76F42", "#89412E", - "#1A3A2A", "#494B5A", "#A88C85", "#F4ABAA", "#A3F3AB", "#00C6C8", "#EA8B66", "#958A9F", - "#BDC9D2", "#9FA064", "#BE4700", "#658188", "#83A485", "#453C23", "#47675D", "#3A3F00", - "#061203", "#DFFB71", "#868E7E", "#98D058", "#6C8F7D", "#D7BFC2", "#3C3E6E", "#D83D66", - - "#2F5D9B", "#6C5E46", "#D25B88", "#5B656C", "#00B57F", "#545C46", "#866097", "#365D25", - "#252F99", "#00CCFF", "#674E60", "#FC009C", "#92896B"] - -def reduce_dimensionality(X): - print("Calculating TSNE") - #embedding= TSNE(n_components=2).fit_transform(X) - embedding= TSNE(n_jobs=20, n_components=2).fit_transform(X) - #embedding= PCA(n_components=2).fit_transform(X) - return embedding - #return X - -def plot_together(embedd, labels, preds, ind, paths, info): - embedding= reduce_dimensionality(embedd) - fig, axs = plt.subplots(nrows=2, ncols=2, constrained_layout=True) - colors= [indexcolors[int(i) % len(indexcolors)] for i in labels.squeeze()] - axs[0,0].sc=axs[0, 0].scatter(embedding[:,0],embedding[:,1], s=1, c= colors ) - colors= [indexcolors[int(i) % len(indexcolors)] for i in preds.squeeze()] - axs[0,1].sc=axs[0, 1].scatter(embedding[:,0],embedding[:,1], s=1, c= colors ) - colors= [indexcolors[int(i) % len(indexcolors)] for i in np.equal(labels, preds).astype(np.int32).squeeze()] - axs[1,0].sc=axs[1, 0].scatter(embedding[:,0],embedding[:,1], s=1, c= colors ) - selected = np.zeros_like(labels) - for i in ind: - selected[i]= 1 - colors= [indexcolors[int(i) % len(indexcolors)] for i in selected.squeeze()] - axs[1,1].sc=axs[1, 1].scatter(embedding[:,0],embedding[:,1], s=1, c= colors ) - - legend_texts= [ x[0] for x in sorted(info.items(), key=lambda kv: kv[1])] - patches=[] - for i,label in enumerate(legend_texts): - patches.append(mpatches.Patch(color=indexcolors[i], label=label)) - plt.legend(handles=patches) - plt.xlabel('Dim 1', fontsize=12) - plt.ylabel('Dim 2', fontsize=12) - plt.grid(True) - t_list=[] - - def onpick3(event): - if event.button==1: - #print(dir(event), type(sc)) - ax = event.inaxes - sys.stdout.flush() - cont, ind = ax.sc.contains(event) - for thumb in ind['ind']: - print(paths[thumb]) - img = PILImage.open(paths[thumb]) - img.thumbnail((128, 96), PILImage.ANTIALIAS) - img = OffsetImage(img, zoom=1) - ab = AnnotationBbox(img, (embedding[thumb,0]+0.2, embedding[thumb,1]+0.2), xycoords='data', frameon=False) - ax.add_artist(ab) - ab.set_visible(True) - t_list.append(ab) - else: - for annot in t_list: - annot.set_visible(False) - event.canvas.draw() - - fig.canvas.mpl_connect('button_press_event', onpick3) - plt.show() - -def plot_embedding(embedd, labels, paths, info): - fig= plt.figure(figsize=(10,10)) - embedding= reduce_dimensionality(embedd) - ax= plt.gca() - colors= [indexcolors[int(i) % len(indexcolors)] for i in labels.squeeze()] - sc=ax.scatter(embedding[:,0],embedding[:,1], s=1, c= colors ) - legend_texts= [ x[1] for x in sorted(info.items(), key=lambda kv: kv[1])] - plt.xticks([]) - plt.yticks([]) - patches=[] - for i,label in enumerate(legend_texts): - if np.count_nonzero(np.equal(labels, i))>0 : - bgcolor= indexcolors[i][1:] - #print(label,bgcolor) - (r, g, b) = (bgcolor[:2], bgcolor[2:4], bgcolor[4:]) - color = 'black' if 1 - (int(r, 16) * 0.299 + int(g, 16) * 0.587 + int(b, 16) * 0.114) / 255 < 0.5 else 'white' - plt.annotate(label, np.median(embedding[labels == i], axis=0), horizontalalignment='center', verticalalignment='center', size=8, weight='bold', color=color, backgroundcolor= indexcolors[i]) - # patches.append(mpatches.Patch(color=indexcolors[i], label=label)) - #plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, - # ncol=12, mode="expand", borderaxespad=0., handles=patches) - #plt.legend(handles=patches) - #plt.xlabel('Dim 1', fontsize=12) - #plt.ylabel('Dim 2', fontsize=12) - #plt.grid(True) - t_list=[] - - def onpick3(event): - if event.button==1: - cont, ind = sc.contains(event) - for thumb in ind['ind']: - print(paths[thumb]) - img = PILImage.open(paths[thumb]) - img.thumbnail((128, 96), PILImage.ANTIALIAS) - img = OffsetImage(img, zoom=1) - ab = AnnotationBbox(img, (embedding[thumb,0]+0.2, embedding[thumb,1]+0.2), xycoords='data', frameon=False) - ax.add_artist(ab) - ab.set_visible(True) - t_list.append(ab) - else: - for annot in t_list: - annot.set_visible(False) - event.canvas.draw() - - - fig.canvas.mpl_connect('button_press_event', onpick3) - plt.savefig('embedding_plot.png') - -def plot_embedding_images(embedd, labels, paths, info, savefile): - fig = plt.figure(figsize=(15,10)) - embedding = reduce_dimensionality(embedd) - ax = plt.gca() - ax.set_xlim(-15, 15) - ax.set_ylim(-10, 10) - colors = [indexcolors[int(i) % len(indexcolors)] for i in labels.squeeze()] - sc = ax.scatter(embedding[:,0],embedding[:,1], s=12, c= colors ) - legend_texts = [ x[0] for x in sorted(info.items(), key=lambda kv: kv[1])] - patches=[] - for i,label in enumerate(legend_texts): - patches.append(mpatches.Patch(color=indexcolors[i], label=label)) - plt.legend(handles=patches) - #plt.xlabel('Dim 1', fontsize=12) - #plt.ylabel('Dim 2', fontsize=12) - #plt.grid(True) - - for i,thumb in enumerate(paths): - #print(thumb) - img = PILImage.open(thumb) - # img.thumbnail((16, 12), PILImage.ANTIALIAS) - img.thumbnail((24, 18), PILImage.ANTIALIAS) - img = OffsetImage(img, zoom=1) - ab = AnnotationBbox(img, (embedding[i,0]+0.3, embedding[i,1]+0.3), xycoords='data', frameon=False) - ax.add_artist(ab) - ab.set_visible(True) - - # plt.show() - plt.savefig(savefile) - -def save_embedding_plot(name, embedding, labels, info): - fig = plt.figure(figsize=(10,10)) - colors= [indexcolors[int(i)] for i in labels.squeeze()] - plt.scatter(embedding[:,0],embedding[:,1], s=1, c= colors) - legend_texts= [ x[0] for x in sorted(info.items(), key=lambda kv: kv[1])] - patches=[] - for i,label in enumerate(legend_texts): - patches.append(mpatches.Patch(color=indexcolors[i], label=label)) - plt.legend(handles=patches) - plt.xlabel('Dim 1', fontsize=12) - plt.ylabel('Dim 2', fontsize=12) - plt.grid(True) - plt.savefig(name) - plt.close(fig) - -def save_checkpoint(state, is_best, filename='checkpoint.pth.tar'): - torch.save(state, filename) - if is_best: - shutil.copyfile(filename, 'model_best.pth.tar') - -def load_checkpoint(filename='model_best.pth.tar'): - return torch.load(filename) - -class AverageMeter(object): - """Computes and stores the average and current value""" - def __init__(self): - self.reset() - - def reset(self): - self.val = 0 - self.avg = 0 - self.sum = 0 - self.count = 0 - - def update(self, val, n=1): - self.val = val - self.sum += val * n - self.count += n - self.avg = self.sum / self.count - -def adjust_learning_rate(optimizer, epoch): - """Sets the learning rate to the initial LR decayed by 10 every 30 epochs""" - lr = args.lr * (0.1 ** (epoch // 30)) - for param_group in optimizer.param_groups: - param_group['lr'] = lr - - -def accuracy(output, target, topk=(1,)): - """Computes the accuracy over the k top predictions for the specified values of k""" - with torch.no_grad(): - maxk = max(topk) - batch_size = target.size(0) - - _, pred = output.topk(maxk, 1, True, True) - pred = pred.t() - correct = pred.eq(target.view(1, -1).expand_as(pred)) - - res = [] - for k in topk: - correct_k = correct[:k].view(-1).float().sum(0, keepdim=True) - res.append(correct_k.mul_(100.0 / batch_size)) - return res - -def pdist(vectors): - distance_matrix = -2 * vectors.mm(torch.t(vectors)) + vectors.pow(2).sum(dim=1).view(1, -1) + vectors.pow(2).sum( - dim=1).view(-1, 1) - return distance_matrix - - -class PairSelector: - """ - Implementation should return indices of positive pairs and negative pairs that will be passed to compute - Contrastive Loss - return positive_pairs, negative_pairs - """ - - def __init__(self): - pass - - def get_pairs(self, embeddings, labels): - raise NotImplementedError - - -class AllPositivePairSelector(PairSelector): - """ - Discards embeddings and generates all possible pairs given labels. - If balance is True, negative pairs are a random sample to match the number of positive samples - """ - def __init__(self, balance=True): - super(AllPositivePairSelector, self).__init__() - self.balance = balance - - def get_pairs(self, embeddings, labels): - labels = labels.cpu().data.numpy() - all_pairs = np.array(list(combinations(range(len(labels)), 2))) - all_pairs = torch.LongTensor(all_pairs) - positive_pairs = all_pairs[(labels[all_pairs[:, 0]] == labels[all_pairs[:, 1]]).nonzero()] - negative_pairs = all_pairs[(labels[all_pairs[:, 0]] != labels[all_pairs[:, 1]]).nonzero()] - if self.balance: - negative_pairs = negative_pairs[torch.randperm(len(negative_pairs))[:len(positive_pairs)]] - - return positive_pairs, negative_pairs - - -class HardNegativePairSelector(PairSelector): - """ - Creates all possible positive pairs. For negative pairs, pairs with smallest distance are taken into consideration, - matching the number of positive pairs. - """ - - def __init__(self, cpu=True): - super(HardNegativePairSelector, self).__init__() - self.cpu = cpu - - def get_pairs(self, embeddings, labels): - if self.cpu: - embeddings = embeddings.cpu() - distance_matrix = pdist(embeddings) - - labels = labels.cpu().data.numpy() - all_pairs = np.array(list(combinations(range(len(labels)), 2))) - all_pairs = torch.LongTensor(all_pairs) - positive_pairs = all_pairs[(labels[all_pairs[:, 0]] == labels[all_pairs[:, 1]]).nonzero()] - negative_pairs = all_pairs[(labels[all_pairs[:, 0]] != labels[all_pairs[:, 1]]).nonzero()] - negative_distances = distance_matrix[negative_pairs[:, 0], negative_pairs[:, 1]] - negative_distances = negative_distances.cpu().data.numpy() - top_negatives = np.argpartition(negative_distances, len(positive_pairs))[:len(positive_pairs)] - top_negative_pairs = negative_pairs[torch.LongTensor(top_negatives)] - - return positive_pairs, top_negative_pairs - -class TripletSelector: - """ - Implementation should return indices of anchors, positive and negative samples - return np array of shape [N_triplets x 3] - """ - - def __init__(self): - pass - - def get_pairs(self, embeddings, labels): - raise NotImplementedError - - -class AllTripletSelector(TripletSelector): - """ - Returns all possible triplets - May be impractical in most cases - """ - - def __init__(self): - super(AllTripletSelector, self).__init__() - - def get_triplets(self, embeddings, labels): - labels = labels.cpu().data.numpy() - triplets = [] - for label in set(labels): - label_mask = (labels == label) - label_indices = np.where(label_mask)[0] - if len(label_indices) < 2: - continue - negative_indices = np.where(np.logical_not(label_mask))[0] - anchor_positives = list(combinations(label_indices, 2)) # All anchor-positive pairs - - # Add all negatives for all positive pairs - temp_triplets = [[anchor_positive[0], anchor_positive[1], neg_ind] for anchor_positive in anchor_positives - for neg_ind in negative_indices] - triplets += temp_triplets - - return torch.LongTensor(np.array(triplets)) - - -def hardest_negative(loss_values): - hard_negative = np.argmax(loss_values) - return hard_negative if loss_values[hard_negative] > 0 else None - - -def random_hard_negative(loss_values): - hard_negatives = np.where(loss_values > 0)[0] - return np.random.choice(hard_negatives) if len(hard_negatives) > 0 else None - - -def semihard_negative(loss_values, margin): - semihard_negatives = np.where(np.logical_and(loss_values < margin, loss_values > 0))[0] - return np.random.choice(semihard_negatives) if len(semihard_negatives) > 0 else None - - -class FunctionNegativeTripletSelector(TripletSelector): - """ - For each positive pair, takes the hardest negative sample (with the greatest triplet loss value) to create a triplet - Margin should match the margin used in triplet loss. - negative_selection_fn should take array of loss_values for a given anchor-positive pair and all negative samples - and return a negative index for that pair - """ - - def __init__(self, margin, negative_selection_fn, cpu=True): - super(FunctionNegativeTripletSelector, self).__init__() - self.cpu = cpu - self.margin = margin - self.negative_selection_fn = negative_selection_fn - - def get_triplets(self, embeddings, labels): - if self.cpu: - embeddings = embeddings.cpu() - distance_matrix = pdist(embeddings) - distance_matrix = distance_matrix.cpu() - - labels = labels.cpu().data.numpy() - triplets = [] - - for label in set(labels): - label_mask = (labels == label) - label_indices = np.where(label_mask)[0] - if len(label_indices) < 2: - continue - negative_indices = np.where(np.logical_not(label_mask))[0] - anchor_positives = list(combinations(label_indices, 2)) # All anchor-positive pairs - anchor_positives = np.array(anchor_positives) - - ap_distances = distance_matrix[anchor_positives[:, 0], anchor_positives[:, 1]] - for anchor_positive, ap_distance in zip(anchor_positives, ap_distances): - loss_values = ap_distance - distance_matrix[torch.LongTensor(np.array([anchor_positive[0]])), torch.LongTensor(negative_indices)] + self.margin - loss_values = loss_values.data.cpu().numpy() - hard_negative = self.negative_selection_fn(loss_values) - if hard_negative is not None: - hard_negative = negative_indices[hard_negative] - triplets.append([anchor_positive[0], anchor_positive[1], hard_negative]) - - if len(triplets) == 0: - triplets.append([anchor_positive[0], anchor_positive[1], negative_indices[0]]) - - triplets = np.array(triplets) - #print(triplets.shape[0]) - return torch.LongTensor(triplets) - - -def HardestNegativeTripletSelector(margin, cpu=False): return FunctionNegativeTripletSelector(margin=margin, - negative_selection_fn=hardest_negative, - cpu=cpu) - - -def RandomNegativeTripletSelector(margin, cpu=False): return FunctionNegativeTripletSelector(margin=margin, - negative_selection_fn=random_hard_negative, - cpu=cpu) - - -def SemihardNegativeTripletSelector(margin, cpu=False): return FunctionNegativeTripletSelector(margin=margin, - negative_selection_fn=lambda x: semihard_negative(x, margin),cpu=cpu) diff --git a/archive/active_learning/Database/DB_models.py b/archive/active_learning/Database/DB_models.py deleted file mode 100644 index b6b20d9f..00000000 --- a/archive/active_learning/Database/DB_models.py +++ /dev/null @@ -1,104 +0,0 @@ -''' -DB_models.py - -Defines model classes representing database tables used in active -learning for classification. - -''' - -from peewee import * -from enum import Enum - -db_proxy = Proxy() # create a proxy backend for the database - -class DetectionKind(Enum): - ActiveDetection = 0 # a detection being currently shown to the user - ModelDetection = 1 # a detection with a label predicted by the classifier - ConfirmedDetection = 2 # a detection whose label was predicted by the classifier and confirmed by a user - UserDetection = 3 # a detection whose label was assigned by the user - - -class Info(Model): - ''' - Table containing information about the dataset. - ''' - name = CharField() # name of dataset - description = CharField() - contributor = CharField() - version = IntegerField() - year = IntegerField() - date_created = DateField() - RM = FloatField(null = True, default= -1) # mean in RGB channels(?) - GM = FloatField(null = True, default= -1) - BM = FloatField(null = True, default= -1) - RS = FloatField(null = True, default= -1) # standard deviation in RGB channels (?) - GS = FloatField(null = True, default= -1) - BS = FloatField(null = True, default= -1) - - class Meta: - database = db_proxy - - -class Image(Model): - ''' - Table containing information about each cropped image in the dataset. - ''' - id = CharField(primary_key=True) # cropped image unique identifier - file_name = CharField() # cropped image file name - width = IntegerField(null = True) # cropped image dimensions in pixels - height = IntegerField(null= True) - grayscale = BooleanField(null = True) # whether the cropped image is grayscale - - ## data related to original image - relative_size = FloatField(null = True) # cropped image size relative to original image size - source_file_name = CharField(null = True) # original image file name from which cropped image was generated - seq_id = CharField(null= True) # sequence identifier for the original image - seq_num_frames = IntegerField(null = True) # number of frames in sequence - frame_num = IntegerField(null = True) # which frame number in sequence - location = CharField(null = True) # location of camera trap - datetime = DateTimeField(null = True) # datetime of image - - class Meta: - database = db_proxy - - -class Category(Model): - ''' - Table containing information about classes (species) in the dataset. - ''' - id = IntegerField(primary_key=True, null = True) - name = CharField(null = True) - - class Meta: - database= db_proxy - - -class Detection(Model): - ''' - Table containing information about detections (annotations) in the dataset. - ''' - id = CharField(primary_key = True) # detection unique identifier - image = ForeignKeyField(Image) # pointer to cropped image the detection corresponds to - kind = IntegerField() # numeric code representing what kind of detection this is - category = ForeignKeyField(Category, null=True) # label assigned to the detection - category_confidence = FloatField(null = True) # confidence associated with the detection - bbox_confidence = FloatField(null = True) - bbox_X1= FloatField(null = True) - bbox_Y1= FloatField(null = True) - bbox_X2= FloatField(null = True) - bbox_Y2= FloatField(null = True) - - class Meta: - database = db_proxy - - - -class Oracle(Model): - ''' - Table containing information about labels for each detection in the dataset. - ''' - detection = ForeignKeyField(Detection) - label = IntegerField(null=True) - - class Meta: - database = db_proxy \ No newline at end of file diff --git a/archive/active_learning/Database/__init__.py b/archive/active_learning/Database/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/archive/active_learning/Database/add_oracle_to_db.py b/archive/active_learning/Database/add_oracle_to_db.py deleted file mode 100644 index 67ccf722..00000000 --- a/archive/active_learning/Database/add_oracle_to_db.py +++ /dev/null @@ -1,77 +0,0 @@ -''' -add_oracle_to_db.py - -Uses crops.json file (from crop_images_from_batch_api_detection.py) and -COCO .json file to initialize and populate Oracle table in a PostgreSQL database. - -''' - -import argparse, json, os, string, time -from peewee import * -from DB_models import * - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--crop_json', type=str, required=True, help='Path to a .json file with information about crops in crop_dir.') - parser.add_argument('--coco_json', type=str, required=True, help='Path to .json file with COCO formatted information about detections.') - parser.add_argument('--img_base', type=str, help='Path to add as prefix to filenames in COCO json.') - parser.add_argument('--class_list', type=str, required=True, help='Path to .txt file containing a list of classes in the dataset.') - parser.add_argument('--db_name', default='missouricameratraps', type=str, help='Name of the output Postgres DB.') - parser.add_argument('--db_user', default='new_user', type=str, help='Name of the user accessing the Postgres DB.') - parser.add_argument('--db_password', default='new_user_password', type=str, help='Password of the user accessing the Postgres DB.') - args = parser.parse_args() - - crop_json = json.load(open(args.crop_json, 'r')) - - # Get class names from .txt list - class_list = ['empty'] + [cname.lower() for cname in open(args.class_list, 'r').read().splitlines()] - - # Initialize Oracle table - DB_NAME = args.db_name - USER = args.db_user - PASSWORD = args.db_password - target_db = PostgresqlDatabase(DB_NAME, user=USER, password=PASSWORD, host='localhost') - db_proxy.initialize(target_db) - target_db.create_tables([Oracle]) - - # Map filenames to classes (NOTE: we assume a single image does not contain more than one class) - coco_json = json.load(open(args.coco_json, 'r')) - coco_categories = {cat['id']:cat['name'].replace('_', ' ') for cat in coco_json['categories']} - coco_imgid_to_fn = {im['id']: os.path.join(args.img_base, im['file_name'].replace('\\', '/')) for im in coco_json['images']} - coco_imgfn_to_catname = {} - for ann in coco_json['annotations']: - ann_imgid = ann['image_id'] - ann_imgfn = coco_imgid_to_fn[ann_imgid] - ann_catid = ann['category_id'] - ann_catname = coco_categories[ann_catid] - coco_imgfn_to_catname[ann_imgfn] = ann_catname - - # For each detection, use source image path to get class - counter = 0 - timer = time.time() - for crop in crop_json: - counter += 1 - crop_info = crop_json[crop] - source_image_file_name = crop_info['source_file_name'] - crop_class = coco_imgfn_to_catname[os.path.join(args.img_base , source_image_file_name)] - existing_cat_entries = Category.select().where(Category.name == crop_class) - try: - existing_category_entry = existing_cat_entries.get() - labelval = existing_category_entry.id - except: - print('Class %s not found in database Category table.'%crop_class) - - existing_oracle_entries = Oracle.select().where(Oracle.detection == crop) - try: - existing_oracle_entry = existing_oracle_entries.get() - except: - oracle_entry = Oracle.create(detection=crop, label=labelval) - oracle_entry.save() - - if counter%100 == 0: - print('Updated database with Oracle table entries for %d out of %d detections in %0.2f seconds'%(counter, len(crop_json), time.time() - timer)) - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/archive/active_learning/Database/initialize_pretrain_db.py b/archive/active_learning/Database/initialize_pretrain_db.py deleted file mode 100644 index 4e84f659..00000000 --- a/archive/active_learning/Database/initialize_pretrain_db.py +++ /dev/null @@ -1,165 +0,0 @@ -''' -initialize_pretrain_db.py - -(Largely draws from MegaDetector/research/active_learning/import_folder.py) - -Creates a PostgreSQL database of camera trap images for use in active learning for classification. The script assumes that the images have been -used to prepare a classification dataset (i.e. the images have been cropped and organized into subfolders named by class). - - -a COCO JSON file is available for the dataset. -# TODO update: Assumes that crops have already -# been generated for the images using make_active_learning_classification_dataset.py. The created DB contains tables: -# - info: information about the dataset -# - image: images present in the dataset -# - detections: crops of images with detections with confidence greater than a specified threshold - -''' - -import argparse, glob, json, os, PIL.Image, psycopg2, sys, time, uuid -import numpy as np -from datetime import datetime -from peewee import * -from DB_models import * - -parser = argparse.ArgumentParser(description='Initialize a PostgreSQL database for a dataset of camera trap images to use for active learning for classification.') -parser.add_argument('--db_name', default='missouricameratraps', type=str, - help='Name of the output Postgres DB.') -parser.add_argument('--db_user', default='new_user', type=str, - help='Name of the user accessing the Postgres DB.') -parser.add_argument('--db_password', default='new_user_password', type=str, - help='Password of the user accessing the Postgres DB.') -parser.add_argument('--crop_dir', metavar='DIR', required=True, - help='Path to dataset directory containing all cropped images') -parser.add_argument('--image_dir', metavar='DIR', - help='Path to dataset directory containing all original images') -parser.add_argument('--verbose', default=False, type=bool, - help='Output print messages while running') -parser.add_argument('--coco_json', metavar='DIR', - help='Path to COCO Camera Traps json file if available', default=None) -args = parser.parse_args() - -# Initialize Database -## database connection credentials -DB_NAME = args.db_name -USER = args.db_user -PASSWORD = args.db_password -#HOST = 'localhost' -#PORT = 5432 - -## first, make sure the (user, password) has been created -## sudo -u postgres psql -c "CREATE USER WITH PASSWORD ;" -## sudo -u postgres psql -c "CREATE DATABASE WITH OWNER CONNECTION LIMIT -1;" -## sudo -u postgres psql -c "GRANT CONNECT ON DATABASE TO ;" -## sudo -u postgres psql -d -c "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";" - -## Try to connect as USER to database DB_NAME through peewee -pretrain_db = PostgresqlDatabase(DB_NAME, user=USER, password=PASSWORD, host='localhost') -db_proxy.initialize(pretrain_db) -pretrain_db.create_tables([Info, Category, Image, Detection, Oracle]) - - - -# Populate Tables - -## create Info table -info_name = args.crop_dir -info_desc = 'Active learning for classification database of cropped images used to train embedding model (pretraining dataset).' -info_contrib = 'Amrita' -info_version = 0 -info_year = 2019 -info_date = datetime.today().date() -existing_info_entries = Info.select().where(Info.name == info_name) -try: - existing_info_entry = existing_info_entries.get() -except: - info_entry = Info.create(name = info_name, description = info_desc, contributor = info_contrib, version = info_version, year = info_year, date_created = info_date) - info_entry.save() - -## get class names for Category table -if sys.version_info >= (3, 5): - # Faster anD available in Python 3.5 and above - classes = [d.name for d in os.scandir(args.crop_dir) if d.is_dir()] -else: - classes = [d for d in os.listdir(args.crop_dir) if os.path.isdir(os.path.join(args.crop_dir, d))] -classes.sort() -class_to_idx = {classes[i]: i for i in range(len(classes))} - -## iterate through images in each class folder -for cat in sorted(class_to_idx.keys()): - # killing this process after over 38 hours adding over 500k white-tailed deer crops from emammal - # resuming for remaining classes - if cat not in ['wild turkey']: - continue - if args.verbose: - print("Start processing "+cat) - timer = time.time() - - existing_cat_entries = Category.select().where((Category.name == cat)) - try: - existing_cat_entry = existing_cat_entries.get() - except: - category_entry = Category.create(id = class_to_idx[cat], name = cat) - category_entry.save() - - for root, _, fnames in sorted(os.walk(os.path.join(args.crop_dir, cat))): - for i, fname in enumerate(sorted(fnames)): - if fname.endswith(".JPG"): - ## get cropped image data for Image table - img_uid = str(uuid.uuid4()) - img_fname = os.path.join(root, fname) - img = np.array(PIL.Image.open(os.path.join(root, fname))) - if img.dtype != np.uint8: - print('Failed to load image ' + fname) - continue - img_width = img.shape[1] - img_height = img.shape[0] - # if mean of each channel is about the same, image is likely grayscale - if (abs(np.mean(img[:,:,0]) - np.mean(img[:,:,1])) < 1e-1) & (abs(np.mean(img[:,:,1]) - np.mean(img[:,:,2])) < 1e-1): - img_grayscale = True - else: - img_grayscale = False - if len(fname.split('.JPG')[-2].split('_')) > 1: - img_source_fname = os.path.join(args.image_dir, os.path.basename(root), fname.split('.JPG')[-2].split('_')[0]+'.JPG') - else: - img_source_fname = os.path.join(args.image_dir, os.path.basename(root), fname) - - orig_img = np.array(PIL.Image.open(img_source_fname)) - orig_img_width = orig_img.shape[1] - orig_img_height = orig_img.shape[0] - img_relative_size = (img_height*img_width)/float(orig_img_height*orig_img_width) - ## still have no info on these: - # seq_id = CharField(null= True) # sequence identifier for the original image - # seq_num_frames = IntegerField(null = True) # number of frames in sequence - # frame_num = IntegerField(null = True) # which frame number in sequence - # location = CharField(null = True) # location of camera trap - # datetime = DateTimeField(null = True) - - existing_image_entries = Image.select().where((Image.file_name == img_fname) & (Image.width == img_width) & (Image.height == img_height)) - try: - existing_image_entry = existing_image_entries.get() - except: - image_entry = Image.create(id = img_uid, file_name = img_fname, width = img_width, height = img_height, grayscale = img_grayscale, - source_file_name = img_source_fname, relative_size = img_relative_size) - image_entry.save() - - ## store info about the detection corresponding to this image - detection_uid = str(uuid.uuid4()) - detection_img = img_uid - detection_kind = DetectionKind.UserDetection.value # pretrain dataset has user-provided annotations; ALTHOUGH crops were actually produced using detector... - detection_cat = class_to_idx[cat] - detection_cat_conf = 1 - detection_entry = Detection.create(id = detection_uid, image = detection_img, kind = detection_kind, category = detection_cat, category_confidence = detection_cat_conf) - detection_entry.save() - - ## store info about the true labels for the detection - ## - for pretrain dataset this is the same as the detection_category if the detection categories - oracle_entry = Oracle.create(detection = detection_uid, label = detection_cat) - - if i%10==0 and i>0 and args.verbose: - print('Processed %d files in %0.2f seconds'%(i, time.time() - timer)) - - - - -# print(classes) \ No newline at end of file diff --git a/archive/active_learning/Database/initialize_target_db.py b/archive/active_learning/Database/initialize_target_db.py deleted file mode 100644 index f687f44e..00000000 --- a/archive/active_learning/Database/initialize_target_db.py +++ /dev/null @@ -1,108 +0,0 @@ -''' -initialize_target_db.py - -Creates a PostgreSQL database of camera trap images for use in active learning for classification. - -Prerequisite steps: -- Create a PostgreSQL user and database: - sudo -u postgres psql -c "CREATE USER WITH PASSWORD ;" - sudo -u postgres psql -c "CREATE DATABASE WITH OWNER CONNECTION LIMIT -1;" - sudo -u postgres psql -c "GRANT CONNECT ON DATABASE TO ;" - sudo -u postgres psql -d -c "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";" -- Create a folder containing crops as well as a crops.json file using crop_images_from_batch_api_detections.py. -- Create a .txt file containing - -Produces: -- A PostgreSQL database with the following tables: - * info: information about the dataset - * category: class names and corresponding ids - * image: images present in the dataset - * detections: crops of images with detections with confidence greater than the specified detector threshold - -''' - -import argparse, glob, json, os, psycopg2, time, uuid -from datetime import datetime -from peewee import * -from DB_models import * - - -parser = argparse.ArgumentParser(description='Initialize a PostgreSQL database for a dataset of camera trap images to use for active learning for classification.') -parser.add_argument('--db_name', default='missouricameratraps', type=str, - help='Name of the output Postgres DB.') -parser.add_argument('--db_user', type=str, required=True, - help='Name of the user accessing the Postgres DB.') -parser.add_argument('--db_password', type=str, required=True, - help='Password of the user accessing the Postgres DB.') -parser.add_argument('--crop_dir', metavar='DIR', required=True, - help='Path to dataset directory containing all cropped images') -parser.add_argument('--class_list', type=str, required=True, - help='Path to .txt file containing a list of classes in the dataset.') -args = parser.parse_args() - -# Connect to database DB_NAME as USER and initialize tables -DB_NAME = args.db_name -USER = args.db_user -PASSWORD = args.db_password -target_db = PostgresqlDatabase(DB_NAME, user=USER, password=PASSWORD, host='localhost') -db_proxy.initialize(target_db) -target_db.create_tables([Info, Category, Image, Detection]) - - - -# Populate Info table -info_name = args.crop_dir -info_desc = 'Active learning for classification database of cropped images to classify via active learning (target dataset).' -info_contrib = 'Amrita' -info_version = 0 -info_year = 2019 -info_date = datetime.today().date() -existing_info_entries = Info.select().where(Info.name == info_name) -try: - existing_info_entry = existing_info_entries.get() -except: - info_entry = Info.create(name=info_name, description=info_desc, contributor=info_contrib, version=info_version, year=info_year, date_created=info_date) - info_entry.save() - -# Populate Category table -## For now, we have a predefined list of species we expect to see in the camera trap database (e.g. maybe from a quick look through the images) -## TODO: allow user to update the class list through the labeling tool UI as they see different species -class_list = ['empty'] + [cname.lower() for cname in open(args.class_list, 'r').read().splitlines()] -for i, cat in enumerate(class_list): - existing_cat_entries = Category.select().where(Category.name == cat) - try: - existing_cat_entry = existing_cat_entries.get() - except: - cat_entry = Category.create(id=i, name=cat) - cat_entry.save() - -# Populate Image and Detection tables -with open(os.path.join(args.crop_dir,'crops.json'), 'r') as infile: - crops_json = json.load(infile) - -counter = 0 -timer = time.time() -num_detections = len(crops_json) -for detectionid in crops_json: - counter += 1 - detection_data = crops_json[detectionid] - - # Image entry data - existing_image_entries = Image.select().where((Image.file_name == detection_data['file_name'])) - try: - existing_image_entry = existing_image_entries.get() - except: - image_entry = Image.create(id=detectionid, file_name=detection_data['file_name'], width=detection_data['width'], height=detection_data['height'], grayscale=detection_data['grayscale'], - source_file_name=detection_data['source_file_name'], relative_size=detection_data['relative_size'], - seq_id=detection_data['seq_id'], seq_num_frames=detection_data['seq_num_frames'], frame_num=detection_data['frame_num']) - image_entry.save() - - # Detection entry data - detection_entry = Detection.create(id=detectionid, image=detectionid, bbox_confidence=detection_data['bbox_confidence'], - bbox_X1=detection_data['bbox_X1'], bbox_Y1=detection_data['bbox_Y1'], bbox_X2=detection_data['bbox_X2'], bbox_Y2=detection_data['bbox_Y2'], - kind=DetectionKind.ModelDetection.value) - detection_entry.save() - - if counter%100 == 0: - print('Updated database with Image and Detection table entries for %d out of %d crops in %0.2f seconds'%(counter, num_detections, time.time() - timer)) - diff --git a/archive/active_learning/Database/initialize_target_db_from_classification_dataset.py b/archive/active_learning/Database/initialize_target_db_from_classification_dataset.py deleted file mode 100644 index ad027194..00000000 --- a/archive/active_learning/Database/initialize_target_db_from_classification_dataset.py +++ /dev/null @@ -1,122 +0,0 @@ -''' -initialize_target_db.py - -[deprecated] - -Creates a PostgreSQL database of camera trap images for use in active learning for classification. - -Prerequisite steps: -- Create a PostgreSQL user and database: - sudo -u postgres psql -c "CREATE USER WITH PASSWORD ;" - sudo -u postgres psql -c "CREATE DATABASE WITH OWNER CONNECTION LIMIT -1;" - sudo -u postgres psql -c "GRANT CONNECT ON DATABASE TO ;" - sudo -u postgres psql -d -c "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";" -- Create a folder containing crops as well as a crops.json file using crop_images_from_batch_api_detections.py. -- Create a .txt file containing - -Produces: -- A PostgreSQL database with the following tables: - * info: information about the dataset - * category: class names and corresponding ids - * image: images present in the dataset - * detections: crops of images with detections with confidence greater than the specified detector threshold - -''' - -import argparse, glob, json, os, pickle, psycopg2, time, uuid -from datetime import datetime -from PIL import Image -from peewee import * -from DB_models import * - - -parser = argparse.ArgumentParser(description='Initialize a PostgreSQL database for a dataset of camera trap images to use for active learning for classification.') -parser.add_argument('--db_name', default='missouricameratraps', type=str, - help='Name of the output Postgres DB.') -parser.add_argument('--db_user', type=str, required=True, - help='Name of the user accessing the Postgres DB.') -parser.add_argument('--db_password', type=str, required=True, - help='Password of the user accessing the Postgres DB.') -parser.add_argument('--coco_clf_crop_dir', metavar='DIR', required=True, - help='Path to dataset directory containing all cropped images in the COCO-style classification dataset') -parser.add_argument('--class_list', type=str, required=True, - help='Path to .txt file containing a list of classes in the dataset.') -args = parser.parse_args() - -# Connect to database DB_NAME as USER and initialize tables -DB_NAME = args.db_name -USER = args.db_user -PASSWORD = args.db_password -target_db = PostgresqlDatabase(DB_NAME, user=USER, password=PASSWORD, host='localhost') -db_proxy.initialize(target_db) -target_db.create_tables([Info, Category, Image, Detection]) - - - -# Populate Info table -info_name = args.coco_clf_crop_dir -info_desc = 'Active learning for classification database of cropped images to classify via active learning (target dataset); crops cosntructed from COCO-style bounding box annotations.' -info_contrib = 'Amrita' -info_version = 0 -info_year = 2019 -info_date = datetime.today().date() -existing_info_entries = Info.select().where(Info.name == info_name) -try: - existing_info_entry = existing_info_entries.get() -except: - info_entry = Info.create(name=info_name, description=info_desc, contributor=info_contrib, version=info_version, year=info_year, date_created=info_date) - info_entry.save() - -# Populate Category table -## Create category records from species present in the COCO camera trap classification dataset (COCO classes minus any excluded classes) -## TODO: allow user to update the class list through the labeling tool UI as they see different species -class_list = ['empty'] + sorted([d for d in os.listdir(args.coco_clf_crop_dir) if os.path.isdir(os.path.join(args.coco_clf_crop_dir, d))]) -for i, cat in enumerate(class_list): - existing_cat_entries = Category.select().where(Category.name == cat) - try: - existing_cat_entry = existing_cat_entries.get() - except: - cat_entry = Category.create(id=i, name=cat) - cat_entry.save() - -# Populate Image and Detection tables -# with open(os.path.join(args.crop_dir,'crops.json'), 'r') as infile: -# crops_json = json.load(infile) - -# counter = 0 -# timer = time.time() -# num_detections = len(crops_json) -with open(os.path.join(args.coco_clf_crop_dir, 'detections_final.pkl'), 'rb') as pklfile: - detections_pkl = pickle.load(pklfile) -sample_detection = list(detections_pkl.keys())[0] -print(detections_pkl[sample_detection]) -for class_name in sorted([d for d in os.listdir(args.coco_clf_crop_dir) if os.path.isdir(os.path.join(args.coco_clf_crop_dir, d))]): - files = [f.split(os.path.join(args.coco_clf_crop_dir, class_name)+'/')[1] for f in glob.glob(os.path.join(args.coco_clf_crop_dir, class_name, '**/*.JPG'), recursive=True)] - for f in files: - crop_id = str(uuid.uuid4()) - crop_fn = os.path.join(args.coco_clf_crop_dir) - assert 2==3, 'break' - -# for detectionid in crops_json: -# counter += 1 -# detection_data = crops_json[detectionid] - -# # Image entry data -# existing_image_entries = Image.select().where((Image.file_name == detection_data['file_name'])) -# try: -# existing_image_entry = existing_image_entries.get() -# except: -# image_entry = Image.create(id=detectionid, file_name=detection_data['file_name'], width=detection_data['width'], height=detection_data['height'], grayscale=detection_data['grayscale'], -# source_file_name=detection_data['source_file_name'], relative_size=detection_data['relative_size'], -# seq_id=detection_data['seq_id'], seq_num_frames=detection_data['seq_num_frames'], frame_num=detection_data['frame_num']) -# image_entry.save() - -# # Detection entry data -# detection_entry = Detection.create(id=detectionid, image=detectionid, bbox_confidence=detection_data['bbox_confidence'], -# bbox_X1=detection_data['bbox_X1'], bbox_Y1=detection_data['bbox_Y1'], bbox_X2=detection_data['bbox_X2'], bbox_Y2=detection_data['bbox_Y2'], -# kind=DetectionKind.ModelDetection.value) -# detection_entry.save() - -# if counter%100 == 0: -# print('Updated database with Image and Detection table entries for %d out of %d crops in %0.2f seconds'%(counter, num_detections, time.time() - timer)) - diff --git a/archive/active_learning/README.md b/archive/active_learning/README.md deleted file mode 100644 index 216a8004..00000000 --- a/archive/active_learning/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# Active learning for camera traps - -## Overview - -This repository contains the code, models, and instructions to run active deep learning animal identification as described in: - -Norouzzadeh MS, Morris D, Beery S, Joshi N, Jojic N, Clune J. [A deep active learning system for species identification and counting in camera trap images](https://besjournals.onlinelibrary.wiley.com/doi/full/10.1111/2041-210X.13504). Methods in ecology and evolution. 2021 Jan;12(1):150-61. - -## Contents - -The remainder of this README summarizes the main folders in this repo. - -### . (root) - -#### train_embedding.py - -This script learns an embedding model for a given dataset or set of datasets using either triplet, constrastive, or cross entropy loss. -Please refer to the command line arguments and the comments inside the code for further details. - -#### run_active_learning.py - -This script runs the active learning model on a given dataset. The algorithm starts with 1,000 randomly selected queries and then actively choose the other labels to be labeled by the oracle. This scripts does not consider an oracle in the loop for labeling. Instead, it simulates an oracle by using pre-labeled samples. For further details, please refer to the command line arguments and the comments inside the code. - -### data_preprocessing - -Produces crops either from detector_output.csv produced by run_tf_detector_batch.py (via crop_images_from_batch_api_detections.py) or from bboxes stored in COCO .json file. Either way, produces a .json file in the format expected by Database/initialize_*.py. - -### Database - -* initialize_*.py populates a database (target or pretrain). -* DB_models.py defines the data representation that's built; this is what's used by the rest of the code. -* add_oracle_to_db.py adds ground truth to a db for offline experiments. - -### experiments - -One-off scripts and notebooks. - -### labeling_tool - -See [labeling_tool/README.md](labeling_tool/README.md). - -### DL - -#### Engine.py - -ML utility functions like "train()" and "validate()" - -#### losses.py - -Loss functions: focal, center, triplet, contrastive - -#### networks.py - -* EmbeddingNet loads pre-trained embedding models -* SoftMaxNet is a wrapper for EmbeddingNet adding softmax loss -* ClassificationNet was the hand-created classification network, but was replaced by a network built into scikit learn - -### sqlite_data_loader - -Loads a data set from an existing SQLite DB. - -### sampling_methods - -Query tools for image selection for active learning. - -Mostly third-party code from: - -https://github.com/google/active-learning/tree/master/sampling_methods - -New stuff: - -* entropy -* confidence -* uniform_sampling (modified) -* constants.py (modified) - -Also miscellaneous fixes related to database assumptions. diff --git a/archive/active_learning/active_learning_methods/__init__.py b/archive/active_learning/active_learning_methods/__init__.py deleted file mode 100644 index 3eeb3066..00000000 --- a/archive/active_learning/active_learning_methods/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/archive/active_learning/active_learning_methods/bandit_discrete.py b/archive/active_learning/active_learning_methods/bandit_discrete.py deleted file mode 100644 index 5923976b..00000000 --- a/archive/active_learning/active_learning_methods/bandit_discrete.py +++ /dev/null @@ -1,125 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Bandit wrapper around base AL sampling methods. - -Assumes adversarial multi-armed bandit setting where arms correspond to -mixtures of different AL methods. - -Uses EXP3 algorithm to decide which AL method to use to create the next batch. -Similar to Hsu & Lin 2015, Active Learning by Learning. -https://www.csie.ntu.edu.tw/~htlin/paper/doc/aaai15albl.pdf -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import numpy as np - -from active_learning_methods.wrapper_sampler_def import AL_MAPPING, WrapperSamplingMethod - - -class BanditDiscreteSampler(WrapperSamplingMethod): - """Wraps EXP3 around mixtures of indicated methods. - - Uses EXP3 mult-armed bandit algorithm to select sampler methods. - """ - def __init__(self, - X, - y, - seed, - reward_function = lambda AL_acc: AL_acc[-1], - gamma=0.5, - samplers=[{'methods':('margin','uniform'),'weights':(0,1)}, - {'methods':('margin','uniform'),'weights':(1,0)}]): - """Initializes sampler with indicated gamma and arms. - - Args: - X: training data - y: labels, may need to be input into base samplers - seed: seed to use for random sampling - reward_function: reward based on previously observed accuracies. Assumes - that the input is a sequence of observed accuracies. Will ultimately be - a class method and may need access to other class properties. - gamma: weight on uniform mixture. Arm probability updates are a weighted - mixture of uniform and an exponentially weighted distribution. - Lower gamma more aggressively updates based on observed rewards. - samplers: list of dicts with two fields - 'samplers': list of named samplers - 'weights': percentage of batch to allocate to each sampler - """ - - self.name = 'bandit_discrete' - np.random.seed(seed) - self.X = X - self.y = y - self.seed = seed - self.initialize_samplers(samplers) - - self.gamma = gamma - self.n_arms = len(samplers) - self.reward_function = reward_function - - self.pull_history = [] - self.acc_history = [] - self.w = np.ones(self.n_arms) - self.x = np.zeros(self.n_arms) - self.p = self.w / (1.0 * self.n_arms) - self.probs = [] - - def update_vars(self, arm_pulled): - reward = self.reward_function(self.acc_history) - self.x = np.zeros(self.n_arms) - self.x[arm_pulled] = reward / self.p[arm_pulled] - self.w = self.w * np.exp(self.gamma * self.x / self.n_arms) - self.p = ((1.0 - self.gamma) * self.w / sum(self.w) - + self.gamma / self.n_arms) - print(self.p) - self.probs.append(self.p) - - def select_batch_(self, already_selected, N, eval_acc, **kwargs): - """Returns batch of datapoints sampled using mixture of AL_methods. - - Assumes that data has already been shuffled. - - Args: - already_selected: index of datapoints already selected - N: batch size - eval_acc: accuracy of model trained after incorporating datapoints from - last recommended batch - - Returns: - indices of points selected to label - """ - # Update observed reward and arm probabilities - self.acc_history.append(eval_acc) - if len(self.pull_history) > 0: - self.update_vars(self.pull_history[-1]) - # Sample an arm - arm = np.random.choice(range(self.n_arms), p=self.p) - self.pull_history.append(arm) - kwargs['N'] = N - kwargs['already_selected'] = already_selected - sample = self.samplers[arm].select_batch(**kwargs) - return sample - - def to_dict(self): - output = {} - output['samplers'] = self.base_samplers - output['arm_probs'] = self.probs - output['pull_history'] = self.pull_history - output['rewards'] = self.acc_history - return output - diff --git a/archive/active_learning/active_learning_methods/confidence_sampling.py b/archive/active_learning/active_learning_methods/confidence_sampling.py deleted file mode 100644 index a91d5487..00000000 --- a/archive/active_learning/active_learning_methods/confidence_sampling.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Margin based AL method. - -Samples in batches based on margin scores. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import numpy as np -from active_learning_methods.sampling_def import SamplingMethod - - -class ConfidenceAL(SamplingMethod): - - def __init__(self, X, y, seed): - self.X = X - self.y = y - self.name = 'confidence' - - def select_batch_(self, model, already_selected, N, **kwargs): - """Returns batch of datapoints with smallest margin/highest uncertainty. - - For binary classification, can just take the absolute distance to decision - boundary for each point. - For multiclass classification, must consider the margin between distance for - top two most likely classes. - - Args: - model: scikit learn model with decision_function implemented - already_selected: index of datapoints already selected - N: batch size - - Returns: - indices of points selected to add using margin active learner - """ - probs = model.predict_proba(self.X) - uncertainty= probs.max(axis=1) - srt = np.argsort(uncertainty) - indices = [] - i = 0 - while len(indices) < N: - if srt[i] not in already_selected: - indices.append(srt[i]) - i += 1 - return indices diff --git a/archive/active_learning/active_learning_methods/constants.py b/archive/active_learning/active_learning_methods/constants.py deleted file mode 100644 index c716d21e..00000000 --- a/archive/active_learning/active_learning_methods/constants.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Controls imports to fill up dictionary of different sampling methods. -""" - -from functools import partial -AL_MAPPING = {} - - -def get_base_AL_mapping(): - from active_learning_methods.margin_AL import MarginAL - from active_learning_methods.informative_diverse import InformativeClusterDiverseSampler - from active_learning_methods.hierarchical_clustering_AL import HierarchicalClusterAL - from active_learning_methods.uniform_sampling import UniformSampling - from active_learning_methods.represent_cluster_centers import RepresentativeClusterMeanSampling - from active_learning_methods.graph_density import GraphDensitySampler - from active_learning_methods.kcenter_greedy import kCenterGreedy - from active_learning_methods.entropy_sampling import EntropyAL - from active_learning_methods.confidence_sampling import ConfidenceAL - AL_MAPPING['margin'] = MarginAL - AL_MAPPING['informative_diverse'] = InformativeClusterDiverseSampler - AL_MAPPING['hierarchical'] = HierarchicalClusterAL - AL_MAPPING['uniform'] = UniformSampling - AL_MAPPING['margin_cluster_mean'] = RepresentativeClusterMeanSampling - AL_MAPPING['graph_density'] = GraphDensitySampler - AL_MAPPING['kcenter'] = kCenterGreedy - AL_MAPPING['entropy'] = EntropyAL - AL_MAPPING['confidence'] = ConfidenceAL - - -def get_all_possible_arms(): - from active_learning_methods.mixture_of_samplers import MixtureOfSamplers - AL_MAPPING['mixture_of_samplers'] = MixtureOfSamplers - - -def get_wrapper_AL_mapping(): - from active_learning_methods.bandit_discrete import BanditDiscreteSampler - from active_learning_methods.simulate_batch import SimulateBatchSampler - AL_MAPPING['bandit_mixture'] = partial( - BanditDiscreteSampler, - samplers=[{ - 'methods': ['margin', 'uniform'], - 'weights': [0, 1] - }, { - 'methods': ['margin', 'uniform'], - 'weights': [0.25, 0.75] - }, { - 'methods': ['margin', 'uniform'], - 'weights': [0.5, 0.5] - }, { - 'methods': ['margin', 'uniform'], - 'weights': [0.75, 0.25] - }, { - 'methods': ['margin', 'uniform'], - 'weights': [1, 0] - }]) - AL_MAPPING['bandit_discrete'] = partial( - BanditDiscreteSampler, - samplers=[{ - 'methods': ['margin', 'uniform'], - 'weights': [0, 1] - }, { - 'methods': ['margin', 'uniform'], - 'weights': [1, 0] - }]) - AL_MAPPING['simulate_batch_mixture'] = partial( - SimulateBatchSampler, - samplers=({ - 'methods': ['margin', 'uniform'], - 'weights': [1, 0] - }, { - 'methods': ['margin', 'uniform'], - 'weights': [0.5, 0.5] - }, { - 'methods': ['margin', 'uniform'], - 'weights': [0, 1] - }), - n_sims=5, - train_per_sim=10, - return_best_sim=False) - AL_MAPPING['simulate_batch_best_sim'] = partial( - SimulateBatchSampler, - samplers=[{ - 'methods': ['margin', 'uniform'], - 'weights': [1, 0] - }], - n_sims=10, - train_per_sim=10, - return_type='best_sim') - AL_MAPPING['simulate_batch_frequency'] = partial( - SimulateBatchSampler, - samplers=[{ - 'methods': ['margin', 'uniform'], - 'weights': [1, 0] - }], - n_sims=10, - train_per_sim=10, - return_type='frequency') - -def get_mixture_of_samplers(name): - assert 'mixture_of_samplers' in name - if 'mixture_of_samplers' not in AL_MAPPING: - raise KeyError('Mixture of Samplers not yet loaded.') - args = name.split('-')[1:] - samplers = args[0::2] - weights = args[1::2] - weights = [float(w) for w in weights] - assert sum(weights) == 1 - mixture = {'methods': samplers, 'weights': weights} - print(mixture) - return partial(AL_MAPPING['mixture_of_samplers'], mixture=mixture) - - -def get_AL_sampler(name): - if name in AL_MAPPING and name != 'mixture_of_samplers': - return AL_MAPPING[name] - if 'mixture_of_samplers' in name: - return get_mixture_of_samplers(name) - raise NotImplementedError('The specified sampler is not available.') diff --git a/archive/active_learning/active_learning_methods/entropy_sampling.py b/archive/active_learning/active_learning_methods/entropy_sampling.py deleted file mode 100644 index 7259d9fb..00000000 --- a/archive/active_learning/active_learning_methods/entropy_sampling.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Margin based AL method. - -Samples in batches based on margin scores. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import numpy as np -from scipy import stats -from active_learning_methods.sampling_def import SamplingMethod - - -class EntropyAL(SamplingMethod): - - def __init__(self, X, y, seed): - self.X = X - self.y = y - self.name = 'entropy' - - def select_batch_(self, model, already_selected, N, **kwargs): - """Returns batch of datapoints with smallest margin/highest uncertainty. - - For binary classification, can just take the absolute distance to decision - boundary for each point. - For multiclass classification, must consider the margin between distance for - top two most likely classes. - - Args: - model: scikit learn model with decision_function implemented - already_selected: index of datapoints already selected - N: batch size - - Returns: - indices of points selected to add using margin active learner - """ - - probs = model.predict_proba(self.X) - entropy= np.apply_along_axis(stats.entropy, 1, probs) - srt = np.argsort(entropy)[::-1] - indices = [] - i = 0 - while len(indices) < N: - if srt[i] not in already_selected: - indices.append(srt[i]) - i += 1 - return indices diff --git a/archive/active_learning/active_learning_methods/graph_density.py b/archive/active_learning/active_learning_methods/graph_density.py deleted file mode 100644 index adaf41a5..00000000 --- a/archive/active_learning/active_learning_methods/graph_density.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Diversity promoting sampling method that uses graph density to determine - most representative points. - -This is an implementation of the method described in -https://www.mpi-inf.mpg.de/fileadmin/inf/d2/Research_projects_files/EbertCVPR2012.pdf -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import copy - -from sklearn.neighbors import kneighbors_graph -from sklearn.metrics import pairwise_distances -import numpy as np -from active_learning_methods.sampling_def import SamplingMethod - - -class GraphDensitySampler(SamplingMethod): - """Diversity promoting sampling method that uses graph density to determine - most representative points. - """ - - def __init__(self, X, y, seed): - self.name = 'graph_density' - self.X = X - self.flat_X = self.flatten_X() - # Set gamma for gaussian kernel to be equal to 1/n_features - self.gamma = 1. / self.X.shape[1] - self.compute_graph_density() - - def compute_graph_density(self, n_neighbor=10): - # kneighbors graph is constructed using k=10 - connect = kneighbors_graph(self.flat_X, n_neighbor,p=1) - # Make connectivity matrix symmetric, if a point is a k nearest neighbor of - # another point, make it vice versa - neighbors = connect.nonzero() - inds = zip(neighbors[0],neighbors[1]) - # Graph edges are weighted by applying gaussian kernel to manhattan dist. - # By default, gamma for rbf kernel is equal to 1/n_features but may - # get better results if gamma is tuned. - for entry in inds: - i = entry[0] - j = entry[1] - distance = pairwise_distances(self.flat_X[[i]],self.flat_X[[j]],metric='manhattan') - distance = distance[0,0] - weight = np.exp(-distance * self.gamma) - connect[i,j] = weight - connect[j,i] = weight - self.connect = connect - # Define graph density for an observation to be sum of weights for all - # edges to the node representing the datapoint. Normalize sum weights - # by total number of neighbors. - self.graph_density = np.zeros(self.X.shape[0]) - for i in np.arange(self.X.shape[0]): - self.graph_density[i] = connect[i,:].sum() / (connect[i,:]>0).sum() - self.starting_density = copy.deepcopy(self.graph_density) - - def select_batch_(self, N, already_selected, **kwargs): - # If a neighbor has already been sampled, reduce the graph density - # for its direct neighbors to promote diversity. - batch = set() - self.graph_density[already_selected] = min(self.graph_density) - 1 - while len(batch) < N: - selected = np.argmax(self.graph_density) - neighbors = (self.connect[selected,:] > 0).nonzero()[1] - self.graph_density[neighbors] = self.graph_density[neighbors] - self.graph_density[selected] - batch.add(selected) - self.graph_density[already_selected] = min(self.graph_density) - 1 - self.graph_density[list(batch)] = min(self.graph_density) - 1 - return list(batch) - - def to_dict(self): - output = {} - output['connectivity'] = self.connect - output['graph_density'] = self.starting_density - return output diff --git a/archive/active_learning/active_learning_methods/hierarchical_clustering_AL.py b/archive/active_learning/active_learning_methods/hierarchical_clustering_AL.py deleted file mode 100644 index 9220b458..00000000 --- a/archive/active_learning/active_learning_methods/hierarchical_clustering_AL.py +++ /dev/null @@ -1,362 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Hierarchical cluster AL method. - -Implements algorithm described in Dasgupta, S and Hsu, D, -"Hierarchical Sampling for Active Learning, 2008 -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import numpy as np -from sklearn.cluster import AgglomerativeClustering -from sklearn.decomposition import PCA -from sklearn.neighbors import kneighbors_graph -from active_learning_methods.sampling_def import SamplingMethod -from active_learning_methods.utils.tree import Tree - - -class HierarchicalClusterAL(SamplingMethod): - """Implements hierarchical cluster AL based method. - - All methods are internal. select_batch_ is called via abstract classes - outward facing method select_batch. - - Default affininity is euclidean and default linkage is ward which links - cluster based on variance reduction. Hence, good results depend on - having normalized and standardized data. - """ - - def __init__(self, X, y, seed, beta=2, affinity='euclidean', linkage='ward', - clustering=None, max_features=None): - """Initializes AL method and fits hierarchical cluster to data. - - Args: - X: data - y: labels for determinining number of clusters as an input to - AgglomerativeClustering - seed: random seed used for sampling datapoints for batch - beta: width of error used to decide admissble labels, higher value of beta - corresponds to wider confidence and less stringent definition of - admissibility - See scikit Aggloerative clustering method for more info - affinity: distance metric used for hierarchical clustering - linkage: linkage method used to determine when to join clusters - clustering: can provide an AgglomerativeClustering that is already fit - max_features: limit number of features used to construct hierarchical - cluster. If specified, PCA is used to perform feature reduction and - the hierarchical clustering is performed using transformed features. - """ - self.name = 'hierarchical' - self.seed = seed - np.random.seed(seed) - # Variables for the hierarchical cluster - self.already_clustered = False - if clustering is not None: - self.model = clustering - self.already_clustered = True - self.n_leaves = None - self.n_components = None - self.children_list = None - self.node_dict = None - self.root = None # Node name, all node instances access through self.tree - self.tree = None - # Variables for the AL algorithm - self.initialized = False - self.beta = beta - self.labels = {} - self.pruning = [] - self.admissible = {} - self.selected_nodes = None - # Data variables - self.classes = None - self.X = X - - classes = list(set(y)) - self.n_classes = len(classes) - if max_features is not None: - transformer = PCA(n_components=max_features) - transformer.fit(X) - self.transformed_X = transformer.fit_transform(X) - #connectivity = kneighbors_graph(self.transformed_X,max_features) - self.model = AgglomerativeClustering( - affinity=affinity, linkage=linkage, n_clusters=len(classes)) - self.fit_cluster(self.transformed_X) - else: - self.model = AgglomerativeClustering( - affinity=affinity, linkage=linkage, n_clusters=len(classes)) - self.fit_cluster(self.X) - self.y = y - - self.y_labels = {} - # Fit cluster and update cluster variables - - self.create_tree() - print('Finished creating hierarchical cluster') - - def fit_cluster(self, X): - if not self.already_clustered: - self.model.fit(X) - self.already_clustered = True - self.n_leaves = self.model.n_leaves_ - self.n_components = self.model.n_components_ - self.children_list = self.model.children_ - - def create_tree(self): - node_dict = {} - for i in range(self.n_leaves): - node_dict[i] = [None, None] - for i in range(len(self.children_list)): - node_dict[self.n_leaves + i] = self.children_list[i] - self.node_dict = node_dict - # The sklearn hierarchical clustering algo numbers leaves which correspond - # to actual datapoints 0 to n_points - 1 and all internal nodes have - # ids greater than n_points - 1 with the root having the highest node id - self.root = max(self.node_dict.keys()) - self.tree = Tree(self.root, self.node_dict) - self.tree.create_child_leaves_mapping(range(self.n_leaves)) - for v in node_dict: - self.admissible[v] = set() - - def get_child_leaves(self, node): - return self.tree.get_child_leaves(node) - - def get_node_leaf_counts(self, node_list): - node_counts = [] - for v in node_list: - node_counts.append(len(self.get_child_leaves(v))) - return np.array(node_counts) - - def get_class_counts(self, y): - """Gets the count of all classes in a sample. - - Args: - y: sample vector for which to perform the count - Returns: - count of classes for the sample vector y, the class order for count will - be the same as that of self.classes - """ - unique, counts = np.unique(y, return_counts=True) - complete_counts = [] - for c in self.classes: - if c not in unique: - complete_counts.append(0) - else: - index = np.where(unique == c)[0][0] - complete_counts.append(counts[index]) - return np.array(complete_counts) - - def observe_labels(self, labeled): - for i in labeled: - self.y_labels[i] = labeled[i] - self.classes = np.array( - sorted(list(set([self.y_labels[k] for k in self.y_labels])))) - self.n_classes = len(self.classes) - - def initialize_algo(self): - self.pruning = [self.root] - self.labels[self.root] = np.random.choice(self.classes) - node = self.tree.get_node(self.root) - node.best_label = self.labels[self.root] - self.selected_nodes = [self.root] - - def get_node_class_probabilities(self, node, y=None): - children = self.get_child_leaves(node) - if y is None: - y_dict = self.y_labels - else: - y_dict = dict(zip(range(len(y)), y)) - labels = [y_dict[c] for c in children if c in y_dict] - # If no labels have been observed, simply return uniform distribution - if len(labels) == 0: - return 0, np.ones(self.n_classes)/self.n_classes - return len(labels), self.get_class_counts(labels) / (len(labels) * 1.0) - - def get_node_upper_lower_bounds(self, node): - n_v, p_v = self.get_node_class_probabilities(node) - # If no observations, return worst possible upper lower bounds - if n_v == 0: - return np.zeros(len(p_v)), np.ones(len(p_v)) - delta = 1. / n_v + np.sqrt(p_v * (1 - p_v) / (1. * n_v)) - return (np.maximum(p_v - delta, np.zeros(len(p_v))), - np.minimum(p_v + delta, np.ones(len(p_v)))) - - def get_node_admissibility(self, node): - p_lb, p_up = self.get_node_upper_lower_bounds(node) - all_other_min = np.vectorize( - lambda i:min([1 - p_up[c] for c in range(len(self.classes)) if c != i])) - lowest_alternative_error = self.beta * all_other_min( - np.arange(len(self.classes))) - return 1 - p_lb < lowest_alternative_error - - def get_adjusted_error(self, node): - _, prob = self.get_node_class_probabilities(node) - error = 1 - prob - admissible = self.get_node_admissibility(node) - not_admissible = np.where(admissible != True)[0] - error[not_admissible] = 1.0 - return error - - def get_class_probability_pruning(self, method='lower'): - prob_pruning = [] - for v in self.pruning: - label = self.labels[v] - label_ind = np.where(self.classes == label)[0][0] - if method == 'empirical': - _, v_prob = self.get_node_class_probabilities(v) - else: - lower, upper = self.get_node_upper_lower_bounds(v) - if method == 'lower': - v_prob = lower - elif method == 'upper': - v_prob = upper - else: - raise NotImplementedError - prob = v_prob[label_ind] - prob_pruning.append(prob) - return np.array(prob_pruning) - - def get_pruning_impurity(self, y): - impurity = [] - for v in self.pruning: - _, prob = self.get_node_class_probabilities(v, y) - impurity.append(1-max(prob)) - impurity = np.array(impurity) - weights = self.get_node_leaf_counts(self.pruning) - weights = weights / sum(weights) - return sum(impurity*weights) - - def update_scores(self): - node_list = set(range(self.n_leaves)) - # Loop through generations from bottom to top - while len(node_list) > 0: - parents = set() - for v in node_list: - node = self.tree.get_node(v) - # Update admissible labels for node - admissible = self.get_node_admissibility(v) - admissable_indices = np.where(admissible)[0] - for l in self.classes[admissable_indices]: - self.admissible[v].add(l) - # Calculate score - v_error = self.get_adjusted_error(v) - best_label_ind = np.argmin(v_error) - if admissible[best_label_ind]: - node.best_label = self.classes[best_label_ind] - score = v_error[best_label_ind] - node.split = False - - # Determine if node should be split - if v >= self.n_leaves: # v is not a leaf - if len(admissable_indices) > 0: # There exists an admissible label - # Make sure label set for node so that we can flow to children - # if necessary - assert node.best_label is not None - # Only split if all ancestors are admissible nodes - # This is part of definition of admissible pruning - admissible_ancestors = [len(self.admissible[a]) > 0 for a in - self.tree.get_ancestor(node)] - if all(admissible_ancestors): - left = self.node_dict[v][0] - left_node = self.tree.get_node(left) - right = self.node_dict[v][1] - right_node = self.tree.get_node(right) - node_counts = self.get_node_leaf_counts([v, left, right]) - split_score = (node_counts[1] / node_counts[0] * - left_node.score + node_counts[2] / - node_counts[0] * right_node.score) - if split_score < score: - score = split_score - node.split = True - node.score = score - if node.parent: - parents.add(node.parent.name) - node_list = parents - - def update_pruning_labels(self): - for v in self.selected_nodes: - node = self.tree.get_node(v) - pruning = self.tree.get_pruning(node) - self.pruning.remove(v) - self.pruning.extend(pruning) - # Check that pruning covers all leave nodes - node_counts = self.get_node_leaf_counts(self.pruning) - assert sum(node_counts) == self.n_leaves - # Fill in labels - for v in self.pruning: - node = self.tree.get_node(v) - if node.best_label is None: - node.best_label = node.parent.best_label - self.labels[v] = node.best_label - - def get_fake_labels(self): - fake_y = np.zeros(self.X.shape[0]) - for p in self.pruning: - indices = self.get_child_leaves(p) - fake_y[indices] = self.labels[p] - return fake_y - - def train_using_fake_labels(self, model, X_test, y_test): - classes_labeled = set([self.labels[p] for p in self.pruning]) - if len(classes_labeled) == self.n_classes: - fake_y = self.get_fake_labels() - model.fit(self.X, fake_y) - test_acc = model.score(X_test, y_test) - return test_acc - return 0 - - def select_batch_(self, N, already_selected, labeled, y, **kwargs): - # Observe labels for previously recommended batches - self.observe_labels(labeled) - - if not self.initialized: - self.initialize_algo() - self.initialized = True - print('Initialized algo') - - print('Updating scores and pruning for labels from last batch') - self.update_scores() - self.update_pruning_labels() - print('Nodes in pruning: %d' % (len(self.pruning))) - print('Actual impurity for pruning is: %.2f' % - (self.get_pruning_impurity(y))) - - # TODO(lishal): implement multiple selection methods - selected_nodes = set() - weights = self.get_node_leaf_counts(self.pruning) - probs = 1 - self.get_class_probability_pruning() - weights = weights * probs - weights = weights / sum(weights) - batch = [] - - print('Sampling batch') - while len(batch) < N: - node = np.random.choice(list(self.pruning), p=weights) - children = self.get_child_leaves(node) - children = [ - c for c in children if c not in self.y_labels and c not in batch - ] - if len(children) > 0: - selected_nodes.add(node) - batch.append(np.random.choice(children)) - self.selected_nodes = selected_nodes - return batch - - def to_dict(self): - output = {} - output['node_dict'] = self.node_dict - return output diff --git a/archive/active_learning/active_learning_methods/informative_diverse.py b/archive/active_learning/active_learning_methods/informative_diverse.py deleted file mode 100644 index f026d2a8..00000000 --- a/archive/active_learning/active_learning_methods/informative_diverse.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Informative and diverse batch sampler that samples points with small margin -while maintaining same distribution over clusters as entire training data. - -Batch is created by sorting datapoints by increasing margin and then growing -the batch greedily. A point is added to the batch if the result batch still -respects the constraint that the cluster distribution of the batch will -match the cluster distribution of the entire training set. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from sklearn.cluster import MiniBatchKMeans -import numpy as np -from active_learning_methods.sampling_def import SamplingMethod - - -class InformativeClusterDiverseSampler(SamplingMethod): - """Selects batch based on informative and diverse criteria. - - Returns highest uncertainty lowest margin points while maintaining - same distribution over clusters as entire dataset. - """ - - def __init__(self, X, y, seed): - self.name = 'informative_and_diverse' - self.X = X - self.flat_X = self.flatten_X() - # y only used for determining how many clusters there should be - # probably not practical to assume we know # of classes before hand - # should also probably scale with dimensionality of data - self.y = y - self.n_clusters = len(list(set(y))) - self.cluster_model = MiniBatchKMeans(n_clusters=self.n_clusters) - self.cluster_data() - - def cluster_data(self): - # Probably okay to always use MiniBatchKMeans - # Should standardize data before clustering - # Can cluster on standardized data but train on raw features if desired - self.cluster_model.fit(self.flat_X) - unique, counts = np.unique(self.cluster_model.labels_, return_counts=True) - self.cluster_prob = counts/sum(counts) - self.cluster_labels = self.cluster_model.labels_ - - def select_batch_(self, model, already_selected, N, **kwargs): - """Returns a batch of size N using informative and diverse selection. - - Args: - model: scikit learn model with decision_function implemented - already_selected: index of datapoints already selected - N: batch size - - Returns: - indices of points selected to add using margin active learner - """ - # TODO(lishal): have MarginSampler and this share margin function - try: - distances = model.decision_function(self.X) - except: - distances = model.predict_proba(self.X) - if len(distances.shape) < 2: - min_margin = abs(distances) - else: - sort_distances = np.sort(distances, 1)[:, -2:] - min_margin = sort_distances[:, 1] - sort_distances[:, 0] - rank_ind = np.argsort(min_margin) - rank_ind = [i for i in rank_ind if i not in already_selected] - new_batch_cluster_counts = [0 for _ in range(self.n_clusters)] - new_batch = [] - for i in rank_ind: - if len(new_batch) == N: - break - label = self.cluster_labels[i] - if new_batch_cluster_counts[label] / N < self.cluster_prob[label]: - new_batch.append(i) - new_batch_cluster_counts[label] += 1 - n_slot_remaining = N - len(new_batch) - batch_filler = list(set(rank_ind) - set(already_selected) - set(new_batch)) - new_batch.extend(batch_filler[0:n_slot_remaining]) - return new_batch - - def to_dict(self): - output = {} - output['cluster_membership'] = self.cluster_labels - return output diff --git a/archive/active_learning/active_learning_methods/kcenter_greedy.py b/archive/active_learning/active_learning_methods/kcenter_greedy.py deleted file mode 100644 index 698b1e8f..00000000 --- a/archive/active_learning/active_learning_methods/kcenter_greedy.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Returns points that minimizes the maximum distance of any point to a center. - -Implements the k-Center-Greedy method in -Ozan Sener and Silvio Savarese. A Geometric Approach to Active Learning for -Convolutional Neural Networks. https://arxiv.org/abs/1708.00489 2017 - -Distance metric defaults to l2 distance. Features used to calculate distance -are either raw features or if a model has transform method then uses the output -of model.transform(X). - -Can be extended to a robust k centers algorithm that ignores a certain number of -outlier datapoints. Resulting centers are solution to multiple integer program. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import numpy as np -from sklearn.metrics import pairwise_distances -from active_learning_methods.sampling_def import SamplingMethod - - -class kCenterGreedy(SamplingMethod): - - def __init__(self, X, y, seed, metric='euclidean'): - self.X = X - self.y = y - self.flat_X = self.flatten_X() - self.name = 'kcenter' - self.features = self.flat_X - self.metric = metric - self.min_distances = None - self.n_obs = self.X.shape[0] - self.already_selected = [] - - def update_distances(self, cluster_centers, only_new=True, reset_dist=False): - """Update min distances given cluster centers. - - Args: - cluster_centers: indices of cluster centers - only_new: only calculate distance for newly selected points and update - min_distances. - rest_dist: whether to reset min_distances. - """ - - if reset_dist: - self.min_distances = None - if only_new: - cluster_centers = [d for d in cluster_centers - if d not in self.already_selected] - if cluster_centers: - # Update min_distances for all examples given new cluster center. - x = self.features[cluster_centers] - dist = pairwise_distances(self.features, x, metric=self.metric) - - if self.min_distances is None: - self.min_distances = np.min(dist, axis=1).reshape(-1,1) - else: - self.min_distances = np.minimum(self.min_distances, dist) - - def select_batch_(self, model, already_selected, N, **kwargs): - """ - Diversity promoting active learning method that greedily forms a batch - to minimize the maximum distance to a cluster center among all unlabeled - datapoints. - - Args: - model: model with scikit-like API with decision_function implemented - already_selected: index of datapoints already selected - N: batch size - - Returns: - indices of points selected to minimize distance to cluster centers - """ - - try: - # Assumes that the transform function takes in original data and not - # flattened data. - print('Getting transformed features...') - self.features = model.transform(self.X) - print('Calculating distances...') - self.update_distances(already_selected, only_new=False, reset_dist=True) - except: - print('Using flat_X as features.') - self.update_distances(already_selected, only_new=True, reset_dist=False) - - new_batch = [] - - for _ in range(N): - if self.already_selected is None: - # Initialize centers with a randomly selected datapoint - ind = np.random.choice(np.arange(self.n_obs)) - else: - ind = np.argmax(self.min_distances) - # New examples should not be in already selected since those points - # should have min_distance of zero to a cluster center. - assert ind not in already_selected - - self.update_distances([ind], only_new=True, reset_dist=False) - new_batch.append(ind) - print('Maximum distance from cluster centers is %0.2f' - % max(self.min_distances)) - - - self.already_selected = already_selected - - return new_batch - diff --git a/archive/active_learning/active_learning_methods/margin_AL.py b/archive/active_learning/active_learning_methods/margin_AL.py deleted file mode 100644 index d5e956e0..00000000 --- a/archive/active_learning/active_learning_methods/margin_AL.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Margin based AL method. - -Samples in batches based on margin scores. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import numpy as np -from active_learning_methods.sampling_def import SamplingMethod - - -class MarginAL(SamplingMethod): - def __init__(self, X, y, seed): - self.X = X - self.y = y - self.name = 'margin' - - def select_batch_(self, model, already_selected, N, **kwargs): - """Returns batch of datapoints with smallest margin/highest uncertainty. - - For binary classification, can just take the absolute distance to decision - boundary for each point. - For multiclass classification, must consider the margin between distance for - top two most likely classes. - - Args: - model: scikit learn model with decision_function implemented - already_selected: index of datapoints already selected - N: batch size - - Returns: - indices of points selected to add using margin active learner - """ - - try: - distances = model.decision_function(self.X) - except: - distances = model.predict_proba(self.X) - if len(distances.shape) < 2: - min_margin = abs(distances) - else: - sort_distances = np.sort(distances, 1)[:, -2:] - min_margin = sort_distances[:, 1] - sort_distances[:, 0] - rank_ind = np.argsort(min_margin) - rank_ind = [i for i in rank_ind if i not in already_selected] - active_samples = rank_ind[0:N] - return active_samples - diff --git a/archive/active_learning/active_learning_methods/mixture_of_samplers.py b/archive/active_learning/active_learning_methods/mixture_of_samplers.py deleted file mode 100644 index 7f791183..00000000 --- a/archive/active_learning/active_learning_methods/mixture_of_samplers.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Mixture of base sampling strategies - -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import copy - -from active_learning_methods.sampling_def import SamplingMethod -from active_learning_methods.constants import AL_MAPPING, get_base_AL_mapping - -get_base_AL_mapping() - - -class MixtureOfSamplers(SamplingMethod): - """Samples according to mixture of base sampling methods. - - If duplicate points are selected by the mixed strategies when forming the batch - then the remaining slots are divided according to mixture weights and - another partial batch is requested until the batch is full. - """ - def __init__(self, - X, - y, - seed, - mixture={'methods': ('margin', 'uniform'), - 'weight': (0.5, 0.5)}, - samplers=None): - self.X = X - self.y = y - self.name = 'mixture_of_samplers' - self.sampling_methods = mixture['methods'] - self.sampling_weights = dict(zip(mixture['methods'], mixture['weights'])) - self.seed = seed - # A list of initialized samplers is allowed as an input because - # for AL_methods that search over different mixtures, may want mixtures to - # have shared AL_methods so that initialization is only performed once for - # computation intensive methods like HierarchicalClusteringAL and - # states are shared between mixtures. - # If initialized samplers are not provided, initialize them ourselves. - if samplers is None: - self.samplers = {} - self.initialize(self.sampling_methods) - else: - self.samplers = samplers - self.history = [] - - def initialize(self, samplers): - self.samplers = {} - for s in samplers: - self.samplers[s] = AL_MAPPING[s](self.X, self.y, self.seed) - - def select_batch_(self, already_selected, N, **kwargs): - """Returns batch of datapoints selected according to mixture weights. - - Args: - already_included: index of datapoints already selected - N: batch size - - Returns: - indices of points selected to add using margin active learner - """ - kwargs['already_selected'] = copy.copy(already_selected) - inds = set() - self.selected_by_sampler = {} - for s in self.sampling_methods: - self.selected_by_sampler[s] = [] - effective_N = 0 - while len(inds) < N: - effective_N += N - len(inds) - for s in self.sampling_methods: - if len(inds) < N: - batch_size = min(max(int(self.sampling_weights[s] * effective_N), 1), N) - sampler = self.samplers[s] - kwargs['N'] = batch_size - s_inds = sampler.select_batch(**kwargs) - for ind in s_inds: - if ind not in self.selected_by_sampler[s]: - self.selected_by_sampler[s].append(ind) - s_inds = [d for d in s_inds if d not in inds] - s_inds = s_inds[0 : min(len(s_inds), N-len(inds))] - inds.update(s_inds) - self.history.append(copy.deepcopy(self.selected_by_sampler)) - return list(inds) - - def to_dict(self): - output = {} - output['history'] = self.history - output['samplers'] = self.sampling_methods - output['mixture_weights'] = self.sampling_weights - for s in self.samplers: - s_output = self.samplers[s].to_dict() - output[s] = s_output - return output diff --git a/archive/active_learning/active_learning_methods/represent_cluster_centers.py b/archive/active_learning/active_learning_methods/represent_cluster_centers.py deleted file mode 100644 index d4997de1..00000000 --- a/archive/active_learning/active_learning_methods/represent_cluster_centers.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Another informative and diverse sampler that mirrors the algorithm described -in Xu, et. al., Representative Sampling for Text Classification Using -Support Vector Machines, 2003 - -Batch is created by clustering points within the margin of the classifier and -choosing points closest to the k centroids. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from sklearn.cluster import MiniBatchKMeans -import numpy as np -from active_learning_methods.sampling_def import SamplingMethod - - -class RepresentativeClusterMeanSampling(SamplingMethod): - """Selects batch based on informative and diverse criteria. - - Returns points within the margin of the classifier that are closest to the - k-means centers of those points. - """ - - def __init__(self, X, y, seed): - self.name = 'cluster_mean' - self.X = X - self.flat_X = self.flatten_X() - self.y = y - self.seed = seed - - def select_batch_(self, model, N, already_selected, **kwargs): - # Probably okay to always use MiniBatchKMeans - # Should standardize data before clustering - # Can cluster on standardized data but train on raw features if desired - try: - distances = model.decision_function(self.X) - except: - distances = model.predict_proba(self.X) - if len(distances.shape) < 2: - min_margin = abs(distances) - else: - sort_distances = np.sort(distances, 1)[:, -2:] - min_margin = sort_distances[:, 1] - sort_distances[:, 0] - rank_ind = np.argsort(min_margin) - rank_ind = [i for i in rank_ind if i not in already_selected] - - distances = abs(distances) - min_margin_by_class = np.min(abs(distances[already_selected]),axis=0) - unlabeled_in_margin = np.array([i for i in range(len(self.y)) - if i not in already_selected and - any(distances[i] 2: - flat_X = np.reshape(self.X, (shape[0],np.product(shape[1:]))) - return flat_X - - - @abc.abstractmethod - def select_batch_(self): - return - - def select_batch(self, **kwargs): - return self.select_batch_(**kwargs) - - def to_dict(self): - return None \ No newline at end of file diff --git a/archive/active_learning/active_learning_methods/simulate_batch.py b/archive/active_learning/active_learning_methods/simulate_batch.py deleted file mode 100644 index 038d42d5..00000000 --- a/archive/active_learning/active_learning_methods/simulate_batch.py +++ /dev/null @@ -1,261 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" Select a new batch based on results of simulated trajectories.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import copy -import math - -import numpy as np - -from active_learning_methods.wrapper_sampler_def import AL_MAPPING -from active_learning_methods.wrapper_sampler_def import WrapperSamplingMethod - - -class SimulateBatchSampler(WrapperSamplingMethod): - """Creates batch based on trajectories simulated using smaller batch sizes. - - Current support use case: simulate smaller batches than the batch size - actually indicated to emulate which points would be selected in a - smaller batch setting. This method can do better than just selecting - a batch straight out if smaller batches perform better and the simulations - are informative enough and are not hurt too much by labeling noise. - """ - - def __init__(self, - X, - y, - seed, - samplers=[{'methods': ('margin', 'uniform'),'weight': (1, 0)}], - n_sims=10, - train_per_sim=10, - return_type='best_sim'): - """ Initialize sampler with options. - - Args: - X: training data - y: labels may be used by base sampling methods - seed: seed for np.random - samplers: list of dicts with two fields - 'samplers': list of named samplers - 'weights': percentage of batch to allocate to each sampler - n_sims: number of total trajectories to simulate - train_per_sim: number of minibatches to split the batch into - return_type: two return types supported right now - best_sim: return points selected by the best trajectory - frequency: returns points selected the most over all trajectories - """ - self.name = 'simulate_batch' - self.X = X - self.y = y - self.seed = seed - self.n_sims = n_sims - self.train_per_sim = train_per_sim - self.return_type = return_type - self.samplers_list = samplers - self.initialize_samplers(self.samplers_list) - self.trace = [] - self.selected = [] - np.random.seed(seed) - - def simulate_batch(self, sampler, N, already_selected, y, model, X_test, - y_test, **kwargs): - """Simulates smaller batches by using hallucinated y to select next batch. - - Assumes that select_batch is only dependent on already_selected and not on - any other states internal to the sampler. i.e. this would not work with - BanditDiscreteSampler but will work with margin, hierarchical, and uniform. - - Args: - sampler: dict with two fields - 'samplers': list of named samplers - 'weights': percentage of batch to allocate to each sampler - N: batch size - already_selected: indices already labeled - y: y to use for training - model: model to use for margin calc - X_test: validaiton data - y_test: validation labels - - Returns: - - mean accuracy - - indices selected by best hallucinated trajectory - - best accuracy achieved by one of the trajectories - """ - minibatch = max(int(math.ceil(N / self.train_per_sim)), 1) - results = [] - best_acc = 0 - best_inds = [] - self.selected = [] - n_minibatch = int(N/minibatch) + (N % minibatch > 0) - - for _ in range(self.n_sims): - inds = [] - hallucinated_y = [] - - # Copy these objects to make sure they are not modified while simulating - # trajectories as they are used later by the main run_experiment script. - kwargs['already_selected'] = copy.copy(already_selected) - kwargs['y'] = copy.copy(y) - # Assumes that model has already by fit using all labeled data so - # the probabilities can be used immediately to hallucinate labels - kwargs['model'] = copy.deepcopy(model) - - for _ in range(n_minibatch): - batch_size = min(minibatch, N-len(inds)) - if batch_size > 0: - kwargs['N'] = batch_size - new_inds = sampler.select_batch(**kwargs) - inds.extend(new_inds) - - # All models need to have predict_proba method - probs = kwargs['model'].predict_proba(self.X[new_inds]) - # Hallucinate labels for selected datapoints to be label - # using class probabilities from model - try: - classes = kwargs['model'].best_estimator_.classes_ - except: - classes = kwargs['model'].classes_ - new_y = ([ - np.random.choice(classes, p=probs[i, :]) - for i in range(batch_size) - ]) - hallucinated_y.extend(new_y) - # Not saving already_selected here, if saving then should sort - # only for the input to fit but preserve ordering of indices in - # already_selected - kwargs['already_selected'] = sorted(kwargs['already_selected'] - + new_inds) - kwargs['y'][new_inds] = new_y - kwargs['model'].fit(self.X[kwargs['already_selected']], - kwargs['y'][kwargs['already_selected']]) - acc_hallucinated = kwargs['model'].score(X_test, y_test) - if acc_hallucinated > best_acc: - best_acc = acc_hallucinated - best_inds = inds - kwargs['model'].fit(self.X[kwargs['already_selected']], - y[kwargs['already_selected']]) - # Useful to know how accuracy compares for model trained on hallucinated - # labels vs trained on true labels. But can remove this train to speed - # up simulations. Won't speed up significantly since many more models - # are being trained inside the loop above. - acc_true = kwargs['model'].score(X_test, y_test) - results.append([acc_hallucinated, acc_true]) - print('Hallucinated acc: %.3f, Actual Acc: %.3f' % (acc_hallucinated, - acc_true)) - - # Save trajectory for reference - t = {} - t['arm'] = sampler - t['data_size'] = len(kwargs['already_selected']) - t['inds'] = inds - t['y_hal'] = hallucinated_y - t['acc_hal'] = acc_hallucinated - t['acc_true'] = acc_true - self.trace.append(t) - self.selected.extend(inds) - # Delete created copies - del kwargs['model'] - del kwargs['already_selected'] - results = np.array(results) - return np.mean(results, axis=0), best_inds, best_acc - - def sampler_select_batch(self, sampler, N, already_selected, y, model, X_test, y_test, **kwargs): - """Calculate the performance of the model if the batch had been selected using the base method without simulation. - - Args: - sampler: dict with two fields - 'samplers': list of named samplers - 'weights': percentage of batch to allocate to each sampler - N: batch size - already_selected: indices already selected - y: labels to use for training - model: model to use for training - X_test, y_test: validation set - - Returns: - - indices selected by base method - - validation accuracy of model trained on new batch - """ - m = copy.deepcopy(model) - kwargs['y'] = y - kwargs['model'] = m - kwargs['already_selected'] = copy.copy(already_selected) - inds = [] - kwargs['N'] = N - inds.extend(sampler.select_batch(**kwargs)) - kwargs['already_selected'] = sorted(kwargs['already_selected'] + inds) - - m.fit(self.X[kwargs['already_selected']], y[kwargs['already_selected']]) - acc = m.score(X_test, y_test) - del m - del kwargs['already_selected'] - return inds, acc - - def select_batch_(self, N, already_selected, y, model, - X_test, y_test, **kwargs): - """ Returns a batch of size N selected by using the best sampler in simulation - - Args: - samplers: list of sampling methods represented by dict with two fields - 'samplers': list of named samplers - 'weights': percentage of batch to allocate to each sampler - N: batch size - already_selected: indices of datapoints already labeled - y: actual labels, used to compare simulation with actual - model: training model to use to evaluate different samplers. Model must - have a predict_proba method with same signature as that in sklearn - n_sims: the number of simulations to perform for each sampler - minibatch: batch size to use for simulation - """ - - results = [] - - # THE INPUTS CANNOT BE MODIFIED SO WE MAKE COPIES FOR THE CHECK LATER - # Should check model but kernel_svm does not have coef_ so need better - # handling here - copy_selected = copy.copy(already_selected) - copy_y = copy.copy(y) - - for s in self.samplers: - sim_results, sim_inds, sim_acc = self.simulate_batch( - s, N, already_selected, y, model, X_test, y_test, **kwargs) - real_inds, acc = self.sampler_select_batch( - s, N, already_selected, y, model, X_test, y_test, **kwargs) - print('Best simulated acc: %.3f, Actual acc: %.3f' % (sim_acc, acc)) - results.append([sim_results, sim_inds, real_inds, acc]) - best_s = np.argmax([r[0][0] for r in results]) - - # Make sure that model object fed in did not change during simulations - assert all(y == copy_y) - assert all([copy_selected[i] == already_selected[i] - for i in range(len(already_selected))]) - - # Return indices based on return type specified - if self.return_type == 'best_sim': - return results[best_s][1] - elif self.return_type == 'frequency': - unique, counts = np.unique(self.selected, return_counts=True) - argcount = np.argsort(-counts) - return list(unique[argcount[0:N]]) - return results[best_s][2] - - def to_dict(self): - output = {} - output['simulated_trajectories'] = self.trace - return output diff --git a/archive/active_learning/active_learning_methods/uniform_sampling.py b/archive/active_learning/active_learning_methods/uniform_sampling.py deleted file mode 100644 index a8b3ab4e..00000000 --- a/archive/active_learning/active_learning_methods/uniform_sampling.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Uniform sampling method. - -Samples in batches. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import numpy as np - -from active_learning_methods.sampling_def import SamplingMethod - - -class UniformSampling(SamplingMethod): - - def __init__(self, X, y, seed): - self.X = X - self.y = y - self.name = 'uniform' - np.random.seed(seed) - - def select_batch_(self, already_selected, N, **kwargs): - """Returns batch of randomly sampled datapoints. - - Assumes that data has already been shuffled. - - Args: - already_selected: index of datapoints already selected - N: batch size - - Returns: - indices of points selected to label - """ - - # This is uniform given the remaining pool but biased wrt the entire pool. - - #sample = [i for i in range(self.X.shape[0]) if i not in already_selected] - #return sample[0:N] - shuffled_indices = np.random.permutation(self.X.shape[0]) - indices = [] - i = 0 - while len(indices) < N: - if shuffled_indices[i] not in already_selected: - indices.append(shuffled_indices[i]) - i += 1 - return indices diff --git a/archive/active_learning/active_learning_methods/utils/__init__.py b/archive/active_learning/active_learning_methods/utils/__init__.py deleted file mode 100644 index 3eeb3066..00000000 --- a/archive/active_learning/active_learning_methods/utils/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - diff --git a/archive/active_learning/active_learning_methods/utils/tree.py b/archive/active_learning/active_learning_methods/utils/tree.py deleted file mode 100644 index bfa59d1a..00000000 --- a/archive/active_learning/active_learning_methods/utils/tree.py +++ /dev/null @@ -1,158 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Node and Tree class to support hierarchical clustering AL method. - -Assumed to be binary tree. - -Node class is used to represent each node in a hierarchical clustering. -Each node has certain properties that are used in the AL method. - -Tree class is used to traverse a hierarchical clustering. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import copy - - -class Node(object): - """Node class for hierarchical clustering. - - Initialized with name and left right children. - """ - - def __init__(self, name, left=None, right=None): - self.name = name - self.left = left - self.right = right - self.is_leaf = left is None and right is None - self.parent = None - # Fields for hierarchical clustering AL - self.score = 1.0 - self.split = False - self.best_label = None - self.weight = None - - def set_parent(self, parent): - self.parent = parent - - -class Tree(object): - """Tree object for traversing a binary tree. - - Most methods apply to trees in general with the exception of get_pruning - which is specific to the hierarchical clustering AL method. - """ - - def __init__(self, root, node_dict): - """Initializes tree and creates all nodes in node_dict. - - Args: - root: id of the root node - node_dict: dictionary with node_id as keys and entries indicating - left and right child of node respectively. - """ - self.node_dict = node_dict - self.root = self.make_tree(root) - self.nodes = {} - self.leaves_mapping = {} - self.fill_parents() - self.n_leaves = None - - def print_tree(self, node, max_depth): - """Helper function to print out tree for debugging.""" - node_list = [node] - output = "" - level = 0 - while level < max_depth and len(node_list): - children = set() - for n in node_list: - node = self.get_node(n) - output += ("\t"*level+"node %d: score %.2f, weight %.2f" % - (node.name, node.score, node.weight)+"\n") - if node.left: - children.add(node.left.name) - if node.right: - children.add(node.right.name) - level += 1 - node_list = children - return print(output) - - def make_tree(self, node_id): - if node_id is not None: - return Node(node_id, - self.make_tree(self.node_dict[node_id][0]), - self.make_tree(self.node_dict[node_id][1])) - - def fill_parents(self): - # Setting parent and storing nodes in dict for fast access - def rec(pointer, parent): - if pointer is not None: - self.nodes[pointer.name] = pointer - pointer.set_parent(parent) - rec(pointer.left, pointer) - rec(pointer.right, pointer) - rec(self.root, None) - - def get_node(self, node_id): - return self.nodes[node_id] - - def get_ancestor(self, node): - ancestors = [] - if isinstance(node, int): - node = self.get_node(node) - while node.name != self.root.name: - node = node.parent - ancestors.append(node.name) - return ancestors - - def fill_weights(self): - for v in self.node_dict: - node = self.get_node(v) - node.weight = len(self.leaves_mapping[v]) / (1.0 * self.n_leaves) - - def create_child_leaves_mapping(self, leaves): - """DP for creating child leaves mapping. - - Storing in dict to save recompute. - """ - self.n_leaves = len(leaves) - for v in leaves: - self.leaves_mapping[v] = [v] - node_list = set([self.get_node(v).parent for v in leaves]) - while node_list: - to_fill = copy.copy(node_list) - for v in node_list: - if (v.left.name in self.leaves_mapping - and v.right.name in self.leaves_mapping): - to_fill.remove(v) - self.leaves_mapping[v.name] = (self.leaves_mapping[v.left.name] + - self.leaves_mapping[v.right.name]) - if v.parent is not None: - to_fill.add(v.parent) - node_list = to_fill - self.fill_weights() - - def get_child_leaves(self, node): - return self.leaves_mapping[node] - - def get_pruning(self, node): - if node.split: - return self.get_pruning(node.left) + self.get_pruning(node.right) - else: - return [node.name] - diff --git a/archive/active_learning/active_learning_methods/utils/tree_test.py b/archive/active_learning/active_learning_methods/utils/tree_test.py deleted file mode 100644 index a9e33dcb..00000000 --- a/archive/active_learning/active_learning_methods/utils/tree_test.py +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Tests for sampling_methods.utils.tree.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import unittest -from active_learning__methods.utils import tree - - -class TreeTest(unittest.TestCase): - - def setUp(self): - node_dict = { - 1: (2, 3), - 2: (4, 5), - 3: (6, 7), - 4: [None, None], - 5: [None, None], - 6: [None, None], - 7: [None, None] - } - self.tree = tree.Tree(1, node_dict) - self.tree.create_child_leaves_mapping([4, 5, 6, 7]) - node = self.tree.get_node(1) - node.split = True - node = self.tree.get_node(2) - node.split = True - - def assertNode(self, node, name, left, right): - self.assertEqual(node.name, name) - self.assertEqual(node.left.name, left) - self.assertEqual(node.right.name, right) - - def testTreeRootSetCorrectly(self): - self.assertNode(self.tree.root, 1, 2, 3) - - def testGetNode(self): - node = self.tree.get_node(1) - assert isinstance(node, tree.Node) - self.assertEqual(node.name, 1) - - def testFillParent(self): - node = self.tree.get_node(3) - self.assertEqual(node.parent.name, 1) - - def testGetAncestors(self): - ancestors = self.tree.get_ancestor(5) - self.assertTrue(all([a in ancestors for a in [1, 2]])) - - def testChildLeaves(self): - leaves = self.tree.get_child_leaves(3) - self.assertTrue(all([c in leaves for c in [6, 7]])) - - def testFillWeights(self): - node = self.tree.get_node(3) - self.assertEqual(node.weight, 0.5) - - def testGetPruning(self): - node = self.tree.get_node(1) - pruning = self.tree.get_pruning(node) - self.assertTrue(all([n in pruning for n in [3, 4, 5]])) - -if __name__ == '__main__': - unittest.main() diff --git a/archive/active_learning/active_learning_methods/wrapper_sampler_def.py b/archive/active_learning/active_learning_methods/wrapper_sampler_def.py deleted file mode 100644 index 55a23843..00000000 --- a/archive/active_learning/active_learning_methods/wrapper_sampler_def.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2017 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Abstract class for wrapper sampling methods that call base sampling methods. - -Provides interface to sampling methods that allow same signature -for select_batch. Each subclass implements select_batch_ with the desired -signature for readability. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import abc - -from active_learning_methods.constants import AL_MAPPING -from active_learning_methods.constants import get_all_possible_arms -from active_learning_methods.sampling_def import SamplingMethod - -get_all_possible_arms() - - -class WrapperSamplingMethod(SamplingMethod): - __metaclass__ = abc.ABCMeta - - def initialize_samplers(self, mixtures): - methods = [] - for m in mixtures: - methods += m['methods'] - methods = set(methods) - self.base_samplers = {} - for s in methods: - self.base_samplers[s] = AL_MAPPING[s](self.X, self.y, self.seed) - self.samplers = [] - for m in mixtures: - self.samplers.append( - AL_MAPPING['mixture_of_samplers'](self.X, self.y, self.seed, m, - self.base_samplers)) diff --git a/archive/active_learning/archive/UI.py b/archive/active_learning/archive/UI.py deleted file mode 100644 index b14d54fa..00000000 --- a/archive/active_learning/archive/UI.py +++ /dev/null @@ -1,181 +0,0 @@ -import sys -#from PyQt5 import QtCore, QtWidgets,QtGui -from PyQt5.QtWidgets import QTabWidget, QMainWindow, QApplication, QSizePolicy, QWidget, QPushButton, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QProgressBar, QInputDialog -from PyQt5.QtCore import Qt,QPoint, pyqtSignal, QRect, QSize, QStringListModel -from PyQt5.QtGui import QColor, QCursor, QPainterPath, QBrush, QPen -from enum import Enum -#from collections import deque -#from peewee import * -from UIComponents.DBObjects import * -from DL.utils import * -from DL.networks import * -#from UIComponents.Tag import Tag -from UIComponents.GridWidget import GridWidget -from UIComponents.SpeciesWidget import SpeciesWidget -from DL.sqlite_data_loader import SQLDataLoader -from DL.Engine import Engine -import os -from multiprocessing import Process - -from sklearn.cluster import DBSCAN, MiniBatchKMeans -from sklearn.externals.joblib import parallel_backend -from sklearn.metrics import pairwise_distances_argmin_min - - -class UI(QTabWidget): - - def __init__(self): - super(UI, self).__init__() - policy = self.sizePolicy() - policy.setHorizontalPolicy(QSizePolicy.Fixed) - policy.setVerticalPolicy(QSizePolicy.Fixed) - #policy.setHeightForWidth(True) - - self.initUI() - self.currentChanged.connect(self.onChange) - - def onChange(event): - if event.currentIndex() in (0,1,2): - event.currentWidget().showCurrentPage() - - def initUI(self): - global species - species = QStringListModel() - speciesList=[] - all_species= Category.select() - for x in all_species: - speciesList.append(x.name) - speciesList.append("Add New") - species.setStringList(speciesList) - - prefered_image_width=(0.7*app.primaryScreen().size().width())/4 - self.tab1 = GridWidget(DetectionKind.ActiveDetection, prefered_image_width, num_cols=3, labeler=True) - self.tab2 = GridWidget(DetectionKind.UserDetection, prefered_image_width) - self.tab3 = GridWidget(DetectionKind.ModelDetection, prefered_image_width) - self.tab4 = SpeciesWidget() - self.setWindowTitle( 'Labeler' ) - self.addTab(self.tab1,"Unlabeled")# ("+str(len(self.unlabeled))+")") - self.addTab(self.tab2,"User Labeled")# ("+str(len(self.labeled))+")") - self.addTab(self.tab3,"Model Labeled")# ("+str(species.rowCount()-1)+")") - self.addTab(self.tab4,"Species")# ("+str(species.rowCount()-1)+")") - #print(self.tab1.parentWidget(),self) - self.tab1.confirm = QPushButton('Confirm Images') - self.tab1.start = QPushButton('Start Learning') - self.tab1.horizontalLayout.addWidget(self.tab1.confirm) - self.tab1.horizontalLayout.addWidget(self.tab1.start) - - self.setWindowTitle( 'Labeler' ) - self.tab1.confirm.clicked.connect(self.confirm) - self.tab1.start.clicked.connect(self.active) - #self.tab4.add.clicked.connect(self.addSpecies) - #self.tab4.update.clicked.connect(self.updateSpecies) - - def confirm(self,event): - for i in range(self.tab1.num_rows): - for j in range(self.tab1.num_cols): - index= i*self.tab1.num_cols+j - final= self.tab1.images[index].getFinal() - for label in final[1]: - det= Detection.get(Detection.id==label[1][0]) - det.category= label[0] - det.kind= DetectionKind.UserDetection.value - det.save() - self.tab1.page=1 - self.tab1.showCurrentPage() - - - def active(self,event): - self.parentWidget().statusBar().showMessage("Start Learning") - #checkpoint= load_checkpoint('../merge/triplet_model_0054.tar') - run_dataset = SQLDataLoader(DetectionKind.ModelDetection, "/home/pangolin/all_crops/SS_full_crops", False, num_workers= 8, batch_size= 2048) - #run_dataset.setup(Detection.select(Detection.id,Category.id).join(Category).where(Detection.kind==DetectionKind.ModelDetection.value).limit(250000)) - num_classes= len(run_dataset.getClassesInfo()) - print("Num Classes= "+str(num_classes)) - run_loader = run_dataset.getSingleLoader() - run_dataset.setDatatype('embedding') - #embedding_net = EmbeddingNet(checkpoint['arch'], checkpoint['feat_dim']) - #if checkpoint['loss_type'].lower()=='center': - # model = torch.nn.DataParallel(ClassificationNet(embedding_net, n_classes=14)).cuda() - #else: - # model= torch.nn.DataParallel(embedding_net).cuda() - #model.load_state_dict(checkpoint['state_dict']) - #self.parentWidget().progressBar.setMaximum(len(run_dataset)//2048) - #e=Engine(model,None,None, verbose=True,progressBar= self.parentWidget().progressBar) - self.parentWidget().statusBar().showMessage("Extract Embeddings") - embd = np.asarray([ np.fromstring(x[2], dtype='0.7 and self.getDistance(embd,selected_set,rand_ind[i])>0.7: - indices.add(rand_ind[i]) - self.moveRecords(DetectionKind.ModelDetection, DetectionKind.ActiveDetection, [paths[i] for i in indices.difference(selected_set)]) - selected_set= selected_set.union(indices) - #print(indices,selected_set) - return selected_set - - def getDistance(self,embd,archive,sample): - if len(archive)==0: - return 100 - else: - return pairwise_distances_argmin_min(embd[sample].reshape(1, -1),embd[np.asarray(list(archive),dtype=np.int32)])[1] - - def moveRecords(self,srcKind,destKind,rList): - query= Detection.update(kind=destKind.value).where(Detection.id.in_(rList), Detection.kind==srcKind.value) - #print(query.sql()) - query.execute() - #src.delete().where(src.image_id<")].strip()+'_set').lower() - self.images=[None]*(self.num_rows*self.num_cols) - self.updated= True - self.num_pages = 1 - for i in range(self.num_rows): - for j in range(self.num_cols): - index= i*self.num_cols+j - self.images[index] = ImageView(im_width, labeler) - self.gridLayout.addWidget(self.images[index], i, j) - - self.next = QPushButton('Next' ) - self.previous = QPushButton('Previous' ) - self.last = QPushButton('Last' ) - self.first = QPushButton('First' ) - self.of = QLabel('of' ) - self.total = QLabel() - self.current= QLineEdit("1") - self.current.setValidator(QIntValidator()); - self.horizontalLayout = QHBoxLayout() - self.horizontalLayout.addWidget(self.first) - self.horizontalLayout.addWidget(self.previous) - self.horizontalLayout.addWidget(self.current) - self.horizontalLayout.addWidget(self.of) - self.horizontalLayout.addWidget(self.total) - self.horizontalLayout.addWidget(self.next) - self.horizontalLayout.addWidget(self.last) - self.verticalLayout = QVBoxLayout() - self.verticalLayout.addLayout( self.gridLayout, stretch=1) - self.verticalLayout.addLayout( self.horizontalLayout, stretch=0) - self.labeler= labeler - if labeler: - self.horizontalLayout2 = QHBoxLayout() - self.horizontalLayout2.addLayout(self.verticalLayout) - self.refreshLabeler() - self.setLayout(self.horizontalLayout2) - else: - self.setLayout(self.verticalLayout) - self.updatePages() - self.next.clicked.connect(self.doNext) - self.previous.clicked.connect(self.doPrevious) - self.last.clicked.connect(self.doLast) - self.first.clicked.connect(self.doFirst) - self.current.returnPressed.connect(self.doJump) - - - def refreshLabeler(self): - if hasattr(self, "labelerGrid"): - for i in reversed(range(self.labelerGrid.count())): - self.labelerGrid.itemAt(i).widget().deleteLater() - - self.labelerGrid.deleteLater() - self.labelerGrid.setParent(None) - self.horizontalLayout2.removeItem(self.labelerGrid) - del self.labelerGrid - self.labelerGrid= QGridLayout() - query= Category.select() - i=0 - for cat in query: - button= QPushButton(cat.name) - button.clicked.connect(self.clickgen(cat)) - self.labelerGrid.addWidget(button, i//2,i%2) - i+=1 - button= QPushButton("Delete") - button.clicked.connect(self.delete) - self.labelerGrid.addWidget(button, i//2,i%2) - self.horizontalLayout2.addLayout(self.labelerGrid) - self.update() - - def clickgen(self,cat): - def labelerClicked(event): - for i in range(self.num_rows): - for j in range(self.num_cols): - index= i*self.num_cols+j - ex_label= self.images[index] - for label in ex_label.tags: - if label.tik.isVisible() and label.tik.checkState(): - label.updateLabel(cat) - return labelerClicked - - def delete(self,event): - for i in range(self.num_rows): - for j in range(self.num_cols): - index= i*self.num_cols+j - ex_label= self.images[index] - for tag in ex_label.tags: - if tag.tik.isVisible() and tag.tik.checkState(): - tag.setParent(None) - ex_label.tags.remove(tag) - - - def updatePages(self): - count= Image.select().join(Detection).where(Detection.kind==self.kind.value).count() - self.num_pages= (count//(self.num_rows*self.num_cols))+1 - self.total.setText(str(self.num_pages)) - self.current.setText(str(self.page)) - if self.page==1: - self.previous.setEnabled(False) - else: - self.previous.setEnabled(True) - if self.page==self.num_pages: - self.next.setEnabled(False) - else: - self.next.setEnabled(True) - - def doNext(self,event): - if self.page<=self.num_pages: - self.page+=1 - self.updated= True - self.showCurrentPage() - self.updatePages() - - def doPrevious(self,event): - if self.page>1: - self.page-=1 - self.updated= True - self.showCurrentPage() - self.updatePages() - - def doLast(self,event): - self.page= self.num_pages - self.updated= True - self.showCurrentPage() - self.updatePages() - - def doFirst(self,event): - self.page=1 - self.updated= True - self.showCurrentPage() - self.updatePages() - - def doJump(self): - val= int(self.current.text()) - if val>=1 and val<=self.num_pages: - self.page= val - self.updated= True - self.showCurrentPage() - else: - self.current.setText("1") - self.updatePages() - - def showCurrentPage(self, force=False): - if self.labeler: - self.refreshLabeler() - if self.updated or force: - #print("Parent", self.parentWidget().width(), self.parentWidget().height()) - self.clear() - query = Image.select().join(Detection).where(Detection.kind==self.kind.value).paginate(self.page, self.num_rows*self.num_cols) - #print(self.model,self.name,query.sql()) - index= 0 - for r in query: - self.images[index].setSample(r.id,os.path.join('/project/evolvingai/mnorouzz/Serengiti/SER',r.file_name),r.detection_set) - index+=1 - self.updated=False - - def clear(self): - self.visited= False - for i in range(self.num_rows): - for j in range(self.num_cols): - index= i*self.num_cols+j - self.images[index].clear() - diff --git a/archive/active_learning/archive/UIComponents/ImageView.py b/archive/active_learning/archive/UIComponents/ImageView.py deleted file mode 100644 index 683e1e6c..00000000 --- a/archive/active_learning/archive/UIComponents/ImageView.py +++ /dev/null @@ -1,69 +0,0 @@ -from PyQt5.QtWidgets import QLabel, QSizePolicy, QMenu -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPixmap - -from .Tag import Tag - -class ImageView(QLabel): - def __init__(self, width, editable=False): - super(QLabel, self).__init__() - self.path= None - self.image_id= None - self.setAlignment(Qt.AlignTop); - self.corner_y= 0 - self.tags= [] - self.prefered_width= width - self.editable= editable - #self.resizeEvent=self.onResize - #print("Parent", parent, parent.width(), parent.height()) - #self.setGeometry(0,0,410,307) - self.setSizePolicy(QSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed)) - - def onResize(self, event): - print(self, self.width(), self.height(), event.oldSize()) - - def setSample(self, image_id, path, tags): - self.clear() - self.path= path - self.image_id= image_id - self.icon= QPixmap(self.path)#,Qt.KeepAspectRatio); - w = self.prefered_width - h = w*(self.icon.height()/self.icon.width())#self.height() - #print(w,h,"w,h") - self.icon= self.icon.scaled(w,h) - self.setPixmap(self.icon) - for label in tags: - self.tags.append(Tag(self, label.id, label.category, [label.bbox_X1, label.bbox_Y1, label.bbox_X2, label.bbox_Y2], self.editable)) - - def clear(self): - if self.path is not None: - self.path= None - for t in self.tags: - t.setParent(None) - self.tags.clear() - self.icon= QPixmap() - self.setPixmap(self.icon) - - def getFinal(self): - tags=[] - for tag in self.tags: - #print("final",tag.getFinal()) - tags.append((tag.label,tag.getFinal())) - return (self.image_id, tags) - - def contextMenuEvent(self, event): - - menu = QMenu(self) - if self.editable: - quitAction = menu.addAction("Add Tag") - action = menu.exec_(self.mapToGlobal(event.pos())) - if action == quitAction: - self.tags.append(Tag(self,Category.get(-1),[0,0,0.1,0.1],True, Qt.red)) - #pass - else: - resetAction = menu.addAction("Reset Image") - action = menu.exec_(self.mapToGlobal(event.pos())) - if action == resetAction: - #self.tags.append(TContainer(self,Category.get(-1),[0,0,0.1,0.1],True, Qt.red)) - pass - diff --git a/archive/active_learning/archive/UIComponents/SpeciesWidget.py b/archive/active_learning/archive/UIComponents/SpeciesWidget.py deleted file mode 100644 index f96a003e..00000000 --- a/archive/active_learning/archive/UIComponents/SpeciesWidget.py +++ /dev/null @@ -1,97 +0,0 @@ -from PyQt5.QtWidgets import QTabWidget, QMainWindow, QApplication, QSizePolicy, QWidget, QPushButton, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QProgressBar, QMessageBox -from PyQt5.QtCore import Qt -from .DBObjects import * - - -class SpeciesWidget(QWidget): - - def __init__(self): - super(SpeciesWidget, self).__init__() - self.verticalLayout= QVBoxLayout(self) - self.speciesList= QTableWidget() - self.speciesList.setRowCount(Category.select().count()) - self.speciesList.setColumnCount(3) - self.speciesList.verticalHeader().setVisible(False) - self.speciesList.setHorizontalHeaderItem(0,QTableWidgetItem("ID")) - self.speciesList.setHorizontalHeaderItem(1,QTableWidgetItem("Name")) - self.speciesList.setHorizontalHeaderItem(2,QTableWidgetItem("Short Name")) - self.id_dict={} - for i, record in enumerate(Category.select().order_by(Category.id)): - id_item= QTableWidgetItem(str(record.id)) - id_item.setFlags(id_item.flags() ^ Qt.ItemIsEditable) - self.speciesList.setItem(i,0, id_item) - self.speciesList.setItem(i,1, QTableWidgetItem(record.name)) - self.speciesList.setItem(i,2, QTableWidgetItem(record.abbr)) - self.id_dict[i]=record.id - #self.tab4.speciesList.setModel(species) - #self.tab4.speciesList.setRowHidden(len(species.stringList())-1, True) - self.insert = QPushButton('Add New' ) - self.delete = QPushButton('Delete Current Row') - self.save = QPushButton('Save Changes' ) - self.horizontalLayout= QHBoxLayout() - self.horizontalLayout.addWidget(self.insert) - self.horizontalLayout.addWidget(self.delete) - self.horizontalLayout.addWidget(self.save) - self.verticalLayout.addWidget(self.speciesList) - self.verticalLayout.addLayout(self.horizontalLayout) - self.insert.clicked.connect(self.addSpecies) - self.delete.clicked.connect(self.deleteSpecies) - self.save.clicked.connect(self.syncSpecies) - #self.speciesList.itemChanged.connect(self.itemChanged) - - def addSpecies(self, event): - rowPosition = self.speciesList.rowCount() - self.speciesList.insertRow(rowPosition) - self.speciesList.setCurrentItem(self.speciesList.item(rowPosition,0)) - - def validate(self): - allRows = self.speciesList.rowCount() - itemsList=[] - id_set= set() - name_set= set() - shortname_set= set() - for row in range(allRows): - id_item= self.speciesList.item(row,0) - rowid = int(id_item.text()) - rowname = self.speciesList.item(row,1).text() - rowshortname = self.speciesList.item(row,2).text() - if rowid is None or rowid in id_set: - return "There is an issue with %s at row %d"%(rowid,rowid), itemsList - if rowname is None or rowname in name_set: - return "There is an issue with %s at row %d"%(rowname,rowid), itemsList - if rowshortname is None or rowshortname in shortname_set or len(rowshortname)>2: - return "There is an issue with %s at row %d"%(rowshortname,rowid), itemsList - id_set.add(rowid) - name_set.add(rowname) - shortname_set.add(rowshortname) - itemsList.append((rowid,rowname,rowshortname,row, id_item)) - return None,itemsList - - def syncSpecies(self, event): - val, itemsList= self.validate() - if val is None: - for x in itemsList: - species= Category.get_or_create(id=x[0], name= x[1], abbr=x[2]) - x[4].setFlags(x[4].flags() ^ Qt.ItemIsEditable) - Category.delete().where(Category.id.not_in([x[0] for x in itemsList])).execute() - msg = QMessageBox() - msg.setIcon(QMessageBox.Information) - msg.setText("The changes are successfully saved!") - msg.setWindowTitle("Save Completed") - msg.setInformativeText(val) - msg.setStandardButtons(QMessageBox.Ok) - msg.exec_() - - else: - msg = QMessageBox() - msg.setIcon(QMessageBox.Critical) - msg.setText("The entered data contain an invalid item") - msg.setWindowTitle("Invalid Data") - msg.setDetailedText(val) - msg.setStandardButtons(QMessageBox.Ok) - msg.exec_() - - def deleteSpecies(self,event): - selected = self.speciesList.currentRow() - self.speciesList.removeRow(selected) - diff --git a/archive/active_learning/archive/UIComponents/Tag.py b/archive/active_learning/archive/UIComponents/Tag.py deleted file mode 100644 index 85dda3b0..00000000 --- a/archive/active_learning/archive/UIComponents/Tag.py +++ /dev/null @@ -1,315 +0,0 @@ -import sys -from PyQt5 import QtCore, QtGui -from PyQt5.QtWidgets import QWidget, QMenu, QCheckBox, QLabel, QApplication -from PyQt5.QtCore import Qt,QPoint, pyqtSignal, QRect, QSize -from PyQt5.QtGui import QColor, QCursor, QPainterPath, QBrush, QPen -from enum import Enum - -class Mode(Enum): - NONE = 0, - MOVE = 1, - RESIZETL = 2, - RESIZET = 3, - RESIZETR = 4, - RESIZER = 5, - RESIZEBR = 6, - RESIZEB = 7, - RESIZEBL = 8, - RESIZEL = 9 - -class Tag(QWidget): - """ allow to move and resize by user""" - menu = None - mode = Mode.NONE - position = None - inFocus = pyqtSignal(bool) - outFocus = pyqtSignal(bool) - newGeometry = pyqtSignal(QRect) - - def __init__(self, parent,detection_id, label, bbox, editable= False, color= None): - super().__init__(parent=parent) - #combo.setEnabled(not finalized) - self.menu = QMenu(parent=self, title='menu') - self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) - self.move(QPoint(0,0)) - - self.m_infocus = True - self.m_showMenu = False - self.m_isEditing = editable - self.installEventFilter(parent) - self.bbox= bbox - self.detection_id= detection_id - - x= round(bbox[1]*self.parentWidget().pixmap().width()) - y= round(bbox[0]*self.parentWidget().pixmap().height()) - w= round(bbox[3]*self.parentWidget().pixmap().width()-x) - h= round(bbox[2]*self.parentWidget().pixmap().height()- y) - self.setGeometry(x,y,w,h) - self.tik= QCheckBox(self) - self.tik.move(0,0) - self.title= QLabel(self) - self.title.move(15,0) - self.title.setStyleSheet("QLabel { color : white; font-weight: bold; }") - if not editable: - self.tik.hide() - self.title.move(0,0) - self.updateLabel(label) - self.pen= QPen() - self.pen.setStyle(Qt.SolidLine) - self.pen.setWidth(3) - self.pen.setBrush(self.color) - - #print(self.bbox) - self.setVisible(True) - self.setAutoFillBackground(False) - self.setMouseTracking(True) - self.setFocusPolicy(QtCore.Qt.ClickFocus) - self.setFocus() - - """def resizeEvent(self, event): - print("resize invalidate",event.oldSize()) - self.valid= False - - def moveEvent(self, event): - print("move invalidate", event.oldPos()) - self.valid= False""" - def updateLabel(self, label): - self.label= label - self.title.setText(self.label.name) - if self.width()<40: - self.title.setText(self.label.name[0:2]) - if self.m_isEditing: - self.title.move(0,15) - if self.label.id==-1: - self.color=Qt.red - else: - self.color=Qt.green - if hasattr(self,'pen'): - self.pen.setBrush(self.color) - self.tik.setCheckState(False) - #self.addWidget(self.child) - self.update() - - def getFinal(self): - y= self.x()/self.parentWidget().pixmap().width() - x= self.y()/self.parentWidget().pixmap().height() - h= (self.x()+self.width())/self.parentWidget().pixmap().width() - w= (self.y()+self.height())/self.parentWidget().pixmap().height() - if abs(x-self.bbox[0])<0.01 and abs(x-self.bbox[0])<0.01 and abs(x-self.bbox[0])<0.01 and abs(x-self.bbox[0])<0.01: - return self.detection_id, (self.bbox[0],self.bbox[1],self.bbox[2],self.bbox[3]) - else: - return self.detection_id, (x,y,w,h) - - def speciesChanged(self, text): - if text=='Add New': - self.parentWidget().parentWidget().parentWidget().parentWidget().setCurrentIndex(2) - else: - self.label= text - - """def setChildWidget(self, cWidget): - if cWidget: - self.childWidget = cWidget - self.childWidget.setParent(self) - self.childWidget.setMinimumSize(70,20) - self.childWidget.move(0,0)""" - - def popupShow(self, pt: QPoint): - if self.menu.isEmpty: - return - global_ = self.mapToGlobal(pt) - self.m_showMenu = True - self.menu.exec(global_) - self.m_showMenu = False - - def contextMenuEvent(self, event): - - menu = QMenu(self) - quitAction = menu.addAction("Delete Tag") - action = menu.exec_(self.mapToGlobal(event.pos())) - if action == quitAction: - self.parentWidget().tags.remove(self) - self.setParent(None) - - def focusInEvent(self, a0: QtGui.QFocusEvent): - self.m_infocus = True - p = self.parentWidget() - p.installEventFilter(self) - p.repaint() - self.inFocus.emit(True) - - def focusOutEvent(self, a0: QtGui.QFocusEvent): - if not self.m_isEditing: - return - if self.m_showMenu: - return - self.mode = Mode.NONE - self.outFocus.emit(False) - self.m_infocus = False - - def paintEvent(self, e: QtGui.QPaintEvent): - painter = QtGui.QPainter(self) - rect = e.rect() - rect.adjust(0,0,-1,-1) - painter.setPen(self.pen) - painter.drawRect(rect) - - def mousePressEvent(self, e: QtGui.QMouseEvent): - self.position = QPoint(e.globalX() - self.geometry().x(), e.globalY() - self.geometry().y()) - if not self.m_isEditing: - return - if not self.m_infocus: - return - if not e.buttons() and QtCore.Qt.LeftButton: - self.setCursorShape(e.pos()) - return - if e.button() == QtCore.Qt.RightButton: - self.popupShow(e.pos()) - e.accept() - - def keyPressEvent(self, e: QtGui.QKeyEvent): - if not self.m_isEditing: return - if e.key() == QtCore.Qt.Key_Delete: - self.deleteLater() - # Moving container with arrows - if QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier: - newPos = QPoint(self.x(), self.y()) - if e.key() == QtCore.Qt.Key_Up: - newPos.setY(newPos.y() - 1) - if e.key() == QtCore.Qt.Key_Down: - newPos.setY(newPos.y() + 1) - if e.key() == QtCore.Qt.Key_Left: - newPos.setX(newPos.x() - 1) - if e.key() == QtCore.Qt.Key_Right: - newPos.setX(newPos.x() + 1) - self.move(newPos) - - if QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: - if e.key() == QtCore.Qt.Key_Up: - self.resize(self.width(), self.height() - 1) - if e.key() == QtCore.Qt.Key_Down: - self.resize(self.width(), self.height() + 1) - if e.key() == QtCore.Qt.Key_Left: - self.resize(self.width() - 1, self.height()) - if e.key() == QtCore.Qt.Key_Right: - self.resize(self.width() + 1, self.height()) - self.newGeometry.emit(self.geometry()) - - - def setCursorShape(self, e_pos: QPoint): - diff = 3 - # Left - Bottom - - if (((e_pos.y() > self.y() + self.height() - diff) and # Bottom - (e_pos.x() < self.x() + diff)) or # Left - # Right-Bottom - ((e_pos.y() > self.y() + self.height() - diff) and # Bottom - (e_pos.x() > self.x() + self.width() - diff)) or # Right - # Left-Top - ((e_pos.y() < self.y() + diff) and # Top - (e_pos.x() < self.x() + diff)) or # Left - # Right-Top - (e_pos.y() < self.y() + diff) and # Top - (e_pos.x() > self.x() + self.width() - diff)): # Right - # Left - Bottom - if ((e_pos.y() > self.y() + self.height() - diff) and # Bottom - (e_pos.x() < self.x() - + diff)): # Left - self.mode = Mode.RESIZEBL - self.setCursor(QCursor(QtCore.Qt.SizeBDiagCursor)) - # Right - Bottom - if ((e_pos.y() > self.y() + self.height() - diff) and # Bottom - (e_pos.x() > self.x() + self.width() - diff)): # Right - self.mode = Mode.RESIZEBR - self.setCursor(QCursor(QtCore.Qt.SizeFDiagCursor)) - # Left - Top - if ((e_pos.y() < self.y() + diff) and # Top - (e_pos.x() < self.x() + diff)): # Left - self.mode = Mode.RESIZETL - self.setCursor(QCursor(QtCore.Qt.SizeFDiagCursor)) - # Right - Top - if ((e_pos.y() < self.y() + diff) and # Top - (e_pos.x() > self.x() + self.width() - diff)): # Right - self.mode = Mode.RESIZETR - self.setCursor(QCursor(QtCore.Qt.SizeBDiagCursor)) - # check cursor horizontal position - elif ((e_pos.x() < self.x() + diff) or # Left - (e_pos.x() > self.x() + self.width() - diff)): # Right - if e_pos.x() < self.x() + diff: # Left - self.setCursor(QCursor(QtCore.Qt.SizeHorCursor)) - self.mode = Mode.RESIZEL - else: # Right - self.setCursor(QCursor(QtCore.Qt.SizeHorCursor)) - self.mode = Mode.RESIZER - # check cursor vertical position - elif ((e_pos.y() > self.y() + self.height() - diff) or # Bottom - (e_pos.y() < self.y() + diff)): # Top - if e_pos.y() < self.y() + diff: # Top - self.setCursor(QCursor(QtCore.Qt.SizeVerCursor)) - self.mode = Mode.RESIZET - else: # Bottom - self.setCursor(QCursor(QtCore.Qt.SizeVerCursor)) - self.mode = Mode.RESIZEB - else: - self.setCursor(QCursor(QtCore.Qt. ArrowCursor)) - self.mode = Mode.MOVE - - - def mouseReleaseEvent(self, e: QtGui.QMouseEvent): - QWidget.mouseReleaseEvent(self, e) - - def mouseMoveEvent(self, e: QtGui.QMouseEvent): - QWidget.mouseMoveEvent(self, e) - if not self.m_isEditing: - return - if not self.m_infocus: - return - if not e.buttons() and QtCore.Qt.LeftButton: - p = QPoint(e.x() + self.geometry().x(), e.y() + self.geometry().y()) - self.setCursorShape(p) - return - - if (self.mode == Mode.MOVE or self.mode == Mode.NONE) and e.buttons() and QtCore.Qt.LeftButton: - toMove = e.globalPos() - self.position - if toMove.x() < 0:return - if toMove.y() < 0:return - if toMove.x() > self.parentWidget().width() - self.width(): return - self.move(toMove) - self.newGeometry.emit(self.geometry()) - self.parentWidget().repaint() - return - if (self.mode != Mode.MOVE) and e.buttons() and QtCore.Qt.LeftButton: - if self.mode == Mode.RESIZETL: # Left - Top - newwidth = e.globalX() - self.position.x() - self.geometry().x() - newheight = e.globalY() - self.position.y() - self.geometry().y() - toMove = e.globalPos() - self.position - self.resize(self.geometry().width() - newwidth, self.geometry().height() - newheight) - self.move(toMove.x(), toMove.y()) - elif self.mode == Mode.RESIZETR: # Right - Top - newheight = e.globalY() - self.position.y() - self.geometry().y() - toMove = e.globalPos() - self.position - self.resize(e.x(), self.geometry().height() - newheight) - self.move(self.x(), toMove.y()) - elif self.mode== Mode.RESIZEBL: # Left - Bottom - newwidth = e.globalX() - self.position.x() - self.geometry().x() - toMove = e.globalPos() - self.position - self.resize(self.geometry().width() - newwidth, e.y()) - self.move(toMove.x(), self.y()) - elif self.mode == Mode.RESIZEB: # Bottom - self.resize(self.width(), e.y()) - elif self.mode == Mode.RESIZEL: # Left - newwidth = e.globalX() - self.position.x() - self.geometry().x() - toMove = e.globalPos() - self.position - self.resize(self.geometry().width() - newwidth, self.height()) - self.move(toMove.x(), self.y()) - elif self.mode == Mode.RESIZET:# Top - newheight = e.globalY() - self.position.y() - self.geometry().y() - toMove = e.globalPos() - self.position - self.resize(self.width(), self.geometry().height() - newheight) - self.move(self.x(), toMove.y()) - elif self.mode == Mode.RESIZER: # Right - self.resize(e.x(), self.height()) - elif self.mode == Mode.RESIZEBR:# Right - Bottom - self.resize(e.x(), e.y()) - self.parentWidget().repaint() - self.newGeometry.emit(self.geometry()) - diff --git a/archive/active_learning/archive/UIComponents/__init__.py b/archive/active_learning/archive/UIComponents/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/archive/active_learning/archive/UIComponents/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/archive/active_learning/archive/data_loader.py b/archive/active_learning/archive/data_loader.py deleted file mode 100644 index c4e2ef11..00000000 --- a/archive/active_learning/archive/data_loader.py +++ /dev/null @@ -1,139 +0,0 @@ -from torch.utils.data import Dataset -from torchvision.datasets import ImageFolder -from torchvision.transforms import * -from torch.utils.data import DataLoader -from torch.utils.data.sampler import BatchSampler - -import numpy as np -import matplotlib.pyplot as plt -import os -import random -from PIL import ImageStat - -def normalize(X): - mean= X.view(3,-1).mean(1) - std= X.view(3,-1).std(1) - for t, m, s in zip(X, mean, std): - t.sub_(m).div_(s) - return X - -class BaseDataLoader(ImageFolder): - - def __init__(self, base_folder, is_training, batch_size=None, shuffle=None, num_workers=8, raw_size=[256,256], processed_size=[224,224]): - self.base_folder= base_folder - super().__init__(base_folder) - self.is_training= is_training - if batch_size is None: - self.batch_size= 128 if self.is_training else 512 - else: - self.batch_size= batch_size - self.shuffle= shuffle if shuffle is not None else is_training - self.num_workers= num_workers - transform_list=[] - transform_list.append(Resize(raw_size)) - if self.is_training: - transform_list.append(RandomCrop(processed_size)) - transform_list.append(RandomHorizontalFlip()) - transform_list.append(ColorJitter()) - transform_list.append(RandomRotation(20)) - #transform_list.append(CenterCrop((processed_size))) - else: - transform_list.append(CenterCrop((processed_size))) - transform_list.append(ToTensor()) - mean, std= self.calc_mean_std() - transform_list.append(Normalize(mean,std)) - #transform_list.append(Lambda(lambda X: normalize(X))) - self.transform = transforms.Compose(transform_list) - - - def calc_mean_std(self): - - cache_file= self.base_folder+"/.meanstd"+".cache" - if not os.path.exists(cache_file): - print("Calculating Mean and Std") - means= np.zeros((3)) - stds = np.zeros((3)) - sample_size= min(len(self.samples), 10000) - for i in range(sample_size): - img = self.loader(random.choice(self.samples)[0]) - stat = ImageStat.Stat(img) - means+= np.array(stat.mean)/255.0 - stds+= np.array(stat.stddev)/255.0 - means= means/sample_size - stds= stds/sample_size - np.savetxt(cache_file, np.vstack((means, stds))) - else: - print("Load Mean and Std from "+cache_file) - contents= np.loadtxt(cache_file) - means= contents[0,:] - stds= contents[1,:] - - return means, stds - - - def __getitem__(self, index): - """ - Args: - index (int): Index - Returns: - tuple: (sample, target) where target is class_index of the target class. - """ - path, target = self.samples[index] - sample = self.loader(path) - if self.transform is not None: - sample = self.transform(sample) - if self.target_transform is not None: - target = self.target_transform(target) - - return sample, target, path - - def getClassesInfo(self): - return self.classes, self.class_to_idx - - def getBalancedLoader(self, P=14, K=10): - train_batch_sampler = BalancedBatchSampler(self, n_classes=P, n_samples=K) - return DataLoader(self, batch_sampler=train_batch_sampler, num_workers= self.num_workers) - - def getSingleLoader(self): - return DataLoader(self, batch_size= self.batch_size, shuffle= self.shuffle, num_workers= self.num_workers) - - -class BalancedBatchSampler(BatchSampler): - """ - BatchSampler - from a MNIST-like dataset, samples n_classes and within these classes samples n_samples. - Returns batches of size n_classes * n_samples - """ - - def __init__(self, underlying_dataset, n_classes, n_samples): - self.labels = [s[1] for s in underlying_dataset.samples] - self.labels_set = set(self.labels) - self.label_to_indices = {label: np.where(np.array(self.labels) == label)[0] - for label in self.labels_set} - for l in self.labels_set: - np.random.shuffle(self.label_to_indices[l]) - self.used_label_indices_count = {label: 0 for label in self.labels_set} - self.count = 0 - self.n_classes = n_classes - self.n_samples = n_samples - self.dataset = underlying_dataset - self.batch_size = self.n_samples * self.n_classes - - def __iter__(self): - self.count = 0 - while self.count + self.batch_size < len(self.dataset): - #print(self.labels_set, self.n_classes) - classes = np.random.choice(list(self.labels_set), self.n_classes, replace=False) - indices = [] - for class_ in classes: - indices.extend(self.label_to_indices[class_][ - self.used_label_indices_count[class_]:self.used_label_indices_count[ - class_] + self.n_samples]) - self.used_label_indices_count[class_] += self.n_samples - if self.used_label_indices_count[class_] + self.n_samples > len(self.label_to_indices[class_]): - np.random.shuffle(self.label_to_indices[class_]) - self.used_label_indices_count[class_] = 0 - yield indices - self.count += self.n_classes * self.n_samples - - def __len__(self): - return len(self.dataset) // (self.n_samples*self.n_classes) diff --git a/archive/active_learning/archive/filebased_main.py b/archive/active_learning/archive/filebased_main.py deleted file mode 100644 index ae9aa5c4..00000000 --- a/archive/active_learning/archive/filebased_main.py +++ /dev/null @@ -1,179 +0,0 @@ -import argparse -import os -import random -import time -import warnings -import sys - -import torch -import torch.nn as nn -import torch.nn.parallel -import torch.backends.cudnn as cudnn -import torch.distributed as dist -import torch.optim -import torch.utils.data -import torch.utils.data.distributed -from torch.optim.lr_scheduler import StepLR - -import numpy as np - -from DL.data_loader import BaseDataLoader -from DL.losses import * -from DL.utils import * -from DL.networks import * -from DL.Engine import Engine - -model_names = sorted(name for name in models.__dict__ - if name.islower() and not name.startswith("__") - and callable(models.__dict__[name])) - -parser = argparse.ArgumentParser(description='PyTorch ImageNet Training') -parser.add_argument('--train_data', metavar='DIR', - help='path to train dataset', default='../../crops_train') -parser.add_argument('--val_data', metavar='DIR', - help='path to validation dataset', default=None) -parser.add_argument('--arch', '-a', metavar='ARCH', default='resnet18', - choices=model_names, - help='model architecture: ' + - ' | '.join(model_names) + - ' (default: resnet18)') -parser.add_argument('-j', '--workers', default=4, type=int, metavar='N', - help='number of data loading workers (default: 4)') -parser.add_argument('--epochs', default=20, type=int, metavar='N', - help='number of total epochs to run') -parser.add_argument('-b', '--batch_size', default=256, type=int, - metavar='N', help='mini-batch size (default: 256)') -parser.add_argument('--lr', '--learning_rate', default=0.001, type=float, - metavar='LR', help='initial learning rate') -parser.add_argument('--weight_decay', '--wd', default=5e-4, type=float, - metavar='W', help='weight decay (default: 5e-4)') -parser.add_argument('--print_freq', '-p', default=10, type=int, - metavar='N', help='print frequency (default: 10)') -parser.add_argument('--resume', default='', type=str, metavar='PATH', - help='path to latest checkpoint (default: none)') - -parser.add_argument('--checkpoint_prefix', default='', type=str, metavar='PATH', - help='path to latest checkpoint (default: none)') - -parser.add_argument('--plot_freq', dest='plot_freq', type= int, action='store', - help='plot embedding frequence', default=1) -parser.add_argument('--pretrained', dest='pretrained', action='store_true', - help='use pre-trained model') -parser.add_argument('--seed', default=None, type=int, - help='seed for initializing training. ') -parser.add_argument('--loss_type', default='triplet', - help='Loss Type') -parser.add_argument('--margin', default=1.0, type=float, metavar='M', - help='margin for siamese or triplet loss') -parser.add_argument('-f', '--feat_dim', default=256, type=int, - metavar='N', help='embedding size (default: 256)') - -parser.add_argument('--raw_size', nargs= 2, default= [256,256], type= int, action= 'store', help= 'The width, height and number of channels of images for loading from disk') -parser.add_argument('--processed_size', nargs= 2, default= [224,224], type= int, action= 'store', help= 'The width and height of images after preprocessing') -parser.add_argument('--balanced_P', default= -1, type= int, action= 'store', help= 'The number of classes in each balanced batch') -parser.add_argument('--balanced_K', default= 10, type= int, action= 'store', help= 'The number of examples from each class in each balanced batch') -best_acc1 = 0 - -def custom_policy(step): - details_str= '21, 36, 46, 0.001, 0.0005, 0.0001, 0.00005' - details= [float(x) for x in details_str.split(",")] - length = len(details) - for i,x in enumerate(details[0:int((length-1)/2)]): - if step<= int(x): - return details[int((length-1)/2)+i] - return details[-1] - -def adjust_lr(optimizer, step): - param= custom_policy(step) - for param_group in optimizer.param_groups: - param_group['lr'] = param - print("Learning rate is set to %f"%param) - -def main(): - global args, best_acc1 - args = parser.parse_args() - - if args.seed is not None: - random.seed(args.seed) - torch.manual_seed(args.seed) - cudnn.deterministic = True - warnings.warn('You have chosen to seed training. ' - 'This will turn on the CUDNN deterministic setting, ' - 'which can slow down your training considerably! ' - 'You may see unexpected behavior when restarting ' - 'from checkpoints.') - checkpoint={} - if args.resume!='': - checkpoint= load_checkpoint(args.resume) - args.loss_type= checkpoint['loss_type'] - args.feat_dim= checkpoint['feat_dim'] - best_accl= checkpoint['best_acc1'] - - train_dataset = BaseDataLoader(args.train_data,True, batch_size= args.batch_size, num_workers= args.workers, raw_size= args.raw_size, processed_size= args.processed_size) - if args.val_data is not None: - val_dataset = BaseDataLoader(args.val_data,False, batch_size= args.batch_size, num_workers= args.workers) - num_classes= len(train_dataset.getClassesInfo()[0]) - if args.balanced_P==-1: - args.balanced_P= num_classes - print("Num Classes= "+str(num_classes)) - if args.loss_type.lower()=='center': - train_loader = train_dataset.getSingleLoader() - train_embd_loader= train_loader - if args.val_data is not None: - val_loader = val_dataset.getSingleLoader() - val_embd_loader= val_loader - else: - train_loader = train_dataset.getBalancedLoader(P=args.balanced_P, K=args.balanced_K) - train_embd_loader= train_dataset.getSingleLoader() - if args.val_data is not None: - val_loader = val_dataset.getBalancedLoader(P=args.balanced_P, K=args.balanced_K) - val_embd_loader = val_dataset.getSingleLoader() - - embedding_net = EmbeddingNet(args.arch, args.feat_dim, args.pretrained) - center_loss= None - if args.loss_type.lower()=='center': - model = torch.nn.DataParallel(ClassificationNet(embedding_net, n_classes=num_classes)).cuda() - criterion= CenterLoss(num_classes= num_classes, feat_dim= args.feat_dim) - params = list(model.parameters()) + list(criterion.parameters()) - else: - model= torch.nn.DataParallel(embedding_net).cuda() - if args.loss_type.lower()=='siamese': - criterion= OnlineContrastiveLoss(args.margin, RandomNegativePairSelector()) - else: - criterion= OnlineTripletLoss(args.margin, SemihardNegativeTripletSelector(args.margin)) - params = model.parameters() - - # define loss function (criterion) and optimizer - #optimizer = torch.optim.Adam(params, lr=args.lr, weight_decay= args.weight_decay) - optimizer = torch.optim.SGD(params, lr=args.lr, weight_decay= args.weight_decay, momentum= 0.9) - start_epoch= 0 - - if checkpoint: - start_epoch= checkpoint['epoch'] - model.load_state_dict(checkpoint['state_dict']) - #optimizer.load_state_dict(checkpoint['optimizer']) - if args.loss_type.lower()=='center': - criterion.load_state_dict(checkpoint['centers']) - - e= Engine(model,criterion,optimizer, verbose= True, print_freq= args.print_freq) - for epoch in range(start_epoch, args.epochs): - # train for one epoch - adjust_lr(optimizer, epoch) - e.train_one_epoch(train_loader, epoch, True if args.loss_type.lower()=='center' else False) - # evaluate on validation set - if args.val_data is not None: - e.validate(val_loader,True if args.loss_type.lower()=='center' else False) - save_checkpoint({ - 'epoch': epoch + 1, - 'arch': args.arch, - 'state_dict': model.state_dict(), - 'best_acc1': best_acc1, - 'optimizer' : optimizer.state_dict(), - 'loss_type' : args.loss_type, - 'feat_dim' : args.feat_dim, - 'centers': criterion.state_dict() if args.loss_type.lower()=='center' else None - }, False, "%s%s_%s_%04d.tar"%(args.checkpoint_prefix, args.loss_type, args.arch,epoch)) - - -if __name__ == '__main__': - main() diff --git a/archive/active_learning/archive/filebased_run_dirty.py b/archive/active_learning/archive/filebased_run_dirty.py deleted file mode 100644 index 5dc01052..00000000 --- a/archive/active_learning/archive/filebased_run_dirty.py +++ /dev/null @@ -1,400 +0,0 @@ -import argparse -import os -import random -import time -import warnings - -import torch -import torch.nn as nn -import torch.nn.parallel -import torch.backends.cudnn as cudnn -import torch.distributed as dist -import torch.optim -import torch.utils.data -import torch.utils.data.distributed - -import numpy as np -import matplotlib -from scipy import stats -from itertools import count -#matplotlib.use('Agg') -import matplotlib.pyplot as plt -from sklearn.cluster import DBSCAN, MiniBatchKMeans -from sklearn.neighbors import KNeighborsClassifier -from sklearn.utils.extmath import softmax - -from DL.sqlite_data_loader import SQLDataLoader -from DL.data_loader import BaseDataLoader -from DL.losses import * -from DL.utils import * -from DL.networks import * -from DL.Engine import Engine - -from sklearn.neural_network import MLPClassifier -from sklearn.neighbors import KNeighborsClassifier -from sklearn.svm import SVC -from sklearn.gaussian_process import GaussianProcessClassifier -from sklearn.gaussian_process.kernels import RBF -from sklearn.tree import DecisionTreeClassifier -from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier -from sklearn.naive_bayes import GaussianNB -from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis -from sklearn.semi_supervised import label_propagation -from sklearn.metrics import silhouette_samples, confusion_matrix - -from torch.utils.data import TensorDataset, DataLoader -import torch.nn as nn -import torch.optim as optim -import torch - -model_names = sorted(name for name in models.__dict__ - if name.islower() and not name.startswith("__") - and callable(models.__dict__[name])) - -parser = argparse.ArgumentParser(description='PyTorch ImageNet Training') -parser.add_argument('--run_data', metavar='DIR', - help='path to train dataset', default='../../NACTI_crops') -parser.add_argument('-j', '--workers', default=4, type=int, metavar='N', - help='number of data loading workers (default: 4)') -parser.add_argument('-b', '--batch_size', default=256, type=int, - metavar='N', help='mini-batch size (default: 256)') -parser.add_argument('--resume', default='', type=str, metavar='PATH', - help='path to latest checkpoint (default: none)') -parser.add_argument('--name_prefix', default='', type=str, metavar='PATH', - help='prefix name for output files') -parser.add_argument('--num_clusters', default=20, type=int, - help='number of clusters') -parser.add_argument('--K', default=5, type=int, - help='number of clusters') - -def find_probablemap(true_labels, clustering_labels, K=5): - clusters= set(clustering_labels) - mapping={} - for x in clusters: - sub= true_labels[clustering_labels==x] - mapping[x]= int(stats.mode(np.random.choice(sub,K), axis=None)[0])#int(stats.mode(sub, axis=None)[0]) - return mapping - -def apply_different_methods(X_train, y_train, X_test, y_test): - """names = ["1-NN", "3-NN", "Linear SVM", "RBF SVM", "Neural Net", "AdaBoost", - "Naive Bayes"] - - classifiers = [ - KNeighborsClassifier(1), - KNeighborsClassifier(3), - SVC(kernel="linear", C=0.025), - SVC(gamma=2, C=1), - MLPClassifier(), - AdaBoostClassifier(), - GaussianNB()] - for name, clf in zip(names, classifiers): - clf.fit(X_train, y_train) - score = clf.score(X_test, y_test) - print(name, score)""" - - trainset = TensorDataset(torch.from_numpy(X_train), torch.from_numpy(y_train)) - trainloader = DataLoader(trainset, batch_size=64, shuffle = True) - testset = TensorDataset(torch.from_numpy(X_test), torch.from_numpy(y_test)) - testloader = DataLoader(testset, batch_size=32, shuffle = False) - - criterion = nn.CrossEntropyLoss() - net= ClassificationNet(256,16) - optimizer = optim.Adam(net.parameters()) - net.train() - conf= ConfusionMatrix(24) - for epoch in range(50): # loop over the dataset multiple times - - running_loss = 0.0 - for i, data in enumerate(trainloader, 0): - # get the inputs - inputs, labels = data - inputs= inputs.float() - labels= labels.long() - # zero the parameter gradients - optimizer.zero_grad() - - # forward + backward + optimize - outputs = net(inputs) - conf.update(outputs,labels) - loss = criterion(outputs, labels) - loss.backward() - optimizer.step() - - # print statistics - running_loss += loss.item() - print(conf.mat) - losses = AverageMeter() - top1 = AverageMeter() - top5 = AverageMeter() - - net.eval() - with torch.no_grad(): - for i, data in enumerate(testloader, 0): - # get the inputs - inputs, labels = data - inputs= inputs.float() - labels= labels.long() - # forward + backward + optimize - outputs = net(inputs) - loss = criterion(outputs, labels) - acc1, acc5 = accuracy(outputs, labels, topk=(1, 5)) - # print statistics - losses.update(loss.item(), inputs.size(0)) - top1.update(acc1[0], inputs.size(0)) - top5.update(acc5[0], inputs.size(0)) - print('loss: %.3f Top-1: %.3f Top-5: %.3f' % (losses.avg,top1.avg,top5.avg)) - - - -print('Finished Training') - -"""def completeClassificationLoop(dataset,model, num_classes): - clf= ClassificationNet(model,num_classes).cuda() - criterion = nn.CrossEntropyLoss() - optimizer = optim.Adam(clf.parameters(), lr=0.0001) - print("Loop Started") - base_ind = set(np.random.choice(np.arange(len(dataset)), 100, replace=False)) - for it in range(10): - X_train= np.zeros((len(base_ind),3,224,224)) - y_train= np.zeros((len(base_ind)),dtype=np.int32) - for i,ind in enumerate(base_ind): - X_train[i,:,:,:], y_train[i],_= dataset[ind] - - trainset = TensorDataset(torch.from_numpy(X_train), torch.from_numpy(y_train)) - trainloader = DataLoader(trainset, batch_size=32, shuffle = True) - - clf.train() - for epoch in range(10): - for i, data in enumerate(trainloader, 0): - # get the inputs - inputs, labels = data - inputs= inputs.float().cuda() - labels= labels.long().cuda() - # zero the parameter gradients - optimizer.zero_grad() - - # forward + backward + optimize - _,outputs = clf(inputs) - loss = criterion(outputs, labels) - loss.backward() - optimizer.step() - print(epoch,loss.item()) - clf.eval() - testloader = DataLoader(dataset, batch_size=512, shuffle = False) - losses = AverageMeter() - top1 = AverageMeter() - top5 = AverageMeter() - - uncertainty= np.zeros((len(dataset))) - with torch.no_grad(): - for i, data in enumerate(testloader, 0): - # get the inputs - inputs, labels, _ = data - inputs= inputs.float().cuda() - labels= labels.long().cuda() - # forward + backward + optimize - _, outputs = clf(inputs) - uncertainty - acc1, acc5 = accuracy(outputs, labels, topk=(1, 5)) - # print statistics - losses.update(loss.item(), inputs.size(0)) - top1.update(acc1[0], inputs.size(0)) - top5.update(acc5[0], inputs.size(0)) - print('loss: %.3f Top-1: %.3f Top-5: %.3f' % (losses.avg,top1.avg,top5.avg)) - - - #conf= NPConfusionMatrix(10) - for it in range(9): - clf.fit(X_train, y_train) - #all_indices= set(range(len(y))) - #diff= all_indices.difference(base_ind) - print("Iteration %d, Accuracy %.3f"%(it,clf.score(X,y)))#[list(diff)],y[list(diff)]))) - preds= clf.predict_proba(X) - preds_tr= clf.predict_proba(X_train) - #conf.update(preds_tr,y_train) - #classes= np.apply_along_axis(conf.classScore,0,preds.argmax(axis=1)) - uncertainty= preds.max(axis=1) - srt = np.argsort(uncertainty) - co=0 - i=0 - while co<100: - if srt[i] not in base_ind: - base_ind.add(srt[i]) - co+=1 - i+=1 - X_train=X[list(base_ind)] - y_train=y[list(base_ind)] - #conf.reset()""" - -def completeLoop(X,y): - print("Loop Started") - base_ind = set(np.random.choice(np.arange(len(y)), 200, replace=False)) - X_train= X[list(base_ind)] - y_train= y[list(base_ind)] - clf= MLPClassifier(hidden_layer_sizes=(200,100), max_iter=300) - #conf= NPConfusionMatrix(10) - for it in range(9): - clf.fit(X_train, y_train) - #all_indices= set(range(len(y))) - #diff= all_indices.difference(base_ind) - print("Iteration %d, Accuracy %.3f"%(it,clf.score(X,y)))#[list(diff)],y[list(diff)]))) - preds= clf.predict_proba(X) - preds_tr= clf.predict_proba(X_train) - #conf.update(preds_tr,y_train) - #classes= np.apply_along_axis(conf.classScore,0,preds.argmax(axis=1)) - uncertainty= preds.max(axis=1) - srt = np.argsort(uncertainty) - co=0 - i=0 - while co<200: - if srt[i] not in base_ind: - base_ind.add(srt[i]) - co+=1 - i+=1 - X_train=X[list(base_ind)] - y_train=y[list(base_ind)] - #conf.reset() - -def active_learning(X,y, base_ind): - print("Pass Started") - X_train= X[base_ind] - y_train= y[base_ind] - uncertainty= np.zeros((X.shape[0])) - classifiers = [SVC(gamma=2, C=1, probability= True), MLPClassifier()] - #for clf in classifiers: - # clf.fit(X_train, y_train) - # preds= clf.predict_proba(X) - # uncertainty+= preds.max(axis=1) - clf=classifiers[1] - clf.fit(X_train, y_train) - preds= clf.predict_proba(X) - uncertainty+= preds.max(axis=1) - - ind = np.argsort(uncertainty)[0:100] - #print(uncertainty[ind]) - return np.append(base_ind, ind) - -def active_learning2(X, num_samples, k=20): - clusters = MiniBatchKMeans(n_clusters= k).fit_predict(X) - uncertainty= silhouette_samples(X,clusters) - ind = np.argsort(uncertainty)[0:num_samples] - return ind - -def active_learning_entropy(X,y, base_ind): - print("Pass Started") - X_train= X[base_ind] - y_train= y[base_ind] - uncertainty= np.zeros((X.shape[0])) - clf=MLPClassifier() - clf.fit(X_train, y_train) - preds= clf.predict_proba(X) - uncertainty+= np.apply_along_axis(stats.entropy,1,preds) - - ind = np.argsort(uncertainty)[-100:] - #print(uncertainty[ind]) - return np.append(base_ind, ind) - -"""def active_learning3(X, y, base_ind): - newy= y - mask = np.ones(y.shape,dtype=bool) #np.ones_like(a,dtype=bool) - mask[base_ind] = False - newy[mask]= -1 - lp_model = label_propagation.LabelSpreading(kernel='knn', gamma=0.25, max_iter=5) - lp_model.fit(X, newy) - predicted_labels = lp_model.transduction_[mask] - #true_labels = y[unlabeled_indices] - pred_entropies = stats.distributions.entropy(lp_model.label_distributions_.T) - - # select up to 5 digit examples that the classifier is most uncertain about - uncertainty_index = np.argsort(pred_entropies)[::-1] - uncertainty_index = uncertainty_index[np.in1d(uncertainty_index, mask)][:100] - return uncertainty_index""" - -def main(): - args = parser.parse_args() - # remember best acc@1 and save checkpoint - checkpoint= load_checkpoint(args.resume) - run_dataset = BaseDataLoader(args.run_data, False, num_workers= args.workers, batch_size= args.batch_size) - num_classes= len(run_dataset.getClassesInfo()[0]) - print("Num Classes= "+str(num_classes)) - run_loader = run_dataset.getSingleLoader() - embedding_net = EmbeddingNet(checkpoint['arch'], checkpoint['feat_dim']) - if checkpoint['loss_type'].lower()=='center': - model = torch.nn.DataParallel(ClassificationNet(embedding_net, n_classes=14)).cuda() - else: - model= torch.nn.DataParallel(embedding_net).cuda() - model.load_state_dict(checkpoint['state_dict']) - #completeClassificationLoop(run_dataset, model,num_classes) - embd, label, paths = extract_embeddings(run_loader, model) - #db = DBSCAN(eps=0.1, min_samples=5).fit(embd) - #db = MiniBatchKMeans(n_clusters=args.num_clusters).fit(embd) - #labels = db.labels_ - #mapp=(find_probablemap(label,labels, K=args.K)) - #print("Clusters") - #for i,x in enumerate(labels): - # labels[i]= mapp[x] - #print(np.sum(labels == label)/labels.size) - #print("Confidence Active Learning") - #idx = np.random.choice(np.arange(len(paths)), 100, replace=False) - #for i in range(9): - # idx= active_learning(embd, label, idx) - #print(idx.shape) - #apply_different_methods(embd[idx], label[idx], embd, label) - - #print("Entropy Active Learning") - #idx = np.random.choice(np.arange(len(paths)), 100, replace=False) - #for i in range(9): - # idx= active_learning_entropy(embd, label, idx) - - #apply_different_methods(embd[idx], label[idx], embd, label) - - print("CompleteLoop") - completeLoop(embd,label) - - #print(idx,idx.shape) - #for i in idx: - # print(paths[i]) - - #print("Silohette active learning") - #idx= active_learning2(embd, 1000, args.num_clusters) - #print(idx.shape) - #apply_different_methods(embd[idx], label[idx], embd, label) - print("Random") - idx = np.random.choice(np.arange(len(paths)), 1000, replace=False) - apply_different_methods(embd[idx], label[idx], embd, label) - - #apply_different_methods(embd[idx], label[idx], embd, label) - #embd= reduce_dimensionality(embd)#[0:10000]) - #labels= labels[0:10000] - #label= label[0:10000] - #paths= paths[0:10000] - #plot_embedding(embd, label, paths, run_dataset.getClassesInfo()[1]) - #plot_embedding(embd, labels, paths, run_dataset.getClassesInfo()[1]) - #plt.show() - #np.save(args.name_prefix+"_embeddings.npy",embd) - #np.save(args.name_prefix+"_labels.npy",label) - #np.savetxt(args.name_prefix+"_paths.txt",paths, fmt="%s") - - - -def extract_embeddings(dataloader, model): - with torch.no_grad(): - model.eval() - embeddings = np.zeros((len(dataloader.dataset),256))# 3*218*218)) - labels = np.zeros(len(dataloader.dataset)) - paths=[None]*len(dataloader.dataset) - k = 0 - for images, target, path in dataloader: - - images = images.cuda() - embedding = model(images) - embeddings[k:k+len(images)] = embedding.data.cpu().numpy().reshape((len(images),-1)) - labels[k:k+len(images)] = target.numpy() - paths[k:k+len(path)]=path - k += len(images) - del embedding - #del output - return embeddings, labels, paths - -if __name__ == '__main__': - main() diff --git a/archive/active_learning/archive/good_run.py b/archive/active_learning/archive/good_run.py deleted file mode 100644 index fc8cd920..00000000 --- a/archive/active_learning/archive/good_run.py +++ /dev/null @@ -1,344 +0,0 @@ -import argparse -import os -import random -import time -import sys -import warnings -from shutil import copy - -import torch -import torch.nn as nn -import torch.nn.parallel -import torch.backends.cudnn as cudnn -import torch.distributed as dist -import torch.optim -import torch.utils.data -import torch.utils.data.distributed - -import numpy as np -np.set_printoptions(threshold=np.inf) -import matplotlib -from scipy import stats -from itertools import count -#matplotlib.use('Agg') -import matplotlib.pyplot as plt -from sklearn.cluster import DBSCAN, MiniBatchKMeans -from sklearn.neighbors import KNeighborsClassifier -from sklearn.utils.extmath import softmax - -from DL.sqlite_data_loader import SQLDataLoader -from DL.losses import * -from DL.utils import * -from DL.networks import * -from DL.Engine import Engine -from UIComponents.DBObjects import * - -from alipy.experiment.al_experiment import AlExperiment -from modAL.models import ActiveLearner -from modAL.uncertainty import * - -from sampling_methods.constants import get_AL_sampler -from sampling_methods.constants import get_wrapper_AL_mapping -get_wrapper_AL_mapping() - -from sklearn.neural_network import MLPClassifier -from sklearn.neighbors import KNeighborsClassifier -from sklearn.svm import SVC -from sklearn.gaussian_process import GaussianProcessClassifier -from sklearn.gaussian_process.kernels import RBF -from sklearn.tree import DecisionTreeClassifier -from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, VotingClassifier -from sklearn.naive_bayes import GaussianNB -from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis -from sklearn.semi_supervised import label_propagation -from sklearn.metrics import silhouette_samples, confusion_matrix -from sklearn.cluster import DBSCAN, MiniBatchKMeans -from sklearn.externals.joblib import parallel_backend -from sklearn.metrics import pairwise_distances_argmin_min - -from torch.utils.data import TensorDataset, DataLoader -import torch.nn as nn -import torch.optim as optim -import torch - -model_names = sorted(name for name in models.__dict__ - if name.islower() and not name.startswith("__") - and callable(models.__dict__[name])) - -parser = argparse.ArgumentParser(description='PyTorch ImageNet Training') -parser.add_argument('--run_data', metavar='DIR', - help='path to train dataset', default='../../NACTI_crops') -parser.add_argument('-j', '--workers', default=4, type=int, metavar='N', - help='number of data loading workers (default: 4)') -parser.add_argument('-b', '--batch_size', default=256, type=int, - metavar='N', help='mini-batch size (default: 256)') -parser.add_argument('--base_model', default='', type=str, metavar='PATH', - help='path to latest checkpoint (default: none)') -parser.add_argument('--name_prefix', default='', type=str, metavar='PATH', - help='prefix name for output files') -parser.add_argument('--num_clusters', default=20, type=int, - help='number of clusters') -parser.add_argument('--K', default=5, type=int, - help='number of clusters') - - -def activeLearning(prob_list, dataset): - #uncertainty= np.apply_along_axis(stats.entropy,1,probs) * (1 - probs.max(axis=1)) - preds = prob_list[0].argmax(axis=1) - paths = dataset.getpaths() - uncertainty= prob_list[0].max(axis=1) - for l in range(1,len(prob_list)): - uncertainty += prob_list[l].max(axis=1) - srt = np.argsort(uncertainty)#[::-1] - base_ind= set() - co = 0 - i = 0 - while co<200: - base_ind.add(srt[i]) - #copy(paths[srt[i]], "active") - co+=1 - i += 1 - #plot_together( dataset.em[dataset.current_set], np.asarray(dataset.getlabels()), preds, base_ind, dataset.getpaths(), {}) - return base_ind - #return np.random.choice(range(0,prob_list[0].shape[0]), 100, replace=False).tolist() - -def selectSamples(embd, current_set, n): - print('Initial sample selection') - sys.stdout.flush() - selected_set= set() - while len(selected_set)= 2.0: - selected_set.add(rand_ind[i]) - return [current_set[i] for i in selected_set] - -def noveltySamples(embd, paths, n, initial_n= 50): - print('Initial sample selection') - sys.stdout.flush() - selected_set= set() - rand_ind= np.random.choice(np.arange(embd.shape[0]), initial_n, replace=False) - for x in rand_ind: - selected_set.add(x) - while len(selected_set)10: - dataset.setKind(DetectionKind.UserDetection.value) - finetune_embedding(model, dataset, 32, 4, 50) - dataset.setKind(DetectionKind.ModelDetection.value) - dataset.updateEmbedding(model) - dataset.embedding_mode() - indices= activeLearning(prob_list, dataset) - moveRecords(dataset, DetectionKind.ModelDetection.value, DetectionKind.UserDetection.value, [dataset.current_set[i] for i in indices]) - print([len(x) for x in dataset.set_indices]) - -if __name__ == '__main__': - print("start") - main() diff --git a/archive/active_learning/archive/import_folder.py b/archive/active_learning/archive/import_folder.py deleted file mode 100644 index 7c698981..00000000 --- a/archive/active_learning/archive/import_folder.py +++ /dev/null @@ -1,71 +0,0 @@ -from peewee import * -import sys -import os -import argparse -import datetime -import uuid -import gc -from shutil import copyfile - -from UIComponents.DBObjects import * - -parser = argparse.ArgumentParser(description='Import folder to DB') -parser.add_argument('--source', metavar='DIR', - help='path to the images folder', default='all_crops/crops_train') -parser.add_argument('--db_name', default='destination', type=str, metavar='PATH', - help='Name of the output SQLite DB.') - - -args = parser.parse_args() -os.mkdir(args.db_name) -os.mkdir(os.path.join(args.db_name,"crops")) -db = SqliteDatabase(os.path.join(args.db_name,args.db_name+'.db')) -proxy.initialize(db) -db.create_tables([Detection, Category, Image, Info, Oracle]) -if sys.version_info >= (3, 5): - # Faster and available in Python 3.5 and above - classes = [d.name for d in os.scandir(args.source) if d.is_dir()] -else: - classes = [d for d in os.listdir(args.source) if os.path.isdir(os.path.join(args.source, d))] -classes.sort() -class_to_idx = {classes[i]: i for i in range(len(classes))} -info = Info.create(name=args.source, description= '', contributor= 'Arash', version= 0, year= 2019, date_created= datetime.datetime.today()) -info.save() - -for cat in sorted(class_to_idx.keys()): - print("Start processing "+cat) - category= Category.create(id= class_to_idx[cat], name= cat, abbr= cat[0:2]) - category.save() - image_list= [] - detection_list= [] - oracle_list= [] - for root, _, fnames in sorted(os.walk(os.path.join(args.source, cat))): - for i, fname in enumerate(fnames): - if fname.endswith(".JPG"): - path = os.path.join(root, fname) - image_id= str(uuid.uuid1()) - image_list.append((image_id, fname)) - copyfile(path,os.path.join(args.db_name,"crops",image_id+".JPG")) - detection_list.append((image_id, image_id, DetectionKind.UserDetection.value, category.id, 1, 1, 0, 0, 1, 1)) - oracle_list.append((image_id, category.id)) - if i%1000==0 and i>0: - print('Processed %d of %d '%(i,len(fnames))) - Image.insert_many(image_list, fields= [Image.id, Image.file_name]).execute() - Detection.insert_many(detection_list, fields= [Detection.id, Detection.image, Detection.kind, Detection.category, Detection.category_confidence, Detection.bbox_confidence, - Detection.bbox_X1, Detection.bbox_Y1, Detection.bbox_X2, Detection.bbox_Y2]).execute() - Oracle.insert_many(oracle_list, fields= [Oracle.detection, Oracle.label]).execute() - image_list= [] - detection_list= [] - oracle_list= [] - gc.collect() - Image.insert_many(image_list, fields= [Image.id, Image.file_name]).execute() - Detection.insert_many(detection_list, fields= [Detection.id, Detection.image, Detection.kind, Detection.category, Detection.category_confidence, Detection.bbox_confidence, - Detection.bbox_X1, Detection.bbox_Y1, Detection.bbox_X2, Detection.bbox_Y2]).execute() - Oracle.insert_many(oracle_list, fields= [Oracle.detection, Oracle.label]).execute() - image_list= [] - detection_list= [] - oracle_list= [] - gc.collect() - - - print(cat+" is done.") diff --git a/archive/active_learning/archive/init.py b/archive/active_learning/archive/init.py deleted file mode 100644 index 34555227..00000000 --- a/archive/active_learning/archive/init.py +++ /dev/null @@ -1,119 +0,0 @@ -from peewee import * -from UIComponents.DBObjects import * -from DL.sqlite_data_loader import SQLDataLoader -from DL.utils import * -from DL.networks import * -from DL.Engine import Engine -import sys - -db = SqliteDatabase('SS.db') -proxy.initialize(db) -db.create_tables([Oracle]) - -"""checkpoint= load_checkpoint('triplet_model_0054.tar') -run_dataset = SQLDataLoader(DetectionKind.ModelDetection, "all_crops/SS_full_crops", False, num_workers= 8, batch_size= 2048) -#run_dataset.setup(Detection.select(Detection.id,Category.id).join(Category).where(Detection.kind==DetectionKind.ModelDetection.value).limit(250000)) -num_classes= len(run_dataset.getClassesInfo()) -print("Num Classes= "+str(num_classes)) -run_loader = run_dataset.getSingleLoader() -embedding_net = EmbeddingNet(checkpoint['arch'], checkpoint['feat_dim']) -if checkpoint['loss_type'].lower()=='center': - model = torch.nn.DataParallel(ClassificationNet(embedding_net, n_classes=14)).cuda() -else: - model= torch.nn.DataParallel(embedding_net).cuda() -model.load_state_dict(checkpoint['state_dict']) -e=Engine(model,None,None, verbose=True, print_freq=1) -embd, label, paths = e.predict(run_loader, load_info=True) -for i, key in enumerate(paths): - Detection.update(embedding=embd[i]).where(Detection.id==key).execute() - if i%10000==0: - print(i) - sys.stdout.flush()""" - -"""import sqlite3 -import uuid - -conn = sqlite3.connect('SS.db') - -c = conn.cursor() - -c.execute("select * from S6") - -rows= c.fetchall() - -for row in rows: - #print(row) - c.execute("insert into model_detection(id,image_id,category_id, bbox_confidence,bbox_x1,bbox_y1,bbox_x2,bbox_y2) values(?,?,?,?,?,?,?,?)",(str(uuid.uuid1()),row[0][27:],-1, -float(row[1]),float(row[2]),float(row[3]),float(row[4]),float(row[5]))) - -conn.commit()""" - - -"""import os -from shutil import copyfile -import sqlite3 -from PIL import Image -import sys -from multiprocessing import Process - -def do_chunk(pid,todo): - print(pid,"Started") - co=0 - for line in todo: - try: - dest= os.path.join(images_folder,line[0]+".JPG") - src= os.path.join(root,line[1]) - image = Image.open(src) - dpi = 100 - s = image.size; imageHeight = s[1]; imageWidth = s[0] - figsize = imageWidth / float(dpi), imageHeight / float(dpi) - topRel = float(line[4]) - leftRel = float(line[5]) - bottomRel = float(line[6]) - rightRel = float(line[7]) - #unq_id= "crops_"+str(uuid.uuid1()) - #print(line,imageWidth,imageHeight) - #print("%s,%s,%s,%0.6f,%0.6f,%0.6f,%0.6f,%0.6f"%(line[0], line[1],line[2],float(line[3]),topRel,leftRel,bottomRel,rightRel)) - sys.stdout.flush() - x1 = int(leftRel * imageWidth) - y1 = int(topRel * imageHeight) - x2 = int(rightRel* imageWidth) - y2 = int(bottomRel * imageHeight) - crop= image.crop((x1,y1,x2,y2)).resize((256,256),Image.BILINEAR) - - #if not os.path.exists(dest): - # os.mkdir(dest) - - crop.save(dest) - image.close() - co+=1 - if co%1000==0: - print(pid,co) - except: - #raise - pass - #out.close() - - -def divide(t,n,i): - length=t/(n+0.0) - #print length,(i-1)*length,i*length - return int(round((i-1)*length)),int(round(i*length)) - - - -conn = sqlite3.connect('SS.db') -c = conn.cursor() -root= '/datadrive0/dataD/snapshot' -images_folder='SS_crops' -c.execute('SELECT * FROM model_detection') -rows = c.fetchall() -os.mkdir(images_folder) -total_records=len(rows) -total_processors=12 -print(total_records) -for i in range(1,total_processors+1): - st,ln=divide(total_records,total_processors,i) - p1 = Process(target=do_chunk, args=(i,rows[st:ln])) - p1.start()""" - diff --git a/archive/active_learning/archive/job_template.tmpl b/archive/active_learning/archive/job_template.tmpl deleted file mode 100644 index 34887c0f..00000000 --- a/archive/active_learning/archive/job_template.tmpl +++ /dev/null @@ -1,13 +0,0 @@ -#! /bin/bash -i -#SBATCH --account=EvolvingAI -#SBATCH --time=167:00:00 -#SBATCH --nodes=1 -#SBATCH --cpus-per-task=10 -#SBATCH -J $name -#SBATCH --mail-type=ALL -#SBATCH --mail-user=mnorouzz@uwyo.edu -#SBATCH -e ${name}.err -#SBATCH -o ${name}.log -#SBATCH --mem=124360 -#SBATCH --gres=gpu:2 -srun --export=ALL python run.py --run_data datasets/SS --base_model triplet_resnet50_1499.tar --strategy ${strategy} diff --git a/archive/active_learning/archive/jobs_info_both.txt b/archive/active_learning/archive/jobs_info_both.txt deleted file mode 100644 index 3d9633a7..00000000 --- a/archive/active_learning/archive/jobs_info_both.txt +++ /dev/null @@ -1,15 +0,0 @@ -name,strategy -#margin,margin -hierarchical,hierarchical -#uniform,uniform -margin_cluster_mean,margin_cluster_mean -bandit_mixture,bandit_mixture -bandit_discrete,bandit_discrete -simulate_batch_mixture,simulate_batch_mixture -simulate_batch_best_sim,simulate_batch_best_sim -simulate_batch_frequency,simulate_batch_frequency -#kcenter,kcenter -#entropy,entropy -#confidence,confidence -#graph_density,graph_density -#informative_diverse,informative_diverse diff --git a/archive/active_learning/archive/make_active_learning_classification_dataset.py b/archive/active_learning/archive/make_active_learning_classification_dataset.py deleted file mode 100644 index bbaab88c..00000000 --- a/archive/active_learning/archive/make_active_learning_classification_dataset.py +++ /dev/null @@ -1,210 +0,0 @@ -''' -make_active_learning_dataset.py - -Creates crops from detections in camera trap images for use in active learning for classification. -Largely drawn from MegaDetector/data_management/databases/classification/make_classification_dataset.py. - -''' - -import argparse, cv2, glob, json, os, pickle, random, sys, tqdm, uuid -import numpy as np -import matplotlib; matplotlib.use('Agg') -from PIL import Image -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), - '../../data_management/tfrecords/utils')) -if sys.version_info.major >= 3: - import create_tfrecords_py3 as tfr -else: - import create_tfrecords as tfr - -print('If you run into import errors, please make sure you added "models/research" and ' +\ - ' "models/research/object_detection" of the tensorflow models repo to the PYTHONPATH\n\n') -import tensorflow as tf -from object_detection.utils import ops as utils_ops -from utils import label_map_util -from utils import visualization_utils as vis_util -from distutils.version import StrictVersion -if StrictVersion(tf.__version__) < StrictVersion('1.9.0'): - raise ImportError('Please upgrade your TensorFlow installation to v1.9.* or later!') - - -########################################################## -### Configuration - -parser = argparse.ArgumentParser() -parser.add_argument('image_dir', type=str, default='missouricameratraps/lila', - help='Root folder of the images.') -parser.add_argument('output_dir', type=str, default='missouricameratraps/crops', - help='Output folder for cropped images, used as inputs for classification model.') -parser.add_argument('frozen_graph', type=str, default='frozen_inference_graph.pb', - help='Frozen graph of detection network as created by export_inference_graph.py of TFODAPI.') -parser.add_argument('--detection_confidence', type=float, default=0.9, - help='Confidence threshold for deciding which detections to keep.') -parser.add_argument('--padding_factor', type=float, default=1.3*1.3, - help='We will crop a tight square box around the animal enlarged by this factor. ' + \ - 'Default is 1.3 * 1.3 = 1.69, which accounts for the cropping at test time and for' + \ - ' a reasonable amount of context') -args = parser.parse_args() - - -########################################################## -### The actual code - -# Check arguments -IMAGE_DIR = args.image_dir -assert os.path.exists(IMAGE_DIR), IMAGE_DIR + ' does not exist' - -OUTPUT_DIR = args.output_dir -# Create output directories -if not os.path.exists(OUTPUT_DIR): - print('Creating crops output directory.') - os.makedirs(OUTPUT_DIR) - -PATH_TO_FROZEN_GRAPH = args.frozen_graph -assert os.path.isfile(PATH_TO_FROZEN_GRAPH), PATH_TO_FROZEN_GRAPH + ' does not exist' - -# Padding around the detected objects when cropping -# 1.3 for the cropping during test time and 1.3 for -# the context that the CNN requires in the left-over -# image -PADDING_FACTOR = args.padding_factor -assert PADDING_FACTOR >= 1, 'Padding factor should be equal or larger 1' - -DETECTION_CONFIDENCE = args.detection_confidence - - -# Load a (frozen) Tensorflow model into memory. -detection_graph = tf.Graph() -with detection_graph.as_default(): - od_graph_def = tf.GraphDef() - with tf.gfile.GFile(PATH_TO_FROZEN_GRAPH, 'rb') as fid: - serialized_graph = fid.read() - od_graph_def.ParseFromString(serialized_graph) - tf.import_graph_def(od_graph_def, name='') -graph = detection_graph - - -detections = dict() -crop_info_json = [] -with graph.as_default(): - with tf.Session() as sess: - ### Preparations: get all the output tensors - ops = tf.get_default_graph().get_operations() - all_tensor_names = {output.name for op in ops for output in op.outputs} - tensor_dict = {} - for key in ['num_detections', 'detection_boxes', 'detection_scores', 'detection_classes', 'detection_masks']: - tensor_name = key + ':0' - if tensor_name in all_tensor_names: - tensor_dict[key] = tf.get_default_graph().get_tensor_by_name(tensor_name) - - if 'detection_masks' in tensor_dict: - # The following processing is only for single image - detection_boxes = tf.squeeze(tensor_dict['detection_boxes'], [0]) - detection_masks = tf.squeeze(tensor_dict['detection_masks'], [0]) - # Reframe is required to translate mask from box coordinates to image coordinates and fit the image size. - real_num_detection = tf.cast(tensor_dict['num_detections'][0], tf.int32) - detection_boxes = tf.slice(detection_boigxes, [0, 0], [real_num_detection, -1]) - detection_masks = tf.slice(detection_masks, [0, 0, 0], [real_num_detection, -1, -1]) - detection_masks_reframed = utils_ops.reframe_box_masks_to_image_masks( - detection_masks, detection_boxes, image.shape[0], image.shape[1]) - detection_masks_reframed = tf.cast( - tf.greater(detection_masks_reframed, 0.5), tf.uint8) - # Follow the convention by adding back the batch dimension - tensor_dict['detection_masks'] = tf.expand_dims( - detection_masks_reframed, 0) - image_tensor = tf.get_default_graph().get_tensor_by_name('image_tensor:0') - - # For all images in the image directoryig - imgs_in_dir = glob.glob(os.path.join(IMAGE_DIR, '**/*.JPG'), recursive=True) # All images in directory (may be a subset of the dataset) - for cur_image in tqdm.tqdm(sorted(imgs_in_dir)): - - # Load image - image = np.array(Image.open(cur_image)) - if image.dtype != np.uint8: - print('Failed to load image ' + cur_image) - continue - - # Run inference - output_dict = sess.run(tensor_dict, - feed_dict={image_tensor: np.expand_dims(image, 0)}) - - # all outputs are float32 numpy arrays, so convert types as appropriate - output_dict['num_detections'] = int(output_dict['num_detections'][0]) - output_dict['detection_classes'] = output_dict[ - 'detection_classes'][0].astype(np.uint8) - output_dict['detection_boxes'] = output_dict['detection_boxes'][0] - output_dict['detection_scores'] = output_dict['detection_scores'][0] - if 'detection_masks' in output_dict: - output_dict['detection_masks'] = output_dict['detection_masks'][0] - - # Add detections to the collection - detections[cur_image] = output_dict - - # Get info about the image - imsize = Image.open(cur_image).size - imwidth = imsize[0] - imheight = imsize[1] - img_cv2 = cv2.imread(cur_image, cv2.IMREAD_COLOR) - channel0_mean = np.mean(img_cv2[:,:,0]) - channel1_mean = np.mean(img_cv2[:,:,1]) - channel2_mean = np.mean(imimageg_cv2[:,:,2]) - max_channel_mean = max(channel0_mean, channel1_mean, channel2_mean) - min_channel_mean = min(channel0_mean, channel1_mean, channel2_mean) - if (max_channel_mean - min_channel_mean) < 1: - grayscale = True - else: - grayscale = False - # Select detections with a confidence larger than DETECTION_CONFIDENCE - selection = output_dict['detection_scores'] > DETECTION_CONFIDENCE - # Get these boxes and convert normalized coordinates to pixel coordinates - selected_boxes = (output_dict['detection_boxes'][selection] * np.tile([imsize[1],imsize[0]], (1,2))) - # Pad the detected animal to a square box and additionally by PADDING_FACTOR, the result will be in crop_boxes - # However, we need to make sure that it box coordinates are still within the image - bbox_sizes = np.vstack([selected_boxes[:,2] - selected_boxes[:,0], selected_boxes[:,3] - selected_boxes[:,1]]).T - offsets = (PADDING_FACTOR * np.max(bbox_sizes, axis=1, keepdims=True) - bbox_sizes) / 2 - crop_boxes = selected_boxes + np.hstack([-offsets,offsets]) - crop_boxes = np.maximum(0,crop_boxes).astype(int) - - # For each detected bounding box with high confidence, we will - # crop the image to the padded box and save it - for box_id in range(selected_boxes.shape[0]): - crop_info = dict() - - # generate a unique identifier for the detection - detection_id = str(uuid.uuid4()) - crop_info['id'] = detection_id - crop_info['image'] = cur_image.split(IMAGE_DIR)[1] - crop_info['grayscale'] = grayscale - - # bbox is the detected box, crop_box the padded / enlarged box - bbox, crop_box = selected_boxes[box_id], crop_boxes[box_id] - new_file_name = os.path.split(cur_image)[1] - crop_box_area = (crop_box[1] - crop_box[0])*(crop_box[3] - crop_box[2]) - img_area = imwidth*imheight - crop_info['relative_size'] = float(crop_box_area)/img_area - - # Add numbering to the original file name if there are multiple boxes - if selected_boxes.shape[0] > 1: - new_file_base, new_file_ext = os.path.splitext(new_file_name) - new_file_name = '{}_{}{}'.format(new_file_base, box_id, new_file_ext) - - # The absolute file path where we will store the image - out_file = os.path.join(OUTPUT_DIR, new_file_name) - - if not os.path.exists(out_file): - try: - img = np.array(Image.open(cur_image)) - cropped_img = img[crop_box[0]:crop_box[2], crop_box[1]:crop_box[3]] - Image.fromarray(cropped_img).save(out_file) - except ValueError: - continue - except FileNotFoundError: - continue - else: - # if COCO_OUTPUT_DIR is set, then we will only use the shape - # of cropped_img in the following code. So instead of reading - # cropped_img = np.array(Image.open(out_file)) - # we can speed everything up by reading only the size of the image - cropped_img = np.zeros((3,) + Image.open(out_file).size).T - -print('Crops created for images in '+IMAGE_DIR+' and stored in '+OUTPUT_DIR) \ No newline at end of file diff --git a/archive/active_learning/archive/multicpu_copy.py b/archive/active_learning/archive/multicpu_copy.py deleted file mode 100755 index 0f4600b5..00000000 --- a/archive/active_learning/archive/multicpu_copy.py +++ /dev/null @@ -1,66 +0,0 @@ -import os -from shutil import copyfile -import sqlite3 -from PIL import Image -import uuid -from multiprocessing import Process - -def do_chunk(pid,todo): - out= open("all_detections_"+str(pid)+".csv","w") - co=0 - for line in todo: - try: - dest= os.path.join(root,line[2].replace(" ","_")) - src= os.path.join('/datadrive0/emammal',line[1]) - image = Image.open(src) - dpi = 100 - s = image.size; imageHeight = s[1]; imageWidth = s[0] - figsize = imageWidth / float(dpi), imageHeight / float(dpi) - topRel = float(line[4]) - leftRel = float(line[5]) - bottomRel = float(line[6]) - rightRel = float(line[7]) - unq_id= "crops_"+str(uuid.uuid1()) - #print(line,imageWidth,imageHeight) - print("%s,%s,%s,%0.6f,%0.6f,%0.6f,%0.6f,%0.6f"%(unq_id, line[0],line[2],float(line[3]),topRel,leftRel,bottomRel,rightRel), file=out) - x1 = int(leftRel * imageWidth) - y1 = int(topRel * imageHeight) - x2 = int(rightRel* imageWidth) - y2 = int(bottomRel * imageHeight) - crop= image.crop((x1,y1,x2,y2)).resize((256,256),Image.BILINEAR) - - if not os.path.exists(dest): - os.mkdir(dest) - - crop.save(os.path.join(dest,unq_id+".JPG")) - image.close() - co+=1 - if co%1000==0: - print(pid,co) - except: - pass - out.close() - - -def divide(t,n,i): - length=t/(n+0.0) - #print length,(i-1)*length,i*length - return int(round((i-1)*length)),int(round(i*length)) - - - -conn = sqlite3.connect('emammal.db') -c = conn.cursor() -root= 'all_crops/emammal_crops' -images_folder='crops' -c.execute('SELECT * FROM detections where label in (select label from species)') -rows = c.fetchall() -os.mkdir(root) -total_records=len(rows) -total_processors=12 -print(total_records) -for i in range(1,total_processors+1): - st,ln=divide(total_records,total_processors,i) - p1 = Process(target=do_chunk, args=(i,rows[st:ln])) - p1.start() - diff --git a/archive/active_learning/archive/plot_active.py b/archive/active_learning/archive/plot_active.py deleted file mode 100644 index a5184637..00000000 --- a/archive/active_learning/archive/plot_active.py +++ /dev/null @@ -1,20 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt - -names = ['Confidence','Entropy','Informative Diverse','K-center','Margin','Margin Cluster Mean','Random'] -colors= ['blue','red','green','yellow','red','purple','black'] -data = np.loadtxt("all_processed.data",delimiter=",",skiprows=1) -x = np.asarray(range(10,301))*100 -plt.xlim(1000,30000) -plt.xticks(np.arange(1000, 31000, 1000), [str(int(x/1000))+'K' for x in np.arange(1000, 31000, 1000)], rotation=90) -plt.yticks(np.arange(0.78, 0.95, 0.01)) -plt.xlabel("Number of Queries",fontweight='bold',fontsize=18) -plt.ylabel("Accuracy",fontweight='bold',fontsize=18) -for i in range(len(names)): - if i!=1: - plt.plot(x, data[:,i], color = colors[i], linewidth = 2, label = names[i]) -plt.hlines([0.909], 0, 30000, colors=['orange'], linestyles=['dashed'], linewidth=3) -plt.text(4000, 0.909 + 0.002, 'Norouzzadeh et al. 2018', fontweight='bold', fontsize=12) -plt.grid(True) -plt.legend(loc=4) -plt.show() diff --git a/archive/active_learning/archive/run_backup.py b/archive/active_learning/archive/run_backup.py deleted file mode 100644 index 6d0a4746..00000000 --- a/archive/active_learning/archive/run_backup.py +++ /dev/null @@ -1,485 +0,0 @@ -import argparse -import os -import random -import time -import sys -import warnings - -import torch -import torch.nn as nn -import torch.nn.parallel -import torch.backends.cudnn as cudnn -import torch.distributed as dist -import torch.optim -import torch.utils.data -import torch.utils.data.distributed - -import numpy as np -import matplotlib -from scipy import stats -from itertools import count -#matplotlib.use('Agg') -import matplotlib.pyplot as plt -from sklearn.cluster import DBSCAN, MiniBatchKMeans -from sklearn.neighbors import KNeighborsClassifier -from sklearn.utils.extmath import softmax - -from DL.sqlite_data_loader import SQLDataLoader -from DL.losses import * -from DL.utils import * -from DL.networks import * -from DL.Engine import Engine -from UIComponents.DBObjects import * - -from sklearn.neural_network import MLPClassifier -from sklearn.neighbors import KNeighborsClassifier -from sklearn.svm import SVC -from sklearn.gaussian_process import GaussianProcessClassifier -from sklearn.gaussian_process.kernels import RBF -from sklearn.tree import DecisionTreeClassifier -from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier -from sklearn.naive_bayes import GaussianNB -from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis -from sklearn.semi_supervised import label_propagation -from sklearn.metrics import silhouette_samples, confusion_matrix -from sklearn.cluster import DBSCAN, MiniBatchKMeans -from sklearn.externals.joblib import parallel_backend -from sklearn.metrics import pairwise_distances_argmin_min - -from torch.utils.data import TensorDataset, DataLoader -import torch.nn as nn -import torch.optim as optim -import torch - -model_names = sorted(name for name in models.__dict__ - if name.islower() and not name.startswith("__") - and callable(models.__dict__[name])) - -parser = argparse.ArgumentParser(description='PyTorch ImageNet Training') -parser.add_argument('--run_data', metavar='DIR', - help='path to train dataset', default='../../NACTI_crops') -parser.add_argument('-j', '--workers', default=4, type=int, metavar='N', - help='number of data loading workers (default: 4)') -parser.add_argument('-b', '--batch_size', default=256, type=int, - metavar='N', help='mini-batch size (default: 256)') -parser.add_argument('--resume', default='', type=str, metavar='PATH', - help='path to latest checkpoint (default: none)') -parser.add_argument('--name_prefix', default='', type=str, metavar='PATH', - help='prefix name for output files') -parser.add_argument('--num_clusters', default=20, type=int, - help='number of clusters') -parser.add_argument('--K', default=5, type=int, - help='number of clusters') - -def find_probablemap(true_labels, clustering_labels, K=5): - clusters= set(clustering_labels) - mapping={} - for x in clusters: - sub= true_labels[clustering_labels==x] - mapping[x]= int(stats.mode(np.random.choice(sub,K), axis=None)[0])#int(stats.mode(sub, axis=None)[0]) - return mapping - -def apply_different_methods(X_train, y_train, X_test, y_test): - """names = ["1-NN", "3-NN", "Linear SVM", "RBF SVM", "Neural Net", "AdaBoost", - "Naive Bayes"] - - classifiers = [ - KNeighborsClassifier(1), - KNeighborsClassifier(3), - SVC(kernel="linear", C=0.025), - SVC(gamma=2, C=1), - MLPClassifier(), - AdaBoostClassifier(), - GaussianNB()] - for name, clf in zip(names, classifiers): - clf.fit(X_train, y_train) - score = clf.score(X_test, y_test) - print(name, score)""" - - trainset = TensorDataset(torch.from_numpy(X_train), torch.from_numpy(y_train)) - trainloader = DataLoader(trainset, batch_size=64, shuffle = True) - testset = TensorDataset(torch.from_numpy(X_test), torch.from_numpy(y_test)) - testloader = DataLoader(testset, batch_size=32, shuffle = False) - - criterion = nn.CrossEntropyLoss() - net= ClassificationNet(256,48) - optimizer = optim.Adam(net.parameters()) - net.train() - #conf= ConfusionMatrix(24) - for epoch in range(50): # loop over the dataset multiple times - - running_loss = 0.0 - for i, data in enumerate(trainloader, 0): - # get the inputs - inputs, labels = data - inputs= inputs.float() - labels= labels.long() - # zero the parameter gradients - optimizer.zero_grad() - - # forward + backward + optimize - outputs = net(inputs) - conf.update(outputs,labels) - loss = criterion(outputs, labels) - loss.backward() - optimizer.step() - - # print statistics - running_loss += loss.item() - print(conf.mat) - losses = AverageMeter() - top1 = AverageMeter() - top5 = AverageMeter() - - net.eval() - with torch.no_grad(): - for i, data in enumerate(testloader, 0): - # get the inputs - inputs, labels = data - inputs= inputs.float() - labels= labels.long() - # forward + backward + optimize - outputs = net(inputs) - loss = criterion(outputs, labels) - acc1, acc5 = accuracy(outputs, labels, topk=(1, 5)) - # print statistics - losses.update(loss.item(), inputs.size(0)) - top1.update(acc1[0], inputs.size(0)) - top5.update(acc5[0], inputs.size(0)) - print('loss: %.3f Top-1: %.3f Top-5: %.3f' % (losses.avg,top1.avg,top5.avg)) - - - -print('Finished Training') - -"""def completeClassificationLoop(dataset,model, num_classes): - clf= ClassificationNet(model,num_classes).cuda() - criterion = nn.CrossEntropyLoss() - optimizer = optim.Adam(clf.parameters(), lr=0.0001) - print("Loop Started") - base_ind = set(np.random.choice(np.arange(len(dataset)), 100, replace=False)) - for it in range(10): - X_train= np.zeros((len(base_ind),3,224,224)) - y_train= np.zeros((len(base_ind)),dtype=np.int32) - for i,ind in enumerate(base_ind): - X_train[i,:,:,:], y_train[i],_= dataset[ind] - - trainset = TensorDataset(torch.from_numpy(X_train), torch.from_numpy(y_train)) - trainloader = DataLoader(trainset, batch_size=32, shuffle = True) - - clf.train() - for epoch in range(10): - for i, data in enumerate(trainloader, 0): - # get the inputs - inputs, labels = data - inputs= inputs.float().cuda() - labels= labels.long().cuda() - # zero the parameter gradients - optimizer.zero_grad() - - # forward + backward + optimize - _,outputs = clf(inputs) - loss = criterion(outputs, labels) - loss.backward() - optimizer.step() - print(epoch,loss.item()) - clf.eval() - testloader = DataLoader(dataset, batch_size=512, shuffle = False) - losses = AverageMeter() - top1 = AverageMeter() - top5 = AverageMeter() - - uncertainty= np.zeros((len(dataset))) - with torch.no_grad(): - for i, data in enumerate(testloader, 0): - # get the inputs - inputs, labels, _ = data - inputs= inputs.float().cuda() - labels= labels.long().cuda() - # forward + backward + optimize - _, outputs = clf(inputs) - uncertainty - acc1, acc5 = accuracy(outputs, labels, topk=(1, 5)) - # print statistics - losses.update(loss.item(), inputs.size(0)) - top1.update(acc1[0], inputs.size(0)) - top5.update(acc5[0], inputs.size(0)) - print('loss: %.3f Top-1: %.3f Top-5: %.3f' % (losses.avg,top1.avg,top5.avg)) - - - #conf= NPConfusionMatrix(10) - for it in range(9): - clf.fit(X_train, y_train) - #all_indices= set(range(len(y))) - #diff= all_indices.difference(base_ind) - print("Iteration %d, Accuracy %.3f"%(it,clf.score(X,y)))#[list(diff)],y[list(diff)]))) - preds= clf.predict_proba(X) - preds_tr= clf.predict_proba(X_train) - #conf.update(preds_tr,y_train) - #classes= np.apply_along_axis(conf.classScore,0,preds.argmax(axis=1)) - uncertainty= preds.max(axis=1) - srt = np.argsort(uncertainty) - co=0 - i=0 - while co<100: - if srt[i] not in base_ind: - base_ind.add(srt[i]) - co+=1 - i+=1 - X_train=X[list(base_ind)] - y_train=y[list(base_ind)] - #conf.reset()""" - -def completeLoop(X,y,base_ind): - embedding_net = EmbeddingNet('densenet161', 256, True) - center_loss= None - model= torch.nn.DataParallel(embedding_net).cuda() - criterion= OnlineTripletLoss(1.0, SemihardNegativeTripletSelector(1.0)) - params = model.parameters() - - # define loss function (criterion) and optimizer - optimizer = torch.optim.Adam(params, lr=0.0001) - start_epoch= 0 - checkpoint= load_checkpoint('triplet_model_0086.tar') - if checkpoint: - model.load_state_dict(checkpoint['state_dict']) - - e= Engine(model,criterion,optimizer, verbose= True, print_freq= 10) - trainset_query= Detection.select(Detection.id,Oracle.label,Detection.embedding).join(Oracle).where(Oracle.status==0, Detection.kind==DetectionKind.UserDetection.value) - embedding_dataset = SQLDataLoader(trainset_query, "all_crops/SS_full_crops", True, num_workers= 4, batch_size= 64) - print(len(embedding_dataset)) - num_classes= 48#len(run_dataset.getClassesInfo()[0]) - print("Num Classes= "+str(num_classes)) - embedding_loader = embedding_dataset.getBalancedLoader(16,4) - for i in range(200): - e.train_one_epoch(embedding_loader,i,False) - embedding_dataset2 = SQLDataLoader(trainset_query, "all_crops/SS_full_crops", False, num_workers= 4, batch_size= 512) - em= e.embedding(embedding_dataset2.getSingleLoader()) - lb = np.asarray([ x[1] for x in embedding_dataset2.samples]) - pt = [x[0] for x in embedding_dataset2.samples]#e.predict(run_loader, load_info=True) - - co=0 - for r,s,t in zip(em,lb,pt): - co+=1 - smp= Detection.get(id=t) - smp.embedding= r - smp.save() - if co%100==0: - print(co) - print("Loop Started") - train_dataset = SQLDataLoader(trainset_query, "all_crops/SS_full_crops", False, datatype='embedding', num_workers= 4, batch_size= 64) - print(len(train_dataset)) - num_classes= 48#len(run_dataset.getClassesInfo()[0]) - print("Num Classes= "+str(num_classes)) - run_loader = train_dataset.getSingleLoader() - clf_model = ClassificationNet(256,48).cuda() - clf_criterion= nn.CrossEntropyLoss() - clf_optimizer = torch.optim.Adam(clf_model.parameters(), lr=0.001, weight_decay=0.0005) - clf_e= Engine(clf_model,clf_criterion,clf_optimizer, verbose= True, print_freq= 10) - for i in range(250): - clf_e.train_one_epoch(run_loader,i, True) - testset_query= Detection.select(Detection.id,Oracle.label,Detection.embedding).join(Oracle).where(Oracle.status==0, Detection.kind==DetectionKind.ModelDetection.value) - test_dataset = SQLDataLoader(testset_query, "all_crops/SS_full_crops", False, datatype='image', num_workers= 4, batch_size= 512) - print(len(test_dataset)) - num_classes= 48#len(run_dataset.getClassesInfo()[0]) - print("Num Classes= "+str(num_classes)) - test_loader = test_dataset.getSingleLoader() - test_em= e.embedding(test_loader) - test_lb = np.asarray([ x[1] for x in test_dataset.samples]) - print(test_lb.shape,test_em.shape) - testset = TensorDataset(torch.from_numpy(test_em), torch.from_numpy(test_lb)) - clf_e.validate(DataLoader(testset, batch_size= 512, shuffle= False), True) - - """X_train= X[list(base_ind)] - y_train= y[list(base_ind)] - clf= MLPClassifier(hidden_layer_sizes=(200,100), max_iter=300) - #conf= NPConfusionMatrix(10) - for it in range(39): - clf.fit(X_train, y_train) - #all_indices= set(range(len(y))) - #diff= all_indices.difference(base_ind) - print("Iteration %d, Accuracy %.3f"%(it,clf.score(X,y)))#[list(diff)],y[list(diff)]))) - preds= clf.predict_proba(X) - preds_tr= clf.predict_proba(X_train) - #conf.update(preds_tr,y_train) - #classes= np.apply_along_axis(conf.classScore,0,preds.argmax(axis=1)) - uncertainty= preds.max(axis=1) - srt = np.argsort(uncertainty) - co=0 - i=0 - while co<100: - if srt[i] not in base_ind: - base_ind.add(srt[i]) - co+=1 - i+=1 - X_train=X[list(base_ind)] - y_train=y[list(base_ind)]""" - #conf.reset() - -def active_learning(X,y, base_ind): - print("Pass Started") - X_train= X[base_ind] - y_train= y[base_ind] - uncertainty= np.zeros((X.shape[0])) - classifiers = [SVC(gamma=2, C=1, probability= True), MLPClassifier()] - #for clf in classifiers: - # clf.fit(X_train, y_train) - # preds= clf.predict_proba(X) - # uncertainty+= preds.max(axis=1) - clf=classifiers[1] - clf.fit(X_train, y_train) - preds= clf.predict_proba(X) - uncertainty+= preds.max(axis=1) - - ind = np.argsort(uncertainty)[0:100] - #print(uncertainty[ind]) - return np.append(base_ind, ind) - -def active_learning2(X, num_samples, k=20): - clusters = MiniBatchKMeans(n_clusters= k).fit_predict(X) - uncertainty= silhouette_samples(X,clusters) - ind = np.argsort(uncertainty)[0:num_samples] - return ind - -def active_learning_entropy(X,y, base_ind): - print("Pass Started") - X_train= X[base_ind] - y_train= y[base_ind] - uncertainty= np.zeros((X.shape[0])) - clf=MLPClassifier() - clf.fit(X_train, y_train) - preds= clf.predict_proba(X) - uncertainty+= np.apply_along_axis(stats.entropy,1,preds) - - ind = np.argsort(uncertainty)[-100:] - #print(uncertainty[ind]) - return np.append(base_ind, ind) - -"""def active_learning3(X, y, base_ind): - newy= y - mask = np.ones(y.shape,dtype=bool) #np.ones_like(a,dtype=bool) - mask[base_ind] = False - newy[mask]= -1 - lp_model = label_propagation.LabelSpreading(kernel='knn', gamma=0.25, max_iter=5) - lp_model.fit(X, newy) - predicted_labels = lp_model.transduction_[mask] - #true_labels = y[unlabeled_indices] - pred_entropies = stats.distributions.entropy(lp_model.label_distributions_.T) - - # select up to 5 digit examples that the classifier is most uncertain about - uncertainty_index = np.argsort(pred_entropies)[::-1] - uncertainty_index = uncertainty_index[np.in1d(uncertainty_index, mask)][:100] - return uncertainty_index""" -def selectSamples(embd, paths, n): - selected_set= set() - while len(selected_set)=0.3 and getDistance(embd,selected_set,rand_ind[i])>=0.3: - indices.add(rand_ind[i]) - moveRecords(DetectionKind.ModelDetection, DetectionKind.ActiveDetection, [paths[i] for i in indices.difference(selected_set)]) - selected_set= selected_set.union(indices) - #print(indices,selected_set) - return selected_set - -def getDistance(embd,archive,sample): - if len(archive)==0: - return 100 - else: - return pairwise_distances_argmin_min(embd[sample].reshape(1, -1),embd[np.asarray(list(archive),dtype=np.int32)])[1] - -def moveRecords(srcKind,destKind,rList): - query= Detection.update(kind=destKind.value).where(Detection.id.in_(rList), Detection.kind==srcKind.value) - #print(query.sql()) - query.execute() - -def main(): - args = parser.parse_args() - print("DB Connect") - db = SqliteDatabase('SS.db') - proxy.initialize(db) - db.connect() - - # remember best acc@1 and save checkpoint - """checkpoint= load_checkpoint("triplet_model_0054.tar") - runset_query= Detection.select(Detection.id,Oracle.label,Detection.embedding).join(Oracle).where(Oracle.status==0) - print("Create Query") - run_dataset = SQLDataLoader(runset_query, "all_crops/SS_full_crops", False, datatype='image', num_workers= args.workers*2, batch_size= 512) - print(len(run_dataset)) - num_classes= 48#len(run_dataset.getClassesInfo()[0]) - print("Num Classes= "+str(num_classes)) - run_loader = run_dataset.getSingleLoader() - embedding_net = EmbeddingNet(checkpoint['arch'], checkpoint['feat_dim']) - if checkpoint['loss_type'].lower()=='center': - model = torch.nn.DataParallel(ClassificationNet(embedding_net, n_classes=14)).cuda() - else: - model= torch.nn.DataParallel(embedding_net).cuda() - model.load_state_dict(checkpoint['state_dict']) - e= Engine(model,None,None, verbose= True, print_freq= 10) - embd = e.embedding(run_loader) - print("inje") - sys.stdout.flush() - label = np.asarray([ x[1] for x in run_dataset.samples]) - paths = [x[0] for x in run_dataset.samples]#e.predict(run_loader, load_info=True) - print("embedding done") - sys.stdout.flush()""" - #completeClassificationLoop(run_dataset, model,num_classes) - #embd, label, paths = extract_embeddings(run_loader, model) - #db = DBSCAN(eps=0.1, min_samples=5).fit(embd) - #db = MiniBatchKMeans(n_clusters=args.num_clusters).fit(embd) - #labels = db.labels_ - #mapp=(find_probablemap(label,labels, K=args.K)) - #print("Clusters") - #for i,x in enumerate(labels): - # labels[i]= mapp[x] - #print(np.sum(labels == label)/labels.size) - #print("Confidence Active Learning") - #idx = np.random.choice(np.arange(len(paths)), 100, replace=False) - #for i in range(9): - # idx= active_learning(embd, label, idx) - #print(idx.shape) - #apply_different_methods(embd[idx], label[idx], embd, label) - - #print("Entropy Active Learning") - #idx = np.random.choice(np.arange(len(paths)), 100, replace=False) - #for i in range(9): - # idx= active_learning_entropy(embd, label, idx) - - #apply_different_methods(embd[idx], label[idx], embd, label) - #new_selected= selectSamples(embd,paths,3000) - - - print("CompleteLoop") - completeLoop(None,None,None)#new_selected) - - #print(idx,idx.shape) - #for i in idx: - # print(paths[i]) - - #print("Silohette active learning") - #idx= active_learning2(embd, 1000, args.num_clusters) - #print(idx.shape) - #apply_different_methods(embd[idx], label[idx], embd, label) - #print("Random") - #idx = np.random.choice(np.arange(len(paths)), 1000, replace=False) - #apply_different_methods(embd[idx], label[idx], embd, label) - - #apply_different_methods(embd[idx], label[idx], embd, label) - #embd= reduce_dimensionality(embd)#[0:10000]) - #labels= labels[0:10000] - #label= label[0:10000] - #paths= paths[0:10000] - #plot_embedding(embd, label, paths, run_dataset.getClassesInfo()[1]) - #plot_embedding(embd, labels, paths, run_dataset.getClassesInfo()[1]) - #plt.show() - #np.save(args.name_prefix+"_embeddings.npy",embd) - #np.save(args.name_prefix+"_labels.npy",label) - #np.savetxt(args.name_prefix+"_paths.txt",paths, fmt="%s") - -if __name__ == '__main__': - print("start") - main() diff --git a/archive/active_learning/archive/run_bk.py b/archive/active_learning/archive/run_bk.py deleted file mode 100644 index c1b2f7c3..00000000 --- a/archive/active_learning/archive/run_bk.py +++ /dev/null @@ -1,247 +0,0 @@ -import argparse -import os -import random -import time -import sys -import warnings - -import torch -import torch.nn as nn -import torch.nn.parallel -import torch.backends.cudnn as cudnn -import torch.distributed as dist -import torch.optim -import torch.utils.data -import torch.utils.data.distributed - -import numpy as np -import matplotlib -from scipy import stats -from itertools import count -#matplotlib.use('Agg') -import matplotlib.pyplot as plt -from sklearn.cluster import DBSCAN, MiniBatchKMeans -from sklearn.neighbors import KNeighborsClassifier -from sklearn.utils.extmath import softmax - -from DL.sqlite_data_loader import SQLDataLoader -from DL.losses import * -from DL.utils import * -from DL.networks import * -from DL.Engine import Engine -from UIComponents.DBObjects import * - -from sklearn.neural_network import MLPClassifier -from sklearn.neighbors import KNeighborsClassifier -from sklearn.svm import SVC -from sklearn.gaussian_process import GaussianProcessClassifier -from sklearn.gaussian_process.kernels import RBF -from sklearn.tree import DecisionTreeClassifier -from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier -from sklearn.naive_bayes import GaussianNB -from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis -from sklearn.semi_supervised import label_propagation -from sklearn.metrics import silhouette_samples, confusion_matrix -from sklearn.cluster import DBSCAN, MiniBatchKMeans -from sklearn.externals.joblib import parallel_backend -from sklearn.metrics import pairwise_distances_argmin_min - -from torch.utils.data import TensorDataset, DataLoader -import torch.nn as nn -import torch.optim as optim -import torch - -model_names = sorted(name for name in models.__dict__ - if name.islower() and not name.startswith("__") - and callable(models.__dict__[name])) - -parser = argparse.ArgumentParser(description='PyTorch ImageNet Training') -parser.add_argument('--run_data', metavar='DIR', - help='path to train dataset', default='../../NACTI_crops') -parser.add_argument('-j', '--workers', default=4, type=int, metavar='N', - help='number of data loading workers (default: 4)') -parser.add_argument('-b', '--batch_size', default=256, type=int, - metavar='N', help='mini-batch size (default: 256)') -parser.add_argument('--base_model', default='', type=str, metavar='PATH', - help='path to latest checkpoint (default: none)') -parser.add_argument('--name_prefix', default='', type=str, metavar='PATH', - help='prefix name for output files') -parser.add_argument('--num_clusters', default=20, type=int, - help='number of clusters') -parser.add_argument('--K', default=5, type=int, - help='number of clusters') - - -def activeLearning(probs, embeddings): - uncertainty= probs.max(axis=1) - srt = np.argsort(uncertainty) - base_ind= set() - co = 0 - i = 0 - while co<100: - base_ind.add(srt[i]) - co+=1 - i += 1 - return base_ind - -def selectSamples(embd, paths, n): - print('Initial sample selection') - sys.stdout.flush() - selected_set= set() - while len(selected_set)= 2.0: - selected_set.add(rand_ind[i]) - return [paths[i] for i in selected_set] - -def noveltySamples(embd, paths, n, initial_n= 50): - print('Initial sample selection') - sys.stdout.flush() - selected_set= set() - rand_ind= np.random.choice(np.arange(embd.shape[0]), initial_n, replace=False) - for x in rand_ind: - selected_set.add(x) - while len(selected_set) len(self.label_to_indices[class_]): - np.random.shuffle(self.label_to_indices[class_]) - self.used_label_indices_count[class_] = 0 - yield indices - self.count += self.n_classes * self.n_samples - - def __len__(self): - return len(self.dataset) // (self.n_samples*self.n_classes) diff --git a/archive/active_learning/data_preprocessing/crop_images_from_batch_api_detections.py b/archive/active_learning/data_preprocessing/crop_images_from_batch_api_detections.py deleted file mode 100644 index 1bcb1bba..00000000 --- a/archive/active_learning/data_preprocessing/crop_images_from_batch_api_detections.py +++ /dev/null @@ -1,132 +0,0 @@ -''' -crop_images_from_batch_api_detections.py - -Creates cropped images for training a classifier. - -Prerequisite steps: -- Run batch detector MegaDetector/detection/run_tf_detector_batch.py to get detections .csv file. - -Produces: -- Cropped images in a specified crops output directory. -- A crops.json file storing info about the cropped images (e.g. about the source image, bounding box confidence, etc.). - -NOTE: -- If using missouricameratraps vs emammal, comment/uncomment the image info as appropriate. This is because - * missouricameratraps data are organized in subfolders named like SEQ75520 containing images named like SEQ75520_IMG_0009.JPG - * emammal data are organized in subfolders named like p139d37340 containing images named like d37340s39i2.JPG -''' - -import numpy as np -import argparse, ast, csv, json, pickle, os, sys, time, tqdm, uuid -from PIL import Image - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--detector_output', type=str, required=True, - help='Path to a .csv file with the output of run_tf_detector_batch.py') - parser.add_argument('--crop_dir', type=str, required=True, - help='Output directory to save cropped image files.') - parser.add_argument('--padding_factor', type=float, default=1.3*1.3, - help='We will crop a tight square box around the animal enlarged by this factor. ' + \ - 'Default is 1.3 * 1.3 = 1.69, which accounts for the cropping at test time and for' + \ - ' a reasonable amount of context') - if len(sys.argv[1:])==0: - parser.print_help() - parser.exit() - args = parser.parse_args() - - DETECTOR_OUTPUT = args.detector_output - CROP_DIR = args.crop_dir - PADDING_FACTOR = args.padding_factor - - # store info about the crops produced in a JSON file - crops_json = {} - - with open(DETECTOR_OUTPUT, 'r') as f: - counter = 0 - timer = time.time() - reader = csv.reader(f) - headers = next(reader, None) - data = list(reader) - num_images = sum(1 for row in data) - for row in data: - counter += 1 - imgfile = row[0] - #------------------------------------------------------------------------------------------------------------# - # COMMENT OUT IF NOT USING A SPECIFIC PROJECT WITHIN ROBERT LONG EMAMMAL DATASET - if 'p158' not in imgfile: # this is for Washington Urban-Wildland Carnivore Project in emammal (Robert Long) - continue - #------------------------------------------------------------------------------------------------------------# - maxconf = float(row[1]) - detections = ast.literal_eval(row[2]) - - if len(detections)>1: # for now, skip if there is more than one detection in the image - continue - - # get some information about the source image - img = np.array(Image.open(imgfile)) - if img.dtype != np.uint8: - print('Failed to load image '+ imgfile) - continue - imgwidth = img.shape[1] - imgheight = img.shape[0] - imggrayscale = bool(np.all(abs(np.mean(img[:,:,0]) - np.mean(img[:,:,1])) < 1) & (abs(np.mean(img[:,:,1]) - np.mean(img[:,:,2])) < 1)) - - #------------------------------------------------------------------------------------------------------------# - # NOTE: EDIT THIS SECTION BASED ON DATASET SOURCE - # get info about sequence the source image belongs to from path and directory - ## missouricameratraps: - # imgframenum = int(os.path.basename(imgfile).split('.JPG')[0].split('_')[-1]) - # imgseqid = int(os.path.split(os.path.dirname(imgfile))[-1]) - # imgseqnumframes = len([name for name in os.listdir(os.path.dirname(imgfile)) if os.path.isfile(os.path.join(os.path.dirname(imgfile), name))]) - - ## emammal: - imgframenum = int(os.path.basename(imgfile).split('.JPG')[0].split('i')[-1]) - imgseqid = int(os.path.basename(imgfile).split('.JPG')[0].split('s')[-1].split('i')[0]) - imgseqid_prefix = os.path.basename(imgfile).split('.JPG')[0].split('i')[0] - imgseqnumframes = len([name for name in os.listdir(os.path.dirname(imgfile)) if (os.path.isfile(os.path.join(os.path.dirname(imgfile), name)) & (imgseqid_prefix in name))]) - #------------------------------------------------------------------------------------------------------------# - - for box_id in range(len(detections)): - if detections[box_id][5] != 1: # something besides an animal was detected (vehicle, person) - continue - detection_box_pix = detections[box_id][0:4]*np.tile([imgheight, imgwidth], (1,2)) - detection_box_pix = detection_box_pix[0] - detection_box_size = np.vstack([detection_box_pix[2] - detection_box_pix[0], detection_box_pix[3] - detection_box_pix[1]]).T - - offsets = (PADDING_FACTOR*np.max(detection_box_size, keepdims=True) - detection_box_size)/2 - crop_box_pix = detection_box_pix + np.hstack([-offsets,offsets]) - crop_box_pix = np.maximum(0,crop_box_pix).astype(int) - crop_box_pix = crop_box_pix[0] - - detection_padded_cropped_img = img[crop_box_pix[0]:crop_box_pix[2], crop_box_pix[1]:crop_box_pix[3]] - crop_data = [] - crop_id = str(uuid.uuid4()) - crop_fn = os.path.join(CROP_DIR, crop_id + '.JPG') - crop_rel_size = (crop_box_pix[2] - crop_box_pix[0])*(crop_box_pix[3] - crop_box_pix[1])/(imgwidth*imgheight) - - try: - Image.fromarray(detection_padded_cropped_img).save(crop_fn) - crops_json[crop_id] = {'id': crop_id, 'file_name': crop_fn, - 'width': detection_padded_cropped_img.shape[1], 'height':detection_padded_cropped_img.shape[0], - 'grayscale': imggrayscale, 'relative_size': crop_rel_size, - 'source_file_name': imgfile, 'seq_id': imgseqid, 'seq_num_frames': imgseqnumframes, 'frame_num': imgframenum, - 'bbox_confidence': detections[box_id][4], 'bbox_X1': detections[box_id][0], 'bbox_Y1': detections[box_id][1], - 'bbox_X2': detections[box_id][2], 'bbox_Y2': detections[box_id][3]} - except ValueError: - continue - except FileNotFoundError: - continue - - if counter%100 == 0: - print('Processed crops for %d out of %d images in %0.2f seconds'%(counter, num_images, time.time() - timer)) - - - with open(os.path.join(CROP_DIR, 'crops.json'), 'w') as outfile: - json.dump(crops_json, outfile) - - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/archive/active_learning/data_preprocessing/crop_images_from_coco_bboxes.py b/archive/active_learning/data_preprocessing/crop_images_from_coco_bboxes.py deleted file mode 100644 index 54294f9e..00000000 --- a/archive/active_learning/data_preprocessing/crop_images_from_coco_bboxes.py +++ /dev/null @@ -1,87 +0,0 @@ -''' -Produces a directory of crops from a COCO-annotated .json full of -bboxes. -''' -import numpy as np -import argparse, ast, csv, json, pickle, os, sys, time, tqdm, uuid -from PIL import Image - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--image_dir', required=True, type=str, help='Path to a directory containing full-sized images in the dataset.') - parser.add_argument('--coco_json', required=True, type=str, help='Path to COCO JSON file for the dataset.') - parser.add_argument('--crop_dir', required=True, type=str, help='Path to output directory for crops.') - parser.add_argument('--padding_factor', type=float, default=1.3, help='We will crop a tight square box around the animal enlarged by this factor. ' + \ - 'Default is 1.3 * 1.3 = 1.69, which accounts for the cropping at test time and for' + \ - ' a reasonable amount of context') - args = parser.parse_args() - if len(sys.argv[1:])==0: - parser.print_help() - parser.exit() - - IMAGE_DIR = args.image_dir - COCO_JSON = args.coco_json - CROP_DIR = args.crop_dir - PADDING_FACTOR = args.padding_factor - - crops_json = {} - - coco_json_data = json.load(open(COCO_JSON, 'r')) - images = {im['id']:im for im in coco_json_data['images']} - bboxes_available = any([('bbox' in a.keys()) for a in coco_json_data['annotations']]) - assert bboxes_available, 'COCO JSON does not contain bounding boxes, need to run a detector first.' - - crop_counter = 0 - timer = time.time() - for ann in coco_json_data['annotations']: - if 'bbox' not in ann.keys(): - continue - image_id = ann['image_id'] - image_fn = images[image_id]['file_name'].replace('\\', '/') - img = np.array(Image.open(os.path.join(IMAGE_DIR, image_fn))) - if img.dtype != np.uint8: - print('Failed to load image '+ image_fn) - continue - crop_counter += 1 - - image_height = images[image_id]['height'] - image_width = images[image_id]['width'] - image_grayscale = bool(np.all(abs(np.mean(img[:,:,0]) - np.mean(img[:,:,1])) < 1) & (abs(np.mean(img[:,:,1]) - np.mean(img[:,:,2])) < 1)) - image_seq_id = images[image_id]['seq_id'] - image_seq_num_frames = images[image_id]['seq_num_frames'] - image_frame_num = images[image_id]['frame_num'] - - detection_box_pix = [ann['bbox'][0], ann['bbox'][1], ann['bbox'][0] + ann['bbox'][2], ann['bbox'][1] + ann['bbox'][3]] - detection_box_size = np.vstack([detection_box_pix[2] - detection_box_pix[0], detection_box_pix[3] - detection_box_pix[1]]).T - offsets = (PADDING_FACTOR*np.max(detection_box_size, keepdims=True) - detection_box_size)/2 - crop_box_pix = detection_box_pix + np.hstack([-offsets,offsets]) - crop_box_pix = np.maximum(0,crop_box_pix).astype(int) - crop_box_pix = crop_box_pix[0] - detection_padded_cropped_img = img[crop_box_pix[1]:crop_box_pix[3], crop_box_pix[0]:crop_box_pix[2]] - - crop_id = str(uuid.uuid4()) - crop_fn = os.path.join(CROP_DIR, crop_id + '.JPG') - crop_width = int(detection_padded_cropped_img.shape[1]) - crop_height = int(detection_padded_cropped_img.shape[0]) - crop_rel_size = (crop_width*crop_height)/(image_width*image_height) - detection_conf = 1 # for annotated crops, assign confidence of 1 - - Image.fromarray(detection_padded_cropped_img).save(crop_fn) - crops_json[crop_id] = {'id': crop_id, 'file_name': crop_fn, - 'width': crop_width, 'height':crop_height, - 'grayscale': image_grayscale, 'relative_size': crop_rel_size, - 'source_file_name': image_fn, 'seq_id': image_seq_id, 'seq_num_frames': image_seq_num_frames, 'frame_num': image_frame_num, - 'bbox_confidence': detection_conf, 'bbox_X1': int(crop_box_pix[1]), 'bbox_Y1': int(crop_box_pix[0]), - 'bbox_X2': int(crop_box_pix[3]), 'bbox_Y2': int(crop_box_pix[2])} - - if crop_counter%100 == 0: - print('Produced crops for %d out of %d detections in %0.2f seconds.'%(crop_counter, len(coco_json_data['annotations']), time.time() - timer)) - assert 2==3, 'brek brek' - - with open(os.path.join(CROP_DIR, 'crops.json'), 'w') as outfile: - json.dump(crops_json, outfile) - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/archive/active_learning/data_preprocessing/prepare_AL_classification_dataset_subset.py b/archive/active_learning/data_preprocessing/prepare_AL_classification_dataset_subset.py deleted file mode 100644 index a199b4c5..00000000 --- a/archive/active_learning/data_preprocessing/prepare_AL_classification_dataset_subset.py +++ /dev/null @@ -1,74 +0,0 @@ -''' -prepare_AL_classification_dataset_subset.py - -Given a full dataset for active learning for species classification, this script creates a random subset of the dataset. -(This is convenient for testing the labeling tool, etc.) - -Prerequisites: -- A directory with cropped images for a dataset -- A crops.json containing information about each cropped image in the crops directory -- A directory with full-size images for a dataset - -Produces: -- A directory with cropped images for the subset dataset -- A crops.json containing information about only cropped images in the subset dataset -- A directory with full-size images corresponding to cropped images in the subset dataset - -''' - -import argparse, copy, json, os, pickle, random, sys, time -import numpy as np - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--num', default=1000, type=int, help='Number of samples to draw from the full dataset for the subset dataset.') - parser.add_argument('--crop_dir', type=str, help='Path to directory with the cropped images of the full dataset.') - parser.add_argument('--crop_json', type=str, help='Path to .json with information about cropped images in the full dataset.') - parser.add_argument('--image_dir', type=str, help='Path to directory with the full-size source images of the full dataset.') - parser.add_argument('--old_base_dir', type=str, help='Path specifying base directory for crop and image subdirs, to be stripped from filenames in crops.json.') - parser.add_argument('--new_base_dir', type=str, help='Path specifying base directory for crop and image subdirs, to be added to filenames in crops.json.') - # parser.add_argument('--db_name', default='missouricameratraps', type=str, help='Name of the training (target) data Postgres DB.') - # parser.add_argument('--db_user', default='user', type=str, help='Name of the user accessing the Postgres DB.') - # parser.add_argument('--db_password', default='password', type=str, help='Password of the user accessing the Postgres DB.') - # parser.add_argument('--base_model', type=str, help='Path to latest embedding model checkpoint.') - # parser.add_argument('--output_dir', type=str, help='Output directory for subset of crops') - parser.add_argument('--random_seed', default=1234, type=int, help='Random seed to get same samples from dataset.') - args = parser.parse_args() - random.seed(args.random_seed) - np.random.seed(args.random_seed) - verbose = True - - if verbose: - print('Reading crops from: \t\t\t%s'%args.crop_dir) - print('Reading full-size images from: \t\t%s'%args.image_dir) - print('Writing subset crops to: \t\t%s'%args.crop_dir.replace(args.old_base_dir, args.new_base_dir)) - print('Writing subset full-size images to: \t%s'%args.image_dir.replace(args.old_base_dir, args.new_base_dir)) - - - crop_data = json.load(open(args.crop_json, 'r')) - subset_indices = np.random.permutation(range(len(crop_data)))[:args.num] - subset_crops = [sorted(list(crop_data.keys()))[sidx] for sidx in subset_indices] - - subset_crop_data = {} - for k in subset_crops: - # Add json entry for this crop - k_data = copy.copy(crop_data[k]) - k_data['file_name'] = k_data['file_name'].replace(args.old_base_dir, args.new_base_dir) # correct base directory from old (full dataset) to new (subset dataset) - k_data['source_file_name'] = k_data['source_file_name'].replace(args.old_base_dir, args.new_base_dir) - subset_crop_data[k] = k_data - - # Copy file for this crop to subset dataset crop dir - sh_command = 'mkdir -p %s && cp %s $_'%(os.path.dirname(k_data['file_name']), crop_data[k]['file_name']) - os.popen(sh_command) - - # Copy file for its full-size source image to subset dataset image dir - sh_command = 'mkdir -p %s && cp %s $_'%(os.path.dirname(k_data['source_file_name']), crop_data[k]['source_file_name']) - os.popen(sh_command) - - # Write crops.json to subset dataset crop dir - subset_crop_json = os.path.join(os.path.dirname(k_data['file_name']), 'crops.json') - with open(subset_crop_json, 'w') as f: - json.dump(subset_crop_data, f) - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/archive/active_learning/data_preprocessing/prepare_crops_subset.py b/archive/active_learning/data_preprocessing/prepare_crops_subset.py deleted file mode 100644 index 6c6a45df..00000000 --- a/archive/active_learning/data_preprocessing/prepare_crops_subset.py +++ /dev/null @@ -1,95 +0,0 @@ -''' -One-off script to look at embedding results for a set of script -''' - -import argparse, os, pickle, random, sys, time -import numpy as np -import torch -import scipy.io -from sklearn.preprocessing import StandardScaler -from sklearn.neighbors import NearestNeighbors - -sys.path.append("..") -from DL.utils import * -from DL.networks import * -from Database.DB_models import * -from DL.sqlite_data_loader import SQLDataLoader - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--db_name', default='missouricameratraps', type=str, help='Name of the training (target) data Postgres DB.') - parser.add_argument('--db_user', default='user', type=str, help='Name of the user accessing the Postgres DB.') - parser.add_argument('--db_password', default='password', type=str, help='Password of the user accessing the Postgres DB.') - parser.add_argument('--num', default=2500, type=int, help='Number of samples to draw from dataset to get embedding features.') - parser.add_argument('--crop_dir', type=str, help='Path to directory with cropped images to get embedding features for.') - parser.add_argument('--base_model', type=str, help='Path to latest embedding model checkpoint.') - parser.add_argument('--random_seed', default=1234, type=int, help='Random seed to get same samples from database.') - parser.add_argument('--output_dir', type=str, help='Output directory for subset of crops') - args = parser.parse_args() - - random.seed(args.random_seed) - np.random.seed(args.random_seed) - - BASE_MODEL = args.base_model - DB_NAME = args.db_name - USER = args.db_user - PASSWORD = args.db_password - - # Connect to database and sample a dataset - target_db = PostgresqlDatabase(DB_NAME, user=USER, password=PASSWORD, host='localhost') - target_db.connect(reuse_if_open=True) - db_proxy.initialize(target_db) - dataset_query = Detection.select(Detection.image_id, Oracle.label, Detection.kind).join(Oracle).limit(args.num) - dataset = SQLDataLoader(args.crop_dir, query=dataset_query, is_training=False, kind=DetectionKind.ModelDetection.value, num_workers=8, limit=args.num) - imagepaths = dataset.getallpaths() - - # Load the saved embedding model from the checkpoint - checkpoint = load_checkpoint(BASE_MODEL) - if checkpoint['loss_type'].lower() == 'center' or checkpoint['loss_type'].lower() == 'softmax': - embedding_net = SoftmaxNet(checkpoint['arch'], checkpoint['feat_dim'], checkpoint['num_classes'], False) - else: - embedding_net = NormalizedEmbeddingNet(checkpoint['arch'], checkpoint['feat_dim'], False) - model = torch.nn.DataParallel(embedding_net).cuda() - model.load_state_dict(checkpoint['state_dict']) - - # Update the dataset embedding - dataset.updateEmbedding(model) - X_train = dataset.em[range(len(dataset))] - y_train = np.asarray(dataset.getalllabels()) - imagepaths = dataset.getallpaths() - - datasetindices = list(range(len(dataset))) - - sample_features = np.array([]).reshape(0, 256) - sample_labels = [] - sample_images = [] - - for idx in datasetindices: - sample_features = np.vstack([sample_features, X_train[idx]]) - sample_labels.append(y_train[idx]) - img_path = imagepaths[idx].split('.JPG')[0] - image = dataset.loader(img_path) - sample_images.append(image) - - # save the images - for idx in datasetindices: - img_path = imagepaths[idx].split('.JPG')[0] - image = dataset.loader(img_path) - os.makedirs(os.path.join(args.output_dir, 'crops'), exist_ok=True) - image.save(os.path.join(args.output_dir, 'crops', '%d.JPG'%idx)) - - # save the features - # with open(os.path.join(args.output_dir, 'lastlayer_features.mat'), 'wb') as f: - # pickle.dump(sample_features, f) - - # with open(os.path.join(args.output_dir, 'labels.mat'), 'wb') as f: - # pickle.dump(sample_labels, f) - - with open(os.path.join(args.output_dir, 'lastlayer_features_and_labels.mat'), 'wb') as f: - scipy.io.savemat(f, mdict={'features': sample_features, 'labels': sample_labels}) - - - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/archive/active_learning/deep_learning/__init__.py b/archive/active_learning/deep_learning/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/archive/active_learning/deep_learning/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/archive/active_learning/deep_learning/active_learning_manager.py b/archive/active_learning/deep_learning/active_learning_manager.py deleted file mode 100644 index 5a4eb3a2..00000000 --- a/archive/active_learning/deep_learning/active_learning_manager.py +++ /dev/null @@ -1,51 +0,0 @@ -from .engine import Engine -import numpy as np -import torch - -class ActiveLearningManager(object): - #constructor - def __init__(self, dataset, embedding_model, device, criterion, normalize): - self.dataset = dataset - self.default_pool = list(range(len(dataset))) - self.active_pool = [] - self.current_pool = self.default_pool - self.model = embedding_model - self.embedding = None - self.device = device - self.criterion = criterion - optimizer = torch.optim.Adam(self.model.parameters(), lr = 0.0001) - self.engine= Engine(self.device, self.model, criterion, optimizer) - self.normalize = normalize - - #update embedding values after a finetuning - def updateEmbedding(self, batch_size = 128, num_workers = 4): - print('Extracting embedding from the provided model ...') - self.embedding = self.engine.embedding(self.dataset.getSingleLoader(batch_size= batch_size, shuffle = False, num_workers=num_workers, - sub_indices= list(range(len(self.dataset))), transfm ="val"), normalize=self.normalize) - - # select either the default or active pools - def setPool(self, pool): - assert pool in ["default", "active"] - if pool == 'default': - self.current_pool = self.default_pool - else: - self.current_pool = self.active_pool - - # gather test set - def getTestSet(self): - return self.embedding[self.default_pool], np.asarray([self.dataset.samples[self.dataset.indices[x]][1] for x in self.default_pool]) - - # gather train set - def getTrainSet(self): - return self.embedding[self.active_pool], np.asarray([self.dataset.samples[self.dataset.indices[x]][1] for x in self.active_pool]) - - # finetune the embedding model over the labeled pool - def finetune_embedding(self, epochs, P, K, lr, num_workers=10): - train_loader = self.dataset.getBalancedLoader(P= P, K= K, num_workers = num_workers, sub_indices= self.active_pool) - for epoch in range(epochs): - self.engine.train_one_epoch(train_loader, epoch, False) - # a utility function for saving the snapshot - def get_pools(self): - return {"embedding":self.embedding, "active_indices": self.active_pool, "default_indices":self.default_pool, - "active_pool":[self.dataset.samples[self.dataset.indices[x]] for x in self.active_pool], - "default_pool":[self.dataset.samples[self.dataset.indices[x]] for x in self.default_pool]} diff --git a/archive/active_learning/deep_learning/data_loader.py b/archive/active_learning/deep_learning/data_loader.py deleted file mode 100644 index daa07470..00000000 --- a/archive/active_learning/deep_learning/data_loader.py +++ /dev/null @@ -1,159 +0,0 @@ -from torch.utils.data import Dataset, Subset -from torchvision.datasets import ImageFolder -from torchvision.transforms import (RandomCrop, RandomErasing, -CenterCrop, ColorJitter, RandomRotation, RandomHorizontalFlip, RandomOrder, -Normalize, Resize, Compose, ToTensor, RandomGrayscale) -from torch.utils.data import DataLoader -from torch.utils.data.sampler import BatchSampler, SubsetRandomSampler - -import numpy as np -import os -import random -from PIL import ImageStat -from .engine import Engine - -class ExtendedImageFolder(ImageFolder): - - def __init__(self, base_folder, indices = None): - super().__init__(base_folder) - self.base_folder = base_folder - self.mean, self.std = self.calc_mean_std() - if indices is None: - self.indices = list(range(len(self.samples))) - elif isinstance(indices, list): - self.indices = indices - elif isinstance(indices, int): - assert indices <= len(self.samples) - self.indices = random.sample(range(len(self.samples)), indices) - else: - raise TypeError('Invalid type for indices') - self.trnsfm = {} - self.trnsfm['train'] = self.get_transform('train') - self.trnsfm['val'] = self.get_transform('val') - - def setTransform(self, transform): - assert transform in ["train", "val"] - self.transform = self.trnsfm[transform] - - def __len__(self): - return len(self.indices) - - def __getitem__(self, ind): - """ - Args: - index (int): Index - Returns: - tuple: (sample, target) where target is class_index of the target class. - """ - index = self.indices[ind] - path, target = self.samples[index] - sample = self.loader(path) - if self.transform is not None: - sample = self.transform(sample) - if self.target_transform is not None: - target = self.target_transform(target) - - return sample, target, path - - def get_transform(self, trns_mode): - transform_list = [] - transform_list.append(Resize((256, 256))) - if trns_mode == 'train': - transform_list.append(RandomCrop((224, 224))) - transform_list.append(RandomGrayscale()) - transform_list.append(RandomOrder( - [RandomHorizontalFlip(), ColorJitter(), RandomRotation(20)])) - else: - transform_list.append(CenterCrop((224, 224))) - transform_list.append(ToTensor()) - transform_list.append(Normalize(self.mean, self.std)) - if trns_mode == 'train': - transform_list.append(RandomErasing(value='random')) - - return Compose(transform_list) - - def calc_mean_std(self): - cache_file = os.path.join(self.base_folder, ".meanstd.cache") - if not os.path.exists(cache_file): - print("Calculating Mean and Std") - means = np.zeros((3)) - stds = np.zeros((3)) - sample_size = min(len(self.samples), 10000) - for i in range(sample_size): - img = self.loader(random.choice(self.samples)[0]) - stat = ImageStat.Stat(img) - means += np.array(stat.mean)/255.0 - stds += np.array(stat.stddev)/255.0 - means = means/sample_size - stds = stds/sample_size - np.savetxt(cache_file, np.vstack((means, stds))) - else: - print("Load Mean and Std from "+cache_file) - contents = np.loadtxt(cache_file) - means = contents[0, :] - stds = contents[1, :] - - return means, stds - - def getClassesInfo(self): - return self.classes, self.class_to_idx - - def getBalancedLoader(self, P= 10, K= 10, num_workers = 4, sub_indices= None, transfm = 'train'): - self.setTransform(transfm) - if sub_indices is not None: - subset = Subset(self, sub_indices) - train_batch_sampler = BalancedBatchSampler(subset, n_classes = P, n_samples = K) - return DataLoader(subset, batch_sampler = train_batch_sampler, num_workers = num_workers) - train_batch_sampler = BalancedBatchSampler(self, n_classes = P, n_samples = K) - return DataLoader(self, batch_sampler = train_batch_sampler, num_workers = num_workers) - - def getSingleLoader(self, batch_size = 128, shuffle = True, num_workers = 4, sub_indices= None, transfm = 'train'): - self.setTransform(transfm) - if sub_indices is not None: - return DataLoader(Subset(self, sub_indices), batch_size = batch_size, shuffle = shuffle, num_workers = num_workers) - return DataLoader(self, batch_size = batch_size, shuffle = shuffle, num_workers = num_workers) - - -class BalancedBatchSampler(BatchSampler): - """ - BatchSampler - from a dataset, samples n_classes and within these classes samples n_samples. - Returns batches of size n_classes * n_samples - """ - - def __init__(self, underlying_dataset, n_classes, n_samples): - if hasattr(underlying_dataset, "dataset"): - self.labels = [underlying_dataset.dataset.samples[underlying_dataset.dataset.indices[i]][1] for i in underlying_dataset.indices] - else: - self.labels = [underlying_dataset.samples[i][1] for i in underlying_dataset.indices] - self.labels_set = set(self.labels) - self.label_to_indices = {label: np.where(np.array(self.labels) == label)[0] - for label in self.labels_set} - for l in self.labels_set: - np.random.shuffle(self.label_to_indices[l]) - self.used_label_indices_count = {label: 0 for label in self.labels_set} - self.count = 0 - self.n_classes = n_classes - self.n_samples = n_samples - self.dataset = underlying_dataset - self.batch_size = self.n_samples * self.n_classes - - def __iter__(self): - self.count = 0 - while self.count + self.batch_size < len(self.dataset): - #print(self.labels_set, self.n_classes) - classes = np.random.choice( - list(self.labels_set), self.n_classes, replace=False) - indices = [] - for class_ in classes: - indices.extend(self.label_to_indices[class_][ - self.used_label_indices_count[class_]:self.used_label_indices_count[ - class_] + self.n_samples]) - self.used_label_indices_count[class_] += self.n_samples - if self.used_label_indices_count[class_] + self.n_samples > len(self.label_to_indices[class_]): - np.random.shuffle(self.label_to_indices[class_]) - self.used_label_indices_count[class_] = 0 - yield indices - self.count += self.n_classes * self.n_samples - - def __len__(self): - return len(self.dataset) // (self.n_samples*self.n_classes) \ No newline at end of file diff --git a/archive/active_learning/deep_learning/engine.py b/archive/active_learning/deep_learning/engine.py deleted file mode 100644 index 6a7e25bd..00000000 --- a/archive/active_learning/deep_learning/engine.py +++ /dev/null @@ -1,172 +0,0 @@ -from .utils import * - -import time -import sys -from tqdm import tqdm -from sklearn.preprocessing import MinMaxScaler - -# utility function -def generate_description(desc, loss, top1 = None, top5 = None): - if top1 is not None and top5 is not None: - return '%s Avg. Loss: %.4f\tAvg. Top-1 Acc.: %.3f\tAvg. Top-5 Acc.: %.3f'%(desc, loss.avg, top1.avg, top5.avg) - else: - return '%s Avg. Loss: %.4f'%(desc, loss.avg) - -class Engine(): - - def __init__(self, device, model, criterion, optimizer): - self.model= model.to(device) - self.criterion= criterion - if self.criterion is not None: - self.criterion.to(device) - self.optimizer= optimizer - self.device = device - - def train_one_batch(self, input, target, iter_num, calcAccuracy): - - input = input.to(self.device) - target = target.to(self.device) - # compute output - output, _ = self.model(input) - - # measure accuracy and record loss - if calcAccuracy: - acc1, acc5 = accuracy(output, target, topk=(1, 5)) - loss = self.criterion(output, target) - - self.optimizer.zero_grad() - loss.backward() - self.optimizer.step() - if not calcAccuracy: - return loss.item() - else: - return loss.item(), acc1, acc5 - - def train_one_epoch(self, train_loader, epoch_num, calcAccuracy): - losses = AverageMeter() - if calcAccuracy: - top1 = AverageMeter() - top5 = AverageMeter() - - # switch to train mode - self.model.train() - train_loader = tqdm(train_loader) - for i, batch in enumerate(train_loader): - input= batch[0] - target= batch[1] - - # measure accuracy and record loss - if calcAccuracy: - loss, acc1, acc5= self.train_one_batch(input, target, i, True) - top1.update(acc1, input.size(0)) - top5.update(acc5, input.size(0)) - else: - loss= self.train_one_batch(input, target, i, False) - losses.update(loss, input.size(0)) - if calcAccuracy: - train_loader.set_description(generate_description("Training epoch %d:"%epoch_num, losses, top1 = top1, top5 = top5)) - else: - train_loader.set_description(generate_description("Training epoch %d:"%epoch_num, losses)) - - def validate_one_batch(self, input, target, iter_num, calcAccuracy): - with torch.no_grad(): - input = input.to(self.device) - target = target.to(self.device) - - # compute output - output, _ = self.model(input) - - # measure accuracy and record loss - if calcAccuracy: - acc1, acc5 = accuracy(output, target, topk=(1, 5)) - loss = self.criterion(output, target) - - if not calcAccuracy: - return loss.item() - else: - return loss.item(), acc1, acc5 - - def validate(self, val_loader, calcAccuracy): - losses = AverageMeter() - if calcAccuracy: - top1 = AverageMeter() - top5 = AverageMeter() - - # switch to evaluate mode - self.model.eval() - val_loader = tqdm(val_loader) - for i, batch in enumerate(val_loader): - input= batch[0] - target= batch[1] - if calcAccuracy: - loss, acc1, acc5= self.validate_one_batch(input, target, i, True) - top1.update(acc1,input.size(0)) - top5.update(acc5,input.size(0)) - else: - loss = self.validate_one_batch(input, target, i, False) - losses.update(loss, input.size(0)) - - if calcAccuracy: - val_loader.set_description(generate_description("Validation:", losses, top1=top1, top5=top5)) - else: - val_loader.set_description(generate_description("Validation:", losses)) - - return losses.avg - - def predict_one_batch(self, input, iter_num): - with torch.no_grad(): - input = input.to(self.device) - # compute output - output, _ = self.model(input) - - return output - - def predict(self, dataloader, load_info= False, dim=256): - - # switch to evaluate mode - self.model.eval() - embeddings = np.zeros((len(dataloader.dataset), dim), dtype=np.float32) - labels = np.zeros(len(dataloader.dataset), dtype=np.int64) - if load_info: - paths=[None]*len(dataloader.dataset) - k = 0 - dataloader = tqdm(dataloader, desc = "Prediction:") - for i, batch in enumerate(dataloader): - images=batch[0] - target= batch[1] - if load_info: - paths[k:k+len(batch[2])]=batch[2] - embedding= self.predict_one_batch(images,i) - embeddings[k:k+len(images)] = embedding.data.cpu().numpy() - labels[k:k+len(images)] = target.numpy() - k += len(images) - if load_info: - return embeddings, labels, paths - else: - return embeddings, labels - - def embedding_one_batch(self, input, iter_num): - with torch.no_grad(): - input = input.to(self.device) - # compute output - _, output = self.model(input) - - return output - - def embedding(self, dataloader, dim=256, normalize = False): - - # switch to evaluate mode - self.model.eval() - embeddings = np.zeros((len(dataloader.dataset), dim), dtype=np.float32) - k = 0 - dataloader = tqdm(dataloader, desc = "Embedding:") - for i, batch in enumerate(dataloader): - images=batch[0] - embedding= self.embedding_one_batch(images,i) - embeddings[k:k+len(images)] = embedding.data.cpu().numpy() - k += len(images) - if normalize: - scaler = MinMaxScaler() - return scaler.fit_transform(embeddings) - else: - return embeddings diff --git a/archive/active_learning/deep_learning/losses.py b/archive/active_learning/deep_learning/losses.py deleted file mode 100644 index ab225b46..00000000 --- a/archive/active_learning/deep_learning/losses.py +++ /dev/null @@ -1,54 +0,0 @@ -import torch -import torch.nn as nn -import torch.nn.functional as F -from torch.autograd import Variable - -class OnlineContrastiveLoss(nn.Module): - """ - Online Contrastive loss - Takes a batch of embeddings and corresponding labels. - Pairs are generated using pair_selector object that take embeddings and targets and return indices of positive - and negative pairs - """ - - def __init__(self, margin, pair_selector): - super(OnlineContrastiveLoss, self).__init__() - self.margin = margin - self.pair_selector = pair_selector - - def forward(self, embeddings, target): - positive_pairs, negative_pairs = self.pair_selector.get_pairs(embeddings, target) - if embeddings.is_cuda: - positive_pairs = positive_pairs.cuda() - negative_pairs = negative_pairs.cuda() - positive_loss = (embeddings[positive_pairs[:, 0]] - embeddings[positive_pairs[:, 1]]).pow(2).sum(1) - negative_loss = F.relu( - self.margin - (embeddings[negative_pairs[:, 0]] - embeddings[negative_pairs[:, 1]]).pow(2).sum( - 1).sqrt()).pow(2) - loss = torch.cat([positive_loss, negative_loss], dim=0) - return loss.mean() - -class OnlineTripletLoss(nn.Module): - """ - Online Triplets loss - Takes a batch of embeddings and corresponding labels. - Triplets are generated using triplet_selector object that take embeddings and targets and return indices of - triplets - """ - - def __init__(self, margin, triplet_selector): - super(OnlineTripletLoss, self).__init__() - self.margin = margin - self.triplet_selector = triplet_selector - - def forward(self, embeddings, target): - - triplets = self.triplet_selector.get_triplets(embeddings, target) - if embeddings.is_cuda: - triplets = triplets.cuda() - - ap_distances = (embeddings[triplets[:, 0]] - embeddings[triplets[:, 1]]).pow(2).sum(1) - an_distances = (embeddings[triplets[:, 0]] - embeddings[triplets[:, 2]]).pow(2).sum(1) - losses = torch.relu(ap_distances - an_distances + self.margin) - - return losses.mean() diff --git a/archive/active_learning/deep_learning/networks.py b/archive/active_learning/deep_learning/networks.py deleted file mode 100644 index 3a616fa6..00000000 --- a/archive/active_learning/deep_learning/networks.py +++ /dev/null @@ -1,50 +0,0 @@ -import torch.nn as nn -import torchvision.models as models -from torchvision.models.resnet import BasicBlock -import torch.utils.model_zoo as model_zoo -import torch -import torch.nn.functional as F - -class EmbeddingNet(nn.Module): - def __init__(self, architecture, feat_dim, use_pretrained=False): - super(EmbeddingNet, self).__init__() - self.feat_dim= feat_dim - self.inner_model = models.__dict__[architecture](pretrained=use_pretrained) - if architecture.startswith('resnet'): - in_feats= self.inner_model.fc.in_features - self.inner_model.fc = nn.Linear(in_feats, feat_dim) - elif architecture.startswith('inception'): - in_feats= self.inner_model.fc.in_features - self.inner_model.fc = nn.Linear(in_feats, feat_dim) - if architecture.startswith('densenet'): - in_feats= self.inner_model.classifier.in_features - self.inner_model.classifier = nn.Linear(in_feats, feat_dim) - if architecture.startswith('vgg'): - in_feats= self.inner_model.classifier._modules['6'].in_features - self.inner_model.classifier._modules['6'] = nn.Linear(in_feats, feat_dim) - if architecture.startswith('alexnet'): - in_feats= self.inner_model.classifier._modules['6'].in_features - self.inner_model.classifier._modules['6'] = nn.Linear(in_feats, feat_dim) - - def forward(self, x): - return self.inner_model.forward(x) - -class NormalizedEmbeddingNet(EmbeddingNet): - def __init__(self, architecture, feat_dim, use_pretrained=False): - EmbeddingNet.__init__(self, architecture, feat_dim, use_pretrained = use_pretrained) - - def forward(self, x): - embedding = self.inner_model.forward(x) - return embedding, embedding - -class SoftmaxNet(nn.Module): - def __init__(self, architecture, feat_dim, num_classes, use_pretrained = False): - super(SoftmaxNet, self).__init__() - self.embedding = EmbeddingNet(architecture, feat_dim, use_pretrained = use_pretrained) - self.classifier = nn.Linear(feat_dim, num_classes) - - def forward(self, x): - embed = self.embedding(x) - x = F.relu(embed) - x = self.classifier(x) - return x, embed \ No newline at end of file diff --git a/archive/active_learning/deep_learning/utils.py b/archive/active_learning/deep_learning/utils.py deleted file mode 100644 index aeaf122e..00000000 --- a/archive/active_learning/deep_learning/utils.py +++ /dev/null @@ -1,319 +0,0 @@ -import numpy as np -import sys -import matplotlib.pyplot as plt -from PIL import Image as PILImage -from matplotlib.offsetbox import OffsetImage, AnnotationBbox -import torch -import matplotlib.patches as mpatches -import shutil -from itertools import combinations -from MulticoreTSNE import MulticoreTSNE as TSNE -from sklearn.decomposition import PCA -from .losses import * - -indexcolors =["#000000", "#FFFF00", "#1CE6FF", "#FF34FF", "#FF4A46", "#008941", "#006FA6", "#A30059", - - "#FFDBE5", "#7A4900", "#0000A6", "#63FFAC", "#B79762", "#004D43", "#8FB0FF", "#997D87", - "#5A0007", "#809693", "#E704C4", "#1B4400", "#4FC601", "#3B5DFF", "#4A3B53", "#FF2F80", - "#61615A", "#BA0900", "#6B7900", "#00C2A0", "#FFAA92", "#FF90C9", "#B903AA", "#D16100", - "#DDEFFF", "#000035", "#7B4F4B", "#A1C299", "#300018", "#0AA6D8", "#013349", "#00846F", - "#372101", "#FFB500", "#C2FFED", "#A079BF", "#CC0744", "#C0B9B2", "#C2FF99", "#001E09", - "#00489C", "#6F0062", "#0CBD66", "#EEC3FF", "#456D75", "#B77B68", "#7A87A1", "#788D66", - "#885578", "#FAD09F", "#FF8A9A", "#D157A0", "#BEC459", "#456648", "#0086ED", "#886F4C", - - "#34362D", "#B4A8BD", "#00A6AA", "#452C2C", "#636375", "#A3C8C9", "#FF913F", "#938A81", - "#575329", "#00FECF", "#B05B6F", "#8CD0FF", "#3B9700", "#04F757", "#C8A1A1", "#1E6E00", - "#7900D7", "#A77500", "#6367A9", "#A05837", "#6B002C", "#772600", "#D790FF", "#9B9700", - "#549E79", "#FFF69F", "#201625", "#72418F", "#BC23FF", "#99ADC0", "#3A2465", "#922329", - "#5B4534", "#FDE8DC", "#404E55", "#0089A3", "#CB7E98", "#A4E804", "#324E72", "#6A3A4C", - "#83AB58", "#001C1E", "#D1F7CE", "#004B28", "#C8D0F6", "#A3A489", "#806C66", "#222800", - "#BF5650", "#E83000", "#66796D", "#DA007C", "#FF1A59", "#8ADBB4", "#1E0200", "#5B4E51", - "#C895C5", "#320033", "#FF6832", "#66E1D3", "#CFCDAC", "#D0AC94", "#7ED379", "#012C58", - - "#7A7BFF", "#D68E01", "#353339", "#78AFA1", "#FEB2C6", "#75797C", "#837393", "#943A4D", - "#B5F4FF", "#D2DCD5", "#9556BD", "#6A714A", "#001325", "#02525F", "#0AA3F7", "#E98176", - "#DBD5DD", "#5EBCD1", "#3D4F44", "#7E6405", "#02684E", "#962B75", "#8D8546", "#9695C5", - "#E773CE", "#D86A78", "#3E89BE", "#CA834E", "#518A87", "#5B113C", "#55813B", "#FEFFE6", - "#00005F", "#A97399", "#4B8160", "#59738A", "#FF5DA7", "#F7C9BF", "#643127", "#513A01", - "#6B94AA", "#51A058", "#A45B02", "#1D1702", "#E20027", "#E7AB63", "#4C6001", "#9C6966", - "#64547B", "#97979E", "#006A66", "#391406", "#F4D749", "#0045D2", "#006C31", "#DDB6D0", - "#7C6571", "#9FB2A4", "#00D891", "#15A08A", "#BC65E9", "#FFFFFE", "#C6DC99", "#203B3C", - - "#671190", "#6B3A64", "#F5E1FF", "#FFA0F2", "#CCAA35", "#374527", "#8BB400", "#797868", - "#C6005A", "#3B000A", "#C86240", "#29607C", "#402334", "#7D5A44", "#CCB87C", "#B88183", - "#AA5199", "#B5D6C3", "#A38469", "#9F94F0", "#A74571", "#B894A6", "#71BB8C", "#00B433", - "#789EC9", "#6D80BA", "#953F00", "#5EFF03", "#E4FFFC", "#1BE177", "#BCB1E5", "#76912F", - "#003109", "#0060CD", "#D20096", "#895563", "#29201D", "#5B3213", "#A76F42", "#89412E", - "#1A3A2A", "#494B5A", "#A88C85", "#F4ABAA", "#A3F3AB", "#00C6C8", "#EA8B66", "#958A9F", - "#BDC9D2", "#9FA064", "#BE4700", "#658188", "#83A485", "#453C23", "#47675D", "#3A3F00", - "#061203", "#DFFB71", "#868E7E", "#98D058", "#6C8F7D", "#D7BFC2", "#3C3E6E", "#D83D66", - - "#2F5D9B", "#6C5E46", "#D25B88", "#5B656C", "#00B57F", "#545C46", "#866097", "#365D25", - "#252F99", "#00CCFF", "#674E60", "#FC009C", "#92896B"] - -def reduce_dimensionality(X): - print("Calculating TSNE") - embedding= TSNE(n_jobs=20, n_components=2).fit_transform(X) - return embedding - - -def save_embedding_plot(name, embedd, labels, info): - fig = plt.figure(figsize=(10,10)) - colors= [indexcolors[int(i)] for i in labels.squeeze()] - embedding= reduce_dimensionality(embedd) - plt.scatter(embedding[:,0],embedding[:,1], s=1, c= colors) - legend_texts= [ x[0] for x in sorted(info.items(), key=lambda kv: kv[1])] - patches=[] - for i,label in enumerate(legend_texts): - patches.append(mpatches.Patch(color=indexcolors[i], label=label)) - plt.legend(handles=patches) - plt.xlabel('Dim 1', fontsize=12) - plt.ylabel('Dim 2', fontsize=12) - plt.grid(True) - plt.savefig(name) - plt.close(fig) - -def save_checkpoint(state, is_best, filename='checkpoint.pth.tar'): - torch.save(state, filename) - if is_best: - shutil.copyfile(filename, 'model_best.pth.tar') - -def load_checkpoint(filename='model_best.pth.tar'): - return torch.load(filename, map_location=torch.device('cpu')) - -class AverageMeter(object): - """Computes and stores the average and current value""" - def __init__(self): - self.reset() - - def reset(self): - self.val = 0 - self.avg = 0 - self.sum = 0 - self.count = 0 - - def update(self, val, n=1): - self.val = val - self.sum += val * n - self.count += n - self.avg = self.sum / self.count - -def accuracy(output, target, topk=(1,)): - """Computes the accuracy over the k top predictions for the specified values of k""" - with torch.no_grad(): - maxk = max(topk) - batch_size = target.size(0) - - _, pred = output.topk(maxk, 1, True, True) - pred = pred.t() - correct = pred.eq(target.view(1, -1).expand_as(pred)) - - res = [] - for k in topk: - correct_k = correct[:k].view(-1).float().sum(0, keepdim=True) - res.append(correct_k.mul_(100.0 / batch_size)) - return res - -def pdist(vectors): - distance_matrix = -2 * vectors.mm(torch.t(vectors)) + vectors.pow(2).sum(dim=1).view(1, -1) + vectors.pow(2).sum( - dim=1).view(-1, 1) - return distance_matrix - -def getCriterion(loss_type, strategy, margin): - if loss_type.lower() == 'softmax': - return torch.nn.CrossEntropyLoss() - - elif loss_type.lower() == 'siamese' and strategy == 'hard_pair': - return OnlineContrastiveLoss( - margin, HardestNegativeTripletSelector(margin)) - - else: - selector = None - if strategy == 'hardest': - selector = HardestNegativeTripletSelector(margin) - elif strategy == 'random': - selector = RandomNegativeTripletSelector(margin) - elif strategy == 'semi_hard': - selector = SemihardNegativeTripletSelector(margin) - - return OnlineTripletLoss(margin, selector) - -class PairSelector: - """ - Implementation should return indices of positive pairs and negative pairs that will be passed to compute - Contrastive Loss - return positive_pairs, negative_pairs - """ - - def __init__(self): - pass - - def get_pairs(self, embeddings, labels): - raise NotImplementedError - - -class AllPositivePairSelector(PairSelector): - """ - Discards embeddings and generates all possible pairs given labels. - If balance is True, negative pairs are a random sample to match the number of positive samples - """ - def __init__(self, balance=True): - super(AllPositivePairSelector, self).__init__() - self.balance = balance - - def get_pairs(self, embeddings, labels): - labels = labels.cpu().data.numpy() - all_pairs = np.array(list(combinations(range(len(labels)), 2))) - all_pairs = torch.LongTensor(all_pairs) - positive_pairs = all_pairs[(labels[all_pairs[:, 0]] == labels[all_pairs[:, 1]]).nonzero()] - negative_pairs = all_pairs[(labels[all_pairs[:, 0]] != labels[all_pairs[:, 1]]).nonzero()] - if self.balance: - negative_pairs = negative_pairs[torch.randperm(len(negative_pairs))[:len(positive_pairs)]] - - return positive_pairs, negative_pairs - - -class HardNegativePairSelector(PairSelector): - """ - Creates all possible positive pairs. For negative pairs, pairs with smallest distance are taken into consideration, - matching the number of positive pairs. - """ - - def __init__(self, cpu=True): - super(HardNegativePairSelector, self).__init__() - self.cpu = cpu - - def get_pairs(self, embeddings, labels): - if self.cpu: - embeddings = embeddings.cpu() - distance_matrix = pdist(embeddings.data) - - labels = labels.cpu().data.numpy() - all_pairs = np.array(list(combinations(range(len(labels)), 2))) - all_pairs = torch.LongTensor(all_pairs) - positive_pairs = all_pairs[(labels[all_pairs[:, 0]] == labels[all_pairs[:, 1]]).nonzero()] - negative_pairs = all_pairs[(labels[all_pairs[:, 0]] != labels[all_pairs[:, 1]]).nonzero()] - negative_distances = distance_matrix[negative_pairs[:, 0], negative_pairs[:, 1]] - negative_distances = negative_distances.cpu().data.numpy() - top_negatives = np.argpartition(negative_distances, len(positive_pairs))[:len(positive_pairs)] - top_negative_pairs = negative_pairs[torch.LongTensor(top_negatives)] - - return positive_pairs, top_negative_pairs - -class TripletSelector: - """ - Implementation should return indices of anchors, positive and negative samples - return np array of shape [N_triplets x 3] - """ - - def __init__(self): - pass - - def get_pairs(self, embeddings, labels): - raise NotImplementedError - - -class AllTripletSelector(TripletSelector): - """ - Returns all possible triplets - May be impractical in most cases - """ - - def __init__(self): - super(AllTripletSelector, self).__init__() - - def get_triplets(self, embeddings, labels): - labels = labels.cpu().data.numpy() - triplets = [] - for label in set(labels): - label_mask = (labels == label) - label_indices = np.where(label_mask)[0] - if len(label_indices) < 2: - continue - negative_indices = np.where(np.logical_not(label_mask))[0] - anchor_positives = list(combinations(label_indices, 2)) # All anchor-positive pairs - - # Add all negatives for all positive pairs - temp_triplets = [[anchor_positive[0], anchor_positive[1], neg_ind] for anchor_positive in anchor_positives - for neg_ind in negative_indices] - triplets += temp_triplets - - return torch.LongTensor(np.array(triplets)) - - -def hardest_negative(loss_values): - hard_negative = np.argmax(loss_values) - return hard_negative if loss_values[hard_negative] > 0 else None - - -def random_hard_negative(loss_values): - hard_negatives = np.where(loss_values > 0)[0] - return np.random.choice(hard_negatives) if len(hard_negatives) > 0 else None - - -def semihard_negative(loss_values, margin): - semihard_negatives = np.where(np.logical_and(loss_values < margin, loss_values > 0))[0] - return np.random.choice(semihard_negatives) if len(semihard_negatives) > 0 else None - - -class FunctionNegativeTripletSelector(TripletSelector): - """ - For each positive pair, takes the hardest negative sample (with the greatest triplet loss value) to create a triplet - Margin should match the margin used in triplet loss. - negative_selection_fn should take array of loss_values for a given anchor-positive pair and all negative samples - and return a negative index for that pair - """ - - def __init__(self, margin, negative_selection_fn, cpu=True): - super(FunctionNegativeTripletSelector, self).__init__() - self.cpu = cpu - self.margin = margin - self.negative_selection_fn = negative_selection_fn - - def get_triplets(self, embeddings, labels): - if self.cpu: - embeddings = embeddings.cpu() - distance_matrix = pdist(embeddings.data) - distance_matrix = distance_matrix.cpu() - - labels = labels.cpu().data.numpy() - triplets = [] - - for label in set(labels): - label_mask = (labels == label) - label_indices = np.where(label_mask)[0] - if len(label_indices) < 2: - continue - negative_indices = np.where(np.logical_not(label_mask))[0] - anchor_positives = list(combinations(label_indices, 2)) # All anchor-positive pairs - anchor_positives = np.array(anchor_positives) - - ap_distances = distance_matrix[anchor_positives[:, 0], anchor_positives[:, 1]] - for anchor_positive, ap_distance in zip(anchor_positives, ap_distances): - loss_values = ap_distance - distance_matrix[torch.LongTensor(np.array([anchor_positive[0]])), torch.LongTensor(negative_indices)] + self.margin - loss_values = loss_values.data.cpu().numpy() - hard_negative = self.negative_selection_fn(loss_values) - if hard_negative is not None: - hard_negative = negative_indices[hard_negative] - triplets.append([anchor_positive[0], anchor_positive[1], hard_negative]) - - if len(triplets) == 0: - triplets.append([anchor_positive[0], anchor_positive[1], negative_indices[0]]) - - triplets = np.array(triplets) - #print(triplets.shape[0]) - return torch.LongTensor(triplets) - - -def HardestNegativeTripletSelector(margin, cpu=False): return FunctionNegativeTripletSelector(margin=margin, - negative_selection_fn=hardest_negative, - cpu=cpu) - - -def RandomNegativeTripletSelector(margin, cpu=False): return FunctionNegativeTripletSelector(margin=margin, - negative_selection_fn=random_hard_negative, - cpu=cpu) - - -def SemihardNegativeTripletSelector(margin, cpu=False): return FunctionNegativeTripletSelector(margin=margin, - negative_selection_fn=lambda x: semihard_negative(x, margin),cpu=cpu) diff --git a/archive/active_learning/experiments/extract_features.py b/archive/active_learning/experiments/extract_features.py deleted file mode 100644 index 3cb556e8..00000000 --- a/archive/active_learning/experiments/extract_features.py +++ /dev/null @@ -1,245 +0,0 @@ -''' -extract_features.py - -Loads a pre-trained embedding model, uses it for inference to obtain embedded feature representations on input images, and explores the embedded feature space. - -''' - -import argparse, os, random, sys, time -import numpy as np -# import matplotlib.pyplot as plt -# plt.switch_backend('agg') - -from DL.utils import * -from DL.networks import * -from DL.sqlite_data_loader import * -from Database.DB_models import * - -import torch -import torch.nn as nn -from torch import optim -import torch.nn.functional as F -from torch.autograd import Variable -from torchvision import datasets, transforms, models - -from sklearn.preprocessing import StandardScaler -from sklearn.neighbors import NearestNeighbors -from sklearn.cluster import KMeans - -# class EmbeddingNet(nn.Module): -# def __init__(self, architecture, feat_dim, use_pretrained=False): -# super(EmbeddingNet, self).__init__() -# self.feat_dim= feat_dim -# self.inner_model = models.__dict__[architecture](pretrained=use_pretrained) -# if architecture.startswith('resnet'): -# in_feats= self.inner_model.fc.in_features -# self.inner_model.fc = nn.Linear(in_feats, feat_dim) -# elif architecture.startswith('inception'): -# in_feats= self.inner_model.fc.in_features -# self.inner_model.fc = nn.Linear(in_feats, feat_dim) -# if architecture.startswith('densenet'): -# in_feats= self.inner_model.classifier.in_features -# self.inner_model.classifier = nn.Linear(in_feats, feat_dim) -# if architecture.startswith('vgg'): -# in_feats= self.inner_model.classifier._modules['6'].in_features -# self.inner_model.classifier._modules['6'] = nn.Linear(in_feats, feat_dim) -# if architecture.startswith('alexnet'): -# in_feats= self.inner_model.classifier._modules['6'].in_features -# self.inner_model.classifier._modules['6'] = nn.Linear(in_feats, feat_dim) - -# def forward(self, x): -# return self.inner_model.forward(x) - -# class NormalizedEmbeddingNet(EmbeddingNet): -# def __init__(self, architecture, feat_dim, use_pretrained=False): -# EmbeddingNet.__init__(self, architecture, feat_dim, use_pretrained = use_pretrained) - -# def forward(self, x): -# embedding = F.normalize(self.inner_model.forward(x))*10.0 -# return embedding, embedding - -# def get_random_images(num, image_dir, test_transforms): -# data = datasets.ImageFolder(image_dir, transform=test_transforms) # slight abuse; this expects subfolders corresponding to classes but we have no classes here -# indices = list(range(len(data))) -# np.random.shuffle(indices) -# idx = indices[:num] -# from torch.utils.data.sampler import SubsetRandomSampler -# sampler = SubsetRandomSampler(idx) -# loader = torch.utils.data.DataLoader(data, -# sampler=sampler, batch_size=num) -# dataiter = iter(loader) -# images, labels = dataiter.next() -# return images, labels - -# def predict_image(image, model, test_transforms): -# device = torch.device("cuda" if torch.cuda.is_available() -# else "cpu") -# image_tensor = test_transforms(image).float() -# image_tensor = image_tensor.unsqueeze_(0) -# input = Variable(image_tensor) -# input = input.to(device) -# output = model(input)[0] -# return output.data.cpu().numpy() - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--db_name', default='missouricameratraps', type=str, help='Name of the training (target) data Postgres DB.') - parser.add_argument('--db_user', default='new_user', type=str, help='Name of the user accessing the Postgres DB.') - parser.add_argument('--db_password', default='new_user_password', type=str, help='Password of the user accessing the Postgres DB.') - parser.add_argument('--base_model', type=str, help='Path to latest embedding model checkpoint.') - parser.add_argument('--crop_dir', type=str, help='Path to directory with cropped images to get embedding features for.') - parser.add_argument('--num', type=int, help='Number of samples to draw from dataset to get embedding features.') - parser.add_argument('--random_seed', type=int, help='Random seed to get same samples from database.') - args = parser.parse_args() - - random.seed(args.random_seed) - np.random.seed(args.random_seed) - - # Connect to database and initialize db_proxy - ## database connection credentials - DB_NAME = args.db_name - USER = args.db_user - PASSWORD = args.db_password - target_db = PostgresqlDatabase(DB_NAME, user=USER, password=PASSWORD, host='localhost') - target_db.connect(reuse_if_open=True) - db_proxy.initialize(target_db) - ## load the dataset - dataset_query = Detection.select(Detection.image_id, Oracle.label, Detection.kind).join(Oracle).limit(args.num) - dataset = SQLDataLoader(args.crop_dir, query=dataset_query, is_training=False, kind=DetectionKind.ModelDetection.value, num_workers=8, limit=args.num) - - - # Load the saved embedding model from the checkpoint - checkpoint = load_checkpoint(args.base_model) - if checkpoint['loss_type'].lower() == 'center' or checkpoint['loss_type'].lower() == 'softmax': - embedding_net = SoftmaxNet(checkpoint['arch'], checkpoint['feat_dim'], checkpoint['num_classes'], False) - else: - embedding_net = NormalizedEmbeddingNet(checkpoint['arch'], checkpoint['feat_dim'], False) - model = torch.nn.DataParallel(embedding_net).cuda() - model.load_state_dict(checkpoint['state_dict']) - ## update the dataset embedding - dataset.updateEmbedding(model) - - print('len(dataset)', len(dataset)) - random_anchor_idx = np.random.randint(len(dataset)) - print(random_anchor_idx) - - - - # # Create a folder for saving embedding visualizations with this model checkpoint - # model_emb_dirname = os.path.basename(args.base_model).split('.')[0] - # os.makedirs(model_emb_dirname, exist_ok=True) - # plot_embedding_images(dataset.em[:], np.asarray(dataset.getlabels()) , dataset.getpaths(), {}, model_emb_dirname+'/embedding_plot.png') - model_emb_dirname = os.path.basename(args.base_model).split('.')[0]+'_temp' - os.makedirs(model_emb_dirname, exist_ok=True) - - # dataset.embedding_mode() - X_train = dataset.em[range(len(dataset))] - y_train = np.asarray(dataset.getalllabels()) - imagepaths = dataset.getallpaths() - random_anchor_img = dataset.loader(imagepaths[random_anchor_idx].split('.JPG')[0]) - random_anchor_img.save(model_emb_dirname+"/anchor_img.png") - random_anchor_img_np = np.asarray(random_anchor_img) - print(random_anchor_img_np.shape) - # assert 2==3, 'break' - - # datasetindices = list(range(len(dataset))) - # np.random.shuffle(datasetindices) - # random_indices = datasetindices[:args.num] - # print(random_indices) - - # selected_sample_features = np.array([]).reshape(0, 256) - # selected_sample_labels = [] - selected_sample_images = [] - - # for idx in random_indices: - # selected_sample_features = np.vstack([selected_sample_features, X_train[idx]]) - # selected_sample_labels.append(y_train[idx]) - # img_path = imagepaths[idx].split('.JPG')[0] - # image = dataset.loader(img_path) - # selected_sample_images.append(image) - - - # # TRY NEAREST NEIGHBORS WALK THROUGH EMBEDDING - # nbrs = NearestNeighbors(n_neighbors=args.num).fit(selected_sample_features) - # distances, indices = nbrs.kneighbors(selected_sample_features) - timer = time.time() - nbrs = NearestNeighbors(n_neighbors=args.num).fit(X_train) - print('Finished fitting nearest neighbors for whole dataset in %0.2f seconds'%(float(time.time() - timer))) - distances, indices = nbrs.kneighbors(X_train) - ten_closest_to_anchor = indices[random_anchor_idx, 1:11] - print(distances[random_anchor_idx, 0:11]) - - for idx in ten_closest_to_anchor: - img_path = imagepaths[idx].split('.JPG')[0] - image = dataset.loader(img_path) - selected_sample_images.append(image) - - # plot_embedding_images(dataset.em[:], np.asarray(dataset.getlabels()) , dataset.getpaths(), {}, model_emb_dirname+'/embedding_plot.png') - - # idx_w_closest_nbr = np.where(distances[:,1] == min(distances[:,1]))[0][0] - # order = [idx_w_closest_nbr] - # for ii in range(len(distances)): - # distances[ii, 0] = np.inf - - # while len(order)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# What do the channels in the network see?\n", - "# # For a given channel, store the images with highest activation in that channel\n", - "channel_no = 441#random.sample(range(l4b1conv1_outputs[0].shape[1]), 1)[0]\n", - "pq = queue.PriorityQueue()\n", - "\n", - "for idx in range(len(l4b1conv1_outputs)):\n", - " im_activation_maps = l4b1conv1_outputs[idx]\n", - " im_channel_activation_map = im_activation_maps[0,channel_no,:,:].cpu().detach().numpy()\n", - " max_activation_pos = np.unravel_index(np.argmax(im_channel_activation_map, axis=None), im_channel_activation_map.shape)\n", - " max_activation = im_channel_activation_map[max_activation_pos]\n", - " pqitem = PQItem(-max_activation, idx, (im_channel_activation_map, max_activation_pos))\n", - " pq.put(pqitem)\n", - "\n", - "dataset.image_mode()\n", - "\n", - "plt.subplots(figsize=(12,40))\n", - "k = 10\n", - "\n", - "for i in range(k):\n", - " max_activating_img_data = pq.get()\n", - " max_activating_img = dataset.loader(sample_ids[max_activating_img_data.idx])\n", - " plt.subplot(k,2,i*2+1)\n", - " plt.imshow(np.asarray(max_activating_img))\n", - " plt.subplot(k,2,i*2+2)\n", - " plt.imshow(max_activating_img_data.act_vec[0][:,:])\n" - ] - }, - { - "cell_type": "code", - "execution_count": 87, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "441\n" - ] - } - ], - "source": [ - "print(channel_no)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "query_sid = random.sample(sample_ids, 1)[0]\n", - "query_sid_idx = sample_ids.index(query_sid)\n", - "\n", - "# random_sid_idx = 0\n", - "# random_sid = sample_ids[random_sid_idx]\n", - "\n", - "# Visualize the random sample image to use as query image\n", - "dataset.image_mode()\n", - "query_img = dataset.loader(query_sid)\n", - "query_img_tensor = dataset.eval_transform(query_img)\n", - "print(query_img_tensor.shape)\n", - "plt.subplot(1,2,1)\n", - "plt.imshow(np.asarray(query_img))\n", - "plt.subplot(1,2,2)\n", - "plt.imshow(np.asarray(ToPILImage()(query_img_tensor)))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def visualize_channel_activations(img_layer_output, channels_to_vis):\n", - " n_channels_to_vis = len(channels_to_vis)\n", - " fig, axs = plt.subplots(figsize=(23,30))\n", - " for i in range(n_channels_to_vis):\n", - " plt.subplot(1,n_channels_to_vis,i+1)\n", - " channel_no = channels_to_vis[i]\n", - " plt.imshow(img_layer_output[0,channel_no,:,:], cmap='viridis')\n", - " plt.title(str(channel_no)) " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "query_img_layer_output = l3b1conv1_outputs[query_sid_idx].cpu().detach().numpy()\n", - "rand_channels_to_vis = random.sample(range(query_img_layer_output.shape[1]), 5)\n", - "visualize_channel_activations(query_img_layer_output, rand_channels_to_vis)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "query_img_layer_output = l4b1conv1_outputs[query_sid_idx].cpu().detach().numpy()\n", - "rand_channels_to_vis = random.sample(range(query_img_layer_output.shape[1]), 5)\n", - "visualize_channel_activations(query_img_layer_output, rand_channels_to_vis)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def find_images_with_similar_activations(layer_outputs, query_sid_idx, position, padding, k=5):\n", - " query_img_layer_output = layer_outputs[query_sid_idx].cpu().detach().numpy()\n", - " query_activations_at_pos = query_img_layer_output[0, :, position[0], position[1]]\n", - " \n", - " pq = queue.PriorityQueue()\n", - " \n", - " for imidx in tqdm.tqdm(range(len(sample_ids))):\n", - " if imidx == query_sid_idx:\n", - " continue\n", - " img_layer_output = layer_outputs[imidx].cpu().detach().numpy()\n", - " # Iterate over positions in padding region\n", - " ymin = max(position[0] - padding, 0)\n", - " ymax = min(position[0] + padding, query_img_layer_output.shape[2]-1)\n", - " xmin = max(position[1] - padding, 0)\n", - " xmax = min(position[1] + padding, query_img_layer_output.shape[3]-1)\n", - " for y in range(ymin, ymax+1):\n", - " for x in range(xmin, xmax+1):\n", - " activations_at_pos = img_layer_output[0, :, y, x]\n", - " edist = np.linalg.norm(activations_at_pos - query_activations_at_pos)\n", - " pqitem = PQItem(edist, imidx, activations_at_pos)\n", - " pq.put(pqitem)\n", - "\n", - " closest_k_idx = []\n", - " closest_k_edist = []\n", - " while len(closest_k_idx) < k:\n", - " next_pqitem = pq.get()\n", - " if next_pqitem.idx in closest_k_idx:\n", - " continue\n", - " else:\n", - " closest_k_idx.append(next_pqitem.idx)\n", - " closest_k_edist.append(next_pqitem.euc_dist)\n", - "# print(closest_k_edist)\n", - " \n", - " return closest_k_idx\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "k = 5\n", - "closest_k_layer3 = find_images_with_similar_activations(l3b1conv1_outputs, query_sid_idx, (11,12), 5, k)\n", - "closest_k_layer4 = find_images_with_similar_activations(l4b1conv1_outputs, query_sid_idx, (5,5), 2, k)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "plt.figure(figsize=(10, 20))\n", - "for i in range(k):\n", - " min_edist_sid = sample_ids[closest_k_layer3[i]]\n", - " # Visualize the image\n", - " dataset.image_mode()\n", - " min_edist_img = dataset.loader(min_edist_sid)\n", - " min_edist_img_tensor = dataset.eval_transform(min_edist_img)\n", - " plt.subplot(k,2,i*2 + 1)\n", - " plt.imshow(np.asarray(min_edist_img))\n", - " plt.subplot(k,2,i*2 + 2)\n", - " plt.imshow(np.asarray(ToPILImage()(min_edist_img_tensor)))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "plt.figure(figsize=(10, 20))\n", - "for i in range(k):\n", - " min_edist_sid = sample_ids[closest_k_layer4[i]]\n", - " # Visualize the image\n", - " dataset.image_mode()\n", - " min_edist_img = dataset.loader(min_edist_sid)\n", - " min_edist_img_tensor = dataset.eval_transform(min_edist_img)\n", - " plt.subplot(k,2,i*2 + 1)\n", - " plt.imshow(np.asarray(min_edist_img))\n", - " plt.subplot(k,2,i*2 + 2)\n", - " plt.imshow(np.asarray(ToPILImage()(min_edist_img_tensor)))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "query_img_layer_output = l4b1conv1_outputs[query_sid_idx].cpu().detach().numpy()\n", - "query_activations_at_pos = query_img_layer_output[0, :, 5, 5]\n", - "img_layer_output = l4b1conv1_outputs[closest_k_layer4[0]].cpu().detach().numpy()\n", - "img_activations_at_pos = img_layer_output[0, :, 5, 5]\n", - "\n", - "plt.subplot(2,1,1)\n", - "plt.bar(range(len(query_activations_at_pos)), query_activations_at_pos)\n", - "plt.subplot(2,1,2)\n", - "plt.bar(range(len(img_activations_at_pos)), img_activations_at_pos)\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(random_sid_idx)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Cool sample IDs: 4509, 1327" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:py35]", - "language": "python", - "name": "conda-env-py35-py" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.5.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/anchor_img.png b/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/anchor_img.png deleted file mode 100644 index 517c60e9..00000000 Binary files a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/anchor_img.png and /dev/null differ diff --git a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img0.png b/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img0.png deleted file mode 100644 index d1b3b435..00000000 Binary files a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img0.png and /dev/null differ diff --git a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img1.png b/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img1.png deleted file mode 100644 index 281bb76b..00000000 Binary files a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img1.png and /dev/null differ diff --git a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img2.png b/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img2.png deleted file mode 100644 index 5e87217f..00000000 Binary files a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img2.png and /dev/null differ diff --git a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img3.png b/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img3.png deleted file mode 100644 index 6c6e88a6..00000000 Binary files a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img3.png and /dev/null differ diff --git a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img4.png b/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img4.png deleted file mode 100644 index 8bf7f70b..00000000 Binary files a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img4.png and /dev/null differ diff --git a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img5.png b/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img5.png deleted file mode 100644 index 5d29dd39..00000000 Binary files a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img5.png and /dev/null differ diff --git a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img6.png b/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img6.png deleted file mode 100644 index 939c2c68..00000000 Binary files a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img6.png and /dev/null differ diff --git a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img7.png b/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img7.png deleted file mode 100644 index 3dde26ba..00000000 Binary files a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img7.png and /dev/null differ diff --git a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img8.png b/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img8.png deleted file mode 100644 index 383489ab..00000000 Binary files a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img8.png and /dev/null differ diff --git a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img9.png b/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img9.png deleted file mode 100644 index 11e77023..00000000 Binary files a/archive/active_learning/experiments/output/triplet_resnet50_1499_temp/img9.png and /dev/null differ diff --git a/archive/active_learning/experiments/sampling_methods.py b/archive/active_learning/experiments/sampling_methods.py deleted file mode 100644 index 25c7f359..00000000 --- a/archive/active_learning/experiments/sampling_methods.py +++ /dev/null @@ -1,95 +0,0 @@ -import argparse, random, sys, time -import torch -from sklearn.preprocessing import StandardScaler -from sklearn.neighbors import NearestNeighbors - -sys.path.append("..") -from DL.utils import * -from DL.networks import * -from Database.DB_models import * -from DL.sqlite_data_loader import SQLDataLoader - - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--db_name', default='missouricameratraps', type=str, help='Name of the training (target) data Postgres DB.') - parser.add_argument('--db_user', default='user', type=str, help='Name of the user accessing the Postgres DB.') - parser.add_argument('--db_password', default='password', type=str, help='Password of the user accessing the Postgres DB.') - parser.add_argument('--num', default=5000, type=int, help='Number of samples to draw from dataset to get embedding features.') - parser.add_argument('--crop_dir', type=str, help='Path to directory with cropped images to get embedding features for.') - parser.add_argument('--base_model', type=str, help='Path to latest embedding model checkpoint.') - parser.add_argument('--random_seed', default=1234, type=int, help='Random seed to get same samples from database.') - args = parser.parse_args() - - random.seed(args.random_seed) - np.random.seed(args.random_seed) - - BASE_MODEL = args.base_model - DB_NAME = args.db_name - USER = args.db_user - PASSWORD = args.db_password - - # Connect to database and sample a dataset - target_db = PostgresqlDatabase(DB_NAME, user=USER, password=PASSWORD, host='localhost') - target_db.connect(reuse_if_open=True) - db_proxy.initialize(target_db) - dataset_query = Detection.select(Detection.image_id, Oracle.label, Detection.kind).join(Oracle).limit(args.num) - dataset = SQLDataLoader(args.crop_dir, query=dataset_query, is_training=False, kind=DetectionKind.ModelDetection.value, num_workers=8, limit=args.num) - imagepaths = dataset.getallpaths() - - # Load the saved embedding model from the checkpoint - checkpoint = load_checkpoint(BASE_MODEL) - if checkpoint['loss_type'].lower() == 'center' or checkpoint['loss_type'].lower() == 'softmax': - embedding_net = SoftmaxNet(checkpoint['arch'], checkpoint['feat_dim'], checkpoint['num_classes'], False) - else: - embedding_net = NormalizedEmbeddingNet(checkpoint['arch'], checkpoint['feat_dim'], False) - model = torch.nn.DataParallel(embedding_net).cuda() - model.load_state_dict(checkpoint['state_dict']) - - # Update the dataset embedding - dataset.updateEmbedding(model) - - # Get a random query image - query_idx = np.random.randint(len(dataset.samples)) - query_img = dataset.loader(imagepaths[query_idx].split('.')[0]) - query_img.save("query_img.png") - - - # # # # IMAGES IN THE SAME SEQUENCE # # # # - matching_image_entries = (Image - .select(Image.seq_id, Image.seq_num_frames, Image.frame_num) - .where((Image.file_name == imagepaths[query_idx]))) - mie = matching_image_entries.get() - if mie.seq_num_frames > 1: - images_in_seq = (Image - .select(Image.file_name) - .where((Image.seq_id == mie.seq_id) & (Image.file_name << imagepaths)) - ) - images_in_seq = sorted(list(set([i.file_name for i in images_in_seq]))) - seq_img_idx = [imagepaths.index(im) for im in images_in_seq] - for i in range(len(seq_img_idx)): - if images_in_seq[i] != imagepaths[query_idx]: - img = dataset.loader(images_in_seq[i].split('.')[0]) - img.save('same_seq_img%d.png'%i) - - - # assert 2==3, 'break' - - # # # # CLOSEST IN (EMBEDDING) FEATURE SPACE # # # # - timer = time.time() - nbrs = NearestNeighbors(n_neighbors=11).fit(dataset.em) - print('Finished fitting nearest neighbors for whole dataset in %0.2f seconds'%(float(time.time() - timer))) - distances, indices = nbrs.kneighbors(dataset.em) - query_nbrs_indices = indices[query_idx, 1:11] - for i in range(len(query_nbrs_indices)): - nbr_idx = query_nbrs_indices[i] - nbr_img = dataset.loader(imagepaths[nbr_idx].split('.')[0]) - nbr_img.save("embedding_nnbr_img%d.png"%i) - - - print('Success!') - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/archive/active_learning/experiments/visualize_feature_maps.py b/archive/active_learning/experiments/visualize_feature_maps.py deleted file mode 100644 index a48c5cd8..00000000 --- a/archive/active_learning/experiments/visualize_feature_maps.py +++ /dev/null @@ -1,125 +0,0 @@ -import argparse, random, sys, time -import PIL -import torch -import torchvision.models as models -import matplotlib.pyplot as plt -from torchvision.transforms import * - -sys.path.append("..") -from DL.utils import * -from DL.networks import * -from Database.DB_models import * -from DL.sqlite_data_loader import SQLDataLoader - -# class SaveFeatures(): -# def __init__(self, module): -# self.hook = module.register_forward_hook(self.hook_fn) -# def hook_fn(self, module, input, output): -# self.features = torch.tensor(output, requires_grad=True).cuda() -# def close(self): -# self.hook.remove() - -outputs = [] -def hook(module, input, output): - outputs.append(output) - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--db_name', default='missouricameratraps', type=str, help='Name of the training (target) data Postgres DB.') - parser.add_argument('--db_user', default='user', type=str, help='Name of the user accessing the Postgres DB.') - parser.add_argument('--db_password', default='password', type=str, help='Password of the user accessing the Postgres DB.') - parser.add_argument('--num', default=1000, type=int, help='Number of samples to draw from dataset to get embedding features.') - parser.add_argument('--crop_dir', type=str, help='Path to directory with cropped images to get embedding features for.') - parser.add_argument('--base_model', type=str, help='Path to latest embedding model checkpoint.') - parser.add_argument('--random_seed', default=1234, type=int, help='Random seed to get same samples from database.') - args = parser.parse_args() - - random.seed(args.random_seed) - np.random.seed(args.random_seed) - - BASE_MODEL = args.base_model - - # Load the saved embedding model from the checkpoint - checkpoint = load_checkpoint(BASE_MODEL) - if checkpoint['loss_type'].lower() == 'center' or checkpoint['loss_type'].lower() == 'softmax': - embedding_net = SoftmaxNet(checkpoint['arch'], checkpoint['feat_dim'], checkpoint['num_classes'], False) - else: - embedding_net = NormalizedEmbeddingNet(checkpoint['arch'], checkpoint['feat_dim'], False) - model = torch.nn.DataParallel(embedding_net).cuda() - model.load_state_dict(checkpoint['state_dict']) - model.eval() - - - # Get a sample from the database, with eval transforms applied, etc. - DB_NAME = args.db_name - USER = args.db_user - PASSWORD = args.db_password - - # Connect to database and sample a dataset - target_db = PostgresqlDatabase(DB_NAME, user=USER, password=PASSWORD, host='localhost') - target_db.connect(reuse_if_open=True) - db_proxy.initialize(target_db) - dataset_query = Detection.select(Detection.image_id, Oracle.label, Detection.kind).join(Oracle).limit(args.num) - dataset = SQLDataLoader(args.crop_dir, query=dataset_query, is_training=False, kind=DetectionKind.ModelDetection.value, num_workers=8, limit=args.num) - imagepaths = dataset.getallpaths() - - sample_image_path = '0ca68a6f-6348-4456-8fb5-c067e2cbfe14'#'0a170ee9-166d-45df-8f45-b14550fc124e'#'43e3d2a6-38ea-4d17-a712-1b0feab92d58'#'0ef0f79f-7b58-473d-abbf-75bba59e834d' - dataset.image_mode() - sample_image = dataset.loader(sample_image_path) - sample_image.save('sample_image_for_activations.png') - print(sample_image.size) - sample_image = dataset.eval_transform(sample_image) - print(sample_image.shape) - - # output = model.forward(sample_image.unsqueeze(0)) - # print(output) - - model_inner_resnet = list(model.children())[0].inner_model - model_inner_resnet.eval() - model_inner_resnet.layer1[0].conv2.register_forward_hook(hook) - - output = model.forward(sample_image.unsqueeze(0)) - intermediate_output = outputs[0].cpu().detach().numpy() - print(intermediate_output.shape) - - for i in range(intermediate_output.shape[1]): - plt.subplot(8,8,i+1) - plt.imshow(intermediate_output[0,i,:,:], cmap='viridis') - plt.axis('off') - plt.suptitle('ResNet Layer1 Conv2 Activations') - plt.savefig('temp.png') - - - # with torch.no_grad(): - # sample_image_input = sample_image.cuda(non_blocking=True) - # _, output = model(sample_image_input) # compute output - # print(output) - - # sample_image = PILImage.open(sample_image_path).convert('RGB') - # sample_image = transforms.Compose([Resize([256, 256]), CenterCrop(([[224,224]])), ToTensor(), Normalize([0.369875, 0.388726, 0.347536], [0.136821, 0.143952, 0.145229])])(sample_image) - - # print(list(model_inner_resnet.children())) - # print(model_inner_resnet.fc) - # print(model_inner_resnet.fc0) - # # print(model_inner_resnet.layer4[0].conv2) - # # print(type(model)) - # # print(len(list(model_inner_resnet.children()))) - # # print(list(model.children())) - # # print(list(list(model.children())[0].children())) - - # img = np.uint8(np.random.uniform(150, 180, (56, 56, 3)))/255 - # img_tensor = torch.unsqueeze(torch.from_numpy(img), 0) - - # full_out = model_inner_resnet.forward(img_tensor) - # print(full_out) - # model(img_tensor) - # activations = SaveFeatures(model_inner_resnet.layer4[0].conv2) - # print(activations.features) - # print(type(activations.features)) - - # activations.close() - - - -if __name__=='__main__': - main() \ No newline at end of file diff --git a/archive/active_learning/labeling_tool/README.md b/archive/active_learning/labeling_tool/README.md deleted file mode 100644 index dc6f7b4a..00000000 --- a/archive/active_learning/labeling_tool/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Labeling Tool for Species Classification with Active Learning - -This web interface allows users to annotate cropped images for training a species classification model with active learning. Built with [`bottle`](https://bottlepy.org/docs/dev/). - -## Setup - -1. Create an `images` folder containing full-size camera trap images, which may be in nested directories. - -2. (Required if COCO bounding-box annotations are not available, otherwise optional.) Use `run_tf_detector_batch.py` to get predicted bounding boxes in an `detector_output.csv` file. - -3. Create a `crops` folder containing cropped images either obtained from COCO bounding-box annotations (from running `crop_images_from_coco_bboxes.py` in `data_preprocessing`) or from bounding boxes predicted by a detector (`crop_images_from_batch_api_detections.py`). These scripts also create a `crops.json` file in the `crops` folder that contains information about each cropped image and its source file. - -4. Create a text file listing the classes to use while labeling the species, put it inlabeling_tool/class_lists. - -5. Initialize a PostgreSQL database and populate it using `initialize_target_db.py` in `Database`. - -## Usage - -Determine which host and which port you will use to serve the web app. e.g.: - -* Serve the app locally. In this case you should provide `runapp.py` with the CLI arguments `--host localhost` and `--port [ANY PORT NUMBER YOU CHOOSE]`. In Line 243 of `labeling_tool/static/html/index.html` you should change the urlPrefix to `http://localhost:[SPECIFIED PORT NUMBER]`. -* Serve the app on a remote VM. In this case you should provide `runapp.py` with CLI arguments `--host 0.0.0.0` and `--port [PORT YOU HAVE INBOUND CONNECTIONS FOR]`. In Line 243 of `labeling_tool/static/html/index.html` you should change urlPrefix to the DNS name or IP address of the VM, along with the exposed port number. - -```bash -cd labeling_tool -python runapp.py --db_name MYDATABASE --db_user USERNAME --db_password PASSWORD --crop_dir PATH_TO_CROPS --class_list class_lists/MYCLASSLIST.TXT --embedding_checkpoint PATH_TO_EMBEDDING_MODEL --checkpoint_dir PATH_TO_OUTPUT_CHECKPOINT_DIR -``` diff --git a/archive/active_learning/labeling_tool/class_lists/missouricameratraps.txt b/archive/active_learning/labeling_tool/class_lists/missouricameratraps.txt deleted file mode 100644 index 29fa4dbe..00000000 --- a/archive/active_learning/labeling_tool/class_lists/missouricameratraps.txt +++ /dev/null @@ -1,20 +0,0 @@ -Agouti -Bird Spec -Coiban Agouti -Collared Peccary -Common Opossum -European Hare -Great Tinamou -Mouflon -Ocelot -Paca -Red Brocket Deer -Red Deer -Red Fox -Red Squirrel -Roe Deer -Spiny Rat -White-Nosed Coati -White Tailed Deer -Wild Boar -Wood Mouse diff --git a/archive/active_learning/labeling_tool/class_lists/wpzurbanwildlandcarnivoreproject.txt b/archive/active_learning/labeling_tool/class_lists/wpzurbanwildlandcarnivoreproject.txt deleted file mode 100644 index ad6210e3..00000000 --- a/archive/active_learning/labeling_tool/class_lists/wpzurbanwildlandcarnivoreproject.txt +++ /dev/null @@ -1,44 +0,0 @@ -American Badger -American Black Bear -American Marten -American Robin -Bobcat -Canada Lynx -Common Raven -Coyote -Domestic Cat -Domestic Chicken -Domestic Cow -Domestic Dog -Domestic Goat -Domestic Horse -Douglas'S Squirrel -Eastern Cottontail -Eastern Gray Squirrel -Elk Aka Red Deer -Elk/Red Deer -Grey Jay -Human -Moose -Mule Deer -North American Otter -Northern Raccoon -Other Bird Species -Puma -Raptor Species -Snowshoe Hare -Steller'S Jay -Striped Skunk -Unknown Animal -Unknown Bird -Unknown Canid -Unknown Chipmunk -Unknown Felid -Unknown Ground Squirrel -Unknown Rabbit/Hare -Unknown Small Rodent -Unknown Small Weasel -Unknown Squirrel -Virginia Opossum -Western Spotted Skunk -Wolverine diff --git a/archive/active_learning/labeling_tool/labeling_tool.png b/archive/active_learning/labeling_tool/labeling_tool.png deleted file mode 100644 index 2fcf2c44..00000000 Binary files a/archive/active_learning/labeling_tool/labeling_tool.png and /dev/null differ diff --git a/archive/active_learning/labeling_tool/runapp.py b/archive/active_learning/labeling_tool/runapp.py deleted file mode 100644 index 568aa192..00000000 --- a/archive/active_learning/labeling_tool/runapp.py +++ /dev/null @@ -1,575 +0,0 @@ -''' -runapp.py - -Starts running a web application for labeling samples. -''' -import argparse, bottle, itertools, json, psycopg2, sys, time -import numpy as np -from peewee import * -from sklearn.neural_network import MLPClassifier -from sklearn.externals import joblib - -sys.path.append('../') -from Database.DB_models import * -from DL.sqlite_data_loader import SQLDataLoader -from DL.networks import * -from DL.losses import * -from DL.utils import * -from DL.Engine import Engine - -from sampling_methods.constants import get_AL_sampler -from sampling_methods.constants import get_wrapper_AL_mapping -get_wrapper_AL_mapping() - -import torch -import torch.nn as nn -import torch.nn.parallel -import torch.backends.cudnn as cudnn -import torch.distributed as dist -import torch.optim -import torch.utils.data -import torch.utils.data.distributed - - -#--------some stuff needed to get AJAX to work with bottle?--------# -def enable_cors(): - ''' - From https://gist.github.com/richard-flosi/3789163 - This globally enables Cross-Origin Resource Sharing (CORS) headers for every response from this server. - ''' - bottle.response.headers['Access-Control-Allow-Origin'] = '*' - bottle.response.headers['Access-Control-Allow-Methods'] = 'PUT, GET, POST, DELETE, OPTIONS' - bottle.response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token' - -def do_options(): - ''' - This seems necessary for CORS to work. - ''' - bottle.response.status = 204 - return - -def moveRecords(dataset, srcKind, destKind, rList): - for e in rList: - if e in dataset.set_indices[srcKind]: - dataset.set_indices[srcKind].remove(e) - dataset.set_indices[destKind].append(e) - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Run a web user interface for labeling camera trap images for classification.') - parser.add_argument('--host', type=str, default='0.0.0.0', help='Web server host to bind to.')## default='localhost', help='Web server host to bind to.') - parser.add_argument('--port', type=int, default=8080, help='Web server port port to listen on.') - parser.add_argument('--verbose', type=bool, default=True, help='Enable verbose debugging.') - parser.add_argument('--db_name', type=str, default='missouricameratraps', help='Name of Postgres DB with target dataset tables.') - parser.add_argument('--db_user', type=str, default=None, help='Name of user accessing Postgres DB.') - parser.add_argument('--db_password', type=str, default=None, help='Password of user accessing Postgres DB.') - parser.add_argument('--db_query_limit', default=3000, type=int, help='Maximum number of records to read from the Postgres DB.') - parser.add_argument('--crop_dir', type=str, required=True, help='Path to directory containing cropped images to display.') - parser.add_argument('--class_list', type=str, required=True, help='Path to .txt file containing classes in dataset.') - parser.add_argument('--checkpoint_dir', type=str, required=True, help='Path to directory where checkpoints will be stored.') - parser.add_argument('--classifier_checkpoint', type=str, default='', help='Path to a specific classifier checkpoint to load initially.') - parser.add_argument('--embedding_checkpoint', type=str, default='/home/lynx/pretrainedmodels/embedding_triplet_resnet50_1499/triplet_resnet50_1499.tar', help='Path to a specific embedding checkpoint to load initially.') - args = parser.parse_args(sys.argv[1:]) - - args.strategy = 'confidence' ## TODO: hard coding this for now - - # -------------------------------------------------------------------------------- # - # PREPARE TO QUEUE IMAGES FOR LABELING - # -------------------------------------------------------------------------------- # - - ## Connect as USER to database DB_NAME through peewee and initialize database proxy - DB_NAME = args.db_name - USER = args.db_user - PASSWORD = args.db_password - target_db = PostgresqlDatabase(DB_NAME, user=USER, password=PASSWORD, host='localhost') - target_db.connect(reuse_if_open=True) - db_proxy.initialize(target_db) - - ## Load embedding model - checkpoint = load_checkpoint(args.embedding_checkpoint) - if checkpoint['loss_type'].lower() == 'center' or checkpoint['loss_type'].lower() == 'softmax': - embedding_net = SoftmaxNet(checkpoint['arch'], checkpoint['feat_dim'], checkpoint['num_classes'], False) - else: - embedding_net = NormalizedEmbeddingNet(checkpoint['arch'], checkpoint['feat_dim'], False) - model = torch.nn.DataParallel(embedding_net).cuda() - model.load_state_dict(checkpoint['state_dict']) - - # ---------------------------------------------------------------------- # - # CREATE QUEUE OF IMAGES TO LABEL - # ---------------------------------------------------------------------- # - dataset_query = (Detection - .select(Detection.id, Detection.category_id, Detection.kind, Detection.category_confidence, Detection.bbox_confidence, Image.file_name, Image.grayscale) - .join(Image, on=(Image.id == Detection.image)) - .order_by(fn.Random()) - .limit(args.db_query_limit)) - dataset = SQLDataLoader(args.crop_dir, query=dataset_query, is_training=False, kind=DetectionKind.ModelDetection.value, num_workers=8) - - grayscale_values = [rec[6] for rec in dataset.samples] - grayscale_indices = list(itertools.compress(range(len(grayscale_values)), grayscale_values)) # records with grayscale images - color_indices = list(set(range(len(dataset.samples))) - set(grayscale_indices)) # records with color images - detection_conf_values = [rec[4] for rec in dataset.samples] - dataset.updateEmbedding(model) - dataset.embedding_mode() - dataset.train() - - - kwargs = {} - kwargs["N"] = 25 - kwargs["already_selected"] = set() - if args.classifier_checkpoint is not '': - print('loading pre-trained classifier') - kwargs["model"] = joblib.load(args.classifier_checkpoint) - classifier_trained = True - sampler = get_AL_sampler('confidence')(dataset.em, dataset.getalllabels(), 1234) - - # Use classifier to generate predictions - dataset.set_kind(DetectionKind.ModelDetection.value) - X_pred = dataset.em[dataset.current_set] - y_pred = kwargs["model"].predict(X_pred) - - # # Update model predicted class in PostgreSQL database - # for pos in range(len(y_pred)): - # idx = dataset.current_set[pos] - # det_id = dataset.samples[idx][0] - # matching_detection_entries = (Detection - # .select(Detection.id, Detection.category_id) - # .where((Detection.id == det_id))) - # mde = matching_detection_entries.get() - # command = Detection.update(category_id=y_pred[pos]).where(Detection.id == mde.id) - # command.execute() - - # Alternative: batch update PostgreSQL database - # timer = time.time() - # det_ids = [dataset.samples[dataset.current_set[pos]][0] for pos in range(len(y_pred))] - # y_pred = [int(y) for y in y_pred] - # det_id_pred_pairs = list(zip(det_ids, y_pred)) - # case_statement = Case(Detection.id, det_id_pred_pairs) - # command = Detection.update(category_id=case_statement).where(Detection.id.in_(det_ids))# switch where and update? - # command.execute() - # print('Updating the database the other way took %0.2f seconds'%(time.time() - timer)) - - # Update dataset dataloader - for pos in range(len(y_pred)): - idx = dataset.current_set[pos] - sample_data = list(dataset.samples[idx]) - sample_data[1] = y_pred[pos] - dataset.samples[idx] = tuple(sample_data) - else: - kwargs["model"] = MLPClassifier(alpha=0.0001) - classifier_trained = False - sampler = get_AL_sampler('uniform')(dataset.em, dataset.getalllabels(), 1234) - - # -------------------------------------------------------------------------------- # - # CREATE AND SET UP A BOTTLE APPLICATION FOR THE WEB UI - # -------------------------------------------------------------------------------- # - - webUIapp = bottle.Bottle() - webUIapp.add_hook("after_request", enable_cors) - webUIapp_server_kwargs = { - "server": "tornado", - "host": args.host, - "port": args.port - } - - ## static routes (to serve CSS, etc.) - @webUIapp.route('/') - def index(): - return bottle.static_file("index.html", root='static/html') - - @webUIapp.route('/favicon.ico') - def favicon(): - return - - @webUIapp.route('/') - def send_js(filename): - return bottle.static_file(filename, root='static') - - @webUIapp.route('/') - def send_css(filename): - return bottle.static_file(filename, root='static') - - @webUIapp.route('/') - def send_placeholder_image(filename): - return bottle.static_file(filename, root='static') - - @webUIapp.route('/') - def send_image(filename): - return bottle.static_file(filename, root='/') - # return bottle.static_file(filename, root='../../../../../../../../../.')## missouricameratraps - # return bottle.static_file(filename, root='../../../../../../../../../../../.') - - ## dynamic routes - @webUIapp.route('/getClassList', method='POST') - def get_class_list(): - data = bottle.request.json - class_list = [cname for cname in open(args.class_list, 'r').read().splitlines()] - data['class_list'] = class_list - bottle.response.content_type = 'application/json' - bottle.response.status = 200 - return json.dumps(data) - - - @webUIapp.route('/refreshImagesToDisplay', method='POST') - def refresh_images_to_display(): - ''' - Updates which images are allowed to be sampled by the dataset sampler when the selectors for - detection confidence threshold, grayscale images, or class prediction are applied in the webUI, - as well as when new images are requested. - - NOTE: This is done by refreshing the list of "already_selected" samples given to the dataset sampler's - select_batch_ function. - ''' - global grayscale_indices - global color_indices - global detection_conf_values - global dataset - global kwargs - - data = bottle.request.json - - indices_to_exclude = set() # records that should not be shown - indices_to_exclude.update(set(dataset.set_indices[DetectionKind.UserDetection.value])) # never show records that have been labeled by the user - indices_to_exclude.update(set(dataset.set_indices[DetectionKind.ConfirmedDetection.value])) # never show records that have been confirmed by the user - detection_conf_thresh_indices = [i for i, e in enumerate(detection_conf_values) if e < float(data['detection_threshold'])] # find records below the detection confidence threshold - indices_to_exclude.update(set(detection_conf_thresh_indices)) - # if data['display_grayscale']: - # indices_to_exclude.update(set(color_indices)) - # elif not data['display_grayscale']: - # indices_to_exclude.update(set(grayscale_indices)) - - if data['display_class'] == 'All Species': - print('Displaying all species') - pass - else: - cat_name = data['display_class'].lower() - existing_category_entries = {cat.name: cat.id for cat in Category.select()} - cat_id = existing_category_entries[cat_name] - dataset_class_labels = [dataset.samples[i][1] for i in range(len(dataset.samples))] - other_classes = [i for i, cl in enumerate(dataset_class_labels) if cl!=cat_id] - indices_to_exclude.update(set(other_classes)) - - kwargs["already_selected"] = indices_to_exclude - - bottle.response.content_type = 'application/json' - bottle.response.status = 200 - return json.dumps(data) - - @webUIapp.route('/loadImages', method='POST') - def load_images(): - ''' - Returns a batch of images from the dataset sampler to be displayed in the webUI. - ''' - global dataset - global sampler - global kwargs - global indices - - data = bottle.request.json - kwargs["N"] = data['num_images'] - indices_to_exclude = set() # records that should not be shown - indices_to_exclude.update(set(dataset.set_indices[DetectionKind.UserDetection.value])) # never show records that have been labeled by the user - indices_to_exclude.update(set(dataset.set_indices[DetectionKind.ConfirmedDetection.value])) # never show records that have been confirmed by the user - kwargs["already_selected"].update(indices_to_exclude) - try: - indices = sampler.select_batch(**kwargs) - data['success_status'] = True - except: - data['success_status'] = False - data['classifier_trained'] = classifier_trained - data['display_images'] = {} - data['display_images']['image_ids'] = [dataset.samples[i][0] for i in indices] - data['display_images']['image_file_names'] = [dataset.samples[i][5] for i in indices] - data['display_images']['detection_kinds'] = [dataset.samples[i][2] for i in indices] - data['display_images']['detection_categories'] = [] - for i in indices: - if str(dataset.samples[i][1]) == 'None': - data['display_images']['detection_categories'].append('None') - else: - existing_category_entries = {cat.id: cat.name for cat in Category.select()} - cat_name = existing_category_entries[dataset.samples[i][1]].title() - data['display_images']['detection_categories'].append(cat_name) - - - bottle.response.content_type = 'application/json' - bottle.response.status = 200 - return json.dumps(data) - - @webUIapp.route('/loadImagesWithPrediction', method='POST') - def load_images_with_prediction(): - ''' - Returns a batch of images from the dataset sampler with a specified predicted class to be displayed in the webUI. - ''' - global dataset - global sampler - global kwargs - global indices - - data = bottle.request.json - kwargs["N"] = data['num_images'] - indices = sampler.select_batch(**kwargs) - - # data['display_images'] = {} - # data['display_images']['image_ids'] = [dataset.samples[i][0] for i in indices] - # data['display_images']['image_file_names'] = [dataset.samples[i][5] for i in indices] - # data['display_images']['detection_kinds'] = [dataset.samples[i][2] for i in indices] - - # data['display_images']['detection_categories'] = [] - # for i in indices: - # if str(dataset.samples[i][1]) == 'None': - # data['display_images']['detection_categories'].append('None') - # else: - # existing_category_entries = {cat.id: cat.name for cat in Category.select()} - # cat_name = existing_category_entries[dataset.samples[i][1]].replace("_", " ").title() - # data['display_images']['detection_categories'].append(cat_name) - - - bottle.response.content_type = 'application/json' - bottle.response.status = 200 - return json.dumps(data) - - @webUIapp.route('/assignLabel', method='POST') - def assign_label(): - ''' - Assigns a label to a set of images, commits this change to the PostgreSQL database, - and updates the dataset dataloader accordingly. - ''' - global dataset - global indices - - print('Started assignLabel call') - - data = bottle.request.json - - images_to_label = [im['id'] for im in data['images']] - label_to_assign = data['label'] - - # Use image ids in images_to_label to get the corresponding dataset indices - indices_to_label = [] - indices_detection_ids = [dataset.samples[i][0] for i in indices] - for im in images_to_label: - pos = indices_detection_ids.index(im) - ind = indices[pos] - indices_to_label.append(ind) - - label_category_name = label_to_assign.lower() - if label_category_name == 'empty': - # Update records in dataset dataloader but not in the PostgreSQL database - moveRecords(dataset, DetectionKind.ModelDetection.value, DetectionKind.UserDetection.value, indices_to_label) - # numLabeled = len(dataset.set_indices[DetectionKind.UserDetection.value]) - else: - # Get the category id for the assigned label - existing_category_entries = {cat.name: cat.id for cat in Category.select()} - try: - label_category_id = existing_category_entries[label_category_name] - except: - print('The label was not found in the database Category table') - raise NotImplementedError - - # Update entries in the PostgreSQL database - ## get Detection table entries corresponding to the images being labeled - matching_detection_entries = (Detection - .select(Detection.id, Detection.category_id) - .where((Detection.id << images_to_label))) # << means IN - ## update category_id, category_confidence, and kind of each Detection entry in the PostgreSQL database - for mde in matching_detection_entries: - command = Detection.update(category_id=label_category_id, category_confidence=1, kind=DetectionKind.UserDetection.value).where(Detection.id == mde.id) - command.execute() - - # Update records in dataset dataloader - for il in indices_to_label: - sample_data = list(dataset.samples[il]) - sample_data[1] = label_category_id - sample_data[2] = DetectionKind.UserDetection.value - sample_data[3] = 1 - dataset.samples[il] = tuple(sample_data) - moveRecords(dataset, DetectionKind.ModelDetection.value, DetectionKind.UserDetection.value, indices_to_label) - # print(set(dataset.set_indices[4]).update(set(indices_to_label))) - dataset.set_indices[4] = list(set(dataset.set_indices[4]).union(set(indices_to_label))) # add the index to the set of labeled/confirmed indices - # numLabeled = len(dataset.set_indices[DetectionKind.UserDetection.value]) - print([len(x) for x in dataset.set_indices]) - - bottle.response.content_type = 'application/json' - bottle.response.status = 200 - return json.dumps(data) - - @webUIapp.route('/confirmPredictedLabel', method='POST') - def confirm_predicted_label(): - global dataset - global indices - - data = bottle.request.json - - image_to_label = data['image'] - label_to_assign = data['label'] - - # Use image id images_to_label to get the corresponding dataset index - indices_detection_ids = [dataset.samples[i][0] for i in indices] - pos = indices_detection_ids.index(image_to_label) - index_to_label = indices[pos] - - label_category_name = label_to_assign.lower() - if label_category_name == 'empty': - # Update records in dataset dataloader but not in the PostgreSQL database - moveRecords(dataset, DetectionKind.ModelDetection.value, DetectionKind.ConfirmedDetection.value, [index_to_label]) - # numLabeled = len(dataset.set_indices[DetectionKind.UserDetection.value]) # userdetection + confirmed detection? - else: - # Get the category id for the assigned label - existing_category_entries = {cat.name: cat.id for cat in Category.select()} - try: - label_category_id = existing_category_entries[label_category_name] - except: - print('The label was not found in the database Category table') - raise NotImplementedError - - # Update entries in the PostgreSQL database - ## get Detection table entries corresponding to the images being labeled - matching_detection_entries = (Detection - .select(Detection.id, Detection.category_id) - .where((Detection.id==image_to_label))) # << means IN - ## update category_id, category_confidence, and kind of each Detection entry in the PostgreSQL database - mde = matching_detection_entries.get() - command = Detection.update(category_id=label_category_id, category_confidence=1, kind=DetectionKind.ConfirmedDetection.value).where(Detection.id == mde.id) - command.execute() - - # Update records in dataset dataloader - sample_data = list(dataset.samples[index_to_label]) - sample_data[1] = label_category_id - sample_data[2] = DetectionKind.ConfirmedDetection.value - sample_data[3] = 1 - dataset.samples[index_to_label] = tuple(sample_data) - moveRecords(dataset, DetectionKind.ModelDetection.value, DetectionKind.ConfirmedDetection.value, [index_to_label]) - dataset.set_indices[4] = list(set(dataset.set_indices[4]).union({index_to_label})) # add the index to the set of labeled/confirmed indices - # numLabeled = len(dataset.set_indices[DetectionKind.UserDetection.value]) - print([len(x) for x in dataset.set_indices]) - - bottle.response.content_type = 'application/json' - bottle.response.status = 200 - return json.dumps(data) - - @webUIapp.route('/trainClassifier', method='POST') - def train_classifier(): - global dataset - global kwargs - global sampler - global classifier_trained - global X_pred - global y_pred - - data = bottle.request.json - - # Train on samples that have been labeled so far - # dataset.set_kind(DetectionKind.UserDetection.value) - dataset.set_kind(4) - print(dataset.current_set) - print(type(dataset.current_set)) - X_train = dataset.em[dataset.current_set] - y_train = np.asarray(dataset.getlabels()) - # print(y_train) - timer = time.time() - kwargs["model"].fit(X_train, y_train) - print('Training took %0.2f seconds'%(time.time() - timer)) - - timer = time.time() - joblib.dump(kwargs["model"], "%s/%s_%04d.skmodel"%(args.checkpoint_dir, 'classifier', len(dataset.current_set))) - print('Saving classifier checkpoint took %0.2f seconds'%(time.time() - timer)) - - - # Predict on the samples that have not been labeled - timer = time.time() - dataset.set_kind(DetectionKind.ModelDetection.value) - X_pred = dataset.em[dataset.current_set] - y_pred = kwargs["model"].predict(X_pred) - print('Predicting on unlabeled samples took %0.2f seconds'%(time.time() - timer)) - # print(y_pred) - - # Update model predicted class in PostgreSQL database - # timer = time.time() - # for pos in range(len(y_pred)): - # idx = dataset.current_set[pos] - # det_id = dataset.samples[idx][0] - # matching_detection_entries = (Detection - # .select(Detection.id, Detection.category_id) - # .where((Detection.id == det_id))) - # mde = matching_detection_entries.get() - # command = Detection.update(category_id=y_pred[pos]).where(Detection.id == mde.id) - # command.execute() - # print('Updating the database took %0.2f seconds'%(time.time() - timer)) - - # Alternative: batch update PostgreSQL database - # timer = time.time() - # det_ids = [dataset.samples[dataset.current_set[pos]][0] for pos in range(len(y_pred))] - # y_pred = [int(y) for y in y_pred] - # det_id_pred_pairs = list(zip(det_ids, y_pred)) - # case_statement = Case(Detection.id, det_id_pred_pairs) - # command = Detection.update(category_id=case_statement).where(Detection.id.in_(det_ids)) - # command.execute() - # print('Updating the database the other way took %0.2f seconds'%(time.time() - timer)) - - # Update dataset dataloader - timer = time.time() - for pos in range(len(y_pred)): - idx = dataset.current_set[pos] - sample_data = list(dataset.samples[idx]) - sample_data[1] = y_pred[pos] - dataset.samples[idx] = tuple(sample_data) - print('Updating the dataset dataloader took %0.2f seconds'%(time.time() - timer)) - - if not classifier_trained: - # once the classifier has been trained the first time, switch to AL sampling - classifier_trained = True - sampler = get_AL_sampler('confidence')(dataset.em, dataset.getalllabels(), 1234) - - bottle.response.content_type = 'application/json' - bottle.response.status = 200 - return json.dumps(data) - - @webUIapp.route('/showFullsizeImage', method='POST') - def show_fullsize_image(): - data = bottle.request.json - - image_src = data['img_src'] - - matching_image_entries = (Image - .select(Image.file_name, Image.source_file_name) - .where((Image.file_name == image_src))) - try: - mie = matching_image_entries.get() - data['success_status'] = True - data['fullsize_src'] = mie.source_file_name - except: - data['success_status'] = False - - bottle.response.content_type = 'application/json' - bottle.response.status = 200 - return json.dumps(data) - - @webUIapp.route('/getSequentialImages', method='POST') - def get_sequential_images(): - data = bottle.request.json - - image_src = data['img_src'] - - matching_image_entries = (Image - .select(Image.seq_id, Image.seq_num_frames, Image.frame_num) - .where((Image.file_name == image_src))) - try: - mie = matching_image_entries.get() - if mie.seq_num_frames > 1: - images_in_seq = (Image - .select(Image.source_file_name) - .where((Image.seq_id == mie.seq_id)) - .order_by(Image.frame_num)) - image_sequence = sorted(list(set([i.source_file_name for i in images_in_seq]))) - if len(image_sequence) > 10: - minidx = max(mie.frame_num - 4, 0) - maxidx = min(mie.frame_num + 4, len(image_sequence)) - image_sequence = image_sequence[minidx:maxidx+1] - data['image_sequence'] = image_sequence - data['success_status'] = True - except: - data['success_status'] = False - - bottle.response.content_type = 'application/json' - bottle.response.status = 200 - return json.dumps(data) - - webUIapp.run(**webUIapp_server_kwargs) \ No newline at end of file diff --git a/archive/active_learning/labeling_tool/static/css/bootstrap.css b/archive/active_learning/labeling_tool/static/css/bootstrap.css deleted file mode 100644 index 9b2c161e..00000000 --- a/archive/active_learning/labeling_tool/static/css/bootstrap.css +++ /dev/null @@ -1,9030 +0,0 @@ -/*! - * Bootstrap v4.1.3 (https://getbootstrap.com/) - * Copyright 2011-2018 The Bootstrap Authors - * Copyright 2011-2018 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ - :root { - --blue: #007bff; - --indigo: #6610f2; - --purple: #6f42c1; - --pink: #e83e8c; - --red: #dc3545; - --orange: #fd7e14; - --yellow: #ffc107; - --green: #28a745; - --teal: #20c997; - --cyan: #17a2b8; - --white: #fff; - --gray: #6c757d; - --gray-dark: #343a40; - --primary: #007bff; - --secondary: #6c757d; - --success: #28a745; - --info: #17a2b8; - --warning: #ffc107; - --danger: #dc3545; - --light: #f8f9fa; - --dark: #343a40; - --breakpoint-xs: 0; - --breakpoint-sm: 576px; - --breakpoint-md: 768px; - --breakpoint-lg: 992px; - --breakpoint-xl: 1200px; - --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - } - - *, - *::before, - *::after { - box-sizing: border-box; - } - - html { - font-family: sans-serif; - line-height: 1.15; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; - -ms-overflow-style: scrollbar; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - } - - @-ms-viewport { - width: device-width; - } - - article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { - display: block; - } - - body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - font-size: 1rem; - font-weight: 400; - line-height: 1.5; - color: #212529; - text-align: left; - background-color: #fff; - } - - [tabindex="-1"]:focus { - outline: 0 !important; - } - - hr { - box-sizing: content-box; - height: 0; - overflow: visible; - } - - h1, h2, h3, h4, h5, h6 { - margin-top: 0; - margin-bottom: 0.5rem; - } - - p { - margin-top: 0; - margin-bottom: 1rem; - } - - abbr[title], - abbr[data-original-title] { - text-decoration: underline; - -webkit-text-decoration: underline dotted; - text-decoration: underline dotted; - cursor: help; - border-bottom: 0; - } - - address { - margin-bottom: 1rem; - font-style: normal; - line-height: inherit; - } - - ol, - ul, - dl { - margin-top: 0; - margin-bottom: 1rem; - } - - ol ol, - ul ul, - ol ul, - ul ol { - margin-bottom: 0; - } - - dt { - font-weight: 700; - } - - dd { - margin-bottom: .5rem; - margin-left: 0; - } - - blockquote { - margin: 0 0 1rem; - } - - dfn { - font-style: italic; - } - - b, - strong { - font-weight: bolder; - } - - small { - font-size: 80%; - } - - sub, - sup { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline; - } - - sub { - bottom: -.25em; - } - - sup { - top: -.5em; - } - - a { - color: #007bff; - text-decoration: none; - background-color: transparent; - -webkit-text-decoration-skip: objects; - } - - a:hover { - color: #0056b3; - text-decoration: underline; - } - - a:not([href]):not([tabindex]) { - color: inherit; - text-decoration: none; - } - - a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { - color: inherit; - text-decoration: none; - } - - a:not([href]):not([tabindex]):focus { - outline: 0; - } - - pre, - code, - kbd, - samp { - font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - font-size: 1em; - } - - pre { - margin-top: 0; - margin-bottom: 1rem; - overflow: auto; - -ms-overflow-style: scrollbar; - } - - figure { - margin: 0 0 1rem; - } - - img { - vertical-align: middle; - border-style: none; - } - - svg { - overflow: hidden; - vertical-align: middle; - } - - table { - border-collapse: collapse; - } - - caption { - padding-top: 0.75rem; - padding-bottom: 0.75rem; - color: #6c757d; - text-align: left; - caption-side: bottom; - } - - th { - text-align: inherit; - } - - label { - display: inline-block; - margin-bottom: 0.5rem; - } - - button { - border-radius: 0; - } - - button:focus { - outline: 1px dotted; - outline: 5px auto -webkit-focus-ring-color; - } - - input, - button, - select, - optgroup, - textarea { - margin: 0; - font-family: inherit; - font-size: inherit; - line-height: inherit; - } - - button, - input { - overflow: visible; - } - - button, - select { - text-transform: none; - } - - button, - html [type="button"], - [type="reset"], - [type="submit"] { - -webkit-appearance: button; - } - - button::-moz-focus-inner, - [type="button"]::-moz-focus-inner, - [type="reset"]::-moz-focus-inner, - [type="submit"]::-moz-focus-inner { - padding: 0; - border-style: none; - } - - input[type="radio"], - input[type="checkbox"] { - box-sizing: border-box; - padding: 0; - } - - input[type="date"], - input[type="time"], - input[type="datetime-local"], - input[type="month"] { - -webkit-appearance: listbox; - } - - textarea { - overflow: auto; - resize: vertical; - } - - fieldset { - min-width: 0; - padding: 0; - margin: 0; - border: 0; - } - - legend { - display: block; - width: 100%; - max-width: 100%; - padding: 0; - margin-bottom: .5rem; - font-size: 1.5rem; - line-height: inherit; - color: inherit; - white-space: normal; - } - - progress { - vertical-align: baseline; - } - - [type="number"]::-webkit-inner-spin-button, - [type="number"]::-webkit-outer-spin-button { - height: auto; - } - - [type="search"] { - outline-offset: -2px; - -webkit-appearance: none; - } - - [type="search"]::-webkit-search-cancel-button, - [type="search"]::-webkit-search-decoration { - -webkit-appearance: none; - } - - ::-webkit-file-upload-button { - font: inherit; - -webkit-appearance: button; - } - - output { - display: inline-block; - } - - summary { - display: list-item; - cursor: pointer; - } - - template { - display: none; - } - - [hidden] { - display: none !important; - } - - h1, h2, h3, h4, h5, h6, - .h1, .h2, .h3, .h4, .h5, .h6 { - margin-bottom: 0.5rem; - font-family: inherit; - font-weight: 500; - line-height: 1.2; - color: inherit; - } - - h1, .h1 { - font-size: 2.5rem; - } - - h2, .h2 { - font-size: 2rem; - } - - h3, .h3 { - font-size: 1.75rem; - } - - h4, .h4 { - font-size: 1.5rem; - } - - h5, .h5 { - font-size: 1.25rem; - } - - h6, .h6 { - font-size: 1rem; - } - - .lead { - font-size: 1.25rem; - font-weight: 300; - } - - .display-1 { - font-size: 6rem; - font-weight: 300; - line-height: 1.2; - } - - .display-2 { - font-size: 5.5rem; - font-weight: 300; - line-height: 1.2; - } - - .display-3 { - font-size: 4.5rem; - font-weight: 300; - line-height: 1.2; - } - - .display-4 { - font-size: 3.5rem; - font-weight: 300; - line-height: 1.2; - } - - hr { - margin-top: 1rem; - margin-bottom: 1rem; - border: 0; - border-top: 1px solid rgba(0, 0, 0, 0.1); - } - - small, - .small { - font-size: 80%; - font-weight: 400; - } - - mark, - .mark { - padding: 0.2em; - background-color: #fcf8e3; - } - - .list-unstyled { - padding-left: 0; - list-style: none; - } - - .list-inline { - padding-left: 0; - list-style: none; - } - - .list-inline-item { - display: inline-block; - } - - .list-inline-item:not(:last-child) { - margin-right: 0.5rem; - } - - .initialism { - font-size: 90%; - text-transform: uppercase; - } - - .blockquote { - margin-bottom: 1rem; - font-size: 1.25rem; - } - - .blockquote-footer { - display: block; - font-size: 80%; - color: #6c757d; - } - - .blockquote-footer::before { - content: "\2014 \00A0"; - } - - .img-fluid { - max-width: 100%; - height: auto; - } - - .img-thumbnail { - padding: 0.25rem; - background-color: #fff; - border: 1px solid #dee2e6; - border-radius: 0.25rem; - max-width: 100%; - height: auto; - } - - .figure { - display: inline-block; - } - - .figure-img { - margin-bottom: 0.5rem; - line-height: 1; - } - - .figure-caption { - font-size: 90%; - color: #6c757d; - } - - code { - font-size: 87.5%; - color: #e83e8c; - word-break: break-word; - } - - a > code { - color: inherit; - } - - kbd { - padding: 0.2rem 0.4rem; - font-size: 87.5%; - color: #fff; - background-color: #212529; - border-radius: 0.2rem; - } - - kbd kbd { - padding: 0; - font-size: 100%; - font-weight: 700; - } - - pre { - display: block; - font-size: 87.5%; - color: #212529; - } - - pre code { - font-size: inherit; - color: inherit; - word-break: normal; - } - - .pre-scrollable { - max-height: 340px; - overflow-y: scroll; - } - - .container { - width: 100%; - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; - } - - @media (min-width: 576px) { - .container { - max-width: 540px; - } - } - - @media (min-width: 768px) { - .container { - max-width: 720px; - } - } - - @media (min-width: 992px) { - .container { - max-width: 960px; - } - } - - @media (min-width: 1200px) { - .container { - max-width: 1140px; - } - } - - .container-fluid { - width: 100%; - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; - } - - .row { - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - margin-right: -15px; - margin-left: -15px; - } - - .no-gutters { - margin-right: 0; - margin-left: 0; - } - - .no-gutters > .col, - .no-gutters > [class*="col-"] { - padding-right: 0; - padding-left: 0; - } - - .col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, - .col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, - .col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, - .col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, - .col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, - .col-xl-auto { - position: relative; - width: 100%; - min-height: 1px; - padding-right: 15px; - padding-left: 15px; - } - - .col { - -ms-flex-preferred-size: 0; - flex-basis: 0; - -ms-flex-positive: 1; - flex-grow: 1; - max-width: 100%; - } - - .col-auto { - -ms-flex: 0 0 auto; - flex: 0 0 auto; - width: auto; - max-width: none; - } - - .col-1 { - -ms-flex: 0 0 8.333333%; - flex: 0 0 8.333333%; - max-width: 8.333333%; - } - - .col-2 { - -ms-flex: 0 0 16.666667%; - flex: 0 0 16.666667%; - max-width: 16.666667%; - } - - .col-3 { - -ms-flex: 0 0 25%; - flex: 0 0 25%; - max-width: 25%; - } - - .col-4 { - -ms-flex: 0 0 33.333333%; - flex: 0 0 33.333333%; - max-width: 33.333333%; - } - - .col-5 { - -ms-flex: 0 0 41.666667%; - flex: 0 0 41.666667%; - max-width: 41.666667%; - } - - .col-6 { - -ms-flex: 0 0 50%; - flex: 0 0 50%; - max-width: 50%; - } - - .col-7 { - -ms-flex: 0 0 58.333333%; - flex: 0 0 58.333333%; - max-width: 58.333333%; - } - - .col-8 { - -ms-flex: 0 0 66.666667%; - flex: 0 0 66.666667%; - max-width: 66.666667%; - } - - .col-9 { - -ms-flex: 0 0 75%; - flex: 0 0 75%; - max-width: 75%; - } - - .col-10 { - -ms-flex: 0 0 83.333333%; - flex: 0 0 83.333333%; - max-width: 83.333333%; - } - - .col-11 { - -ms-flex: 0 0 91.666667%; - flex: 0 0 91.666667%; - max-width: 91.666667%; - } - - .col-12 { - -ms-flex: 0 0 100%; - flex: 0 0 100%; - max-width: 100%; - } - - .order-first { - -ms-flex-order: -1; - order: -1; - } - - .order-last { - -ms-flex-order: 13; - order: 13; - } - - .order-0 { - -ms-flex-order: 0; - order: 0; - } - - .order-1 { - -ms-flex-order: 1; - order: 1; - } - - .order-2 { - -ms-flex-order: 2; - order: 2; - } - - .order-3 { - -ms-flex-order: 3; - order: 3; - } - - .order-4 { - -ms-flex-order: 4; - order: 4; - } - - .order-5 { - -ms-flex-order: 5; - order: 5; - } - - .order-6 { - -ms-flex-order: 6; - order: 6; - } - - .order-7 { - -ms-flex-order: 7; - order: 7; - } - - .order-8 { - -ms-flex-order: 8; - order: 8; - } - - .order-9 { - -ms-flex-order: 9; - order: 9; - } - - .order-10 { - -ms-flex-order: 10; - order: 10; - } - - .order-11 { - -ms-flex-order: 11; - order: 11; - } - - .order-12 { - -ms-flex-order: 12; - order: 12; - } - - .offset-1 { - margin-left: 8.333333%; - } - - .offset-2 { - margin-left: 16.666667%; - } - - .offset-3 { - margin-left: 25%; - } - - .offset-4 { - margin-left: 33.333333%; - } - - .offset-5 { - margin-left: 41.666667%; - } - - .offset-6 { - margin-left: 50%; - } - - .offset-7 { - margin-left: 58.333333%; - } - - .offset-8 { - margin-left: 66.666667%; - } - - .offset-9 { - margin-left: 75%; - } - - .offset-10 { - margin-left: 83.333333%; - } - - .offset-11 { - margin-left: 91.666667%; - } - - @media (min-width: 576px) { - .col-sm { - -ms-flex-preferred-size: 0; - flex-basis: 0; - -ms-flex-positive: 1; - flex-grow: 1; - max-width: 100%; - } - .col-sm-auto { - -ms-flex: 0 0 auto; - flex: 0 0 auto; - width: auto; - max-width: none; - } - .col-sm-1 { - -ms-flex: 0 0 8.333333%; - flex: 0 0 8.333333%; - max-width: 8.333333%; - } - .col-sm-2 { - -ms-flex: 0 0 16.666667%; - flex: 0 0 16.666667%; - max-width: 16.666667%; - } - .col-sm-3 { - -ms-flex: 0 0 25%; - flex: 0 0 25%; - max-width: 25%; - } - .col-sm-4 { - -ms-flex: 0 0 33.333333%; - flex: 0 0 33.333333%; - max-width: 33.333333%; - } - .col-sm-5 { - -ms-flex: 0 0 41.666667%; - flex: 0 0 41.666667%; - max-width: 41.666667%; - } - .col-sm-6 { - -ms-flex: 0 0 50%; - flex: 0 0 50%; - max-width: 50%; - } - .col-sm-7 { - -ms-flex: 0 0 58.333333%; - flex: 0 0 58.333333%; - max-width: 58.333333%; - } - .col-sm-8 { - -ms-flex: 0 0 66.666667%; - flex: 0 0 66.666667%; - max-width: 66.666667%; - } - .col-sm-9 { - -ms-flex: 0 0 75%; - flex: 0 0 75%; - max-width: 75%; - } - .col-sm-10 { - -ms-flex: 0 0 83.333333%; - flex: 0 0 83.333333%; - max-width: 83.333333%; - } - .col-sm-11 { - -ms-flex: 0 0 91.666667%; - flex: 0 0 91.666667%; - max-width: 91.666667%; - } - .col-sm-12 { - -ms-flex: 0 0 100%; - flex: 0 0 100%; - max-width: 100%; - } - .order-sm-first { - -ms-flex-order: -1; - order: -1; - } - .order-sm-last { - -ms-flex-order: 13; - order: 13; - } - .order-sm-0 { - -ms-flex-order: 0; - order: 0; - } - .order-sm-1 { - -ms-flex-order: 1; - order: 1; - } - .order-sm-2 { - -ms-flex-order: 2; - order: 2; - } - .order-sm-3 { - -ms-flex-order: 3; - order: 3; - } - .order-sm-4 { - -ms-flex-order: 4; - order: 4; - } - .order-sm-5 { - -ms-flex-order: 5; - order: 5; - } - .order-sm-6 { - -ms-flex-order: 6; - order: 6; - } - .order-sm-7 { - -ms-flex-order: 7; - order: 7; - } - .order-sm-8 { - -ms-flex-order: 8; - order: 8; - } - .order-sm-9 { - -ms-flex-order: 9; - order: 9; - } - .order-sm-10 { - -ms-flex-order: 10; - order: 10; - } - .order-sm-11 { - -ms-flex-order: 11; - order: 11; - } - .order-sm-12 { - -ms-flex-order: 12; - order: 12; - } - .offset-sm-0 { - margin-left: 0; - } - .offset-sm-1 { - margin-left: 8.333333%; - } - .offset-sm-2 { - margin-left: 16.666667%; - } - .offset-sm-3 { - margin-left: 25%; - } - .offset-sm-4 { - margin-left: 33.333333%; - } - .offset-sm-5 { - margin-left: 41.666667%; - } - .offset-sm-6 { - margin-left: 50%; - } - .offset-sm-7 { - margin-left: 58.333333%; - } - .offset-sm-8 { - margin-left: 66.666667%; - } - .offset-sm-9 { - margin-left: 75%; - } - .offset-sm-10 { - margin-left: 83.333333%; - } - .offset-sm-11 { - margin-left: 91.666667%; - } - } - - @media (min-width: 768px) { - .col-md { - -ms-flex-preferred-size: 0; - flex-basis: 0; - -ms-flex-positive: 1; - flex-grow: 1; - max-width: 100%; - } - .col-md-auto { - -ms-flex: 0 0 auto; - flex: 0 0 auto; - width: auto; - max-width: none; - } - .col-md-1 { - -ms-flex: 0 0 8.333333%; - flex: 0 0 8.333333%; - max-width: 8.333333%; - } - .col-md-2 { - -ms-flex: 0 0 16.666667%; - flex: 0 0 16.666667%; - max-width: 16.666667%; - } - .col-md-3 { - -ms-flex: 0 0 25%; - flex: 0 0 25%; - max-width: 25%; - } - .col-md-4 { - -ms-flex: 0 0 33.333333%; - flex: 0 0 33.333333%; - max-width: 33.333333%; - } - .col-md-5 { - -ms-flex: 0 0 41.666667%; - flex: 0 0 41.666667%; - max-width: 41.666667%; - } - .col-md-6 { - -ms-flex: 0 0 50%; - flex: 0 0 50%; - max-width: 50%; - } - .col-md-7 { - -ms-flex: 0 0 58.333333%; - flex: 0 0 58.333333%; - max-width: 58.333333%; - } - .col-md-8 { - -ms-flex: 0 0 66.666667%; - flex: 0 0 66.666667%; - max-width: 66.666667%; - } - .col-md-9 { - -ms-flex: 0 0 75%; - flex: 0 0 75%; - max-width: 75%; - } - .col-md-10 { - -ms-flex: 0 0 83.333333%; - flex: 0 0 83.333333%; - max-width: 83.333333%; - } - .col-md-11 { - -ms-flex: 0 0 91.666667%; - flex: 0 0 91.666667%; - max-width: 91.666667%; - } - .col-md-12 { - -ms-flex: 0 0 100%; - flex: 0 0 100%; - max-width: 100%; - } - .order-md-first { - -ms-flex-order: -1; - order: -1; - } - .order-md-last { - -ms-flex-order: 13; - order: 13; - } - .order-md-0 { - -ms-flex-order: 0; - order: 0; - } - .order-md-1 { - -ms-flex-order: 1; - order: 1; - } - .order-md-2 { - -ms-flex-order: 2; - order: 2; - } - .order-md-3 { - -ms-flex-order: 3; - order: 3; - } - .order-md-4 { - -ms-flex-order: 4; - order: 4; - } - .order-md-5 { - -ms-flex-order: 5; - order: 5; - } - .order-md-6 { - -ms-flex-order: 6; - order: 6; - } - .order-md-7 { - -ms-flex-order: 7; - order: 7; - } - .order-md-8 { - -ms-flex-order: 8; - order: 8; - } - .order-md-9 { - -ms-flex-order: 9; - order: 9; - } - .order-md-10 { - -ms-flex-order: 10; - order: 10; - } - .order-md-11 { - -ms-flex-order: 11; - order: 11; - } - .order-md-12 { - -ms-flex-order: 12; - order: 12; - } - .offset-md-0 { - margin-left: 0; - } - .offset-md-1 { - margin-left: 8.333333%; - } - .offset-md-2 { - margin-left: 16.666667%; - } - .offset-md-3 { - margin-left: 25%; - } - .offset-md-4 { - margin-left: 33.333333%; - } - .offset-md-5 { - margin-left: 41.666667%; - } - .offset-md-6 { - margin-left: 50%; - } - .offset-md-7 { - margin-left: 58.333333%; - } - .offset-md-8 { - margin-left: 66.666667%; - } - .offset-md-9 { - margin-left: 75%; - } - .offset-md-10 { - margin-left: 83.333333%; - } - .offset-md-11 { - margin-left: 91.666667%; - } - } - - @media (min-width: 992px) { - .col-lg { - -ms-flex-preferred-size: 0; - flex-basis: 0; - -ms-flex-positive: 1; - flex-grow: 1; - max-width: 100%; - } - .col-lg-auto { - -ms-flex: 0 0 auto; - flex: 0 0 auto; - width: auto; - max-width: none; - } - .col-lg-1 { - -ms-flex: 0 0 8.333333%; - flex: 0 0 8.333333%; - max-width: 8.333333%; - } - .col-lg-2 { - -ms-flex: 0 0 16.666667%; - flex: 0 0 16.666667%; - max-width: 16.666667%; - } - .col-lg-3 { - -ms-flex: 0 0 25%; - flex: 0 0 25%; - max-width: 25%; - } - .col-lg-4 { - -ms-flex: 0 0 33.333333%; - flex: 0 0 33.333333%; - max-width: 33.333333%; - } - .col-lg-5 { - -ms-flex: 0 0 41.666667%; - flex: 0 0 41.666667%; - max-width: 41.666667%; - } - .col-lg-6 { - -ms-flex: 0 0 50%; - flex: 0 0 50%; - max-width: 50%; - } - .col-lg-7 { - -ms-flex: 0 0 58.333333%; - flex: 0 0 58.333333%; - max-width: 58.333333%; - } - .col-lg-8 { - -ms-flex: 0 0 66.666667%; - flex: 0 0 66.666667%; - max-width: 66.666667%; - } - .col-lg-9 { - -ms-flex: 0 0 75%; - flex: 0 0 75%; - max-width: 75%; - } - .col-lg-10 { - -ms-flex: 0 0 83.333333%; - flex: 0 0 83.333333%; - max-width: 83.333333%; - } - .col-lg-11 { - -ms-flex: 0 0 91.666667%; - flex: 0 0 91.666667%; - max-width: 91.666667%; - } - .col-lg-12 { - -ms-flex: 0 0 100%; - flex: 0 0 100%; - max-width: 100%; - } - .order-lg-first { - -ms-flex-order: -1; - order: -1; - } - .order-lg-last { - -ms-flex-order: 13; - order: 13; - } - .order-lg-0 { - -ms-flex-order: 0; - order: 0; - } - .order-lg-1 { - -ms-flex-order: 1; - order: 1; - } - .order-lg-2 { - -ms-flex-order: 2; - order: 2; - } - .order-lg-3 { - -ms-flex-order: 3; - order: 3; - } - .order-lg-4 { - -ms-flex-order: 4; - order: 4; - } - .order-lg-5 { - -ms-flex-order: 5; - order: 5; - } - .order-lg-6 { - -ms-flex-order: 6; - order: 6; - } - .order-lg-7 { - -ms-flex-order: 7; - order: 7; - } - .order-lg-8 { - -ms-flex-order: 8; - order: 8; - } - .order-lg-9 { - -ms-flex-order: 9; - order: 9; - } - .order-lg-10 { - -ms-flex-order: 10; - order: 10; - } - .order-lg-11 { - -ms-flex-order: 11; - order: 11; - } - .order-lg-12 { - -ms-flex-order: 12; - order: 12; - } - .offset-lg-0 { - margin-left: 0; - } - .offset-lg-1 { - margin-left: 8.333333%; - } - .offset-lg-2 { - margin-left: 16.666667%; - } - .offset-lg-3 { - margin-left: 25%; - } - .offset-lg-4 { - margin-left: 33.333333%; - } - .offset-lg-5 { - margin-left: 41.666667%; - } - .offset-lg-6 { - margin-left: 50%; - } - .offset-lg-7 { - margin-left: 58.333333%; - } - .offset-lg-8 { - margin-left: 66.666667%; - } - .offset-lg-9 { - margin-left: 75%; - } - .offset-lg-10 { - margin-left: 83.333333%; - } - .offset-lg-11 { - margin-left: 91.666667%; - } - } - - @media (min-width: 1200px) { - .col-xl { - -ms-flex-preferred-size: 0; - flex-basis: 0; - -ms-flex-positive: 1; - flex-grow: 1; - max-width: 100%; - } - .col-xl-auto { - -ms-flex: 0 0 auto; - flex: 0 0 auto; - width: auto; - max-width: none; - } - .col-xl-1 { - -ms-flex: 0 0 8.333333%; - flex: 0 0 8.333333%; - max-width: 8.333333%; - } - .col-xl-2 { - -ms-flex: 0 0 16.666667%; - flex: 0 0 16.666667%; - max-width: 16.666667%; - } - .col-xl-3 { - -ms-flex: 0 0 25%; - flex: 0 0 25%; - max-width: 25%; - } - .col-xl-4 { - -ms-flex: 0 0 33.333333%; - flex: 0 0 33.333333%; - max-width: 33.333333%; - } - .col-xl-5 { - -ms-flex: 0 0 41.666667%; - flex: 0 0 41.666667%; - max-width: 41.666667%; - } - .col-xl-6 { - -ms-flex: 0 0 50%; - flex: 0 0 50%; - max-width: 50%; - } - .col-xl-7 { - -ms-flex: 0 0 58.333333%; - flex: 0 0 58.333333%; - max-width: 58.333333%; - } - .col-xl-8 { - -ms-flex: 0 0 66.666667%; - flex: 0 0 66.666667%; - max-width: 66.666667%; - } - .col-xl-9 { - -ms-flex: 0 0 75%; - flex: 0 0 75%; - max-width: 75%; - } - .col-xl-10 { - -ms-flex: 0 0 83.333333%; - flex: 0 0 83.333333%; - max-width: 83.333333%; - } - .col-xl-11 { - -ms-flex: 0 0 91.666667%; - flex: 0 0 91.666667%; - max-width: 91.666667%; - } - .col-xl-12 { - -ms-flex: 0 0 100%; - flex: 0 0 100%; - max-width: 100%; - } - .order-xl-first { - -ms-flex-order: -1; - order: -1; - } - .order-xl-last { - -ms-flex-order: 13; - order: 13; - } - .order-xl-0 { - -ms-flex-order: 0; - order: 0; - } - .order-xl-1 { - -ms-flex-order: 1; - order: 1; - } - .order-xl-2 { - -ms-flex-order: 2; - order: 2; - } - .order-xl-3 { - -ms-flex-order: 3; - order: 3; - } - .order-xl-4 { - -ms-flex-order: 4; - order: 4; - } - .order-xl-5 { - -ms-flex-order: 5; - order: 5; - } - .order-xl-6 { - -ms-flex-order: 6; - order: 6; - } - .order-xl-7 { - -ms-flex-order: 7; - order: 7; - } - .order-xl-8 { - -ms-flex-order: 8; - order: 8; - } - .order-xl-9 { - -ms-flex-order: 9; - order: 9; - } - .order-xl-10 { - -ms-flex-order: 10; - order: 10; - } - .order-xl-11 { - -ms-flex-order: 11; - order: 11; - } - .order-xl-12 { - -ms-flex-order: 12; - order: 12; - } - .offset-xl-0 { - margin-left: 0; - } - .offset-xl-1 { - margin-left: 8.333333%; - } - .offset-xl-2 { - margin-left: 16.666667%; - } - .offset-xl-3 { - margin-left: 25%; - } - .offset-xl-4 { - margin-left: 33.333333%; - } - .offset-xl-5 { - margin-left: 41.666667%; - } - .offset-xl-6 { - margin-left: 50%; - } - .offset-xl-7 { - margin-left: 58.333333%; - } - .offset-xl-8 { - margin-left: 66.666667%; - } - .offset-xl-9 { - margin-left: 75%; - } - .offset-xl-10 { - margin-left: 83.333333%; - } - .offset-xl-11 { - margin-left: 91.666667%; - } - } - - .table { - width: 100%; - margin-bottom: 1rem; - background-color: transparent; - } - - .table th, - .table td { - padding: 0.75rem; - vertical-align: top; - border-top: 1px solid #dee2e6; - } - - .table thead th { - vertical-align: bottom; - border-bottom: 2px solid #dee2e6; - } - - .table tbody + tbody { - border-top: 2px solid #dee2e6; - } - - .table .table { - background-color: #fff; - } - - .table-sm th, - .table-sm td { - padding: 0.3rem; - } - - .table-bordered { - border: 1px solid #dee2e6; - } - - .table-bordered th, - .table-bordered td { - border: 1px solid #dee2e6; - } - - .table-bordered thead th, - .table-bordered thead td { - border-bottom-width: 2px; - } - - .table-borderless th, - .table-borderless td, - .table-borderless thead th, - .table-borderless tbody + tbody { - border: 0; - } - - .table-striped tbody tr:nth-of-type(odd) { - background-color: rgba(0, 0, 0, 0.05); - } - - .table-hover tbody tr:hover { - background-color: rgba(0, 0, 0, 0.075); - } - - .table-primary, - .table-primary > th, - .table-primary > td { - background-color: #b8daff; - } - - .table-hover .table-primary:hover { - background-color: #9fcdff; - } - - .table-hover .table-primary:hover > td, - .table-hover .table-primary:hover > th { - background-color: #9fcdff; - } - - .table-secondary, - .table-secondary > th, - .table-secondary > td { - background-color: #d6d8db; - } - - .table-hover .table-secondary:hover { - background-color: #c8cbcf; - } - - .table-hover .table-secondary:hover > td, - .table-hover .table-secondary:hover > th { - background-color: #c8cbcf; - } - - .table-success, - .table-success > th, - .table-success > td { - background-color: #c3e6cb; - } - - .table-hover .table-success:hover { - background-color: #b1dfbb; - } - - .table-hover .table-success:hover > td, - .table-hover .table-success:hover > th { - background-color: #b1dfbb; - } - - .table-info, - .table-info > th, - .table-info > td { - background-color: #bee5eb; - } - - .table-hover .table-info:hover { - background-color: #abdde5; - } - - .table-hover .table-info:hover > td, - .table-hover .table-info:hover > th { - background-color: #abdde5; - } - - .table-warning, - .table-warning > th, - .table-warning > td { - background-color: #ffeeba; - } - - .table-hover .table-warning:hover { - background-color: #ffe8a1; - } - - .table-hover .table-warning:hover > td, - .table-hover .table-warning:hover > th { - background-color: #ffe8a1; - } - - .table-danger, - .table-danger > th, - .table-danger > td { - background-color: #f5c6cb; - } - - .table-hover .table-danger:hover { - background-color: #f1b0b7; - } - - .table-hover .table-danger:hover > td, - .table-hover .table-danger:hover > th { - background-color: #f1b0b7; - } - - .table-light, - .table-light > th, - .table-light > td { - background-color: #fdfdfe; - } - - .table-hover .table-light:hover { - background-color: #ececf6; - } - - .table-hover .table-light:hover > td, - .table-hover .table-light:hover > th { - background-color: #ececf6; - } - - .table-dark, - .table-dark > th, - .table-dark > td { - background-color: #c6c8ca; - } - - .table-hover .table-dark:hover { - background-color: #b9bbbe; - } - - .table-hover .table-dark:hover > td, - .table-hover .table-dark:hover > th { - background-color: #b9bbbe; - } - - .table-active, - .table-active > th, - .table-active > td { - background-color: rgba(0, 0, 0, 0.075); - } - - .table-hover .table-active:hover { - background-color: rgba(0, 0, 0, 0.075); - } - - .table-hover .table-active:hover > td, - .table-hover .table-active:hover > th { - background-color: rgba(0, 0, 0, 0.075); - } - - .table .thead-dark th { - color: #fff; - background-color: #212529; - border-color: #32383e; - } - - .table .thead-light th { - color: #495057; - background-color: #e9ecef; - border-color: #dee2e6; - } - - .table-dark { - color: #fff; - background-color: #212529; - } - - .table-dark th, - .table-dark td, - .table-dark thead th { - border-color: #32383e; - } - - .table-dark.table-bordered { - border: 0; - } - - .table-dark.table-striped tbody tr:nth-of-type(odd) { - background-color: rgba(255, 255, 255, 0.05); - } - - .table-dark.table-hover tbody tr:hover { - background-color: rgba(255, 255, 255, 0.075); - } - - @media (max-width: 575.98px) { - .table-responsive-sm { - display: block; - width: 100%; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; - } - .table-responsive-sm > .table-bordered { - border: 0; - } - } - - @media (max-width: 767.98px) { - .table-responsive-md { - display: block; - width: 100%; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; - } - .table-responsive-md > .table-bordered { - border: 0; - } - } - - @media (max-width: 991.98px) { - .table-responsive-lg { - display: block; - width: 100%; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; - } - .table-responsive-lg > .table-bordered { - border: 0; - } - } - - @media (max-width: 1199.98px) { - .table-responsive-xl { - display: block; - width: 100%; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; - } - .table-responsive-xl > .table-bordered { - border: 0; - } - } - - .table-responsive { - display: block; - width: 100%; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; - } - - .table-responsive > .table-bordered { - border: 0; - } - - .form-control { - display: block; - width: 100%; - height: calc(2.25rem + 2px); - padding: 0.375rem 0.75rem; - font-size: 1rem; - line-height: 1.5; - color: #495057; - background-color: #fff; - background-clip: padding-box; - border: 1px solid #ced4da; - border-radius: 0.25rem; - transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - } - - @media screen and (prefers-reduced-motion: reduce) { - .form-control { - transition: none; - } - } - - .form-control::-ms-expand { - background-color: transparent; - border: 0; - } - - .form-control:focus { - color: #495057; - background-color: #fff; - border-color: #80bdff; - outline: 0; - box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); - } - - .form-control::-webkit-input-placeholder { - color: #6c757d; - opacity: 1; - } - - .form-control::-moz-placeholder { - color: #6c757d; - opacity: 1; - } - - .form-control:-ms-input-placeholder { - color: #6c757d; - opacity: 1; - } - - .form-control::-ms-input-placeholder { - color: #6c757d; - opacity: 1; - } - - .form-control::placeholder { - color: #6c757d; - opacity: 1; - } - - .form-control:disabled, .form-control[readonly] { - background-color: #e9ecef; - opacity: 1; - } - - select.form-control:focus::-ms-value { - color: #495057; - background-color: #fff; - } - - .form-control-file, - .form-control-range { - display: block; - width: 100%; - } - - .col-form-label { - padding-top: calc(0.375rem + 1px); - padding-bottom: calc(0.375rem + 1px); - margin-bottom: 0; - font-size: inherit; - line-height: 1.5; - } - - .col-form-label-lg { - padding-top: calc(0.5rem + 1px); - padding-bottom: calc(0.5rem + 1px); - font-size: 1.25rem; - line-height: 1.5; - } - - .col-form-label-sm { - padding-top: calc(0.25rem + 1px); - padding-bottom: calc(0.25rem + 1px); - font-size: 0.875rem; - line-height: 1.5; - } - - .form-control-plaintext { - display: block; - width: 100%; - padding-top: 0.375rem; - padding-bottom: 0.375rem; - margin-bottom: 0; - line-height: 1.5; - color: #212529; - background-color: transparent; - border: solid transparent; - border-width: 1px 0; - } - - .form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg { - padding-right: 0; - padding-left: 0; - } - - .form-control-sm { - height: calc(1.8125rem + 2px); - padding: 0.25rem 0.5rem; - font-size: 0.875rem; - line-height: 1.5; - border-radius: 0.2rem; - } - - .form-control-lg { - height: calc(2.875rem + 2px); - padding: 0.5rem 1rem; - font-size: 1.25rem; - line-height: 1.5; - border-radius: 0.3rem; - } - - select.form-control[size], select.form-control[multiple] { - height: auto; - } - - textarea.form-control { - height: auto; - } - - .form-group { - margin-bottom: 1rem; - } - - .form-text { - display: block; - margin-top: 0.25rem; - } - - .form-row { - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - margin-right: -5px; - margin-left: -5px; - } - - .form-row > .col, - .form-row > [class*="col-"] { - padding-right: 5px; - padding-left: 5px; - } - - .form-check { - position: relative; - display: block; - padding-left: 1.25rem; - } - - .form-check-input { - position: absolute; - margin-top: 0.3rem; - margin-left: -1.25rem; - } - - .form-check-input:disabled ~ .form-check-label { - color: #6c757d; - } - - .form-check-label { - margin-bottom: 0; - } - - .form-check-inline { - display: -ms-inline-flexbox; - display: inline-flex; - -ms-flex-align: center; - align-items: center; - padding-left: 0; - margin-right: 0.75rem; - } - - .form-check-inline .form-check-input { - position: static; - margin-top: 0; - margin-right: 0.3125rem; - margin-left: 0; - } - - .valid-feedback { - display: none; - width: 100%; - margin-top: 0.25rem; - font-size: 80%; - color: #28a745; - } - - .valid-tooltip { - position: absolute; - top: 100%; - z-index: 5; - display: none; - max-width: 100%; - padding: 0.25rem 0.5rem; - margin-top: .1rem; - font-size: 0.875rem; - line-height: 1.5; - color: #fff; - background-color: rgba(40, 167, 69, 0.9); - border-radius: 0.25rem; - } - - .was-validated .form-control:valid, .form-control.is-valid, .was-validated - .custom-select:valid, - .custom-select.is-valid { - border-color: #28a745; - } - - .was-validated .form-control:valid:focus, .form-control.is-valid:focus, .was-validated - .custom-select:valid:focus, - .custom-select.is-valid:focus { - border-color: #28a745; - box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); - } - - .was-validated .form-control:valid ~ .valid-feedback, - .was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback, - .form-control.is-valid ~ .valid-tooltip, .was-validated - .custom-select:valid ~ .valid-feedback, - .was-validated - .custom-select:valid ~ .valid-tooltip, - .custom-select.is-valid ~ .valid-feedback, - .custom-select.is-valid ~ .valid-tooltip { - display: block; - } - - .was-validated .form-control-file:valid ~ .valid-feedback, - .was-validated .form-control-file:valid ~ .valid-tooltip, .form-control-file.is-valid ~ .valid-feedback, - .form-control-file.is-valid ~ .valid-tooltip { - display: block; - } - - .was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label { - color: #28a745; - } - - .was-validated .form-check-input:valid ~ .valid-feedback, - .was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback, - .form-check-input.is-valid ~ .valid-tooltip { - display: block; - } - - .was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label { - color: #28a745; - } - - .was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before { - background-color: #71dd8a; - } - - .was-validated .custom-control-input:valid ~ .valid-feedback, - .was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback, - .custom-control-input.is-valid ~ .valid-tooltip { - display: block; - } - - .was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before { - background-color: #34ce57; - } - - .was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before { - box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(40, 167, 69, 0.25); - } - - .was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label { - border-color: #28a745; - } - - .was-validated .custom-file-input:valid ~ .custom-file-label::after, .custom-file-input.is-valid ~ .custom-file-label::after { - border-color: inherit; - } - - .was-validated .custom-file-input:valid ~ .valid-feedback, - .was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback, - .custom-file-input.is-valid ~ .valid-tooltip { - display: block; - } - - .was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label { - box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); - } - - .invalid-feedback { - display: none; - width: 100%; - margin-top: 0.25rem; - font-size: 80%; - color: #dc3545; - } - - .invalid-tooltip { - position: absolute; - top: 100%; - z-index: 5; - display: none; - max-width: 100%; - padding: 0.25rem 0.5rem; - margin-top: .1rem; - font-size: 0.875rem; - line-height: 1.5; - color: #fff; - background-color: rgba(220, 53, 69, 0.9); - border-radius: 0.25rem; - } - - .was-validated .form-control:invalid, .form-control.is-invalid, .was-validated - .custom-select:invalid, - .custom-select.is-invalid { - border-color: #dc3545; - } - - .was-validated .form-control:invalid:focus, .form-control.is-invalid:focus, .was-validated - .custom-select:invalid:focus, - .custom-select.is-invalid:focus { - border-color: #dc3545; - box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); - } - - .was-validated .form-control:invalid ~ .invalid-feedback, - .was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback, - .form-control.is-invalid ~ .invalid-tooltip, .was-validated - .custom-select:invalid ~ .invalid-feedback, - .was-validated - .custom-select:invalid ~ .invalid-tooltip, - .custom-select.is-invalid ~ .invalid-feedback, - .custom-select.is-invalid ~ .invalid-tooltip { - display: block; - } - - .was-validated .form-control-file:invalid ~ .invalid-feedback, - .was-validated .form-control-file:invalid ~ .invalid-tooltip, .form-control-file.is-invalid ~ .invalid-feedback, - .form-control-file.is-invalid ~ .invalid-tooltip { - display: block; - } - - .was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label { - color: #dc3545; - } - - .was-validated .form-check-input:invalid ~ .invalid-feedback, - .was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback, - .form-check-input.is-invalid ~ .invalid-tooltip { - display: block; - } - - .was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label { - color: #dc3545; - } - - .was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before { - background-color: #efa2a9; - } - - .was-validated .custom-control-input:invalid ~ .invalid-feedback, - .was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback, - .custom-control-input.is-invalid ~ .invalid-tooltip { - display: block; - } - - .was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before { - background-color: #e4606d; - } - - .was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before { - box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(220, 53, 69, 0.25); - } - - .was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label { - border-color: #dc3545; - } - - .was-validated .custom-file-input:invalid ~ .custom-file-label::after, .custom-file-input.is-invalid ~ .custom-file-label::after { - border-color: inherit; - } - - .was-validated .custom-file-input:invalid ~ .invalid-feedback, - .was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback, - .custom-file-input.is-invalid ~ .invalid-tooltip { - display: block; - } - - .was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label { - box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); - } - - .form-inline { - display: -ms-flexbox; - display: flex; - -ms-flex-flow: row wrap; - flex-flow: row wrap; - -ms-flex-align: center; - align-items: center; - } - - .form-inline .form-check { - width: 100%; - } - - @media (min-width: 576px) { - .form-inline label { - display: -ms-flexbox; - display: flex; - -ms-flex-align: center; - align-items: center; - -ms-flex-pack: center; - justify-content: center; - margin-bottom: 0; - } - .form-inline .form-group { - display: -ms-flexbox; - display: flex; - -ms-flex: 0 0 auto; - flex: 0 0 auto; - -ms-flex-flow: row wrap; - flex-flow: row wrap; - -ms-flex-align: center; - align-items: center; - margin-bottom: 0; - } - .form-inline .form-control { - display: inline-block; - width: auto; - vertical-align: middle; - } - .form-inline .form-control-plaintext { - display: inline-block; - } - .form-inline .input-group, - .form-inline .custom-select { - width: auto; - } - .form-inline .form-check { - display: -ms-flexbox; - display: flex; - -ms-flex-align: center; - align-items: center; - -ms-flex-pack: center; - justify-content: center; - width: auto; - padding-left: 0; - } - .form-inline .form-check-input { - position: relative; - margin-top: 0; - margin-right: 0.25rem; - margin-left: 0; - } - .form-inline .custom-control { - -ms-flex-align: center; - align-items: center; - -ms-flex-pack: center; - justify-content: center; - } - .form-inline .custom-control-label { - margin-bottom: 0; - } - } - - .btn { - display: inline-block; - font-weight: 400; - text-align: center; - white-space: nowrap; - vertical-align: middle; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - border: 1px solid transparent; - padding: 0.375rem 0.75rem; - font-size: 1rem; - line-height: 1.5; - border-radius: 0.25rem; - transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - } - - @media screen and (prefers-reduced-motion: reduce) { - .btn { - transition: none; - } - } - - .btn:hover, .btn:focus { - text-decoration: none; - } - - .btn:focus, .btn.focus { - outline: 0; - box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); - } - - .btn.disabled, .btn:disabled { - opacity: 0.65; - } - - .btn:not(:disabled):not(.disabled) { - cursor: pointer; - } - - a.btn.disabled, - fieldset:disabled a.btn { - pointer-events: none; - } - - .btn-primary { - color: #fff; - background-color: #007bff; - border-color: #007bff; - } - - .btn-primary:hover { - color: #fff; - background-color: #0069d9; - border-color: #0062cc; - } - - .btn-primary:focus, .btn-primary.focus { - box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); - } - - .btn-primary.disabled, .btn-primary:disabled { - color: #fff; - background-color: #007bff; - border-color: #007bff; - } - - .btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active, - .show > .btn-primary.dropdown-toggle { - color: #fff; - background-color: #0062cc; - border-color: #005cbf; - } - - .btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus, - .show > .btn-primary.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); - } - - .btn-secondary { - color: #fff; - background-color: #6c757d; - border-color: #6c757d; - } - - .btn-secondary:hover { - color: #fff; - background-color: #5a6268; - border-color: #545b62; - } - - .btn-secondary:focus, .btn-secondary.focus { - box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); - } - - .btn-secondary.disabled, .btn-secondary:disabled { - color: #fff; - background-color: #6c757d; - border-color: #6c757d; - } - - .btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active, - .show > .btn-secondary.dropdown-toggle { - color: #fff; - background-color: #545b62; - border-color: #4e555b; - } - - .btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus, - .show > .btn-secondary.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); - } - - .btn-success { - color: #fff; - background-color: #28a745; - border-color: #28a745; - } - - .btn-success:hover { - color: #fff; - background-color: #218838; - border-color: #1e7e34; - } - - .btn-success:focus, .btn-success.focus { - box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); - } - - .btn-success.disabled, .btn-success:disabled { - color: #fff; - background-color: #28a745; - border-color: #28a745; - } - - .btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active, - .show > .btn-success.dropdown-toggle { - color: #fff; - background-color: #1e7e34; - border-color: #1c7430; - } - - .btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus, - .show > .btn-success.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); - } - - .btn-info { - color: #fff; - background-color: #17a2b8; - border-color: #17a2b8; - } - - .btn-info:hover { - color: #fff; - background-color: #138496; - border-color: #117a8b; - } - - .btn-info:focus, .btn-info.focus { - box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); - } - - .btn-info.disabled, .btn-info:disabled { - color: #fff; - background-color: #17a2b8; - border-color: #17a2b8; - } - - .btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active, - .show > .btn-info.dropdown-toggle { - color: #fff; - background-color: #117a8b; - border-color: #10707f; - } - - .btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus, - .show > .btn-info.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); - } - - .btn-warning { - color: #212529; - background-color: #ffc107; - border-color: #ffc107; - } - - .btn-warning:hover { - color: #212529; - background-color: #e0a800; - border-color: #d39e00; - } - - .btn-warning:focus, .btn-warning.focus { - box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); - } - - .btn-warning.disabled, .btn-warning:disabled { - color: #212529; - background-color: #ffc107; - border-color: #ffc107; - } - - .btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active, - .show > .btn-warning.dropdown-toggle { - color: #212529; - background-color: #d39e00; - border-color: #c69500; - } - - .btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus, - .show > .btn-warning.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); - } - - .btn-danger { - color: #fff; - background-color: #dc3545; - border-color: #dc3545; - } - - .btn-danger:hover { - color: #fff; - background-color: #c82333; - border-color: #bd2130; - } - - .btn-danger:focus, .btn-danger.focus { - box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); - } - - .btn-danger.disabled, .btn-danger:disabled { - color: #fff; - background-color: #dc3545; - border-color: #dc3545; - } - - .btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active, - .show > .btn-danger.dropdown-toggle { - color: #fff; - background-color: #bd2130; - border-color: #b21f2d; - } - - .btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus, - .show > .btn-danger.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); - } - - .btn-light { - color: #212529; - background-color: #f8f9fa; - border-color: #f8f9fa; - } - - .btn-light:hover { - color: #212529; - background-color: #e2e6ea; - border-color: #dae0e5; - } - - .btn-light:focus, .btn-light.focus { - box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); - } - - .btn-light.disabled, .btn-light:disabled { - color: #212529; - background-color: #f8f9fa; - border-color: #f8f9fa; - } - - .btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active, - .show > .btn-light.dropdown-toggle { - color: #212529; - background-color: #dae0e5; - border-color: #d3d9df; - } - - .btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus, - .show > .btn-light.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); - } - - .btn-dark { - color: #fff; - background-color: #343a40; - border-color: #343a40; - } - - .btn-dark:hover { - color: #fff; - background-color: #23272b; - border-color: #1d2124; - } - - .btn-dark:focus, .btn-dark.focus { - box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); - } - - .btn-dark.disabled, .btn-dark:disabled { - color: #fff; - background-color: #343a40; - border-color: #343a40; - } - - .btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active, - .show > .btn-dark.dropdown-toggle { - color: #fff; - background-color: #1d2124; - border-color: #171a1d; - } - - .btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus, - .show > .btn-dark.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); - } - - .btn-outline-primary { - color: #007bff; - background-color: transparent; - background-image: none; - border-color: #007bff; - } - - .btn-outline-primary:hover { - color: #fff; - background-color: #007bff; - border-color: #007bff; - } - - .btn-outline-primary:focus, .btn-outline-primary.focus { - box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); - } - - .btn-outline-primary.disabled, .btn-outline-primary:disabled { - color: #007bff; - background-color: transparent; - } - - .btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active, - .show > .btn-outline-primary.dropdown-toggle { - color: #fff; - background-color: #007bff; - border-color: #007bff; - } - - .btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus, - .show > .btn-outline-primary.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); - } - - .btn-outline-secondary { - color: #6c757d; - background-color: transparent; - background-image: none; - border-color: #6c757d; - } - - .btn-outline-secondary:hover { - color: #fff; - background-color: #6c757d; - border-color: #6c757d; - } - - .btn-outline-secondary:focus, .btn-outline-secondary.focus { - box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); - } - - .btn-outline-secondary.disabled, .btn-outline-secondary:disabled { - color: #6c757d; - background-color: transparent; - } - - .btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active, - .show > .btn-outline-secondary.dropdown-toggle { - color: #fff; - background-color: #6c757d; - border-color: #6c757d; - } - - .btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus, - .show > .btn-outline-secondary.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); - } - - .btn-outline-success { - color: #28a745; - background-color: transparent; - background-image: none; - border-color: #28a745; - } - - .btn-outline-success:hover { - color: #fff; - background-color: #28a745; - border-color: #28a745; - } - - .btn-outline-success:focus, .btn-outline-success.focus { - box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); - } - - .btn-outline-success.disabled, .btn-outline-success:disabled { - color: #28a745; - background-color: transparent; - } - - .btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active, - .show > .btn-outline-success.dropdown-toggle { - color: #fff; - background-color: #28a745; - border-color: #28a745; - } - - .btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus, - .show > .btn-outline-success.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); - } - - .btn-outline-info { - color: #17a2b8; - background-color: transparent; - background-image: none; - border-color: #17a2b8; - } - - .btn-outline-info:hover { - color: #fff; - background-color: #17a2b8; - border-color: #17a2b8; - } - - .btn-outline-info:focus, .btn-outline-info.focus { - box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); - } - - .btn-outline-info.disabled, .btn-outline-info:disabled { - color: #17a2b8; - background-color: transparent; - } - - .btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active, - .show > .btn-outline-info.dropdown-toggle { - color: #fff; - background-color: #17a2b8; - border-color: #17a2b8; - } - - .btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus, - .show > .btn-outline-info.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); - } - - .btn-outline-warning { - color: #ffc107; - background-color: transparent; - background-image: none; - border-color: #ffc107; - } - - .btn-outline-warning:hover { - color: #212529; - background-color: #ffc107; - border-color: #ffc107; - } - - .btn-outline-warning:focus, .btn-outline-warning.focus { - box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); - } - - .btn-outline-warning.disabled, .btn-outline-warning:disabled { - color: #ffc107; - background-color: transparent; - } - - .btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active, - .show > .btn-outline-warning.dropdown-toggle { - color: #212529; - background-color: #ffc107; - border-color: #ffc107; - } - - .btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus, - .show > .btn-outline-warning.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); - } - - .btn-outline-danger { - color: #dc3545; - background-color: transparent; - background-image: none; - border-color: #dc3545; - } - - .btn-outline-danger:hover { - color: #fff; - background-color: #dc3545; - border-color: #dc3545; - } - - .btn-outline-danger:focus, .btn-outline-danger.focus { - box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); - } - - .btn-outline-danger.disabled, .btn-outline-danger:disabled { - color: #dc3545; - background-color: transparent; - } - - .btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active, - .show > .btn-outline-danger.dropdown-toggle { - color: #fff; - background-color: #dc3545; - border-color: #dc3545; - } - - .btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus, - .show > .btn-outline-danger.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); - } - - .btn-outline-light { - color: #f8f9fa; - background-color: transparent; - background-image: none; - border-color: #f8f9fa; - } - - .btn-outline-light:hover { - color: #212529; - background-color: #f8f9fa; - border-color: #f8f9fa; - } - - .btn-outline-light:focus, .btn-outline-light.focus { - box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); - } - - .btn-outline-light.disabled, .btn-outline-light:disabled { - color: #f8f9fa; - background-color: transparent; - } - - .btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active, - .show > .btn-outline-light.dropdown-toggle { - color: #212529; - background-color: #f8f9fa; - border-color: #f8f9fa; - } - - .btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus, - .show > .btn-outline-light.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); - } - - .btn-outline-dark { - color: #343a40; - background-color: transparent; - background-image: none; - border-color: #343a40; - } - - .btn-outline-dark:hover { - color: #fff; - background-color: #343a40; - border-color: #343a40; - } - - .btn-outline-dark:focus, .btn-outline-dark.focus { - box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); - } - - .btn-outline-dark.disabled, .btn-outline-dark:disabled { - color: #343a40; - background-color: transparent; - } - - .btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active, - .show > .btn-outline-dark.dropdown-toggle { - color: #fff; - background-color: #343a40; - border-color: #343a40; - } - - .btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus, - .show > .btn-outline-dark.dropdown-toggle:focus { - box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); - } - - .btn-link { - font-weight: 400; - color: #007bff; - background-color: transparent; - } - - .btn-link:hover { - color: #0056b3; - text-decoration: underline; - background-color: transparent; - border-color: transparent; - } - - .btn-link:focus, .btn-link.focus { - text-decoration: underline; - border-color: transparent; - box-shadow: none; - } - - .btn-link:disabled, .btn-link.disabled { - color: #6c757d; - pointer-events: none; - } - - .btn-lg, .btn-group-lg > .btn { - padding: 0.5rem 1rem; - font-size: 1.25rem; - line-height: 1.5; - border-radius: 0.3rem; - } - - .btn-sm, .btn-group-sm > .btn { - padding: 0.25rem 0.5rem; - font-size: 0.875rem; - line-height: 1.5; - border-radius: 0.2rem; - } - - .btn-block { - display: block; - width: 100%; - } - - .btn-block + .btn-block { - margin-top: 0.5rem; - } - - input[type="submit"].btn-block, - input[type="reset"].btn-block, - input[type="button"].btn-block { - width: 100%; - } - - .fade { - transition: opacity 0.15s linear; - } - - @media screen and (prefers-reduced-motion: reduce) { - .fade { - transition: none; - } - } - - .fade:not(.show) { - opacity: 0; - } - - .collapse:not(.show) { - display: none; - } - - .collapsing { - position: relative; - height: 0; - overflow: hidden; - transition: height 0.35s ease; - } - - @media screen and (prefers-reduced-motion: reduce) { - .collapsing { - transition: none; - } - } - - .dropup, - .dropright, - .dropdown, - .dropleft { - position: relative; - } - - .dropdown-toggle::after { - display: inline-block; - width: 0; - height: 0; - margin-left: 0.255em; - vertical-align: 0.255em; - content: ""; - border-top: 0.3em solid; - border-right: 0.3em solid transparent; - border-bottom: 0; - border-left: 0.3em solid transparent; - } - - .dropdown-toggle:empty::after { - margin-left: 0; - } - - .dropdown-menu { - position: absolute; - top: 100%; - left: 0; - z-index: 1000; - display: none; - float: left; - min-width: 10rem; - padding: 0.5rem 0; - margin: 0.125rem 0 0; - font-size: 1rem; - color: #212529; - text-align: left; - list-style: none; - background-color: #fff; - background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 0.25rem; - } - - .dropdown-menu-right { - right: 0; - left: auto; - } - - .dropup .dropdown-menu { - top: auto; - bottom: 100%; - margin-top: 0; - margin-bottom: 0.125rem; - } - - .dropup .dropdown-toggle::after { - display: inline-block; - width: 0; - height: 0; - margin-left: 0.255em; - vertical-align: 0.255em; - content: ""; - border-top: 0; - border-right: 0.3em solid transparent; - border-bottom: 0.3em solid; - border-left: 0.3em solid transparent; - } - - .dropup .dropdown-toggle:empty::after { - margin-left: 0; - } - - .dropright .dropdown-menu { - top: 0; - right: auto; - left: 100%; - margin-top: 0; - margin-left: 0.125rem; - } - - .dropright .dropdown-toggle::after { - display: inline-block; - width: 0; - height: 0; - margin-left: 0.255em; - vertical-align: 0.255em; - content: ""; - border-top: 0.3em solid transparent; - border-right: 0; - border-bottom: 0.3em solid transparent; - border-left: 0.3em solid; - } - - .dropright .dropdown-toggle:empty::after { - margin-left: 0; - } - - .dropright .dropdown-toggle::after { - vertical-align: 0; - } - - .dropleft .dropdown-menu { - top: 0; - right: 100%; - left: auto; - margin-top: 0; - margin-right: 0.125rem; - } - - .dropleft .dropdown-toggle::after { - display: inline-block; - width: 0; - height: 0; - margin-left: 0.255em; - vertical-align: 0.255em; - content: ""; - } - - .dropleft .dropdown-toggle::after { - display: none; - } - - .dropleft .dropdown-toggle::before { - display: inline-block; - width: 0; - height: 0; - margin-right: 0.255em; - vertical-align: 0.255em; - content: ""; - border-top: 0.3em solid transparent; - border-right: 0.3em solid; - border-bottom: 0.3em solid transparent; - } - - .dropleft .dropdown-toggle:empty::after { - margin-left: 0; - } - - .dropleft .dropdown-toggle::before { - vertical-align: 0; - } - - .dropdown-menu[x-placement^="top"], .dropdown-menu[x-placement^="right"], .dropdown-menu[x-placement^="bottom"], .dropdown-menu[x-placement^="left"] { - right: auto; - bottom: auto; - } - - .dropdown-divider { - height: 0; - margin: 0.5rem 0; - overflow: hidden; - border-top: 1px solid #e9ecef; - } - - .dropdown-item { - display: block; - width: 100%; - padding: 0.25rem 1.5rem; - clear: both; - font-weight: 400; - color: #212529; - text-align: inherit; - white-space: nowrap; - background-color: transparent; - border: 0; - } - - .dropdown-item:hover, .dropdown-item:focus { - color: #16181b; - text-decoration: none; - background-color: #f8f9fa; - } - - .dropdown-item.active, .dropdown-item:active { - color: #fff; - text-decoration: none; - background-color: #007bff; - } - - .dropdown-item.disabled, .dropdown-item:disabled { - color: #6c757d; - background-color: transparent; - } - - .dropdown-menu.show { - display: block; - } - - .dropdown-header { - display: block; - padding: 0.5rem 1.5rem; - margin-bottom: 0; - font-size: 0.875rem; - color: #6c757d; - white-space: nowrap; - } - - .dropdown-item-text { - display: block; - padding: 0.25rem 1.5rem; - color: #212529; - } - - .btn-group, - .btn-group-vertical { - position: relative; - display: -ms-inline-flexbox; - display: inline-flex; - vertical-align: middle; - } - - .btn-group > .btn, - .btn-group-vertical > .btn { - position: relative; - -ms-flex: 0 1 auto; - flex: 0 1 auto; - } - - .btn-group > .btn:hover, - .btn-group-vertical > .btn:hover { - z-index: 1; - } - - .btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active, - .btn-group-vertical > .btn:focus, - .btn-group-vertical > .btn:active, - .btn-group-vertical > .btn.active { - z-index: 1; - } - - .btn-group .btn + .btn, - .btn-group .btn + .btn-group, - .btn-group .btn-group + .btn, - .btn-group .btn-group + .btn-group, - .btn-group-vertical .btn + .btn, - .btn-group-vertical .btn + .btn-group, - .btn-group-vertical .btn-group + .btn, - .btn-group-vertical .btn-group + .btn-group { - margin-left: -1px; - } - - .btn-toolbar { - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - -ms-flex-pack: start; - justify-content: flex-start; - } - - .btn-toolbar .input-group { - width: auto; - } - - .btn-group > .btn:first-child { - margin-left: 0; - } - - .btn-group > .btn:not(:last-child):not(.dropdown-toggle), - .btn-group > .btn-group:not(:last-child) > .btn { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - - .btn-group > .btn:not(:first-child), - .btn-group > .btn-group:not(:first-child) > .btn { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - - .dropdown-toggle-split { - padding-right: 0.5625rem; - padding-left: 0.5625rem; - } - - .dropdown-toggle-split::after, - .dropup .dropdown-toggle-split::after, - .dropright .dropdown-toggle-split::after { - margin-left: 0; - } - - .dropleft .dropdown-toggle-split::before { - margin-right: 0; - } - - .btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { - padding-right: 0.375rem; - padding-left: 0.375rem; - } - - .btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { - padding-right: 0.75rem; - padding-left: 0.75rem; - } - - .btn-group-vertical { - -ms-flex-direction: column; - flex-direction: column; - -ms-flex-align: start; - align-items: flex-start; - -ms-flex-pack: center; - justify-content: center; - } - - .btn-group-vertical .btn, - .btn-group-vertical .btn-group { - width: 100%; - } - - .btn-group-vertical > .btn + .btn, - .btn-group-vertical > .btn + .btn-group, - .btn-group-vertical > .btn-group + .btn, - .btn-group-vertical > .btn-group + .btn-group { - margin-top: -1px; - margin-left: 0; - } - - .btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle), - .btn-group-vertical > .btn-group:not(:last-child) > .btn { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; - } - - .btn-group-vertical > .btn:not(:first-child), - .btn-group-vertical > .btn-group:not(:first-child) > .btn { - border-top-left-radius: 0; - border-top-right-radius: 0; - } - - .btn-group-toggle > .btn, - .btn-group-toggle > .btn-group > .btn { - margin-bottom: 0; - } - - .btn-group-toggle > .btn input[type="radio"], - .btn-group-toggle > .btn input[type="checkbox"], - .btn-group-toggle > .btn-group > .btn input[type="radio"], - .btn-group-toggle > .btn-group > .btn input[type="checkbox"] { - position: absolute; - clip: rect(0, 0, 0, 0); - pointer-events: none; - } - - .input-group { - position: relative; - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - -ms-flex-align: stretch; - align-items: stretch; - width: 100%; - } - - .input-group > .form-control, - .input-group > .custom-select, - .input-group > .custom-file { - position: relative; - -ms-flex: 1 1 auto; - flex: 1 1 auto; - width: 1%; - margin-bottom: 0; - } - - .input-group > .form-control + .form-control, - .input-group > .form-control + .custom-select, - .input-group > .form-control + .custom-file, - .input-group > .custom-select + .form-control, - .input-group > .custom-select + .custom-select, - .input-group > .custom-select + .custom-file, - .input-group > .custom-file + .form-control, - .input-group > .custom-file + .custom-select, - .input-group > .custom-file + .custom-file { - margin-left: -1px; - } - - .input-group > .form-control:focus, - .input-group > .custom-select:focus, - .input-group > .custom-file .custom-file-input:focus ~ .custom-file-label { - z-index: 3; - } - - .input-group > .custom-file .custom-file-input:focus { - z-index: 4; - } - - .input-group > .form-control:not(:last-child), - .input-group > .custom-select:not(:last-child) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - - .input-group > .form-control:not(:first-child), - .input-group > .custom-select:not(:first-child) { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - - .input-group > .custom-file { - display: -ms-flexbox; - display: flex; - -ms-flex-align: center; - align-items: center; - } - - .input-group > .custom-file:not(:last-child) .custom-file-label, - .input-group > .custom-file:not(:last-child) .custom-file-label::after { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - - .input-group > .custom-file:not(:first-child) .custom-file-label { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - - .input-group-prepend, - .input-group-append { - display: -ms-flexbox; - display: flex; - } - - .input-group-prepend .btn, - .input-group-append .btn { - position: relative; - z-index: 2; - } - - .input-group-prepend .btn + .btn, - .input-group-prepend .btn + .input-group-text, - .input-group-prepend .input-group-text + .input-group-text, - .input-group-prepend .input-group-text + .btn, - .input-group-append .btn + .btn, - .input-group-append .btn + .input-group-text, - .input-group-append .input-group-text + .input-group-text, - .input-group-append .input-group-text + .btn { - margin-left: -1px; - } - - .input-group-prepend { - margin-right: -1px; - } - - .input-group-append { - margin-left: -1px; - } - - .input-group-text { - display: -ms-flexbox; - display: flex; - -ms-flex-align: center; - align-items: center; - padding: 0.375rem 0.75rem; - margin-bottom: 0; - font-size: 1rem; - font-weight: 400; - line-height: 1.5; - color: #495057; - text-align: center; - white-space: nowrap; - background-color: #e9ecef; - border: 1px solid #ced4da; - border-radius: 0.25rem; - } - - .input-group-text input[type="radio"], - .input-group-text input[type="checkbox"] { - margin-top: 0; - } - - .input-group-lg > .form-control, - .input-group-lg > .input-group-prepend > .input-group-text, - .input-group-lg > .input-group-append > .input-group-text, - .input-group-lg > .input-group-prepend > .btn, - .input-group-lg > .input-group-append > .btn { - height: calc(2.875rem + 2px); - padding: 0.5rem 1rem; - font-size: 1.25rem; - line-height: 1.5; - border-radius: 0.3rem; - } - - .input-group-sm > .form-control, - .input-group-sm > .input-group-prepend > .input-group-text, - .input-group-sm > .input-group-append > .input-group-text, - .input-group-sm > .input-group-prepend > .btn, - .input-group-sm > .input-group-append > .btn { - height: calc(1.8125rem + 2px); - padding: 0.25rem 0.5rem; - font-size: 0.875rem; - line-height: 1.5; - border-radius: 0.2rem; - } - - .input-group > .input-group-prepend > .btn, - .input-group > .input-group-prepend > .input-group-text, - .input-group > .input-group-append:not(:last-child) > .btn, - .input-group > .input-group-append:not(:last-child) > .input-group-text, - .input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle), - .input-group > .input-group-append:last-child > .input-group-text:not(:last-child) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - - .input-group > .input-group-append > .btn, - .input-group > .input-group-append > .input-group-text, - .input-group > .input-group-prepend:not(:first-child) > .btn, - .input-group > .input-group-prepend:not(:first-child) > .input-group-text, - .input-group > .input-group-prepend:first-child > .btn:not(:first-child), - .input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - - .custom-control { - position: relative; - display: block; - min-height: 1.5rem; - padding-left: 1.5rem; - } - - .custom-control-inline { - display: -ms-inline-flexbox; - display: inline-flex; - margin-right: 1rem; - } - - .custom-control-input { - position: absolute; - z-index: -1; - opacity: 0; - } - - .custom-control-input:checked ~ .custom-control-label::before { - color: #fff; - background-color: #007bff; - } - - .custom-control-input:focus ~ .custom-control-label::before { - box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); - } - - .custom-control-input:active ~ .custom-control-label::before { - color: #fff; - background-color: #b3d7ff; - } - - .custom-control-input:disabled ~ .custom-control-label { - color: #6c757d; - } - - .custom-control-input:disabled ~ .custom-control-label::before { - background-color: #e9ecef; - } - - .custom-control-label { - position: relative; - margin-bottom: 0; - } - - .custom-control-label::before { - position: absolute; - top: 0.25rem; - left: -1.5rem; - display: block; - width: 1rem; - height: 1rem; - pointer-events: none; - content: ""; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - background-color: #dee2e6; - } - - .custom-control-label::after { - position: absolute; - top: 0.25rem; - left: -1.5rem; - display: block; - width: 1rem; - height: 1rem; - content: ""; - background-repeat: no-repeat; - background-position: center center; - background-size: 50% 50%; - } - - .custom-checkbox .custom-control-label::before { - border-radius: 0.25rem; - } - - .custom-checkbox .custom-control-input:checked ~ .custom-control-label::before { - background-color: #007bff; - } - - .custom-checkbox .custom-control-input:checked ~ .custom-control-label::after { - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"); - } - - .custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before { - background-color: #007bff; - } - - .custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after { - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E"); - } - - .custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before { - background-color: rgba(0, 123, 255, 0.5); - } - - .custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before { - background-color: rgba(0, 123, 255, 0.5); - } - - .custom-radio .custom-control-label::before { - border-radius: 50%; - } - - .custom-radio .custom-control-input:checked ~ .custom-control-label::before { - background-color: #007bff; - } - - .custom-radio .custom-control-input:checked ~ .custom-control-label::after { - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E"); - } - - .custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before { - background-color: rgba(0, 123, 255, 0.5); - } - - .custom-select { - display: inline-block; - width: 100%; - height: calc(2.25rem + 2px); - padding: 0.375rem 1.75rem 0.375rem 0.75rem; - line-height: 1.5; - color: #495057; - vertical-align: middle; - background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right 0.75rem center; - background-size: 8px 10px; - border: 1px solid #ced4da; - border-radius: 0.25rem; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - } - - .custom-select:focus { - border-color: #80bdff; - outline: 0; - box-shadow: 0 0 0 0.2rem rgba(128, 189, 255, 0.5); - } - - .custom-select:focus::-ms-value { - color: #495057; - background-color: #fff; - } - - .custom-select[multiple], .custom-select[size]:not([size="1"]) { - height: auto; - padding-right: 0.75rem; - background-image: none; - } - - .custom-select:disabled { - color: #6c757d; - background-color: #e9ecef; - } - - .custom-select::-ms-expand { - opacity: 0; - } - - .custom-select-sm { - height: calc(1.8125rem + 2px); - padding-top: 0.375rem; - padding-bottom: 0.375rem; - font-size: 75%; - } - - .custom-select-lg { - height: calc(2.875rem + 2px); - padding-top: 0.375rem; - padding-bottom: 0.375rem; - font-size: 125%; - } - - .custom-file { - position: relative; - display: inline-block; - width: 100%; - height: calc(2.25rem + 2px); - margin-bottom: 0; - } - - .custom-file-input { - position: relative; - z-index: 2; - width: 100%; - height: calc(2.25rem + 2px); - margin: 0; - opacity: 0; - } - - .custom-file-input:focus ~ .custom-file-label { - border-color: #80bdff; - box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); - } - - .custom-file-input:focus ~ .custom-file-label::after { - border-color: #80bdff; - } - - .custom-file-input:disabled ~ .custom-file-label { - background-color: #e9ecef; - } - - .custom-file-input:lang(en) ~ .custom-file-label::after { - content: "Browse"; - } - - .custom-file-label { - position: absolute; - top: 0; - right: 0; - left: 0; - z-index: 1; - height: calc(2.25rem + 2px); - padding: 0.375rem 0.75rem; - line-height: 1.5; - color: #495057; - background-color: #fff; - border: 1px solid #ced4da; - border-radius: 0.25rem; - } - - .custom-file-label::after { - position: absolute; - top: 0; - right: 0; - bottom: 0; - z-index: 3; - display: block; - height: 2.25rem; - padding: 0.375rem 0.75rem; - line-height: 1.5; - color: #495057; - content: "Browse"; - background-color: #e9ecef; - border-left: 1px solid #ced4da; - border-radius: 0 0.25rem 0.25rem 0; - } - - .custom-range { - width: 100%; - padding-left: 0; - background-color: transparent; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - } - - .custom-range:focus { - outline: none; - } - - .custom-range:focus::-webkit-slider-thumb { - box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); - } - - .custom-range:focus::-moz-range-thumb { - box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); - } - - .custom-range:focus::-ms-thumb { - box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); - } - - .custom-range::-moz-focus-outer { - border: 0; - } - - .custom-range::-webkit-slider-thumb { - width: 1rem; - height: 1rem; - margin-top: -0.25rem; - background-color: #007bff; - border: 0; - border-radius: 1rem; - transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - -webkit-appearance: none; - appearance: none; - } - - @media screen and (prefers-reduced-motion: reduce) { - .custom-range::-webkit-slider-thumb { - transition: none; - } - } - - .custom-range::-webkit-slider-thumb:active { - background-color: #b3d7ff; - } - - .custom-range::-webkit-slider-runnable-track { - width: 100%; - height: 0.5rem; - color: transparent; - cursor: pointer; - background-color: #dee2e6; - border-color: transparent; - border-radius: 1rem; - } - - .custom-range::-moz-range-thumb { - width: 1rem; - height: 1rem; - background-color: #007bff; - border: 0; - border-radius: 1rem; - transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - -moz-appearance: none; - appearance: none; - } - - @media screen and (prefers-reduced-motion: reduce) { - .custom-range::-moz-range-thumb { - transition: none; - } - } - - .custom-range::-moz-range-thumb:active { - background-color: #b3d7ff; - } - - .custom-range::-moz-range-track { - width: 100%; - height: 0.5rem; - color: transparent; - cursor: pointer; - background-color: #dee2e6; - border-color: transparent; - border-radius: 1rem; - } - - .custom-range::-ms-thumb { - width: 1rem; - height: 1rem; - margin-top: 0; - margin-right: 0.2rem; - margin-left: 0.2rem; - background-color: #007bff; - border: 0; - border-radius: 1rem; - transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - appearance: none; - } - - @media screen and (prefers-reduced-motion: reduce) { - .custom-range::-ms-thumb { - transition: none; - } - } - - .custom-range::-ms-thumb:active { - background-color: #b3d7ff; - } - - .custom-range::-ms-track { - width: 100%; - height: 0.5rem; - color: transparent; - cursor: pointer; - background-color: transparent; - border-color: transparent; - border-width: 0.5rem; - } - - .custom-range::-ms-fill-lower { - background-color: #dee2e6; - border-radius: 1rem; - } - - .custom-range::-ms-fill-upper { - margin-right: 15px; - background-color: #dee2e6; - border-radius: 1rem; - } - - .custom-control-label::before, - .custom-file-label, - .custom-select { - transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - } - - @media screen and (prefers-reduced-motion: reduce) { - .custom-control-label::before, - .custom-file-label, - .custom-select { - transition: none; - } - } - - .nav { - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - padding-left: 0; - margin-bottom: 0; - list-style: none; - } - - .nav-link { - display: block; - padding: 0.5rem 1rem; - } - - .nav-link:hover, .nav-link:focus { - text-decoration: none; - } - - .nav-link.disabled { - color: #6c757d; - } - - .nav-tabs { - border-bottom: 1px solid #dee2e6; - } - - .nav-tabs .nav-item { - margin-bottom: -1px; - } - - .nav-tabs .nav-link { - border: 1px solid transparent; - border-top-left-radius: 0.25rem; - border-top-right-radius: 0.25rem; - } - - .nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { - border-color: #e9ecef #e9ecef #dee2e6; - } - - .nav-tabs .nav-link.disabled { - color: #6c757d; - background-color: transparent; - border-color: transparent; - } - - .nav-tabs .nav-link.active, - .nav-tabs .nav-item.show .nav-link { - color: #495057; - background-color: #fff; - border-color: #dee2e6 #dee2e6 #fff; - } - - .nav-tabs .dropdown-menu { - margin-top: -1px; - border-top-left-radius: 0; - border-top-right-radius: 0; - } - - .nav-pills .nav-link { - border-radius: 0.25rem; - } - - .nav-pills .nav-link.active, - .nav-pills .show > .nav-link { - color: #fff; - background-color: #007bff; - } - - .nav-fill .nav-item { - -ms-flex: 1 1 auto; - flex: 1 1 auto; - text-align: center; - } - - .nav-justified .nav-item { - -ms-flex-preferred-size: 0; - flex-basis: 0; - -ms-flex-positive: 1; - flex-grow: 1; - text-align: center; - } - - .tab-content > .tab-pane { - display: none; - } - - .tab-content > .active { - display: block; - } - - .navbar { - position: relative; - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - -ms-flex-align: center; - align-items: center; - -ms-flex-pack: justify; - justify-content: space-between; - padding: 0.5rem 1rem; - } - - .navbar > .container, - .navbar > .container-fluid { - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - -ms-flex-align: center; - align-items: center; - -ms-flex-pack: justify; - justify-content: space-between; - } - - .navbar-brand { - display: inline-block; - padding-top: 0.3125rem; - padding-bottom: 0.3125rem; - margin-right: 1rem; - font-size: 1.25rem; - line-height: inherit; - white-space: nowrap; - } - - .navbar-brand:hover, .navbar-brand:focus { - text-decoration: none; - } - - .navbar-nav { - display: -ms-flexbox; - display: flex; - -ms-flex-direction: column; - flex-direction: column; - padding-left: 0; - margin-bottom: 0; - list-style: none; - } - - .navbar-nav .nav-link { - padding-right: 0; - padding-left: 0; - } - - .navbar-nav .dropdown-menu { - position: static; - float: none; - } - - .navbar-text { - display: inline-block; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - } - - .navbar-collapse { - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - -ms-flex-positive: 1; - flex-grow: 1; - -ms-flex-align: center; - align-items: center; - } - - .navbar-toggler { - padding: 0.25rem 0.75rem; - font-size: 1.25rem; - line-height: 1; - background-color: transparent; - border: 1px solid transparent; - border-radius: 0.25rem; - } - - .navbar-toggler:hover, .navbar-toggler:focus { - text-decoration: none; - } - - .navbar-toggler:not(:disabled):not(.disabled) { - cursor: pointer; - } - - .navbar-toggler-icon { - display: inline-block; - width: 1.5em; - height: 1.5em; - vertical-align: middle; - content: ""; - background: no-repeat center center; - background-size: 100% 100%; - } - - @media (max-width: 575.98px) { - .navbar-expand-sm > .container, - .navbar-expand-sm > .container-fluid { - padding-right: 0; - padding-left: 0; - } - } - - @media (min-width: 576px) { - .navbar-expand-sm { - -ms-flex-flow: row nowrap; - flex-flow: row nowrap; - -ms-flex-pack: start; - justify-content: flex-start; - } - .navbar-expand-sm .navbar-nav { - -ms-flex-direction: row; - flex-direction: row; - } - .navbar-expand-sm .navbar-nav .dropdown-menu { - position: absolute; - } - .navbar-expand-sm .navbar-nav .nav-link { - padding-right: 0.5rem; - padding-left: 0.5rem; - } - .navbar-expand-sm > .container, - .navbar-expand-sm > .container-fluid { - -ms-flex-wrap: nowrap; - flex-wrap: nowrap; - } - .navbar-expand-sm .navbar-collapse { - display: -ms-flexbox !important; - display: flex !important; - -ms-flex-preferred-size: auto; - flex-basis: auto; - } - .navbar-expand-sm .navbar-toggler { - display: none; - } - } - - @media (max-width: 767.98px) { - .navbar-expand-md > .container, - .navbar-expand-md > .container-fluid { - padding-right: 0; - padding-left: 0; - } - } - - @media (min-width: 768px) { - .navbar-expand-md { - -ms-flex-flow: row nowrap; - flex-flow: row nowrap; - -ms-flex-pack: start; - justify-content: flex-start; - } - .navbar-expand-md .navbar-nav { - -ms-flex-direction: row; - flex-direction: row; - } - .navbar-expand-md .navbar-nav .dropdown-menu { - position: absolute; - } - .navbar-expand-md .navbar-nav .nav-link { - padding-right: 0.5rem; - padding-left: 0.5rem; - } - .navbar-expand-md > .container, - .navbar-expand-md > .container-fluid { - -ms-flex-wrap: nowrap; - flex-wrap: nowrap; - } - .navbar-expand-md .navbar-collapse { - display: -ms-flexbox !important; - display: flex !important; - -ms-flex-preferred-size: auto; - flex-basis: auto; - } - .navbar-expand-md .navbar-toggler { - display: none; - } - } - - @media (max-width: 991.98px) { - .navbar-expand-lg > .container, - .navbar-expand-lg > .container-fluid { - padding-right: 0; - padding-left: 0; - } - } - - @media (min-width: 992px) { - .navbar-expand-lg { - -ms-flex-flow: row nowrap; - flex-flow: row nowrap; - -ms-flex-pack: start; - justify-content: flex-start; - } - .navbar-expand-lg .navbar-nav { - -ms-flex-direction: row; - flex-direction: row; - } - .navbar-expand-lg .navbar-nav .dropdown-menu { - position: absolute; - } - .navbar-expand-lg .navbar-nav .nav-link { - padding-right: 0.5rem; - padding-left: 0.5rem; - } - .navbar-expand-lg > .container, - .navbar-expand-lg > .container-fluid { - -ms-flex-wrap: nowrap; - flex-wrap: nowrap; - } - .navbar-expand-lg .navbar-collapse { - display: -ms-flexbox !important; - display: flex !important; - -ms-flex-preferred-size: auto; - flex-basis: auto; - } - .navbar-expand-lg .navbar-toggler { - display: none; - } - } - - @media (max-width: 1199.98px) { - .navbar-expand-xl > .container, - .navbar-expand-xl > .container-fluid { - padding-right: 0; - padding-left: 0; - } - } - - @media (min-width: 1200px) { - .navbar-expand-xl { - -ms-flex-flow: row nowrap; - flex-flow: row nowrap; - -ms-flex-pack: start; - justify-content: flex-start; - } - .navbar-expand-xl .navbar-nav { - -ms-flex-direction: row; - flex-direction: row; - } - .navbar-expand-xl .navbar-nav .dropdown-menu { - position: absolute; - } - .navbar-expand-xl .navbar-nav .nav-link { - padding-right: 0.5rem; - padding-left: 0.5rem; - } - .navbar-expand-xl > .container, - .navbar-expand-xl > .container-fluid { - -ms-flex-wrap: nowrap; - flex-wrap: nowrap; - } - .navbar-expand-xl .navbar-collapse { - display: -ms-flexbox !important; - display: flex !important; - -ms-flex-preferred-size: auto; - flex-basis: auto; - } - .navbar-expand-xl .navbar-toggler { - display: none; - } - } - - .navbar-expand { - -ms-flex-flow: row nowrap; - flex-flow: row nowrap; - -ms-flex-pack: start; - justify-content: flex-start; - } - - .navbar-expand > .container, - .navbar-expand > .container-fluid { - padding-right: 0; - padding-left: 0; - } - - .navbar-expand .navbar-nav { - -ms-flex-direction: row; - flex-direction: row; - } - - .navbar-expand .navbar-nav .dropdown-menu { - position: absolute; - } - - .navbar-expand .navbar-nav .nav-link { - padding-right: 0.5rem; - padding-left: 0.5rem; - } - - .navbar-expand > .container, - .navbar-expand > .container-fluid { - -ms-flex-wrap: nowrap; - flex-wrap: nowrap; - } - - .navbar-expand .navbar-collapse { - display: -ms-flexbox !important; - display: flex !important; - -ms-flex-preferred-size: auto; - flex-basis: auto; - } - - .navbar-expand .navbar-toggler { - display: none; - } - - .navbar-light .navbar-brand { - color: rgba(0, 0, 0, 0.9); - } - - .navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus { - color: rgba(0, 0, 0, 0.9); - } - - .navbar-light .navbar-nav .nav-link { - color: rgba(0, 0, 0, 0.5); - } - - .navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus { - color: rgba(0, 0, 0, 0.7); - } - - .navbar-light .navbar-nav .nav-link.disabled { - color: rgba(0, 0, 0, 0.3); - } - - .navbar-light .navbar-nav .show > .nav-link, - .navbar-light .navbar-nav .active > .nav-link, - .navbar-light .navbar-nav .nav-link.show, - .navbar-light .navbar-nav .nav-link.active { - color: rgba(0, 0, 0, 0.9); - } - - .navbar-light .navbar-toggler { - color: rgba(0, 0, 0, 0.5); - border-color: rgba(0, 0, 0, 0.1); - } - - .navbar-light .navbar-toggler-icon { - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); - } - - .navbar-light .navbar-text { - color: rgba(0, 0, 0, 0.5); - } - - .navbar-light .navbar-text a { - color: rgba(0, 0, 0, 0.9); - } - - .navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus { - color: rgba(0, 0, 0, 0.9); - } - - .navbar-dark .navbar-brand { - color: #fff; - } - - .navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus { - color: #fff; - } - - .navbar-dark .navbar-nav .nav-link { - color: rgba(255, 255, 255, 0.5); - } - - .navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus { - color: rgba(255, 255, 255, 0.75); - } - - .navbar-dark .navbar-nav .nav-link.disabled { - color: rgba(255, 255, 255, 0.25); - } - - .navbar-dark .navbar-nav .show > .nav-link, - .navbar-dark .navbar-nav .active > .nav-link, - .navbar-dark .navbar-nav .nav-link.show, - .navbar-dark .navbar-nav .nav-link.active { - color: #fff; - } - - .navbar-dark .navbar-toggler { - color: rgba(255, 255, 255, 0.5); - border-color: rgba(255, 255, 255, 0.1); - } - - .navbar-dark .navbar-toggler-icon { - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); - } - - .navbar-dark .navbar-text { - color: rgba(255, 255, 255, 0.5); - } - - .navbar-dark .navbar-text a { - color: #fff; - } - - .navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus { - color: #fff; - } - - .card { - position: relative; - display: -ms-flexbox; - display: flex; - -ms-flex-direction: column; - flex-direction: column; - min-width: 0; - word-wrap: break-word; - background-color: #fff; - background-clip: border-box; - border: 1px solid rgba(0, 0, 0, 0.125); - border-radius: 0.25rem; - } - - .card > hr { - margin-right: 0; - margin-left: 0; - } - - .card > .list-group:first-child .list-group-item:first-child { - border-top-left-radius: 0.25rem; - border-top-right-radius: 0.25rem; - } - - .card > .list-group:last-child .list-group-item:last-child { - border-bottom-right-radius: 0.25rem; - border-bottom-left-radius: 0.25rem; - } - - .card-body { - -ms-flex: 1 1 auto; - flex: 1 1 auto; - padding: 1.25rem; - } - - .card-title { - margin-bottom: 0.75rem; - } - - .card-subtitle { - margin-top: -0.375rem; - margin-bottom: 0; - } - - .card-text:last-child { - margin-bottom: 0; - } - - .card-link:hover { - text-decoration: none; - } - - .card-link + .card-link { - margin-left: 1.25rem; - } - - .card-header { - padding: 0.75rem 1.25rem; - margin-bottom: 0; - background-color: rgba(0, 0, 0, 0.03); - border-bottom: 1px solid rgba(0, 0, 0, 0.125); - } - - .card-header:first-child { - border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; - } - - .card-header + .list-group .list-group-item:first-child { - border-top: 0; - } - - .card-footer { - padding: 0.75rem 1.25rem; - background-color: rgba(0, 0, 0, 0.03); - border-top: 1px solid rgba(0, 0, 0, 0.125); - } - - .card-footer:last-child { - border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); - } - - .card-header-tabs { - margin-right: -0.625rem; - margin-bottom: -0.75rem; - margin-left: -0.625rem; - border-bottom: 0; - } - - .card-header-pills { - margin-right: -0.625rem; - margin-left: -0.625rem; - } - - .card-img-overlay { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - padding: 1.25rem; - } - - .card-img { - width: 100%; - border-radius: calc(0.25rem - 1px); - } - - .card-img-top { - width: 100%; - border-top-left-radius: calc(0.25rem - 1px); - border-top-right-radius: calc(0.25rem - 1px); - } - - .card-img-bottom { - width: 100%; - border-bottom-right-radius: calc(0.25rem - 1px); - border-bottom-left-radius: calc(0.25rem - 1px); - } - - .card-deck { - display: -ms-flexbox; - display: flex; - -ms-flex-direction: column; - flex-direction: column; - } - - .card-deck .card { - margin-bottom: 15px; - } - - @media (min-width: 576px) { - .card-deck { - -ms-flex-flow: row wrap; - flex-flow: row wrap; - margin-right: -15px; - margin-left: -15px; - } - .card-deck .card { - display: -ms-flexbox; - display: flex; - -ms-flex: 1 0 0%; - flex: 1 0 0%; - -ms-flex-direction: column; - flex-direction: column; - margin-right: 15px; - margin-bottom: 0; - margin-left: 15px; - } - } - - .card-group { - display: -ms-flexbox; - display: flex; - -ms-flex-direction: column; - flex-direction: column; - } - - .card-group > .card { - margin-bottom: 15px; - } - - @media (min-width: 576px) { - .card-group { - -ms-flex-flow: row wrap; - flex-flow: row wrap; - } - .card-group > .card { - -ms-flex: 1 0 0%; - flex: 1 0 0%; - margin-bottom: 0; - } - .card-group > .card + .card { - margin-left: 0; - border-left: 0; - } - .card-group > .card:first-child { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - .card-group > .card:first-child .card-img-top, - .card-group > .card:first-child .card-header { - border-top-right-radius: 0; - } - .card-group > .card:first-child .card-img-bottom, - .card-group > .card:first-child .card-footer { - border-bottom-right-radius: 0; - } - .card-group > .card:last-child { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - .card-group > .card:last-child .card-img-top, - .card-group > .card:last-child .card-header { - border-top-left-radius: 0; - } - .card-group > .card:last-child .card-img-bottom, - .card-group > .card:last-child .card-footer { - border-bottom-left-radius: 0; - } - .card-group > .card:only-child { - border-radius: 0.25rem; - } - .card-group > .card:only-child .card-img-top, - .card-group > .card:only-child .card-header { - border-top-left-radius: 0.25rem; - border-top-right-radius: 0.25rem; - } - .card-group > .card:only-child .card-img-bottom, - .card-group > .card:only-child .card-footer { - border-bottom-right-radius: 0.25rem; - border-bottom-left-radius: 0.25rem; - } - .card-group > .card:not(:first-child):not(:last-child):not(:only-child) { - border-radius: 0; - } - .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-top, - .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom, - .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-header, - .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-footer { - border-radius: 0; - } - } - - .card-columns .card { - margin-bottom: 0.75rem; - } - - @media (min-width: 576px) { - .card-columns { - -webkit-column-count: 3; - -moz-column-count: 3; - column-count: 3; - -webkit-column-gap: 1.25rem; - -moz-column-gap: 1.25rem; - column-gap: 1.25rem; - orphans: 1; - widows: 1; - } - .card-columns .card { - display: inline-block; - width: 100%; - } - } - - .accordion .card:not(:first-of-type):not(:last-of-type) { - border-bottom: 0; - border-radius: 0; - } - - .accordion .card:not(:first-of-type) .card-header:first-child { - border-radius: 0; - } - - .accordion .card:first-of-type { - border-bottom: 0; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; - } - - .accordion .card:last-of-type { - border-top-left-radius: 0; - border-top-right-radius: 0; - } - - .breadcrumb { - display: -ms-flexbox; - display: flex; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - padding: 0.75rem 1rem; - margin-bottom: 1rem; - list-style: none; - background-color: #e9ecef; - border-radius: 0.25rem; - } - - .breadcrumb-item + .breadcrumb-item { - padding-left: 0.5rem; - } - - .breadcrumb-item + .breadcrumb-item::before { - display: inline-block; - padding-right: 0.5rem; - color: #6c757d; - content: "/"; - } - - .breadcrumb-item + .breadcrumb-item:hover::before { - text-decoration: underline; - } - - .breadcrumb-item + .breadcrumb-item:hover::before { - text-decoration: none; - } - - .breadcrumb-item.active { - color: #6c757d; - } - - .pagination { - display: -ms-flexbox; - display: flex; - padding-left: 0; - list-style: none; - border-radius: 0.25rem; - } - - .page-link { - position: relative; - display: block; - padding: 0.5rem 0.75rem; - margin-left: -1px; - line-height: 1.25; - color: #007bff; - background-color: #fff; - border: 1px solid #dee2e6; - } - - .page-link:hover { - z-index: 2; - color: #0056b3; - text-decoration: none; - background-color: #e9ecef; - border-color: #dee2e6; - } - - .page-link:focus { - z-index: 2; - outline: 0; - box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); - } - - .page-link:not(:disabled):not(.disabled) { - cursor: pointer; - } - - .page-item:first-child .page-link { - margin-left: 0; - border-top-left-radius: 0.25rem; - border-bottom-left-radius: 0.25rem; - } - - .page-item:last-child .page-link { - border-top-right-radius: 0.25rem; - border-bottom-right-radius: 0.25rem; - } - - .page-item.active .page-link { - z-index: 1; - color: #fff; - background-color: #007bff; - border-color: #007bff; - } - - .page-item.disabled .page-link { - color: #6c757d; - pointer-events: none; - cursor: auto; - background-color: #fff; - border-color: #dee2e6; - } - - .pagination-lg .page-link { - padding: 0.75rem 1.5rem; - font-size: 1.25rem; - line-height: 1.5; - } - - .pagination-lg .page-item:first-child .page-link { - border-top-left-radius: 0.3rem; - border-bottom-left-radius: 0.3rem; - } - - .pagination-lg .page-item:last-child .page-link { - border-top-right-radius: 0.3rem; - border-bottom-right-radius: 0.3rem; - } - - .pagination-sm .page-link { - padding: 0.25rem 0.5rem; - font-size: 0.875rem; - line-height: 1.5; - } - - .pagination-sm .page-item:first-child .page-link { - border-top-left-radius: 0.2rem; - border-bottom-left-radius: 0.2rem; - } - - .pagination-sm .page-item:last-child .page-link { - border-top-right-radius: 0.2rem; - border-bottom-right-radius: 0.2rem; - } - - .badge { - display: inline-block; - padding: 0.25em 0.4em; - font-size: 75%; - font-weight: 700; - line-height: 1; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: 0.25rem; - } - - .badge:empty { - display: none; - } - - .btn .badge { - position: relative; - top: -1px; - } - - .badge-pill { - padding-right: 0.6em; - padding-left: 0.6em; - border-radius: 10rem; - } - - .badge-primary { - color: #fff; - background-color: #007bff; - } - - .badge-primary[href]:hover, .badge-primary[href]:focus { - color: #fff; - text-decoration: none; - background-color: #0062cc; - } - - .badge-secondary { - color: #fff; - background-color: #6c757d; - } - - .badge-secondary[href]:hover, .badge-secondary[href]:focus { - color: #fff; - text-decoration: none; - background-color: #545b62; - } - - .badge-success { - color: #fff; - background-color: #28a745; - } - - .badge-success[href]:hover, .badge-success[href]:focus { - color: #fff; - text-decoration: none; - background-color: #1e7e34; - } - - .badge-info { - color: #fff; - background-color: #17a2b8; - } - - .badge-info[href]:hover, .badge-info[href]:focus { - color: #fff; - text-decoration: none; - background-color: #117a8b; - } - - .badge-warning { - color: #212529; - background-color: #ffc107; - } - - .badge-warning[href]:hover, .badge-warning[href]:focus { - color: #212529; - text-decoration: none; - background-color: #d39e00; - } - - .badge-danger { - color: #fff; - background-color: #dc3545; - } - - .badge-danger[href]:hover, .badge-danger[href]:focus { - color: #fff; - text-decoration: none; - background-color: #bd2130; - } - - .badge-light { - color: #212529; - background-color: #f8f9fa; - } - - .badge-light[href]:hover, .badge-light[href]:focus { - color: #212529; - text-decoration: none; - background-color: #dae0e5; - } - - .badge-dark { - color: #fff; - background-color: #343a40; - } - - .badge-dark[href]:hover, .badge-dark[href]:focus { - color: #fff; - text-decoration: none; - background-color: #1d2124; - } - - .jumbotron { - padding: 2rem 1rem; - margin-bottom: 2rem; - background-color: #e9ecef; - border-radius: 0.3rem; - } - - @media (min-width: 576px) { - .jumbotron { - padding: 4rem 2rem; - } - } - - .jumbotron-fluid { - padding-right: 0; - padding-left: 0; - border-radius: 0; - } - - .alert { - position: relative; - padding: 0.75rem 1.25rem; - margin-bottom: 1rem; - border: 1px solid transparent; - border-radius: 0.25rem; - } - - .alert-heading { - color: inherit; - } - - .alert-link { - font-weight: 700; - } - - .alert-dismissible { - padding-right: 4rem; - } - - .alert-dismissible .close { - position: absolute; - top: 0; - right: 0; - padding: 0.75rem 1.25rem; - color: inherit; - } - - .alert-primary { - color: #004085; - background-color: #cce5ff; - border-color: #b8daff; - } - - .alert-primary hr { - border-top-color: #9fcdff; - } - - .alert-primary .alert-link { - color: #002752; - } - - .alert-secondary { - color: #383d41; - background-color: #e2e3e5; - border-color: #d6d8db; - } - - .alert-secondary hr { - border-top-color: #c8cbcf; - } - - .alert-secondary .alert-link { - color: #202326; - } - - .alert-success { - color: #155724; - background-color: #d4edda; - border-color: #c3e6cb; - } - - .alert-success hr { - border-top-color: #b1dfbb; - } - - .alert-success .alert-link { - color: #0b2e13; - } - - .alert-info { - color: #0c5460; - background-color: #d1ecf1; - border-color: #bee5eb; - } - - .alert-info hr { - border-top-color: #abdde5; - } - - .alert-info .alert-link { - color: #062c33; - } - - .alert-warning { - color: #856404; - background-color: #fff3cd; - border-color: #ffeeba; - } - - .alert-warning hr { - border-top-color: #ffe8a1; - } - - .alert-warning .alert-link { - color: #533f03; - } - - .alert-danger { - color: #721c24; - background-color: #f8d7da; - border-color: #f5c6cb; - } - - .alert-danger hr { - border-top-color: #f1b0b7; - } - - .alert-danger .alert-link { - color: #491217; - } - - .alert-light { - color: #818182; - background-color: #fefefe; - border-color: #fdfdfe; - } - - .alert-light hr { - border-top-color: #ececf6; - } - - .alert-light .alert-link { - color: #686868; - } - - .alert-dark { - color: #1b1e21; - background-color: #d6d8d9; - border-color: #c6c8ca; - } - - .alert-dark hr { - border-top-color: #b9bbbe; - } - - .alert-dark .alert-link { - color: #040505; - } - - @-webkit-keyframes progress-bar-stripes { - from { - background-position: 1rem 0; - } - to { - background-position: 0 0; - } - } - - @keyframes progress-bar-stripes { - from { - background-position: 1rem 0; - } - to { - background-position: 0 0; - } - } - - .progress { - display: -ms-flexbox; - display: flex; - height: 1rem; - overflow: hidden; - font-size: 0.75rem; - background-color: #e9ecef; - border-radius: 0.25rem; - } - - .progress-bar { - display: -ms-flexbox; - display: flex; - -ms-flex-direction: column; - flex-direction: column; - -ms-flex-pack: center; - justify-content: center; - color: #fff; - text-align: center; - white-space: nowrap; - background-color: #007bff; - transition: width 0.6s ease; - } - - @media screen and (prefers-reduced-motion: reduce) { - .progress-bar { - transition: none; - } - } - - .progress-bar-striped { - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-size: 1rem 1rem; - } - - .progress-bar-animated { - -webkit-animation: progress-bar-stripes 1s linear infinite; - animation: progress-bar-stripes 1s linear infinite; - } - - .media { - display: -ms-flexbox; - display: flex; - -ms-flex-align: start; - align-items: flex-start; - } - - .media-body { - -ms-flex: 1; - flex: 1; - } - - .list-group { - display: -ms-flexbox; - display: flex; - -ms-flex-direction: column; - flex-direction: column; - padding-left: 0; - margin-bottom: 0; - } - - .list-group-item-action { - width: 100%; - color: #495057; - text-align: inherit; - } - - .list-group-item-action:hover, .list-group-item-action:focus { - color: #495057; - text-decoration: none; - background-color: #f8f9fa; - } - - .list-group-item-action:active { - color: #212529; - background-color: #e9ecef; - } - - .list-group-item { - position: relative; - display: block; - padding: 0.75rem 1.25rem; - margin-bottom: -1px; - background-color: #fff; - border: 1px solid rgba(0, 0, 0, 0.125); - } - - .list-group-item:first-child { - border-top-left-radius: 0.25rem; - border-top-right-radius: 0.25rem; - } - - .list-group-item:last-child { - margin-bottom: 0; - border-bottom-right-radius: 0.25rem; - border-bottom-left-radius: 0.25rem; - } - - .list-group-item:hover, .list-group-item:focus { - z-index: 1; - text-decoration: none; - } - - .list-group-item.disabled, .list-group-item:disabled { - color: #6c757d; - background-color: #fff; - } - - .list-group-item.active { - z-index: 2; - color: #fff; - background-color: #007bff; - border-color: #007bff; - } - - .list-group-flush .list-group-item { - border-right: 0; - border-left: 0; - border-radius: 0; - } - - .list-group-flush:first-child .list-group-item:first-child { - border-top: 0; - } - - .list-group-flush:last-child .list-group-item:last-child { - border-bottom: 0; - } - - .list-group-item-primary { - color: #004085; - background-color: #b8daff; - } - - .list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus { - color: #004085; - background-color: #9fcdff; - } - - .list-group-item-primary.list-group-item-action.active { - color: #fff; - background-color: #004085; - border-color: #004085; - } - - .list-group-item-secondary { - color: #383d41; - background-color: #d6d8db; - } - - .list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus { - color: #383d41; - background-color: #c8cbcf; - } - - .list-group-item-secondary.list-group-item-action.active { - color: #fff; - background-color: #383d41; - border-color: #383d41; - } - - .list-group-item-success { - color: #155724; - background-color: #c3e6cb; - } - - .list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus { - color: #155724; - background-color: #b1dfbb; - } - - .list-group-item-success.list-group-item-action.active { - color: #fff; - background-color: #155724; - border-color: #155724; - } - - .list-group-item-info { - color: #0c5460; - background-color: #bee5eb; - } - - .list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus { - color: #0c5460; - background-color: #abdde5; - } - - .list-group-item-info.list-group-item-action.active { - color: #fff; - background-color: #0c5460; - border-color: #0c5460; - } - - .list-group-item-warning { - color: #856404; - background-color: #ffeeba; - } - - .list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus { - color: #856404; - background-color: #ffe8a1; - } - - .list-group-item-warning.list-group-item-action.active { - color: #fff; - background-color: #856404; - border-color: #856404; - } - - .list-group-item-danger { - color: #721c24; - background-color: #f5c6cb; - } - - .list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus { - color: #721c24; - background-color: #f1b0b7; - } - - .list-group-item-danger.list-group-item-action.active { - color: #fff; - background-color: #721c24; - border-color: #721c24; - } - - .list-group-item-light { - color: #818182; - background-color: #fdfdfe; - } - - .list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus { - color: #818182; - background-color: #ececf6; - } - - .list-group-item-light.list-group-item-action.active { - color: #fff; - background-color: #818182; - border-color: #818182; - } - - .list-group-item-dark { - color: #1b1e21; - background-color: #c6c8ca; - } - - .list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus { - color: #1b1e21; - background-color: #b9bbbe; - } - - .list-group-item-dark.list-group-item-action.active { - color: #fff; - background-color: #1b1e21; - border-color: #1b1e21; - } - - .close { - float: right; - font-size: 1.5rem; - font-weight: 700; - line-height: 1; - color: #000; - text-shadow: 0 1px 0 #fff; - opacity: .5; - } - - .close:not(:disabled):not(.disabled) { - cursor: pointer; - } - - .close:not(:disabled):not(.disabled):hover, .close:not(:disabled):not(.disabled):focus { - color: #000; - text-decoration: none; - opacity: .75; - } - - button.close { - padding: 0; - background-color: transparent; - border: 0; - -webkit-appearance: none; - } - - .modal-open { - overflow: hidden; - } - - .modal-open .modal { - overflow-x: hidden; - overflow-y: auto; - } - - .modal { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1050; - display: none; - overflow: hidden; - outline: 0; - } - - .modal-dialog { - position: relative; - width: auto; - margin: 0.5rem; - pointer-events: none; - } - - .modal.fade .modal-dialog { - transition: -webkit-transform 0.3s ease-out; - transition: transform 0.3s ease-out; - transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out; - -webkit-transform: translate(0, -25%); - transform: translate(0, -25%); - } - - @media screen and (prefers-reduced-motion: reduce) { - .modal.fade .modal-dialog { - transition: none; - } - } - - .modal.show .modal-dialog { - -webkit-transform: translate(0, 0); - transform: translate(0, 0); - } - - .modal-dialog-centered { - display: -ms-flexbox; - display: flex; - -ms-flex-align: center; - align-items: center; - min-height: calc(100% - (0.5rem * 2)); - } - - .modal-dialog-centered::before { - display: block; - height: calc(100vh - (0.5rem * 2)); - content: ""; - } - - .modal-content { - position: relative; - display: -ms-flexbox; - display: flex; - -ms-flex-direction: column; - flex-direction: column; - width: 100%; - pointer-events: auto; - background-color: #fff; - background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, 0.2); - border-radius: 0.3rem; - outline: 0; - } - - .modal-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1040; - background-color: #000; - } - - .modal-backdrop.fade { - opacity: 0; - } - - .modal-backdrop.show { - opacity: 0.5; - } - - .modal-header { - display: -ms-flexbox; - display: flex; - -ms-flex-align: start; - align-items: flex-start; - -ms-flex-pack: justify; - justify-content: space-between; - padding: 1rem; - border-bottom: 1px solid #e9ecef; - border-top-left-radius: 0.3rem; - border-top-right-radius: 0.3rem; - } - - .modal-header .close { - padding: 1rem; - margin: -1rem -1rem -1rem auto; - } - - .modal-title { - margin-bottom: 0; - line-height: 1.5; - } - - .modal-body { - position: relative; - -ms-flex: 1 1 auto; - flex: 1 1 auto; - padding: 1rem; - } - - .modal-footer { - display: -ms-flexbox; - display: flex; - -ms-flex-align: center; - align-items: center; - -ms-flex-pack: end; - justify-content: flex-end; - padding: 1rem; - border-top: 1px solid #e9ecef; - } - - .modal-footer > :not(:first-child) { - margin-left: .25rem; - } - - .modal-footer > :not(:last-child) { - margin-right: .25rem; - } - - .modal-scrollbar-measure { - position: absolute; - top: -9999px; - width: 50px; - height: 50px; - overflow: scroll; - } - - @media (min-width: 576px) { - .modal-dialog { - max-width: 500px; - margin: 1.75rem auto; - } - .modal-dialog-centered { - min-height: calc(100% - (1.75rem * 2)); - } - .modal-dialog-centered::before { - height: calc(100vh - (1.75rem * 2)); - } - .modal-sm { - max-width: 300px; - } - } - - @media (min-width: 992px) { - .modal-lg { - max-width: 800px; - } - } - - .tooltip { - position: absolute; - z-index: 1070; - display: block; - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - font-style: normal; - font-weight: 400; - line-height: 1.5; - text-align: left; - text-align: start; - text-decoration: none; - text-shadow: none; - text-transform: none; - letter-spacing: normal; - word-break: normal; - word-spacing: normal; - white-space: normal; - line-break: auto; - font-size: 0.875rem; - word-wrap: break-word; - opacity: 0; - } - - .tooltip.show { - opacity: 0.9; - } - - .tooltip .arrow { - position: absolute; - display: block; - width: 0.8rem; - height: 0.4rem; - } - - .tooltip .arrow::before { - position: absolute; - content: ""; - border-color: transparent; - border-style: solid; - } - - .bs-tooltip-top, .bs-tooltip-auto[x-placement^="top"] { - padding: 0.4rem 0; - } - - .bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^="top"] .arrow { - bottom: 0; - } - - .bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^="top"] .arrow::before { - top: 0; - border-width: 0.4rem 0.4rem 0; - border-top-color: #000; - } - - .bs-tooltip-right, .bs-tooltip-auto[x-placement^="right"] { - padding: 0 0.4rem; - } - - .bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^="right"] .arrow { - left: 0; - width: 0.4rem; - height: 0.8rem; - } - - .bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^="right"] .arrow::before { - right: 0; - border-width: 0.4rem 0.4rem 0.4rem 0; - border-right-color: #000; - } - - .bs-tooltip-bottom, .bs-tooltip-auto[x-placement^="bottom"] { - padding: 0.4rem 0; - } - - .bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^="bottom"] .arrow { - top: 0; - } - - .bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^="bottom"] .arrow::before { - bottom: 0; - border-width: 0 0.4rem 0.4rem; - border-bottom-color: #000; - } - - .bs-tooltip-left, .bs-tooltip-auto[x-placement^="left"] { - padding: 0 0.4rem; - } - - .bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^="left"] .arrow { - right: 0; - width: 0.4rem; - height: 0.8rem; - } - - .bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^="left"] .arrow::before { - left: 0; - border-width: 0.4rem 0 0.4rem 0.4rem; - border-left-color: #000; - } - - .tooltip-inner { - max-width: 200px; - padding: 0.25rem 0.5rem; - color: #fff; - text-align: center; - background-color: #000; - border-radius: 0.25rem; - } - - .popover { - position: absolute; - top: 0; - left: 0; - z-index: 1060; - display: block; - max-width: 276px; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - font-style: normal; - font-weight: 400; - line-height: 1.5; - text-align: left; - text-align: start; - text-decoration: none; - text-shadow: none; - text-transform: none; - letter-spacing: normal; - word-break: normal; - word-spacing: normal; - white-space: normal; - line-break: auto; - font-size: 0.875rem; - word-wrap: break-word; - background-color: #fff; - background-clip: padding-box; - border: 1px solid rgba(0, 0, 0, 0.2); - border-radius: 0.3rem; - } - - .popover .arrow { - position: absolute; - display: block; - width: 1rem; - height: 0.5rem; - margin: 0 0.3rem; - } - - .popover .arrow::before, .popover .arrow::after { - position: absolute; - display: block; - content: ""; - border-color: transparent; - border-style: solid; - } - - .bs-popover-top, .bs-popover-auto[x-placement^="top"] { - margin-bottom: 0.5rem; - } - - .bs-popover-top .arrow, .bs-popover-auto[x-placement^="top"] .arrow { - bottom: calc((0.5rem + 1px) * -1); - } - - .bs-popover-top .arrow::before, .bs-popover-auto[x-placement^="top"] .arrow::before, - .bs-popover-top .arrow::after, - .bs-popover-auto[x-placement^="top"] .arrow::after { - border-width: 0.5rem 0.5rem 0; - } - - .bs-popover-top .arrow::before, .bs-popover-auto[x-placement^="top"] .arrow::before { - bottom: 0; - border-top-color: rgba(0, 0, 0, 0.25); - } - - - .bs-popover-top .arrow::after, - .bs-popover-auto[x-placement^="top"] .arrow::after { - bottom: 1px; - border-top-color: #fff; - } - - .bs-popover-right, .bs-popover-auto[x-placement^="right"] { - margin-left: 0.5rem; - } - - .bs-popover-right .arrow, .bs-popover-auto[x-placement^="right"] .arrow { - left: calc((0.5rem + 1px) * -1); - width: 0.5rem; - height: 1rem; - margin: 0.3rem 0; - } - - .bs-popover-right .arrow::before, .bs-popover-auto[x-placement^="right"] .arrow::before, - .bs-popover-right .arrow::after, - .bs-popover-auto[x-placement^="right"] .arrow::after { - border-width: 0.5rem 0.5rem 0.5rem 0; - } - - .bs-popover-right .arrow::before, .bs-popover-auto[x-placement^="right"] .arrow::before { - left: 0; - border-right-color: rgba(0, 0, 0, 0.25); - } - - - .bs-popover-right .arrow::after, - .bs-popover-auto[x-placement^="right"] .arrow::after { - left: 1px; - border-right-color: #fff; - } - - .bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"] { - margin-top: 0.5rem; - } - - .bs-popover-bottom .arrow, .bs-popover-auto[x-placement^="bottom"] .arrow { - top: calc((0.5rem + 1px) * -1); - } - - .bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^="bottom"] .arrow::before, - .bs-popover-bottom .arrow::after, - .bs-popover-auto[x-placement^="bottom"] .arrow::after { - border-width: 0 0.5rem 0.5rem 0.5rem; - } - - .bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^="bottom"] .arrow::before { - top: 0; - border-bottom-color: rgba(0, 0, 0, 0.25); - } - - - .bs-popover-bottom .arrow::after, - .bs-popover-auto[x-placement^="bottom"] .arrow::after { - top: 1px; - border-bottom-color: #fff; - } - - .bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^="bottom"] .popover-header::before { - position: absolute; - top: 0; - left: 50%; - display: block; - width: 1rem; - margin-left: -0.5rem; - content: ""; - border-bottom: 1px solid #f7f7f7; - } - - .bs-popover-left, .bs-popover-auto[x-placement^="left"] { - margin-right: 0.5rem; - } - - .bs-popover-left .arrow, .bs-popover-auto[x-placement^="left"] .arrow { - right: calc((0.5rem + 1px) * -1); - width: 0.5rem; - height: 1rem; - margin: 0.3rem 0; - } - - .bs-popover-left .arrow::before, .bs-popover-auto[x-placement^="left"] .arrow::before, - .bs-popover-left .arrow::after, - .bs-popover-auto[x-placement^="left"] .arrow::after { - border-width: 0.5rem 0 0.5rem 0.5rem; - } - - .bs-popover-left .arrow::before, .bs-popover-auto[x-placement^="left"] .arrow::before { - right: 0; - border-left-color: rgba(0, 0, 0, 0.25); - } - - - .bs-popover-left .arrow::after, - .bs-popover-auto[x-placement^="left"] .arrow::after { - right: 1px; - border-left-color: #fff; - } - - .popover-header { - padding: 0.5rem 0.75rem; - margin-bottom: 0; - font-size: 1rem; - color: inherit; - background-color: #f7f7f7; - border-bottom: 1px solid #ebebeb; - border-top-left-radius: calc(0.3rem - 1px); - border-top-right-radius: calc(0.3rem - 1px); - } - - .popover-header:empty { - display: none; - } - - .popover-body { - padding: 0.5rem 0.75rem; - color: #212529; - } - - .carousel { - position: relative; - } - - .carousel-inner { - position: relative; - width: 100%; - overflow: hidden; - } - - .carousel-item { - position: relative; - display: none; - -ms-flex-align: center; - align-items: center; - width: 100%; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - -webkit-perspective: 1000px; - perspective: 1000px; - } - - .carousel-item.active, - .carousel-item-next, - .carousel-item-prev { - display: block; - transition: -webkit-transform 0.6s ease; - transition: transform 0.6s ease; - transition: transform 0.6s ease, -webkit-transform 0.6s ease; - } - - @media screen and (prefers-reduced-motion: reduce) { - .carousel-item.active, - .carousel-item-next, - .carousel-item-prev { - transition: none; - } - } - - .carousel-item-next, - .carousel-item-prev { - position: absolute; - top: 0; - } - - .carousel-item-next.carousel-item-left, - .carousel-item-prev.carousel-item-right { - -webkit-transform: translateX(0); - transform: translateX(0); - } - - @supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) { - .carousel-item-next.carousel-item-left, - .carousel-item-prev.carousel-item-right { - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } - } - - .carousel-item-next, - .active.carousel-item-right { - -webkit-transform: translateX(100%); - transform: translateX(100%); - } - - @supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) { - .carousel-item-next, - .active.carousel-item-right { - -webkit-transform: translate3d(100%, 0, 0); - transform: translate3d(100%, 0, 0); - } - } - - .carousel-item-prev, - .active.carousel-item-left { - -webkit-transform: translateX(-100%); - transform: translateX(-100%); - } - - @supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) { - .carousel-item-prev, - .active.carousel-item-left { - -webkit-transform: translate3d(-100%, 0, 0); - transform: translate3d(-100%, 0, 0); - } - } - - .carousel-fade .carousel-item { - opacity: 0; - transition-duration: .6s; - transition-property: opacity; - } - - .carousel-fade .carousel-item.active, - .carousel-fade .carousel-item-next.carousel-item-left, - .carousel-fade .carousel-item-prev.carousel-item-right { - opacity: 1; - } - - .carousel-fade .active.carousel-item-left, - .carousel-fade .active.carousel-item-right { - opacity: 0; - } - - .carousel-fade .carousel-item-next, - .carousel-fade .carousel-item-prev, - .carousel-fade .carousel-item.active, - .carousel-fade .active.carousel-item-left, - .carousel-fade .active.carousel-item-prev { - -webkit-transform: translateX(0); - transform: translateX(0); - } - - @supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) { - .carousel-fade .carousel-item-next, - .carousel-fade .carousel-item-prev, - .carousel-fade .carousel-item.active, - .carousel-fade .active.carousel-item-left, - .carousel-fade .active.carousel-item-prev { - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } - } - - .carousel-control-prev, - .carousel-control-next { - position: absolute; - top: 0; - bottom: 0; - display: -ms-flexbox; - display: flex; - -ms-flex-align: center; - align-items: center; - -ms-flex-pack: center; - justify-content: center; - width: 15%; - color: #fff; - text-align: center; - opacity: 0.5; - } - - .carousel-control-prev:hover, .carousel-control-prev:focus, - .carousel-control-next:hover, - .carousel-control-next:focus { - color: #fff; - text-decoration: none; - outline: 0; - opacity: .9; - } - - .carousel-control-prev { - left: 0; - } - - .carousel-control-next { - right: 0; - } - - .carousel-control-prev-icon, - .carousel-control-next-icon { - display: inline-block; - width: 20px; - height: 20px; - background: transparent no-repeat center center; - background-size: 100% 100%; - } - - .carousel-control-prev-icon { - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"); - } - - .carousel-control-next-icon { - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"); - } - - .carousel-indicators { - position: absolute; - right: 0; - bottom: 10px; - left: 0; - z-index: 15; - display: -ms-flexbox; - display: flex; - -ms-flex-pack: center; - justify-content: center; - padding-left: 0; - margin-right: 15%; - margin-left: 15%; - list-style: none; - } - - .carousel-indicators li { - position: relative; - -ms-flex: 0 1 auto; - flex: 0 1 auto; - width: 30px; - height: 3px; - margin-right: 3px; - margin-left: 3px; - text-indent: -999px; - cursor: pointer; - background-color: rgba(255, 255, 255, 0.5); - } - - .carousel-indicators li::before { - position: absolute; - top: -10px; - left: 0; - display: inline-block; - width: 100%; - height: 10px; - content: ""; - } - - .carousel-indicators li::after { - position: absolute; - bottom: -10px; - left: 0; - display: inline-block; - width: 100%; - height: 10px; - content: ""; - } - - .carousel-indicators .active { - background-color: #fff; - } - - .carousel-caption { - position: absolute; - right: 15%; - bottom: 20px; - left: 15%; - z-index: 10; - padding-top: 20px; - padding-bottom: 20px; - color: #fff; - text-align: center; - } - - .align-baseline { - vertical-align: baseline !important; - } - - .align-top { - vertical-align: top !important; - } - - .align-middle { - vertical-align: middle !important; - } - - .align-bottom { - vertical-align: bottom !important; - } - - .align-text-bottom { - vertical-align: text-bottom !important; - } - - .align-text-top { - vertical-align: text-top !important; - } - - .bg-primary { - background-color: #007bff !important; - } - - a.bg-primary:hover, a.bg-primary:focus, - button.bg-primary:hover, - button.bg-primary:focus { - background-color: #0062cc !important; - } - - .bg-secondary { - background-color: #6c757d !important; - } - - a.bg-secondary:hover, a.bg-secondary:focus, - button.bg-secondary:hover, - button.bg-secondary:focus { - background-color: #545b62 !important; - } - - .bg-success { - background-color: #28a745 !important; - } - - a.bg-success:hover, a.bg-success:focus, - button.bg-success:hover, - button.bg-success:focus { - background-color: #1e7e34 !important; - } - - .bg-info { - background-color: #17a2b8 !important; - } - - a.bg-info:hover, a.bg-info:focus, - button.bg-info:hover, - button.bg-info:focus { - background-color: #117a8b !important; - } - - .bg-warning { - background-color: #ffc107 !important; - } - - a.bg-warning:hover, a.bg-warning:focus, - button.bg-warning:hover, - button.bg-warning:focus { - background-color: #d39e00 !important; - } - - .bg-danger { - background-color: #dc3545 !important; - } - - a.bg-danger:hover, a.bg-danger:focus, - button.bg-danger:hover, - button.bg-danger:focus { - background-color: #bd2130 !important; - } - - .bg-light { - background-color: #f8f9fa !important; - } - - a.bg-light:hover, a.bg-light:focus, - button.bg-light:hover, - button.bg-light:focus { - background-color: #dae0e5 !important; - } - - .bg-dark { - background-color: #343a40 !important; - } - - a.bg-dark:hover, a.bg-dark:focus, - button.bg-dark:hover, - button.bg-dark:focus { - background-color: #1d2124 !important; - } - - .bg-white { - background-color: #fff !important; - } - - .bg-transparent { - background-color: transparent !important; - } - - .border { - border: 1px solid #dee2e6 !important; - } - - .border-top { - border-top: 1px solid #dee2e6 !important; - } - - .border-right { - border-right: 1px solid #dee2e6 !important; - } - - .border-bottom { - border-bottom: 1px solid #dee2e6 !important; - } - - .border-left { - border-left: 1px solid #dee2e6 !important; - } - - .border-0 { - border: 0 !important; - } - - .border-top-0 { - border-top: 0 !important; - } - - .border-right-0 { - border-right: 0 !important; - } - - .border-bottom-0 { - border-bottom: 0 !important; - } - - .border-left-0 { - border-left: 0 !important; - } - - .border-primary { - border-color: #007bff !important; - } - - .border-secondary { - border-color: #6c757d !important; - } - - .border-success { - border-color: #28a745 !important; - } - - .border-info { - border-color: #17a2b8 !important; - } - - .border-warning { - border-color: #ffc107 !important; - } - - .border-danger { - border-color: #dc3545 !important; - } - - .border-light { - border-color: #f8f9fa !important; - } - - .border-dark { - border-color: #343a40 !important; - } - - .border-white { - border-color: #fff !important; - } - - .rounded { - border-radius: 0.25rem !important; - } - - .rounded-top { - border-top-left-radius: 0.25rem !important; - border-top-right-radius: 0.25rem !important; - } - - .rounded-right { - border-top-right-radius: 0.25rem !important; - border-bottom-right-radius: 0.25rem !important; - } - - .rounded-bottom { - border-bottom-right-radius: 0.25rem !important; - border-bottom-left-radius: 0.25rem !important; - } - - .rounded-left { - border-top-left-radius: 0.25rem !important; - border-bottom-left-radius: 0.25rem !important; - } - - .rounded-circle { - border-radius: 50% !important; - } - - .rounded-0 { - border-radius: 0 !important; - } - - .clearfix::after { - display: block; - clear: both; - content: ""; - } - - .d-none { - display: none !important; - } - - .d-inline { - display: inline !important; - } - - .d-inline-block { - display: inline-block !important; - } - - .d-block { - display: block !important; - } - - .d-table { - display: table !important; - } - - .d-table-row { - display: table-row !important; - } - - .d-table-cell { - display: table-cell !important; - } - - .d-flex { - display: -ms-flexbox !important; - display: flex !important; - } - - .d-inline-flex { - display: -ms-inline-flexbox !important; - display: inline-flex !important; - } - - @media (min-width: 576px) { - .d-sm-none { - display: none !important; - } - .d-sm-inline { - display: inline !important; - } - .d-sm-inline-block { - display: inline-block !important; - } - .d-sm-block { - display: block !important; - } - .d-sm-table { - display: table !important; - } - .d-sm-table-row { - display: table-row !important; - } - .d-sm-table-cell { - display: table-cell !important; - } - .d-sm-flex { - display: -ms-flexbox !important; - display: flex !important; - } - .d-sm-inline-flex { - display: -ms-inline-flexbox !important; - display: inline-flex !important; - } - } - - @media (min-width: 768px) { - .d-md-none { - display: none !important; - } - .d-md-inline { - display: inline !important; - } - .d-md-inline-block { - display: inline-block !important; - } - .d-md-block { - display: block !important; - } - .d-md-table { - display: table !important; - } - .d-md-table-row { - display: table-row !important; - } - .d-md-table-cell { - display: table-cell !important; - } - .d-md-flex { - display: -ms-flexbox !important; - display: flex !important; - } - .d-md-inline-flex { - display: -ms-inline-flexbox !important; - display: inline-flex !important; - } - } - - @media (min-width: 992px) { - .d-lg-none { - display: none !important; - } - .d-lg-inline { - display: inline !important; - } - .d-lg-inline-block { - display: inline-block !important; - } - .d-lg-block { - display: block !important; - } - .d-lg-table { - display: table !important; - } - .d-lg-table-row { - display: table-row !important; - } - .d-lg-table-cell { - display: table-cell !important; - } - .d-lg-flex { - display: -ms-flexbox !important; - display: flex !important; - } - .d-lg-inline-flex { - display: -ms-inline-flexbox !important; - display: inline-flex !important; - } - } - - @media (min-width: 1200px) { - .d-xl-none { - display: none !important; - } - .d-xl-inline { - display: inline !important; - } - .d-xl-inline-block { - display: inline-block !important; - } - .d-xl-block { - display: block !important; - } - .d-xl-table { - display: table !important; - } - .d-xl-table-row { - display: table-row !important; - } - .d-xl-table-cell { - display: table-cell !important; - } - .d-xl-flex { - display: -ms-flexbox !important; - display: flex !important; - } - .d-xl-inline-flex { - display: -ms-inline-flexbox !important; - display: inline-flex !important; - } - } - - @media print { - .d-print-none { - display: none !important; - } - .d-print-inline { - display: inline !important; - } - .d-print-inline-block { - display: inline-block !important; - } - .d-print-block { - display: block !important; - } - .d-print-table { - display: table !important; - } - .d-print-table-row { - display: table-row !important; - } - .d-print-table-cell { - display: table-cell !important; - } - .d-print-flex { - display: -ms-flexbox !important; - display: flex !important; - } - .d-print-inline-flex { - display: -ms-inline-flexbox !important; - display: inline-flex !important; - } - } - - .embed-responsive { - position: relative; - display: block; - width: 100%; - padding: 0; - overflow: hidden; - } - - .embed-responsive::before { - display: block; - content: ""; - } - - .embed-responsive .embed-responsive-item, - .embed-responsive iframe, - .embed-responsive embed, - .embed-responsive object, - .embed-responsive video { - position: absolute; - top: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - border: 0; - } - - .embed-responsive-21by9::before { - padding-top: 42.857143%; - } - - .embed-responsive-16by9::before { - padding-top: 56.25%; - } - - .embed-responsive-4by3::before { - padding-top: 75%; - } - - .embed-responsive-1by1::before { - padding-top: 100%; - } - - .flex-row { - -ms-flex-direction: row !important; - flex-direction: row !important; - } - - .flex-column { - -ms-flex-direction: column !important; - flex-direction: column !important; - } - - .flex-row-reverse { - -ms-flex-direction: row-reverse !important; - flex-direction: row-reverse !important; - } - - .flex-column-reverse { - -ms-flex-direction: column-reverse !important; - flex-direction: column-reverse !important; - } - - .flex-wrap { - -ms-flex-wrap: wrap !important; - flex-wrap: wrap !important; - } - - .flex-nowrap { - -ms-flex-wrap: nowrap !important; - flex-wrap: nowrap !important; - } - - .flex-wrap-reverse { - -ms-flex-wrap: wrap-reverse !important; - flex-wrap: wrap-reverse !important; - } - - .flex-fill { - -ms-flex: 1 1 auto !important; - flex: 1 1 auto !important; - } - - .flex-grow-0 { - -ms-flex-positive: 0 !important; - flex-grow: 0 !important; - } - - .flex-grow-1 { - -ms-flex-positive: 1 !important; - flex-grow: 1 !important; - } - - .flex-shrink-0 { - -ms-flex-negative: 0 !important; - flex-shrink: 0 !important; - } - - .flex-shrink-1 { - -ms-flex-negative: 1 !important; - flex-shrink: 1 !important; - } - - .justify-content-start { - -ms-flex-pack: start !important; - justify-content: flex-start !important; - } - - .justify-content-end { - -ms-flex-pack: end !important; - justify-content: flex-end !important; - } - - .justify-content-center { - -ms-flex-pack: center !important; - justify-content: center !important; - } - - .justify-content-between { - -ms-flex-pack: justify !important; - justify-content: space-between !important; - } - - .justify-content-around { - -ms-flex-pack: distribute !important; - justify-content: space-around !important; - } - - .align-items-start { - -ms-flex-align: start !important; - align-items: flex-start !important; - } - - .align-items-end { - -ms-flex-align: end !important; - align-items: flex-end !important; - } - - .align-items-center { - -ms-flex-align: center !important; - align-items: center !important; - } - - .align-items-baseline { - -ms-flex-align: baseline !important; - align-items: baseline !important; - } - - .align-items-stretch { - -ms-flex-align: stretch !important; - align-items: stretch !important; - } - - .align-content-start { - -ms-flex-line-pack: start !important; - align-content: flex-start !important; - } - - .align-content-end { - -ms-flex-line-pack: end !important; - align-content: flex-end !important; - } - - .align-content-center { - -ms-flex-line-pack: center !important; - align-content: center !important; - } - - .align-content-between { - -ms-flex-line-pack: justify !important; - align-content: space-between !important; - } - - .align-content-around { - -ms-flex-line-pack: distribute !important; - align-content: space-around !important; - } - - .align-content-stretch { - -ms-flex-line-pack: stretch !important; - align-content: stretch !important; - } - - .align-self-auto { - -ms-flex-item-align: auto !important; - align-self: auto !important; - } - - .align-self-start { - -ms-flex-item-align: start !important; - align-self: flex-start !important; - } - - .align-self-end { - -ms-flex-item-align: end !important; - align-self: flex-end !important; - } - - .align-self-center { - -ms-flex-item-align: center !important; - align-self: center !important; - } - - .align-self-baseline { - -ms-flex-item-align: baseline !important; - align-self: baseline !important; - } - - .align-self-stretch { - -ms-flex-item-align: stretch !important; - align-self: stretch !important; - } - - @media (min-width: 576px) { - .flex-sm-row { - -ms-flex-direction: row !important; - flex-direction: row !important; - } - .flex-sm-column { - -ms-flex-direction: column !important; - flex-direction: column !important; - } - .flex-sm-row-reverse { - -ms-flex-direction: row-reverse !important; - flex-direction: row-reverse !important; - } - .flex-sm-column-reverse { - -ms-flex-direction: column-reverse !important; - flex-direction: column-reverse !important; - } - .flex-sm-wrap { - -ms-flex-wrap: wrap !important; - flex-wrap: wrap !important; - } - .flex-sm-nowrap { - -ms-flex-wrap: nowrap !important; - flex-wrap: nowrap !important; - } - .flex-sm-wrap-reverse { - -ms-flex-wrap: wrap-reverse !important; - flex-wrap: wrap-reverse !important; - } - .flex-sm-fill { - -ms-flex: 1 1 auto !important; - flex: 1 1 auto !important; - } - .flex-sm-grow-0 { - -ms-flex-positive: 0 !important; - flex-grow: 0 !important; - } - .flex-sm-grow-1 { - -ms-flex-positive: 1 !important; - flex-grow: 1 !important; - } - .flex-sm-shrink-0 { - -ms-flex-negative: 0 !important; - flex-shrink: 0 !important; - } - .flex-sm-shrink-1 { - -ms-flex-negative: 1 !important; - flex-shrink: 1 !important; - } - .justify-content-sm-start { - -ms-flex-pack: start !important; - justify-content: flex-start !important; - } - .justify-content-sm-end { - -ms-flex-pack: end !important; - justify-content: flex-end !important; - } - .justify-content-sm-center { - -ms-flex-pack: center !important; - justify-content: center !important; - } - .justify-content-sm-between { - -ms-flex-pack: justify !important; - justify-content: space-between !important; - } - .justify-content-sm-around { - -ms-flex-pack: distribute !important; - justify-content: space-around !important; - } - .align-items-sm-start { - -ms-flex-align: start !important; - align-items: flex-start !important; - } - .align-items-sm-end { - -ms-flex-align: end !important; - align-items: flex-end !important; - } - .align-items-sm-center { - -ms-flex-align: center !important; - align-items: center !important; - } - .align-items-sm-baseline { - -ms-flex-align: baseline !important; - align-items: baseline !important; - } - .align-items-sm-stretch { - -ms-flex-align: stretch !important; - align-items: stretch !important; - } - .align-content-sm-start { - -ms-flex-line-pack: start !important; - align-content: flex-start !important; - } - .align-content-sm-end { - -ms-flex-line-pack: end !important; - align-content: flex-end !important; - } - .align-content-sm-center { - -ms-flex-line-pack: center !important; - align-content: center !important; - } - .align-content-sm-between { - -ms-flex-line-pack: justify !important; - align-content: space-between !important; - } - .align-content-sm-around { - -ms-flex-line-pack: distribute !important; - align-content: space-around !important; - } - .align-content-sm-stretch { - -ms-flex-line-pack: stretch !important; - align-content: stretch !important; - } - .align-self-sm-auto { - -ms-flex-item-align: auto !important; - align-self: auto !important; - } - .align-self-sm-start { - -ms-flex-item-align: start !important; - align-self: flex-start !important; - } - .align-self-sm-end { - -ms-flex-item-align: end !important; - align-self: flex-end !important; - } - .align-self-sm-center { - -ms-flex-item-align: center !important; - align-self: center !important; - } - .align-self-sm-baseline { - -ms-flex-item-align: baseline !important; - align-self: baseline !important; - } - .align-self-sm-stretch { - -ms-flex-item-align: stretch !important; - align-self: stretch !important; - } - } - - @media (min-width: 768px) { - .flex-md-row { - -ms-flex-direction: row !important; - flex-direction: row !important; - } - .flex-md-column { - -ms-flex-direction: column !important; - flex-direction: column !important; - } - .flex-md-row-reverse { - -ms-flex-direction: row-reverse !important; - flex-direction: row-reverse !important; - } - .flex-md-column-reverse { - -ms-flex-direction: column-reverse !important; - flex-direction: column-reverse !important; - } - .flex-md-wrap { - -ms-flex-wrap: wrap !important; - flex-wrap: wrap !important; - } - .flex-md-nowrap { - -ms-flex-wrap: nowrap !important; - flex-wrap: nowrap !important; - } - .flex-md-wrap-reverse { - -ms-flex-wrap: wrap-reverse !important; - flex-wrap: wrap-reverse !important; - } - .flex-md-fill { - -ms-flex: 1 1 auto !important; - flex: 1 1 auto !important; - } - .flex-md-grow-0 { - -ms-flex-positive: 0 !important; - flex-grow: 0 !important; - } - .flex-md-grow-1 { - -ms-flex-positive: 1 !important; - flex-grow: 1 !important; - } - .flex-md-shrink-0 { - -ms-flex-negative: 0 !important; - flex-shrink: 0 !important; - } - .flex-md-shrink-1 { - -ms-flex-negative: 1 !important; - flex-shrink: 1 !important; - } - .justify-content-md-start { - -ms-flex-pack: start !important; - justify-content: flex-start !important; - } - .justify-content-md-end { - -ms-flex-pack: end !important; - justify-content: flex-end !important; - } - .justify-content-md-center { - -ms-flex-pack: center !important; - justify-content: center !important; - } - .justify-content-md-between { - -ms-flex-pack: justify !important; - justify-content: space-between !important; - } - .justify-content-md-around { - -ms-flex-pack: distribute !important; - justify-content: space-around !important; - } - .align-items-md-start { - -ms-flex-align: start !important; - align-items: flex-start !important; - } - .align-items-md-end { - -ms-flex-align: end !important; - align-items: flex-end !important; - } - .align-items-md-center { - -ms-flex-align: center !important; - align-items: center !important; - } - .align-items-md-baseline { - -ms-flex-align: baseline !important; - align-items: baseline !important; - } - .align-items-md-stretch { - -ms-flex-align: stretch !important; - align-items: stretch !important; - } - .align-content-md-start { - -ms-flex-line-pack: start !important; - align-content: flex-start !important; - } - .align-content-md-end { - -ms-flex-line-pack: end !important; - align-content: flex-end !important; - } - .align-content-md-center { - -ms-flex-line-pack: center !important; - align-content: center !important; - } - .align-content-md-between { - -ms-flex-line-pack: justify !important; - align-content: space-between !important; - } - .align-content-md-around { - -ms-flex-line-pack: distribute !important; - align-content: space-around !important; - } - .align-content-md-stretch { - -ms-flex-line-pack: stretch !important; - align-content: stretch !important; - } - .align-self-md-auto { - -ms-flex-item-align: auto !important; - align-self: auto !important; - } - .align-self-md-start { - -ms-flex-item-align: start !important; - align-self: flex-start !important; - } - .align-self-md-end { - -ms-flex-item-align: end !important; - align-self: flex-end !important; - } - .align-self-md-center { - -ms-flex-item-align: center !important; - align-self: center !important; - } - .align-self-md-baseline { - -ms-flex-item-align: baseline !important; - align-self: baseline !important; - } - .align-self-md-stretch { - -ms-flex-item-align: stretch !important; - align-self: stretch !important; - } - } - - @media (min-width: 992px) { - .flex-lg-row { - -ms-flex-direction: row !important; - flex-direction: row !important; - } - .flex-lg-column { - -ms-flex-direction: column !important; - flex-direction: column !important; - } - .flex-lg-row-reverse { - -ms-flex-direction: row-reverse !important; - flex-direction: row-reverse !important; - } - .flex-lg-column-reverse { - -ms-flex-direction: column-reverse !important; - flex-direction: column-reverse !important; - } - .flex-lg-wrap { - -ms-flex-wrap: wrap !important; - flex-wrap: wrap !important; - } - .flex-lg-nowrap { - -ms-flex-wrap: nowrap !important; - flex-wrap: nowrap !important; - } - .flex-lg-wrap-reverse { - -ms-flex-wrap: wrap-reverse !important; - flex-wrap: wrap-reverse !important; - } - .flex-lg-fill { - -ms-flex: 1 1 auto !important; - flex: 1 1 auto !important; - } - .flex-lg-grow-0 { - -ms-flex-positive: 0 !important; - flex-grow: 0 !important; - } - .flex-lg-grow-1 { - -ms-flex-positive: 1 !important; - flex-grow: 1 !important; - } - .flex-lg-shrink-0 { - -ms-flex-negative: 0 !important; - flex-shrink: 0 !important; - } - .flex-lg-shrink-1 { - -ms-flex-negative: 1 !important; - flex-shrink: 1 !important; - } - .justify-content-lg-start { - -ms-flex-pack: start !important; - justify-content: flex-start !important; - } - .justify-content-lg-end { - -ms-flex-pack: end !important; - justify-content: flex-end !important; - } - .justify-content-lg-center { - -ms-flex-pack: center !important; - justify-content: center !important; - } - .justify-content-lg-between { - -ms-flex-pack: justify !important; - justify-content: space-between !important; - } - .justify-content-lg-around { - -ms-flex-pack: distribute !important; - justify-content: space-around !important; - } - .align-items-lg-start { - -ms-flex-align: start !important; - align-items: flex-start !important; - } - .align-items-lg-end { - -ms-flex-align: end !important; - align-items: flex-end !important; - } - .align-items-lg-center { - -ms-flex-align: center !important; - align-items: center !important; - } - .align-items-lg-baseline { - -ms-flex-align: baseline !important; - align-items: baseline !important; - } - .align-items-lg-stretch { - -ms-flex-align: stretch !important; - align-items: stretch !important; - } - .align-content-lg-start { - -ms-flex-line-pack: start !important; - align-content: flex-start !important; - } - .align-content-lg-end { - -ms-flex-line-pack: end !important; - align-content: flex-end !important; - } - .align-content-lg-center { - -ms-flex-line-pack: center !important; - align-content: center !important; - } - .align-content-lg-between { - -ms-flex-line-pack: justify !important; - align-content: space-between !important; - } - .align-content-lg-around { - -ms-flex-line-pack: distribute !important; - align-content: space-around !important; - } - .align-content-lg-stretch { - -ms-flex-line-pack: stretch !important; - align-content: stretch !important; - } - .align-self-lg-auto { - -ms-flex-item-align: auto !important; - align-self: auto !important; - } - .align-self-lg-start { - -ms-flex-item-align: start !important; - align-self: flex-start !important; - } - .align-self-lg-end { - -ms-flex-item-align: end !important; - align-self: flex-end !important; - } - .align-self-lg-center { - -ms-flex-item-align: center !important; - align-self: center !important; - } - .align-self-lg-baseline { - -ms-flex-item-align: baseline !important; - align-self: baseline !important; - } - .align-self-lg-stretch { - -ms-flex-item-align: stretch !important; - align-self: stretch !important; - } - } - - @media (min-width: 1200px) { - .flex-xl-row { - -ms-flex-direction: row !important; - flex-direction: row !important; - } - .flex-xl-column { - -ms-flex-direction: column !important; - flex-direction: column !important; - } - .flex-xl-row-reverse { - -ms-flex-direction: row-reverse !important; - flex-direction: row-reverse !important; - } - .flex-xl-column-reverse { - -ms-flex-direction: column-reverse !important; - flex-direction: column-reverse !important; - } - .flex-xl-wrap { - -ms-flex-wrap: wrap !important; - flex-wrap: wrap !important; - } - .flex-xl-nowrap { - -ms-flex-wrap: nowrap !important; - flex-wrap: nowrap !important; - } - .flex-xl-wrap-reverse { - -ms-flex-wrap: wrap-reverse !important; - flex-wrap: wrap-reverse !important; - } - .flex-xl-fill { - -ms-flex: 1 1 auto !important; - flex: 1 1 auto !important; - } - .flex-xl-grow-0 { - -ms-flex-positive: 0 !important; - flex-grow: 0 !important; - } - .flex-xl-grow-1 { - -ms-flex-positive: 1 !important; - flex-grow: 1 !important; - } - .flex-xl-shrink-0 { - -ms-flex-negative: 0 !important; - flex-shrink: 0 !important; - } - .flex-xl-shrink-1 { - -ms-flex-negative: 1 !important; - flex-shrink: 1 !important; - } - .justify-content-xl-start { - -ms-flex-pack: start !important; - justify-content: flex-start !important; - } - .justify-content-xl-end { - -ms-flex-pack: end !important; - justify-content: flex-end !important; - } - .justify-content-xl-center { - -ms-flex-pack: center !important; - justify-content: center !important; - } - .justify-content-xl-between { - -ms-flex-pack: justify !important; - justify-content: space-between !important; - } - .justify-content-xl-around { - -ms-flex-pack: distribute !important; - justify-content: space-around !important; - } - .align-items-xl-start { - -ms-flex-align: start !important; - align-items: flex-start !important; - } - .align-items-xl-end { - -ms-flex-align: end !important; - align-items: flex-end !important; - } - .align-items-xl-center { - -ms-flex-align: center !important; - align-items: center !important; - } - .align-items-xl-baseline { - -ms-flex-align: baseline !important; - align-items: baseline !important; - } - .align-items-xl-stretch { - -ms-flex-align: stretch !important; - align-items: stretch !important; - } - .align-content-xl-start { - -ms-flex-line-pack: start !important; - align-content: flex-start !important; - } - .align-content-xl-end { - -ms-flex-line-pack: end !important; - align-content: flex-end !important; - } - .align-content-xl-center { - -ms-flex-line-pack: center !important; - align-content: center !important; - } - .align-content-xl-between { - -ms-flex-line-pack: justify !important; - align-content: space-between !important; - } - .align-content-xl-around { - -ms-flex-line-pack: distribute !important; - align-content: space-around !important; - } - .align-content-xl-stretch { - -ms-flex-line-pack: stretch !important; - align-content: stretch !important; - } - .align-self-xl-auto { - -ms-flex-item-align: auto !important; - align-self: auto !important; - } - .align-self-xl-start { - -ms-flex-item-align: start !important; - align-self: flex-start !important; - } - .align-self-xl-end { - -ms-flex-item-align: end !important; - align-self: flex-end !important; - } - .align-self-xl-center { - -ms-flex-item-align: center !important; - align-self: center !important; - } - .align-self-xl-baseline { - -ms-flex-item-align: baseline !important; - align-self: baseline !important; - } - .align-self-xl-stretch { - -ms-flex-item-align: stretch !important; - align-self: stretch !important; - } - } - - .float-left { - float: left !important; - } - - .float-right { - float: right !important; - } - - .float-none { - float: none !important; - } - - @media (min-width: 576px) { - .float-sm-left { - float: left !important; - } - .float-sm-right { - float: right !important; - } - .float-sm-none { - float: none !important; - } - } - - @media (min-width: 768px) { - .float-md-left { - float: left !important; - } - .float-md-right { - float: right !important; - } - .float-md-none { - float: none !important; - } - } - - @media (min-width: 992px) { - .float-lg-left { - float: left !important; - } - .float-lg-right { - float: right !important; - } - .float-lg-none { - float: none !important; - } - } - - @media (min-width: 1200px) { - .float-xl-left { - float: left !important; - } - .float-xl-right { - float: right !important; - } - .float-xl-none { - float: none !important; - } - } - - .position-static { - position: static !important; - } - - .position-relative { - position: relative !important; - } - - .position-absolute { - position: absolute !important; - } - - .position-fixed { - position: fixed !important; - } - - .position-sticky { - position: -webkit-sticky !important; - position: sticky !important; - } - - .fixed-top { - position: fixed; - top: 0; - right: 0; - left: 0; - z-index: 1030; - } - - .fixed-bottom { - position: fixed; - right: 0; - bottom: 0; - left: 0; - z-index: 1030; - } - - @supports ((position: -webkit-sticky) or (position: sticky)) { - .sticky-top { - position: -webkit-sticky; - position: sticky; - top: 0; - z-index: 1020; - } - } - - .sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; - } - - .sr-only-focusable:active, .sr-only-focusable:focus { - position: static; - width: auto; - height: auto; - overflow: visible; - clip: auto; - white-space: normal; - } - - .shadow-sm { - box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; - } - - .shadow { - box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; - } - - .shadow-lg { - box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important; - } - - .shadow-none { - box-shadow: none !important; - } - - .w-25 { - width: 25% !important; - } - - .w-50 { - width: 50% !important; - } - - .w-75 { - width: 75% !important; - } - - .w-100 { - width: 100% !important; - } - - .w-auto { - width: auto !important; - } - - .h-25 { - height: 25% !important; - } - - .h-50 { - height: 50% !important; - } - - .h-75 { - height: 75% !important; - } - - .h-100 { - height: 100% !important; - } - - .h-auto { - height: auto !important; - } - - .mw-100 { - max-width: 100% !important; - } - - .mh-100 { - max-height: 100% !important; - } - - .m-0 { - margin: 0 !important; - } - - .mt-0, - .my-0 { - margin-top: 0 !important; - } - - .mr-0, - .mx-0 { - margin-right: 0 !important; - } - - .mb-0, - .my-0 { - margin-bottom: 0 !important; - } - - .ml-0, - .mx-0 { - margin-left: 0 !important; - } - - .m-1 { - margin: 0.25rem !important; - } - - .mt-1, - .my-1 { - margin-top: 0.25rem !important; - } - - .mr-1, - .mx-1 { - margin-right: 0.25rem !important; - } - - .mb-1, - .my-1 { - margin-bottom: 0.25rem !important; - } - - .ml-1, - .mx-1 { - margin-left: 0.25rem !important; - } - - .m-2 { - margin: 0.5rem !important; - } - - .mt-2, - .my-2 { - margin-top: 0.5rem !important; - } - - .mr-2, - .mx-2 { - margin-right: 0.5rem !important; - } - - .mb-2, - .my-2 { - margin-bottom: 0.5rem !important; - } - - .ml-2, - .mx-2 { - margin-left: 0.5rem !important; - } - - .m-3 { - margin: 1rem !important; - } - - .mt-3, - .my-3 { - margin-top: 1rem !important; - } - - .mr-3, - .mx-3 { - margin-right: 1rem !important; - } - - .mb-3, - .my-3 { - margin-bottom: 1rem !important; - } - - .ml-3, - .mx-3 { - margin-left: 1rem !important; - } - - .m-4 { - margin: 1.5rem !important; - } - - .mt-4, - .my-4 { - margin-top: 1.5rem !important; - } - - .mr-4, - .mx-4 { - margin-right: 1.5rem !important; - } - - .mb-4, - .my-4 { - margin-bottom: 1.5rem !important; - } - - .ml-4, - .mx-4 { - margin-left: 1.5rem !important; - } - - .m-5 { - margin: 3rem !important; - } - - .mt-5, - .my-5 { - margin-top: 3rem !important; - } - - .mr-5, - .mx-5 { - margin-right: 3rem !important; - } - - .mb-5, - .my-5 { - margin-bottom: 3rem !important; - } - - .ml-5, - .mx-5 { - margin-left: 3rem !important; - } - - .p-0 { - padding: 0 !important; - } - - .pt-0, - .py-0 { - padding-top: 0 !important; - } - - .pr-0, - .px-0 { - padding-right: 0 !important; - } - - .pb-0, - .py-0 { - padding-bottom: 0 !important; - } - - .pl-0, - .px-0 { - padding-left: 0 !important; - } - - .p-1 { - padding: 0.25rem !important; - } - - .pt-1, - .py-1 { - padding-top: 0.25rem !important; - } - - .pr-1, - .px-1 { - padding-right: 0.25rem !important; - } - - .pb-1, - .py-1 { - padding-bottom: 0.25rem !important; - } - - .pl-1, - .px-1 { - padding-left: 0.25rem !important; - } - - .p-2 { - padding: 0.5rem !important; - } - - .pt-2, - .py-2 { - padding-top: 0.5rem !important; - } - - .pr-2, - .px-2 { - padding-right: 0.5rem !important; - } - - .pb-2, - .py-2 { - padding-bottom: 0.5rem !important; - } - - .pl-2, - .px-2 { - padding-left: 0.5rem !important; - } - - .p-3 { - padding: 1rem !important; - } - - .pt-3, - .py-3 { - padding-top: 1rem !important; - } - - .pr-3, - .px-3 { - padding-right: 1rem !important; - } - - .pb-3, - .py-3 { - padding-bottom: 1rem !important; - } - - .pl-3, - .px-3 { - padding-left: 1rem !important; - } - - .p-4 { - padding: 1.5rem !important; - } - - .pt-4, - .py-4 { - padding-top: 1.5rem !important; - } - - .pr-4, - .px-4 { - padding-right: 1.5rem !important; - } - - .pb-4, - .py-4 { - padding-bottom: 1.5rem !important; - } - - .pl-4, - .px-4 { - padding-left: 1.5rem !important; - } - - .p-5 { - padding: 3rem !important; - } - - .pt-5, - .py-5 { - padding-top: 3rem !important; - } - - .pr-5, - .px-5 { - padding-right: 3rem !important; - } - - .pb-5, - .py-5 { - padding-bottom: 3rem !important; - } - - .pl-5, - .px-5 { - padding-left: 3rem !important; - } - - .m-auto { - margin: auto !important; - } - - .mt-auto, - .my-auto { - margin-top: auto !important; - } - - .mr-auto, - .mx-auto { - margin-right: auto !important; - } - - .mb-auto, - .my-auto { - margin-bottom: auto !important; - } - - .ml-auto, - .mx-auto { - margin-left: auto !important; - } - - @media (min-width: 576px) { - .m-sm-0 { - margin: 0 !important; - } - .mt-sm-0, - .my-sm-0 { - margin-top: 0 !important; - } - .mr-sm-0, - .mx-sm-0 { - margin-right: 0 !important; - } - .mb-sm-0, - .my-sm-0 { - margin-bottom: 0 !important; - } - .ml-sm-0, - .mx-sm-0 { - margin-left: 0 !important; - } - .m-sm-1 { - margin: 0.25rem !important; - } - .mt-sm-1, - .my-sm-1 { - margin-top: 0.25rem !important; - } - .mr-sm-1, - .mx-sm-1 { - margin-right: 0.25rem !important; - } - .mb-sm-1, - .my-sm-1 { - margin-bottom: 0.25rem !important; - } - .ml-sm-1, - .mx-sm-1 { - margin-left: 0.25rem !important; - } - .m-sm-2 { - margin: 0.5rem !important; - } - .mt-sm-2, - .my-sm-2 { - margin-top: 0.5rem !important; - } - .mr-sm-2, - .mx-sm-2 { - margin-right: 0.5rem !important; - } - .mb-sm-2, - .my-sm-2 { - margin-bottom: 0.5rem !important; - } - .ml-sm-2, - .mx-sm-2 { - margin-left: 0.5rem !important; - } - .m-sm-3 { - margin: 1rem !important; - } - .mt-sm-3, - .my-sm-3 { - margin-top: 1rem !important; - } - .mr-sm-3, - .mx-sm-3 { - margin-right: 1rem !important; - } - .mb-sm-3, - .my-sm-3 { - margin-bottom: 1rem !important; - } - .ml-sm-3, - .mx-sm-3 { - margin-left: 1rem !important; - } - .m-sm-4 { - margin: 1.5rem !important; - } - .mt-sm-4, - .my-sm-4 { - margin-top: 1.5rem !important; - } - .mr-sm-4, - .mx-sm-4 { - margin-right: 1.5rem !important; - } - .mb-sm-4, - .my-sm-4 { - margin-bottom: 1.5rem !important; - } - .ml-sm-4, - .mx-sm-4 { - margin-left: 1.5rem !important; - } - .m-sm-5 { - margin: 3rem !important; - } - .mt-sm-5, - .my-sm-5 { - margin-top: 3rem !important; - } - .mr-sm-5, - .mx-sm-5 { - margin-right: 3rem !important; - } - .mb-sm-5, - .my-sm-5 { - margin-bottom: 3rem !important; - } - .ml-sm-5, - .mx-sm-5 { - margin-left: 3rem !important; - } - .p-sm-0 { - padding: 0 !important; - } - .pt-sm-0, - .py-sm-0 { - padding-top: 0 !important; - } - .pr-sm-0, - .px-sm-0 { - padding-right: 0 !important; - } - .pb-sm-0, - .py-sm-0 { - padding-bottom: 0 !important; - } - .pl-sm-0, - .px-sm-0 { - padding-left: 0 !important; - } - .p-sm-1 { - padding: 0.25rem !important; - } - .pt-sm-1, - .py-sm-1 { - padding-top: 0.25rem !important; - } - .pr-sm-1, - .px-sm-1 { - padding-right: 0.25rem !important; - } - .pb-sm-1, - .py-sm-1 { - padding-bottom: 0.25rem !important; - } - .pl-sm-1, - .px-sm-1 { - padding-left: 0.25rem !important; - } - .p-sm-2 { - padding: 0.5rem !important; - } - .pt-sm-2, - .py-sm-2 { - padding-top: 0.5rem !important; - } - .pr-sm-2, - .px-sm-2 { - padding-right: 0.5rem !important; - } - .pb-sm-2, - .py-sm-2 { - padding-bottom: 0.5rem !important; - } - .pl-sm-2, - .px-sm-2 { - padding-left: 0.5rem !important; - } - .p-sm-3 { - padding: 1rem !important; - } - .pt-sm-3, - .py-sm-3 { - padding-top: 1rem !important; - } - .pr-sm-3, - .px-sm-3 { - padding-right: 1rem !important; - } - .pb-sm-3, - .py-sm-3 { - padding-bottom: 1rem !important; - } - .pl-sm-3, - .px-sm-3 { - padding-left: 1rem !important; - } - .p-sm-4 { - padding: 1.5rem !important; - } - .pt-sm-4, - .py-sm-4 { - padding-top: 1.5rem !important; - } - .pr-sm-4, - .px-sm-4 { - padding-right: 1.5rem !important; - } - .pb-sm-4, - .py-sm-4 { - padding-bottom: 1.5rem !important; - } - .pl-sm-4, - .px-sm-4 { - padding-left: 1.5rem !important; - } - .p-sm-5 { - padding: 3rem !important; - } - .pt-sm-5, - .py-sm-5 { - padding-top: 3rem !important; - } - .pr-sm-5, - .px-sm-5 { - padding-right: 3rem !important; - } - .pb-sm-5, - .py-sm-5 { - padding-bottom: 3rem !important; - } - .pl-sm-5, - .px-sm-5 { - padding-left: 3rem !important; - } - .m-sm-auto { - margin: auto !important; - } - .mt-sm-auto, - .my-sm-auto { - margin-top: auto !important; - } - .mr-sm-auto, - .mx-sm-auto { - margin-right: auto !important; - } - .mb-sm-auto, - .my-sm-auto { - margin-bottom: auto !important; - } - .ml-sm-auto, - .mx-sm-auto { - margin-left: auto !important; - } - } - - @media (min-width: 768px) { - .m-md-0 { - margin: 0 !important; - } - .mt-md-0, - .my-md-0 { - margin-top: 0 !important; - } - .mr-md-0, - .mx-md-0 { - margin-right: 0 !important; - } - .mb-md-0, - .my-md-0 { - margin-bottom: 0 !important; - } - .ml-md-0, - .mx-md-0 { - margin-left: 0 !important; - } - .m-md-1 { - margin: 0.25rem !important; - } - .mt-md-1, - .my-md-1 { - margin-top: 0.25rem !important; - } - .mr-md-1, - .mx-md-1 { - margin-right: 0.25rem !important; - } - .mb-md-1, - .my-md-1 { - margin-bottom: 0.25rem !important; - } - .ml-md-1, - .mx-md-1 { - margin-left: 0.25rem !important; - } - .m-md-2 { - margin: 0.5rem !important; - } - .mt-md-2, - .my-md-2 { - margin-top: 0.5rem !important; - } - .mr-md-2, - .mx-md-2 { - margin-right: 0.5rem !important; - } - .mb-md-2, - .my-md-2 { - margin-bottom: 0.5rem !important; - } - .ml-md-2, - .mx-md-2 { - margin-left: 0.5rem !important; - } - .m-md-3 { - margin: 1rem !important; - } - .mt-md-3, - .my-md-3 { - margin-top: 1rem !important; - } - .mr-md-3, - .mx-md-3 { - margin-right: 1rem !important; - } - .mb-md-3, - .my-md-3 { - margin-bottom: 1rem !important; - } - .ml-md-3, - .mx-md-3 { - margin-left: 1rem !important; - } - .m-md-4 { - margin: 1.5rem !important; - } - .mt-md-4, - .my-md-4 { - margin-top: 1.5rem !important; - } - .mr-md-4, - .mx-md-4 { - margin-right: 1.5rem !important; - } - .mb-md-4, - .my-md-4 { - margin-bottom: 1.5rem !important; - } - .ml-md-4, - .mx-md-4 { - margin-left: 1.5rem !important; - } - .m-md-5 { - margin: 3rem !important; - } - .mt-md-5, - .my-md-5 { - margin-top: 3rem !important; - } - .mr-md-5, - .mx-md-5 { - margin-right: 3rem !important; - } - .mb-md-5, - .my-md-5 { - margin-bottom: 3rem !important; - } - .ml-md-5, - .mx-md-5 { - margin-left: 3rem !important; - } - .p-md-0 { - padding: 0 !important; - } - .pt-md-0, - .py-md-0 { - padding-top: 0 !important; - } - .pr-md-0, - .px-md-0 { - padding-right: 0 !important; - } - .pb-md-0, - .py-md-0 { - padding-bottom: 0 !important; - } - .pl-md-0, - .px-md-0 { - padding-left: 0 !important; - } - .p-md-1 { - padding: 0.25rem !important; - } - .pt-md-1, - .py-md-1 { - padding-top: 0.25rem !important; - } - .pr-md-1, - .px-md-1 { - padding-right: 0.25rem !important; - } - .pb-md-1, - .py-md-1 { - padding-bottom: 0.25rem !important; - } - .pl-md-1, - .px-md-1 { - padding-left: 0.25rem !important; - } - .p-md-2 { - padding: 0.5rem !important; - } - .pt-md-2, - .py-md-2 { - padding-top: 0.5rem !important; - } - .pr-md-2, - .px-md-2 { - padding-right: 0.5rem !important; - } - .pb-md-2, - .py-md-2 { - padding-bottom: 0.5rem !important; - } - .pl-md-2, - .px-md-2 { - padding-left: 0.5rem !important; - } - .p-md-3 { - padding: 1rem !important; - } - .pt-md-3, - .py-md-3 { - padding-top: 1rem !important; - } - .pr-md-3, - .px-md-3 { - padding-right: 1rem !important; - } - .pb-md-3, - .py-md-3 { - padding-bottom: 1rem !important; - } - .pl-md-3, - .px-md-3 { - padding-left: 1rem !important; - } - .p-md-4 { - padding: 1.5rem !important; - } - .pt-md-4, - .py-md-4 { - padding-top: 1.5rem !important; - } - .pr-md-4, - .px-md-4 { - padding-right: 1.5rem !important; - } - .pb-md-4, - .py-md-4 { - padding-bottom: 1.5rem !important; - } - .pl-md-4, - .px-md-4 { - padding-left: 1.5rem !important; - } - .p-md-5 { - padding: 3rem !important; - } - .pt-md-5, - .py-md-5 { - padding-top: 3rem !important; - } - .pr-md-5, - .px-md-5 { - padding-right: 3rem !important; - } - .pb-md-5, - .py-md-5 { - padding-bottom: 3rem !important; - } - .pl-md-5, - .px-md-5 { - padding-left: 3rem !important; - } - .m-md-auto { - margin: auto !important; - } - .mt-md-auto, - .my-md-auto { - margin-top: auto !important; - } - .mr-md-auto, - .mx-md-auto { - margin-right: auto !important; - } - .mb-md-auto, - .my-md-auto { - margin-bottom: auto !important; - } - .ml-md-auto, - .mx-md-auto { - margin-left: auto !important; - } - } - - @media (min-width: 992px) { - .m-lg-0 { - margin: 0 !important; - } - .mt-lg-0, - .my-lg-0 { - margin-top: 0 !important; - } - .mr-lg-0, - .mx-lg-0 { - margin-right: 0 !important; - } - .mb-lg-0, - .my-lg-0 { - margin-bottom: 0 !important; - } - .ml-lg-0, - .mx-lg-0 { - margin-left: 0 !important; - } - .m-lg-1 { - margin: 0.25rem !important; - } - .mt-lg-1, - .my-lg-1 { - margin-top: 0.25rem !important; - } - .mr-lg-1, - .mx-lg-1 { - margin-right: 0.25rem !important; - } - .mb-lg-1, - .my-lg-1 { - margin-bottom: 0.25rem !important; - } - .ml-lg-1, - .mx-lg-1 { - margin-left: 0.25rem !important; - } - .m-lg-2 { - margin: 0.5rem !important; - } - .mt-lg-2, - .my-lg-2 { - margin-top: 0.5rem !important; - } - .mr-lg-2, - .mx-lg-2 { - margin-right: 0.5rem !important; - } - .mb-lg-2, - .my-lg-2 { - margin-bottom: 0.5rem !important; - } - .ml-lg-2, - .mx-lg-2 { - margin-left: 0.5rem !important; - } - .m-lg-3 { - margin: 1rem !important; - } - .mt-lg-3, - .my-lg-3 { - margin-top: 1rem !important; - } - .mr-lg-3, - .mx-lg-3 { - margin-right: 1rem !important; - } - .mb-lg-3, - .my-lg-3 { - margin-bottom: 1rem !important; - } - .ml-lg-3, - .mx-lg-3 { - margin-left: 1rem !important; - } - .m-lg-4 { - margin: 1.5rem !important; - } - .mt-lg-4, - .my-lg-4 { - margin-top: 1.5rem !important; - } - .mr-lg-4, - .mx-lg-4 { - margin-right: 1.5rem !important; - } - .mb-lg-4, - .my-lg-4 { - margin-bottom: 1.5rem !important; - } - .ml-lg-4, - .mx-lg-4 { - margin-left: 1.5rem !important; - } - .m-lg-5 { - margin: 3rem !important; - } - .mt-lg-5, - .my-lg-5 { - margin-top: 3rem !important; - } - .mr-lg-5, - .mx-lg-5 { - margin-right: 3rem !important; - } - .mb-lg-5, - .my-lg-5 { - margin-bottom: 3rem !important; - } - .ml-lg-5, - .mx-lg-5 { - margin-left: 3rem !important; - } - .p-lg-0 { - padding: 0 !important; - } - .pt-lg-0, - .py-lg-0 { - padding-top: 0 !important; - } - .pr-lg-0, - .px-lg-0 { - padding-right: 0 !important; - } - .pb-lg-0, - .py-lg-0 { - padding-bottom: 0 !important; - } - .pl-lg-0, - .px-lg-0 { - padding-left: 0 !important; - } - .p-lg-1 { - padding: 0.25rem !important; - } - .pt-lg-1, - .py-lg-1 { - padding-top: 0.25rem !important; - } - .pr-lg-1, - .px-lg-1 { - padding-right: 0.25rem !important; - } - .pb-lg-1, - .py-lg-1 { - padding-bottom: 0.25rem !important; - } - .pl-lg-1, - .px-lg-1 { - padding-left: 0.25rem !important; - } - .p-lg-2 { - padding: 0.5rem !important; - } - .pt-lg-2, - .py-lg-2 { - padding-top: 0.5rem !important; - } - .pr-lg-2, - .px-lg-2 { - padding-right: 0.5rem !important; - } - .pb-lg-2, - .py-lg-2 { - padding-bottom: 0.5rem !important; - } - .pl-lg-2, - .px-lg-2 { - padding-left: 0.5rem !important; - } - .p-lg-3 { - padding: 1rem !important; - } - .pt-lg-3, - .py-lg-3 { - padding-top: 1rem !important; - } - .pr-lg-3, - .px-lg-3 { - padding-right: 1rem !important; - } - .pb-lg-3, - .py-lg-3 { - padding-bottom: 1rem !important; - } - .pl-lg-3, - .px-lg-3 { - padding-left: 1rem !important; - } - .p-lg-4 { - padding: 1.5rem !important; - } - .pt-lg-4, - .py-lg-4 { - padding-top: 1.5rem !important; - } - .pr-lg-4, - .px-lg-4 { - padding-right: 1.5rem !important; - } - .pb-lg-4, - .py-lg-4 { - padding-bottom: 1.5rem !important; - } - .pl-lg-4, - .px-lg-4 { - padding-left: 1.5rem !important; - } - .p-lg-5 { - padding: 3rem !important; - } - .pt-lg-5, - .py-lg-5 { - padding-top: 3rem !important; - } - .pr-lg-5, - .px-lg-5 { - padding-right: 3rem !important; - } - .pb-lg-5, - .py-lg-5 { - padding-bottom: 3rem !important; - } - .pl-lg-5, - .px-lg-5 { - padding-left: 3rem !important; - } - .m-lg-auto { - margin: auto !important; - } - .mt-lg-auto, - .my-lg-auto { - margin-top: auto !important; - } - .mr-lg-auto, - .mx-lg-auto { - margin-right: auto !important; - } - .mb-lg-auto, - .my-lg-auto { - margin-bottom: auto !important; - } - .ml-lg-auto, - .mx-lg-auto { - margin-left: auto !important; - } - } - - @media (min-width: 1200px) { - .m-xl-0 { - margin: 0 !important; - } - .mt-xl-0, - .my-xl-0 { - margin-top: 0 !important; - } - .mr-xl-0, - .mx-xl-0 { - margin-right: 0 !important; - } - .mb-xl-0, - .my-xl-0 { - margin-bottom: 0 !important; - } - .ml-xl-0, - .mx-xl-0 { - margin-left: 0 !important; - } - .m-xl-1 { - margin: 0.25rem !important; - } - .mt-xl-1, - .my-xl-1 { - margin-top: 0.25rem !important; - } - .mr-xl-1, - .mx-xl-1 { - margin-right: 0.25rem !important; - } - .mb-xl-1, - .my-xl-1 { - margin-bottom: 0.25rem !important; - } - .ml-xl-1, - .mx-xl-1 { - margin-left: 0.25rem !important; - } - .m-xl-2 { - margin: 0.5rem !important; - } - .mt-xl-2, - .my-xl-2 { - margin-top: 0.5rem !important; - } - .mr-xl-2, - .mx-xl-2 { - margin-right: 0.5rem !important; - } - .mb-xl-2, - .my-xl-2 { - margin-bottom: 0.5rem !important; - } - .ml-xl-2, - .mx-xl-2 { - margin-left: 0.5rem !important; - } - .m-xl-3 { - margin: 1rem !important; - } - .mt-xl-3, - .my-xl-3 { - margin-top: 1rem !important; - } - .mr-xl-3, - .mx-xl-3 { - margin-right: 1rem !important; - } - .mb-xl-3, - .my-xl-3 { - margin-bottom: 1rem !important; - } - .ml-xl-3, - .mx-xl-3 { - margin-left: 1rem !important; - } - .m-xl-4 { - margin: 1.5rem !important; - } - .mt-xl-4, - .my-xl-4 { - margin-top: 1.5rem !important; - } - .mr-xl-4, - .mx-xl-4 { - margin-right: 1.5rem !important; - } - .mb-xl-4, - .my-xl-4 { - margin-bottom: 1.5rem !important; - } - .ml-xl-4, - .mx-xl-4 { - margin-left: 1.5rem !important; - } - .m-xl-5 { - margin: 3rem !important; - } - .mt-xl-5, - .my-xl-5 { - margin-top: 3rem !important; - } - .mr-xl-5, - .mx-xl-5 { - margin-right: 3rem !important; - } - .mb-xl-5, - .my-xl-5 { - margin-bottom: 3rem !important; - } - .ml-xl-5, - .mx-xl-5 { - margin-left: 3rem !important; - } - .p-xl-0 { - padding: 0 !important; - } - .pt-xl-0, - .py-xl-0 { - padding-top: 0 !important; - } - .pr-xl-0, - .px-xl-0 { - padding-right: 0 !important; - } - .pb-xl-0, - .py-xl-0 { - padding-bottom: 0 !important; - } - .pl-xl-0, - .px-xl-0 { - padding-left: 0 !important; - } - .p-xl-1 { - padding: 0.25rem !important; - } - .pt-xl-1, - .py-xl-1 { - padding-top: 0.25rem !important; - } - .pr-xl-1, - .px-xl-1 { - padding-right: 0.25rem !important; - } - .pb-xl-1, - .py-xl-1 { - padding-bottom: 0.25rem !important; - } - .pl-xl-1, - .px-xl-1 { - padding-left: 0.25rem !important; - } - .p-xl-2 { - padding: 0.5rem !important; - } - .pt-xl-2, - .py-xl-2 { - padding-top: 0.5rem !important; - } - .pr-xl-2, - .px-xl-2 { - padding-right: 0.5rem !important; - } - .pb-xl-2, - .py-xl-2 { - padding-bottom: 0.5rem !important; - } - .pl-xl-2, - .px-xl-2 { - padding-left: 0.5rem !important; - } - .p-xl-3 { - padding: 1rem !important; - } - .pt-xl-3, - .py-xl-3 { - padding-top: 1rem !important; - } - .pr-xl-3, - .px-xl-3 { - padding-right: 1rem !important; - } - .pb-xl-3, - .py-xl-3 { - padding-bottom: 1rem !important; - } - .pl-xl-3, - .px-xl-3 { - padding-left: 1rem !important; - } - .p-xl-4 { - padding: 1.5rem !important; - } - .pt-xl-4, - .py-xl-4 { - padding-top: 1.5rem !important; - } - .pr-xl-4, - .px-xl-4 { - padding-right: 1.5rem !important; - } - .pb-xl-4, - .py-xl-4 { - padding-bottom: 1.5rem !important; - } - .pl-xl-4, - .px-xl-4 { - padding-left: 1.5rem !important; - } - .p-xl-5 { - padding: 3rem !important; - } - .pt-xl-5, - .py-xl-5 { - padding-top: 3rem !important; - } - .pr-xl-5, - .px-xl-5 { - padding-right: 3rem !important; - } - .pb-xl-5, - .py-xl-5 { - padding-bottom: 3rem !important; - } - .pl-xl-5, - .px-xl-5 { - padding-left: 3rem !important; - } - .m-xl-auto { - margin: auto !important; - } - .mt-xl-auto, - .my-xl-auto { - margin-top: auto !important; - } - .mr-xl-auto, - .mx-xl-auto { - margin-right: auto !important; - } - .mb-xl-auto, - .my-xl-auto { - margin-bottom: auto !important; - } - .ml-xl-auto, - .mx-xl-auto { - margin-left: auto !important; - } - } - - .text-monospace { - font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - } - - .text-justify { - text-align: justify !important; - } - - .text-nowrap { - white-space: nowrap !important; - } - - .text-truncate { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .text-left { - text-align: left !important; - } - - .text-right { - text-align: right !important; - } - - .text-center { - text-align: center !important; - } - - @media (min-width: 576px) { - .text-sm-left { - text-align: left !important; - } - .text-sm-right { - text-align: right !important; - } - .text-sm-center { - text-align: center !important; - } - } - - @media (min-width: 768px) { - .text-md-left { - text-align: left !important; - } - .text-md-right { - text-align: right !important; - } - .text-md-center { - text-align: center !important; - } - } - - @media (min-width: 992px) { - .text-lg-left { - text-align: left !important; - } - .text-lg-right { - text-align: right !important; - } - .text-lg-center { - text-align: center !important; - } - } - - @media (min-width: 1200px) { - .text-xl-left { - text-align: left !important; - } - .text-xl-right { - text-align: right !important; - } - .text-xl-center { - text-align: center !important; - } - } - - .text-lowercase { - text-transform: lowercase !important; - } - - .text-uppercase { - text-transform: uppercase !important; - } - - .text-capitalize { - text-transform: capitalize !important; - } - - .font-weight-light { - font-weight: 300 !important; - } - - .font-weight-normal { - font-weight: 400 !important; - } - - .font-weight-bold { - font-weight: 700 !important; - } - - .font-italic { - font-style: italic !important; - } - - .text-white { - color: #fff !important; - } - - .text-primary { - color: #007bff !important; - } - - a.text-primary:hover, a.text-primary:focus { - color: #0062cc !important; - } - - .text-secondary { - color: #6c757d !important; - } - - a.text-secondary:hover, a.text-secondary:focus { - color: #545b62 !important; - } - - .text-success { - color: #28a745 !important; - } - - a.text-success:hover, a.text-success:focus { - color: #1e7e34 !important; - } - - .text-info { - color: #17a2b8 !important; - } - - a.text-info:hover, a.text-info:focus { - color: #117a8b !important; - } - - .text-warning { - color: #ffc107 !important; - } - - a.text-warning:hover, a.text-warning:focus { - color: #d39e00 !important; - } - - .text-danger { - color: #dc3545 !important; - } - - a.text-danger:hover, a.text-danger:focus { - color: #bd2130 !important; - } - - .text-light { - color: #f8f9fa !important; - } - - a.text-light:hover, a.text-light:focus { - color: #dae0e5 !important; - } - - .text-dark { - color: #343a40 !important; - } - - a.text-dark:hover, a.text-dark:focus { - color: #1d2124 !important; - } - - .text-body { - color: #212529 !important; - } - - .text-muted { - color: #6c757d !important; - } - - .text-black-50 { - color: rgba(0, 0, 0, 0.5) !important; - } - - .text-white-50 { - color: rgba(255, 255, 255, 0.5) !important; - } - - .text-hide { - font: 0/0 a; - color: transparent; - text-shadow: none; - background-color: transparent; - border: 0; - } - - .visible { - visibility: visible !important; - } - - .invisible { - visibility: hidden !important; - } - - @media print { - *, - *::before, - *::after { - text-shadow: none !important; - box-shadow: none !important; - } - a:not(.btn) { - text-decoration: underline; - } - abbr[title]::after { - content: " (" attr(title) ")"; - } - pre { - white-space: pre-wrap !important; - } - pre, - blockquote { - border: 1px solid #adb5bd; - page-break-inside: avoid; - } - thead { - display: table-header-group; - } - tr, - img { - page-break-inside: avoid; - } - p, - h2, - h3 { - orphans: 3; - widows: 3; - } - h2, - h3 { - page-break-after: avoid; - } - @page { - size: a3; - } - body { - min-width: 992px !important; - } - .container { - min-width: 992px !important; - } - .navbar { - display: none; - } - .badge { - border: 1px solid #000; - } - .table { - border-collapse: collapse !important; - } - .table td, - .table th { - background-color: #fff !important; - } - .table-bordered th, - .table-bordered td { - border: 1px solid #dee2e6 !important; - } - .table-dark { - color: inherit; - } - .table-dark th, - .table-dark td, - .table-dark thead th, - .table-dark tbody + tbody { - border-color: #dee2e6; - } - .table .thead-dark th { - color: inherit; - border-color: #dee2e6; - } - } - /*# sourceMappingURL=bootstrap.css.map */ \ No newline at end of file diff --git a/archive/active_learning/labeling_tool/static/css/bootstrap.min.css b/archive/active_learning/labeling_tool/static/css/bootstrap.min.css deleted file mode 100644 index 88269128..00000000 --- a/archive/active_learning/labeling_tool/static/css/bootstrap.min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v4.1.3 (https://getbootstrap.com/) - * Copyright 2011-2018 The Bootstrap Authors - * Copyright 2011-2018 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-family:inherit;font-weight:500;line-height:1.2;color:inherit}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014 \00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;min-height:1px;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;background-color:transparent}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table .table{background-color:#fff}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#212529;border-color:#32383e}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#212529}.table-dark td,.table-dark th,.table-dark thead th{border-color:#32383e}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(2.25rem + 2px);padding:.375rem .75rem;font-size:1rem;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media screen and (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.8125rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(2.875rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.custom-select.is-valid,.form-control.is-valid,.was-validated .custom-select:valid,.was-validated .form-control:valid{border-color:#28a745}.custom-select.is-valid:focus,.form-control.is-valid:focus,.was-validated .custom-select:valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.form-control-file.is-valid~.valid-feedback,.form-control-file.is-valid~.valid-tooltip,.was-validated .form-control-file:valid~.valid-feedback,.was-validated .form-control-file:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{background-color:#71dd8a}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(40,167,69,.25)}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label::after,.was-validated .custom-file-input:valid~.custom-file-label::after{border-color:inherit}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.custom-select.is-invalid,.form-control.is-invalid,.was-validated .custom-select:invalid,.was-validated .form-control:invalid{border-color:#dc3545}.custom-select.is-invalid:focus,.form-control.is-invalid:focus,.was-validated .custom-select:invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.form-control-file.is-invalid~.invalid-feedback,.form-control-file.is-invalid~.invalid-tooltip,.was-validated .form-control-file:invalid~.invalid-feedback,.was-validated .form-control-file:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{background-color:#efa2a9}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(220,53,69,.25)}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label::after,.was-validated .custom-file-input:invalid~.custom-file-label::after{border-color:inherit}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media screen and (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:focus,.btn:hover{text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-primary{color:#007bff;background-color:transparent;background-image:none;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;background-color:transparent;background-image:none;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;background-color:transparent;background-image:none;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;background-color:transparent;background-image:none;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;background-color:transparent;background-image:none;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;background-color:transparent;background-image:none;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;background-color:transparent;background-image:none;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;background-color:transparent;background-image:none;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;background-color:transparent}.btn-link:hover{color:#0056b3;text-decoration:underline;background-color:transparent;border-color:transparent}.btn-link.focus,.btn-link:focus{text-decoration:underline;border-color:transparent;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media screen and (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media screen and (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-right{right:0;left:auto}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;width:0;height:0;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:0 1 auto;flex:0 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group,.btn-group-vertical .btn+.btn,.btn-group-vertical .btn+.btn-group,.btn-group-vertical .btn-group+.btn,.btn-group-vertical .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical .btn,.btn-group-vertical .btn-group{width:100%}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{height:calc(2.875rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{height:calc(1.8125rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:active~.custom-control-label::before{color:#fff;background-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#dee2e6}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background-repeat:no-repeat;background-position:center center;background-size:50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(2.25rem + 2px);padding:.375rem 1.75rem .375rem .75rem;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center;background-size:8px 10px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(128,189,255,.5)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{opacity:0}.custom-select-sm{height:calc(1.8125rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:75%}.custom-select-lg{height:calc(2.875rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:125%}.custom-file{position:relative;display:inline-block;width:100%;height:calc(2.25rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(2.25rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:focus~.custom-file-label::after{border-color:#80bdff}.custom-file-input:disabled~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(2.25rem + 2px);padding:.375rem .75rem;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:2.25rem;padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:1px solid #ced4da;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;padding-left:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media screen and (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media screen and (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media screen and (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media screen and (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler:not(:disabled):not(.disabled){cursor:pointer}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-ms-flexbox;display:flex;-ms-flex:1 0 0%;flex:1 0 0%;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:first-child .card-header,.card-group>.card:first-child .card-img-top{border-top-right-radius:0}.card-group>.card:first-child .card-footer,.card-group>.card:first-child .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:last-child .card-header,.card-group>.card:last-child .card-img-top{border-top-left-radius:0}.card-group>.card:last-child .card-footer,.card-group>.card:last-child .card-img-bottom{border-bottom-left-radius:0}.card-group>.card:only-child{border-radius:.25rem}.card-group>.card:only-child .card-header,.card-group>.card:only-child .card-img-top{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-group>.card:only-child .card-footer,.card-group>.card:only-child .card-img-bottom{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-group>.card:not(:first-child):not(:last-child):not(:only-child){border-radius:0}.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-footer,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-header,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-top{border-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion .card:not(:first-of-type):not(:last-of-type){border-bottom:0;border-radius:0}.accordion .card:not(:first-of-type) .card-header:first-child{border-radius:0}.accordion .card:first-of-type{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion .card:last-of-type{border-top-left-radius:0;border-top-right-radius:0}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-link:not(:disabled):not(.disabled){cursor:pointer}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}.badge-primary[href]:focus,.badge-primary[href]:hover{color:#fff;text-decoration:none;background-color:#0062cc}.badge-secondary{color:#fff;background-color:#6c757d}.badge-secondary[href]:focus,.badge-secondary[href]:hover{color:#fff;text-decoration:none;background-color:#545b62}.badge-success{color:#fff;background-color:#28a745}.badge-success[href]:focus,.badge-success[href]:hover{color:#fff;text-decoration:none;background-color:#1e7e34}.badge-info{color:#fff;background-color:#17a2b8}.badge-info[href]:focus,.badge-info[href]:hover{color:#fff;text-decoration:none;background-color:#117a8b}.badge-warning{color:#212529;background-color:#ffc107}.badge-warning[href]:focus,.badge-warning[href]:hover{color:#212529;text-decoration:none;background-color:#d39e00}.badge-danger{color:#fff;background-color:#dc3545}.badge-danger[href]:focus,.badge-danger[href]:hover{color:#fff;text-decoration:none;background-color:#bd2130}.badge-light{color:#212529;background-color:#f8f9fa}.badge-light[href]:focus,.badge-light[href]:hover{color:#212529;text-decoration:none;background-color:#dae0e5}.badge-dark{color:#fff;background-color:#343a40}.badge-dark[href]:focus,.badge-dark[href]:hover{color:#fff;text-decoration:none;background-color:#1d2124}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media screen and (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item:focus,.list-group-item:hover{z-index:1;text-decoration:none}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:not(:disabled):not(.disabled){cursor:pointer}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{color:#000;text-decoration:none;opacity:.75}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-25%);transform:translate(0,-25%)}@media screen and (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:translate(0,0);transform:translate(0,0)}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - (.5rem * 2))}.modal-dialog-centered::before{display:block;height:calc(100vh - (.5rem * 2));content:""}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem;border-bottom:1px solid #e9ecef;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #e9ecef}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-centered{min-height:calc(100% - (1.75rem * 2))}.modal-dialog-centered::before{height:calc(100vh - (1.75rem * 2))}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg{max-width:800px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top] .arrow,.bs-popover-top .arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::after,.bs-popover-top .arrow::before{border-width:.5rem .5rem 0}.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::before{bottom:0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-top .arrow::after{bottom:1px;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right] .arrow,.bs-popover-right .arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::after,.bs-popover-right .arrow::before{border-width:.5rem .5rem .5rem 0}.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::before{left:0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-right .arrow::after{left:1px;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom] .arrow,.bs-popover-bottom .arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::after,.bs-popover-bottom .arrow::before{border-width:0 .5rem .5rem .5rem}.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::before{top:0;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-bottom .arrow::after{top:1px;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left] .arrow,.bs-popover-left .arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::after,.bs-popover-left .arrow::before{border-width:.5rem 0 .5rem .5rem}.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::before{right:0;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-left .arrow::after{right:1px;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;color:inherit;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-item{position:relative;display:none;-ms-flex-align:center;align-items:center;width:100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block;transition:-webkit-transform .6s ease;transition:transform .6s ease;transition:transform .6s ease,-webkit-transform .6s ease}@media screen and (prefers-reduced-motion:reduce){.carousel-item-next,.carousel-item-prev,.carousel-item.active{transition:none}}.carousel-item-next,.carousel-item-prev{position:absolute;top:0}.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translateX(0);transform:translateX(0)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.active.carousel-item-right,.carousel-item-next{-webkit-transform:translateX(100%);transform:translateX(100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-right,.carousel-item-next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translateX(-100%);transform:translateX(-100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.carousel-fade .carousel-item{opacity:0;transition-duration:.6s;transition-property:opacity}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{opacity:0}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-prev,.carousel-fade .carousel-item-next,.carousel-fade .carousel-item-prev,.carousel-fade .carousel-item.active{-webkit-transform:translateX(0);transform:translateX(0)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-prev,.carousel-fade .carousel-item-next,.carousel-fade .carousel-item-prev,.carousel-fade .carousel-item.active{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:transparent no-repeat center center;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{position:absolute;right:0;bottom:10px;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{position:relative;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:rgba(255,255,255,.5)}.carousel-indicators li::before{position:absolute;top:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators li::after{position:absolute;bottom:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-circle{border-radius:50%!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.text-justify{text-align:justify!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0062cc!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#545b62!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#1e7e34!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#117a8b!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#d39e00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#bd2130!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#dae0e5!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#1d2124!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/archive/active_learning/labeling_tool/static/html/index.html b/archive/active_learning/labeling_tool/static/html/index.html deleted file mode 100644 index 9effa96d..00000000 --- a/archive/active_learning/labeling_tool/static/html/index.html +++ /dev/null @@ -1,541 +0,0 @@ - - - - labeling-tool - - - - - - - - - - - - - - - - - -
- - - -
- - -
-
- -
-
-
- Image 1 -
None
-
- -
- Image 2 -
None
-
- -
- Image 3 -
None
-
- -
- Image 4 -
None
-
- -
- Image 5 -
None
-
- -
- Image 6 -
None
-
- -
- Image 7 -
None
-
- -
- Image 8 -
None
-
- -
- Image 9 -
None
-
- -
- Image 10 -
None
-
- -
- Image 11 -
None
-
- -
- Image 12 -
None
-
-
-
- - -
-
- -
- Load More Images -

Assign Label:

- - Train Classifier -
- - -
-

Detection Confidence:

- - -

Predicted Class:

-
- -
- Apply Selection Criteria -
-
-
-
-
- - - - - - - - -
- -
- -
- - - - - - - \ No newline at end of file diff --git a/archive/active_learning/labeling_tool/static/img/placeholder.JPG b/archive/active_learning/labeling_tool/static/img/placeholder.JPG deleted file mode 100644 index 51acb646..00000000 Binary files a/archive/active_learning/labeling_tool/static/img/placeholder.JPG and /dev/null differ diff --git a/archive/active_learning/labeling_tool/static/js/bootstrap.bundle.min.js b/archive/active_learning/labeling_tool/static/js/bootstrap.bundle.min.js deleted file mode 100644 index 43203684..00000000 --- a/archive/active_learning/labeling_tool/static/js/bootstrap.bundle.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v4.3.1 (https://getbootstrap.com/) - * Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery")):"function"==typeof define&&define.amd?define(["exports","jquery"],e):e((t=t||self).bootstrap={},t.jQuery)}(this,function(t,p){"use strict";function i(t,e){for(var n=0;nthis._items.length-1||t<0))if(this._isSliding)p(this._element).one(q.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=n=i.clientWidth&&n>=i.clientHeight}),h=0l[t]&&!i.escapeWithReference&&(n=Math.min(h[e],l[t]-("right"===t?h.width:h.height))),Kt({},e,n)}};return c.forEach(function(t){var e=-1!==["left","top"].indexOf(t)?"primary":"secondary";h=Qt({},h,u[e](t))}),t.offsets.popper=h,t},priority:["left","right","top","bottom"],padding:5,boundariesElement:"scrollParent"},keepTogether:{order:400,enabled:!0,fn:function(t){var e=t.offsets,n=e.popper,i=e.reference,o=t.placement.split("-")[0],r=Math.floor,s=-1!==["top","bottom"].indexOf(o),a=s?"right":"bottom",l=s?"left":"top",c=s?"width":"height";return n[a]r(i[a])&&(t.offsets.popper[l]=r(i[a])),t}},arrow:{order:500,enabled:!0,fn:function(t,e){var n;if(!fe(t.instance.modifiers,"arrow","keepTogether"))return t;var i=e.element;if("string"==typeof i){if(!(i=t.instance.popper.querySelector(i)))return t}else if(!t.instance.popper.contains(i))return console.warn("WARNING: `arrow.element` must be child of its popper element!"),t;var o=t.placement.split("-")[0],r=t.offsets,s=r.popper,a=r.reference,l=-1!==["left","right"].indexOf(o),c=l?"height":"width",h=l?"Top":"Left",u=h.toLowerCase(),f=l?"left":"top",d=l?"bottom":"right",p=Zt(i)[c];a[d]-ps[d]&&(t.offsets.popper[u]+=a[u]+p-s[d]),t.offsets.popper=Vt(t.offsets.popper);var m=a[u]+a[c]/2-p/2,g=Nt(t.instance.popper),_=parseFloat(g["margin"+h],10),v=parseFloat(g["border"+h+"Width"],10),y=m-t.offsets.popper[u]-_-v;return y=Math.max(Math.min(s[c]-p,y),0),t.arrowElement=i,t.offsets.arrow=(Kt(n={},u,Math.round(y)),Kt(n,f,""),n),t},element:"[x-arrow]"},flip:{order:600,enabled:!0,fn:function(p,m){if(oe(p.instance.modifiers,"inner"))return p;if(p.flipped&&p.placement===p.originalPlacement)return p;var g=Gt(p.instance.popper,p.instance.reference,m.padding,m.boundariesElement,p.positionFixed),_=p.placement.split("-")[0],v=te(_),y=p.placement.split("-")[1]||"",E=[];switch(m.behavior){case ge:E=[_,v];break;case _e:E=me(_);break;case ve:E=me(_,!0);break;default:E=m.behavior}return E.forEach(function(t,e){if(_!==t||E.length===e+1)return p;_=p.placement.split("-")[0],v=te(_);var n,i=p.offsets.popper,o=p.offsets.reference,r=Math.floor,s="left"===_&&r(i.right)>r(o.left)||"right"===_&&r(i.left)r(o.top)||"bottom"===_&&r(i.top)r(g.right),c=r(i.top)r(g.bottom),u="left"===_&&a||"right"===_&&l||"top"===_&&c||"bottom"===_&&h,f=-1!==["top","bottom"].indexOf(_),d=!!m.flipVariations&&(f&&"start"===y&&a||f&&"end"===y&&l||!f&&"start"===y&&c||!f&&"end"===y&&h);(s||u||d)&&(p.flipped=!0,(s||u)&&(_=E[e+1]),d&&(y="end"===(n=y)?"start":"start"===n?"end":n),p.placement=_+(y?"-"+y:""),p.offsets.popper=Qt({},p.offsets.popper,ee(p.instance.popper,p.offsets.reference,p.placement)),p=ie(p.instance.modifiers,p,"flip"))}),p},behavior:"flip",padding:5,boundariesElement:"viewport"},inner:{order:700,enabled:!1,fn:function(t){var e=t.placement,n=e.split("-")[0],i=t.offsets,o=i.popper,r=i.reference,s=-1!==["left","right"].indexOf(n),a=-1===["top","left"].indexOf(n);return o[s?"left":"top"]=r[n]-(a?o[s?"width":"height"]:0),t.placement=te(e),t.offsets.popper=Vt(o),t}},hide:{order:800,enabled:!0,fn:function(t){if(!fe(t.instance.modifiers,"hide","preventOverflow"))return t;var e=t.offsets.reference,n=ne(t.instance.modifiers,function(t){return"preventOverflow"===t.name}).boundaries;if(e.bottomn.right||e.top>n.bottom||e.rightdocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",sanitize:!0,sanitizeFn:null,whiteList:vn},Ln="show",xn="out",Pn={HIDE:"hide"+Tn,HIDDEN:"hidden"+Tn,SHOW:"show"+Tn,SHOWN:"shown"+Tn,INSERTED:"inserted"+Tn,CLICK:"click"+Tn,FOCUSIN:"focusin"+Tn,FOCUSOUT:"focusout"+Tn,MOUSEENTER:"mouseenter"+Tn,MOUSELEAVE:"mouseleave"+Tn},Hn="fade",jn="show",Rn=".tooltip-inner",Fn=".arrow",Mn="hover",Wn="focus",Un="click",Bn="manual",qn=function(){function i(t,e){if("undefined"==typeof be)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=p(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),p(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(p(this.getTipElement()).hasClass(jn))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),p.removeData(this.element,this.constructor.DATA_KEY),p(this.element).off(this.constructor.EVENT_KEY),p(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&p(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,(this._activeTrigger=null)!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===p(this.element).css("display"))throw new Error("Please use show on visible elements");var t=p.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){p(this.element).trigger(t);var n=m.findShadowRoot(this.element),i=p.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!i)return;var o=this.getTipElement(),r=m.getUID(this.constructor.NAME);o.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&p(o).addClass(Hn);var s="function"==typeof this.config.placement?this.config.placement.call(this,o,this.element):this.config.placement,a=this._getAttachment(s);this.addAttachmentClass(a);var l=this._getContainer();p(o).data(this.constructor.DATA_KEY,this),p.contains(this.element.ownerDocument.documentElement,this.tip)||p(o).appendTo(l),p(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new be(this.element,o,{placement:a,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:Fn},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}}),p(o).addClass(jn),"ontouchstart"in document.documentElement&&p(document.body).children().on("mouseover",null,p.noop);var c=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,p(e.element).trigger(e.constructor.Event.SHOWN),t===xn&&e._leave(null,e)};if(p(this.tip).hasClass(Hn)){var h=m.getTransitionDurationFromElement(this.tip);p(this.tip).one(m.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},t.hide=function(t){var e=this,n=this.getTipElement(),i=p.Event(this.constructor.Event.HIDE),o=function(){e._hoverState!==Ln&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),p(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(p(this.element).trigger(i),!i.isDefaultPrevented()){if(p(n).removeClass(jn),"ontouchstart"in document.documentElement&&p(document.body).children().off("mouseover",null,p.noop),this._activeTrigger[Un]=!1,this._activeTrigger[Wn]=!1,this._activeTrigger[Mn]=!1,p(this.tip).hasClass(Hn)){var r=m.getTransitionDurationFromElement(n);p(n).one(m.TRANSITION_END,o).emulateTransitionEnd(r)}else o();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){p(this.getTipElement()).addClass(Dn+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||p(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(p(t.querySelectorAll(Rn)),this.getTitle()),p(t).removeClass(Hn+" "+jn)},t.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=bn(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?p(e).parent().is(t)||t.empty().append(e):t.text(p(e).text())},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},t._getOffset=function(){var e=this,t={};return"function"==typeof this.config.offset?t.fn=function(t){return t.offsets=l({},t.offsets,e.config.offset(t.offsets,e.element)||{}),t}:t.offset=this.config.offset,t},t._getContainer=function(){return!1===this.config.container?document.body:m.isElement(this.config.container)?p(this.config.container):p(document).find(this.config.container)},t._getAttachment=function(t){return Nn[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)p(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==Bn){var e=t===Mn?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===Mn?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;p(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}}),p(this.element).closest(".modal").on("hide.bs.modal",function(){i.element&&i.hide()}),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||p(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),p(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Wn:Mn]=!0),p(e.getTipElement()).hasClass(jn)||e._hoverState===Ln?e._hoverState=Ln:(clearTimeout(e._timeout),e._hoverState=Ln,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===Ln&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||p(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),p(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Wn:Mn]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=xn,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===xn&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){var e=p(this.element).data();return Object.keys(e).forEach(function(t){-1!==An.indexOf(t)&&delete e[t]}),"number"==typeof(t=l({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),m.typeCheckConfig(wn,t,this.constructor.DefaultType),t.sanitize&&(t.template=bn(t.template,t.whiteList,t.sanitizeFn)),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=p(this.getTipElement()),e=t.attr("class").match(In);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(p(t).removeClass(Hn),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=p(this).data(Cn),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),p(this).data(Cn,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.3.1"}},{key:"Default",get:function(){return kn}},{key:"NAME",get:function(){return wn}},{key:"DATA_KEY",get:function(){return Cn}},{key:"Event",get:function(){return Pn}},{key:"EVENT_KEY",get:function(){return Tn}},{key:"DefaultType",get:function(){return On}}]),i}();p.fn[wn]=qn._jQueryInterface,p.fn[wn].Constructor=qn,p.fn[wn].noConflict=function(){return p.fn[wn]=Sn,qn._jQueryInterface};var Kn="popover",Qn="bs.popover",Vn="."+Qn,Yn=p.fn[Kn],zn="bs-popover",Xn=new RegExp("(^|\\s)"+zn+"\\S+","g"),Gn=l({},qn.Default,{placement:"right",trigger:"click",content:"",template:''}),$n=l({},qn.DefaultType,{content:"(string|element|function)"}),Jn="fade",Zn="show",ti=".popover-header",ei=".popover-body",ni={HIDE:"hide"+Vn,HIDDEN:"hidden"+Vn,SHOW:"show"+Vn,SHOWN:"shown"+Vn,INSERTED:"inserted"+Vn,CLICK:"click"+Vn,FOCUSIN:"focusin"+Vn,FOCUSOUT:"focusout"+Vn,MOUSEENTER:"mouseenter"+Vn,MOUSELEAVE:"mouseleave"+Vn},ii=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),(e.prototype.constructor=e).__proto__=n;var o=i.prototype;return o.isWithContent=function(){return this.getTitle()||this._getContent()},o.addAttachmentClass=function(t){p(this.getTipElement()).addClass(zn+"-"+t)},o.getTipElement=function(){return this.tip=this.tip||p(this.config.template)[0],this.tip},o.setContent=function(){var t=p(this.getTipElement());this.setElementContent(t.find(ti),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(ei),e),t.removeClass(Jn+" "+Zn)},o._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},o._cleanTipClass=function(){var t=p(this.getTipElement()),e=t.attr("class").match(Xn);null!==e&&0=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;while(s--)h[s]="#"+c+" "+ve(h[s]);v=h.join(","),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||y.push("~="),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||y.push(".#.+[+~]")}),ue(function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",W)}),y=y.length&&new RegExp(y.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(D).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,"parentNode")},parentsUntil:function(e,t,n){return k(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return k(e,"nextSibling")},prevAll:function(e){return k(e,"previousSibling")},nextUntil:function(e,t,n){return k(e,"nextSibling",n)},prevUntil:function(e,t,n){return k(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener("DOMContentLoaded",_),e.removeEventListener("load",_),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",_),e.addEventListener("load",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/\s*$/g;function Le(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function He(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Oe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Pe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof y&&!h.checkClone&&je.test(y))return e.each(function(i){var o=e.eq(i);v&&(t[0]=y.call(this,i,o.html())),Re(o,t,n,r)});if(p&&(i=xe(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=w.map(ye(i,"script"),He)).length;f")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ye(s),r=0,i=(o=ye(e)).length;r0&&ve(a,!u&&ye(e,"script")),s},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return z(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ye(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return z(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ae.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=$e(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(We.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=G(t),u=Xe.test(t),l=e.style;if(u||(t=Je(s)),a=w.cssHooks[t]||w.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[s]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=G(t);return Xe.test(t)||(t=Je(s)),(a=w.cssHooks[t]||w.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Ve&&(i=Ve[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ue,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a="border-box"===w.css(e,"boxSizing",!1,o),s=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),s&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Ke(e,n,s)}}}),w.cssHooks.marginLeft=_e(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Ke)}),w.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a1)}});function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}w.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||w.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(w.cssNumber[n]?"":"px")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=w.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=w.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){w.fx.step[e.prop]?w.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[w.cssProps[e.prop]]&&!w.cssHooks[e.prop]?e.elem[e.prop]=e.now:w.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},w.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},w.fx=tt.prototype.init,w.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,w.fx.interval),w.fx.tick())}function st(){return e.setTimeout(function(){nt=void 0}),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(pt.tweeners[t]||[]).concat(pt.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&N(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(M);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ht[t]||w.find.attr;ht[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ht[a],ht[a]=i,i=null!=n(e,t,r)?a:null,ht[a]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return z(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):gt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function vt(e){return(e.match(M)||[]).join(" ")}function mt(e){return e.getAttribute&&e.getAttribute("class")||""}function xt(e){return Array.isArray(e)?e:"string"==typeof e?e.match(M)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,mt(this)))});if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr("class","");if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=xt(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=mt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+vt(mt(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(bt,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:vt(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var wt=/^(?:focusinfocus|focusoutblur)$/,Tt=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h,v=[i||r],m=f.call(t,"type")?t.type:t,x=f.call(t,"namespace")?t.namespace.split("."):[];if(s=h=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!wt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(x=m.split(".")).shift(),x.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=x.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+x.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),d=w.event.special[m]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!y(i)){for(l=d.delegateType||m,wt.test(l+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(i.ownerDocument||r)&&v.push(u.defaultView||u.parentWindow||e)}a=0;while((s=v[a++])&&!t.isPropagationStopped())h=s,t.type=a>1?l:d.bindType||m,(p=(J.get(s,"events")||{})[t.type]&&J.get(s,"handle"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&Y(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(v.pop(),n)||!Y(i)||c&&g(i[m])&&!y(i)&&((u=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,Tt),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,Tt),w.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var Ct=e.location,Et=Date.now(),kt=/\?/;w.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||w.error("Invalid XML: "+t),n};var St=/\[\]$/,Dt=/\r?\n/g,Nt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function jt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||St.test(e)?r(e,i):jt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==x(t))r(e,t);else for(i in t)jt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)jt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&At.test(this.nodeName)&&!Nt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(Dt,"\r\n")}}):{name:t.name,value:n.replace(Dt,"\r\n")}}).get()}});var qt=/%20/g,Lt=/#.*$/,Ht=/([?&])_=[^&]*/,Ot=/^(.*?):[ \t]*([^\r\n]*)$/gm,Pt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Rt=/^\/\//,It={},Wt={},$t="*/".concat("*"),Bt=r.createElement("a");Bt.href=Ct.href;function Ft(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(M)||[];if(g(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function _t(e,t,n,r){var i={},o=e===Wt;function a(s){var u;return i[s]=!0,w.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function zt(e,t){var n,r,i=w.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&w.extend(!0,e,r),e}function Xt(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function Ut(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}w.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Pt.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$t,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":w.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,w.ajaxSettings),t):zt(w.ajaxSettings,e)},ajaxPrefilter:Ft(It),ajaxTransport:Ft(Wt),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=w.ajaxSetup({},n),g=h.context||h,y=h.context&&(g.nodeType||g.jquery)?w(g):w.event,v=w.Deferred(),m=w.Callbacks("once memory"),x=h.statusCode||{},b={},T={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s){s={};while(t=Ot.exec(a))s[t[1].toLowerCase()]=t[2]}t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||C;return i&&i.abort(t),k(0,t),this}};if(v.promise(E),h.url=((t||h.url||Ct.href)+"").replace(Rt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(M)||[""],null==h.crossDomain){l=r.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Bt.protocol+"//"+Bt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=w.param(h.data,h.traditional)),_t(It,h,n,E),c)return E;(f=w.event&&h.global)&&0==w.active++&&w.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Mt.test(h.type),o=h.url.replace(Lt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(qt,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"==typeof h.data)&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ht,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(w.lastModified[o]&&E.setRequestHeader("If-Modified-Since",w.lastModified[o]),w.etag[o]&&E.setRequestHeader("If-None-Match",w.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+$t+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C="abort",m.add(h.complete),E.done(h.success),E.fail(h.error),i=_t(Wt,h,n,E)){if(E.readyState=1,f&&y.trigger("ajaxSend",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort("timeout")},h.timeout));try{c=!1,i.send(b,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,s){var l,p,d,b,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Xt(h,E,r)),b=Ut(h,b,E,l),l?(h.ifModified&&((T=E.getResponseHeader("Last-Modified"))&&(w.lastModified[o]=T),(T=E.getResponseHeader("etag"))&&(w.etag[o]=T)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=b.state,p=b.data,l=!(d=b.error))):(d=C,!t&&C||(C="error",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+"",l?v.resolveWith(g,[p,C,E]):v.rejectWith(g,[E,C,d]),E.statusCode(x),x=void 0,f&&y.trigger(l?"ajaxSuccess":"ajaxError",[E,h,l?p:d]),m.fireWith(g,[E,C]),f&&(y.trigger("ajaxComplete",[E,h]),--w.active||w.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return w.get(e,t,n,"json")},getScript:function(e,t){return w.get(e,void 0,t,"script")}}),w.each(["get","post"],function(e,t){w[t]=function(e,n,r,i){return g(n)&&(i=i||r,r=n,n=void 0),w.ajax(w.extend({url:e,type:t,dataType:i,data:n,success:r},w.isPlainObject(e)&&e))}}),w._evalUrl=function(e){return w.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},w.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Gt=w.ajaxSettings.xhr();h.cors=!!Gt&&"withCredentials"in Gt,h.ajax=Gt=!!Gt,w.ajaxTransport(function(t){var n,r;if(h.cors||Gt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Vt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=s.ontimeout=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),w.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),w.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return w.globalEval(e),e}}}),w.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),w.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(i,o){t=w("