diff --git a/app.py b/app.py new file mode 100644 index 0000000000000000000000000000000000000000..b2eac074ad05ccac3adf52711ed2b0ed91968bfe --- /dev/null +++ b/app.py @@ -0,0 +1,36 @@ +import streamlit as st +from carvekit.api.interface import Interface +from carvekit.ml.wrap.fba_matting import FBAMatting +from carvekit.ml.wrap.tracer_b7 import TracerUniversalB7 +from carvekit.pipelines.postprocessing import MattingMethod +from carvekit.pipelines.preprocessing import PreprocessingStub +from carvekit.trimap.generator import TrimapGenerator +from PIL import Image + +# Create Streamlit app title +st.title("Image Background Remover") + +# Create a file uploader +uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "png"]) + +if uploaded_file is not None: + # Load the image + image = Image.open(uploaded_file) + + # Set up ML pipeline + seg_net = TracerUniversalB7(device='cpu', batch_size=1) + fba = FBAMatting(device='cpu', input_tensor_size=2048, batch_size=1) + trimap = TrimapGenerator() + preprocessing = PreprocessingStub() + postprocessing = MattingMethod(matting_module=fba, trimap_generator=trimap, device='cpu') + interface = Interface(pre_pipe=preprocessing, post_pipe=postprocessing, seg_pipe=seg_net) + + # Process the image + processed_bg = interface([image])[0] + + # Display original and processed images + col1, col2 = st.columns(2) + with col1: + st.image(image, caption='Original Image', use_column_width=True) + with col2: + st.image(processed_bg, caption='Background Removed', use_column_width=True) diff --git a/carvekit/__init__.py b/carvekit/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b58821b7960ebe4595a1bccc778a13187301c469 --- /dev/null +++ b/carvekit/__init__.py @@ -0,0 +1 @@ +version = "4.1.0" diff --git a/carvekit/__main__.py b/carvekit/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..acf901dbe8c00a36726ba199b7e7b51c82f74a48 --- /dev/null +++ b/carvekit/__main__.py @@ -0,0 +1,149 @@ +from pathlib import Path + +import click +import tqdm + +from carvekit.utils.image_utils import ALLOWED_SUFFIXES +from carvekit.utils.pool_utils import batch_generator, thread_pool_processing +from carvekit.web.schemas.config import MLConfig +from carvekit.web.utils.init_utils import init_interface +from carvekit.utils.fs_utils import save_file + + +@click.command( + "removebg", + help="Performs background removal on specified photos using console interface.", +) +@click.option("-i", required=True, type=str, help="Path to input file or dir") +@click.option("-o", default="none", type=str, help="Path to output file or dir") +@click.option("--pre", default="none", type=str, help="Preprocessing method") +@click.option("--post", default="fba", type=str, help="Postprocessing method.") +@click.option("--net", default="tracer_b7", type=str, help="Segmentation Network") +@click.option( + "--recursive", + default=False, + type=bool, + help="Enables recursive search for images in a folder", +) +@click.option( + "--batch_size", + default=10, + type=int, + help="Batch Size for list of images to be loaded to RAM", +) +@click.option( + "--batch_size_seg", + default=5, + type=int, + help="Batch size for list of images to be processed by segmentation " "network", +) +@click.option( + "--batch_size_mat", + default=1, + type=int, + help="Batch size for list of images to be processed by matting " "network", +) +@click.option( + "--seg_mask_size", + default=640, + type=int, + help="The size of the input image for the segmentation neural network.", +) +@click.option( + "--matting_mask_size", + default=2048, + type=int, + help="The size of the input image for the matting neural network.", +) +@click.option( + "--trimap_dilation", + default=30, + type=int, + help="The size of the offset radius from the object mask in " + "pixels when forming an unknown area", +) +@click.option( + "--trimap_erosion", + default=5, + type=int, + help="The number of iterations of erosion that the object's " + "mask will be subjected to before forming an unknown area", +) +@click.option( + "--trimap_prob_threshold", + default=231, + type=int, + help="Probability threshold at which the prob_filter " + "and prob_as_unknown_area operations will be " + "applied", +) +@click.option("--device", default="cpu", type=str, help="Processing Device.") +@click.option( + "--fp16", default=False, type=bool, help="Enables mixed precision processing." +) +def removebg( + i: str, + o: str, + pre: str, + post: str, + net: str, + recursive: bool, + batch_size: int, + batch_size_seg: int, + batch_size_mat: int, + seg_mask_size: int, + matting_mask_size: int, + device: str, + fp16: bool, + trimap_dilation: int, + trimap_erosion: int, + trimap_prob_threshold: int, +): + out_path = Path(o) + input_path = Path(i) + if input_path.is_dir(): + if recursive: + all_images = input_path.rglob("*.*") + else: + all_images = input_path.glob("*.*") + all_images = [ + i + for i in all_images + if i.suffix.lower() in ALLOWED_SUFFIXES and "_bg_removed" not in i.name + ] + else: + all_images = [input_path] + + interface_config = MLConfig( + segmentation_network=net, + preprocessing_method=pre, + postprocessing_method=post, + device=device, + batch_size_seg=batch_size_seg, + batch_size_matting=batch_size_mat, + seg_mask_size=seg_mask_size, + matting_mask_size=matting_mask_size, + fp16=fp16, + trimap_dilation=trimap_dilation, + trimap_erosion=trimap_erosion, + trimap_prob_threshold=trimap_prob_threshold, + ) + + interface = init_interface(interface_config) + + for image_batch in tqdm.tqdm( + batch_generator(all_images, n=batch_size), + total=int(len(all_images) / batch_size), + desc="Removing background", + unit=" image batch", + colour="blue", + ): + images_without_background = interface(image_batch) # Remove background + thread_pool_processing( + lambda x: save_file(out_path, image_batch[x], images_without_background[x]), + range((len(image_batch))), + ) # Drop images to fs + + +if __name__ == "__main__": + removebg() diff --git a/carvekit/__pycache__/__init__.cpython-38.pyc b/carvekit/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58de2b6bd510e415838dca8670862819b638d0f2 Binary files /dev/null and b/carvekit/__pycache__/__init__.cpython-38.pyc differ diff --git a/carvekit/api/__init__.py b/carvekit/api/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/api/__pycache__/__init__.cpython-38.pyc b/carvekit/api/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f07b5115695ef5b28bac6e2ab0c82310f5922b92 Binary files /dev/null and b/carvekit/api/__pycache__/__init__.cpython-38.pyc differ diff --git a/carvekit/api/__pycache__/high.cpython-38.pyc b/carvekit/api/__pycache__/high.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..080728eaa0e124144d1ce3c1e48e2bd6ac53e7a6 Binary files /dev/null and b/carvekit/api/__pycache__/high.cpython-38.pyc differ diff --git a/carvekit/api/__pycache__/interface.cpython-38.pyc b/carvekit/api/__pycache__/interface.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4627e1d81d292d403cc5e54e3f02b565bb0626b Binary files /dev/null and b/carvekit/api/__pycache__/interface.cpython-38.pyc differ diff --git a/carvekit/api/high.py b/carvekit/api/high.py new file mode 100644 index 0000000000000000000000000000000000000000..46fb9d3dbb642e4098bb57a43b29667b66be220c --- /dev/null +++ b/carvekit/api/high.py @@ -0,0 +1,100 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +import warnings + +from carvekit.api.interface import Interface +from carvekit.ml.wrap.fba_matting import FBAMatting +from carvekit.ml.wrap.tracer_b7 import TracerUniversalB7 +from carvekit.ml.wrap.u2net import U2NET +from carvekit.pipelines.postprocessing import MattingMethod +from carvekit.trimap.generator import TrimapGenerator + + +class HiInterface(Interface): + def __init__( + self, + object_type: str = "object", + batch_size_seg=2, + batch_size_matting=1, + device="cpu", + seg_mask_size=640, + matting_mask_size=2048, + trimap_prob_threshold=231, + trimap_dilation=30, + trimap_erosion_iters=5, + fp16=False, + ): + """ + Initializes High Level interface. + + Args: + object_type: Interest object type. Can be "object" or "hairs-like". + matting_mask_size: The size of the input image for the matting neural network. + seg_mask_size: The size of the input image for the segmentation neural network. + batch_size_seg: Number of images processed per one segmentation neural network call. + batch_size_matting: Number of images processed per one matting neural network call. + device: Processing device + fp16: Use half precision. Reduce memory usage and increase speed. Experimental support + trimap_prob_threshold: Probability threshold at which the prob_filter and prob_as_unknown_area operations will be applied + trimap_dilation: The size of the offset radius from the object mask in pixels when forming an unknown area + trimap_erosion_iters: The number of iterations of erosion that the object's mask will be subjected to before forming an unknown area + + Notes: + 1. Changing seg_mask_size may cause an out-of-memory error if the value is too large, and it may also + result in reduced precision. I do not recommend changing this value. You can change matting_mask_size in + range from (1024 to 4096) to improve object edge refining quality, but it will cause extra large RAM and + video memory consume. Also, you can change batch size to accelerate background removal, but it also causes + extra large video memory consume, if value is too big. + + 2. Changing trimap_prob_threshold, trimap_kernel_size, trimap_erosion_iters may improve object edge + refining quality, + """ + if object_type == "object": + self.u2net = TracerUniversalB7( + device=device, + batch_size=batch_size_seg, + input_image_size=seg_mask_size, + fp16=fp16, + ) + elif object_type == "hairs-like": + self.u2net = U2NET( + device=device, + batch_size=batch_size_seg, + input_image_size=seg_mask_size, + fp16=fp16, + ) + else: + warnings.warn( + f"Unknown object type: {object_type}. Using default object type: object" + ) + self.u2net = TracerUniversalB7( + device=device, + batch_size=batch_size_seg, + input_image_size=seg_mask_size, + fp16=fp16, + ) + + self.fba = FBAMatting( + batch_size=batch_size_matting, + device=device, + input_tensor_size=matting_mask_size, + fp16=fp16, + ) + self.trimap_generator = TrimapGenerator( + prob_threshold=trimap_prob_threshold, + kernel_size=trimap_dilation, + erosion_iters=trimap_erosion_iters, + ) + super(HiInterface, self).__init__( + pre_pipe=None, + seg_pipe=self.u2net, + post_pipe=MattingMethod( + matting_module=self.fba, + trimap_generator=self.trimap_generator, + device=device, + ), + device=device, + ) diff --git a/carvekit/api/interface.py b/carvekit/api/interface.py new file mode 100644 index 0000000000000000000000000000000000000000..364d24775aee2ef7aaa973450c26660007184c77 --- /dev/null +++ b/carvekit/api/interface.py @@ -0,0 +1,77 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +from pathlib import Path +from typing import Union, List, Optional + +from PIL import Image + +from carvekit.ml.wrap.basnet import BASNET +from carvekit.ml.wrap.deeplab_v3 import DeepLabV3 +from carvekit.ml.wrap.u2net import U2NET +from carvekit.ml.wrap.tracer_b7 import TracerUniversalB7 +from carvekit.pipelines.preprocessing import PreprocessingStub +from carvekit.pipelines.postprocessing import MattingMethod +from carvekit.utils.image_utils import load_image +from carvekit.utils.mask_utils import apply_mask +from carvekit.utils.pool_utils import thread_pool_processing + + +class Interface: + def __init__( + self, + seg_pipe: Union[U2NET, BASNET, DeepLabV3, TracerUniversalB7], + pre_pipe: Optional[Union[PreprocessingStub]] = None, + post_pipe: Optional[Union[MattingMethod]] = None, + device="cpu", + ): + """ + Initializes an object for interacting with pipelines and other components of the CarveKit framework. + + Args: + pre_pipe: Initialized pre-processing pipeline object + seg_pipe: Initialized segmentation network object + post_pipe: Initialized postprocessing pipeline object + device: The processing device that will be used to apply the masks to the images. + """ + self.device = device + self.preprocessing_pipeline = pre_pipe + self.segmentation_pipeline = seg_pipe + self.postprocessing_pipeline = post_pipe + + def __call__( + self, images: List[Union[str, Path, Image.Image]] + ) -> List[Image.Image]: + """ + Removes the background from the specified images. + + Args: + images: list of input images + + Returns: + List of images without background as PIL.Image.Image instances + """ + images = thread_pool_processing(load_image, images) + if self.preprocessing_pipeline is not None: + masks: List[Image.Image] = self.preprocessing_pipeline( + interface=self, images=images + ) + else: + masks: List[Image.Image] = self.segmentation_pipeline(images=images) + + if self.postprocessing_pipeline is not None: + images: List[Image.Image] = self.postprocessing_pipeline( + images=images, masks=masks + ) + else: + images = list( + map( + lambda x: apply_mask( + image=images[x], mask=masks[x], device=self.device + ), + range(len(images)), + ) + ) + return images diff --git a/carvekit/ml/__init__.py b/carvekit/ml/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5b1844387efde8b2f26b9a64634a56b144558101 --- /dev/null +++ b/carvekit/ml/__init__.py @@ -0,0 +1,4 @@ +from carvekit.utils.models_utils import fix_seed, suppress_warnings + +fix_seed() +suppress_warnings() diff --git a/carvekit/ml/__pycache__/__init__.cpython-38.pyc b/carvekit/ml/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73696a391d23e92a9c5a9b67c0bb31bfbfa2d1ee Binary files /dev/null and b/carvekit/ml/__pycache__/__init__.cpython-38.pyc differ diff --git a/carvekit/ml/arch/__init__.py b/carvekit/ml/arch/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/ml/arch/__pycache__/__init__.cpython-38.pyc b/carvekit/ml/arch/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3131cb6c2fa953d5fd6f6ccb33969889d3369d3 Binary files /dev/null and b/carvekit/ml/arch/__pycache__/__init__.cpython-38.pyc differ diff --git a/carvekit/ml/arch/basnet/__init__.py b/carvekit/ml/arch/basnet/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/ml/arch/basnet/__pycache__/__init__.cpython-38.pyc b/carvekit/ml/arch/basnet/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52e2a61a73006869dae6844de7fced9944b40d95 Binary files /dev/null and b/carvekit/ml/arch/basnet/__pycache__/__init__.cpython-38.pyc differ diff --git a/carvekit/ml/arch/basnet/__pycache__/basnet.cpython-38.pyc b/carvekit/ml/arch/basnet/__pycache__/basnet.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6872fd25d9bd7e982e44ed6a397c9f7fd6f8f5b Binary files /dev/null and b/carvekit/ml/arch/basnet/__pycache__/basnet.cpython-38.pyc differ diff --git a/carvekit/ml/arch/basnet/basnet.py b/carvekit/ml/arch/basnet/basnet.py new file mode 100644 index 0000000000000000000000000000000000000000..e2ead6a7195374e19de182a63f26449092ec935e --- /dev/null +++ b/carvekit/ml/arch/basnet/basnet.py @@ -0,0 +1,478 @@ +""" +Source url: https://github.com/NathanUA/BASNet +Modified by Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: MIT License +""" +import torch +import torch.nn as nn +from torchvision import models + + +def conv3x3(in_planes, out_planes, stride=1): + """3x3 convolution with padding""" + return nn.Conv2d( + in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False + ) + + +class BasicBlock(nn.Module): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(BasicBlock, self).__init__() + self.conv1 = conv3x3(inplanes, planes, stride) + self.bn1 = nn.BatchNorm2d(planes) + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes) + self.bn2 = nn.BatchNorm2d(planes) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class BasicBlockDe(nn.Module): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(BasicBlockDe, self).__init__() + + self.convRes = conv3x3(inplanes, planes, stride) + self.bnRes = nn.BatchNorm2d(planes) + self.reluRes = nn.ReLU(inplace=True) + + self.conv1 = conv3x3(inplanes, planes, stride) + self.bn1 = nn.BatchNorm2d(planes) + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes) + self.bn2 = nn.BatchNorm2d(planes) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = self.convRes(x) + residual = self.bnRes(residual) + residual = self.reluRes(residual) + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(Bottleneck, self).__init__() + self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) + self.bn1 = nn.BatchNorm2d(planes) + self.conv2 = nn.Conv2d( + planes, planes, kernel_size=3, stride=stride, padding=1, bias=False + ) + self.bn2 = nn.BatchNorm2d(planes) + self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False) + self.bn3 = nn.BatchNorm2d(planes * 4) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class RefUnet(nn.Module): + def __init__(self, in_ch, inc_ch): + super(RefUnet, self).__init__() + + self.conv0 = nn.Conv2d(in_ch, inc_ch, 3, padding=1) + + self.conv1 = nn.Conv2d(inc_ch, 64, 3, padding=1) + self.bn1 = nn.BatchNorm2d(64) + self.relu1 = nn.ReLU(inplace=True) + + self.pool1 = nn.MaxPool2d(2, 2, ceil_mode=True) + + self.conv2 = nn.Conv2d(64, 64, 3, padding=1) + self.bn2 = nn.BatchNorm2d(64) + self.relu2 = nn.ReLU(inplace=True) + + self.pool2 = nn.MaxPool2d(2, 2, ceil_mode=True) + + self.conv3 = nn.Conv2d(64, 64, 3, padding=1) + self.bn3 = nn.BatchNorm2d(64) + self.relu3 = nn.ReLU(inplace=True) + + self.pool3 = nn.MaxPool2d(2, 2, ceil_mode=True) + + self.conv4 = nn.Conv2d(64, 64, 3, padding=1) + self.bn4 = nn.BatchNorm2d(64) + self.relu4 = nn.ReLU(inplace=True) + + self.pool4 = nn.MaxPool2d(2, 2, ceil_mode=True) + + self.conv5 = nn.Conv2d(64, 64, 3, padding=1) + self.bn5 = nn.BatchNorm2d(64) + self.relu5 = nn.ReLU(inplace=True) + + self.conv_d4 = nn.Conv2d(128, 64, 3, padding=1) + self.bn_d4 = nn.BatchNorm2d(64) + self.relu_d4 = nn.ReLU(inplace=True) + + self.conv_d3 = nn.Conv2d(128, 64, 3, padding=1) + self.bn_d3 = nn.BatchNorm2d(64) + self.relu_d3 = nn.ReLU(inplace=True) + + self.conv_d2 = nn.Conv2d(128, 64, 3, padding=1) + self.bn_d2 = nn.BatchNorm2d(64) + self.relu_d2 = nn.ReLU(inplace=True) + + self.conv_d1 = nn.Conv2d(128, 64, 3, padding=1) + self.bn_d1 = nn.BatchNorm2d(64) + self.relu_d1 = nn.ReLU(inplace=True) + + self.conv_d0 = nn.Conv2d(64, 1, 3, padding=1) + + self.upscore2 = nn.Upsample( + scale_factor=2, mode="bilinear", align_corners=False + ) + + def forward(self, x): + hx = x + hx = self.conv0(hx) + + hx1 = self.relu1(self.bn1(self.conv1(hx))) + hx = self.pool1(hx1) + + hx2 = self.relu2(self.bn2(self.conv2(hx))) + hx = self.pool2(hx2) + + hx3 = self.relu3(self.bn3(self.conv3(hx))) + hx = self.pool3(hx3) + + hx4 = self.relu4(self.bn4(self.conv4(hx))) + hx = self.pool4(hx4) + + hx5 = self.relu5(self.bn5(self.conv5(hx))) + + hx = self.upscore2(hx5) + + d4 = self.relu_d4(self.bn_d4(self.conv_d4(torch.cat((hx, hx4), 1)))) + hx = self.upscore2(d4) + + d3 = self.relu_d3(self.bn_d3(self.conv_d3(torch.cat((hx, hx3), 1)))) + hx = self.upscore2(d3) + + d2 = self.relu_d2(self.bn_d2(self.conv_d2(torch.cat((hx, hx2), 1)))) + hx = self.upscore2(d2) + + d1 = self.relu_d1(self.bn_d1(self.conv_d1(torch.cat((hx, hx1), 1)))) + + residual = self.conv_d0(d1) + + return x + residual + + +class BASNet(nn.Module): + def __init__(self, n_channels, n_classes): + super(BASNet, self).__init__() + + resnet = models.resnet34(pretrained=False) + + # -------------Encoder-------------- + + self.inconv = nn.Conv2d(n_channels, 64, 3, padding=1) + self.inbn = nn.BatchNorm2d(64) + self.inrelu = nn.ReLU(inplace=True) + + # stage 1 + self.encoder1 = resnet.layer1 # 224 + # stage 2 + self.encoder2 = resnet.layer2 # 112 + # stage 3 + self.encoder3 = resnet.layer3 # 56 + # stage 4 + self.encoder4 = resnet.layer4 # 28 + + self.pool4 = nn.MaxPool2d(2, 2, ceil_mode=True) + + # stage 5 + self.resb5_1 = BasicBlock(512, 512) + self.resb5_2 = BasicBlock(512, 512) + self.resb5_3 = BasicBlock(512, 512) # 14 + + self.pool5 = nn.MaxPool2d(2, 2, ceil_mode=True) + + # stage 6 + self.resb6_1 = BasicBlock(512, 512) + self.resb6_2 = BasicBlock(512, 512) + self.resb6_3 = BasicBlock(512, 512) # 7 + + # -------------Bridge-------------- + + # stage Bridge + self.convbg_1 = nn.Conv2d(512, 512, 3, dilation=2, padding=2) # 7 + self.bnbg_1 = nn.BatchNorm2d(512) + self.relubg_1 = nn.ReLU(inplace=True) + self.convbg_m = nn.Conv2d(512, 512, 3, dilation=2, padding=2) + self.bnbg_m = nn.BatchNorm2d(512) + self.relubg_m = nn.ReLU(inplace=True) + self.convbg_2 = nn.Conv2d(512, 512, 3, dilation=2, padding=2) + self.bnbg_2 = nn.BatchNorm2d(512) + self.relubg_2 = nn.ReLU(inplace=True) + + # -------------Decoder-------------- + + # stage 6d + self.conv6d_1 = nn.Conv2d(1024, 512, 3, padding=1) # 16 + self.bn6d_1 = nn.BatchNorm2d(512) + self.relu6d_1 = nn.ReLU(inplace=True) + + self.conv6d_m = nn.Conv2d(512, 512, 3, dilation=2, padding=2) + self.bn6d_m = nn.BatchNorm2d(512) + self.relu6d_m = nn.ReLU(inplace=True) + + self.conv6d_2 = nn.Conv2d(512, 512, 3, dilation=2, padding=2) + self.bn6d_2 = nn.BatchNorm2d(512) + self.relu6d_2 = nn.ReLU(inplace=True) + + # stage 5d + self.conv5d_1 = nn.Conv2d(1024, 512, 3, padding=1) # 16 + self.bn5d_1 = nn.BatchNorm2d(512) + self.relu5d_1 = nn.ReLU(inplace=True) + + self.conv5d_m = nn.Conv2d(512, 512, 3, padding=1) + self.bn5d_m = nn.BatchNorm2d(512) + self.relu5d_m = nn.ReLU(inplace=True) + + self.conv5d_2 = nn.Conv2d(512, 512, 3, padding=1) + self.bn5d_2 = nn.BatchNorm2d(512) + self.relu5d_2 = nn.ReLU(inplace=True) + + # stage 4d + self.conv4d_1 = nn.Conv2d(1024, 512, 3, padding=1) # 32 + self.bn4d_1 = nn.BatchNorm2d(512) + self.relu4d_1 = nn.ReLU(inplace=True) + + self.conv4d_m = nn.Conv2d(512, 512, 3, padding=1) + self.bn4d_m = nn.BatchNorm2d(512) + self.relu4d_m = nn.ReLU(inplace=True) + + self.conv4d_2 = nn.Conv2d(512, 256, 3, padding=1) + self.bn4d_2 = nn.BatchNorm2d(256) + self.relu4d_2 = nn.ReLU(inplace=True) + + # stage 3d + self.conv3d_1 = nn.Conv2d(512, 256, 3, padding=1) # 64 + self.bn3d_1 = nn.BatchNorm2d(256) + self.relu3d_1 = nn.ReLU(inplace=True) + + self.conv3d_m = nn.Conv2d(256, 256, 3, padding=1) + self.bn3d_m = nn.BatchNorm2d(256) + self.relu3d_m = nn.ReLU(inplace=True) + + self.conv3d_2 = nn.Conv2d(256, 128, 3, padding=1) + self.bn3d_2 = nn.BatchNorm2d(128) + self.relu3d_2 = nn.ReLU(inplace=True) + + # stage 2d + + self.conv2d_1 = nn.Conv2d(256, 128, 3, padding=1) # 128 + self.bn2d_1 = nn.BatchNorm2d(128) + self.relu2d_1 = nn.ReLU(inplace=True) + + self.conv2d_m = nn.Conv2d(128, 128, 3, padding=1) + self.bn2d_m = nn.BatchNorm2d(128) + self.relu2d_m = nn.ReLU(inplace=True) + + self.conv2d_2 = nn.Conv2d(128, 64, 3, padding=1) + self.bn2d_2 = nn.BatchNorm2d(64) + self.relu2d_2 = nn.ReLU(inplace=True) + + # stage 1d + self.conv1d_1 = nn.Conv2d(128, 64, 3, padding=1) # 256 + self.bn1d_1 = nn.BatchNorm2d(64) + self.relu1d_1 = nn.ReLU(inplace=True) + + self.conv1d_m = nn.Conv2d(64, 64, 3, padding=1) + self.bn1d_m = nn.BatchNorm2d(64) + self.relu1d_m = nn.ReLU(inplace=True) + + self.conv1d_2 = nn.Conv2d(64, 64, 3, padding=1) + self.bn1d_2 = nn.BatchNorm2d(64) + self.relu1d_2 = nn.ReLU(inplace=True) + + # -------------Bilinear Upsampling-------------- + self.upscore6 = nn.Upsample( + scale_factor=32, mode="bilinear", align_corners=False + ) + self.upscore5 = nn.Upsample( + scale_factor=16, mode="bilinear", align_corners=False + ) + self.upscore4 = nn.Upsample( + scale_factor=8, mode="bilinear", align_corners=False + ) + self.upscore3 = nn.Upsample( + scale_factor=4, mode="bilinear", align_corners=False + ) + self.upscore2 = nn.Upsample( + scale_factor=2, mode="bilinear", align_corners=False + ) + + # -------------Side Output-------------- + self.outconvb = nn.Conv2d(512, 1, 3, padding=1) + self.outconv6 = nn.Conv2d(512, 1, 3, padding=1) + self.outconv5 = nn.Conv2d(512, 1, 3, padding=1) + self.outconv4 = nn.Conv2d(256, 1, 3, padding=1) + self.outconv3 = nn.Conv2d(128, 1, 3, padding=1) + self.outconv2 = nn.Conv2d(64, 1, 3, padding=1) + self.outconv1 = nn.Conv2d(64, 1, 3, padding=1) + + # -------------Refine Module------------- + self.refunet = RefUnet(1, 64) + + def forward(self, x): + hx = x + + # -------------Encoder------------- + hx = self.inconv(hx) + hx = self.inbn(hx) + hx = self.inrelu(hx) + + h1 = self.encoder1(hx) # 256 + h2 = self.encoder2(h1) # 128 + h3 = self.encoder3(h2) # 64 + h4 = self.encoder4(h3) # 32 + + hx = self.pool4(h4) # 16 + + hx = self.resb5_1(hx) + hx = self.resb5_2(hx) + h5 = self.resb5_3(hx) + + hx = self.pool5(h5) # 8 + + hx = self.resb6_1(hx) + hx = self.resb6_2(hx) + h6 = self.resb6_3(hx) + + # -------------Bridge------------- + hx = self.relubg_1(self.bnbg_1(self.convbg_1(h6))) # 8 + hx = self.relubg_m(self.bnbg_m(self.convbg_m(hx))) + hbg = self.relubg_2(self.bnbg_2(self.convbg_2(hx))) + + # -------------Decoder------------- + + hx = self.relu6d_1(self.bn6d_1(self.conv6d_1(torch.cat((hbg, h6), 1)))) + hx = self.relu6d_m(self.bn6d_m(self.conv6d_m(hx))) + hd6 = self.relu6d_2(self.bn6d_2(self.conv6d_2(hx))) + + hx = self.upscore2(hd6) # 8 -> 16 + + hx = self.relu5d_1(self.bn5d_1(self.conv5d_1(torch.cat((hx, h5), 1)))) + hx = self.relu5d_m(self.bn5d_m(self.conv5d_m(hx))) + hd5 = self.relu5d_2(self.bn5d_2(self.conv5d_2(hx))) + + hx = self.upscore2(hd5) # 16 -> 32 + + hx = self.relu4d_1(self.bn4d_1(self.conv4d_1(torch.cat((hx, h4), 1)))) + hx = self.relu4d_m(self.bn4d_m(self.conv4d_m(hx))) + hd4 = self.relu4d_2(self.bn4d_2(self.conv4d_2(hx))) + + hx = self.upscore2(hd4) # 32 -> 64 + + hx = self.relu3d_1(self.bn3d_1(self.conv3d_1(torch.cat((hx, h3), 1)))) + hx = self.relu3d_m(self.bn3d_m(self.conv3d_m(hx))) + hd3 = self.relu3d_2(self.bn3d_2(self.conv3d_2(hx))) + + hx = self.upscore2(hd3) # 64 -> 128 + + hx = self.relu2d_1(self.bn2d_1(self.conv2d_1(torch.cat((hx, h2), 1)))) + hx = self.relu2d_m(self.bn2d_m(self.conv2d_m(hx))) + hd2 = self.relu2d_2(self.bn2d_2(self.conv2d_2(hx))) + + hx = self.upscore2(hd2) # 128 -> 256 + + hx = self.relu1d_1(self.bn1d_1(self.conv1d_1(torch.cat((hx, h1), 1)))) + hx = self.relu1d_m(self.bn1d_m(self.conv1d_m(hx))) + hd1 = self.relu1d_2(self.bn1d_2(self.conv1d_2(hx))) + + # -------------Side Output------------- + db = self.outconvb(hbg) + db = self.upscore6(db) # 8->256 + + d6 = self.outconv6(hd6) + d6 = self.upscore6(d6) # 8->256 + + d5 = self.outconv5(hd5) + d5 = self.upscore5(d5) # 16->256 + + d4 = self.outconv4(hd4) + d4 = self.upscore4(d4) # 32->256 + + d3 = self.outconv3(hd3) + d3 = self.upscore3(d3) # 64->256 + + d2 = self.outconv2(hd2) + d2 = self.upscore2(d2) # 128->256 + + d1 = self.outconv1(hd1) # 256 + + # -------------Refine Module------------- + dout = self.refunet(d1) # 256 + + return ( + torch.sigmoid(dout), + torch.sigmoid(d1), + torch.sigmoid(d2), + torch.sigmoid(d3), + torch.sigmoid(d4), + torch.sigmoid(d5), + torch.sigmoid(d6), + torch.sigmoid(db), + ) diff --git a/carvekit/ml/arch/fba_matting/__init__.py b/carvekit/ml/arch/fba_matting/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/ml/arch/fba_matting/__pycache__/__init__.cpython-38.pyc b/carvekit/ml/arch/fba_matting/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8ce27b5712febd1032baae5a13ac3f20746412e7 Binary files /dev/null and b/carvekit/ml/arch/fba_matting/__pycache__/__init__.cpython-38.pyc differ diff --git a/carvekit/ml/arch/fba_matting/__pycache__/layers_WS.cpython-38.pyc b/carvekit/ml/arch/fba_matting/__pycache__/layers_WS.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fdc02bb129a24de80bf4a47f234e670335051b8e Binary files /dev/null and b/carvekit/ml/arch/fba_matting/__pycache__/layers_WS.cpython-38.pyc differ diff --git a/carvekit/ml/arch/fba_matting/__pycache__/models.cpython-38.pyc b/carvekit/ml/arch/fba_matting/__pycache__/models.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6eaa9c735de00e173cb4daaeb9a8991b1eb51ae3 Binary files /dev/null and b/carvekit/ml/arch/fba_matting/__pycache__/models.cpython-38.pyc differ diff --git a/carvekit/ml/arch/fba_matting/__pycache__/resnet_GN_WS.cpython-38.pyc b/carvekit/ml/arch/fba_matting/__pycache__/resnet_GN_WS.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93b294481d47e8928560f04aeeaa72a0d33fc08d Binary files /dev/null and b/carvekit/ml/arch/fba_matting/__pycache__/resnet_GN_WS.cpython-38.pyc differ diff --git a/carvekit/ml/arch/fba_matting/__pycache__/resnet_bn.cpython-38.pyc b/carvekit/ml/arch/fba_matting/__pycache__/resnet_bn.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55740358ae37cf3669d7914a07a88c806e1da6ee Binary files /dev/null and b/carvekit/ml/arch/fba_matting/__pycache__/resnet_bn.cpython-38.pyc differ diff --git a/carvekit/ml/arch/fba_matting/__pycache__/transforms.cpython-38.pyc b/carvekit/ml/arch/fba_matting/__pycache__/transforms.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6356dcfd4e7e761ea0f93ff623a9478f46ad405d Binary files /dev/null and b/carvekit/ml/arch/fba_matting/__pycache__/transforms.cpython-38.pyc differ diff --git a/carvekit/ml/arch/fba_matting/layers_WS.py b/carvekit/ml/arch/fba_matting/layers_WS.py new file mode 100644 index 0000000000000000000000000000000000000000..51085989c4f090d4dc5f599be3c550d16ec0b2e7 --- /dev/null +++ b/carvekit/ml/arch/fba_matting/layers_WS.py @@ -0,0 +1,57 @@ +""" +Modified by Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +Source url: https://github.com/MarcoForte/FBA_Matting +License: MIT License +""" +import torch +import torch.nn as nn +from torch.nn import functional as F + + +class Conv2d(nn.Conv2d): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + groups=1, + bias=True, + ): + super(Conv2d, self).__init__( + in_channels, + out_channels, + kernel_size, + stride, + padding, + dilation, + groups, + bias, + ) + + def forward(self, x): + # return super(Conv2d, self).forward(x) + weight = self.weight + weight_mean = ( + weight.mean(dim=1, keepdim=True) + .mean(dim=2, keepdim=True) + .mean(dim=3, keepdim=True) + ) + weight = weight - weight_mean + # std = (weight).view(weight.size(0), -1).std(dim=1).view(-1, 1, 1, 1) + 1e-5 + std = ( + torch.sqrt(torch.var(weight.view(weight.size(0), -1), dim=1) + 1e-12).view( + -1, 1, 1, 1 + ) + + 1e-5 + ) + weight = weight / std.expand_as(weight) + return F.conv2d( + x, weight, self.bias, self.stride, self.padding, self.dilation, self.groups + ) + + +def BatchNorm2d(num_features): + return nn.GroupNorm(num_channels=num_features, num_groups=32) diff --git a/carvekit/ml/arch/fba_matting/models.py b/carvekit/ml/arch/fba_matting/models.py new file mode 100644 index 0000000000000000000000000000000000000000..dc2b0a861e4a5a9d4f77ae69d3b327b59ec628eb --- /dev/null +++ b/carvekit/ml/arch/fba_matting/models.py @@ -0,0 +1,341 @@ +""" +Modified by Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +Source url: https://github.com/MarcoForte/FBA_Matting +License: MIT License +""" +import torch +import torch.nn as nn +import carvekit.ml.arch.fba_matting.resnet_GN_WS as resnet_GN_WS +import carvekit.ml.arch.fba_matting.layers_WS as L +import carvekit.ml.arch.fba_matting.resnet_bn as resnet_bn +from functools import partial + + +class FBA(nn.Module): + def __init__(self, encoder: str): + super(FBA, self).__init__() + self.encoder = build_encoder(arch=encoder) + self.decoder = fba_decoder(batch_norm=True if "BN" in encoder else False) + + def forward(self, image, two_chan_trimap, image_n, trimap_transformed): + resnet_input = torch.cat((image_n, trimap_transformed, two_chan_trimap), 1) + conv_out, indices = self.encoder(resnet_input, return_feature_maps=True) + return self.decoder(conv_out, image, indices, two_chan_trimap) + + +class ResnetDilatedBN(nn.Module): + def __init__(self, orig_resnet, dilate_scale=8): + super(ResnetDilatedBN, self).__init__() + + if dilate_scale == 8: + orig_resnet.layer3.apply(partial(self._nostride_dilate, dilate=2)) + orig_resnet.layer4.apply(partial(self._nostride_dilate, dilate=4)) + elif dilate_scale == 16: + orig_resnet.layer4.apply(partial(self._nostride_dilate, dilate=2)) + + # take pretrained resnet, except AvgPool and FC + self.conv1 = orig_resnet.conv1 + self.bn1 = orig_resnet.bn1 + self.relu1 = orig_resnet.relu1 + self.conv2 = orig_resnet.conv2 + self.bn2 = orig_resnet.bn2 + self.relu2 = orig_resnet.relu2 + self.conv3 = orig_resnet.conv3 + self.bn3 = orig_resnet.bn3 + self.relu3 = orig_resnet.relu3 + self.maxpool = orig_resnet.maxpool + self.layer1 = orig_resnet.layer1 + self.layer2 = orig_resnet.layer2 + self.layer3 = orig_resnet.layer3 + self.layer4 = orig_resnet.layer4 + + def _nostride_dilate(self, m, dilate): + classname = m.__class__.__name__ + if classname.find("Conv") != -1: + # the convolution with stride + if m.stride == (2, 2): + m.stride = (1, 1) + if m.kernel_size == (3, 3): + m.dilation = (dilate // 2, dilate // 2) + m.padding = (dilate // 2, dilate // 2) + # other convoluions + else: + if m.kernel_size == (3, 3): + m.dilation = (dilate, dilate) + m.padding = (dilate, dilate) + + def forward(self, x, return_feature_maps=False): + conv_out = [x] + x = self.relu1(self.bn1(self.conv1(x))) + x = self.relu2(self.bn2(self.conv2(x))) + x = self.relu3(self.bn3(self.conv3(x))) + conv_out.append(x) + x, indices = self.maxpool(x) + x = self.layer1(x) + conv_out.append(x) + x = self.layer2(x) + conv_out.append(x) + x = self.layer3(x) + conv_out.append(x) + x = self.layer4(x) + conv_out.append(x) + + if return_feature_maps: + return conv_out, indices + return [x] + + +class Resnet(nn.Module): + def __init__(self, orig_resnet): + super(Resnet, self).__init__() + + # take pretrained resnet, except AvgPool and FC + self.conv1 = orig_resnet.conv1 + self.bn1 = orig_resnet.bn1 + self.relu1 = orig_resnet.relu1 + self.conv2 = orig_resnet.conv2 + self.bn2 = orig_resnet.bn2 + self.relu2 = orig_resnet.relu2 + self.conv3 = orig_resnet.conv3 + self.bn3 = orig_resnet.bn3 + self.relu3 = orig_resnet.relu3 + self.maxpool = orig_resnet.maxpool + self.layer1 = orig_resnet.layer1 + self.layer2 = orig_resnet.layer2 + self.layer3 = orig_resnet.layer3 + self.layer4 = orig_resnet.layer4 + + def forward(self, x, return_feature_maps=False): + conv_out = [] + + x = self.relu1(self.bn1(self.conv1(x))) + x = self.relu2(self.bn2(self.conv2(x))) + x = self.relu3(self.bn3(self.conv3(x))) + conv_out.append(x) + x, indices = self.maxpool(x) + + x = self.layer1(x) + conv_out.append(x) + x = self.layer2(x) + conv_out.append(x) + x = self.layer3(x) + conv_out.append(x) + x = self.layer4(x) + conv_out.append(x) + + if return_feature_maps: + return conv_out + return [x] + + +class ResnetDilated(nn.Module): + def __init__(self, orig_resnet, dilate_scale=8): + super(ResnetDilated, self).__init__() + + if dilate_scale == 8: + orig_resnet.layer3.apply(partial(self._nostride_dilate, dilate=2)) + orig_resnet.layer4.apply(partial(self._nostride_dilate, dilate=4)) + elif dilate_scale == 16: + orig_resnet.layer4.apply(partial(self._nostride_dilate, dilate=2)) + + # take pretrained resnet, except AvgPool and FC + self.conv1 = orig_resnet.conv1 + self.bn1 = orig_resnet.bn1 + self.relu = orig_resnet.relu + self.maxpool = orig_resnet.maxpool + self.layer1 = orig_resnet.layer1 + self.layer2 = orig_resnet.layer2 + self.layer3 = orig_resnet.layer3 + self.layer4 = orig_resnet.layer4 + + def _nostride_dilate(self, m, dilate): + classname = m.__class__.__name__ + if classname.find("Conv") != -1: + # the convolution with stride + if m.stride == (2, 2): + m.stride = (1, 1) + if m.kernel_size == (3, 3): + m.dilation = (dilate // 2, dilate // 2) + m.padding = (dilate // 2, dilate // 2) + # other convoluions + else: + if m.kernel_size == (3, 3): + m.dilation = (dilate, dilate) + m.padding = (dilate, dilate) + + def forward(self, x, return_feature_maps=False): + conv_out = [x] + x = self.relu(self.bn1(self.conv1(x))) + conv_out.append(x) + x, indices = self.maxpool(x) + x = self.layer1(x) + conv_out.append(x) + x = self.layer2(x) + conv_out.append(x) + x = self.layer3(x) + conv_out.append(x) + x = self.layer4(x) + conv_out.append(x) + + if return_feature_maps: + return conv_out, indices + return [x] + + +def norm(dim, bn=False): + if bn is False: + return nn.GroupNorm(32, dim) + else: + return nn.BatchNorm2d(dim) + + +def fba_fusion(alpha, img, F, B): + F = alpha * img + (1 - alpha**2) * F - alpha * (1 - alpha) * B + B = (1 - alpha) * img + (2 * alpha - alpha**2) * B - alpha * (1 - alpha) * F + + F = torch.clamp(F, 0, 1) + B = torch.clamp(B, 0, 1) + la = 0.1 + alpha = (alpha * la + torch.sum((img - B) * (F - B), 1, keepdim=True)) / ( + torch.sum((F - B) * (F - B), 1, keepdim=True) + la + ) + alpha = torch.clamp(alpha, 0, 1) + return alpha, F, B + + +class fba_decoder(nn.Module): + def __init__(self, batch_norm=False): + super(fba_decoder, self).__init__() + pool_scales = (1, 2, 3, 6) + self.batch_norm = batch_norm + + self.ppm = [] + + for scale in pool_scales: + self.ppm.append( + nn.Sequential( + nn.AdaptiveAvgPool2d(scale), + L.Conv2d(2048, 256, kernel_size=1, bias=True), + norm(256, self.batch_norm), + nn.LeakyReLU(), + ) + ) + self.ppm = nn.ModuleList(self.ppm) + + self.conv_up1 = nn.Sequential( + L.Conv2d( + 2048 + len(pool_scales) * 256, 256, kernel_size=3, padding=1, bias=True + ), + norm(256, self.batch_norm), + nn.LeakyReLU(), + L.Conv2d(256, 256, kernel_size=3, padding=1), + norm(256, self.batch_norm), + nn.LeakyReLU(), + ) + + self.conv_up2 = nn.Sequential( + L.Conv2d(256 + 256, 256, kernel_size=3, padding=1, bias=True), + norm(256, self.batch_norm), + nn.LeakyReLU(), + ) + if self.batch_norm: + d_up3 = 128 + else: + d_up3 = 64 + self.conv_up3 = nn.Sequential( + L.Conv2d(256 + d_up3, 64, kernel_size=3, padding=1, bias=True), + norm(64, self.batch_norm), + nn.LeakyReLU(), + ) + + self.unpool = nn.MaxUnpool2d(2, stride=2) + + self.conv_up4 = nn.Sequential( + nn.Conv2d(64 + 3 + 3 + 2, 32, kernel_size=3, padding=1, bias=True), + nn.LeakyReLU(), + nn.Conv2d(32, 16, kernel_size=3, padding=1, bias=True), + nn.LeakyReLU(), + nn.Conv2d(16, 7, kernel_size=1, padding=0, bias=True), + ) + + def forward(self, conv_out, img, indices, two_chan_trimap): + conv5 = conv_out[-1] + + input_size = conv5.size() + ppm_out = [conv5] + for pool_scale in self.ppm: + ppm_out.append( + nn.functional.interpolate( + pool_scale(conv5), + (input_size[2], input_size[3]), + mode="bilinear", + align_corners=False, + ) + ) + ppm_out = torch.cat(ppm_out, 1) + x = self.conv_up1(ppm_out) + + x = torch.nn.functional.interpolate( + x, scale_factor=2, mode="bilinear", align_corners=False + ) + + x = torch.cat((x, conv_out[-4]), 1) + + x = self.conv_up2(x) + x = torch.nn.functional.interpolate( + x, scale_factor=2, mode="bilinear", align_corners=False + ) + + x = torch.cat((x, conv_out[-5]), 1) + x = self.conv_up3(x) + + x = torch.nn.functional.interpolate( + x, scale_factor=2, mode="bilinear", align_corners=False + ) + x = torch.cat((x, conv_out[-6][:, :3], img, two_chan_trimap), 1) + + output = self.conv_up4(x) + + alpha = torch.clamp(output[:, 0][:, None], 0, 1) + F = torch.sigmoid(output[:, 1:4]) + B = torch.sigmoid(output[:, 4:7]) + + # FBA Fusion + alpha, F, B = fba_fusion(alpha, img, F, B) + + output = torch.cat((alpha, F, B), 1) + + return output + + +def build_encoder(arch="resnet50_GN"): + if arch == "resnet50_GN_WS": + orig_resnet = resnet_GN_WS.__dict__["l_resnet50"]() + net_encoder = ResnetDilated(orig_resnet, dilate_scale=8) + elif arch == "resnet50_BN": + orig_resnet = resnet_bn.__dict__["l_resnet50"]() + net_encoder = ResnetDilatedBN(orig_resnet, dilate_scale=8) + + else: + raise ValueError("Architecture undefined!") + + num_channels = 3 + 6 + 2 + + if num_channels > 3: + net_encoder_sd = net_encoder.state_dict() + conv1_weights = net_encoder_sd["conv1.weight"] + + c_out, c_in, h, w = conv1_weights.size() + conv1_mod = torch.zeros(c_out, num_channels, h, w) + conv1_mod[:, :3, :, :] = conv1_weights + + conv1 = net_encoder.conv1 + conv1.in_channels = num_channels + conv1.weight = torch.nn.Parameter(conv1_mod) + + net_encoder.conv1 = conv1 + + net_encoder_sd["conv1.weight"] = conv1_mod + + net_encoder.load_state_dict(net_encoder_sd) + return net_encoder diff --git a/carvekit/ml/arch/fba_matting/resnet_GN_WS.py b/carvekit/ml/arch/fba_matting/resnet_GN_WS.py new file mode 100644 index 0000000000000000000000000000000000000000..a730ed6ea07f4953a4b6fcf7f9d92f3da71ed783 --- /dev/null +++ b/carvekit/ml/arch/fba_matting/resnet_GN_WS.py @@ -0,0 +1,151 @@ +""" +Modified by Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +Source url: https://github.com/MarcoForte/FBA_Matting +License: MIT License +""" +import torch.nn as nn +import carvekit.ml.arch.fba_matting.layers_WS as L + +__all__ = ["ResNet", "l_resnet50"] + + +def conv3x3(in_planes, out_planes, stride=1): + """3x3 convolution with padding""" + return L.Conv2d( + in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False + ) + + +def conv1x1(in_planes, out_planes, stride=1): + """1x1 convolution""" + return L.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False) + + +class BasicBlock(nn.Module): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(BasicBlock, self).__init__() + self.conv1 = conv3x3(inplanes, planes, stride) + self.bn1 = L.BatchNorm2d(planes) + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes) + self.bn2 = L.BatchNorm2d(planes) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + identity = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(Bottleneck, self).__init__() + self.conv1 = conv1x1(inplanes, planes) + self.bn1 = L.BatchNorm2d(planes) + self.conv2 = conv3x3(planes, planes, stride) + self.bn2 = L.BatchNorm2d(planes) + self.conv3 = conv1x1(planes, planes * self.expansion) + self.bn3 = L.BatchNorm2d(planes * self.expansion) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + identity = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + out = self.relu(out) + + return out + + +class ResNet(nn.Module): + def __init__(self, block, layers, num_classes=1000): + super(ResNet, self).__init__() + self.inplanes = 64 + self.conv1 = L.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False) + self.bn1 = L.BatchNorm2d(64) + self.relu = nn.ReLU(inplace=True) + self.maxpool = nn.MaxPool2d( + kernel_size=3, stride=2, padding=1, return_indices=True + ) + self.layer1 = self._make_layer(block, 64, layers[0]) + self.layer2 = self._make_layer(block, 128, layers[1], stride=2) + self.layer3 = self._make_layer(block, 256, layers[2], stride=2) + self.layer4 = self._make_layer(block, 512, layers[3], stride=2) + self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) + self.fc = nn.Linear(512 * block.expansion, num_classes) + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + conv1x1(self.inplanes, planes * block.expansion, stride), + L.BatchNorm2d(planes * block.expansion), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for _ in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + + x = self.avgpool(x) + x = x.view(x.size(0), -1) + x = self.fc(x) + + return x + + +def l_resnet50(pretrained=False, **kwargs): + """Constructs a ResNet-50 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs) + return model diff --git a/carvekit/ml/arch/fba_matting/resnet_bn.py b/carvekit/ml/arch/fba_matting/resnet_bn.py new file mode 100644 index 0000000000000000000000000000000000000000..9662ca857a20b4d44c0ceb09f968ae3947956f53 --- /dev/null +++ b/carvekit/ml/arch/fba_matting/resnet_bn.py @@ -0,0 +1,169 @@ +""" +Modified by Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +Source url: https://github.com/MarcoForte/FBA_Matting +License: MIT License +""" +import torch.nn as nn +import math +from torch.nn import BatchNorm2d + +__all__ = ["ResNet"] + + +def conv3x3(in_planes, out_planes, stride=1): + "3x3 convolution with padding" + return nn.Conv2d( + in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False + ) + + +class BasicBlock(nn.Module): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(BasicBlock, self).__init__() + self.conv1 = conv3x3(inplanes, planes, stride) + self.bn1 = BatchNorm2d(planes) + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes) + self.bn2 = BatchNorm2d(planes) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(Bottleneck, self).__init__() + self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) + self.bn1 = BatchNorm2d(planes) + self.conv2 = nn.Conv2d( + planes, planes, kernel_size=3, stride=stride, padding=1, bias=False + ) + self.bn2 = BatchNorm2d(planes, momentum=0.01) + self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False) + self.bn3 = BatchNorm2d(planes * 4) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class ResNet(nn.Module): + def __init__(self, block, layers, num_classes=1000): + self.inplanes = 128 + super(ResNet, self).__init__() + self.conv1 = conv3x3(3, 64, stride=2) + self.bn1 = BatchNorm2d(64) + self.relu1 = nn.ReLU(inplace=True) + self.conv2 = conv3x3(64, 64) + self.bn2 = BatchNorm2d(64) + self.relu2 = nn.ReLU(inplace=True) + self.conv3 = conv3x3(64, 128) + self.bn3 = BatchNorm2d(128) + self.relu3 = nn.ReLU(inplace=True) + self.maxpool = nn.MaxPool2d( + kernel_size=3, stride=2, padding=1, return_indices=True + ) + + self.layer1 = self._make_layer(block, 64, layers[0]) + self.layer2 = self._make_layer(block, 128, layers[1], stride=2) + self.layer3 = self._make_layer(block, 256, layers[2], stride=2) + self.layer4 = self._make_layer(block, 512, layers[3], stride=2) + self.avgpool = nn.AvgPool2d(7, stride=1) + self.fc = nn.Linear(512 * block.expansion, num_classes) + + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2.0 / n)) + elif isinstance(m, BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + bias=False, + ), + BatchNorm2d(planes * block.expansion), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def forward(self, x): + x = self.relu1(self.bn1(self.conv1(x))) + x = self.relu2(self.bn2(self.conv2(x))) + x = self.relu3(self.bn3(self.conv3(x))) + x, indices = self.maxpool(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + + x = self.avgpool(x) + x = x.view(x.size(0), -1) + x = self.fc(x) + return x + + +def l_resnet50(): + """Constructs a ResNet-50 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 4, 6, 3]) + return model diff --git a/carvekit/ml/arch/fba_matting/transforms.py b/carvekit/ml/arch/fba_matting/transforms.py new file mode 100644 index 0000000000000000000000000000000000000000..20251ef28bc8a9c2b599ee7365a5394ae568720e --- /dev/null +++ b/carvekit/ml/arch/fba_matting/transforms.py @@ -0,0 +1,45 @@ +""" +Modified by Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +Source url: https://github.com/MarcoForte/FBA_Matting +License: MIT License +""" +import cv2 +import numpy as np + +group_norm_std = [0.229, 0.224, 0.225] +group_norm_mean = [0.485, 0.456, 0.406] + + +def dt(a): + return cv2.distanceTransform((a * 255).astype(np.uint8), cv2.DIST_L2, 0) + + +def trimap_transform(trimap): + h, w = trimap.shape[0], trimap.shape[1] + + clicks = np.zeros((h, w, 6)) + for k in range(2): + if np.count_nonzero(trimap[:, :, k]) > 0: + dt_mask = -dt(1 - trimap[:, :, k]) ** 2 + L = 320 + clicks[:, :, 3 * k] = np.exp(dt_mask / (2 * ((0.02 * L) ** 2))) + clicks[:, :, 3 * k + 1] = np.exp(dt_mask / (2 * ((0.08 * L) ** 2))) + clicks[:, :, 3 * k + 2] = np.exp(dt_mask / (2 * ((0.16 * L) ** 2))) + + return clicks + + +def groupnorm_normalise_image(img, format="nhwc"): + """ + Accept rgb in range 0,1 + """ + if format == "nhwc": + for i in range(3): + img[..., i] = (img[..., i] - group_norm_mean[i]) / group_norm_std[i] + else: + for i in range(3): + img[..., i, :, :] = ( + img[..., i, :, :] - group_norm_mean[i] + ) / group_norm_std[i] + + return img diff --git a/carvekit/ml/arch/tracerb7/__init__.py b/carvekit/ml/arch/tracerb7/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/ml/arch/tracerb7/__pycache__/__init__.cpython-38.pyc b/carvekit/ml/arch/tracerb7/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..062ae69bf8f546791440440f53e1ac77d879287b Binary files /dev/null and b/carvekit/ml/arch/tracerb7/__pycache__/__init__.cpython-38.pyc differ diff --git a/carvekit/ml/arch/tracerb7/__pycache__/att_modules.cpython-38.pyc b/carvekit/ml/arch/tracerb7/__pycache__/att_modules.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34afb6d6ccb3b804cccfb9c8fccbba65cea5d918 Binary files /dev/null and b/carvekit/ml/arch/tracerb7/__pycache__/att_modules.cpython-38.pyc differ diff --git a/carvekit/ml/arch/tracerb7/__pycache__/conv_modules.cpython-38.pyc b/carvekit/ml/arch/tracerb7/__pycache__/conv_modules.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ce79783287ee79b7a89975ceab60f54793952b4 Binary files /dev/null and b/carvekit/ml/arch/tracerb7/__pycache__/conv_modules.cpython-38.pyc differ diff --git a/carvekit/ml/arch/tracerb7/__pycache__/effi_utils.cpython-38.pyc b/carvekit/ml/arch/tracerb7/__pycache__/effi_utils.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a018f342ed827a7467a7fd6d2216931ade9b1601 Binary files /dev/null and b/carvekit/ml/arch/tracerb7/__pycache__/effi_utils.cpython-38.pyc differ diff --git a/carvekit/ml/arch/tracerb7/__pycache__/efficientnet.cpython-38.pyc b/carvekit/ml/arch/tracerb7/__pycache__/efficientnet.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..380ad62f14bb36a1951806064f5b0f2736453214 Binary files /dev/null and b/carvekit/ml/arch/tracerb7/__pycache__/efficientnet.cpython-38.pyc differ diff --git a/carvekit/ml/arch/tracerb7/__pycache__/tracer.cpython-38.pyc b/carvekit/ml/arch/tracerb7/__pycache__/tracer.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df7773d9efeb4e8569305abfdf0d1f90075f3109 Binary files /dev/null and b/carvekit/ml/arch/tracerb7/__pycache__/tracer.cpython-38.pyc differ diff --git a/carvekit/ml/arch/tracerb7/att_modules.py b/carvekit/ml/arch/tracerb7/att_modules.py new file mode 100644 index 0000000000000000000000000000000000000000..07e47403907753c2a873d35fa5a9336740f5d91b --- /dev/null +++ b/carvekit/ml/arch/tracerb7/att_modules.py @@ -0,0 +1,290 @@ +""" +Source url: https://github.com/Karel911/TRACER +Author: Min Seok Lee and Wooseok Shin +License: Apache License 2.0 +""" +import torch +import torch.nn as nn +import torch.nn.functional as F + +from carvekit.ml.arch.tracerb7.conv_modules import BasicConv2d, DWConv, DWSConv + + +class RFB_Block(nn.Module): + def __init__(self, in_channel, out_channel): + super(RFB_Block, self).__init__() + self.relu = nn.ReLU(True) + self.branch0 = nn.Sequential( + BasicConv2d(in_channel, out_channel, 1), + ) + self.branch1 = nn.Sequential( + BasicConv2d(in_channel, out_channel, 1), + BasicConv2d(out_channel, out_channel, kernel_size=(1, 3), padding=(0, 1)), + BasicConv2d(out_channel, out_channel, kernel_size=(3, 1), padding=(1, 0)), + BasicConv2d(out_channel, out_channel, 3, padding=3, dilation=3), + ) + self.branch2 = nn.Sequential( + BasicConv2d(in_channel, out_channel, 1), + BasicConv2d(out_channel, out_channel, kernel_size=(1, 5), padding=(0, 2)), + BasicConv2d(out_channel, out_channel, kernel_size=(5, 1), padding=(2, 0)), + BasicConv2d(out_channel, out_channel, 3, padding=5, dilation=5), + ) + self.branch3 = nn.Sequential( + BasicConv2d(in_channel, out_channel, 1), + BasicConv2d(out_channel, out_channel, kernel_size=(1, 7), padding=(0, 3)), + BasicConv2d(out_channel, out_channel, kernel_size=(7, 1), padding=(3, 0)), + BasicConv2d(out_channel, out_channel, 3, padding=7, dilation=7), + ) + self.conv_cat = BasicConv2d(4 * out_channel, out_channel, 3, padding=1) + self.conv_res = BasicConv2d(in_channel, out_channel, 1) + + def forward(self, x): + x0 = self.branch0(x) + x1 = self.branch1(x) + x2 = self.branch2(x) + x3 = self.branch3(x) + x_cat = torch.cat((x0, x1, x2, x3), 1) + x_cat = self.conv_cat(x_cat) + + x = self.relu(x_cat + self.conv_res(x)) + return x + + +class GlobalAvgPool(nn.Module): + def __init__(self, flatten=False): + super(GlobalAvgPool, self).__init__() + self.flatten = flatten + + def forward(self, x): + if self.flatten: + in_size = x.size() + return x.view((in_size[0], in_size[1], -1)).mean(dim=2) + else: + return ( + x.view(x.size(0), x.size(1), -1) + .mean(-1) + .view(x.size(0), x.size(1), 1, 1) + ) + + +class UnionAttentionModule(nn.Module): + def __init__(self, n_channels, only_channel_tracing=False): + super(UnionAttentionModule, self).__init__() + self.GAP = GlobalAvgPool() + self.confidence_ratio = 0.1 + self.bn = nn.BatchNorm2d(n_channels) + self.norm = nn.Sequential( + nn.BatchNorm2d(n_channels), nn.Dropout3d(self.confidence_ratio) + ) + self.channel_q = nn.Conv2d( + in_channels=n_channels, + out_channels=n_channels, + kernel_size=1, + stride=1, + padding=0, + bias=False, + ) + self.channel_k = nn.Conv2d( + in_channels=n_channels, + out_channels=n_channels, + kernel_size=1, + stride=1, + padding=0, + bias=False, + ) + self.channel_v = nn.Conv2d( + in_channels=n_channels, + out_channels=n_channels, + kernel_size=1, + stride=1, + padding=0, + bias=False, + ) + + self.fc = nn.Conv2d( + in_channels=n_channels, + out_channels=n_channels, + kernel_size=1, + stride=1, + padding=0, + bias=False, + ) + + if only_channel_tracing is False: + self.spatial_q = nn.Conv2d( + in_channels=n_channels, + out_channels=1, + kernel_size=1, + stride=1, + padding=0, + bias=False, + ) + self.spatial_k = nn.Conv2d( + in_channels=n_channels, + out_channels=1, + kernel_size=1, + stride=1, + padding=0, + bias=False, + ) + self.spatial_v = nn.Conv2d( + in_channels=n_channels, + out_channels=1, + kernel_size=1, + stride=1, + padding=0, + bias=False, + ) + self.sigmoid = nn.Sigmoid() + + def masking(self, x, mask): + mask = mask.squeeze(3).squeeze(2) + threshold = torch.quantile( + mask.float(), self.confidence_ratio, dim=-1, keepdim=True + ) + mask[mask <= threshold] = 0.0 + mask = mask.unsqueeze(2).unsqueeze(3) + mask = mask.expand(-1, x.shape[1], x.shape[2], x.shape[3]).contiguous() + masked_x = x * mask + + return masked_x + + def Channel_Tracer(self, x): + avg_pool = self.GAP(x) + x_norm = self.norm(avg_pool) + + q = self.channel_q(x_norm).squeeze(-1) + k = self.channel_k(x_norm).squeeze(-1) + v = self.channel_v(x_norm).squeeze(-1) + + # softmax(Q*K^T) + QK_T = torch.matmul(q, k.transpose(1, 2)) + alpha = F.softmax(QK_T, dim=-1) + + # a*v + att = torch.matmul(alpha, v).unsqueeze(-1) + att = self.fc(att) + att = self.sigmoid(att) + + output = (x * att) + x + alpha_mask = att.clone() + + return output, alpha_mask + + def forward(self, x): + X_c, alpha_mask = self.Channel_Tracer(x) + X_c = self.bn(X_c) + x_drop = self.masking(X_c, alpha_mask) + + q = self.spatial_q(x_drop).squeeze(1) + k = self.spatial_k(x_drop).squeeze(1) + v = self.spatial_v(x_drop).squeeze(1) + + # softmax(Q*K^T) + QK_T = torch.matmul(q, k.transpose(1, 2)) + alpha = F.softmax(QK_T, dim=-1) + + output = torch.matmul(alpha, v).unsqueeze(1) + v.unsqueeze(1) + + return output + + +class aggregation(nn.Module): + def __init__(self, channel): + super(aggregation, self).__init__() + self.relu = nn.ReLU(True) + + self.upsample = nn.Upsample(scale_factor=2, mode="bilinear", align_corners=True) + self.conv_upsample1 = BasicConv2d(channel[2], channel[1], 3, padding=1) + self.conv_upsample2 = BasicConv2d(channel[2], channel[0], 3, padding=1) + self.conv_upsample3 = BasicConv2d(channel[1], channel[0], 3, padding=1) + self.conv_upsample4 = BasicConv2d(channel[2], channel[2], 3, padding=1) + self.conv_upsample5 = BasicConv2d( + channel[2] + channel[1], channel[2] + channel[1], 3, padding=1 + ) + + self.conv_concat2 = BasicConv2d( + (channel[2] + channel[1]), (channel[2] + channel[1]), 3, padding=1 + ) + self.conv_concat3 = BasicConv2d( + (channel[0] + channel[1] + channel[2]), + (channel[0] + channel[1] + channel[2]), + 3, + padding=1, + ) + + self.UAM = UnionAttentionModule(channel[0] + channel[1] + channel[2]) + + def forward(self, e4, e3, e2): + e4_1 = e4 + e3_1 = self.conv_upsample1(self.upsample(e4)) * e3 + e2_1 = ( + self.conv_upsample2(self.upsample(self.upsample(e4))) + * self.conv_upsample3(self.upsample(e3)) + * e2 + ) + + e3_2 = torch.cat((e3_1, self.conv_upsample4(self.upsample(e4_1))), 1) + e3_2 = self.conv_concat2(e3_2) + + e2_2 = torch.cat((e2_1, self.conv_upsample5(self.upsample(e3_2))), 1) + x = self.conv_concat3(e2_2) + + output = self.UAM(x) + + return output + + +class ObjectAttention(nn.Module): + def __init__(self, channel, kernel_size): + super(ObjectAttention, self).__init__() + self.channel = channel + self.DWSConv = DWSConv( + channel, channel // 2, kernel=kernel_size, padding=1, kernels_per_layer=1 + ) + self.DWConv1 = nn.Sequential( + DWConv(channel // 2, channel // 2, kernel=1, padding=0, dilation=1), + BasicConv2d(channel // 2, channel // 8, 1), + ) + self.DWConv2 = nn.Sequential( + DWConv(channel // 2, channel // 2, kernel=3, padding=1, dilation=1), + BasicConv2d(channel // 2, channel // 8, 1), + ) + self.DWConv3 = nn.Sequential( + DWConv(channel // 2, channel // 2, kernel=3, padding=3, dilation=3), + BasicConv2d(channel // 2, channel // 8, 1), + ) + self.DWConv4 = nn.Sequential( + DWConv(channel // 2, channel // 2, kernel=3, padding=5, dilation=5), + BasicConv2d(channel // 2, channel // 8, 1), + ) + self.conv1 = BasicConv2d(channel // 2, 1, 1) + + def forward(self, decoder_map, encoder_map): + """ + Args: + decoder_map: decoder representation (B, 1, H, W). + encoder_map: encoder block output (B, C, H, W). + Returns: + decoder representation: (B, 1, H, W) + """ + mask_bg = -1 * torch.sigmoid(decoder_map) + 1 # Sigmoid & Reverse + mask_ob = torch.sigmoid(decoder_map) # object attention + x = mask_ob.expand(-1, self.channel, -1, -1).mul(encoder_map) + + edge = mask_bg.clone() + edge[edge > 0.93] = 0 + x = x + (edge * encoder_map) + + x = self.DWSConv(x) + skip = x.clone() + x = ( + torch.cat( + [self.DWConv1(x), self.DWConv2(x), self.DWConv3(x), self.DWConv4(x)], + dim=1, + ) + + skip + ) + x = torch.relu(self.conv1(x)) + + return x + decoder_map diff --git a/carvekit/ml/arch/tracerb7/conv_modules.py b/carvekit/ml/arch/tracerb7/conv_modules.py new file mode 100644 index 0000000000000000000000000000000000000000..90395a687851e44be66e465efa33a61e9a21a6dd --- /dev/null +++ b/carvekit/ml/arch/tracerb7/conv_modules.py @@ -0,0 +1,88 @@ +""" +Source url: https://github.com/Karel911/TRACER +Author: Min Seok Lee and Wooseok Shin +License: Apache License 2.0 +""" +import torch.nn as nn + + +class BasicConv2d(nn.Module): + def __init__( + self, + in_channel, + out_channel, + kernel_size, + stride=(1, 1), + padding=(0, 0), + dilation=(1, 1), + ): + super(BasicConv2d, self).__init__() + self.conv = nn.Conv2d( + in_channel, + out_channel, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=False, + ) + self.bn = nn.BatchNorm2d(out_channel) + self.selu = nn.SELU() + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + x = self.selu(x) + + return x + + +class DWConv(nn.Module): + def __init__(self, in_channel, out_channel, kernel, dilation, padding): + super(DWConv, self).__init__() + self.out_channel = out_channel + self.DWConv = nn.Conv2d( + in_channel, + out_channel, + kernel_size=kernel, + padding=padding, + groups=in_channel, + dilation=dilation, + bias=False, + ) + self.bn = nn.BatchNorm2d(out_channel) + self.selu = nn.SELU() + + def forward(self, x): + x = self.DWConv(x) + out = self.selu(self.bn(x)) + + return out + + +class DWSConv(nn.Module): + def __init__(self, in_channel, out_channel, kernel, padding, kernels_per_layer): + super(DWSConv, self).__init__() + self.out_channel = out_channel + self.DWConv = nn.Conv2d( + in_channel, + in_channel * kernels_per_layer, + kernel_size=kernel, + padding=padding, + groups=in_channel, + bias=False, + ) + self.bn = nn.BatchNorm2d(in_channel * kernels_per_layer) + self.selu = nn.SELU() + self.PWConv = nn.Conv2d( + in_channel * kernels_per_layer, out_channel, kernel_size=1, bias=False + ) + self.bn2 = nn.BatchNorm2d(out_channel) + + def forward(self, x): + x = self.DWConv(x) + x = self.selu(self.bn(x)) + out = self.PWConv(x) + out = self.selu(self.bn2(out)) + + return out diff --git a/carvekit/ml/arch/tracerb7/effi_utils.py b/carvekit/ml/arch/tracerb7/effi_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..b578ca258f4d9301483320a6db019953f78ed4cc --- /dev/null +++ b/carvekit/ml/arch/tracerb7/effi_utils.py @@ -0,0 +1,579 @@ +""" +Original author: lukemelas (github username) +Github repo: https://github.com/lukemelas/EfficientNet-PyTorch +With adjustments and added comments by workingcoder (github username). +License: Apache License 2.0 +Reimplemented: Min Seok Lee and Wooseok Shin +""" + +import collections +import re +from functools import partial + +import math +import torch +from torch import nn +from torch.nn import functional as F + +# Parameters for the entire model (stem, all blocks, and head) +GlobalParams = collections.namedtuple( + "GlobalParams", + [ + "width_coefficient", + "depth_coefficient", + "image_size", + "dropout_rate", + "num_classes", + "batch_norm_momentum", + "batch_norm_epsilon", + "drop_connect_rate", + "depth_divisor", + "min_depth", + "include_top", + ], +) + +# Parameters for an individual model block +BlockArgs = collections.namedtuple( + "BlockArgs", + [ + "num_repeat", + "kernel_size", + "stride", + "expand_ratio", + "input_filters", + "output_filters", + "se_ratio", + "id_skip", + ], +) + +# Set GlobalParams and BlockArgs's defaults +GlobalParams.__new__.__defaults__ = (None,) * len(GlobalParams._fields) +BlockArgs.__new__.__defaults__ = (None,) * len(BlockArgs._fields) + + +# An ordinary implementation of Swish function +class Swish(nn.Module): + def forward(self, x): + return x * torch.sigmoid(x) + + +# A memory-efficient implementation of Swish function +class SwishImplementation(torch.autograd.Function): + @staticmethod + def forward(ctx, i): + result = i * torch.sigmoid(i) + ctx.save_for_backward(i) + return result + + @staticmethod + def backward(ctx, grad_output): + i = ctx.saved_tensors[0] + sigmoid_i = torch.sigmoid(i) + return grad_output * (sigmoid_i * (1 + i * (1 - sigmoid_i))) + + +class MemoryEfficientSwish(nn.Module): + def forward(self, x): + return SwishImplementation.apply(x) + + +def round_filters(filters, global_params): + """Calculate and round number of filters based on width multiplier. + Use width_coefficient, depth_divisor and min_depth of global_params. + + Args: + filters (int): Filters number to be calculated. + global_params (namedtuple): Global params of the model. + + Returns: + new_filters: New filters number after calculating. + """ + multiplier = global_params.width_coefficient + if not multiplier: + return filters + divisor = global_params.depth_divisor + min_depth = global_params.min_depth + filters *= multiplier + min_depth = min_depth or divisor # pay attention to this line when using min_depth + # follow the formula transferred from official TensorFlow implementation + new_filters = max(min_depth, int(filters + divisor / 2) // divisor * divisor) + if new_filters < 0.9 * filters: # prevent rounding by more than 10% + new_filters += divisor + return int(new_filters) + + +def round_repeats(repeats, global_params): + """Calculate module's repeat number of a block based on depth multiplier. + Use depth_coefficient of global_params. + + Args: + repeats (int): num_repeat to be calculated. + global_params (namedtuple): Global params of the model. + + Returns: + new repeat: New repeat number after calculating. + """ + multiplier = global_params.depth_coefficient + if not multiplier: + return repeats + # follow the formula transferred from official TensorFlow implementation + return int(math.ceil(multiplier * repeats)) + + +def drop_connect(inputs, p, training): + """Drop connect. + + Args: + input (tensor: BCWH): Input of this structure. + p (float: 0.0~1.0): Probability of drop connection. + training (bool): The running mode. + + Returns: + output: Output after drop connection. + """ + assert 0 <= p <= 1, "p must be in range of [0,1]" + + if not training: + return inputs + + batch_size = inputs.shape[0] + keep_prob = 1 - p + + # generate binary_tensor mask according to probability (p for 0, 1-p for 1) + random_tensor = keep_prob + random_tensor += torch.rand( + [batch_size, 1, 1, 1], dtype=inputs.dtype, device=inputs.device + ) + binary_tensor = torch.floor(random_tensor) + + output = inputs / keep_prob * binary_tensor + return output + + +def get_width_and_height_from_size(x): + """Obtain height and width from x. + + Args: + x (int, tuple or list): Data size. + + Returns: + size: A tuple or list (H,W). + """ + if isinstance(x, int): + return x, x + if isinstance(x, list) or isinstance(x, tuple): + return x + else: + raise TypeError() + + +def calculate_output_image_size(input_image_size, stride): + """Calculates the output image size when using Conv2dSamePadding with a stride. + Necessary for static padding. Thanks to mannatsingh for pointing this out. + + Args: + input_image_size (int, tuple or list): Size of input image. + stride (int, tuple or list): Conv2d operation's stride. + + Returns: + output_image_size: A list [H,W]. + """ + if input_image_size is None: + return None + image_height, image_width = get_width_and_height_from_size(input_image_size) + stride = stride if isinstance(stride, int) else stride[0] + image_height = int(math.ceil(image_height / stride)) + image_width = int(math.ceil(image_width / stride)) + return [image_height, image_width] + + +# Note: +# The following 'SamePadding' functions make output size equal ceil(input size/stride). +# Only when stride equals 1, can the output size be the same as input size. +# Don't be confused by their function names ! ! ! + + +def get_same_padding_conv2d(image_size=None): + """Chooses static padding if you have specified an image size, and dynamic padding otherwise. + Static padding is necessary for ONNX exporting of models. + + Args: + image_size (int or tuple): Size of the image. + + Returns: + Conv2dDynamicSamePadding or Conv2dStaticSamePadding. + """ + if image_size is None: + return Conv2dDynamicSamePadding + else: + return partial(Conv2dStaticSamePadding, image_size=image_size) + + +class Conv2dDynamicSamePadding(nn.Conv2d): + """2D Convolutions like TensorFlow, for a dynamic image size. + The padding is operated in forward function by calculating dynamically. + """ + + # Tips for 'SAME' mode padding. + # Given the following: + # i: width or height + # s: stride + # k: kernel size + # d: dilation + # p: padding + # Output after Conv2d: + # o = floor((i+p-((k-1)*d+1))/s+1) + # If o equals i, i = floor((i+p-((k-1)*d+1))/s+1), + # => p = (i-1)*s+((k-1)*d+1)-i + + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride=1, + dilation=1, + groups=1, + bias=True, + ): + super().__init__( + in_channels, out_channels, kernel_size, stride, 0, dilation, groups, bias + ) + self.stride = self.stride if len(self.stride) == 2 else [self.stride[0]] * 2 + + def forward(self, x): + ih, iw = x.size()[-2:] + kh, kw = self.weight.size()[-2:] + sh, sw = self.stride + oh, ow = math.ceil(ih / sh), math.ceil( + iw / sw + ) # change the output size according to stride ! ! ! + pad_h = max((oh - 1) * self.stride[0] + (kh - 1) * self.dilation[0] + 1 - ih, 0) + pad_w = max((ow - 1) * self.stride[1] + (kw - 1) * self.dilation[1] + 1 - iw, 0) + if pad_h > 0 or pad_w > 0: + x = F.pad( + x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2] + ) + return F.conv2d( + x, + self.weight, + self.bias, + self.stride, + self.padding, + self.dilation, + self.groups, + ) + + +class Conv2dStaticSamePadding(nn.Conv2d): + """2D Convolutions like TensorFlow's 'SAME' mode, with the given input image size. + The padding mudule is calculated in construction function, then used in forward. + """ + + # With the same calculation as Conv2dDynamicSamePadding + + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride=1, + image_size=None, + **kwargs + ): + super().__init__(in_channels, out_channels, kernel_size, stride, **kwargs) + self.stride = self.stride if len(self.stride) == 2 else [self.stride[0]] * 2 + + # Calculate padding based on image size and save it + assert image_size is not None + ih, iw = (image_size, image_size) if isinstance(image_size, int) else image_size + kh, kw = self.weight.size()[-2:] + sh, sw = self.stride + oh, ow = math.ceil(ih / sh), math.ceil(iw / sw) + pad_h = max((oh - 1) * self.stride[0] + (kh - 1) * self.dilation[0] + 1 - ih, 0) + pad_w = max((ow - 1) * self.stride[1] + (kw - 1) * self.dilation[1] + 1 - iw, 0) + if pad_h > 0 or pad_w > 0: + self.static_padding = nn.ZeroPad2d( + (pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2) + ) + else: + self.static_padding = nn.Identity() + + def forward(self, x): + x = self.static_padding(x) + x = F.conv2d( + x, + self.weight, + self.bias, + self.stride, + self.padding, + self.dilation, + self.groups, + ) + return x + + +def get_same_padding_maxPool2d(image_size=None): + """Chooses static padding if you have specified an image size, and dynamic padding otherwise. + Static padding is necessary for ONNX exporting of models. + + Args: + image_size (int or tuple): Size of the image. + + Returns: + MaxPool2dDynamicSamePadding or MaxPool2dStaticSamePadding. + """ + if image_size is None: + return MaxPool2dDynamicSamePadding + else: + return partial(MaxPool2dStaticSamePadding, image_size=image_size) + + +class MaxPool2dDynamicSamePadding(nn.MaxPool2d): + """2D MaxPooling like TensorFlow's 'SAME' mode, with a dynamic image size. + The padding is operated in forward function by calculating dynamically. + """ + + def __init__( + self, + kernel_size, + stride, + padding=0, + dilation=1, + return_indices=False, + ceil_mode=False, + ): + super().__init__( + kernel_size, stride, padding, dilation, return_indices, ceil_mode + ) + self.stride = [self.stride] * 2 if isinstance(self.stride, int) else self.stride + self.kernel_size = ( + [self.kernel_size] * 2 + if isinstance(self.kernel_size, int) + else self.kernel_size + ) + self.dilation = ( + [self.dilation] * 2 if isinstance(self.dilation, int) else self.dilation + ) + + def forward(self, x): + ih, iw = x.size()[-2:] + kh, kw = self.kernel_size + sh, sw = self.stride + oh, ow = math.ceil(ih / sh), math.ceil(iw / sw) + pad_h = max((oh - 1) * self.stride[0] + (kh - 1) * self.dilation[0] + 1 - ih, 0) + pad_w = max((ow - 1) * self.stride[1] + (kw - 1) * self.dilation[1] + 1 - iw, 0) + if pad_h > 0 or pad_w > 0: + x = F.pad( + x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2] + ) + return F.max_pool2d( + x, + self.kernel_size, + self.stride, + self.padding, + self.dilation, + self.ceil_mode, + self.return_indices, + ) + + +class MaxPool2dStaticSamePadding(nn.MaxPool2d): + """2D MaxPooling like TensorFlow's 'SAME' mode, with the given input image size. + The padding mudule is calculated in construction function, then used in forward. + """ + + def __init__(self, kernel_size, stride, image_size=None, **kwargs): + super().__init__(kernel_size, stride, **kwargs) + self.stride = [self.stride] * 2 if isinstance(self.stride, int) else self.stride + self.kernel_size = ( + [self.kernel_size] * 2 + if isinstance(self.kernel_size, int) + else self.kernel_size + ) + self.dilation = ( + [self.dilation] * 2 if isinstance(self.dilation, int) else self.dilation + ) + + # Calculate padding based on image size and save it + assert image_size is not None + ih, iw = (image_size, image_size) if isinstance(image_size, int) else image_size + kh, kw = self.kernel_size + sh, sw = self.stride + oh, ow = math.ceil(ih / sh), math.ceil(iw / sw) + pad_h = max((oh - 1) * self.stride[0] + (kh - 1) * self.dilation[0] + 1 - ih, 0) + pad_w = max((ow - 1) * self.stride[1] + (kw - 1) * self.dilation[1] + 1 - iw, 0) + if pad_h > 0 or pad_w > 0: + self.static_padding = nn.ZeroPad2d( + (pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2) + ) + else: + self.static_padding = nn.Identity() + + def forward(self, x): + x = self.static_padding(x) + x = F.max_pool2d( + x, + self.kernel_size, + self.stride, + self.padding, + self.dilation, + self.ceil_mode, + self.return_indices, + ) + return x + + +class BlockDecoder(object): + """Block Decoder for readability, + straight from the official TensorFlow repository. + """ + + @staticmethod + def _decode_block_string(block_string): + """Get a block through a string notation of arguments. + + Args: + block_string (str): A string notation of arguments. + Examples: 'r1_k3_s11_e1_i32_o16_se0.25_noskip'. + + Returns: + BlockArgs: The namedtuple defined at the top of this file. + """ + assert isinstance(block_string, str) + + ops = block_string.split("_") + options = {} + for op in ops: + splits = re.split(r"(\d.*)", op) + if len(splits) >= 2: + key, value = splits[:2] + options[key] = value + + # Check stride + assert ("s" in options and len(options["s"]) == 1) or ( + len(options["s"]) == 2 and options["s"][0] == options["s"][1] + ) + + return BlockArgs( + num_repeat=int(options["r"]), + kernel_size=int(options["k"]), + stride=[int(options["s"][0])], + expand_ratio=int(options["e"]), + input_filters=int(options["i"]), + output_filters=int(options["o"]), + se_ratio=float(options["se"]) if "se" in options else None, + id_skip=("noskip" not in block_string), + ) + + @staticmethod + def _encode_block_string(block): + """Encode a block to a string. + + Args: + block (namedtuple): A BlockArgs type argument. + + Returns: + block_string: A String form of BlockArgs. + """ + args = [ + "r%d" % block.num_repeat, + "k%d" % block.kernel_size, + "s%d%d" % (block.strides[0], block.strides[1]), + "e%s" % block.expand_ratio, + "i%d" % block.input_filters, + "o%d" % block.output_filters, + ] + if 0 < block.se_ratio <= 1: + args.append("se%s" % block.se_ratio) + if block.id_skip is False: + args.append("noskip") + return "_".join(args) + + @staticmethod + def decode(string_list): + """Decode a list of string notations to specify blocks inside the network. + + Args: + string_list (list[str]): A list of strings, each string is a notation of block. + + Returns: + blocks_args: A list of BlockArgs namedtuples of block args. + """ + assert isinstance(string_list, list) + blocks_args = [] + for block_string in string_list: + blocks_args.append(BlockDecoder._decode_block_string(block_string)) + return blocks_args + + @staticmethod + def encode(blocks_args): + """Encode a list of BlockArgs to a list of strings. + + Args: + blocks_args (list[namedtuples]): A list of BlockArgs namedtuples of block args. + + Returns: + block_strings: A list of strings, each string is a notation of block. + """ + block_strings = [] + for block in blocks_args: + block_strings.append(BlockDecoder._encode_block_string(block)) + return block_strings + + +def create_block_args( + width_coefficient=None, + depth_coefficient=None, + image_size=None, + dropout_rate=0.2, + drop_connect_rate=0.2, + num_classes=1000, + include_top=True, +): + """Create BlockArgs and GlobalParams for efficientnet model. + + Args: + width_coefficient (float) + depth_coefficient (float) + image_size (int) + dropout_rate (float) + drop_connect_rate (float) + num_classes (int) + + Meaning as the name suggests. + + Returns: + blocks_args, global_params. + """ + + # Blocks args for the whole model(efficientnet-b0 by default) + # It will be modified in the construction of EfficientNet Class according to model + blocks_args = [ + "r1_k3_s11_e1_i32_o16_se0.25", + "r2_k3_s22_e6_i16_o24_se0.25", + "r2_k5_s22_e6_i24_o40_se0.25", + "r3_k3_s22_e6_i40_o80_se0.25", + "r3_k5_s11_e6_i80_o112_se0.25", + "r4_k5_s22_e6_i112_o192_se0.25", + "r1_k3_s11_e6_i192_o320_se0.25", + ] + blocks_args = BlockDecoder.decode(blocks_args) + + global_params = GlobalParams( + width_coefficient=width_coefficient, + depth_coefficient=depth_coefficient, + image_size=image_size, + dropout_rate=dropout_rate, + num_classes=num_classes, + batch_norm_momentum=0.99, + batch_norm_epsilon=1e-3, + drop_connect_rate=drop_connect_rate, + depth_divisor=8, + min_depth=None, + include_top=include_top, + ) + + return blocks_args, global_params diff --git a/carvekit/ml/arch/tracerb7/efficientnet.py b/carvekit/ml/arch/tracerb7/efficientnet.py new file mode 100644 index 0000000000000000000000000000000000000000..ea327d0482c93dfe1a1d3c4cf77c3d36e4e79791 --- /dev/null +++ b/carvekit/ml/arch/tracerb7/efficientnet.py @@ -0,0 +1,325 @@ +""" +Source url: https://github.com/lukemelas/EfficientNet-PyTorch +Modified by Min Seok Lee, Wooseok Shin, Nikita Selin +License: Apache License 2.0 +Changes: + - Added support for extracting edge features + - Added support for extracting object features at different levels + - Refactored the code +""" +from typing import Any, List + +import torch +from torch import nn +from torch.nn import functional as F + +from carvekit.ml.arch.tracerb7.effi_utils import ( + get_same_padding_conv2d, + calculate_output_image_size, + MemoryEfficientSwish, + drop_connect, + round_filters, + round_repeats, + Swish, + create_block_args, +) + + +class MBConvBlock(nn.Module): + """Mobile Inverted Residual Bottleneck Block. + + Args: + block_args (namedtuple): BlockArgs, defined in utils.py. + global_params (namedtuple): GlobalParam, defined in utils.py. + image_size (tuple or list): [image_height, image_width]. + + References: + [1] https://arxiv.org/abs/1704.04861 (MobileNet v1) + [2] https://arxiv.org/abs/1801.04381 (MobileNet v2) + [3] https://arxiv.org/abs/1905.02244 (MobileNet v3) + """ + + def __init__(self, block_args, global_params, image_size=None): + super().__init__() + self._block_args = block_args + self._bn_mom = ( + 1 - global_params.batch_norm_momentum + ) # pytorch's difference from tensorflow + self._bn_eps = global_params.batch_norm_epsilon + self.has_se = (self._block_args.se_ratio is not None) and ( + 0 < self._block_args.se_ratio <= 1 + ) + self.id_skip = ( + block_args.id_skip + ) # whether to use skip connection and drop connect + + # Expansion phase (Inverted Bottleneck) + inp = self._block_args.input_filters # number of input channels + oup = ( + self._block_args.input_filters * self._block_args.expand_ratio + ) # number of output channels + if self._block_args.expand_ratio != 1: + Conv2d = get_same_padding_conv2d(image_size=image_size) + self._expand_conv = Conv2d( + in_channels=inp, out_channels=oup, kernel_size=1, bias=False + ) + self._bn0 = nn.BatchNorm2d( + num_features=oup, momentum=self._bn_mom, eps=self._bn_eps + ) + # image_size = calculate_output_image_size(image_size, 1) <-- this wouldn't modify image_size + + # Depthwise convolution phase + k = self._block_args.kernel_size + s = self._block_args.stride + Conv2d = get_same_padding_conv2d(image_size=image_size) + self._depthwise_conv = Conv2d( + in_channels=oup, + out_channels=oup, + groups=oup, # groups makes it depthwise + kernel_size=k, + stride=s, + bias=False, + ) + self._bn1 = nn.BatchNorm2d( + num_features=oup, momentum=self._bn_mom, eps=self._bn_eps + ) + image_size = calculate_output_image_size(image_size, s) + + # Squeeze and Excitation layer, if desired + if self.has_se: + Conv2d = get_same_padding_conv2d(image_size=(1, 1)) + num_squeezed_channels = max( + 1, int(self._block_args.input_filters * self._block_args.se_ratio) + ) + self._se_reduce = Conv2d( + in_channels=oup, out_channels=num_squeezed_channels, kernel_size=1 + ) + self._se_expand = Conv2d( + in_channels=num_squeezed_channels, out_channels=oup, kernel_size=1 + ) + + # Pointwise convolution phase + final_oup = self._block_args.output_filters + Conv2d = get_same_padding_conv2d(image_size=image_size) + self._project_conv = Conv2d( + in_channels=oup, out_channels=final_oup, kernel_size=1, bias=False + ) + self._bn2 = nn.BatchNorm2d( + num_features=final_oup, momentum=self._bn_mom, eps=self._bn_eps + ) + self._swish = MemoryEfficientSwish() + + def forward(self, inputs, drop_connect_rate=None): + """MBConvBlock's forward function. + + Args: + inputs (tensor): Input tensor. + drop_connect_rate (bool): Drop connect rate (float, between 0 and 1). + + Returns: + Output of this block after processing. + """ + + # Expansion and Depthwise Convolution + x = inputs + if self._block_args.expand_ratio != 1: + x = self._expand_conv(inputs) + x = self._bn0(x) + x = self._swish(x) + + x = self._depthwise_conv(x) + x = self._bn1(x) + x = self._swish(x) + + # Squeeze and Excitation + if self.has_se: + x_squeezed = F.adaptive_avg_pool2d(x, 1) + x_squeezed = self._se_reduce(x_squeezed) + x_squeezed = self._swish(x_squeezed) + x_squeezed = self._se_expand(x_squeezed) + x = torch.sigmoid(x_squeezed) * x + + # Pointwise Convolution + x = self._project_conv(x) + x = self._bn2(x) + + # Skip connection and drop connect + input_filters, output_filters = ( + self._block_args.input_filters, + self._block_args.output_filters, + ) + if ( + self.id_skip + and self._block_args.stride == 1 + and input_filters == output_filters + ): + # The combination of skip connection and drop connect brings about stochastic depth. + if drop_connect_rate: + x = drop_connect(x, p=drop_connect_rate, training=self.training) + x = x + inputs # skip connection + return x + + def set_swish(self, memory_efficient=True): + """Sets swish function as memory efficient (for training) or standard (for export). + + Args: + memory_efficient (bool): Whether to use memory-efficient version of swish. + """ + self._swish = MemoryEfficientSwish() if memory_efficient else Swish() + + +class EfficientNet(nn.Module): + def __init__(self, blocks_args=None, global_params=None): + super().__init__() + assert isinstance(blocks_args, list), "blocks_args should be a list" + assert len(blocks_args) > 0, "block args must be greater than 0" + self._global_params = global_params + self._blocks_args = blocks_args + + # Batch norm parameters + bn_mom = 1 - self._global_params.batch_norm_momentum + bn_eps = self._global_params.batch_norm_epsilon + + # Get stem static or dynamic convolution depending on image size + image_size = global_params.image_size + Conv2d = get_same_padding_conv2d(image_size=image_size) + + # Stem + in_channels = 3 # rgb + out_channels = round_filters( + 32, self._global_params + ) # number of output channels + self._conv_stem = Conv2d( + in_channels, out_channels, kernel_size=3, stride=2, bias=False + ) + self._bn0 = nn.BatchNorm2d( + num_features=out_channels, momentum=bn_mom, eps=bn_eps + ) + image_size = calculate_output_image_size(image_size, 2) + + # Build blocks + self._blocks = nn.ModuleList([]) + for block_args in self._blocks_args: + + # Update block input and output filters based on depth multiplier. + block_args = block_args._replace( + input_filters=round_filters( + block_args.input_filters, self._global_params + ), + output_filters=round_filters( + block_args.output_filters, self._global_params + ), + num_repeat=round_repeats(block_args.num_repeat, self._global_params), + ) + + # The first block needs to take care of stride and filter size increase. + self._blocks.append( + MBConvBlock(block_args, self._global_params, image_size=image_size) + ) + image_size = calculate_output_image_size(image_size, block_args.stride) + if block_args.num_repeat > 1: # modify block_args to keep same output size + block_args = block_args._replace( + input_filters=block_args.output_filters, stride=1 + ) + for _ in range(block_args.num_repeat - 1): + self._blocks.append( + MBConvBlock(block_args, self._global_params, image_size=image_size) + ) + # image_size = calculate_output_image_size(image_size, block_args.stride) # stride = 1 + + self._swish = MemoryEfficientSwish() + + def set_swish(self, memory_efficient=True): + """Sets swish function as memory efficient (for training) or standard (for export). + + Args: + memory_efficient (bool): Whether to use memory-efficient version of swish. + + """ + self._swish = MemoryEfficientSwish() if memory_efficient else Swish() + for block in self._blocks: + block.set_swish(memory_efficient) + + def extract_endpoints(self, inputs): + endpoints = dict() + + # Stem + x = self._swish(self._bn0(self._conv_stem(inputs))) + prev_x = x + + # Blocks + for idx, block in enumerate(self._blocks): + drop_connect_rate = self._global_params.drop_connect_rate + if drop_connect_rate: + drop_connect_rate *= float(idx) / len( + self._blocks + ) # scale drop connect_rate + x = block(x, drop_connect_rate=drop_connect_rate) + if prev_x.size(2) > x.size(2): + endpoints["reduction_{}".format(len(endpoints) + 1)] = prev_x + prev_x = x + + # Head + x = self._swish(self._bn1(self._conv_head(x))) + endpoints["reduction_{}".format(len(endpoints) + 1)] = x + + return endpoints + + def _change_in_channels(self, in_channels): + """Adjust model's first convolution layer to in_channels, if in_channels not equals 3. + + Args: + in_channels (int): Input data's channel number. + """ + if in_channels != 3: + Conv2d = get_same_padding_conv2d(image_size=self._global_params.image_size) + out_channels = round_filters(32, self._global_params) + self._conv_stem = Conv2d( + in_channels, out_channels, kernel_size=3, stride=2, bias=False + ) + + +class EfficientEncoderB7(EfficientNet): + def __init__(self): + super().__init__( + *create_block_args( + width_coefficient=2.0, + depth_coefficient=3.1, + dropout_rate=0.5, + image_size=600, + ) + ) + self._change_in_channels(3) + self.block_idx = [10, 17, 37, 54] + self.channels = [48, 80, 224, 640] + + def initial_conv(self, inputs): + x = self._swish(self._bn0(self._conv_stem(inputs))) + return x + + def get_blocks(self, x, H, W, block_idx): + features = [] + for idx, block in enumerate(self._blocks): + drop_connect_rate = self._global_params.drop_connect_rate + if drop_connect_rate: + drop_connect_rate *= float(idx) / len( + self._blocks + ) # scale drop connect_rate + x = block(x, drop_connect_rate=drop_connect_rate) + if idx == block_idx[0]: + features.append(x.clone()) + if idx == block_idx[1]: + features.append(x.clone()) + if idx == block_idx[2]: + features.append(x.clone()) + if idx == block_idx[3]: + features.append(x.clone()) + + return features + + def forward(self, inputs: torch.Tensor) -> List[Any]: + B, C, H, W = inputs.size() + x = self.initial_conv(inputs) # Prepare input for the backbone + return self.get_blocks( + x, H, W, block_idx=self.block_idx + ) # Get backbone features and edge maps diff --git a/carvekit/ml/arch/tracerb7/tracer.py b/carvekit/ml/arch/tracerb7/tracer.py new file mode 100644 index 0000000000000000000000000000000000000000..70cc3f2d8169ba0da87ed46d2d3de69b69f57c0c --- /dev/null +++ b/carvekit/ml/arch/tracerb7/tracer.py @@ -0,0 +1,97 @@ +""" +Source url: https://github.com/Karel911/TRACER +Author: Min Seok Lee and Wooseok Shin +Modified by Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +Changes: + - Refactored code + - Removed unused code + - Added comments +""" + +import torch +import torch.nn as nn +import torch.nn.functional as F +from typing import List, Optional, Tuple + +from torch import Tensor + +from carvekit.ml.arch.tracerb7.efficientnet import EfficientEncoderB7 +from carvekit.ml.arch.tracerb7.att_modules import ( + RFB_Block, + aggregation, + ObjectAttention, +) + + +class TracerDecoder(nn.Module): + """Tracer Decoder""" + + def __init__( + self, + encoder: EfficientEncoderB7, + features_channels: Optional[List[int]] = None, + rfb_channel: Optional[List[int]] = None, + ): + """ + Initialize the tracer decoder. + + Args: + encoder: The encoder to use. + features_channels: The channels of the backbone features at different stages. default: [48, 80, 224, 640] + rfb_channel: The channels of the RFB features. default: [32, 64, 128] + """ + super().__init__() + if rfb_channel is None: + rfb_channel = [32, 64, 128] + if features_channels is None: + features_channels = [48, 80, 224, 640] + self.encoder = encoder + self.features_channels = features_channels + + # Receptive Field Blocks + features_channels = rfb_channel + self.rfb2 = RFB_Block(self.features_channels[1], features_channels[0]) + self.rfb3 = RFB_Block(self.features_channels[2], features_channels[1]) + self.rfb4 = RFB_Block(self.features_channels[3], features_channels[2]) + + # Multi-level aggregation + self.agg = aggregation(features_channels) + + # Object Attention + self.ObjectAttention2 = ObjectAttention( + channel=self.features_channels[1], kernel_size=3 + ) + self.ObjectAttention1 = ObjectAttention( + channel=self.features_channels[0], kernel_size=3 + ) + + def forward(self, inputs: torch.Tensor) -> Tensor: + """ + Forward pass of the tracer decoder. + + Args: + inputs: Preprocessed images. + + Returns: + Tensors of segmentation masks and mask of object edges. + """ + features = self.encoder(inputs) + x3_rfb = self.rfb2(features[1]) + x4_rfb = self.rfb3(features[2]) + x5_rfb = self.rfb4(features[3]) + + D_0 = self.agg(x5_rfb, x4_rfb, x3_rfb) + + ds_map0 = F.interpolate(D_0, scale_factor=8, mode="bilinear") + + D_1 = self.ObjectAttention2(D_0, features[1]) + ds_map1 = F.interpolate(D_1, scale_factor=8, mode="bilinear") + + ds_map = F.interpolate(D_1, scale_factor=2, mode="bilinear") + D_2 = self.ObjectAttention1(ds_map, features[0]) + ds_map2 = F.interpolate(D_2, scale_factor=4, mode="bilinear") + + final_map = (ds_map2 + ds_map1 + ds_map0) / 3 + + return torch.sigmoid(final_map) diff --git a/carvekit/ml/arch/u2net/__init__.py b/carvekit/ml/arch/u2net/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/ml/arch/u2net/__pycache__/__init__.cpython-38.pyc b/carvekit/ml/arch/u2net/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5cef916d56e9e7d20b1a2b2a0a936abe1f1f50e7 Binary files /dev/null and b/carvekit/ml/arch/u2net/__pycache__/__init__.cpython-38.pyc differ diff --git a/carvekit/ml/arch/u2net/__pycache__/u2net.cpython-38.pyc b/carvekit/ml/arch/u2net/__pycache__/u2net.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da5d3ecb5e70ebcc23603121ce79119228a9886b Binary files /dev/null and b/carvekit/ml/arch/u2net/__pycache__/u2net.cpython-38.pyc differ diff --git a/carvekit/ml/arch/u2net/u2net.py b/carvekit/ml/arch/u2net/u2net.py new file mode 100644 index 0000000000000000000000000000000000000000..2225acf51aa043cd84fffe324497b2818f20acc8 --- /dev/null +++ b/carvekit/ml/arch/u2net/u2net.py @@ -0,0 +1,172 @@ +""" +Modified by Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +Source url: https://github.com/xuebinqin/U-2-Net +License: Apache License 2.0 +""" +from typing import Union + +import torch +import torch.nn as nn + +import math + +__all__ = ["U2NETArchitecture"] + + +def _upsample_like(x, size): + return nn.Upsample(size=size, mode="bilinear", align_corners=False)(x) + + +def _size_map(x, height): + # {height: size} for Upsample + size = list(x.shape[-2:]) + sizes = {} + for h in range(1, height): + sizes[h] = size + size = [math.ceil(w / 2) for w in size] + return sizes + + +class REBNCONV(nn.Module): + def __init__(self, in_ch=3, out_ch=3, dilate=1): + super(REBNCONV, self).__init__() + + self.conv_s1 = nn.Conv2d( + in_ch, out_ch, 3, padding=1 * dilate, dilation=1 * dilate + ) + self.bn_s1 = nn.BatchNorm2d(out_ch) + self.relu_s1 = nn.ReLU(inplace=True) + + def forward(self, x): + return self.relu_s1(self.bn_s1(self.conv_s1(x))) + + +class RSU(nn.Module): + def __init__(self, name, height, in_ch, mid_ch, out_ch, dilated=False): + super(RSU, self).__init__() + self.name = name + self.height = height + self.dilated = dilated + self._make_layers(height, in_ch, mid_ch, out_ch, dilated) + + def forward(self, x): + sizes = _size_map(x, self.height) + x = self.rebnconvin(x) + + # U-Net like symmetric encoder-decoder structure + def unet(x, height=1): + if height < self.height: + x1 = getattr(self, f"rebnconv{height}")(x) + if not self.dilated and height < self.height - 1: + x2 = unet(getattr(self, "downsample")(x1), height + 1) + else: + x2 = unet(x1, height + 1) + + x = getattr(self, f"rebnconv{height}d")(torch.cat((x2, x1), 1)) + return ( + _upsample_like(x, sizes[height - 1]) + if not self.dilated and height > 1 + else x + ) + else: + return getattr(self, f"rebnconv{height}")(x) + + return x + unet(x) + + def _make_layers(self, height, in_ch, mid_ch, out_ch, dilated=False): + self.add_module("rebnconvin", REBNCONV(in_ch, out_ch)) + self.add_module("downsample", nn.MaxPool2d(2, stride=2, ceil_mode=True)) + + self.add_module("rebnconv1", REBNCONV(out_ch, mid_ch)) + self.add_module("rebnconv1d", REBNCONV(mid_ch * 2, out_ch)) + + for i in range(2, height): + dilate = 1 if not dilated else 2 ** (i - 1) + self.add_module(f"rebnconv{i}", REBNCONV(mid_ch, mid_ch, dilate=dilate)) + self.add_module( + f"rebnconv{i}d", REBNCONV(mid_ch * 2, mid_ch, dilate=dilate) + ) + + dilate = 2 if not dilated else 2 ** (height - 1) + self.add_module(f"rebnconv{height}", REBNCONV(mid_ch, mid_ch, dilate=dilate)) + + +class U2NETArchitecture(nn.Module): + def __init__(self, cfg_type: Union[dict, str] = "full", out_ch: int = 1): + super(U2NETArchitecture, self).__init__() + if isinstance(cfg_type, str): + if cfg_type == "full": + layers_cfgs = { + # cfgs for building RSUs and sides + # {stage : [name, (height(L), in_ch, mid_ch, out_ch, dilated), side]} + "stage1": ["En_1", (7, 3, 32, 64), -1], + "stage2": ["En_2", (6, 64, 32, 128), -1], + "stage3": ["En_3", (5, 128, 64, 256), -1], + "stage4": ["En_4", (4, 256, 128, 512), -1], + "stage5": ["En_5", (4, 512, 256, 512, True), -1], + "stage6": ["En_6", (4, 512, 256, 512, True), 512], + "stage5d": ["De_5", (4, 1024, 256, 512, True), 512], + "stage4d": ["De_4", (4, 1024, 128, 256), 256], + "stage3d": ["De_3", (5, 512, 64, 128), 128], + "stage2d": ["De_2", (6, 256, 32, 64), 64], + "stage1d": ["De_1", (7, 128, 16, 64), 64], + } + else: + raise ValueError("Unknown U^2-Net architecture conf. name") + elif isinstance(cfg_type, dict): + layers_cfgs = cfg_type + else: + raise ValueError("Unknown U^2-Net architecture conf. type") + self.out_ch = out_ch + self._make_layers(layers_cfgs) + + def forward(self, x): + sizes = _size_map(x, self.height) + maps = [] # storage for maps + + # side saliency map + def unet(x, height=1): + if height < 6: + x1 = getattr(self, f"stage{height}")(x) + x2 = unet(getattr(self, "downsample")(x1), height + 1) + x = getattr(self, f"stage{height}d")(torch.cat((x2, x1), 1)) + side(x, height) + return _upsample_like(x, sizes[height - 1]) if height > 1 else x + else: + x = getattr(self, f"stage{height}")(x) + side(x, height) + return _upsample_like(x, sizes[height - 1]) + + def side(x, h): + # side output saliency map (before sigmoid) + x = getattr(self, f"side{h}")(x) + x = _upsample_like(x, sizes[1]) + maps.append(x) + + def fuse(): + # fuse saliency probability maps + maps.reverse() + x = torch.cat(maps, 1) + x = getattr(self, "outconv")(x) + maps.insert(0, x) + return [torch.sigmoid(x) for x in maps] + + unet(x) + maps = fuse() + return maps + + def _make_layers(self, cfgs): + self.height = int((len(cfgs) + 1) / 2) + self.add_module("downsample", nn.MaxPool2d(2, stride=2, ceil_mode=True)) + for k, v in cfgs.items(): + # build rsu block + self.add_module(k, RSU(v[0], *v[1])) + if v[2] > 0: + # build side layer + self.add_module( + f"side{v[0][-1]}", nn.Conv2d(v[2], self.out_ch, 3, padding=1) + ) + # build fuse layer + self.add_module( + "outconv", nn.Conv2d(int(self.height * self.out_ch), self.out_ch, 1) + ) diff --git a/carvekit/ml/files/__init__.py b/carvekit/ml/files/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..dc535a72515f2e170ce1e47284d7883d0a28bda9 --- /dev/null +++ b/carvekit/ml/files/__init__.py @@ -0,0 +1,7 @@ +from pathlib import Path + +carvekit_dir = Path.home().joinpath(".cache/carvekit") + +carvekit_dir.mkdir(parents=True, exist_ok=True) + +checkpoints_dir = carvekit_dir.joinpath("checkpoints") diff --git a/carvekit/ml/files/__pycache__/__init__.cpython-38.pyc b/carvekit/ml/files/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8478100d48fdc630fa56208d3604a2c6dd4ffb53 Binary files /dev/null and b/carvekit/ml/files/__pycache__/__init__.cpython-38.pyc differ diff --git a/carvekit/ml/files/__pycache__/models_loc.cpython-38.pyc b/carvekit/ml/files/__pycache__/models_loc.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..16d196c5a3479a0267dc3cb4ae45111f3cb3ffa2 Binary files /dev/null and b/carvekit/ml/files/__pycache__/models_loc.cpython-38.pyc differ diff --git a/carvekit/ml/files/models_loc.py b/carvekit/ml/files/models_loc.py new file mode 100644 index 0000000000000000000000000000000000000000..45f9a56e211fa180c1fd241067df65d88b6465b5 --- /dev/null +++ b/carvekit/ml/files/models_loc.py @@ -0,0 +1,70 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +import pathlib +from carvekit.ml.files import checkpoints_dir +from carvekit.utils.download_models import downloader + + +def u2net_full_pretrained() -> pathlib.Path: + """Returns u2net pretrained model location + + Returns: + pathlib.Path to model location + """ + return downloader("u2net.pth") + + +def basnet_pretrained() -> pathlib.Path: + """Returns basnet pretrained model location + + Returns: + pathlib.Path to model location + """ + return downloader("basnet.pth") + + +def deeplab_pretrained() -> pathlib.Path: + """Returns basnet pretrained model location + + Returns: + pathlib.Path to model location + """ + return downloader("deeplab.pth") + + +def fba_pretrained() -> pathlib.Path: + """Returns basnet pretrained model location + + Returns: + pathlib.Path to model location + """ + return downloader("fba_matting.pth") + + +def tracer_b7_pretrained() -> pathlib.Path: + """Returns TRACER with EfficientNet v1 b7 encoder pretrained model location + + Returns: + pathlib.Path to model location + """ + return downloader("tracer_b7.pth") + + +def tracer_hair_pretrained() -> pathlib.Path: + """Returns TRACER with EfficientNet v1 b7 encoder model for hair segmentation location + + Returns: + pathlib.Path to model location + """ + return downloader("tracer_hair.pth") + + +def download_all(): + u2net_full_pretrained() + fba_pretrained() + deeplab_pretrained() + basnet_pretrained() + tracer_b7_pretrained() diff --git a/carvekit/ml/wrap/__init__.py b/carvekit/ml/wrap/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/ml/wrap/__pycache__/__init__.cpython-38.pyc b/carvekit/ml/wrap/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a7255183655ffbbb29a4d50eb123bcdcb20d3b50 Binary files /dev/null and b/carvekit/ml/wrap/__pycache__/__init__.cpython-38.pyc differ diff --git a/carvekit/ml/wrap/__pycache__/basnet.cpython-38.pyc b/carvekit/ml/wrap/__pycache__/basnet.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06329b65d73c553207dbe63ab1f0a9da93f5283a Binary files /dev/null and b/carvekit/ml/wrap/__pycache__/basnet.cpython-38.pyc differ diff --git a/carvekit/ml/wrap/__pycache__/deeplab_v3.cpython-38.pyc b/carvekit/ml/wrap/__pycache__/deeplab_v3.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca6494e2a9f8a946c358fa60af7a4504907a3862 Binary files /dev/null and b/carvekit/ml/wrap/__pycache__/deeplab_v3.cpython-38.pyc differ diff --git a/carvekit/ml/wrap/__pycache__/fba_matting.cpython-38.pyc b/carvekit/ml/wrap/__pycache__/fba_matting.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..29bd75be6af40351e068e9f6433f9dcd6cf5565d Binary files /dev/null and b/carvekit/ml/wrap/__pycache__/fba_matting.cpython-38.pyc differ diff --git a/carvekit/ml/wrap/__pycache__/tracer_b7.cpython-38.pyc b/carvekit/ml/wrap/__pycache__/tracer_b7.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3149c51393b9dbd04cb4dd41897e4172ae9af033 Binary files /dev/null and b/carvekit/ml/wrap/__pycache__/tracer_b7.cpython-38.pyc differ diff --git a/carvekit/ml/wrap/__pycache__/u2net.cpython-38.pyc b/carvekit/ml/wrap/__pycache__/u2net.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e12f6cad8f6b5836b8bcf89f3e7564407bebb5e3 Binary files /dev/null and b/carvekit/ml/wrap/__pycache__/u2net.cpython-38.pyc differ diff --git a/carvekit/ml/wrap/basnet.py b/carvekit/ml/wrap/basnet.py new file mode 100644 index 0000000000000000000000000000000000000000..9912e8191ef9d723278a640a160fc83c47951caf --- /dev/null +++ b/carvekit/ml/wrap/basnet.py @@ -0,0 +1,141 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +import pathlib +from typing import Union, List + +import PIL +import numpy as np +import torch +from PIL import Image + +from carvekit.ml.arch.basnet.basnet import BASNet +from carvekit.ml.files.models_loc import basnet_pretrained +from carvekit.utils.image_utils import convert_image, load_image +from carvekit.utils.pool_utils import batch_generator, thread_pool_processing + +__all__ = ["BASNET"] + + +class BASNET(BASNet): + """BASNet model interface""" + + def __init__( + self, + device="cpu", + input_image_size: Union[List[int], int] = 320, + batch_size: int = 10, + load_pretrained: bool = True, + fp16: bool = False, + ): + """ + Initialize the BASNET model + + Args: + device: processing device + input_image_size: input image size + batch_size: the number of images that the neural network processes in one run + load_pretrained: loading pretrained model + fp16: use fp16 precision // not supported at this moment + + """ + super(BASNET, self).__init__(n_channels=3, n_classes=1) + self.device = device + self.batch_size = batch_size + if isinstance(input_image_size, list): + self.input_image_size = input_image_size[:2] + else: + self.input_image_size = (input_image_size, input_image_size) + self.to(device) + if load_pretrained: + self.load_state_dict( + torch.load(basnet_pretrained(), map_location=self.device) + ) + self.eval() + + def data_preprocessing(self, data: PIL.Image.Image) -> torch.Tensor: + """ + Transform input image to suitable data format for neural network + + Args: + data: input image + + Returns: + input for neural network + + """ + resized = data.resize(self.input_image_size) + # noinspection PyTypeChecker + resized_arr = np.array(resized, dtype=np.float64) + temp_image = np.zeros((resized_arr.shape[0], resized_arr.shape[1], 3)) + if np.max(resized_arr) != 0: + resized_arr /= np.max(resized_arr) + temp_image[:, :, 0] = (resized_arr[:, :, 0] - 0.485) / 0.229 + temp_image[:, :, 1] = (resized_arr[:, :, 1] - 0.456) / 0.224 + temp_image[:, :, 2] = (resized_arr[:, :, 2] - 0.406) / 0.225 + temp_image = temp_image.transpose((2, 0, 1)) + temp_image = np.expand_dims(temp_image, 0) + return torch.from_numpy(temp_image).type(torch.FloatTensor) + + @staticmethod + def data_postprocessing( + data: torch.tensor, original_image: PIL.Image.Image + ) -> PIL.Image.Image: + """ + Transforms output data from neural network to suitable data + format for using with other components of this framework. + + Args: + data: output data from neural network + original_image: input image which was used for predicted data + + Returns: + Segmentation mask as PIL Image instance + + """ + data = data.unsqueeze(0) + mask = data[:, 0, :, :] + ma = torch.max(mask) # Normalizes prediction + mi = torch.min(mask) + predict = ((mask - mi) / (ma - mi)).squeeze() + predict_np = predict.cpu().data.numpy() * 255 + mask = Image.fromarray(predict_np).convert("L") + mask = mask.resize(original_image.size, resample=3) + return mask + + def __call__( + self, images: List[Union[str, pathlib.Path, PIL.Image.Image]] + ) -> List[PIL.Image.Image]: + """ + Passes input images through neural network and returns segmentation masks as PIL.Image.Image instances + + Args: + images: input images + + Returns: + segmentation masks as for input images, as PIL.Image.Image instances + + """ + collect_masks = [] + for image_batch in batch_generator(images, self.batch_size): + images = thread_pool_processing( + lambda x: convert_image(load_image(x)), image_batch + ) + batches = torch.vstack( + thread_pool_processing(self.data_preprocessing, images) + ) + with torch.no_grad(): + batches = batches.to(self.device) + masks, d2, d3, d4, d5, d6, d7, d8 = super(BASNET, self).__call__( + batches + ) + masks_cpu = masks.cpu() + del d2, d3, d4, d5, d6, d7, d8, batches, masks + masks = thread_pool_processing( + lambda x: self.data_postprocessing(masks_cpu[x], images[x]), + range(len(images)), + ) + collect_masks += masks + return collect_masks diff --git a/carvekit/ml/wrap/deeplab_v3.py b/carvekit/ml/wrap/deeplab_v3.py new file mode 100644 index 0000000000000000000000000000000000000000..4b19542a3e890fee9d919d0a33a64aff3430ea83 --- /dev/null +++ b/carvekit/ml/wrap/deeplab_v3.py @@ -0,0 +1,150 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +import pathlib +from typing import List, Union + +import PIL.Image +import torch +from PIL import Image +from torchvision import transforms +from torchvision.models.segmentation import deeplabv3_resnet101 +from carvekit.ml.files.models_loc import deeplab_pretrained +from carvekit.utils.image_utils import convert_image, load_image +from carvekit.utils.models_utils import get_precision_autocast, cast_network +from carvekit.utils.pool_utils import batch_generator, thread_pool_processing + +__all__ = ["DeepLabV3"] + + +class DeepLabV3: + def __init__( + self, + device="cpu", + batch_size: int = 10, + input_image_size: Union[List[int], int] = 1024, + load_pretrained: bool = True, + fp16: bool = False, + ): + """ + Initialize the DeepLabV3 model + + Args: + device: processing device + input_image_size: input image size + batch_size: the number of images that the neural network processes in one run + load_pretrained: loading pretrained model + fp16: use half precision + + """ + self.device = device + self.batch_size = batch_size + self.network = deeplabv3_resnet101( + pretrained=False, pretrained_backbone=False, aux_loss=True + ) + self.network.to(self.device) + if load_pretrained: + self.network.load_state_dict( + torch.load(deeplab_pretrained(), map_location=self.device) + ) + if isinstance(input_image_size, list): + self.input_image_size = input_image_size[:2] + else: + self.input_image_size = (input_image_size, input_image_size) + self.network.eval() + self.fp16 = fp16 + self.transform = transforms.Compose( + [ + transforms.ToTensor(), + transforms.Normalize( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ), + ] + ) + + def to(self, device: str): + """ + Moves neural network to specified processing device + + Args: + device (:class:`torch.device`): the desired device. + Returns: + None + + """ + self.network.to(device) + + def data_preprocessing(self, data: PIL.Image.Image) -> torch.Tensor: + """ + Transform input image to suitable data format for neural network + + Args: + data: input image + + Returns: + input for neural network + + """ + copy = data.copy() + copy.thumbnail(self.input_image_size, resample=3) + return self.transform(copy) + + @staticmethod + def data_postprocessing( + data: torch.tensor, original_image: PIL.Image.Image + ) -> PIL.Image.Image: + """ + Transforms output data from neural network to suitable data + format for using with other components of this framework. + + Args: + data: output data from neural network + original_image: input image which was used for predicted data + + Returns: + Segmentation mask as PIL Image instance + + """ + return ( + Image.fromarray(data.numpy() * 255).convert("L").resize(original_image.size) + ) + + def __call__( + self, images: List[Union[str, pathlib.Path, PIL.Image.Image]] + ) -> List[PIL.Image.Image]: + """ + Passes input images though neural network and returns segmentation masks as PIL.Image.Image instances + + Args: + images: input images + + Returns: + segmentation masks as for input images, as PIL.Image.Image instances + + """ + collect_masks = [] + autocast, dtype = get_precision_autocast(device=self.device, fp16=self.fp16) + with autocast: + cast_network(self.network, dtype) + for image_batch in batch_generator(images, self.batch_size): + images = thread_pool_processing( + lambda x: convert_image(load_image(x)), image_batch + ) + batches = thread_pool_processing(self.data_preprocessing, images) + with torch.no_grad(): + masks = [ + self.network(i.to(self.device).unsqueeze(0))["out"][0] + .argmax(0) + .byte() + .cpu() + for i in batches + ] + del batches + masks = thread_pool_processing( + lambda x: self.data_postprocessing(masks[x], images[x]), + range(len(images)), + ) + collect_masks += masks + return collect_masks diff --git a/carvekit/ml/wrap/fba_matting.py b/carvekit/ml/wrap/fba_matting.py new file mode 100644 index 0000000000000000000000000000000000000000..c285df07168c0db432f7f893025df1c14dc7099d --- /dev/null +++ b/carvekit/ml/wrap/fba_matting.py @@ -0,0 +1,224 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +import pathlib +from typing import Union, List, Tuple + +import PIL +import cv2 +import numpy as np +import torch +from PIL import Image + +from carvekit.ml.arch.fba_matting.models import FBA +from carvekit.ml.arch.fba_matting.transforms import ( + trimap_transform, + groupnorm_normalise_image, +) +from carvekit.ml.files.models_loc import fba_pretrained +from carvekit.utils.image_utils import convert_image, load_image +from carvekit.utils.models_utils import get_precision_autocast, cast_network +from carvekit.utils.pool_utils import batch_generator, thread_pool_processing + +__all__ = ["FBAMatting"] + + +class FBAMatting(FBA): + """ + FBA Matting Neural Network to improve edges on image. + """ + + def __init__( + self, + device="cpu", + input_tensor_size: Union[List[int], int] = 2048, + batch_size: int = 2, + encoder="resnet50_GN_WS", + load_pretrained: bool = True, + fp16: bool = False, + ): + """ + Initialize the FBAMatting model + + Args: + device: processing device + input_tensor_size: input image size + batch_size: the number of images that the neural network processes in one run + encoder: neural network encoder head + load_pretrained: loading pretrained model + fp16: use half precision + + """ + super(FBAMatting, self).__init__(encoder=encoder) + self.fp16 = fp16 + self.device = device + self.batch_size = batch_size + if isinstance(input_tensor_size, list): + self.input_image_size = input_tensor_size[:2] + else: + self.input_image_size = (input_tensor_size, input_tensor_size) + self.to(device) + if load_pretrained: + self.load_state_dict(torch.load(fba_pretrained(), map_location=self.device)) + self.eval() + + def data_preprocessing( + self, data: Union[PIL.Image.Image, np.ndarray] + ) -> Tuple[torch.FloatTensor, torch.FloatTensor]: + """ + Transform input image to suitable data format for neural network + + Args: + data: input image + + Returns: + input for neural network + + """ + resized = data.copy() + if self.batch_size == 1: + resized.thumbnail(self.input_image_size, resample=3) + else: + resized = resized.resize(self.input_image_size, resample=3) + # noinspection PyTypeChecker + image = np.array(resized, dtype=np.float64) + image = image / 255.0 # Normalize image to [0, 1] values range + if resized.mode == "RGB": + image = image[:, :, ::-1] + elif resized.mode == "L": + image2 = np.copy(image) + h, w = image2.shape + image = np.zeros((h, w, 2)) # Transform trimap to binary data format + image[image2 == 1, 1] = 1 + image[image2 == 0, 0] = 1 + else: + raise ValueError("Incorrect color mode for image") + h, w = image.shape[:2] # Scale input mlt to 8 + h1 = int(np.ceil(1.0 * h / 8) * 8) + w1 = int(np.ceil(1.0 * w / 8) * 8) + x_scale = cv2.resize(image, (w1, h1), interpolation=cv2.INTER_LANCZOS4) + image_tensor = torch.from_numpy(x_scale).permute(2, 0, 1)[None, :, :, :].float() + if resized.mode == "RGB": + return image_tensor, groupnorm_normalise_image( + image_tensor.clone(), format="nchw" + ) + else: + return ( + image_tensor, + torch.from_numpy(trimap_transform(x_scale)) + .permute(2, 0, 1)[None, :, :, :] + .float(), + ) + + @staticmethod + def data_postprocessing( + data: torch.tensor, trimap: PIL.Image.Image + ) -> PIL.Image.Image: + """ + Transforms output data from neural network to suitable data + format for using with other components of this framework. + + Args: + data: output data from neural network + trimap: Map with the area we need to refine + + Returns: + Segmentation mask as PIL Image instance + + """ + if trimap.mode != "L": + raise ValueError("Incorrect color mode for trimap") + pred = data.numpy().transpose((1, 2, 0)) + pred = cv2.resize(pred, trimap.size, cv2.INTER_LANCZOS4)[:, :, 0] + # noinspection PyTypeChecker + # Clean mask by removing all false predictions outside trimap and already known area + trimap_arr = np.array(trimap.copy()) + pred[trimap_arr[:, :] == 0] = 0 + # pred[trimap_arr[:, :] == 255] = 1 + pred[pred < 0.3] = 0 + return Image.fromarray(pred * 255).convert("L") + + def __call__( + self, + images: List[Union[str, pathlib.Path, PIL.Image.Image]], + trimaps: List[Union[str, pathlib.Path, PIL.Image.Image]], + ) -> List[PIL.Image.Image]: + """ + Passes input images though neural network and returns segmentation masks as PIL.Image.Image instances + + Args: + images: input images + trimaps: Maps with the areas we need to refine + + Returns: + segmentation masks as for input images, as PIL.Image.Image instances + + """ + + if len(images) != len(trimaps): + raise ValueError( + "Len of specified arrays of images and trimaps should be equal!" + ) + + collect_masks = [] + autocast, dtype = get_precision_autocast(device=self.device, fp16=self.fp16) + with autocast: + cast_network(self, dtype) + for idx_batch in batch_generator(range(len(images)), self.batch_size): + inpt_images = thread_pool_processing( + lambda x: convert_image(load_image(images[x])), idx_batch + ) + + inpt_trimaps = thread_pool_processing( + lambda x: convert_image(load_image(trimaps[x]), mode="L"), idx_batch + ) + + inpt_img_batches = thread_pool_processing( + self.data_preprocessing, inpt_images + ) + inpt_trimaps_batches = thread_pool_processing( + self.data_preprocessing, inpt_trimaps + ) + + inpt_img_batches_transformed = torch.vstack( + [i[1] for i in inpt_img_batches] + ) + inpt_img_batches = torch.vstack([i[0] for i in inpt_img_batches]) + + inpt_trimaps_transformed = torch.vstack( + [i[1] for i in inpt_trimaps_batches] + ) + inpt_trimaps_batches = torch.vstack( + [i[0] for i in inpt_trimaps_batches] + ) + + with torch.no_grad(): + inpt_img_batches = inpt_img_batches.to(self.device) + inpt_trimaps_batches = inpt_trimaps_batches.to(self.device) + inpt_img_batches_transformed = inpt_img_batches_transformed.to( + self.device + ) + inpt_trimaps_transformed = inpt_trimaps_transformed.to(self.device) + + output = super(FBAMatting, self).__call__( + inpt_img_batches, + inpt_trimaps_batches, + inpt_img_batches_transformed, + inpt_trimaps_transformed, + ) + output_cpu = output.cpu() + del ( + inpt_img_batches, + inpt_trimaps_batches, + inpt_img_batches_transformed, + inpt_trimaps_transformed, + output, + ) + masks = thread_pool_processing( + lambda x: self.data_postprocessing(output_cpu[x], inpt_trimaps[x]), + range(len(inpt_images)), + ) + collect_masks += masks + return collect_masks diff --git a/carvekit/ml/wrap/tracer_b7.py b/carvekit/ml/wrap/tracer_b7.py new file mode 100644 index 0000000000000000000000000000000000000000..20a8e458bf52a35038c12fdcd42226024266884a --- /dev/null +++ b/carvekit/ml/wrap/tracer_b7.py @@ -0,0 +1,178 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +import pathlib +import warnings +from typing import List, Union +import PIL.Image +import numpy as np +import torch +import torchvision.transforms as transforms +from PIL import Image + +from carvekit.ml.arch.tracerb7.tracer import TracerDecoder +from carvekit.ml.arch.tracerb7.efficientnet import EfficientEncoderB7 +from carvekit.ml.files.models_loc import tracer_b7_pretrained, tracer_hair_pretrained +from carvekit.utils.models_utils import get_precision_autocast, cast_network +from carvekit.utils.image_utils import load_image, convert_image +from carvekit.utils.pool_utils import thread_pool_processing, batch_generator + +__all__ = ["TracerUniversalB7"] + + +class TracerUniversalB7(TracerDecoder): + """TRACER B7 model interface""" + + def __init__( + self, + device="cpu", + input_image_size: Union[List[int], int] = 640, + batch_size: int = 4, + load_pretrained: bool = True, + fp16: bool = False, + model_path: Union[str, pathlib.Path] = None, + ): + """ + Initialize the U2NET model + + Args: + layers_cfg: neural network layers configuration + device: processing device + input_image_size: input image size + batch_size: the number of images that the neural network processes in one run + load_pretrained: loading pretrained model + fp16: use fp16 precision + + """ + if model_path is None: + model_path = tracer_b7_pretrained() + super(TracerUniversalB7, self).__init__( + encoder=EfficientEncoderB7(), + rfb_channel=[32, 64, 128], + features_channels=[48, 80, 224, 640], + ) + + self.fp16 = fp16 + self.device = device + self.batch_size = batch_size + if isinstance(input_image_size, list): + self.input_image_size = input_image_size[:2] + else: + self.input_image_size = (input_image_size, input_image_size) + + self.transform = transforms.Compose( + [ + transforms.ToTensor(), + transforms.Resize(self.input_image_size), + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), + ] + ) + self.to(device) + if load_pretrained: + # TODO remove edge detector from weights. It doesn't work well with this model! + self.load_state_dict( + torch.load(model_path, map_location=self.device), strict=False + ) + self.eval() + + def data_preprocessing(self, data: PIL.Image.Image) -> torch.FloatTensor: + """ + Transform input image to suitable data format for neural network + + Args: + data: input image + + Returns: + input for neural network + + """ + + return torch.unsqueeze(self.transform(data), 0).type(torch.FloatTensor) + + @staticmethod + def data_postprocessing( + data: torch.tensor, original_image: PIL.Image.Image + ) -> PIL.Image.Image: + """ + Transforms output data from neural network to suitable data + format for using with other components of this framework. + + Args: + data: output data from neural network + original_image: input image which was used for predicted data + + Returns: + Segmentation mask as PIL Image instance + + """ + output = (data.type(torch.FloatTensor).detach().cpu().numpy() * 255.0).astype( + np.uint8 + ) + output = output.squeeze(0) + mask = Image.fromarray(output).convert("L") + mask = mask.resize(original_image.size, resample=Image.BILINEAR) + return mask + + def __call__( + self, images: List[Union[str, pathlib.Path, PIL.Image.Image]] + ) -> List[PIL.Image.Image]: + """ + Passes input images though neural network and returns segmentation masks as PIL.Image.Image instances + + Args: + images: input images + + Returns: + segmentation masks as for input images, as PIL.Image.Image instances + + """ + collect_masks = [] + autocast, dtype = get_precision_autocast(device=self.device, fp16=self.fp16) + with autocast: + cast_network(self, dtype) + for image_batch in batch_generator(images, self.batch_size): + images = thread_pool_processing( + lambda x: convert_image(load_image(x)), image_batch + ) + batches = torch.vstack( + thread_pool_processing(self.data_preprocessing, images) + ) + with torch.no_grad(): + batches = batches.to(self.device) + masks = super(TracerDecoder, self).__call__(batches) + masks_cpu = masks.cpu() + del batches, masks + masks = thread_pool_processing( + lambda x: self.data_postprocessing(masks_cpu[x], images[x]), + range(len(images)), + ) + collect_masks += masks + + return collect_masks + + +class TracerHair(TracerUniversalB7): + """TRACER HAIR model interface""" + + def __init__( + self, + device="cpu", + input_image_size: Union[List[int], int] = 640, + batch_size: int = 4, + load_pretrained: bool = True, + fp16: bool = False, + model_path: Union[str, pathlib.Path] = None, + ): + if model_path is None: + model_path = tracer_hair_pretrained() + warnings.warn("TracerHair has not public model yet. Don't use it!", UserWarning) + super(TracerHair, self).__init__( + device=device, + input_image_size=input_image_size, + batch_size=batch_size, + load_pretrained=load_pretrained, + fp16=fp16, + model_path=model_path, + ) diff --git a/carvekit/ml/wrap/u2net.py b/carvekit/ml/wrap/u2net.py new file mode 100644 index 0000000000000000000000000000000000000000..7d126df2b93efc2d6d276b70173d5ff90043ce6d --- /dev/null +++ b/carvekit/ml/wrap/u2net.py @@ -0,0 +1,140 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +import pathlib +from typing import List, Union +import PIL.Image +import numpy as np +import torch +from PIL import Image + +from carvekit.ml.arch.u2net.u2net import U2NETArchitecture +from carvekit.ml.files.models_loc import u2net_full_pretrained +from carvekit.utils.image_utils import load_image, convert_image +from carvekit.utils.pool_utils import thread_pool_processing, batch_generator + +__all__ = ["U2NET"] + + +class U2NET(U2NETArchitecture): + """U^2-Net model interface""" + + def __init__( + self, + layers_cfg="full", + device="cpu", + input_image_size: Union[List[int], int] = 320, + batch_size: int = 10, + load_pretrained: bool = True, + fp16: bool = False, + ): + """ + Initialize the U2NET model + + Args: + layers_cfg: neural network layers configuration + device: processing device + input_image_size: input image size + batch_size: the number of images that the neural network processes in one run + load_pretrained: loading pretrained model + fp16: use fp16 precision // not supported at this moment. + + """ + super(U2NET, self).__init__(cfg_type=layers_cfg, out_ch=1) + self.device = device + self.batch_size = batch_size + if isinstance(input_image_size, list): + self.input_image_size = input_image_size[:2] + else: + self.input_image_size = (input_image_size, input_image_size) + self.to(device) + if load_pretrained: + self.load_state_dict( + torch.load(u2net_full_pretrained(), map_location=self.device) + ) + self.eval() + + def data_preprocessing(self, data: PIL.Image.Image) -> torch.FloatTensor: + """ + Transform input image to suitable data format for neural network + + Args: + data: input image + + Returns: + input for neural network + + """ + resized = data.resize(self.input_image_size, resample=3) + # noinspection PyTypeChecker + resized_arr = np.array(resized, dtype=float) + temp_image = np.zeros((resized_arr.shape[0], resized_arr.shape[1], 3)) + if np.max(resized_arr) != 0: + resized_arr /= np.max(resized_arr) + temp_image[:, :, 0] = (resized_arr[:, :, 0] - 0.485) / 0.229 + temp_image[:, :, 1] = (resized_arr[:, :, 1] - 0.456) / 0.224 + temp_image[:, :, 2] = (resized_arr[:, :, 2] - 0.406) / 0.225 + temp_image = temp_image.transpose((2, 0, 1)) + temp_image = np.expand_dims(temp_image, 0) + return torch.from_numpy(temp_image).type(torch.FloatTensor) + + @staticmethod + def data_postprocessing( + data: torch.tensor, original_image: PIL.Image.Image + ) -> PIL.Image.Image: + """ + Transforms output data from neural network to suitable data + format for using with other components of this framework. + + Args: + data: output data from neural network + original_image: input image which was used for predicted data + + Returns: + Segmentation mask as PIL Image instance + + """ + data = data.unsqueeze(0) + mask = data[:, 0, :, :] + ma = torch.max(mask) # Normalizes prediction + mi = torch.min(mask) + predict = ((mask - mi) / (ma - mi)).squeeze() + predict_np = predict.cpu().data.numpy() * 255 + mask = Image.fromarray(predict_np).convert("L") + mask = mask.resize(original_image.size, resample=3) + return mask + + def __call__( + self, images: List[Union[str, pathlib.Path, PIL.Image.Image]] + ) -> List[PIL.Image.Image]: + """ + Passes input images though neural network and returns segmentation masks as PIL.Image.Image instances + + Args: + images: input images + + Returns: + segmentation masks as for input images, as PIL.Image.Image instances + + """ + collect_masks = [] + for image_batch in batch_generator(images, self.batch_size): + images = thread_pool_processing( + lambda x: convert_image(load_image(x)), image_batch + ) + batches = torch.vstack( + thread_pool_processing(self.data_preprocessing, images) + ) + with torch.no_grad(): + batches = batches.to(self.device) + masks, d2, d3, d4, d5, d6, d7 = super(U2NET, self).__call__(batches) + masks_cpu = masks.cpu() + del d2, d3, d4, d5, d6, d7, batches, masks + masks = thread_pool_processing( + lambda x: self.data_postprocessing(masks_cpu[x], images[x]), + range(len(images)), + ) + collect_masks += masks + return collect_masks diff --git a/carvekit/pipelines/__init__.py b/carvekit/pipelines/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/pipelines/__pycache__/__init__.cpython-38.pyc b/carvekit/pipelines/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..836e6b8b0795cb8c3fd06ff2e3c045c5a9ad1450 Binary files /dev/null and b/carvekit/pipelines/__pycache__/__init__.cpython-38.pyc differ diff --git a/carvekit/pipelines/__pycache__/postprocessing.cpython-38.pyc b/carvekit/pipelines/__pycache__/postprocessing.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e6096ce16cfb01ff47cb6e516446c9b1ad28e0a Binary files /dev/null and b/carvekit/pipelines/__pycache__/postprocessing.cpython-38.pyc differ diff --git a/carvekit/pipelines/__pycache__/preprocessing.cpython-38.pyc b/carvekit/pipelines/__pycache__/preprocessing.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6b8259211fa4193038d9e60d0ec1960b83e54ff Binary files /dev/null and b/carvekit/pipelines/__pycache__/preprocessing.cpython-38.pyc differ diff --git a/carvekit/pipelines/postprocessing.py b/carvekit/pipelines/postprocessing.py new file mode 100644 index 0000000000000000000000000000000000000000..fc22451c9a527ff14dac053e8a74d99e8357b360 --- /dev/null +++ b/carvekit/pipelines/postprocessing.py @@ -0,0 +1,76 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +from carvekit.ml.wrap.fba_matting import FBAMatting +from typing import Union, List +from PIL import Image +from pathlib import Path +from carvekit.trimap.cv_gen import CV2TrimapGenerator +from carvekit.trimap.generator import TrimapGenerator +from carvekit.utils.mask_utils import apply_mask +from carvekit.utils.pool_utils import thread_pool_processing +from carvekit.utils.image_utils import load_image, convert_image + +__all__ = ["MattingMethod"] + + +class MattingMethod: + """ + Improving the edges of the object mask using neural networks for matting and algorithms for creating trimap. + Neural network for matting performs accurate object edge detection by using a special map called trimap, + with unknown area that we scan for boundary, already known general object area and the background.""" + + def __init__( + self, + matting_module: Union[FBAMatting], + trimap_generator: Union[TrimapGenerator, CV2TrimapGenerator], + device="cpu", + ): + """ + Initializes Matting Method class. + + Args: + matting_module: Initialized matting neural network class + trimap_generator: Initialized trimap generator class + device: Processing device used for applying mask to image + """ + self.device = device + self.matting_module = matting_module + self.trimap_generator = trimap_generator + + def __call__( + self, + images: List[Union[str, Path, Image.Image]], + masks: List[Union[str, Path, Image.Image]], + ): + """ + Passes data through apply_mask function + + Args: + images: list of images + masks: list pf masks + + Returns: + list of images + """ + if len(images) != len(masks): + raise ValueError("Images and Masks lists should have same length!") + images = thread_pool_processing(lambda x: convert_image(load_image(x)), images) + masks = thread_pool_processing( + lambda x: convert_image(load_image(x), mode="L"), masks + ) + trimaps = thread_pool_processing( + lambda x: self.trimap_generator(original_image=images[x], mask=masks[x]), + range(len(images)), + ) + alpha = self.matting_module(images=images, trimaps=trimaps) + return list( + map( + lambda x: apply_mask( + image=images[x], mask=alpha[x], device=self.device + ), + range(len(images)), + ) + ) diff --git a/carvekit/pipelines/preprocessing.py b/carvekit/pipelines/preprocessing.py new file mode 100644 index 0000000000000000000000000000000000000000..3d1e848d10bb99ddc06b84fbc52c9d0f36ebe1c0 --- /dev/null +++ b/carvekit/pipelines/preprocessing.py @@ -0,0 +1,28 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +from pathlib import Path +from typing import Union, List + +from PIL import Image + +__all__ = ["PreprocessingStub"] + + +class PreprocessingStub: + """Stub for future preprocessing methods""" + + def __call__(self, interface, images: List[Union[str, Path, Image.Image]]): + """ + Passes data though interface.segmentation_pipeline() method + + Args: + interface: Interface instance + images: list of images + + Returns: + the result of passing data through segmentation_pipeline method of interface + """ + return interface.segmentation_pipeline(images=images) diff --git a/carvekit/trimap/__init__.py b/carvekit/trimap/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/trimap/__pycache__/__init__.cpython-38.pyc b/carvekit/trimap/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8dcb89b578b83f2a8e075e23f2b8b00fd45d92ba Binary files /dev/null and b/carvekit/trimap/__pycache__/__init__.cpython-38.pyc differ diff --git a/carvekit/trimap/__pycache__/add_ops.cpython-38.pyc b/carvekit/trimap/__pycache__/add_ops.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2475dd616c11d5b9d803debaf2267351e821f38 Binary files /dev/null and b/carvekit/trimap/__pycache__/add_ops.cpython-38.pyc differ diff --git a/carvekit/trimap/__pycache__/cv_gen.cpython-38.pyc b/carvekit/trimap/__pycache__/cv_gen.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb55fd41528ff3e7276db4d386ab0ef83a5d3327 Binary files /dev/null and b/carvekit/trimap/__pycache__/cv_gen.cpython-38.pyc differ diff --git a/carvekit/trimap/__pycache__/generator.cpython-38.pyc b/carvekit/trimap/__pycache__/generator.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed243d5ea370a89cda4dfdb1f0a91a0dd7129fd3 Binary files /dev/null and b/carvekit/trimap/__pycache__/generator.cpython-38.pyc differ diff --git a/carvekit/trimap/add_ops.py b/carvekit/trimap/add_ops.py new file mode 100644 index 0000000000000000000000000000000000000000..dfb37ca63680a515bcb4a0e4d50823f2ba6f0685 --- /dev/null +++ b/carvekit/trimap/add_ops.py @@ -0,0 +1,91 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +import cv2 +import numpy as np +from PIL import Image + + +def prob_filter(mask: Image.Image, prob_threshold=231) -> Image.Image: + """ + Applies a filter to the mask by the probability of locating an object in the object area. + + Args: + prob_threshold: Threshold of probability for mark area as background. + mask: Predicted object mask + + Raises: + ValueError if mask or trimap has wrong color mode + + Returns: + Generated trimap for image. + """ + if mask.mode != "L": + raise ValueError("Input mask has wrong color mode.") + # noinspection PyTypeChecker + mask_array = np.array(mask) + mask_array[mask_array > prob_threshold] = 255 # Probability filter for mask + mask_array[mask_array <= prob_threshold] = 0 + return Image.fromarray(mask_array).convert("L") + + +def prob_as_unknown_area( + trimap: Image.Image, mask: Image.Image, prob_threshold=255 +) -> Image.Image: + """ + Marks any uncertainty in the seg mask as an unknown region. + + Args: + prob_threshold: Threshold of probability for mark area as unknown. + trimap: Generated trimap. + mask: Predicted object mask + + Raises: + ValueError if mask or trimap has wrong color mode + + Returns: + Generated trimap for image. + """ + if mask.mode != "L" or trimap.mode != "L": + raise ValueError("Input mask has wrong color mode.") + # noinspection PyTypeChecker + mask_array = np.array(mask) + # noinspection PyTypeChecker + trimap_array = np.array(trimap) + trimap_array[np.logical_and(mask_array <= prob_threshold, mask_array > 0)] = 127 + return Image.fromarray(trimap_array).convert("L") + + +def post_erosion(trimap: Image.Image, erosion_iters=1) -> Image.Image: + """ + Performs erosion on the mask and marks the resulting area as an unknown region. + + Args: + erosion_iters: The number of iterations of erosion that + the object's mask will be subjected to before forming an unknown area + trimap: Generated trimap. + mask: Predicted object mask + + Returns: + Generated trimap for image. + """ + if trimap.mode != "L": + raise ValueError("Input mask has wrong color mode.") + # noinspection PyTypeChecker + trimap_array = np.array(trimap) + if erosion_iters > 0: + without_unknown_area = trimap_array.copy() + without_unknown_area[without_unknown_area == 127] = 0 + + erosion_kernel = np.ones((3, 3), np.uint8) + erode = cv2.erode( + without_unknown_area, erosion_kernel, iterations=erosion_iters + ) + erode = np.where(erode == 0, 0, without_unknown_area) + trimap_array[np.logical_and(erode == 0, without_unknown_area > 0)] = 127 + erode = trimap_array.copy() + else: + erode = trimap_array.copy() + return Image.fromarray(erode).convert("L") diff --git a/carvekit/trimap/cv_gen.py b/carvekit/trimap/cv_gen.py new file mode 100644 index 0000000000000000000000000000000000000000..fc2c229d7a3b1f683a4e9f9be7086783ab418654 --- /dev/null +++ b/carvekit/trimap/cv_gen.py @@ -0,0 +1,64 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +import PIL.Image +import cv2 +import numpy as np + + +class CV2TrimapGenerator: + def __init__(self, kernel_size: int = 30, erosion_iters: int = 1): + """ + Initialize a new CV2TrimapGenerator instance + + Args: + kernel_size: The size of the offset from the object mask + in pixels when an unknown area is detected in the trimap + erosion_iters: The number of iterations of erosion that + the object's mask will be subjected to before forming an unknown area + """ + self.kernel_size = kernel_size + self.erosion_iters = erosion_iters + + def __call__( + self, original_image: PIL.Image.Image, mask: PIL.Image.Image + ) -> PIL.Image.Image: + """ + Generates trimap based on predicted object mask to refine object mask borders. + Based on cv2 erosion algorithm. + + Args: + original_image: Original image + mask: Predicted object mask + + Returns: + Generated trimap for image. + """ + if mask.mode != "L": + raise ValueError("Input mask has wrong color mode.") + if mask.size != original_image.size: + raise ValueError("Sizes of input image and predicted mask doesn't equal") + # noinspection PyTypeChecker + mask_array = np.array(mask) + pixels = 2 * self.kernel_size + 1 + kernel = np.ones((pixels, pixels), np.uint8) + + if self.erosion_iters > 0: + erosion_kernel = np.ones((3, 3), np.uint8) + erode = cv2.erode(mask_array, erosion_kernel, iterations=self.erosion_iters) + erode = np.where(erode == 0, 0, mask_array) + else: + erode = mask_array.copy() + + dilation = cv2.dilate(erode, kernel, iterations=1) + + dilation = np.where(dilation == 255, 127, dilation) # WHITE to GRAY + trimap = np.where(erode > 127, 200, dilation) # mark the tumor inside GRAY + + trimap = np.where(trimap < 127, 0, trimap) # Embelishment + trimap = np.where(trimap > 200, 0, trimap) # Embelishment + trimap = np.where(trimap == 200, 255, trimap) # GRAY to WHITE + + return PIL.Image.fromarray(trimap).convert("L") diff --git a/carvekit/trimap/generator.py b/carvekit/trimap/generator.py new file mode 100644 index 0000000000000000000000000000000000000000..0656f4590c2e33ff0ed535e85fe8ca1266bd9cb5 --- /dev/null +++ b/carvekit/trimap/generator.py @@ -0,0 +1,47 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +from PIL import Image +from carvekit.trimap.cv_gen import CV2TrimapGenerator +from carvekit.trimap.add_ops import prob_filter, prob_as_unknown_area, post_erosion + + +class TrimapGenerator(CV2TrimapGenerator): + def __init__( + self, prob_threshold: int = 231, kernel_size: int = 30, erosion_iters: int = 5 + ): + """ + Initialize a TrimapGenerator instance + + Args: + prob_threshold: Probability threshold at which the + prob_filter and prob_as_unknown_area operations will be applied + kernel_size: The size of the offset from the object mask + in pixels when an unknown area is detected in the trimap + erosion_iters: The number of iterations of erosion that + the object's mask will be subjected to before forming an unknown area + """ + super().__init__(kernel_size, erosion_iters=0) + self.prob_threshold = prob_threshold + self.__erosion_iters = erosion_iters + + def __call__(self, original_image: Image.Image, mask: Image.Image) -> Image.Image: + """ + Generates trimap based on predicted object mask to refine object mask borders. + Based on cv2 erosion algorithm and additional prob. filters. + Args: + original_image: Original image + mask: Predicted object mask + + Returns: + Generated trimap for image. + """ + filter_mask = prob_filter(mask=mask, prob_threshold=self.prob_threshold) + trimap = super(TrimapGenerator, self).__call__(original_image, filter_mask) + new_trimap = prob_as_unknown_area( + trimap=trimap, mask=mask, prob_threshold=self.prob_threshold + ) + new_trimap = post_erosion(new_trimap, self.__erosion_iters) + return new_trimap diff --git a/carvekit/utils/__init__.py b/carvekit/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/utils/__pycache__/__init__.cpython-38.pyc b/carvekit/utils/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8896143d15e97f8e6c9051963f95b1f970c71ff Binary files /dev/null and b/carvekit/utils/__pycache__/__init__.cpython-38.pyc differ diff --git a/carvekit/utils/__pycache__/download_models.cpython-38.pyc b/carvekit/utils/__pycache__/download_models.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca10257830d809666c87306e0d9b47b161484a1e Binary files /dev/null and b/carvekit/utils/__pycache__/download_models.cpython-38.pyc differ diff --git a/carvekit/utils/__pycache__/image_utils.cpython-38.pyc b/carvekit/utils/__pycache__/image_utils.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a61bc5110bb3c28b3d0ac7badd868f83ce0abf09 Binary files /dev/null and b/carvekit/utils/__pycache__/image_utils.cpython-38.pyc differ diff --git a/carvekit/utils/__pycache__/mask_utils.cpython-38.pyc b/carvekit/utils/__pycache__/mask_utils.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f1d4da74b5ed39cedea0b2438764f575629708d Binary files /dev/null and b/carvekit/utils/__pycache__/mask_utils.cpython-38.pyc differ diff --git a/carvekit/utils/__pycache__/models_utils.cpython-38.pyc b/carvekit/utils/__pycache__/models_utils.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e91d41e120b7f2d14bb7e501b5e9a18f4844c6a4 Binary files /dev/null and b/carvekit/utils/__pycache__/models_utils.cpython-38.pyc differ diff --git a/carvekit/utils/__pycache__/pool_utils.cpython-38.pyc b/carvekit/utils/__pycache__/pool_utils.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b60e281e1e8a08f9e9b193e8020187e5b31f6326 Binary files /dev/null and b/carvekit/utils/__pycache__/pool_utils.cpython-38.pyc differ diff --git a/carvekit/utils/download_models.py b/carvekit/utils/download_models.py new file mode 100644 index 0000000000000000000000000000000000000000..b1b52adfa2ba66b2ed88a6a231912c259b60df48 --- /dev/null +++ b/carvekit/utils/download_models.py @@ -0,0 +1,214 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +import hashlib +import os +import warnings +from abc import ABCMeta, abstractmethod, ABC +from pathlib import Path +from typing import Optional + +import carvekit +from carvekit.ml.files import checkpoints_dir + +import requests +import tqdm + +requests = requests.Session() +requests.headers.update({"User-Agent": f"Carvekit/{carvekit.version}"}) + +MODELS_URLS = { + "basnet.pth": { + "repository": "Carve/basnet-universal", + "revision": "870becbdb364fda6d8fdb2c10b072542f8d08701", + "filename": "basnet.pth", + }, + "deeplab.pth": { + "repository": "Carve/deeplabv3-resnet101", + "revision": "d504005392fc877565afdf58aad0cd524682d2b0", + "filename": "deeplab.pth", + }, + "fba_matting.pth": { + "repository": "Carve/fba", + "revision": "a5d3457df0fb9c88ea19ed700d409756ca2069d1", + "filename": "fba_matting.pth", + }, + "u2net.pth": { + "repository": "Carve/u2net-universal", + "revision": "10305d785481cf4b2eee1d447c39cd6e5f43d74b", + "filename": "full_weights.pth", + }, + "tracer_b7.pth": { + "repository": "Carve/tracer_b7", + "revision": "d8a8fd9e7b3fa0d2f1506fe7242966b34381e9c5", + "filename": "tracer_b7.pth", + }, + "tracer_hair.pth": { + "repository": "Carve/tracer_b7", + "revision": "d8a8fd9e7b3fa0d2f1506fe7242966b34381e9c5", + "filename": "tracer_b7.pth", # TODO don't forget change this link!! + }, +} + +MODELS_CHECKSUMS = { + "basnet.pth": "e409cb709f4abca87cb11bd44a9ad3f909044a917977ab65244b4c94dd33" + "8b1a37755c4253d7cb54526b7763622a094d7b676d34b5e6886689256754e5a5e6ad", + "deeplab.pth": "9c5a1795bc8baa267200a44b49ac544a1ba2687d210f63777e4bd715387324469a59b072f8a28" + "9cc471c637b367932177e5b312e8ea6351c1763d9ff44b4857c", + "fba_matting.pth": "890906ec94c1bfd2ad08707a63e4ccb0955d7f5d25e32853950c24c78" + "4cbad2e59be277999defc3754905d0f15aa75702cdead3cfe669ff72f08811c52971613", + "u2net.pth": "16f8125e2fedd8c85db0e001ee15338b4aa2fda77bab8ba70c25e" + "bea1533fda5ee70a909b934a9bd495b432cef89d629f00a07858a517742476fa8b346de24f7", + "tracer_b7.pth": "c439c5c12d4d43d5f9be9ec61e68b2e54658a541bccac2577ef5a54fb252b6e8415d41f7e" + "c2487033d0c02b4dd08367958e4e62091318111c519f93e2632be7b", + "tracer_hair.pth": "5c2fb9973fc42fa6208920ffa9ac233cc2ea9f770b24b4a96969d3449aed7ac89e6d37e" + "e486a13e63be5499f2df6ccef1109e9e8797d1326207ac89b2f39a7cf", +} + + +def sha512_checksum_calc(file: Path) -> str: + """ + Calculates the SHA512 hash digest of a file on fs + + Args: + file: Path to the file + + Returns: + SHA512 hash digest of a file. + """ + dd = hashlib.sha512() + with file.open("rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + dd.update(chunk) + return dd.hexdigest() + + +class CachedDownloader: + __metaclass__ = ABCMeta + + @property + @abstractmethod + def name(self) -> str: + return self.__class__.__name__ + + @property + @abstractmethod + def fallback_downloader(self) -> Optional["CachedDownloader"]: + pass + + def download_model(self, file_name: str) -> Path: + try: + return self.download_model_base(file_name) + except BaseException as e: + if self.fallback_downloader is not None: + warnings.warn( + f"Failed to download model from {self.name} downloader." + f" Trying to download from {self.fallback_downloader.name} downloader." + ) + return self.fallback_downloader.download_model(file_name) + else: + warnings.warn( + f"Failed to download model from {self.name} downloader." + f" No fallback downloader available." + ) + raise e + + @abstractmethod + def download_model_base(self, file_name: str) -> Path: + """Download model from any source if not cached. Returns path if cached""" + + def __call__(self, file_name: str): + return self.download_model(file_name) + + +class HuggingFaceCompatibleDownloader(CachedDownloader, ABC): + def __init__( + self, + name: str = "Huggingface.co", + base_url: str = "https://huggingface.co", + fb_downloader: Optional["CachedDownloader"] = None, + ): + self.cache_dir = checkpoints_dir + self.base_url = base_url + self._name = name + self._fallback_downloader = fb_downloader + + @property + def fallback_downloader(self) -> Optional["CachedDownloader"]: + return self._fallback_downloader + + @property + def name(self): + return self._name + + def check_for_existence(self, file_name: str) -> Optional[Path]: + if file_name not in MODELS_URLS.keys(): + raise FileNotFoundError("Unknown model!") + path = ( + self.cache_dir + / MODELS_URLS[file_name]["repository"].split("/")[1] + / file_name + ) + + if not path.exists(): + return None + + if MODELS_CHECKSUMS[path.name] != sha512_checksum_calc(path): + warnings.warn( + f"Invalid checksum for model {path.name}. Downloading correct model!" + ) + os.remove(path) + return None + return path + + def download_model_base(self, file_name: str) -> Path: + cached_path = self.check_for_existence(file_name) + if cached_path is not None: + return cached_path + else: + cached_path = ( + self.cache_dir + / MODELS_URLS[file_name]["repository"].split("/")[1] + / file_name + ) + cached_path.parent.mkdir(parents=True, exist_ok=True) + url = MODELS_URLS[file_name] + hugging_face_url = f"{self.base_url}/{url['repository']}/resolve/{url['revision']}/{url['filename']}" + + try: + r = requests.get(hugging_face_url, stream=True, timeout=10) + if r.status_code < 400: + with open(cached_path, "wb") as f: + r.raw.decode_content = True + for chunk in tqdm.tqdm( + r, + desc="Downloading " + cached_path.name + " model", + colour="blue", + ): + f.write(chunk) + else: + if r.status_code == 404: + raise FileNotFoundError(f"Model {file_name} not found!") + else: + raise ConnectionError( + f"Error {r.status_code} while downloading model {file_name}!" + ) + except BaseException as e: + if cached_path.exists(): + os.remove(cached_path) + raise ConnectionError( + f"Exception caught when downloading model! " + f"Model name: {cached_path.name}. Exception: {str(e)}." + ) + return cached_path + + +fallback_downloader: CachedDownloader = HuggingFaceCompatibleDownloader() +downloader: CachedDownloader = HuggingFaceCompatibleDownloader( + base_url="https://cdn.carve.photos", + fb_downloader=fallback_downloader, + name="Carve CDN", +) +downloader._fallback_downloader = fallback_downloader diff --git a/carvekit/utils/fs_utils.py b/carvekit/utils/fs_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..bd6291e96dd7489a0cff560e75a80fa9c7d2d3d0 --- /dev/null +++ b/carvekit/utils/fs_utils.py @@ -0,0 +1,38 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +from pathlib import Path +from PIL import Image +import warnings +from typing import Optional + + +def save_file(output: Optional[Path], input_path: Path, image: Image.Image): + """ + Saves an image to the file system + + Args: + output: Output path [dir or end file] + input_path: Input path of the image + image: Image to be saved. + """ + if isinstance(output, Path) and str(output) != "none": + if output.is_dir() and output.exists(): + image.save(output.joinpath(input_path.with_suffix(".png").name)) + elif output.suffix != "": + if output.suffix != ".png": + warnings.warn( + f"Only export with .png extension is supported! Your {output.suffix}" + f" extension will be ignored and replaced with .png!" + ) + image.save(output.with_suffix(".png")) + else: + raise ValueError("Wrong output path!") + elif output is None or str(output) == "none": + image.save( + input_path.with_name( + input_path.stem.split(".")[0] + "_bg_removed" + ).with_suffix(".png") + ) diff --git a/carvekit/utils/image_utils.py b/carvekit/utils/image_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..8b939f56a1041f3fb3db1b6e9874d0c8c99473c7 --- /dev/null +++ b/carvekit/utils/image_utils.py @@ -0,0 +1,150 @@ +""" + Source url: https://github.com/OPHoperHPO/image-background-remove-tool + Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. + License: Apache License 2.0 +""" + +import pathlib +from typing import Union, Any, Tuple + +import PIL.Image +import numpy as np +import torch + +ALLOWED_SUFFIXES = [".jpg", ".jpeg", ".bmp", ".png", ".webp"] + + +def to_tensor(x: Any) -> torch.Tensor: + """ + Returns a PIL.Image.Image as torch tensor without swap tensor dims. + + Args: + x: PIL.Image.Image instance + + Returns: + torch.Tensor instance + """ + return torch.tensor(np.array(x, copy=True)) + + +def load_image(file: Union[str, pathlib.Path, PIL.Image.Image]) -> PIL.Image.Image: + """Returns a PIL.Image.Image class by string path or pathlib path or PIL.Image.Image instance + + Args: + file: File path or PIL.Image.Image instance + + Returns: + PIL.Image.Image instance + + Raises: + ValueError: If file not exists or file is directory or file isn't an image or file is not correct PIL Image + + """ + if isinstance(file, str) and is_image_valid(pathlib.Path(file)): + return PIL.Image.open(file) + elif isinstance(file, PIL.Image.Image): + return file + elif isinstance(file, pathlib.Path) and is_image_valid(file): + return PIL.Image.open(str(file)) + else: + raise ValueError("Unknown input file type") + + +def convert_image(image: PIL.Image.Image, mode="RGB") -> PIL.Image.Image: + """Performs image conversion to correct color mode + + Args: + image: PIL.Image.Image instance + mode: Colort Mode to convert + + Returns: + PIL.Image.Image instance + + Raises: + ValueError: If image hasn't convertable color mode, or it is too small + """ + if is_image_valid(image): + return image.convert(mode) + + +def is_image_valid(image: Union[pathlib.Path, PIL.Image.Image]) -> bool: + """This function performs image validation. + + Args: + image: Path to the image or PIL.Image.Image instance being checked. + + Returns: + True if image is valid + + Raises: + ValueError: If file not a valid image path or image hasn't convertable color mode, or it is too small + + """ + if isinstance(image, pathlib.Path): + if not image.exists(): + raise ValueError("File is not exists") + elif image.is_dir(): + raise ValueError("File is a directory") + elif image.suffix.lower() not in ALLOWED_SUFFIXES: + raise ValueError( + f"Unsupported image format. Supported file formats: {', '.join(ALLOWED_SUFFIXES)}" + ) + elif isinstance(image, PIL.Image.Image): + if not (image.size[0] > 32 and image.size[1] > 32): + raise ValueError("Image should be bigger then (32x32) pixels.") + elif image.mode not in ["RGB", "RGBA", "L"]: + raise ValueError("Wrong image color mode.") + else: + raise ValueError("Unknown input file type") + return True + + +def transparency_paste( + bg_img: PIL.Image.Image, fg_img: PIL.Image.Image, box=(0, 0) +) -> PIL.Image.Image: + """ + Inserts an image into another image while maintaining transparency. + + Args: + bg_img: background image + fg_img: foreground image + box: place to paste + + Returns: + Background image with pasted foreground image at point or in the specified box + """ + fg_img_trans = PIL.Image.new("RGBA", bg_img.size) + fg_img_trans.paste(fg_img, box, mask=fg_img) + new_img = PIL.Image.alpha_composite(bg_img, fg_img_trans) + return new_img + + +def add_margin( + pil_img: PIL.Image.Image, + top: int, + right: int, + bottom: int, + left: int, + color: Tuple[int, int, int, int], +) -> PIL.Image.Image: + """ + Adds margin to the image. + + Args: + pil_img: Image that needed to add margin. + top: pixels count at top side + right: pixels count at right side + bottom: pixels count at bottom side + left: pixels count at left side + color: color of margin + + Returns: + Image with margin. + """ + width, height = pil_img.size + new_width = width + right + left + new_height = height + top + bottom + # noinspection PyTypeChecker + result = PIL.Image.new(pil_img.mode, (new_width, new_height), color) + result.paste(pil_img, (left, top)) + return result diff --git a/carvekit/utils/mask_utils.py b/carvekit/utils/mask_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..440203631cf53ff727c301b9683c018081a3e168 --- /dev/null +++ b/carvekit/utils/mask_utils.py @@ -0,0 +1,85 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +import PIL.Image +import torch +from carvekit.utils.image_utils import to_tensor + + +def composite( + foreground: PIL.Image.Image, + background: PIL.Image.Image, + alpha: PIL.Image.Image, + device="cpu", +): + """ + Composites foreground with background by following + https://pymatting.github.io/intro.html#alpha-matting math formula. + + Args: + device: Processing device + foreground: Image that will be pasted to background image with following alpha mask. + background: Background image + alpha: Alpha Image + + Returns: + Composited image as PIL.Image instance. + """ + + foreground = foreground.convert("RGBA") + background = background.convert("RGBA") + alpha_rgba = alpha.convert("RGBA") + alpha_l = alpha.convert("L") + + fg = to_tensor(foreground).to(device) + alpha_rgba = to_tensor(alpha_rgba).to(device) + alpha_l = to_tensor(alpha_l).to(device) + bg = to_tensor(background).to(device) + + alpha_l = alpha_l / 255 + alpha_rgba = alpha_rgba / 255 + + bg = torch.where(torch.logical_not(alpha_rgba >= 1), bg, fg) + bg[:, :, 0] = alpha_l[:, :] * fg[:, :, 0] + (1 - alpha_l[:, :]) * bg[:, :, 0] + bg[:, :, 1] = alpha_l[:, :] * fg[:, :, 1] + (1 - alpha_l[:, :]) * bg[:, :, 1] + bg[:, :, 2] = alpha_l[:, :] * fg[:, :, 2] + (1 - alpha_l[:, :]) * bg[:, :, 2] + bg[:, :, 3] = alpha_l[:, :] * 255 + + del alpha_l, alpha_rgba, fg + return PIL.Image.fromarray(bg.cpu().numpy()).convert("RGBA") + + +def apply_mask( + image: PIL.Image.Image, mask: PIL.Image.Image, device="cpu" +) -> PIL.Image.Image: + """ + Applies mask to foreground. + + Args: + device: Processing device. + image: Image with background. + mask: Alpha Channel mask for this image. + + Returns: + Image without background, where mask was black. + """ + background = PIL.Image.new("RGBA", image.size, color=(130, 130, 130, 0)) + return composite(image, background, mask, device=device).convert("RGBA") + + +def extract_alpha_channel(image: PIL.Image.Image) -> PIL.Image.Image: + """ + Extracts alpha channel from the RGBA image. + + Args: + image: RGBA PIL image + + Returns: + RGBA alpha channel image + """ + alpha = image.split()[-1] + bg = PIL.Image.new("RGBA", image.size, (0, 0, 0, 255)) + bg.paste(alpha, mask=alpha) + return bg.convert("RGBA") diff --git a/carvekit/utils/models_utils.py b/carvekit/utils/models_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..da0141de0d3ed2d90559f5f590022e62a5e659aa --- /dev/null +++ b/carvekit/utils/models_utils.py @@ -0,0 +1,126 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" + +import random +import warnings +from typing import Union, Tuple, Any + +import torch +from torch import autocast + + +class EmptyAutocast(object): + """ + Empty class for disable any autocasting. + """ + + def __enter__(self): + return None + + def __exit__(self, exc_type, exc_val, exc_tb): + return + + def __call__(self, func): + return + + +def get_precision_autocast( + device="cpu", fp16=True, override_dtype=None +) -> Union[ + Tuple[EmptyAutocast, Union[torch.dtype, Any]], + Tuple[autocast, Union[torch.dtype, Any]], +]: + """ + Returns precision and autocast settings for given device and fp16 settings. + Args: + device: Device to get precision and autocast settings for. + fp16: Whether to use fp16 precision. + override_dtype: Override dtype for autocast. + + Returns: + Autocast object, dtype + """ + dtype = torch.float32 + cache_enabled = None + + if device == "cpu" and fp16: + warnings.warn('FP16 is not supported on CPU. Using FP32 instead.') + dtype = torch.float32 + + # TODO: Implement BFP16 on CPU. There are unexpected slowdowns on cpu on a clean environment. + # warnings.warn( + # "Accuracy BFP16 has experimental support on the CPU. " + # "This may result in an unexpected reduction in quality." + # ) + # dtype = ( + # torch.bfloat16 + # ) # Using bfloat16 for CPU, since autocast is not supported for float16 + + + if "cuda" in device and fp16: + dtype = torch.float16 + cache_enabled = True + + if override_dtype is not None: + dtype = override_dtype + + if dtype == torch.float32 and device == "cpu": + return EmptyAutocast(), dtype + + return ( + torch.autocast( + device_type=device, dtype=dtype, enabled=True, cache_enabled=cache_enabled + ), + dtype, + ) + + +def cast_network(network: torch.nn.Module, dtype: torch.dtype): + """Cast network to given dtype + + Args: + network: Network to be casted + dtype: Dtype to cast network to + """ + if dtype == torch.float16: + network.half() + elif dtype == torch.bfloat16: + network.bfloat16() + elif dtype == torch.float32: + network.float() + else: + raise ValueError(f"Unknown dtype {dtype}") + + +def fix_seed(seed=42): + """Sets fixed random seed + + Args: + seed: Random seed to be set + """ + random.seed(seed) + torch.manual_seed(seed) + if torch.cuda.is_available(): + torch.cuda.manual_seed(seed) + torch.cuda.manual_seed_all(seed) + # noinspection PyUnresolvedReferences + torch.backends.cudnn.deterministic = True + # noinspection PyUnresolvedReferences + torch.backends.cudnn.benchmark = False + return True + + +def suppress_warnings(): + # Suppress PyTorch 1.11.0 warning associated with changing order of args in nn.MaxPool2d layer, + # since source code is not affected by this issue and there aren't any other correct way to hide this message. + warnings.filterwarnings( + "ignore", + category=UserWarning, + message="Note that order of the arguments: ceil_mode and " + "return_indices will changeto match the args list " + "in nn.MaxPool2d in a future release.", + module="torch", + ) diff --git a/carvekit/utils/pool_utils.py b/carvekit/utils/pool_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..ae3b741b54e9edcbb2d5e631c3f158465e3ccb48 --- /dev/null +++ b/carvekit/utils/pool_utils.py @@ -0,0 +1,40 @@ +""" +Source url: https://github.com/OPHoperHPO/image-background-remove-tool +Author: Nikita Selin (OPHoperHPO)[https://github.com/OPHoperHPO]. +License: Apache License 2.0 +""" +from concurrent.futures import ThreadPoolExecutor +from typing import Any, Iterable + + +def thread_pool_processing(func: Any, data: Iterable, workers=18): + """ + Passes all iterator data through the given function + + Args: + workers: Count of workers. + func: function to pass data through + data: input iterator + + Returns: + function return list + + """ + with ThreadPoolExecutor(workers) as p: + return list(p.map(func, data)) + + +def batch_generator(iterable, n=1): + """ + Splits any iterable into n-size packets + + Args: + iterable: iterator + n: size of packets + + Returns: + new n-size packet + """ + it = len(iterable) + for ndx in range(0, it, n): + yield iterable[ndx : min(ndx + n, it)] diff --git a/carvekit/web/__init__.py b/carvekit/web/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/web/app.py b/carvekit/web/app.py new file mode 100644 index 0000000000000000000000000000000000000000..cea35269297433799ee28415a946d20bb1f2d8a7 --- /dev/null +++ b/carvekit/web/app.py @@ -0,0 +1,30 @@ +from pathlib import Path + +import uvicorn +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from starlette.staticfiles import StaticFiles + +from carvekit import version +from carvekit.web.deps import config +from carvekit.web.routers.api_router import api_router + +app = FastAPI(title="CarveKit Web API", version=version) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +app.include_router(api_router, prefix="/api") +app.mount( + "/", + StaticFiles(directory=Path(__file__).parent.joinpath("static"), html=True), + name="static", +) + +if __name__ == "__main__": + uvicorn.run(app, host=config.host, port=config.port) diff --git a/carvekit/web/deps.py b/carvekit/web/deps.py new file mode 100644 index 0000000000000000000000000000000000000000..37a41ae932ff6daf93cfac6ec92344ab3bf1e2cc --- /dev/null +++ b/carvekit/web/deps.py @@ -0,0 +1,6 @@ +from carvekit.web.schemas.config import WebAPIConfig +from carvekit.web.utils.init_utils import init_config +from carvekit.web.utils.task_queue import MLProcessor + +config: WebAPIConfig = init_config() +ml_processor = MLProcessor(api_config=config) diff --git a/carvekit/web/handlers/__init__.py b/carvekit/web/handlers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/web/handlers/response.py b/carvekit/web/handlers/response.py new file mode 100644 index 0000000000000000000000000000000000000000..f359b3c7746fbf96c8c7600756cdf64cd59997b0 --- /dev/null +++ b/carvekit/web/handlers/response.py @@ -0,0 +1,61 @@ +from typing import Union + +from fastapi import Header +from fastapi.responses import Response, JSONResponse +from carvekit.web.deps import config + + +def Authenticate(x_api_key: Union[str, None] = Header(None)) -> Union[bool, str]: + if x_api_key in config.auth.allowed_tokens: + return "allowed" + elif x_api_key == config.auth.admin_token: + return "admin" + elif config.auth.auth is False: + return "allowed" + else: + return False + + +def handle_response(response, original_image) -> Response: + """ + Response handler from TaskQueue + :param response: TaskQueue response + :param original_image: Original PIL image + :return: Complete flask response + """ + response_object = None + if isinstance(response, dict): + if response["type"] == "jpg": + response_object = Response( + content=response["data"][0].read(), media_type="image/jpeg" + ) + elif response["type"] == "png": + response_object = Response( + content=response["data"][0].read(), media_type="image/png" + ) + elif response["type"] == "zip": + response_object = Response( + content=response["data"][0], media_type="application/zip" + ) + response_object.headers[ + "Content-Disposition" + ] = "attachment; filename='no-bg.zip'" + + # Add headers to output result + response_object.headers["X-Credits-Charged"] = "0" + response_object.headers["X-Type"] = "other" # TODO Make support for this + response_object.headers["X-Max-Width"] = str(original_image.size[0]) + response_object.headers["X-Max-Height"] = str(original_image.size[1]) + response_object.headers[ + "X-Ratelimit-Limit" + ] = "500" # TODO Make ratelimit support + response_object.headers["X-Ratelimit-Remaining"] = "500" + response_object.headers["X-Ratelimit-Reset"] = "1" + response_object.headers["X-Width"] = str(response["data"][1][0]) + response_object.headers["X-Height"] = str(response["data"][1][1]) + + else: + response = JSONResponse(content=response[0]) + response.headers["X-Credits-Charged"] = "0" + + return response_object diff --git a/carvekit/web/other/__init__.py b/carvekit/web/other/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/web/other/removebg.py b/carvekit/web/other/removebg.py new file mode 100644 index 0000000000000000000000000000000000000000..30dca1e75a7f696a36cd89efd16b51c729bb07ce --- /dev/null +++ b/carvekit/web/other/removebg.py @@ -0,0 +1,247 @@ +import io +import time +import zipfile + +import requests +from PIL import Image, ImageColor + +from carvekit.utils.image_utils import transparency_paste, add_margin +from carvekit.utils.mask_utils import extract_alpha_channel +from carvekit.web.responses.api import error_dict +from carvekit.api.interface import Interface + + +def process_remove_bg( + interface: Interface, params, image, bg, is_json_or_www_encoded=False +): + """ + Handles a request to the removebg api method + + Args: + interface: CarveKit interface + bg: background pil image + is_json_or_www_encoded: is "json" or "x-www-form-urlencoded" content-type + image: foreground pil image + params: parameters + """ + h, w = image.size + if h < 2 or w < 2: + return error_dict("Image is too small. Minimum size 2x2"), 400 + + if "size" in params.keys(): + value = params["size"] + if value == "preview" or value == "small" or value == "regular": + image.thumbnail((625, 400), resample=3) # 0.25 mp + elif value == "medium": + image.thumbnail((1504, 1000), resample=3) # 1.5 mp + elif value == "hd": + image.thumbnail((2000, 2000), resample=3) # 2.5 mp + else: + image.thumbnail((6250, 4000), resample=3) # 25 mp + + roi_box = [0, 0, image.size[0], image.size[1]] + if "type" in params.keys(): + value = params["type"] + pass + + if "roi" in params.keys(): + value = params["roi"].split(" ") + if len(value) == 4: + for i, coord in enumerate(value): + if "px" in coord: + coord = coord.replace("px", "") + try: + coord = int(coord) + except BaseException: + return ( + error_dict( + "Error converting roi coordinate string to number!" + ), + 400, + ) + if coord < 0: + error_dict("Bad roi coordinate."), 400 + if (i == 0 or i == 2) and coord > image.size[0]: + return ( + error_dict( + "The roi coordinate cannot be larger than the image size." + ), + 400, + ) + elif (i == 1 or i == 3) and coord > image.size[1]: + return ( + error_dict( + "The roi coordinate cannot be larger than the image size." + ), + 400, + ) + roi_box[i] = int(coord) + elif "%" in coord: + coord = coord.replace("%", "") + try: + coord = int(coord) + except BaseException: + return ( + error_dict( + "Error converting roi coordinate string to number!" + ), + 400, + ) + if coord > 100: + return ( + error_dict("The coordinate cannot be more than 100%"), + 400, + ) + elif coord < 0: + return error_dict("Coordinate cannot be less than 0%"), 400 + if i == 0 or i == 2: + coord = int(image.size[0] * coord / 100) + elif i == 1 or i == 3: + coord = int(image.size[1] * coord / 100) + roi_box[i] = coord + else: + return error_dict("Something wrong with roi coordinates!"), 400 + + new_image = image.copy() + new_image = new_image.crop(roi_box) + h, w = new_image.size + if h < 2 or w < 2: + return error_dict("Image is too small. Minimum size 2x2"), 400 + new_image = interface([new_image])[0] + + scaled = False + if "scale" in params.keys() and params["scale"] != 100: + value = params["scale"] + new_image.thumbnail( + (int(image.size[0] * value / 100), int(image.size[1] * value / 100)), + resample=3, + ) + scaled = True + if "crop" in params.keys(): + value = params["crop"] + if value: + new_image = new_image.crop(new_image.getbbox()) + if "crop_margin" in params.keys(): + crop_margin = params["crop_margin"] + if "px" in crop_margin: + crop_margin = crop_margin.replace("px", "") + crop_margin = abs(int(crop_margin)) + if crop_margin > 500: + return ( + error_dict( + "The crop_margin cannot be larger than the original image size." + ), + 400, + ) + new_image = add_margin( + new_image, + crop_margin, + crop_margin, + crop_margin, + crop_margin, + (0, 0, 0, 0), + ) + elif "%" in crop_margin: + crop_margin = crop_margin.replace("%", "") + crop_margin = int(crop_margin) + new_image = add_margin( + new_image, + int(new_image.size[1] * crop_margin / 100), + int(new_image.size[0] * crop_margin / 100), + int(new_image.size[1] * crop_margin / 100), + int(new_image.size[0] * crop_margin / 100), + (0, 0, 0, 0), + ) + else: + if "position" in params.keys() and scaled is False: + value = params["position"] + if len(value) == 2: + new_image = transparency_paste( + Image.new("RGBA", image.size), + new_image, + ( + int(image.size[0] * value[0] / 100), + int(image.size[1] * value[1] / 100), + ), + ) + else: + new_image = transparency_paste( + Image.new("RGBA", image.size), new_image, roi_box + ) + elif scaled is False: + new_image = transparency_paste( + Image.new("RGBA", image.size), new_image, roi_box + ) + + if "channels" in params.keys(): + value = params["channels"] + if value == "alpha": + new_image = extract_alpha_channel(new_image) + else: + bg_changed = False + if "bg_color" in params.keys(): + value = params["bg_color"] + if len(value) > 0: + color = ImageColor.getcolor(value, "RGB") + bg = Image.new("RGBA", new_image.size, color) + bg = transparency_paste(bg, new_image, (0, 0)) + new_image = bg.copy() + bg_changed = True + if "bg_image_url" in params.keys() and bg_changed is False: + value = params["bg_image_url"] + if len(value) > 0: + try: + bg = Image.open(io.BytesIO(requests.get(value).content)) + except BaseException: + return error_dict("Error download background image!"), 400 + bg = bg.resize(new_image.size) + bg = bg.convert("RGBA") + bg = transparency_paste(bg, new_image, (0, 0)) + new_image = bg.copy() + bg_changed = True + if not is_json_or_www_encoded: + if bg and bg_changed is False: + bg = bg.resize(new_image.size) + bg = bg.convert("RGBA") + bg = transparency_paste(bg, new_image, (0, 0)) + new_image = bg.copy() + if "format" in params.keys(): + value = params["format"] + if value == "jpg": + new_image = new_image.convert("RGB") + img_io = io.BytesIO() + new_image.save(img_io, "JPEG", quality=100) + img_io.seek(0) + return {"type": "jpg", "data": [img_io, new_image.size]} + elif value == "zip": + mask = extract_alpha_channel(new_image) + mask_buff = io.BytesIO() + mask.save(mask_buff, "PNG") + mask_buff.seek(0) + image_buff = io.BytesIO() + image.save(image_buff, "JPEG") + image_buff.seek(0) + fileobj = io.BytesIO() + with zipfile.ZipFile(fileobj, "w") as zip_file: + zip_info = zipfile.ZipInfo(filename="color.jpg") + zip_info.date_time = time.localtime(time.time())[:6] + zip_info.compress_type = zipfile.ZIP_DEFLATED + zip_file.writestr(zip_info, image_buff.getvalue()) + zip_info = zipfile.ZipInfo(filename="alpha.png") + zip_info.date_time = time.localtime(time.time())[:6] + zip_info.compress_type = zipfile.ZIP_DEFLATED + zip_file.writestr(zip_info, mask_buff.getvalue()) + fileobj.seek(0) + return {"type": "zip", "data": [fileobj.read(), new_image.size]} + else: + buff = io.BytesIO() + new_image.save(buff, "PNG") + buff.seek(0) + return {"type": "png", "data": [buff, new_image.size]} + return ( + error_dict( + "Something wrong with request or http api. Please, open new issue on Github! This is error in " + "code." + ), + 400, + ) diff --git a/carvekit/web/responses/__init__.py b/carvekit/web/responses/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/web/responses/api.py b/carvekit/web/responses/api.py new file mode 100644 index 0000000000000000000000000000000000000000..94b660fce0241b34546246e2d1467bc4001a208e --- /dev/null +++ b/carvekit/web/responses/api.py @@ -0,0 +1,8 @@ +def error_dict(error_text: str): + """ + Generates a dictionary containing $error_text error + :param error_text: Error text + :return: error dictionary + """ + resp = {"errors": [{"title": error_text}]} + return resp diff --git a/carvekit/web/routers/__init__.py b/carvekit/web/routers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/web/routers/api_router.py b/carvekit/web/routers/api_router.py new file mode 100644 index 0000000000000000000000000000000000000000..c452cacbb15ac13919b9fcaa482ed829983a8fd6 --- /dev/null +++ b/carvekit/web/routers/api_router.py @@ -0,0 +1,222 @@ +import base64 +import http +import io +import time +from json import JSONDecodeError +from typing import Optional + +import requests +from PIL import Image +from fastapi import Header, Depends, Form, File, Request, APIRouter, UploadFile +from fastapi.openapi.models import Response +from pydantic import ValidationError +from starlette.responses import JSONResponse + +from carvekit.web.deps import config, ml_processor +from carvekit.web.handlers.response import handle_response, Authenticate +from carvekit.web.responses.api import error_dict +from carvekit.web.schemas.request import Parameters +from carvekit.web.utils.net_utils import is_loopback + +api_router = APIRouter(prefix="", tags=["api"]) + + +# noinspection PyBroadException +@api_router.post("/removebg") +async def removebg( + request: Request, + image_file: Optional[bytes] = File(None), + auth: bool = Depends(Authenticate), + content_type: str = Header(""), + image_file_b64: Optional[str] = Form(None), + image_url: Optional[str] = Form(None), + bg_image_file: Optional[bytes] = File(None), + size: Optional[str] = Form("full"), + type: Optional[str] = Form("auto"), + format: Optional[str] = Form("auto"), + roi: str = Form("0% 0% 100% 100%"), + crop: bool = Form(False), + crop_margin: Optional[str] = Form("0px"), + scale: Optional[str] = Form("original"), + position: Optional[str] = Form("original"), + channels: Optional[str] = Form("rgba"), + add_shadow: bool = Form(False), # Not supported at the moment + semitransparency: bool = Form(False), # Not supported at the moment + bg_color: Optional[str] = Form(""), +): + if auth is False: + return JSONResponse(content=error_dict("Missing API Key"), status_code=403) + if ( + content_type not in ["application/x-www-form-urlencoded", "application/json"] + and "multipart/form-data" not in content_type + ): + return JSONResponse( + content=error_dict("Invalid request content type"), status_code=400 + ) + + if image_url: + if not ( + image_url.startswith("http://") or image_url.startswith("https://") + ) or is_loopback(image_url): + print( + f"Possible ssrf attempt to /api/removebg endpoint with image url: {image_url}" + ) + return JSONResponse( + content=error_dict("Invalid image url."), status_code=400 + ) # possible ssrf attempt + + image = None + bg = None + parameters = None + if ( + content_type == "application/x-www-form-urlencoded" + or "multipart/form-data" in content_type + ): + if image_file_b64 is None and image_url is None and image_file is None: + return JSONResponse(content=error_dict("File not found"), status_code=400) + + if image_file_b64: + if len(image_file_b64) == 0: + return JSONResponse(content=error_dict("Empty image"), status_code=400) + try: + image = Image.open(io.BytesIO(base64.b64decode(image_file_b64))) + except BaseException: + return JSONResponse( + content=error_dict("Error decode image!"), status_code=400 + ) + elif image_url: + try: + image = Image.open(io.BytesIO(requests.get(image_url).content)) + except BaseException: + return JSONResponse( + content=error_dict("Error download image!"), status_code=400 + ) + elif image_file: + if len(image_file) == 0: + return JSONResponse(content=error_dict("Empty image"), status_code=400) + image = Image.open(io.BytesIO(image_file)) + + if bg_image_file: + if len(bg_image_file) == 0: + return JSONResponse(content=error_dict("Empty image"), status_code=400) + bg = Image.open(io.BytesIO(bg_image_file)) + try: + parameters = Parameters( + image_file_b64=image_file_b64, + image_url=image_url, + size=size, + type=type, + format=format, + roi=roi, + crop=crop, + crop_margin=crop_margin, + scale=scale, + position=position, + channels=channels, + add_shadow=add_shadow, + semitransparency=semitransparency, + bg_color=bg_color, + ) + except ValidationError as e: + return JSONResponse( + content=e.json(), status_code=400, media_type="application/json" + ) + + else: + payload = None + try: + payload = await request.json() + except JSONDecodeError: + return JSONResponse(content=error_dict("Empty json"), status_code=400) + try: + parameters = Parameters(**payload) + except ValidationError as e: + return Response( + content=e.json(), status_code=400, media_type="application/json" + ) + if parameters.image_file_b64 is None and parameters.image_url is None: + return JSONResponse(content=error_dict("File not found"), status_code=400) + + if parameters.image_file_b64: + if len(parameters.image_file_b64) == 0: + return JSONResponse(content=error_dict("Empty image"), status_code=400) + try: + image = Image.open( + io.BytesIO(base64.b64decode(parameters.image_file_b64)) + ) + except BaseException: + return JSONResponse( + content=error_dict("Error decode image!"), status_code=400 + ) + elif parameters.image_url: + if not ( + parameters.image_url.startswith("http://") + or parameters.image_url.startswith("https://") + ) or is_loopback(parameters.image_url): + print( + f"Possible ssrf attempt to /api/removebg endpoint with image url: {parameters.image_url}" + ) + return JSONResponse( + content=error_dict("Invalid image url."), status_code=400 + ) # possible ssrf attempt + try: + image = Image.open( + io.BytesIO(requests.get(parameters.image_url).content) + ) + except BaseException: + return JSONResponse( + content=error_dict("Error download image!"), status_code=400 + ) + if image is None: + return JSONResponse( + content=error_dict("Error download image!"), status_code=400 + ) + + job_id = ml_processor.job_create([parameters.dict(), image, bg, False]) + + while ml_processor.job_status(job_id) != "finished": + if ml_processor.job_status(job_id) == "not_found": + return JSONResponse( + content=error_dict("Job ID not found!"), status_code=500 + ) + time.sleep(5) + + result = ml_processor.job_result(job_id) + return handle_response(result, image) + + +@api_router.get("/account") +def account(): + """ + Stub for compatibility with remove.bg api libraries + """ + return JSONResponse( + content={ + "data": { + "attributes": { + "credits": { + "total": 99999, + "subscription": 99999, + "payg": 99999, + "enterprise": 99999, + }, + "api": {"free_calls": 99999, "sizes": "all"}, + } + } + }, + status_code=200, + ) + + +@api_router.get("/admin/config") +def status(auth: str = Depends(Authenticate)): + """ + Returns the current server config. + """ + if not auth or auth != "admin": + return JSONResponse( + content=error_dict("Authentication failed"), status_code=403 + ) + resp = JSONResponse(content=config.json(), status_code=200) + resp.headers["X-Credits-Charged"] = "0" + return resp diff --git a/carvekit/web/schemas/__init__.py b/carvekit/web/schemas/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/carvekit/web/schemas/config.py b/carvekit/web/schemas/config.py new file mode 100644 index 0000000000000000000000000000000000000000..5d47ffc3f16702c5bcac3874f2a1a3548e86a1f2 --- /dev/null +++ b/carvekit/web/schemas/config.py @@ -0,0 +1,99 @@ +import secrets +from typing import List +from typing_extensions import Literal + +import torch.cuda +from pydantic import BaseModel, validator + + +class AuthConfig(BaseModel): + """Config for web api token authentication""" + + auth: bool = True + """Enables Token Authentication for API""" + admin_token: str = secrets.token_hex(32) + """Admin Token""" + allowed_tokens: List[str] = [secrets.token_hex(32)] + """All allowed tokens""" + + +class MLConfig(BaseModel): + """Config for ml part of framework""" + + segmentation_network: Literal[ + "u2net", "deeplabv3", "basnet", "tracer_b7" + ] = "tracer_b7" + """Segmentation Network""" + preprocessing_method: Literal["none", "stub"] = "none" + """Pre-processing Method""" + postprocessing_method: Literal["fba", "none"] = "fba" + """Post-Processing Network""" + device: str = "cpu" + """Processing device""" + batch_size_seg: int = 5 + """Batch size for segmentation network""" + batch_size_matting: int = 1 + """Batch size for matting network""" + seg_mask_size: int = 640 + """The size of the input image for the segmentation neural network.""" + matting_mask_size: int = 2048 + """The size of the input image for the matting neural network.""" + fp16: bool = False + """Use half precision for inference""" + trimap_dilation: int = 30 + """Dilation size for trimap""" + trimap_erosion: int = 5 + """Erosion levels for trimap""" + trimap_prob_threshold: int = 231 + """Probability threshold for trimap generation""" + + @validator("seg_mask_size") + def seg_mask_size_validator(cls, value: int, values): + if value > 0: + return value + else: + raise ValueError("Incorrect seg_mask_size!") + + @validator("matting_mask_size") + def matting_mask_size_validator(cls, value: int, values): + if value > 0: + return value + else: + raise ValueError("Incorrect matting_mask_size!") + + @validator("batch_size_seg") + def batch_size_seg_validator(cls, value: int, values): + if value > 0: + return value + else: + raise ValueError("Incorrect batch size!") + + @validator("batch_size_matting") + def batch_size_matting_validator(cls, value: int, values): + if value > 0: + return value + else: + raise ValueError("Incorrect batch size!") + + @validator("device") + def device_validator(cls, value): + if torch.cuda.is_available() is False and "cuda" in value: + raise ValueError( + "GPU is not available, but specified as processing device!" + ) + if "cuda" not in value and "cpu" != value: + raise ValueError("Unknown processing device! It should be cpu or cuda!") + return value + + +class WebAPIConfig(BaseModel): + """FastAPI app config""" + + port: int = 5000 + """Web API port""" + host: str = "0.0.0.0" + """Web API host""" + ml: MLConfig = MLConfig() + """Config for ml part of framework""" + auth: AuthConfig = AuthConfig() + """Config for web api token authentication """ diff --git a/carvekit/web/schemas/request.py b/carvekit/web/schemas/request.py new file mode 100644 index 0000000000000000000000000000000000000000..d7ebefc14cdc4d861b68a39b206eac6d07622975 --- /dev/null +++ b/carvekit/web/schemas/request.py @@ -0,0 +1,72 @@ +import re +from typing import Optional + +from pydantic import BaseModel, validator +from typing_extensions import Literal + + +class Parameters(BaseModel): + image_file_b64: Optional[str] = "" + image_url: Optional[str] = "" + size: Optional[Literal["preview", "full", "auto"]] = "preview" + type: Optional[ + Literal["auto", "product", "person", "car"] + ] = "auto" # Not supported at the moment + format: Optional[Literal["auto", "jpg", "png", "zip"]] = "auto" + roi: str = "0% 0% 100% 100%" + crop: bool = False + crop_margin: Optional[str] = "0px" + scale: Optional[str] = "original" + position: Optional[str] = "original" + channels: Optional[Literal["rgba", "alpha"]] = "rgba" + add_shadow: str = "false" # Not supported at the moment + semitransparency: str = "false" # Not supported at the moment + bg_color: Optional[str] = "" + bg_image_url: Optional[str] = "" + + @validator("crop_margin") + def crop_margin_validator(cls, value): + if not re.match(r"[0-9]+(px|%)$", value): + raise ValueError( + "crop_margin paramter is not valid" + ) # TODO: Add support of several values + if "%" in value and (int(value[:-1]) < 0 or int(value[:-1]) > 100): + raise ValueError("crop_margin mast be in range between 0% and 100%") + return value + + @validator("scale") + def scale_validator(cls, value): + if value != "original" and ( + not re.match(r"[0-9]+%$", value) + or not int(value[:-1]) <= 100 + or not int(value[:-1]) >= 10 + ): + raise ValueError("scale must be original or in between of 10% and 100%") + + if value == "original": + return 100 + + return int(value[:-1]) + + @validator("position") + def position_validator(cls, value, values): + if len(value.split(" ")) > 2: + raise ValueError( + "Position must be a value from 0 to 100 " + "for both vertical and horizontal axises or for both axises respectively" + ) + + if value == "original": + return "original" + elif len(value.split(" ")) == 1: + return [int(value[:-1]), int(value[:-1])] + else: + return [int(value.split(" ")[0][:-1]), int(value.split(" ")[1][:-1])] + + @validator("bg_color") + def bg_color_validator(cls, value): + if not re.match(r"(#{0,1}[0-9a-f]{3}){0,2}$", value): + raise ValueError("bg_color is not in hex") + if len(value) and value[0] != "#": + value = "#" + value + return value diff --git a/carvekit/web/static/css/animate.css b/carvekit/web/static/css/animate.css new file mode 100644 index 0000000000000000000000000000000000000000..bc37b73f41c235914e5cabcd914ed7d8e3450b3f --- /dev/null +++ b/carvekit/web/static/css/animate.css @@ -0,0 +1,3297 @@ +@charset "UTF-8"; + +/*! +Animate.css - http://daneden.me/animate +Licensed under the MIT license - http://opensource.org/licenses/MIT + +Copyright (c) 2013 Daniel Eden +*/ + +.animated { + -webkit-animation-duration: 1.5s; + animation-duration: 1.5s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} + +.animated.infinite { + -webkit-animation-iteration-count: infinite; + animation-iteration-count: infinite; +} + +.animated.hinge { + -webkit-animation-duration: 2s; + animation-duration: 2s; +} + +@-webkit-keyframes bounce { + 0%, 20%, 50%, 80%, 100% { + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 40% { + -webkit-transform: translateY(-30px); + transform: translateY(-30px); + } + + 60% { + -webkit-transform: translateY(-15px); + transform: translateY(-15px); + } +} + +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 40% { + -webkit-transform: translateY(-30px); + -ms-transform: translateY(-30px); + transform: translateY(-30px); + } + + 60% { + -webkit-transform: translateY(-15px); + -ms-transform: translateY(-15px); + transform: translateY(-15px); + } +} + +.bounce { + -webkit-animation-name: bounce; + animation-name: bounce; +} + +@-webkit-keyframes flash { + 0%, 50%, 100% { + opacity: 1; + } + + 25%, 75% { + opacity: 0; + } +} + +@keyframes flash { + 0%, 50%, 100% { + opacity: 1; + } + + 25%, 75% { + opacity: 0; + } +} + +.flash { + -webkit-animation-name: flash; + animation-name: flash; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes pulse { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 50% { + -webkit-transform: scale(1.1); + transform: scale(1.1); + } + + 100% { + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes pulse { + 0% { + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + + 50% { + -webkit-transform: scale(1.1); + -ms-transform: scale(1.1); + transform: scale(1.1); + } + + 100% { + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } +} + +.pulse { + -webkit-animation-name: pulse; + animation-name: pulse; +} + +@-webkit-keyframes rubberBand { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 30% { + -webkit-transform: scaleX(1.25) scaleY(0.75); + transform: scaleX(1.25) scaleY(0.75); + } + + 40% { + -webkit-transform: scaleX(0.75) scaleY(1.25); + transform: scaleX(0.75) scaleY(1.25); + } + + 60% { + -webkit-transform: scaleX(1.15) scaleY(0.85); + transform: scaleX(1.15) scaleY(0.85); + } + + 100% { + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes rubberBand { + 0% { + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + + 30% { + -webkit-transform: scaleX(1.25) scaleY(0.75); + -ms-transform: scaleX(1.25) scaleY(0.75); + transform: scaleX(1.25) scaleY(0.75); + } + + 40% { + -webkit-transform: scaleX(0.75) scaleY(1.25); + -ms-transform: scaleX(0.75) scaleY(1.25); + transform: scaleX(0.75) scaleY(1.25); + } + + 60% { + -webkit-transform: scaleX(1.15) scaleY(0.85); + -ms-transform: scaleX(1.15) scaleY(0.85); + transform: scaleX(1.15) scaleY(0.85); + } + + 100% { + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } +} + +.rubberBand { + -webkit-animation-name: rubberBand; + animation-name: rubberBand; +} + +@-webkit-keyframes shake { + 0%, 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 10%, 30%, 50%, 70%, 90% { + -webkit-transform: translateX(-10px); + transform: translateX(-10px); + } + + 20%, 40%, 60%, 80% { + -webkit-transform: translateX(10px); + transform: translateX(10px); + } +} + +@keyframes shake { + 0%, 100% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 10%, 30%, 50%, 70%, 90% { + -webkit-transform: translateX(-10px); + -ms-transform: translateX(-10px); + transform: translateX(-10px); + } + + 20%, 40%, 60%, 80% { + -webkit-transform: translateX(10px); + -ms-transform: translateX(10px); + transform: translateX(10px); + } +} + +.shake { + -webkit-animation-name: shake; + animation-name: shake; +} + +@-webkit-keyframes swing { + 20% { + -webkit-transform: rotate(15deg); + transform: rotate(15deg); + } + + 40% { + -webkit-transform: rotate(-10deg); + transform: rotate(-10deg); + } + + 60% { + -webkit-transform: rotate(5deg); + transform: rotate(5deg); + } + + 80% { + -webkit-transform: rotate(-5deg); + transform: rotate(-5deg); + } + + 100% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } +} + +@keyframes swing { + 20% { + -webkit-transform: rotate(15deg); + -ms-transform: rotate(15deg); + transform: rotate(15deg); + } + + 40% { + -webkit-transform: rotate(-10deg); + -ms-transform: rotate(-10deg); + transform: rotate(-10deg); + } + + 60% { + -webkit-transform: rotate(5deg); + -ms-transform: rotate(5deg); + transform: rotate(5deg); + } + + 80% { + -webkit-transform: rotate(-5deg); + -ms-transform: rotate(-5deg); + transform: rotate(-5deg); + } + + 100% { + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); + } +} + +.swing { + -webkit-transform-origin: top center; + -ms-transform-origin: top center; + transform-origin: top center; + -webkit-animation-name: swing; + animation-name: swing; +} + +@-webkit-keyframes tada { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 10%, 20% { + -webkit-transform: scale(0.9) rotate(-3deg); + transform: scale(0.9) rotate(-3deg); + } + + 30%, 50%, 70%, 90% { + -webkit-transform: scale(1.1) rotate(3deg); + transform: scale(1.1) rotate(3deg); + } + + 40%, 60%, 80% { + -webkit-transform: scale(1.1) rotate(-3deg); + transform: scale(1.1) rotate(-3deg); + } + + 100% { + -webkit-transform: scale(1) rotate(0); + transform: scale(1) rotate(0); + } +} + +@keyframes tada { + 0% { + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + + 10%, 20% { + -webkit-transform: scale(0.9) rotate(-3deg); + -ms-transform: scale(0.9) rotate(-3deg); + transform: scale(0.9) rotate(-3deg); + } + + 30%, 50%, 70%, 90% { + -webkit-transform: scale(1.1) rotate(3deg); + -ms-transform: scale(1.1) rotate(3deg); + transform: scale(1.1) rotate(3deg); + } + + 40%, 60%, 80% { + -webkit-transform: scale(1.1) rotate(-3deg); + -ms-transform: scale(1.1) rotate(-3deg); + transform: scale(1.1) rotate(-3deg); + } + + 100% { + -webkit-transform: scale(1) rotate(0); + -ms-transform: scale(1) rotate(0); + transform: scale(1) rotate(0); + } +} + +.tada { + -webkit-animation-name: tada; + animation-name: tada; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes wobble { + 0% { + -webkit-transform: translateX(0%); + transform: translateX(0%); + } + + 15% { + -webkit-transform: translateX(-25%) rotate(-5deg); + transform: translateX(-25%) rotate(-5deg); + } + + 30% { + -webkit-transform: translateX(20%) rotate(3deg); + transform: translateX(20%) rotate(3deg); + } + + 45% { + -webkit-transform: translateX(-15%) rotate(-3deg); + transform: translateX(-15%) rotate(-3deg); + } + + 60% { + -webkit-transform: translateX(10%) rotate(2deg); + transform: translateX(10%) rotate(2deg); + } + + 75% { + -webkit-transform: translateX(-5%) rotate(-1deg); + transform: translateX(-5%) rotate(-1deg); + } + + 100% { + -webkit-transform: translateX(0%); + transform: translateX(0%); + } +} + +@keyframes wobble { + 0% { + -webkit-transform: translateX(0%); + -ms-transform: translateX(0%); + transform: translateX(0%); + } + + 15% { + -webkit-transform: translateX(-25%) rotate(-5deg); + -ms-transform: translateX(-25%) rotate(-5deg); + transform: translateX(-25%) rotate(-5deg); + } + + 30% { + -webkit-transform: translateX(20%) rotate(3deg); + -ms-transform: translateX(20%) rotate(3deg); + transform: translateX(20%) rotate(3deg); + } + + 45% { + -webkit-transform: translateX(-15%) rotate(-3deg); + -ms-transform: translateX(-15%) rotate(-3deg); + transform: translateX(-15%) rotate(-3deg); + } + + 60% { + -webkit-transform: translateX(10%) rotate(2deg); + -ms-transform: translateX(10%) rotate(2deg); + transform: translateX(10%) rotate(2deg); + } + + 75% { + -webkit-transform: translateX(-5%) rotate(-1deg); + -ms-transform: translateX(-5%) rotate(-1deg); + transform: translateX(-5%) rotate(-1deg); + } + + 100% { + -webkit-transform: translateX(0%); + -ms-transform: translateX(0%); + transform: translateX(0%); + } +} + +.wobble { + -webkit-animation-name: wobble; + animation-name: wobble; +} + +@-webkit-keyframes bounceIn { + 0% { + opacity: 0; + -webkit-transform: scale(.3); + transform: scale(.3); + } + + 50% { + opacity: 1; + -webkit-transform: scale(1.05); + transform: scale(1.05); + } + + 70% { + -webkit-transform: scale(.9); + transform: scale(.9); + } + + 100% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } +} + +@keyframes bounceIn { + 0% { + opacity: 0; + -webkit-transform: scale(.3); + -ms-transform: scale(.3); + transform: scale(.3); + } + + 50% { + opacity: 1; + -webkit-transform: scale(1.05); + -ms-transform: scale(1.05); + transform: scale(1.05); + } + + 70% { + -webkit-transform: scale(.9); + -ms-transform: scale(.9); + transform: scale(.9); + } + + 100% { + opacity: 1; + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } +} + +.bounceIn { + -webkit-animation-name: bounceIn; + animation-name: bounceIn; +} + +@-webkit-keyframes bounceInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-2000px); + transform: translateY(-2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateY(30px); + transform: translateY(30px); + } + + 80% { + -webkit-transform: translateY(-10px); + transform: translateY(-10px); + } + + 100% { + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes bounceInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-2000px); + -ms-transform: translateY(-2000px); + transform: translateY(-2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateY(30px); + -ms-transform: translateY(30px); + transform: translateY(30px); + } + + 80% { + -webkit-transform: translateY(-10px); + -ms-transform: translateY(-10px); + transform: translateY(-10px); + } + + 100% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +.bounceInDown { + -webkit-animation-name: bounceInDown; + animation-name: bounceInDown; +} + +@-webkit-keyframes bounceInLeft { + 0% { + opacity: 0; + -webkit-transform: translateX(-2000px); + transform: translateX(-2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateX(30px); + transform: translateX(30px); + } + + 80% { + -webkit-transform: translateX(-10px); + transform: translateX(-10px); + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes bounceInLeft { + 0% { + opacity: 0; + -webkit-transform: translateX(-2000px); + -ms-transform: translateX(-2000px); + transform: translateX(-2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateX(30px); + -ms-transform: translateX(30px); + transform: translateX(30px); + } + + 80% { + -webkit-transform: translateX(-10px); + -ms-transform: translateX(-10px); + transform: translateX(-10px); + } + + 100% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.bounceInLeft { + -webkit-animation-name: bounceInLeft; + animation-name: bounceInLeft; +} + +@-webkit-keyframes bounceInRight { + 0% { + opacity: 0; + -webkit-transform: translateX(2000px); + transform: translateX(2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateX(-30px); + transform: translateX(-30px); + } + + 80% { + -webkit-transform: translateX(10px); + transform: translateX(10px); + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes bounceInRight { + 0% { + opacity: 0; + -webkit-transform: translateX(2000px); + -ms-transform: translateX(2000px); + transform: translateX(2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateX(-30px); + -ms-transform: translateX(-30px); + transform: translateX(-30px); + } + + 80% { + -webkit-transform: translateX(10px); + -ms-transform: translateX(10px); + transform: translateX(10px); + } + + 100% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.bounceInRight { + -webkit-animation-name: bounceInRight; + animation-name: bounceInRight; +} + +@-webkit-keyframes bounceInUp { + 0% { + opacity: 0; + -webkit-transform: translateY(2000px); + transform: translateY(2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateY(-30px); + transform: translateY(-30px); + } + + 80% { + -webkit-transform: translateY(10px); + transform: translateY(10px); + } + + 100% { + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes bounceInUp { + 0% { + opacity: 0; + -webkit-transform: translateY(2000px); + -ms-transform: translateY(2000px); + transform: translateY(2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateY(-30px); + -ms-transform: translateY(-30px); + transform: translateY(-30px); + } + + 80% { + -webkit-transform: translateY(10px); + -ms-transform: translateY(10px); + transform: translateY(10px); + } + + 100% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +.bounceInUp { + -webkit-animation-name: bounceInUp; + animation-name: bounceInUp; +} + +@-webkit-keyframes bounceOut { + 0% { + -webkit-transform: scale(1); + transform: scale(1); + } + + 25% { + -webkit-transform: scale(.95); + transform: scale(.95); + } + + 50% { + opacity: 1; + -webkit-transform: scale(1.1); + transform: scale(1.1); + } + + 100% { + opacity: 0; + -webkit-transform: scale(.3); + transform: scale(.3); + } +} + +@keyframes bounceOut { + 0% { + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + + 25% { + -webkit-transform: scale(.95); + -ms-transform: scale(.95); + transform: scale(.95); + } + + 50% { + opacity: 1; + -webkit-transform: scale(1.1); + -ms-transform: scale(1.1); + transform: scale(1.1); + } + + 100% { + opacity: 0; + -webkit-transform: scale(.3); + -ms-transform: scale(.3); + transform: scale(.3); + } +} + +.bounceOut { + -webkit-animation-name: bounceOut; + animation-name: bounceOut; +} + +@-webkit-keyframes bounceOutDown { + 0% { + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateY(-20px); + transform: translateY(-20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(2000px); + transform: translateY(2000px); + } +} + +@keyframes bounceOutDown { + 0% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateY(-20px); + -ms-transform: translateY(-20px); + transform: translateY(-20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(2000px); + -ms-transform: translateY(2000px); + transform: translateY(2000px); + } +} + +.bounceOutDown { + -webkit-animation-name: bounceOutDown; + animation-name: bounceOutDown; +} + +@-webkit-keyframes bounceOutLeft { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateX(20px); + transform: translateX(20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-2000px); + transform: translateX(-2000px); + } +} + +@keyframes bounceOutLeft { + 0% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateX(20px); + -ms-transform: translateX(20px); + transform: translateX(20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-2000px); + -ms-transform: translateX(-2000px); + transform: translateX(-2000px); + } +} + +.bounceOutLeft { + -webkit-animation-name: bounceOutLeft; + animation-name: bounceOutLeft; +} + +@-webkit-keyframes bounceOutRight { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateX(-20px); + transform: translateX(-20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(2000px); + transform: translateX(2000px); + } +} + +@keyframes bounceOutRight { + 0% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateX(-20px); + -ms-transform: translateX(-20px); + transform: translateX(-20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(2000px); + -ms-transform: translateX(2000px); + transform: translateX(2000px); + } +} + +.bounceOutRight { + -webkit-animation-name: bounceOutRight; + animation-name: bounceOutRight; +} + +@-webkit-keyframes bounceOutUp { + 0% { + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateY(20px); + transform: translateY(20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-2000px); + transform: translateY(-2000px); + } +} + +@keyframes bounceOutUp { + 0% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateY(20px); + -ms-transform: translateY(20px); + transform: translateY(20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-2000px); + -ms-transform: translateY(-2000px); + transform: translateY(-2000px); + } +} + +.bounceOutUp { + -webkit-animation-name: bounceOutUp; + animation-name: bounceOutUp; +} + +@-webkit-keyframes fadeIn { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes fadeIn { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +.fadeIn { + -webkit-animation-name: fadeIn; + animation-name: fadeIn; +} + +@-webkit-keyframes fadeInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-20px); + transform: translateY(-20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes fadeInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-20px); + -ms-transform: translateY(-20px); + transform: translateY(-20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +.fadeInDown { + -webkit-animation-name: fadeInDown; + animation-name: fadeInDown; +} + +@-webkit-keyframes fadeInDownBig { + 0% { + opacity: 0; + -webkit-transform: translateY(-2000px); + transform: translateY(-2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes fadeInDownBig { + 0% { + opacity: 0; + -webkit-transform: translateY(-2000px); + -ms-transform: translateY(-2000px); + transform: translateY(-2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +.fadeInDownBig { + -webkit-animation-name: fadeInDownBig; + animation-name: fadeInDownBig; +} + +@-webkit-keyframes fadeInLeft { + 0% { + opacity: 0; + -webkit-transform: translateX(-20px); + transform: translateX(-20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes fadeInLeft { + 0% { + opacity: 0; + -webkit-transform: translateX(-20px); + -ms-transform: translateX(-20px); + transform: translateX(-20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.fadeInLeft { + -webkit-animation-name: fadeInLeft; + animation-name: fadeInLeft; +} + +@-webkit-keyframes fadeInLeftBig { + 0% { + opacity: 0; + -webkit-transform: translateX(-2000px); + transform: translateX(-2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes fadeInLeftBig { + 0% { + opacity: 0; + -webkit-transform: translateX(-2000px); + -ms-transform: translateX(-2000px); + transform: translateX(-2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.fadeInLeftBig { + -webkit-animation-name: fadeInLeftBig; + animation-name: fadeInLeftBig; +} + +@-webkit-keyframes fadeInRight { + 0% { + opacity: 0; + -webkit-transform: translateX(20px); + transform: translateX(20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes fadeInRight { + 0% { + opacity: 0; + -webkit-transform: translateX(20px); + -ms-transform: translateX(20px); + transform: translateX(20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.fadeInRight { + -webkit-animation-name: fadeInRight; + animation-name: fadeInRight; +} + +@-webkit-keyframes fadeInRightBig { + 0% { + opacity: 0; + -webkit-transform: translateX(2000px); + transform: translateX(2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes fadeInRightBig { + 0% { + opacity: 0; + -webkit-transform: translateX(2000px); + -ms-transform: translateX(2000px); + transform: translateX(2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.fadeInRightBig { + -webkit-animation-name: fadeInRightBig; + animation-name: fadeInRightBig; +} + +@-webkit-keyframes fadeInUp { + 0% { + opacity: 0; + -webkit-transform: translateY(20px); + transform: translateY(20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes fadeInUp { + 0% { + opacity: 0; + -webkit-transform: translateY(20px); + -ms-transform: translateY(20px); + transform: translateY(20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +.fadeInUp { + -webkit-animation-name: fadeInUp; + animation-name: fadeInUp; +} + +@-webkit-keyframes fadeInUpBig { + 0% { + opacity: 0; + -webkit-transform: translateY(2000px); + transform: translateY(2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes fadeInUpBig { + 0% { + opacity: 0; + -webkit-transform: translateY(2000px); + -ms-transform: translateY(2000px); + transform: translateY(2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +.fadeInUpBig { + -webkit-animation-name: fadeInUpBig; + animation-name: fadeInUpBig; +} + +@-webkit-keyframes fadeOut { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} + +@keyframes fadeOut { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} + +.fadeOut { + -webkit-animation-name: fadeOut; + animation-name: fadeOut; +} + +@-webkit-keyframes fadeOutDown { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(20px); + transform: translateY(20px); + } +} + +@keyframes fadeOutDown { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(20px); + -ms-transform: translateY(20px); + transform: translateY(20px); + } +} + +.fadeOutDown { + -webkit-animation-name: fadeOutDown; + animation-name: fadeOutDown; +} + +@-webkit-keyframes fadeOutDownBig { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(2000px); + transform: translateY(2000px); + } +} + +@keyframes fadeOutDownBig { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(2000px); + -ms-transform: translateY(2000px); + transform: translateY(2000px); + } +} + +.fadeOutDownBig { + -webkit-animation-name: fadeOutDownBig; + animation-name: fadeOutDownBig; +} + +@-webkit-keyframes fadeOutLeft { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-20px); + transform: translateX(-20px); + } +} + +@keyframes fadeOutLeft { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-20px); + -ms-transform: translateX(-20px); + transform: translateX(-20px); + } +} + +.fadeOutLeft { + -webkit-animation-name: fadeOutLeft; + animation-name: fadeOutLeft; +} + +@-webkit-keyframes fadeOutLeftBig { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-2000px); + transform: translateX(-2000px); + } +} + +@keyframes fadeOutLeftBig { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-2000px); + -ms-transform: translateX(-2000px); + transform: translateX(-2000px); + } +} + +.fadeOutLeftBig { + -webkit-animation-name: fadeOutLeftBig; + animation-name: fadeOutLeftBig; +} + +@-webkit-keyframes fadeOutRight { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(20px); + transform: translateX(20px); + } +} + +@keyframes fadeOutRight { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(20px); + -ms-transform: translateX(20px); + transform: translateX(20px); + } +} + +.fadeOutRight { + -webkit-animation-name: fadeOutRight; + animation-name: fadeOutRight; +} + +@-webkit-keyframes fadeOutRightBig { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(2000px); + transform: translateX(2000px); + } +} + +@keyframes fadeOutRightBig { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(2000px); + -ms-transform: translateX(2000px); + transform: translateX(2000px); + } +} + +.fadeOutRightBig { + -webkit-animation-name: fadeOutRightBig; + animation-name: fadeOutRightBig; +} + +@-webkit-keyframes fadeOutUp { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-20px); + transform: translateY(-20px); + } +} + +@keyframes fadeOutUp { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-20px); + -ms-transform: translateY(-20px); + transform: translateY(-20px); + } +} + +.fadeOutUp { + -webkit-animation-name: fadeOutUp; + animation-name: fadeOutUp; +} + +@-webkit-keyframes fadeOutUpBig { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-2000px); + transform: translateY(-2000px); + } +} + +@keyframes fadeOutUpBig { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-2000px); + -ms-transform: translateY(-2000px); + transform: translateY(-2000px); + } +} + +.fadeOutUpBig { + -webkit-animation-name: fadeOutUpBig; + animation-name: fadeOutUpBig; +} + +@-webkit-keyframes flip { + 0% { + -webkit-transform: perspective(400px) translateZ(0) rotateY(-360deg) scale(1); + transform: perspective(400px) translateZ(0) rotateY(-360deg) scale(1); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 40% { + -webkit-transform: perspective(400px) translateZ(150px) rotateY(-190deg) scale(1); + transform: perspective(400px) translateZ(150px) rotateY(-190deg) scale(1); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 50% { + -webkit-transform: perspective(400px) translateZ(150px) rotateY(-170deg) scale(1); + transform: perspective(400px) translateZ(150px) rotateY(-170deg) scale(1); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 80% { + -webkit-transform: perspective(400px) translateZ(0) rotateY(0deg) scale(.95); + transform: perspective(400px) translateZ(0) rotateY(0deg) scale(.95); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 100% { + -webkit-transform: perspective(400px) translateZ(0) rotateY(0deg) scale(1); + transform: perspective(400px) translateZ(0) rotateY(0deg) scale(1); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } +} + +@keyframes flip { + 0% { + -webkit-transform: perspective(400px) translateZ(0) rotateY(-360deg) scale(1); + -ms-transform: perspective(400px) translateZ(0) rotateY(-360deg) scale(1); + transform: perspective(400px) translateZ(0) rotateY(-360deg) scale(1); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 40% { + -webkit-transform: perspective(400px) translateZ(150px) rotateY(-190deg) scale(1); + -ms-transform: perspective(400px) translateZ(150px) rotateY(-190deg) scale(1); + transform: perspective(400px) translateZ(150px) rotateY(-190deg) scale(1); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 50% { + -webkit-transform: perspective(400px) translateZ(150px) rotateY(-170deg) scale(1); + -ms-transform: perspective(400px) translateZ(150px) rotateY(-170deg) scale(1); + transform: perspective(400px) translateZ(150px) rotateY(-170deg) scale(1); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 80% { + -webkit-transform: perspective(400px) translateZ(0) rotateY(0deg) scale(.95); + -ms-transform: perspective(400px) translateZ(0) rotateY(0deg) scale(.95); + transform: perspective(400px) translateZ(0) rotateY(0deg) scale(.95); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 100% { + -webkit-transform: perspective(400px) translateZ(0) rotateY(0deg) scale(1); + -ms-transform: perspective(400px) translateZ(0) rotateY(0deg) scale(1); + transform: perspective(400px) translateZ(0) rotateY(0deg) scale(1); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } +} + +.animated.flip { + -webkit-backface-visibility: visible; + -ms-backface-visibility: visible; + backface-visibility: visible; + -webkit-animation-name: flip; + animation-name: flip; +} + +@-webkit-keyframes flipInX { + 0% { + -webkit-transform: perspective(400px) rotateX(90deg); + transform: perspective(400px) rotateX(90deg); + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotateX(-10deg); + transform: perspective(400px) rotateX(-10deg); + } + + 70% { + -webkit-transform: perspective(400px) rotateX(10deg); + transform: perspective(400px) rotateX(10deg); + } + + 100% { + -webkit-transform: perspective(400px) rotateX(0deg); + transform: perspective(400px) rotateX(0deg); + opacity: 1; + } +} + +@keyframes flipInX { + 0% { + -webkit-transform: perspective(400px) rotateX(90deg); + -ms-transform: perspective(400px) rotateX(90deg); + transform: perspective(400px) rotateX(90deg); + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotateX(-10deg); + -ms-transform: perspective(400px) rotateX(-10deg); + transform: perspective(400px) rotateX(-10deg); + } + + 70% { + -webkit-transform: perspective(400px) rotateX(10deg); + -ms-transform: perspective(400px) rotateX(10deg); + transform: perspective(400px) rotateX(10deg); + } + + 100% { + -webkit-transform: perspective(400px) rotateX(0deg); + -ms-transform: perspective(400px) rotateX(0deg); + transform: perspective(400px) rotateX(0deg); + opacity: 1; + } +} + +.flipInX { + -webkit-backface-visibility: visible !important; + -ms-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipInX; + animation-name: flipInX; +} + +@-webkit-keyframes flipInY { + 0% { + -webkit-transform: perspective(400px) rotateY(90deg); + transform: perspective(400px) rotateY(90deg); + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotateY(-10deg); + transform: perspective(400px) rotateY(-10deg); + } + + 70% { + -webkit-transform: perspective(400px) rotateY(10deg); + transform: perspective(400px) rotateY(10deg); + } + + 100% { + -webkit-transform: perspective(400px) rotateY(0deg); + transform: perspective(400px) rotateY(0deg); + opacity: 1; + } +} + +@keyframes flipInY { + 0% { + -webkit-transform: perspective(400px) rotateY(90deg); + -ms-transform: perspective(400px) rotateY(90deg); + transform: perspective(400px) rotateY(90deg); + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotateY(-10deg); + -ms-transform: perspective(400px) rotateY(-10deg); + transform: perspective(400px) rotateY(-10deg); + } + + 70% { + -webkit-transform: perspective(400px) rotateY(10deg); + -ms-transform: perspective(400px) rotateY(10deg); + transform: perspective(400px) rotateY(10deg); + } + + 100% { + -webkit-transform: perspective(400px) rotateY(0deg); + -ms-transform: perspective(400px) rotateY(0deg); + transform: perspective(400px) rotateY(0deg); + opacity: 1; + } +} + +.flipInY { + -webkit-backface-visibility: visible !important; + -ms-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipInY; + animation-name: flipInY; +} + +@-webkit-keyframes flipOutX { + 0% { + -webkit-transform: perspective(400px) rotateX(0deg); + transform: perspective(400px) rotateX(0deg); + opacity: 1; + } + + 100% { + -webkit-transform: perspective(400px) rotateX(90deg); + transform: perspective(400px) rotateX(90deg); + opacity: 0; + } +} + +@keyframes flipOutX { + 0% { + -webkit-transform: perspective(400px) rotateX(0deg); + -ms-transform: perspective(400px) rotateX(0deg); + transform: perspective(400px) rotateX(0deg); + opacity: 1; + } + + 100% { + -webkit-transform: perspective(400px) rotateX(90deg); + -ms-transform: perspective(400px) rotateX(90deg); + transform: perspective(400px) rotateX(90deg); + opacity: 0; + } +} + +.flipOutX { + -webkit-animation-name: flipOutX; + animation-name: flipOutX; + -webkit-backface-visibility: visible !important; + -ms-backface-visibility: visible !important; + backface-visibility: visible !important; +} + +@-webkit-keyframes flipOutY { + 0% { + -webkit-transform: perspective(400px) rotateY(0deg); + transform: perspective(400px) rotateY(0deg); + opacity: 1; + } + + 100% { + -webkit-transform: perspective(400px) rotateY(90deg); + transform: perspective(400px) rotateY(90deg); + opacity: 0; + } +} + +@keyframes flipOutY { + 0% { + -webkit-transform: perspective(400px) rotateY(0deg); + -ms-transform: perspective(400px) rotateY(0deg); + transform: perspective(400px) rotateY(0deg); + opacity: 1; + } + + 100% { + -webkit-transform: perspective(400px) rotateY(90deg); + -ms-transform: perspective(400px) rotateY(90deg); + transform: perspective(400px) rotateY(90deg); + opacity: 0; + } +} + +.flipOutY { + -webkit-backface-visibility: visible !important; + -ms-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipOutY; + animation-name: flipOutY; +} + +@-webkit-keyframes lightSpeedIn { + 0% { + -webkit-transform: translateX(100%) skewX(-30deg); + transform: translateX(100%) skewX(-30deg); + opacity: 0; + } + + 60% { + -webkit-transform: translateX(-20%) skewX(30deg); + transform: translateX(-20%) skewX(30deg); + opacity: 1; + } + + 80% { + -webkit-transform: translateX(0%) skewX(-15deg); + transform: translateX(0%) skewX(-15deg); + opacity: 1; + } + + 100% { + -webkit-transform: translateX(0%) skewX(0deg); + transform: translateX(0%) skewX(0deg); + opacity: 1; + } +} + +@keyframes lightSpeedIn { + 0% { + -webkit-transform: translateX(100%) skewX(-30deg); + -ms-transform: translateX(100%) skewX(-30deg); + transform: translateX(100%) skewX(-30deg); + opacity: 0; + } + + 60% { + -webkit-transform: translateX(-20%) skewX(30deg); + -ms-transform: translateX(-20%) skewX(30deg); + transform: translateX(-20%) skewX(30deg); + opacity: 1; + } + + 80% { + -webkit-transform: translateX(0%) skewX(-15deg); + -ms-transform: translateX(0%) skewX(-15deg); + transform: translateX(0%) skewX(-15deg); + opacity: 1; + } + + 100% { + -webkit-transform: translateX(0%) skewX(0deg); + -ms-transform: translateX(0%) skewX(0deg); + transform: translateX(0%) skewX(0deg); + opacity: 1; + } +} + +.lightSpeedIn { + -webkit-animation-name: lightSpeedIn; + animation-name: lightSpeedIn; + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; +} + +@-webkit-keyframes lightSpeedOut { + 0% { + -webkit-transform: translateX(0%) skewX(0deg); + transform: translateX(0%) skewX(0deg); + opacity: 1; + } + + 100% { + -webkit-transform: translateX(100%) skewX(-30deg); + transform: translateX(100%) skewX(-30deg); + opacity: 0; + } +} + +@keyframes lightSpeedOut { + 0% { + -webkit-transform: translateX(0%) skewX(0deg); + -ms-transform: translateX(0%) skewX(0deg); + transform: translateX(0%) skewX(0deg); + opacity: 1; + } + + 100% { + -webkit-transform: translateX(100%) skewX(-30deg); + -ms-transform: translateX(100%) skewX(-30deg); + transform: translateX(100%) skewX(-30deg); + opacity: 0; + } +} + +.lightSpeedOut { + -webkit-animation-name: lightSpeedOut; + animation-name: lightSpeedOut; + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; +} + +@-webkit-keyframes rotateIn { + 0% { + -webkit-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(-200deg); + transform: rotate(-200deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +@keyframes rotateIn { + 0% { + -webkit-transform-origin: center center; + -ms-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(-200deg); + -ms-transform: rotate(-200deg); + transform: rotate(-200deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: center center; + -ms-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +.rotateIn { + -webkit-animation-name: rotateIn; + animation-name: rotateIn; +} + +@-webkit-keyframes rotateInDownLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +@keyframes rotateInDownLeft { + 0% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +.rotateInDownLeft { + -webkit-animation-name: rotateInDownLeft; + animation-name: rotateInDownLeft; +} + +@-webkit-keyframes rotateInDownRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +@keyframes rotateInDownRight { + 0% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +.rotateInDownRight { + -webkit-animation-name: rotateInDownRight; + animation-name: rotateInDownRight; +} + +@-webkit-keyframes rotateInUpLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +@keyframes rotateInUpLeft { + 0% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +.rotateInUpLeft { + -webkit-animation-name: rotateInUpLeft; + animation-name: rotateInUpLeft; +} + +@-webkit-keyframes rotateInUpRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +@keyframes rotateInUpRight { + 0% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } +} + +.rotateInUpRight { + -webkit-animation-name: rotateInUpRight; + animation-name: rotateInUpRight; +} + +@-webkit-keyframes rotateOut { + 0% { + -webkit-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(200deg); + transform: rotate(200deg); + opacity: 0; + } +} + +@keyframes rotateOut { + 0% { + -webkit-transform-origin: center center; + -ms-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: center center; + -ms-transform-origin: center center; + transform-origin: center center; + -webkit-transform: rotate(200deg); + -ms-transform: rotate(200deg); + transform: rotate(200deg); + opacity: 0; + } +} + +.rotateOut { + -webkit-animation-name: rotateOut; + animation-name: rotateOut; +} + +@-webkit-keyframes rotateOutDownLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } +} + +@keyframes rotateOutDownLeft { + 0% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } +} + +.rotateOutDownLeft { + -webkit-animation-name: rotateOutDownLeft; + animation-name: rotateOutDownLeft; +} + +@-webkit-keyframes rotateOutDownRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } +} + +@keyframes rotateOutDownRight { + 0% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } +} + +.rotateOutDownRight { + -webkit-animation-name: rotateOutDownRight; + animation-name: rotateOutDownRight; +} + +@-webkit-keyframes rotateOutUpLeft { + 0% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } +} + +@keyframes rotateOutUpLeft { + 0% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: left bottom; + -ms-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + transform: rotate(-90deg); + opacity: 0; + } +} + +.rotateOutUpLeft { + -webkit-animation-name: rotateOutUpLeft; + animation-name: rotateOutUpLeft; +} + +@-webkit-keyframes rotateOutUpRight { + 0% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } +} + +@keyframes rotateOutUpRight { + 0% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); + opacity: 0; + } +} + +.rotateOutUpRight { + -webkit-animation-name: rotateOutUpRight; + animation-name: rotateOutUpRight; +} + +@-webkit-keyframes slideInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-2000px); + transform: translateY(-2000px); + } + + 100% { + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes slideInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-2000px); + -ms-transform: translateY(-2000px); + transform: translateY(-2000px); + } + + 100% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +.slideInDown { + -webkit-animation-name: slideInDown; + animation-name: slideInDown; +} + +@-webkit-keyframes slideInLeft { + 0% { + opacity: 0; + -webkit-transform: translateX(-2000px); + transform: translateX(-2000px); + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes slideInLeft { + 0% { + opacity: 0; + -webkit-transform: translateX(-2000px); + -ms-transform: translateX(-2000px); + transform: translateX(-2000px); + } + + 100% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.slideInLeft { + -webkit-animation-name: slideInLeft; + animation-name: slideInLeft; +} + +@-webkit-keyframes slideInRight { + 0% { + opacity: 0; + -webkit-transform: translateX(2000px); + transform: translateX(2000px); + } + + 100% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes slideInRight { + 0% { + opacity: 0; + -webkit-transform: translateX(2000px); + -ms-transform: translateX(2000px); + transform: translateX(2000px); + } + + 100% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} + +.slideInRight { + -webkit-animation-name: slideInRight; + animation-name: slideInRight; +} + +@-webkit-keyframes slideOutLeft { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-2000px); + transform: translateX(-2000px); + } +} + +@keyframes slideOutLeft { + 0% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-2000px); + -ms-transform: translateX(-2000px); + transform: translateX(-2000px); + } +} + +.slideOutLeft { + -webkit-animation-name: slideOutLeft; + animation-name: slideOutLeft; +} + +@-webkit-keyframes slideOutRight { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(2000px); + transform: translateX(2000px); + } +} + +@keyframes slideOutRight { + 0% { + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(2000px); + -ms-transform: translateX(2000px); + transform: translateX(2000px); + } +} + +.slideOutRight { + -webkit-animation-name: slideOutRight; + animation-name: slideOutRight; +} + +@-webkit-keyframes slideOutUp { + 0% { + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-2000px); + transform: translateY(-2000px); + } +} + +@keyframes slideOutUp { + 0% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-2000px); + -ms-transform: translateY(-2000px); + transform: translateY(-2000px); + } +} + +.slideOutUp { + -webkit-animation-name: slideOutUp; + animation-name: slideOutUp; +} + +@-webkit-keyframes slideInUp { + 0% { + opacity: 0; + -webkit-transform: translateY(2000px); + transform: translateY(2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes slideInUp { + 0% { + opacity: 0; + -webkit-transform: translateY(2000px); + -ms-transform: translateY(2000px); + transform: translateY(2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +.slideInUp { + -webkit-animation-name: slideInUp; + animation-name: slideInUp; +} + +@-webkit-keyframes slideOutDown { + 0% { + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(2000px); + transform: translateY(2000px); + } +} + +@keyframes slideOutDown { + 0% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(2000px); + -ms-transform: translateY(2000px); + transform: translateY(2000px); + } +} + +.slideOutDown { + -webkit-animation-name: slideOutDown; + animation-name: slideOutDown; +} + +@-webkit-keyframes hinge { + 0% { + -webkit-transform: rotate(0); + transform: rotate(0); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 20%, 60% { + -webkit-transform: rotate(80deg); + transform: rotate(80deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 40% { + -webkit-transform: rotate(60deg); + transform: rotate(60deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 80% { + -webkit-transform: rotate(60deg) translateY(0); + transform: rotate(60deg) translateY(0); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + opacity: 1; + } + + 100% { + -webkit-transform: translateY(700px); + transform: translateY(700px); + opacity: 0; + } +} + +@keyframes hinge { + 0% { + -webkit-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + -webkit-transform-origin: top left; + -ms-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 20%, 60% { + -webkit-transform: rotate(80deg); + -ms-transform: rotate(80deg); + transform: rotate(80deg); + -webkit-transform-origin: top left; + -ms-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 40% { + -webkit-transform: rotate(60deg); + -ms-transform: rotate(60deg); + transform: rotate(60deg); + -webkit-transform-origin: top left; + -ms-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 80% { + -webkit-transform: rotate(60deg) translateY(0); + -ms-transform: rotate(60deg) translateY(0); + transform: rotate(60deg) translateY(0); + -webkit-transform-origin: top left; + -ms-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + opacity: 1; + } + + 100% { + -webkit-transform: translateY(700px); + -ms-transform: translateY(700px); + transform: translateY(700px); + opacity: 0; + } +} + +.hinge { + -webkit-animation-name: hinge; + animation-name: hinge; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollIn { + 0% { + opacity: 0; + -webkit-transform: translateX(-100%) rotate(-120deg); + transform: translateX(-100%) rotate(-120deg); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0px) rotate(0deg); + transform: translateX(0px) rotate(0deg); + } +} + +@keyframes rollIn { + 0% { + opacity: 0; + -webkit-transform: translateX(-100%) rotate(-120deg); + -ms-transform: translateX(-100%) rotate(-120deg); + transform: translateX(-100%) rotate(-120deg); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0px) rotate(0deg); + -ms-transform: translateX(0px) rotate(0deg); + transform: translateX(0px) rotate(0deg); + } +} + +.rollIn { + -webkit-animation-name: rollIn; + animation-name: rollIn; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollOut { + 0% { + opacity: 1; + -webkit-transform: translateX(0px) rotate(0deg); + transform: translateX(0px) rotate(0deg); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(100%) rotate(120deg); + transform: translateX(100%) rotate(120deg); + } +} + +@keyframes rollOut { + 0% { + opacity: 1; + -webkit-transform: translateX(0px) rotate(0deg); + -ms-transform: translateX(0px) rotate(0deg); + transform: translateX(0px) rotate(0deg); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(100%) rotate(120deg); + -ms-transform: translateX(100%) rotate(120deg); + transform: translateX(100%) rotate(120deg); + } +} + +.rollOut { + -webkit-animation-name: rollOut; + animation-name: rollOut; +} + +@-webkit-keyframes zoomIn { + 0% { + opacity: 0; + -webkit-transform: scale(.3); + transform: scale(.3); + } + + 50% { + opacity: 1; + } +} + +@keyframes zoomIn { + 0% { + opacity: 0; + -webkit-transform: scale(.3); + -ms-transform: scale(.3); + transform: scale(.3); + } + + 50% { + opacity: 1; + } +} + +.zoomIn { + -webkit-animation-name: zoomIn; + animation-name: zoomIn; +} + +@-webkit-keyframes zoomInDown { + 0% { + opacity: 0; + -webkit-transform: scale(.1) translateY(-2000px); + transform: scale(.1) translateY(-2000px); + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 60% { + opacity: 1; + -webkit-transform: scale(.475) translateY(60px); + transform: scale(.475) translateY(60px); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } +} + +@keyframes zoomInDown { + 0% { + opacity: 0; + -webkit-transform: scale(.1) translateY(-2000px); + -ms-transform: scale(.1) translateY(-2000px); + transform: scale(.1) translateY(-2000px); + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 60% { + opacity: 1; + -webkit-transform: scale(.475) translateY(60px); + -ms-transform: scale(.475) translateY(60px); + transform: scale(.475) translateY(60px); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } +} + +.zoomInDown { + -webkit-animation-name: zoomInDown; + animation-name: zoomInDown; +} + +@-webkit-keyframes zoomInLeft { + 0% { + opacity: 0; + -webkit-transform: scale(.1) translateX(-2000px); + transform: scale(.1) translateX(-2000px); + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 60% { + opacity: 1; + -webkit-transform: scale(.475) translateX(48px); + transform: scale(.475) translateX(48px); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } +} + +@keyframes zoomInLeft { + 0% { + opacity: 0; + -webkit-transform: scale(.1) translateX(-2000px); + -ms-transform: scale(.1) translateX(-2000px); + transform: scale(.1) translateX(-2000px); + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 60% { + opacity: 1; + -webkit-transform: scale(.475) translateX(48px); + -ms-transform: scale(.475) translateX(48px); + transform: scale(.475) translateX(48px); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } +} + +.zoomInLeft { + -webkit-animation-name: zoomInLeft; + animation-name: zoomInLeft; +} + +@-webkit-keyframes zoomInRight { + 0% { + opacity: 0; + -webkit-transform: scale(.1) translateX(2000px); + transform: scale(.1) translateX(2000px); + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 60% { + opacity: 1; + -webkit-transform: scale(.475) translateX(-48px); + transform: scale(.475) translateX(-48px); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } +} + +@keyframes zoomInRight { + 0% { + opacity: 0; + -webkit-transform: scale(.1) translateX(2000px); + -ms-transform: scale(.1) translateX(2000px); + transform: scale(.1) translateX(2000px); + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 60% { + opacity: 1; + -webkit-transform: scale(.475) translateX(-48px); + -ms-transform: scale(.475) translateX(-48px); + transform: scale(.475) translateX(-48px); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } +} + +.zoomInRight { + -webkit-animation-name: zoomInRight; + animation-name: zoomInRight; +} + +@-webkit-keyframes zoomInUp { + 0% { + opacity: 0; + -webkit-transform: scale(.1) translateY(2000px); + transform: scale(.1) translateY(2000px); + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 60% { + opacity: 1; + -webkit-transform: scale(.475) translateY(-60px); + transform: scale(.475) translateY(-60px); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } +} + +@keyframes zoomInUp { + 0% { + opacity: 0; + -webkit-transform: scale(.1) translateY(2000px); + -ms-transform: scale(.1) translateY(2000px); + transform: scale(.1) translateY(2000px); + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 60% { + opacity: 1; + -webkit-transform: scale(.475) translateY(-60px); + -ms-transform: scale(.475) translateY(-60px); + transform: scale(.475) translateY(-60px); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } +} + +.zoomInUp { + -webkit-animation-name: zoomInUp; + animation-name: zoomInUp; +} + +@-webkit-keyframes zoomOut { + 0% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); + } + + 50% { + opacity: 0; + -webkit-transform: scale(.3); + transform: scale(.3); + } + + 100% { + opacity: 0; + } +} + +@keyframes zoomOut { + 0% { + opacity: 1; + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + + 50% { + opacity: 0; + -webkit-transform: scale(.3); + -ms-transform: scale(.3); + transform: scale(.3); + } + + 100% { + opacity: 0; + } +} + +.zoomOut { + -webkit-animation-name: zoomOut; + animation-name: zoomOut; +} + +@-webkit-keyframes zoomOutDown { + 40% { + opacity: 1; + -webkit-transform: scale(.475) translateY(-60px); + transform: scale(.475) translateY(-60px); + -webkit-animation-timing-function: linear; + animation-timing-function: linear; + } + + 100% { + opacity: 0; + -webkit-transform: scale(.1) translateY(2000px); + transform: scale(.1) translateY(2000px); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + } +} + +@keyframes zoomOutDown { + 40% { + opacity: 1; + -webkit-transform: scale(.475) translateY(-60px); + -ms-transform: scale(.475) translateY(-60px); + transform: scale(.475) translateY(-60px); + -webkit-animation-timing-function: linear; + animation-timing-function: linear; + } + + 100% { + opacity: 0; + -webkit-transform: scale(.1) translateY(2000px); + -ms-transform: scale(.1) translateY(2000px); + transform: scale(.1) translateY(2000px); + -webkit-transform-origin: center bottom; + -ms-transform-origin: center bottom; + transform-origin: center bottom; + } +} + +.zoomOutDown { + -webkit-animation-name: zoomOutDown; + animation-name: zoomOutDown; +} + +@-webkit-keyframes zoomOutLeft { + 40% { + opacity: 1; + -webkit-transform: scale(.475) translateX(42px); + transform: scale(.475) translateX(42px); + -webkit-animation-timing-function: linear; + animation-timing-function: linear; + } + + 100% { + opacity: 0; + -webkit-transform: scale(.1) translateX(-2000px); + transform: scale(.1) translateX(-2000px); + -webkit-transform-origin: left center; + transform-origin: left center; + } +} + +@keyframes zoomOutLeft { + 40% { + opacity: 1; + -webkit-transform: scale(.475) translateX(42px); + -ms-transform: scale(.475) translateX(42px); + transform: scale(.475) translateX(42px); + -webkit-animation-timing-function: linear; + animation-timing-function: linear; + } + + 100% { + opacity: 0; + -webkit-transform: scale(.1) translateX(-2000px); + -ms-transform: scale(.1) translateX(-2000px); + transform: scale(.1) translateX(-2000px); + -webkit-transform-origin: left center; + -ms-transform-origin: left center; + transform-origin: left center; + } +} + +.zoomOutLeft { + -webkit-animation-name: zoomOutLeft; + animation-name: zoomOutLeft; +} + +@-webkit-keyframes zoomOutRight { + 40% { + opacity: 1; + -webkit-transform: scale(.475) translateX(-42px); + transform: scale(.475) translateX(-42px); + -webkit-animation-timing-function: linear; + animation-timing-function: linear; + } + + 100% { + opacity: 0; + -webkit-transform: scale(.1) translateX(2000px); + transform: scale(.1) translateX(2000px); + -webkit-transform-origin: right center; + transform-origin: right center; + } +} + +@keyframes zoomOutRight { + 40% { + opacity: 1; + -webkit-transform: scale(.475) translateX(-42px); + -ms-transform: scale(.475) translateX(-42px); + transform: scale(.475) translateX(-42px); + -webkit-animation-timing-function: linear; + animation-timing-function: linear; + } + + 100% { + opacity: 0; + -webkit-transform: scale(.1) translateX(2000px); + -ms-transform: scale(.1) translateX(2000px); + transform: scale(.1) translateX(2000px); + -webkit-transform-origin: right center; + -ms-transform-origin: right center; + transform-origin: right center; + } +} + +.zoomOutRight { + -webkit-animation-name: zoomOutRight; + animation-name: zoomOutRight; +} + +@-webkit-keyframes zoomOutUp { + 40% { + opacity: 1; + -webkit-transform: scale(.475) translateY(60px); + transform: scale(.475) translateY(60px); + -webkit-animation-timing-function: linear; + animation-timing-function: linear; + } + + 100% { + opacity: 0; + -webkit-transform: scale(.1) translateY(-2000px); + transform: scale(.1) translateY(-2000px); + -webkit-transform-origin: center top; + transform-origin: center top; + } +} + +@keyframes zoomOutUp { + 40% { + opacity: 1; + -webkit-transform: scale(.475) translateY(60px); + -ms-transform: scale(.475) translateY(60px); + transform: scale(.475) translateY(60px); + -webkit-animation-timing-function: linear; + animation-timing-function: linear; + } + + 100% { + opacity: 0; + -webkit-transform: scale(.1) translateY(-2000px); + -ms-transform: scale(.1) translateY(-2000px); + transform: scale(.1) translateY(-2000px); + -webkit-transform-origin: center top; + -ms-transform-origin: center top; + transform-origin: center top; + } +} + +.zoomOutUp { + -webkit-animation-name: zoomOutUp; + animation-name: zoomOutUp; +} \ No newline at end of file diff --git a/carvekit/web/static/css/bootstrap.min.css b/carvekit/web/static/css/bootstrap.min.css new file mode 100644 index 0000000000000000000000000000000000000000..b68ee65bc15b833071bbdf97e549a068eae9706b --- /dev/null +++ b/carvekit/web/static/css/bootstrap.min.css @@ -0,0 +1,7772 @@ +/*! + * Bootstrap v3.3.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100% +} + +body { + margin: 0 +} + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block +} + +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline +} + +audio:not([controls]) { + display: none; + height: 0 +} + +[hidden], +template { + display: none +} + +a { + background-color: transparent +} + +a:active, +a:hover { + outline: 0 +} + +abbr[title] { + border-bottom: 1px dotted +} + +b, +strong { + font-weight: 700 +} + +dfn { + font-style: italic +} + +h1 { + margin: .67em 0; + font-size: 2em +} + +mark { + color: #000; + background: #ff0 +} + +small { + font-size: 80% +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline +} + +sup { + top: -.5em +} + +sub { + bottom: -.25em +} + +img { + border: 0 +} + +svg:not(:root) { + overflow: hidden +} + +figure { + margin: 1em 40px +} + +hr { + height: 0; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box +} + +pre { + overflow: auto +} + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em +} + +button, +input, +optgroup, +select, +textarea { + margin: 0; + font: inherit; + color: inherit +} + +button { + overflow: visible +} + +button, +select { + text-transform: none +} + +button, +html input[type=button], +input[type=reset], +input[type=submit] { + -webkit-appearance: button; + cursor: pointer +} + +button[disabled], +html input[disabled] { + cursor: default +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0 +} + +input { + line-height: normal +} + +input[type=checkbox], +input[type=radio] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0 +} + +input[type=number]::-webkit-inner-spin-button, +input[type=number]::-webkit-outer-spin-button { + height: auto +} + +input[type=search] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield +} + +input[type=search]::-webkit-search-cancel-button, +input[type=search]::-webkit-search-decoration { + -webkit-appearance: none +} + +fieldset { + padding: .35em .625em .75em; + margin: 0 2px; + border: 1px solid silver +} + +legend { + padding: 0; + border: 0 +} + +textarea { + overflow: auto +} + +optgroup { + font-weight: 700 +} + +table { + border-spacing: 0; + border-collapse: collapse +} + +td, +th { + padding: 0 +} + +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ +@media print { + + *, + :before, + :after { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + -webkit-box-shadow: none !important; + box-shadow: none !important + } + + a, + a:visited { + text-decoration: underline + } + + a[href]:after { + content: " (" attr(href) ")" + } + + abbr[title]:after { + content: " (" attr(title) ")" + } + + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: "" + } + + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid + } + + thead { + display: table-header-group + } + + tr, + img { + page-break-inside: avoid + } + + img { + max-width: 100% !important + } + + p, + h2, + h3 { + orphans: 3; + widows: 3 + } + + h2, + h3 { + page-break-after: avoid + } + + select { + background: #fff !important + } + + .navbar { + display: none + } + + .btn>.caret, + .dropup>.btn>.caret { + border-top-color: #000 !important + } + + .label { + 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 #ddd !important + } +} + +@font-face { + font-family: 'Glyphicons Halflings'; + src: url(../fonts/glyphicons-halflings-regular.eot); + src: url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'), url(../fonts/glyphicons-halflings-regular.woff) format('woff'), url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'), url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg') +} + +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: 400; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale +} + +.glyphicon-asterisk:before { + content: "\2a" +} + +.glyphicon-plus:before { + content: "\2b" +} + +.glyphicon-euro:before, +.glyphicon-eur:before { + content: "\20ac" +} + +.glyphicon-minus:before { + content: "\2212" +} + +.glyphicon-cloud:before { + content: "\2601" +} + +.glyphicon-envelope:before { + content: "\2709" +} + +.glyphicon-pencil:before { + content: "\270f" +} + +.glyphicon-glass:before { + content: "\e001" +} + +.glyphicon-music:before { + content: "\e002" +} + +.glyphicon-search:before { + content: "\e003" +} + +.glyphicon-heart:before { + content: "\e005" +} + +.glyphicon-star:before { + content: "\e006" +} + +.glyphicon-star-empty:before { + content: "\e007" +} + +.glyphicon-user:before { + content: "\e008" +} + +.glyphicon-film:before { + content: "\e009" +} + +.glyphicon-th-large:before { + content: "\e010" +} + +.glyphicon-th:before { + content: "\e011" +} + +.glyphicon-th-list:before { + content: "\e012" +} + +.glyphicon-ok:before { + content: "\e013" +} + +.glyphicon-remove:before { + content: "\e014" +} + +.glyphicon-zoom-in:before { + content: "\e015" +} + +.glyphicon-zoom-out:before { + content: "\e016" +} + +.glyphicon-off:before { + content: "\e017" +} + +.glyphicon-signal:before { + content: "\e018" +} + +.glyphicon-cog:before { + content: "\e019" +} + +.glyphicon-trash:before { + content: "\e020" +} + +.glyphicon-home:before { + content: "\e021" +} + +.glyphicon-file:before { + content: "\e022" +} + +.glyphicon-time:before { + content: "\e023" +} + +.glyphicon-road:before { + content: "\e024" +} + +.glyphicon-download-alt:before { + content: "\e025" +} + +.glyphicon-download:before { + content: "\e026" +} + +.glyphicon-upload:before { + content: "\e027" +} + +.glyphicon-inbox:before { + content: "\e028" +} + +.glyphicon-play-circle:before { + content: "\e029" +} + +.glyphicon-repeat:before { + content: "\e030" +} + +.glyphicon-refresh:before { + content: "\e031" +} + +.glyphicon-list-alt:before { + content: "\e032" +} + +.glyphicon-lock:before { + content: "\e033" +} + +.glyphicon-flag:before { + content: "\e034" +} + +.glyphicon-headphones:before { + content: "\e035" +} + +.glyphicon-volume-off:before { + content: "\e036" +} + +.glyphicon-volume-down:before { + content: "\e037" +} + +.glyphicon-volume-up:before { + content: "\e038" +} + +.glyphicon-qrcode:before { + content: "\e039" +} + +.glyphicon-barcode:before { + content: "\e040" +} + +.glyphicon-tag:before { + content: "\e041" +} + +.glyphicon-tags:before { + content: "\e042" +} + +.glyphicon-book:before { + content: "\e043" +} + +.glyphicon-bookmark:before { + content: "\e044" +} + +.glyphicon-print:before { + content: "\e045" +} + +.glyphicon-camera:before { + content: "\e046" +} + +.glyphicon-font:before { + content: "\e047" +} + +.glyphicon-bold:before { + content: "\e048" +} + +.glyphicon-italic:before { + content: "\e049" +} + +.glyphicon-text-height:before { + content: "\e050" +} + +.glyphicon-text-width:before { + content: "\e051" +} + +.glyphicon-align-left:before { + content: "\e052" +} + +.glyphicon-align-center:before { + content: "\e053" +} + +.glyphicon-align-right:before { + content: "\e054" +} + +.glyphicon-align-justify:before { + content: "\e055" +} + +.glyphicon-list:before { + content: "\e056" +} + +.glyphicon-indent-left:before { + content: "\e057" +} + +.glyphicon-indent-right:before { + content: "\e058" +} + +.glyphicon-facetime-video:before { + content: "\e059" +} + +.glyphicon-picture:before { + content: "\e060" +} + +.glyphicon-map-marker:before { + content: "\e062" +} + +.glyphicon-adjust:before { + content: "\e063" +} + +.glyphicon-tint:before { + content: "\e064" +} + +.glyphicon-edit:before { + content: "\e065" +} + +.glyphicon-share:before { + content: "\e066" +} + +.glyphicon-check:before { + content: "\e067" +} + +.glyphicon-move:before { + content: "\e068" +} + +.glyphicon-step-backward:before { + content: "\e069" +} + +.glyphicon-fast-backward:before { + content: "\e070" +} + +.glyphicon-backward:before { + content: "\e071" +} + +.glyphicon-play:before { + content: "\e072" +} + +.glyphicon-pause:before { + content: "\e073" +} + +.glyphicon-stop:before { + content: "\e074" +} + +.glyphicon-forward:before { + content: "\e075" +} + +.glyphicon-fast-forward:before { + content: "\e076" +} + +.glyphicon-step-forward:before { + content: "\e077" +} + +.glyphicon-eject:before { + content: "\e078" +} + +.glyphicon-chevron-left:before { + content: "\e079" +} + +.glyphicon-chevron-right:before { + content: "\e080" +} + +.glyphicon-plus-sign:before { + content: "\e081" +} + +.glyphicon-minus-sign:before { + content: "\e082" +} + +.glyphicon-remove-sign:before { + content: "\e083" +} + +.glyphicon-ok-sign:before { + content: "\e084" +} + +.glyphicon-question-sign:before { + content: "\e085" +} + +.glyphicon-info-sign:before { + content: "\e086" +} + +.glyphicon-screenshot:before { + content: "\e087" +} + +.glyphicon-remove-circle:before { + content: "\e088" +} + +.glyphicon-ok-circle:before { + content: "\e089" +} + +.glyphicon-ban-circle:before { + content: "\e090" +} + +.glyphicon-arrow-left:before { + content: "\e091" +} + +.glyphicon-arrow-right:before { + content: "\e092" +} + +.glyphicon-arrow-up:before { + content: "\e093" +} + +.glyphicon-arrow-down:before { + content: "\e094" +} + +.glyphicon-share-alt:before { + content: "\e095" +} + +.glyphicon-resize-full:before { + content: "\e096" +} + +.glyphicon-resize-small:before { + content: "\e097" +} + +.glyphicon-exclamation-sign:before { + content: "\e101" +} + +.glyphicon-gift:before { + content: "\e102" +} + +.glyphicon-leaf:before { + content: "\e103" +} + +.glyphicon-fire:before { + content: "\e104" +} + +.glyphicon-eye-open:before { + content: "\e105" +} + +.glyphicon-eye-close:before { + content: "\e106" +} + +.glyphicon-warning-sign:before { + content: "\e107" +} + +.glyphicon-plane:before { + content: "\e108" +} + +.glyphicon-calendar:before { + content: "\e109" +} + +.glyphicon-random:before { + content: "\e110" +} + +.glyphicon-comment:before { + content: "\e111" +} + +.glyphicon-magnet:before { + content: "\e112" +} + +.glyphicon-chevron-up:before { + content: "\e113" +} + +.glyphicon-chevron-down:before { + content: "\e114" +} + +.glyphicon-retweet:before { + content: "\e115" +} + +.glyphicon-shopping-cart:before { + content: "\e116" +} + +.glyphicon-folder-close:before { + content: "\e117" +} + +.glyphicon-folder-open:before { + content: "\e118" +} + +.glyphicon-resize-vertical:before { + content: "\e119" +} + +.glyphicon-resize-horizontal:before { + content: "\e120" +} + +.glyphicon-hdd:before { + content: "\e121" +} + +.glyphicon-bullhorn:before { + content: "\e122" +} + +.glyphicon-bell:before { + content: "\e123" +} + +.glyphicon-certificate:before { + content: "\e124" +} + +.glyphicon-thumbs-up:before { + content: "\e125" +} + +.glyphicon-thumbs-down:before { + content: "\e126" +} + +.glyphicon-hand-right:before { + content: "\e127" +} + +.glyphicon-hand-left:before { + content: "\e128" +} + +.glyphicon-hand-up:before { + content: "\e129" +} + +.glyphicon-hand-down:before { + content: "\e130" +} + +.glyphicon-circle-arrow-right:before { + content: "\e131" +} + +.glyphicon-circle-arrow-left:before { + content: "\e132" +} + +.glyphicon-circle-arrow-up:before { + content: "\e133" +} + +.glyphicon-circle-arrow-down:before { + content: "\e134" +} + +.glyphicon-globe:before { + content: "\e135" +} + +.glyphicon-wrench:before { + content: "\e136" +} + +.glyphicon-tasks:before { + content: "\e137" +} + +.glyphicon-filter:before { + content: "\e138" +} + +.glyphicon-briefcase:before { + content: "\e139" +} + +.glyphicon-fullscreen:before { + content: "\e140" +} + +.glyphicon-dashboard:before { + content: "\e141" +} + +.glyphicon-paperclip:before { + content: "\e142" +} + +.glyphicon-heart-empty:before { + content: "\e143" +} + +.glyphicon-link:before { + content: "\e144" +} + +.glyphicon-phone:before { + content: "\e145" +} + +.glyphicon-pushpin:before { + content: "\e146" +} + +.glyphicon-usd:before { + content: "\e148" +} + +.glyphicon-gbp:before { + content: "\e149" +} + +.glyphicon-sort:before { + content: "\e150" +} + +.glyphicon-sort-by-alphabet:before { + content: "\e151" +} + +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152" +} + +.glyphicon-sort-by-order:before { + content: "\e153" +} + +.glyphicon-sort-by-order-alt:before { + content: "\e154" +} + +.glyphicon-sort-by-attributes:before { + content: "\e155" +} + +.glyphicon-sort-by-attributes-alt:before { + content: "\e156" +} + +.glyphicon-unchecked:before { + content: "\e157" +} + +.glyphicon-expand:before { + content: "\e158" +} + +.glyphicon-collapse-down:before { + content: "\e159" +} + +.glyphicon-collapse-up:before { + content: "\e160" +} + +.glyphicon-log-in:before { + content: "\e161" +} + +.glyphicon-flash:before { + content: "\e162" +} + +.glyphicon-log-out:before { + content: "\e163" +} + +.glyphicon-new-window:before { + content: "\e164" +} + +.glyphicon-record:before { + content: "\e165" +} + +.glyphicon-save:before { + content: "\e166" +} + +.glyphicon-open:before { + content: "\e167" +} + +.glyphicon-saved:before { + content: "\e168" +} + +.glyphicon-import:before { + content: "\e169" +} + +.glyphicon-export:before { + content: "\e170" +} + +.glyphicon-send:before { + content: "\e171" +} + +.glyphicon-floppy-disk:before { + content: "\e172" +} + +.glyphicon-floppy-saved:before { + content: "\e173" +} + +.glyphicon-floppy-remove:before { + content: "\e174" +} + +.glyphicon-floppy-save:before { + content: "\e175" +} + +.glyphicon-floppy-open:before { + content: "\e176" +} + +.glyphicon-credit-card:before { + content: "\e177" +} + +.glyphicon-transfer:before { + content: "\e178" +} + +.glyphicon-cutlery:before { + content: "\e179" +} + +.glyphicon-header:before { + content: "\e180" +} + +.glyphicon-compressed:before { + content: "\e181" +} + +.glyphicon-earphone:before { + content: "\e182" +} + +.glyphicon-phone-alt:before { + content: "\e183" +} + +.glyphicon-tower:before { + content: "\e184" +} + +.glyphicon-stats:before { + content: "\e185" +} + +.glyphicon-sd-video:before { + content: "\e186" +} + +.glyphicon-hd-video:before { + content: "\e187" +} + +.glyphicon-subtitles:before { + content: "\e188" +} + +.glyphicon-sound-stereo:before { + content: "\e189" +} + +.glyphicon-sound-dolby:before { + content: "\e190" +} + +.glyphicon-sound-5-1:before { + content: "\e191" +} + +.glyphicon-sound-6-1:before { + content: "\e192" +} + +.glyphicon-sound-7-1:before { + content: "\e193" +} + +.glyphicon-copyright-mark:before { + content: "\e194" +} + +.glyphicon-registration-mark:before { + content: "\e195" +} + +.glyphicon-cloud-download:before { + content: "\e197" +} + +.glyphicon-cloud-upload:before { + content: "\e198" +} + +.glyphicon-tree-conifer:before { + content: "\e199" +} + +.glyphicon-tree-deciduous:before { + content: "\e200" +} + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box +} + +:before, +:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box +} + +html { + font-size: 10px; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0) +} + +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333; + background-color: #fff +} + +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit +} + +a { + color: #337ab7; + text-decoration: none +} + +a:hover, +a:focus { + color: #23527c; + text-decoration: underline +} + +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px +} + +figure { + margin: 0 +} + +img { + vertical-align: middle +} + +.img-responsive, +.thumbnail>img, +.thumbnail a>img, +.carousel-inner>.item>img, +.carousel-inner>.item>a>img { + display: block; + max-width: 100%; + height: auto +} + +.img-rounded { + border-radius: 6px +} + +.img-thumbnail { + display: inline-block; + max-width: 100%; + height: auto; + padding: 4px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + -o-transition: all .2s ease-in-out; + transition: all .2s ease-in-out +} + +.img-circle { + border-radius: 50% +} + +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eee +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0 +} + +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto +} + +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit +} + +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: 400; + line-height: 1; + color: #777 +} + +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px +} + +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65% +} + +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px +} + +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75% +} + +h1, +.h1 { + font-size: 36px +} + +h2, +.h2 { + font-size: 30px +} + +h3, +.h3 { + font-size: 24px +} + +h4, +.h4 { + font-size: 18px +} + +h5, +.h5 { + font-size: 14px +} + +h6, +.h6 { + font-size: 12px +} + +p { + margin: 0 0 10px +} + +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4 +} + +@media (min-width:768px) { + .lead { + font-size: 21px + } +} + +small, +.small { + font-size: 85% +} + +mark, +.mark { + padding: .2em; + background-color: #fcf8e3 +} + +.text-left { + text-align: left +} + +.text-right { + text-align: right +} + +.text-center { + text-align: center +} + +.text-justify { + text-align: justify +} + +.text-nowrap { + white-space: nowrap +} + +.text-lowercase { + text-transform: lowercase +} + +.text-uppercase { + text-transform: uppercase +} + +.text-capitalize { + text-transform: capitalize +} + +.text-muted { + color: #777 +} + +.text-primary { + color: #337ab7 +} + +a.text-primary:hover { + color: #6f420a +} + +.text-success { + color: #3c763d +} + +a.text-success:hover { + color: #2b542c +} + +.text-info { + color: #31708f +} + +a.text-info:hover { + color: #245269 +} + +.text-warning { + color: #8a6d3b +} + +a.text-warning:hover { + color: #66512c +} + +.text-danger { + color: #a94442 +} + +a.text-danger:hover { + color: #843534 +} + +.bg-primary { + color: #fff; + background-color: #337ab7 +} + +a.bg-primary:hover { + background-color: #6f420a +} + +.bg-success { + background-color: #dff0d8 +} + +a.bg-success:hover { + background-color: #c1e2b3 +} + +.bg-info { + background-color: #d9edf7 +} + +a.bg-info:hover { + background-color: #afd9ee +} + +.bg-warning { + background-color: #fcf8e3 +} + +a.bg-warning:hover { + background-color: #f7ecb5 +} + +.bg-danger { + background-color: #f2dede +} + +a.bg-danger:hover { + background-color: #e4b9b9 +} + +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eee +} + +ul, +ol { + margin-top: 0; + margin-bottom: 10px +} + +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0 +} + +.list-unstyled { + padding-left: 0; + list-style: none +} + +.list-inline { + padding-left: 0; + margin-left: -5px; + list-style: none +} + +.list-inline>li { + display: inline-block; + padding-right: 5px; + padding-left: 5px +} + +dl { + margin-top: 0; + margin-bottom: 20px +} + +dt, +dd { + line-height: 1.42857143 +} + +dt { + font-weight: 700 +} + +dd { + margin-left: 0 +} + +@media (min-width:768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap + } + + .dl-horizontal dd { + margin-left: 180px + } +} + +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #777 +} + +.initialism { + font-size: 90%; + text-transform: uppercase +} + +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eee +} + +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0 +} + +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #777 +} + +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0' +} + +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + text-align: right; + border-right: 5px solid #eee; + border-left: 0 +} + +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: '' +} + +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014' +} + +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143 +} + +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace +} + +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px +} + +kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25) +} + +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: 700; + -webkit-box-shadow: none; + box-shadow: none +} + +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px +} + +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0 +} + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll +} + +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto +} + +@media (min-width:768px) { + .container { + width: 750px + } +} + +@media (min-width:992px) { + .container { + width: 970px + } +} + +@media (min-width:1200px) { + .container { + width: 1170px + } +} + +.container-fluid { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto +} + +.row { + margin-right: -15px; + margin-left: -15px +} + +.col-xs-1, +.col-sm-1, +.col-md-1, +.col-lg-1, +.col-xs-2, +.col-sm-2, +.col-md-2, +.col-lg-2, +.col-xs-3, +.col-sm-3, +.col-md-3, +.col-lg-3, +.col-xs-4, +.col-sm-4, +.col-md-4, +.col-lg-4, +.col-xs-5, +.col-sm-5, +.col-md-5, +.col-lg-5, +.col-xs-6, +.col-sm-6, +.col-md-6, +.col-lg-6, +.col-xs-7, +.col-sm-7, +.col-md-7, +.col-lg-7, +.col-xs-8, +.col-sm-8, +.col-md-8, +.col-lg-8, +.col-xs-9, +.col-sm-9, +.col-md-9, +.col-lg-9, +.col-xs-10, +.col-sm-10, +.col-md-10, +.col-lg-10, +.col-xs-11, +.col-sm-11, +.col-md-11, +.col-lg-11, +.col-xs-12, +.col-sm-12, +.col-md-12, +.col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px +} + +.col-xs-1, +.col-xs-2, +.col-xs-3, +.col-xs-4, +.col-xs-5, +.col-xs-6, +.col-xs-7, +.col-xs-8, +.col-xs-9, +.col-xs-10, +.col-xs-11, +.col-xs-12 { + float: left +} + +.col-xs-12 { + width: 100% +} + +.col-xs-11 { + width: 91.66666667% +} + +.col-xs-10 { + width: 83.33333333% +} + +.col-xs-9 { + width: 75% +} + +.col-xs-8 { + width: 66.66666667% +} + +.col-xs-7 { + width: 58.33333333% +} + +.col-xs-6 { + width: 50% +} + +.col-xs-5 { + width: 41.66666667% +} + +.col-xs-4 { + width: 33.33333333% +} + +.col-xs-3 { + width: 25% +} + +.col-xs-2 { + width: 16.66666667% +} + +.col-xs-1 { + width: 8.33333333% +} + +.col-xs-pull-12 { + right: 100% +} + +.col-xs-pull-11 { + right: 91.66666667% +} + +.col-xs-pull-10 { + right: 83.33333333% +} + +.col-xs-pull-9 { + right: 75% +} + +.col-xs-pull-8 { + right: 66.66666667% +} + +.col-xs-pull-7 { + right: 58.33333333% +} + +.col-xs-pull-6 { + right: 50% +} + +.col-xs-pull-5 { + right: 41.66666667% +} + +.col-xs-pull-4 { + right: 33.33333333% +} + +.col-xs-pull-3 { + right: 25% +} + +.col-xs-pull-2 { + right: 16.66666667% +} + +.col-xs-pull-1 { + right: 8.33333333% +} + +.col-xs-pull-0 { + right: auto +} + +.col-xs-push-12 { + left: 100% +} + +.col-xs-push-11 { + left: 91.66666667% +} + +.col-xs-push-10 { + left: 83.33333333% +} + +.col-xs-push-9 { + left: 75% +} + +.col-xs-push-8 { + left: 66.66666667% +} + +.col-xs-push-7 { + left: 58.33333333% +} + +.col-xs-push-6 { + left: 50% +} + +.col-xs-push-5 { + left: 41.66666667% +} + +.col-xs-push-4 { + left: 33.33333333% +} + +.col-xs-push-3 { + left: 25% +} + +.col-xs-push-2 { + left: 16.66666667% +} + +.col-xs-push-1 { + left: 8.33333333% +} + +.col-xs-push-0 { + left: auto +} + +.col-xs-offset-12 { + margin-left: 100% +} + +.col-xs-offset-11 { + margin-left: 91.66666667% +} + +.col-xs-offset-10 { + margin-left: 83.33333333% +} + +.col-xs-offset-9 { + margin-left: 75% +} + +.col-xs-offset-8 { + margin-left: 66.66666667% +} + +.col-xs-offset-7 { + margin-left: 58.33333333% +} + +.col-xs-offset-6 { + margin-left: 50% +} + +.col-xs-offset-5 { + margin-left: 41.66666667% +} + +.col-xs-offset-4 { + margin-left: 33.33333333% +} + +.col-xs-offset-3 { + margin-left: 25% +} + +.col-xs-offset-2 { + margin-left: 16.66666667% +} + +.col-xs-offset-1 { + margin-left: 8.33333333% +} + +.col-xs-offset-0 { + margin-left: 0 +} + +@media (min-width:768px) { + + .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 { + float: left + } + + .col-sm-12 { + width: 100% + } + + .col-sm-11 { + width: 91.66666667% + } + + .col-sm-10 { + width: 83.33333333% + } + + .col-sm-9 { + width: 75% + } + + .col-sm-8 { + width: 66.66666667% + } + + .col-sm-7 { + width: 58.33333333% + } + + .col-sm-6 { + width: 50% + } + + .col-sm-5 { + width: 41.66666667% + } + + .col-sm-4 { + width: 33.33333333% + } + + .col-sm-3 { + width: 25% + } + + .col-sm-2 { + width: 16.66666667% + } + + .col-sm-1 { + width: 8.33333333% + } + + .col-sm-pull-12 { + right: 100% + } + + .col-sm-pull-11 { + right: 91.66666667% + } + + .col-sm-pull-10 { + right: 83.33333333% + } + + .col-sm-pull-9 { + right: 75% + } + + .col-sm-pull-8 { + right: 66.66666667% + } + + .col-sm-pull-7 { + right: 58.33333333% + } + + .col-sm-pull-6 { + right: 50% + } + + .col-sm-pull-5 { + right: 41.66666667% + } + + .col-sm-pull-4 { + right: 33.33333333% + } + + .col-sm-pull-3 { + right: 25% + } + + .col-sm-pull-2 { + right: 16.66666667% + } + + .col-sm-pull-1 { + right: 8.33333333% + } + + .col-sm-pull-0 { + right: auto + } + + .col-sm-push-12 { + left: 100% + } + + .col-sm-push-11 { + left: 91.66666667% + } + + .col-sm-push-10 { + left: 83.33333333% + } + + .col-sm-push-9 { + left: 75% + } + + .col-sm-push-8 { + left: 66.66666667% + } + + .col-sm-push-7 { + left: 58.33333333% + } + + .col-sm-push-6 { + left: 50% + } + + .col-sm-push-5 { + left: 41.66666667% + } + + .col-sm-push-4 { + left: 33.33333333% + } + + .col-sm-push-3 { + left: 25% + } + + .col-sm-push-2 { + left: 16.66666667% + } + + .col-sm-push-1 { + left: 8.33333333% + } + + .col-sm-push-0 { + left: auto + } + + .col-sm-offset-12 { + margin-left: 100% + } + + .col-sm-offset-11 { + margin-left: 91.66666667% + } + + .col-sm-offset-10 { + margin-left: 83.33333333% + } + + .col-sm-offset-9 { + margin-left: 75% + } + + .col-sm-offset-8 { + margin-left: 66.66666667% + } + + .col-sm-offset-7 { + margin-left: 58.33333333% + } + + .col-sm-offset-6 { + margin-left: 50% + } + + .col-sm-offset-5 { + margin-left: 41.66666667% + } + + .col-sm-offset-4 { + margin-left: 33.33333333% + } + + .col-sm-offset-3 { + margin-left: 25% + } + + .col-sm-offset-2 { + margin-left: 16.66666667% + } + + .col-sm-offset-1 { + margin-left: 8.33333333% + } + + .col-sm-offset-0 { + margin-left: 0 + } +} + +@media (min-width:992px) { + + .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 { + float: left + } + + .col-md-12 { + width: 100% + } + + .col-md-11 { + width: 91.66666667% + } + + .col-md-10 { + width: 83.33333333% + } + + .col-md-9 { + width: 75% + } + + .col-md-8 { + width: 66.66666667% + } + + .col-md-7 { + width: 58.33333333% + } + + .col-md-6 { + width: 50% + } + + .col-md-5 { + width: 41.66666667% + } + + .col-md-4 { + width: 33.33333333% + } + + .col-md-3 { + width: 25% + } + + .col-md-2 { + width: 16.66666667% + } + + .col-md-1 { + width: 8.33333333% + } + + .col-md-pull-12 { + right: 100% + } + + .col-md-pull-11 { + right: 91.66666667% + } + + .col-md-pull-10 { + right: 83.33333333% + } + + .col-md-pull-9 { + right: 75% + } + + .col-md-pull-8 { + right: 66.66666667% + } + + .col-md-pull-7 { + right: 58.33333333% + } + + .col-md-pull-6 { + right: 50% + } + + .col-md-pull-5 { + right: 41.66666667% + } + + .col-md-pull-4 { + right: 33.33333333% + } + + .col-md-pull-3 { + right: 25% + } + + .col-md-pull-2 { + right: 16.66666667% + } + + .col-md-pull-1 { + right: 8.33333333% + } + + .col-md-pull-0 { + right: auto + } + + .col-md-push-12 { + left: 100% + } + + .col-md-push-11 { + left: 91.66666667% + } + + .col-md-push-10 { + left: 83.33333333% + } + + .col-md-push-9 { + left: 75% + } + + .col-md-push-8 { + left: 66.66666667% + } + + .col-md-push-7 { + left: 58.33333333% + } + + .col-md-push-6 { + left: 50% + } + + .col-md-push-5 { + left: 41.66666667% + } + + .col-md-push-4 { + left: 33.33333333% + } + + .col-md-push-3 { + left: 25% + } + + .col-md-push-2 { + left: 16.66666667% + } + + .col-md-push-1 { + left: 8.33333333% + } + + .col-md-push-0 { + left: auto + } + + .col-md-offset-12 { + margin-left: 100% + } + + .col-md-offset-11 { + margin-left: 91.66666667% + } + + .col-md-offset-10 { + margin-left: 83.33333333% + } + + .col-md-offset-9 { + margin-left: 75% + } + + .col-md-offset-8 { + margin-left: 66.66666667% + } + + .col-md-offset-7 { + margin-left: 58.33333333% + } + + .col-md-offset-6 { + margin-left: 50% + } + + .col-md-offset-5 { + margin-left: 41.66666667% + } + + .col-md-offset-4 { + margin-left: 33.33333333% + } + + .col-md-offset-3 { + margin-left: 25% + } + + .col-md-offset-2 { + margin-left: 16.66666667% + } + + .col-md-offset-1 { + margin-left: 8.33333333% + } + + .col-md-offset-0 { + margin-left: 0 + } +} + +@media (min-width:1200px) { + + .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 { + float: left + } + + .col-lg-12 { + width: 100% + } + + .col-lg-11 { + width: 91.66666667% + } + + .col-lg-10 { + width: 83.33333333% + } + + .col-lg-9 { + width: 75% + } + + .col-lg-8 { + width: 66.66666667% + } + + .col-lg-7 { + width: 58.33333333% + } + + .col-lg-6 { + width: 50% + } + + .col-lg-5 { + width: 41.66666667% + } + + .col-lg-4 { + width: 33.33333333% + } + + .col-lg-3 { + width: 25% + } + + .col-lg-2 { + width: 16.66666667% + } + + .col-lg-1 { + width: 8.33333333% + } + + .col-lg-pull-12 { + right: 100% + } + + .col-lg-pull-11 { + right: 91.66666667% + } + + .col-lg-pull-10 { + right: 83.33333333% + } + + .col-lg-pull-9 { + right: 75% + } + + .col-lg-pull-8 { + right: 66.66666667% + } + + .col-lg-pull-7 { + right: 58.33333333% + } + + .col-lg-pull-6 { + right: 50% + } + + .col-lg-pull-5 { + right: 41.66666667% + } + + .col-lg-pull-4 { + right: 33.33333333% + } + + .col-lg-pull-3 { + right: 25% + } + + .col-lg-pull-2 { + right: 16.66666667% + } + + .col-lg-pull-1 { + right: 8.33333333% + } + + .col-lg-pull-0 { + right: auto + } + + .col-lg-push-12 { + left: 100% + } + + .col-lg-push-11 { + left: 91.66666667% + } + + .col-lg-push-10 { + left: 83.33333333% + } + + .col-lg-push-9 { + left: 75% + } + + .col-lg-push-8 { + left: 66.66666667% + } + + .col-lg-push-7 { + left: 58.33333333% + } + + .col-lg-push-6 { + left: 50% + } + + .col-lg-push-5 { + left: 41.66666667% + } + + .col-lg-push-4 { + left: 33.33333333% + } + + .col-lg-push-3 { + left: 25% + } + + .col-lg-push-2 { + left: 16.66666667% + } + + .col-lg-push-1 { + left: 8.33333333% + } + + .col-lg-push-0 { + left: auto + } + + .col-lg-offset-12 { + margin-left: 100% + } + + .col-lg-offset-11 { + margin-left: 91.66666667% + } + + .col-lg-offset-10 { + margin-left: 83.33333333% + } + + .col-lg-offset-9 { + margin-left: 75% + } + + .col-lg-offset-8 { + margin-left: 66.66666667% + } + + .col-lg-offset-7 { + margin-left: 58.33333333% + } + + .col-lg-offset-6 { + margin-left: 50% + } + + .col-lg-offset-5 { + margin-left: 41.66666667% + } + + .col-lg-offset-4 { + margin-left: 33.33333333% + } + + .col-lg-offset-3 { + margin-left: 25% + } + + .col-lg-offset-2 { + margin-left: 16.66666667% + } + + .col-lg-offset-1 { + margin-left: 8.33333333% + } + + .col-lg-offset-0 { + margin-left: 0 + } +} + +table { + background-color: transparent +} + +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #777; + text-align: left +} + +th { + text-align: left +} + +.table { + width: 100%; + max-width: 100%; + margin-bottom: 20px +} + +.table>thead>tr>th, +.table>tbody>tr>th, +.table>tfoot>tr>th, +.table>thead>tr>td, +.table>tbody>tr>td, +.table>tfoot>tr>td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd +} + +.table>thead>tr>th { + vertical-align: bottom; + border-bottom: 2px solid #ddd +} + +.table>caption+thead>tr:first-child>th, +.table>colgroup+thead>tr:first-child>th, +.table>thead:first-child>tr:first-child>th, +.table>caption+thead>tr:first-child>td, +.table>colgroup+thead>tr:first-child>td, +.table>thead:first-child>tr:first-child>td { + border-top: 0 +} + +.table>tbody+tbody { + border-top: 2px solid #ddd +} + +.table .table { + background-color: #fff +} + +.table-condensed>thead>tr>th, +.table-condensed>tbody>tr>th, +.table-condensed>tfoot>tr>th, +.table-condensed>thead>tr>td, +.table-condensed>tbody>tr>td, +.table-condensed>tfoot>tr>td { + padding: 5px +} + +.table-bordered { + border: 1px solid #ddd +} + +.table-bordered>thead>tr>th, +.table-bordered>tbody>tr>th, +.table-bordered>tfoot>tr>th, +.table-bordered>thead>tr>td, +.table-bordered>tbody>tr>td, +.table-bordered>tfoot>tr>td { + border: 1px solid #ddd +} + +.table-bordered>thead>tr>th, +.table-bordered>thead>tr>td { + border-bottom-width: 2px +} + +.table-striped>tbody>tr:nth-child(odd) { + background-color: #f9f9f9 +} + +.table-hover>tbody>tr:hover { + background-color: #f5f5f5 +} + +table col[class*=col-] { + position: static; + display: table-column; + float: none +} + +table td[class*=col-], +table th[class*=col-] { + position: static; + display: table-cell; + float: none +} + +.table>thead>tr>td.active, +.table>tbody>tr>td.active, +.table>tfoot>tr>td.active, +.table>thead>tr>th.active, +.table>tbody>tr>th.active, +.table>tfoot>tr>th.active, +.table>thead>tr.active>td, +.table>tbody>tr.active>td, +.table>tfoot>tr.active>td, +.table>thead>tr.active>th, +.table>tbody>tr.active>th, +.table>tfoot>tr.active>th { + background-color: #f5f5f5 +} + +.table-hover>tbody>tr>td.active:hover, +.table-hover>tbody>tr>th.active:hover, +.table-hover>tbody>tr.active:hover>td, +.table-hover>tbody>tr:hover>.active, +.table-hover>tbody>tr.active:hover>th { + background-color: #e8e8e8 +} + +.table>thead>tr>td.success, +.table>tbody>tr>td.success, +.table>tfoot>tr>td.success, +.table>thead>tr>th.success, +.table>tbody>tr>th.success, +.table>tfoot>tr>th.success, +.table>thead>tr.success>td, +.table>tbody>tr.success>td, +.table>tfoot>tr.success>td, +.table>thead>tr.success>th, +.table>tbody>tr.success>th, +.table>tfoot>tr.success>th { + background-color: #dff0d8 +} + +.table-hover>tbody>tr>td.success:hover, +.table-hover>tbody>tr>th.success:hover, +.table-hover>tbody>tr.success:hover>td, +.table-hover>tbody>tr:hover>.success, +.table-hover>tbody>tr.success:hover>th { + background-color: #d0e9c6 +} + +.table>thead>tr>td.info, +.table>tbody>tr>td.info, +.table>tfoot>tr>td.info, +.table>thead>tr>th.info, +.table>tbody>tr>th.info, +.table>tfoot>tr>th.info, +.table>thead>tr.info>td, +.table>tbody>tr.info>td, +.table>tfoot>tr.info>td, +.table>thead>tr.info>th, +.table>tbody>tr.info>th, +.table>tfoot>tr.info>th { + background-color: #d9edf7 +} + +.table-hover>tbody>tr>td.info:hover, +.table-hover>tbody>tr>th.info:hover, +.table-hover>tbody>tr.info:hover>td, +.table-hover>tbody>tr:hover>.info, +.table-hover>tbody>tr.info:hover>th { + background-color: #c4e3f3 +} + +.table>thead>tr>td.warning, +.table>tbody>tr>td.warning, +.table>tfoot>tr>td.warning, +.table>thead>tr>th.warning, +.table>tbody>tr>th.warning, +.table>tfoot>tr>th.warning, +.table>thead>tr.warning>td, +.table>tbody>tr.warning>td, +.table>tfoot>tr.warning>td, +.table>thead>tr.warning>th, +.table>tbody>tr.warning>th, +.table>tfoot>tr.warning>th { + background-color: #fcf8e3 +} + +.table-hover>tbody>tr>td.warning:hover, +.table-hover>tbody>tr>th.warning:hover, +.table-hover>tbody>tr.warning:hover>td, +.table-hover>tbody>tr:hover>.warning, +.table-hover>tbody>tr.warning:hover>th { + background-color: #faf2cc +} + +.table>thead>tr>td.danger, +.table>tbody>tr>td.danger, +.table>tfoot>tr>td.danger, +.table>thead>tr>th.danger, +.table>tbody>tr>th.danger, +.table>tfoot>tr>th.danger, +.table>thead>tr.danger>td, +.table>tbody>tr.danger>td, +.table>tfoot>tr.danger>td, +.table>thead>tr.danger>th, +.table>tbody>tr.danger>th, +.table>tfoot>tr.danger>th { + background-color: #f2dede +} + +.table-hover>tbody>tr>td.danger:hover, +.table-hover>tbody>tr>th.danger:hover, +.table-hover>tbody>tr.danger:hover>td, +.table-hover>tbody>tr:hover>.danger, +.table-hover>tbody>tr.danger:hover>th { + background-color: #ebcccc +} + +.table-responsive { + min-height: .01%; + overflow-x: auto +} + +@media screen and (max-width:767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd + } + + .table-responsive>.table { + margin-bottom: 0 + } + + .table-responsive>.table>thead>tr>th, + .table-responsive>.table>tbody>tr>th, + .table-responsive>.table>tfoot>tr>th, + .table-responsive>.table>thead>tr>td, + .table-responsive>.table>tbody>tr>td, + .table-responsive>.table>tfoot>tr>td { + white-space: nowrap + } + + .table-responsive>.table-bordered { + border: 0 + } + + .table-responsive>.table-bordered>thead>tr>th:first-child, + .table-responsive>.table-bordered>tbody>tr>th:first-child, + .table-responsive>.table-bordered>tfoot>tr>th:first-child, + .table-responsive>.table-bordered>thead>tr>td:first-child, + .table-responsive>.table-bordered>tbody>tr>td:first-child, + .table-responsive>.table-bordered>tfoot>tr>td:first-child { + border-left: 0 + } + + .table-responsive>.table-bordered>thead>tr>th:last-child, + .table-responsive>.table-bordered>tbody>tr>th:last-child, + .table-responsive>.table-bordered>tfoot>tr>th:last-child, + .table-responsive>.table-bordered>thead>tr>td:last-child, + .table-responsive>.table-bordered>tbody>tr>td:last-child, + .table-responsive>.table-bordered>tfoot>tr>td:last-child { + border-right: 0 + } + + .table-responsive>.table-bordered>tbody>tr:last-child>th, + .table-responsive>.table-bordered>tfoot>tr:last-child>th, + .table-responsive>.table-bordered>tbody>tr:last-child>td, + .table-responsive>.table-bordered>tfoot>tr:last-child>td { + border-bottom: 0 + } +} + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0 +} + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333; + border: 0; + border-bottom: 1px solid #e5e5e5 +} + +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: 700 +} + +input[type=search] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box +} + +input[type=radio], +input[type=checkbox] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal +} + +input[type=file] { + display: block +} + +input[type=range] { + display: block; + width: 100% +} + +select[multiple], +select[size] { + height: auto +} + +input[type=file]:focus, +input[type=radio]:focus, +input[type=checkbox]:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px +} + +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555 +} + +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s +} + +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6) +} + +.form-control::-moz-placeholder { + color: #999; + opacity: 1 +} + +.form-control:-ms-input-placeholder { + color: #999 +} + +.form-control::-webkit-input-placeholder { + color: #999 +} + +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + cursor: not-allowed; + background-color: #eee; + opacity: 1 +} + +textarea.form-control { + height: auto +} + +input[type=search] { + -webkit-appearance: none +} + +@media screen and (-webkit-min-device-pixel-ratio:0) { + + input[type=date], + input[type=time], + input[type=datetime-local], + input[type=month] { + line-height: 34px + } + + input[type=date].input-sm, + input[type=time].input-sm, + input[type=datetime-local].input-sm, + input[type=month].input-sm { + line-height: 30px + } + + input[type=date].input-lg, + input[type=time].input-lg, + input[type=datetime-local].input-lg, + input[type=month].input-lg { + line-height: 46px + } +} + +.form-group { + margin-bottom: 15px +} + +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px +} + +.radio label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: 400; + cursor: pointer +} + +.radio input[type=radio], +.radio-inline input[type=radio], +.checkbox input[type=checkbox], +.checkbox-inline input[type=checkbox] { + position: absolute; + margin-top: 4px \9; + margin-left: -20px +} + +.radio+.radio, +.checkbox+.checkbox { + margin-top: -5px +} + +.radio-inline, +.checkbox-inline { + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: 400; + vertical-align: middle; + cursor: pointer +} + +.radio-inline+.radio-inline, +.checkbox-inline+.checkbox-inline { + margin-top: 0; + margin-left: 10px +} + +input[type=radio][disabled], +input[type=checkbox][disabled], +input[type=radio].disabled, +input[type=checkbox].disabled, +fieldset[disabled] input[type=radio], +fieldset[disabled] input[type=checkbox] { + cursor: not-allowed +} + +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed +} + +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed +} + +.form-control-static { + padding-top: 7px; + padding-bottom: 7px; + margin-bottom: 0 +} + +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-right: 0; + padding-left: 0 +} + +.input-sm, +.form-group-sm .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px +} + +select.input-sm, +select.form-group-sm .form-control { + height: 30px; + line-height: 30px +} + +textarea.input-sm, +textarea.form-group-sm .form-control, +select[multiple].input-sm, +select[multiple].form-group-sm .form-control { + height: auto +} + +.input-lg, +.form-group-lg .form-control { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px +} + +select.input-lg, +select.form-group-lg .form-control { + height: 46px; + line-height: 46px +} + +textarea.input-lg, +textarea.form-group-lg .form-control, +select[multiple].input-lg, +select[multiple].form-group-lg .form-control { + height: auto +} + +.has-feedback { + position: relative +} + +.has-feedback .form-control { + padding-right: 42.5px +} + +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; + pointer-events: none +} + +.input-lg+.form-control-feedback { + width: 46px; + height: 46px; + line-height: 46px +} + +.input-sm+.form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px +} + +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label { + color: #3c763d +} + +.has-success .form-control { + border-color: #3c763d; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075) +} + +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168 +} + +.has-success .input-group-addon { + color: #3c763d; + background-color: #dff0d8; + border-color: #3c763d +} + +.has-success .form-control-feedback { + color: #3c763d +} + +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label { + color: #8a6d3b +} + +.has-warning .form-control { + border-color: #8a6d3b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075) +} + +.has-warning .form-control:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b +} + +.has-warning .input-group-addon { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #8a6d3b +} + +.has-warning .form-control-feedback { + color: #8a6d3b +} + +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label { + color: #a94442 +} + +.has-error .form-control { + border-color: #a94442; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075) +} + +.has-error .form-control:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483 +} + +.has-error .input-group-addon { + color: #a94442; + background-color: #f2dede; + border-color: #a94442 +} + +.has-error .form-control-feedback { + color: #a94442 +} + +.has-feedback label~.form-control-feedback { + top: 25px +} + +.has-feedback label.sr-only~.form-control-feedback { + top: 0 +} + +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373 +} + +@media (min-width:768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle + } + + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle + } + + .form-inline .form-control-static { + display: inline-block + } + + .form-inline .input-group { + display: inline-table; + vertical-align: middle + } + + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto + } + + .form-inline .input-group>.form-control { + width: 100% + } + + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle + } + + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle + } + + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0 + } + + .form-inline .radio input[type=radio], + .form-inline .checkbox input[type=checkbox] { + position: relative; + margin-left: 0 + } + + .form-inline .has-feedback .form-control-feedback { + top: 0 + } +} + +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0 +} + +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px +} + +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px +} + +@media (min-width:768px) { + .form-horizontal .control-label { + padding-top: 7px; + margin-bottom: 0; + text-align: right + } +} + +.form-horizontal .has-feedback .form-control-feedback { + right: 15px +} + +@media (min-width:768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 14.3px + } +} + +@media (min-width:768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 6px + } +} + +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: 400; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px +} + +.btn:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px +} + +.btn:hover, +.btn:focus, +.btn.focus { + color: #333; + text-decoration: none +} + +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125) +} + +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + pointer-events: none; + cursor: not-allowed; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; + opacity: .65 +} + +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc +} + +.btn-default:hover, +.btn-default:focus, +.btn-default.focus, +.btn-default:active, +.btn-default.active, +.open>.dropdown-toggle.btn-default { + color: #333; + background-color: #e6e6e6; + border-color: #adadad +} + +.btn-default:active, +.btn-default.active, +.open>.dropdown-toggle.btn-default { + background-image: none +} + +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #fff; + border-color: #ccc +} + +.btn-default .badge { + color: #fff; + background-color: #333 +} + +.btn-primary { + color: #fff; + background-color: #a96308; + border-color: #aa6309 +} + +.btn-primary:hover, +.btn-primary:focus, +.btn-primary.focus, +.btn-primary:active, +.btn-primary.active, +.open>.dropdown-toggle.btn-primary { + color: #fff; + background-color: #6f420a; + border-color: #1e1202 +} + +.btn-primary:active, +.btn-primary.active, +.open>.dropdown-toggle.btn-primary { + background-image: none +} + +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #337ab7; + border-color: #2e6da4 +} + +.btn-primary .badge { + color: #337ab7; + background-color: #fff +} + +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4cae4c +} + +.btn-success:hover, +.btn-success:focus, +.btn-success.focus, +.btn-success:active, +.btn-success.active, +.open>.dropdown-toggle.btn-success { + color: #fff; + background-color: #449d44; + border-color: #398439 +} + +.btn-success:active, +.btn-success.active, +.open>.dropdown-toggle.btn-success { + background-image: none +} + +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #5cb85c; + border-color: #4cae4c +} + +.btn-success .badge { + color: #5cb85c; + background-color: #fff +} + +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46b8da +} + +.btn-info:hover, +.btn-info:focus, +.btn-info.focus, +.btn-info:active, +.btn-info.active, +.open>.dropdown-toggle.btn-info { + color: #fff; + background-color: #31b0d5; + border-color: #269abc +} + +.btn-info:active, +.btn-info.active, +.open>.dropdown-toggle.btn-info { + background-image: none +} + +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #5bc0de; + border-color: #46b8da +} + +.btn-info .badge { + color: #5bc0de; + background-color: #fff +} + +.btn-warning { + color: #fff; + background-color: #f0ad4e; + border-color: #eea236 +} + +.btn-warning:hover, +.btn-warning:focus, +.btn-warning.focus, +.btn-warning:active, +.btn-warning.active, +.open>.dropdown-toggle.btn-warning { + color: #fff; + background-color: #ec971f; + border-color: #d58512 +} + +.btn-warning:active, +.btn-warning.active, +.open>.dropdown-toggle.btn-warning { + background-image: none +} + +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #f0ad4e; + border-color: #eea236 +} + +.btn-warning .badge { + color: #f0ad4e; + background-color: #fff +} + +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43f3a +} + +.btn-danger:hover, +.btn-danger:focus, +.btn-danger.focus, +.btn-danger:active, +.btn-danger.active, +.open>.dropdown-toggle.btn-danger { + color: #fff; + background-color: #c9302c; + border-color: #ac2925 +} + +.btn-danger:active, +.btn-danger.active, +.open>.dropdown-toggle.btn-danger { + background-image: none +} + +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #d9534f; + border-color: #d43f3a +} + +.btn-danger .badge { + color: #d9534f; + background-color: #fff +} + +.btn-link { + font-weight: 400; + color: #337ab7; + border-radius: 0 +} + +.btn-link, +.btn-link:active, +.btn-link.active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none +} + +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent +} + +.btn-link:hover, +.btn-link:focus { + color: #23527c; + text-decoration: underline; + background-color: transparent +} + +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #777; + text-decoration: none +} + +.btn-lg, +.btn-group-lg>.btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px +} + +.btn-sm, +.btn-group-sm>.btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px +} + +.btn-xs, +.btn-group-xs>.btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px +} + +.btn-block { + display: block; + width: 100% +} + +.btn-block+.btn-block { + margin-top: 5px +} + +input[type=submit].btn-block, +input[type=reset].btn-block, +input[type=button].btn-block { + width: 100% +} + +.fade { + opacity: 0; + -webkit-transition: opacity .15s linear; + -o-transition: opacity .15s linear; + transition: opacity .15s linear +} + +.fade.in { + opacity: 1 +} + +.collapse { + display: none; + visibility: hidden +} + +.collapse.in { + display: block; + visibility: visible +} + +tr.collapse.in { + display: table-row +} + +tbody.collapse.in { + display: table-row-group +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition-timing-function: ease; + -o-transition-timing-function: ease; + transition-timing-function: ease; + -webkit-transition-duration: .35s; + -o-transition-duration: .35s; + transition-duration: .35s; + -webkit-transition-property: height, visibility; + -o-transition-property: height, visibility; + transition-property: height, visibility +} + +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px solid; + border-right: 4px solid transparent; + border-left: 4px solid transparent +} + +.dropdown { + position: relative +} + +.dropdown-toggle:focus { + outline: 0 +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + text-align: left; + list-style: none; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); + box-shadow: 0 6px 12px rgba(0, 0, 0, .175) +} + +.dropdown-menu.pull-right { + right: 0; + left: auto +} + +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5 +} + +.dropdown-menu>li>a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: 400; + line-height: 1.42857143; + color: #333; + white-space: nowrap +} + +.dropdown-menu>li>a:hover, +.dropdown-menu>li>a:focus { + color: #262626; + text-decoration: none; + background-color: #f5f5f5 +} + +.dropdown-menu>.active>a, +.dropdown-menu>.active>a:hover, +.dropdown-menu>.active>a:focus { + color: #fff; + text-decoration: none; + background-color: #337ab7; + outline: 0 +} + +.dropdown-menu>.disabled>a, +.dropdown-menu>.disabled>a:hover, +.dropdown-menu>.disabled>a:focus { + color: #777 +} + +.dropdown-menu>.disabled>a:hover, +.dropdown-menu>.disabled>a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false) +} + +.open>.dropdown-menu { + display: block +} + +.open>a { + outline: 0 +} + +.dropdown-menu-right { + right: 0; + left: auto +} + +.dropdown-menu-left { + right: auto; + left: 0 +} + +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #777; + white-space: nowrap +} + +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990 +} + +.pull-right>.dropdown-menu { + right: 0; + left: auto +} + +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + content: ""; + border-top: 0; + border-bottom: 4px solid +} + +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px +} + +@media (min-width:768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto + } + + .navbar-right .dropdown-menu-left { + right: auto; + left: 0 + } +} + +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle +} + +.btn-group>.btn, +.btn-group-vertical>.btn { + position: relative; + float: left +} + +.btn-group>.btn:hover, +.btn-group-vertical>.btn:hover, +.btn-group>.btn:focus, +.btn-group-vertical>.btn:focus, +.btn-group>.btn:active, +.btn-group-vertical>.btn:active, +.btn-group>.btn.active, +.btn-group-vertical>.btn.active { + z-index: 2 +} + +.btn-group .btn+.btn, +.btn-group .btn+.btn-group, +.btn-group .btn-group+.btn, +.btn-group .btn-group+.btn-group { + margin-left: -1px +} + +.btn-toolbar { + margin-left: -5px +} + +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left +} + +.btn-toolbar>.btn, +.btn-toolbar>.btn-group, +.btn-toolbar>.input-group { + margin-left: 5px +} + +.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0 +} + +.btn-group>.btn:first-child { + margin-left: 0 +} + +.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0 +} + +.btn-group>.btn:last-child:not(:first-child), +.btn-group>.dropdown-toggle:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0 +} + +.btn-group>.btn-group { + float: left +} + +.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn { + border-radius: 0 +} + +.btn-group>.btn-group:first-child>.btn:last-child, +.btn-group>.btn-group:first-child>.dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0 +} + +.btn-group>.btn-group:last-child>.btn:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0 +} + +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0 +} + +.btn-group>.btn+.dropdown-toggle { + padding-right: 8px; + padding-left: 8px +} + +.btn-group>.btn-lg+.dropdown-toggle { + padding-right: 12px; + padding-left: 12px +} + +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125) +} + +.btn-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none +} + +.btn .caret { + margin-left: 0 +} + +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0 +} + +.dropup .btn-lg .caret { + border-width: 0 5px 5px +} + +.btn-group-vertical>.btn, +.btn-group-vertical>.btn-group, +.btn-group-vertical>.btn-group>.btn { + display: block; + float: none; + width: 100%; + max-width: 100% +} + +.btn-group-vertical>.btn-group>.btn { + float: none +} + +.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(:first-child):not(:last-child) { + border-radius: 0 +} + +.btn-group-vertical>.btn:first-child:not(:last-child) { + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0 +} + +.btn-group-vertical>.btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 4px +} + +.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn { + border-radius: 0 +} + +.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child, +.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0 +} + +.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0 +} + +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate +} + +.btn-group-justified>.btn, +.btn-group-justified>.btn-group { + display: table-cell; + float: none; + width: 1% +} + +.btn-group-justified>.btn-group .btn { + width: 100% +} + +.btn-group-justified>.btn-group .dropdown-menu { + left: auto +} + +[data-toggle=buttons]>.btn input[type=radio], +[data-toggle=buttons]>.btn-group>.btn input[type=radio], +[data-toggle=buttons]>.btn input[type=checkbox], +[data-toggle=buttons]>.btn-group>.btn input[type=checkbox] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none +} + +.input-group { + position: relative; + display: table; + border-collapse: separate +} + +.input-group[class*=col-] { + float: none; + padding-right: 0; + padding-left: 0 +} + +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0 +} + +.input-group-lg>.form-control, +.input-group-lg>.input-group-addon, +.input-group-lg>.input-group-btn>.btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px +} + +select.input-group-lg>.form-control, +select.input-group-lg>.input-group-addon, +select.input-group-lg>.input-group-btn>.btn { + height: 46px; + line-height: 46px +} + +textarea.input-group-lg>.form-control, +textarea.input-group-lg>.input-group-addon, +textarea.input-group-lg>.input-group-btn>.btn, +select[multiple].input-group-lg>.form-control, +select[multiple].input-group-lg>.input-group-addon, +select[multiple].input-group-lg>.input-group-btn>.btn { + height: auto +} + +.input-group-sm>.form-control, +.input-group-sm>.input-group-addon, +.input-group-sm>.input-group-btn>.btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px +} + +select.input-group-sm>.form-control, +select.input-group-sm>.input-group-addon, +select.input-group-sm>.input-group-btn>.btn { + height: 30px; + line-height: 30px +} + +textarea.input-group-sm>.form-control, +textarea.input-group-sm>.input-group-addon, +textarea.input-group-sm>.input-group-btn>.btn, +select[multiple].input-group-sm>.form-control, +select[multiple].input-group-sm>.input-group-addon, +select[multiple].input-group-sm>.input-group-btn>.btn { + height: auto +} + +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell +} + +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0 +} + +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle +} + +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: 400; + line-height: 1; + color: #555; + text-align: center; + background-color: #eee; + border: 1px solid #ccc; + border-radius: 4px +} + +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px +} + +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px +} + +.input-group-addon input[type=radio], +.input-group-addon input[type=checkbox] { + margin-top: 0 +} + +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child>.btn, +.input-group-btn:first-child>.btn-group>.btn, +.input-group-btn:first-child>.dropdown-toggle, +.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child>.btn-group:not(:last-child)>.btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0 +} + +.input-group-addon:first-child { + border-right: 0 +} + +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child>.btn, +.input-group-btn:last-child>.btn-group>.btn, +.input-group-btn:last-child>.dropdown-toggle, +.input-group-btn:first-child>.btn:not(:first-child), +.input-group-btn:first-child>.btn-group:not(:first-child)>.btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0 +} + +.input-group-addon:last-child { + border-left: 0 +} + +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap +} + +.input-group-btn>.btn { + position: relative +} + +.input-group-btn>.btn+.btn { + margin-left: -1px +} + +.input-group-btn>.btn:hover, +.input-group-btn>.btn:focus, +.input-group-btn>.btn:active { + z-index: 2 +} + +.input-group-btn:first-child>.btn, +.input-group-btn:first-child>.btn-group { + margin-right: -1px +} + +.input-group-btn:last-child>.btn, +.input-group-btn:last-child>.btn-group { + margin-left: -1px +} + +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none +} + +.nav>li { + position: relative; + display: block +} + +.nav>li>a { + position: relative; + display: block; + padding: 10px 15px +} + +.nav>li>a:hover, +.nav>li>a:focus { + text-decoration: none; + background-color: #eee +} + +.nav>li.disabled>a { + color: #777 +} + +.nav>li.disabled>a:hover, +.nav>li.disabled>a:focus { + color: #777; + text-decoration: none; + cursor: not-allowed; + background-color: transparent +} + +.nav .open>a, +.nav .open>a:hover, +.nav .open>a:focus { + background-color: #eee; + border-color: #337ab7 +} + +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5 +} + +.nav>li>a>img { + max-width: none +} + +.nav-tabs { + border-bottom: 1px solid #ddd +} + +.nav-tabs>li { + float: left; + margin-bottom: -1px +} + +.nav-tabs>li>a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0 +} + +.nav-tabs>li>a:hover { + border-color: #eee #eee #ddd +} + +.nav-tabs>li.active>a, +.nav-tabs>li.active>a:hover, +.nav-tabs>li.active>a:focus { + color: #555; + cursor: default; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent +} + +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0 +} + +.nav-tabs.nav-justified>li { + float: none +} + +.nav-tabs.nav-justified>li>a { + margin-bottom: 5px; + text-align: center +} + +.nav-tabs.nav-justified>.dropdown .dropdown-menu { + top: auto; + left: auto +} + +@media (min-width:768px) { + .nav-tabs.nav-justified>li { + display: table-cell; + width: 1% + } + + .nav-tabs.nav-justified>li>a { + margin-bottom: 0 + } +} + +.nav-tabs.nav-justified>li>a { + margin-right: 0; + border-radius: 4px +} + +.nav-tabs.nav-justified>.active>a, +.nav-tabs.nav-justified>.active>a:hover, +.nav-tabs.nav-justified>.active>a:focus { + border: 1px solid #ddd +} + +@media (min-width:768px) { + .nav-tabs.nav-justified>li>a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0 + } + + .nav-tabs.nav-justified>.active>a, + .nav-tabs.nav-justified>.active>a:hover, + .nav-tabs.nav-justified>.active>a:focus { + border-bottom-color: #fff + } +} + +.nav-pills>li { + float: left +} + +.nav-pills>li>a { + border-radius: 4px +} + +.nav-pills>li+li { + margin-left: 2px +} + +.nav-pills>li.active>a, +.nav-pills>li.active>a:hover, +.nav-pills>li.active>a:focus { + color: #fff; + background-color: #337ab7 +} + +.nav-stacked>li { + float: none +} + +.nav-stacked>li+li { + margin-top: 2px; + margin-left: 0 +} + +.nav-justified { + width: 100% +} + +.nav-justified>li { + float: none +} + +.nav-justified>li>a { + margin-bottom: 5px; + text-align: center +} + +.nav-justified>.dropdown .dropdown-menu { + top: auto; + left: auto +} + +@media (min-width:768px) { + .nav-justified>li { + display: table-cell; + width: 1% + } + + .nav-justified>li>a { + margin-bottom: 0 + } +} + +.nav-tabs-justified { + border-bottom: 0 +} + +.nav-tabs-justified>li>a { + margin-right: 0; + border-radius: 4px +} + +.nav-tabs-justified>.active>a, +.nav-tabs-justified>.active>a:hover, +.nav-tabs-justified>.active>a:focus { + border: 1px solid #ddd +} + +@media (min-width:768px) { + .nav-tabs-justified>li>a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0 + } + + .nav-tabs-justified>.active>a, + .nav-tabs-justified>.active>a:hover, + .nav-tabs-justified>.active>a:focus { + border-bottom-color: #fff + } +} + +.tab-content>.tab-pane { + display: none; + visibility: hidden +} + +.tab-content>.active { + display: block; + visibility: visible +} + +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0 +} + +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent +} + +@media (min-width:768px) { + .navbar { + border-radius: 4px + } +} + +@media (min-width:768px) { + .navbar-header { + float: left + } +} + +.navbar-collapse { + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + -webkit-overflow-scrolling: touch; + border-top: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1) +} + +.navbar-collapse.in { + overflow-y: auto +} + +@media (min-width:768px) { + .navbar-collapse { + width: auto; + border-top: 0; + -webkit-box-shadow: none; + box-shadow: none + } + + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + visibility: visible !important + } + + .navbar-collapse.in { + overflow-y: visible + } + + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-right: 0; + padding-left: 0 + } +} + +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px +} + +@media (max-device-width:480px) and (orientation:landscape) { + + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px + } +} + +.container>.navbar-header, +.container-fluid>.navbar-header, +.container>.navbar-collapse, +.container-fluid>.navbar-collapse { + margin-right: -15px; + margin-left: -15px +} + +@media (min-width:768px) { + + .container>.navbar-header, + .container-fluid>.navbar-header, + .container>.navbar-collapse, + .container-fluid>.navbar-collapse { + margin-right: 0; + margin-left: 0 + } +} + +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px +} + +@media (min-width:768px) { + .navbar-static-top { + border-radius: 0 + } +} + +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030 +} + +@media (min-width:768px) { + + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0 + } +} + +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px +} + +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0 +} + +.navbar-brand { + float: left; + height: 50px; + padding: 15px 15px; + font-size: 18px; + line-height: 20px +} + +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none +} + +.navbar-brand>img { + display: block +} + +@media (min-width:768px) { + + .navbar>.container .navbar-brand, + .navbar>.container-fluid .navbar-brand { + margin-left: -15px + } +} + +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px +} + +.navbar-toggle:focus { + outline: 0 +} + +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px +} + +.navbar-toggle .icon-bar+.icon-bar { + margin-top: 4px +} + +@media (min-width:768px) { + .navbar-toggle { + display: none + } +} + +.navbar-nav { + margin: 7.5px -15px +} + +.navbar-nav>li>a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px +} + +@media (max-width:767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + -webkit-box-shadow: none; + box-shadow: none + } + + .navbar-nav .open .dropdown-menu>li>a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px + } + + .navbar-nav .open .dropdown-menu>li>a { + line-height: 20px + } + + .navbar-nav .open .dropdown-menu>li>a:hover, + .navbar-nav .open .dropdown-menu>li>a:focus { + background-image: none + } +} + +@media (min-width:768px) { + .navbar-nav { + float: left; + margin: 0 + } + + .navbar-nav>li { + float: left + } + + .navbar-nav>li>a { + padding-top: 15px; + padding-bottom: 15px + } +} + +.navbar-form { + padding: 10px 15px; + margin-top: 8px; + margin-right: -15px; + margin-bottom: 8px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1) +} + +@media (min-width:768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle + } + + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle + } + + .navbar-form .form-control-static { + display: inline-block + } + + .navbar-form .input-group { + display: inline-table; + vertical-align: middle + } + + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto + } + + .navbar-form .input-group>.form-control { + width: 100% + } + + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle + } + + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle + } + + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0 + } + + .navbar-form .radio input[type=radio], + .navbar-form .checkbox input[type=checkbox] { + position: relative; + margin-left: 0 + } + + .navbar-form .has-feedback .form-control-feedback { + top: 0 + } +} + +@media (max-width:767px) { + .navbar-form .form-group { + margin-bottom: 5px + } + + .navbar-form .form-group:last-child { + margin-bottom: 0 + } +} + +@media (min-width:768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none + } +} + +.navbar-nav>li>.dropdown-menu { + margin-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0 +} + +.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0 +} + +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px +} + +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px +} + +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px +} + +.navbar-text { + margin-top: 15px; + margin-bottom: 15px +} + +@media (min-width:768px) { + .navbar-text { + float: left; + margin-right: 15px; + margin-left: 15px + } +} + +@media (min-width:768px) { + .navbar-left { + float: left !important + } + + .navbar-right { + float: right !important; + margin-right: -15px + } + + .navbar-right~.navbar-right { + margin-right: 0 + } +} + +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7 +} + +.navbar-default .navbar-brand { + color: #777 +} + +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent +} + +.navbar-default .navbar-text { + color: #777 +} + +.navbar-default .navbar-nav>li>a { + color: #777 +} + +.navbar-default .navbar-nav>li>a:hover, +.navbar-default .navbar-nav>li>a:focus { + color: #333; + background-color: transparent +} + +.navbar-default .navbar-nav>.active>a, +.navbar-default .navbar-nav>.active>a:hover, +.navbar-default .navbar-nav>.active>a:focus { + color: #555; + background-color: #e7e7e7 +} + +.navbar-default .navbar-nav>.disabled>a, +.navbar-default .navbar-nav>.disabled>a:hover, +.navbar-default .navbar-nav>.disabled>a:focus { + color: #ccc; + background-color: transparent +} + +.navbar-default .navbar-toggle { + border-color: #ddd +} + +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #ddd +} + +.navbar-default .navbar-toggle .icon-bar { + background-color: #888 +} + +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e7e7e7 +} + +.navbar-default .navbar-nav>.open>a, +.navbar-default .navbar-nav>.open>a:hover, +.navbar-default .navbar-nav>.open>a:focus { + color: #555; + background-color: #e7e7e7 +} + +@media (max-width:767px) { + .navbar-default .navbar-nav .open .dropdown-menu>li>a { + color: #777 + } + + .navbar-default .navbar-nav .open .dropdown-menu>li>a:hover, + .navbar-default .navbar-nav .open .dropdown-menu>li>a:focus { + color: #333; + background-color: transparent + } + + .navbar-default .navbar-nav .open .dropdown-menu>.active>a, + .navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover, + .navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus { + color: #555; + background-color: #e7e7e7 + } + + .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a, + .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover, + .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus { + color: #ccc; + background-color: transparent + } +} + +.navbar-default .navbar-link { + color: #777 +} + +.navbar-default .navbar-link:hover { + color: #333 +} + +.navbar-default .btn-link { + color: #777 +} + +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #333 +} + +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #ccc +} + +.navbar-inverse { + background-color: #222; + border-color: #080808 +} + +.navbar-inverse .navbar-brand { + color: #9d9d9d +} + +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #fff; + background-color: transparent +} + +.navbar-inverse .navbar-text { + color: #9d9d9d +} + +.navbar-inverse .navbar-nav>li>a { + color: #9d9d9d +} + +.navbar-inverse .navbar-nav>li>a:hover, +.navbar-inverse .navbar-nav>li>a:focus { + color: #fff; + background-color: transparent +} + +.navbar-inverse .navbar-nav>.active>a, +.navbar-inverse .navbar-nav>.active>a:hover, +.navbar-inverse .navbar-nav>.active>a:focus { + color: #fff; + background-color: #080808 +} + +.navbar-inverse .navbar-nav>.disabled>a, +.navbar-inverse .navbar-nav>.disabled>a:hover, +.navbar-inverse .navbar-nav>.disabled>a:focus { + color: #444; + background-color: transparent +} + +.navbar-inverse .navbar-toggle { + border-color: #333 +} + +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333 +} + +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #fff +} + +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010 +} + +.navbar-inverse .navbar-nav>.open>a, +.navbar-inverse .navbar-nav>.open>a:hover, +.navbar-inverse .navbar-nav>.open>a:focus { + color: #fff; + background-color: #080808 +} + +@media (max-width:767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header { + border-color: #080808 + } + + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #080808 + } + + .navbar-inverse .navbar-nav .open .dropdown-menu>li>a { + color: #9d9d9d + } + + .navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus { + color: #fff; + background-color: transparent + } + + .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a, + .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus { + color: #fff; + background-color: #080808 + } + + .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a, + .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus { + color: #444; + background-color: transparent + } +} + +.navbar-inverse .navbar-link { + color: #9d9d9d +} + +.navbar-inverse .navbar-link:hover { + color: #fff +} + +.navbar-inverse .btn-link { + color: #9d9d9d +} + +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #fff +} + +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #444 +} + +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px +} + +.breadcrumb>li { + display: inline-block +} + +.breadcrumb>li+li:before { + padding: 0 5px; + color: #ccc; + content: "/\00a0" +} + +.breadcrumb>.active { + color: #777 +} + +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px +} + +.pagination>li { + display: inline +} + +.pagination>li>a, +.pagination>li>span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.42857143; + color: #337ab7; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd +} + +.pagination>li:first-child>a, +.pagination>li:first-child>span { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px +} + +.pagination>li:last-child>a, +.pagination>li:last-child>span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px +} + +.pagination>li>a:hover, +.pagination>li>span:hover, +.pagination>li>a:focus, +.pagination>li>span:focus { + color: #23527c; + background-color: #eee; + border-color: #ddd +} + +.pagination>.active>a, +.pagination>.active>span, +.pagination>.active>a:hover, +.pagination>.active>span:hover, +.pagination>.active>a:focus, +.pagination>.active>span:focus { + z-index: 2; + color: #fff; + cursor: default; + background-color: #337ab7; + border-color: #337ab7 +} + +.pagination>.disabled>span, +.pagination>.disabled>span:hover, +.pagination>.disabled>span:focus, +.pagination>.disabled>a, +.pagination>.disabled>a:hover, +.pagination>.disabled>a:focus { + color: #777; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd +} + +.pagination-lg>li>a, +.pagination-lg>li>span { + padding: 10px 16px; + font-size: 18px +} + +.pagination-lg>li:first-child>a, +.pagination-lg>li:first-child>span { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px +} + +.pagination-lg>li:last-child>a, +.pagination-lg>li:last-child>span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px +} + +.pagination-sm>li>a, +.pagination-sm>li>span { + padding: 5px 10px; + font-size: 12px +} + +.pagination-sm>li:first-child>a, +.pagination-sm>li:first-child>span { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px +} + +.pagination-sm>li:last-child>a, +.pagination-sm>li:last-child>span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px +} + +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none +} + +.pager li { + display: inline +} + +.pager li>a, +.pager li>span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 15px +} + +.pager li>a:hover, +.pager li>a:focus { + text-decoration: none; + background-color: #eee +} + +.pager .next>a, +.pager .next>span { + float: right +} + +.pager .previous>a, +.pager .previous>span { + float: left +} + +.pager .disabled>a, +.pager .disabled>a:hover, +.pager .disabled>a:focus, +.pager .disabled>span { + color: #777; + cursor: not-allowed; + background-color: #fff +} + +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: 700; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em +} + +a.label:hover, +a.label:focus { + color: #fff; + text-decoration: none; + cursor: pointer +} + +.label:empty { + display: none +} + +.btn .label { + position: relative; + top: -1px +} + +.label-default { + background-color: #777 +} + +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #5e5e5e +} + +.label-primary { + background-color: #337ab7 +} + +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #6f420a +} + +.label-success { + background-color: #5cb85c +} + +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44 +} + +.label-info { + background-color: #5bc0de +} + +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5 +} + +.label-warning { + background-color: #f0ad4e +} + +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f +} + +.label-danger { + background-color: #d9534f +} + +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c +} + +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: 700; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + background-color: #777; + border-radius: 10px +} + +.badge:empty { + display: none +} + +.btn .badge { + position: relative; + top: -1px +} + +.btn-xs .badge { + top: 0; + padding: 1px 5px +} + +a.badge:hover, +a.badge:focus { + color: #fff; + text-decoration: none; + cursor: pointer +} + +.list-group-item.active>.badge, +.nav-pills>.active>a>.badge { + color: #337ab7; + background-color: #fff +} + +.list-group-item>.badge { + float: right +} + +.list-group-item>.badge+.badge { + margin-right: 5px +} + +.nav-pills>li>a>.badge { + margin-left: 3px +} + +.jumbotron { + padding: 30px 15px; + margin-bottom: 30px; + color: inherit; + background-color: #eee +} + +.jumbotron h1, +.jumbotron .h1 { + color: inherit +} + +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200 +} + +.jumbotron>hr { + border-top-color: #d5d5d5 +} + +.container .jumbotron, +.container-fluid .jumbotron { + border-radius: 6px +} + +.jumbotron .container { + max-width: 100% +} + +@media screen and (min-width:768px) { + .jumbotron { + padding: 48px 0 + } + + .container .jumbotron, + .container-fluid .jumbotron { + padding-right: 60px; + padding-left: 60px + } + + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px + } +} + +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: border .2s ease-in-out; + -o-transition: border .2s ease-in-out; + transition: border .2s ease-in-out +} + +.thumbnail>img, +.thumbnail a>img { + margin-right: auto; + margin-left: auto +} + +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #337ab7 +} + +.thumbnail .caption { + padding: 9px; + color: #333 +} + +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px +} + +.alert h4 { + margin-top: 0; + color: inherit +} + +.alert .alert-link { + font-weight: 700 +} + +.alert>p, +.alert>ul { + margin-bottom: 0 +} + +.alert>p+p { + margin-top: 5px +} + +.alert-dismissable, +.alert-dismissible { + padding-right: 35px +} + +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit +} + +.alert-success { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6 +} + +.alert-success hr { + border-top-color: #c9e2b3 +} + +.alert-success .alert-link { + color: #2b542c +} + +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1 +} + +.alert-info hr { + border-top-color: #a6e1ec +} + +.alert-info .alert-link { + color: #245269 +} + +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc +} + +.alert-warning hr { + border-top-color: #f7e1b5 +} + +.alert-warning .alert-link { + color: #66512c +} + +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1 +} + +.alert-danger hr { + border-top-color: #e4b9c0 +} + +.alert-danger .alert-link { + color: #843534 +} + +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0 + } + + to { + background-position: 0 0 + } +} + +@-o-keyframes progress-bar-stripes { + from { + background-position: 40px 0 + } + + to { + background-position: 0 0 + } +} + +@keyframes progress-bar-stripes { + from { + background-position: 40px 0 + } + + to { + background-position: 0 0 + } +} + +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1) +} + +.progress-bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: #337ab7; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + -webkit-transition: width .6s ease; + -o-transition: width .6s ease; + transition: width .6s ease +} + +.progress-striped .progress-bar, +.progress-bar-striped { + background-image: -webkit-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-image: -o-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-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); + -webkit-background-size: 40px 40px; + background-size: 40px 40px +} + +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite +} + +.progress-bar-success { + background-color: #5cb85c +} + +.progress-striped .progress-bar-success { + background-image: -webkit-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-image: -o-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-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) +} + +.progress-bar-info { + background-color: #5bc0de +} + +.progress-striped .progress-bar-info { + background-image: -webkit-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-image: -o-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-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) +} + +.progress-bar-warning { + background-color: #f0ad4e +} + +.progress-striped .progress-bar-warning { + background-image: -webkit-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-image: -o-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-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) +} + +.progress-bar-danger { + background-color: #d9534f +} + +.progress-striped .progress-bar-danger { + background-image: -webkit-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-image: -o-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-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) +} + +.media { + margin-top: 15px +} + +.media:first-child { + margin-top: 0 +} + +.media-right, +.media>.pull-right { + padding-left: 10px +} + +.media-left, +.media>.pull-left { + padding-right: 10px +} + +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top +} + +.media-middle { + vertical-align: middle +} + +.media-bottom { + vertical-align: bottom +} + +.media-heading { + margin-top: 0; + margin-bottom: 5px +} + +.media-list { + padding-left: 0; + list-style: none +} + +.list-group { + padding-left: 0; + margin-bottom: 20px +} + +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid #ddd +} + +.list-group-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px +} + +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px +} + +a.list-group-item { + color: #555 +} + +a.list-group-item .list-group-item-heading { + color: #333 +} + +a.list-group-item:hover, +a.list-group-item:focus { + color: #555; + text-decoration: none; + background-color: #f5f5f5 +} + +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + color: #777; + cursor: not-allowed; + background-color: #eee +} + +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit +} + +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #777 +} + +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #fff; + background-color: #337ab7; + border-color: #337ab7 +} + +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading>small, +.list-group-item.active:hover .list-group-item-heading>small, +.list-group-item.active:focus .list-group-item-heading>small, +.list-group-item.active .list-group-item-heading>.small, +.list-group-item.active:hover .list-group-item-heading>.small, +.list-group-item.active:focus .list-group-item-heading>.small { + color: inherit +} + +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #c7ddef +} + +.list-group-item-success { + color: #3c763d; + background-color: #dff0d8 +} + +a.list-group-item-success { + color: #3c763d +} + +a.list-group-item-success .list-group-item-heading { + color: inherit +} + +a.list-group-item-success:hover, +a.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6 +} + +a.list-group-item-success.active, +a.list-group-item-success.active:hover, +a.list-group-item-success.active:focus { + color: #fff; + background-color: #3c763d; + border-color: #3c763d +} + +.list-group-item-info { + color: #31708f; + background-color: #d9edf7 +} + +a.list-group-item-info { + color: #31708f +} + +a.list-group-item-info .list-group-item-heading { + color: inherit +} + +a.list-group-item-info:hover, +a.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3 +} + +a.list-group-item-info.active, +a.list-group-item-info.active:hover, +a.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f +} + +.list-group-item-warning { + color: #8a6d3b; + background-color: #fcf8e3 +} + +a.list-group-item-warning { + color: #8a6d3b +} + +a.list-group-item-warning .list-group-item-heading { + color: inherit +} + +a.list-group-item-warning:hover, +a.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc +} + +a.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus { + color: #fff; + background-color: #8a6d3b; + border-color: #8a6d3b +} + +.list-group-item-danger { + color: #a94442; + background-color: #f2dede +} + +a.list-group-item-danger { + color: #a94442 +} + +a.list-group-item-danger .list-group-item-heading { + color: inherit +} + +a.list-group-item-danger:hover, +a.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc +} + +a.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442 +} + +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px +} + +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3 +} + +.panel { + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: 0 1px 1px rgba(0, 0, 0, .05) +} + +.panel-body { + padding: 15px +} + +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px +} + +.panel-heading>.dropdown .dropdown-toggle { + color: inherit +} + +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit +} + +.panel-title>a { + color: inherit +} + +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px +} + +.panel>.list-group, +.panel>.panel-collapse>.list-group { + margin-bottom: 0 +} + +.panel>.list-group .list-group-item, +.panel>.panel-collapse>.list-group .list-group-item { + border-width: 1px 0; + border-radius: 0 +} + +.panel>.list-group:first-child .list-group-item:first-child, +.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px +} + +.panel>.list-group:last-child .list-group-item:last-child, +.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px +} + +.panel-heading+.list-group .list-group-item:first-child { + border-top-width: 0 +} + +.list-group+.panel-footer { + border-top-width: 0 +} + +.panel>.table, +.panel>.table-responsive>.table, +.panel>.panel-collapse>.table { + margin-bottom: 0 +} + +.panel>.table caption, +.panel>.table-responsive>.table caption, +.panel>.panel-collapse>.table caption { + padding-right: 15px; + padding-left: 15px +} + +.panel>.table:first-child, +.panel>.table-responsive:first-child>.table:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px +} + +.panel>.table:first-child>thead:first-child>tr:first-child, +.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child, +.panel>.table:first-child>tbody:first-child>tr:first-child, +.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px +} + +.panel>.table:first-child>thead:first-child>tr:first-child td:first-child, +.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child, +.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child, +.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child, +.panel>.table:first-child>thead:first-child>tr:first-child th:first-child, +.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child, +.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child, +.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child { + border-top-left-radius: 3px +} + +.panel>.table:first-child>thead:first-child>tr:first-child td:last-child, +.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child, +.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child, +.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child, +.panel>.table:first-child>thead:first-child>tr:first-child th:last-child, +.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child, +.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child, +.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child { + border-top-right-radius: 3px +} + +.panel>.table:last-child, +.panel>.table-responsive:last-child>.table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px +} + +.panel>.table:last-child>tbody:last-child>tr:last-child, +.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child, +.panel>.table:last-child>tfoot:last-child>tr:last-child, +.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px +} + +.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child, +.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child, +.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child, +.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child, +.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child, +.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child, +.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child, +.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child { + border-bottom-left-radius: 3px +} + +.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child, +.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child, +.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child, +.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child, +.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child, +.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child, +.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child, +.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child { + border-bottom-right-radius: 3px +} + +.panel>.panel-body+.table, +.panel>.panel-body+.table-responsive, +.panel>.table+.panel-body, +.panel>.table-responsive+.panel-body { + border-top: 1px solid #ddd +} + +.panel>.table>tbody:first-child>tr:first-child th, +.panel>.table>tbody:first-child>tr:first-child td { + border-top: 0 +} + +.panel>.table-bordered, +.panel>.table-responsive>.table-bordered { + border: 0 +} + +.panel>.table-bordered>thead>tr>th:first-child, +.panel>.table-responsive>.table-bordered>thead>tr>th:first-child, +.panel>.table-bordered>tbody>tr>th:first-child, +.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child, +.panel>.table-bordered>tfoot>tr>th:first-child, +.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child, +.panel>.table-bordered>thead>tr>td:first-child, +.panel>.table-responsive>.table-bordered>thead>tr>td:first-child, +.panel>.table-bordered>tbody>tr>td:first-child, +.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child, +.panel>.table-bordered>tfoot>tr>td:first-child, +.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child { + border-left: 0 +} + +.panel>.table-bordered>thead>tr>th:last-child, +.panel>.table-responsive>.table-bordered>thead>tr>th:last-child, +.panel>.table-bordered>tbody>tr>th:last-child, +.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child, +.panel>.table-bordered>tfoot>tr>th:last-child, +.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child, +.panel>.table-bordered>thead>tr>td:last-child, +.panel>.table-responsive>.table-bordered>thead>tr>td:last-child, +.panel>.table-bordered>tbody>tr>td:last-child, +.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child, +.panel>.table-bordered>tfoot>tr>td:last-child, +.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child { + border-right: 0 +} + +.panel>.table-bordered>thead>tr:first-child>td, +.panel>.table-responsive>.table-bordered>thead>tr:first-child>td, +.panel>.table-bordered>tbody>tr:first-child>td, +.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td, +.panel>.table-bordered>thead>tr:first-child>th, +.panel>.table-responsive>.table-bordered>thead>tr:first-child>th, +.panel>.table-bordered>tbody>tr:first-child>th, +.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th { + border-bottom: 0 +} + +.panel>.table-bordered>tbody>tr:last-child>td, +.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td, +.panel>.table-bordered>tfoot>tr:last-child>td, +.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td, +.panel>.table-bordered>tbody>tr:last-child>th, +.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th, +.panel>.table-bordered>tfoot>tr:last-child>th, +.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th { + border-bottom: 0 +} + +.panel>.table-responsive { + margin-bottom: 0; + border: 0 +} + +.panel-group { + margin-bottom: 20px +} + +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px +} + +.panel-group .panel+.panel { + margin-top: 5px +} + +.panel-group .panel-heading { + border-bottom: 0 +} + +.panel-group .panel-heading+.panel-collapse>.panel-body, +.panel-group .panel-heading+.panel-collapse>.list-group { + border-top: 1px solid #ddd +} + +.panel-group .panel-footer { + border-top: 0 +} + +.panel-group .panel-footer+.panel-collapse .panel-body { + border-bottom: 1px solid #ddd +} + +.panel-default { + border-color: #ddd +} + +.panel-default>.panel-heading { + color: #333; + background-color: #f5f5f5; + border-color: #ddd +} + +.panel-default>.panel-heading+.panel-collapse>.panel-body { + border-top-color: #ddd +} + +.panel-default>.panel-heading .badge { + color: #f5f5f5; + background-color: #333 +} + +.panel-default>.panel-footer+.panel-collapse>.panel-body { + border-bottom-color: #ddd +} + +.panel-primary { + border-color: #337ab7 +} + +.panel-primary>.panel-heading { + color: #fff; + background-color: #337ab7; + border-color: #337ab7 +} + +.panel-primary>.panel-heading+.panel-collapse>.panel-body { + border-top-color: #337ab7 +} + +.panel-primary>.panel-heading .badge { + color: #337ab7; + background-color: #fff +} + +.panel-primary>.panel-footer+.panel-collapse>.panel-body { + border-bottom-color: #337ab7 +} + +.panel-success { + border-color: #d6e9c6 +} + +.panel-success>.panel-heading { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6 +} + +.panel-success>.panel-heading+.panel-collapse>.panel-body { + border-top-color: #d6e9c6 +} + +.panel-success>.panel-heading .badge { + color: #dff0d8; + background-color: #3c763d +} + +.panel-success>.panel-footer+.panel-collapse>.panel-body { + border-bottom-color: #d6e9c6 +} + +.panel-info { + border-color: #bce8f1 +} + +.panel-info>.panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1 +} + +.panel-info>.panel-heading+.panel-collapse>.panel-body { + border-top-color: #bce8f1 +} + +.panel-info>.panel-heading .badge { + color: #d9edf7; + background-color: #31708f +} + +.panel-info>.panel-footer+.panel-collapse>.panel-body { + border-bottom-color: #bce8f1 +} + +.panel-warning { + border-color: #faebcc +} + +.panel-warning>.panel-heading { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc +} + +.panel-warning>.panel-heading+.panel-collapse>.panel-body { + border-top-color: #faebcc +} + +.panel-warning>.panel-heading .badge { + color: #fcf8e3; + background-color: #8a6d3b +} + +.panel-warning>.panel-footer+.panel-collapse>.panel-body { + border-bottom-color: #faebcc +} + +.panel-danger { + border-color: #ebccd1 +} + +.panel-danger>.panel-heading { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1 +} + +.panel-danger>.panel-heading+.panel-collapse>.panel-body { + border-top-color: #ebccd1 +} + +.panel-danger>.panel-heading .badge { + color: #f2dede; + background-color: #a94442 +} + +.panel-danger>.panel-footer+.panel-collapse>.panel-body { + border-bottom-color: #ebccd1 +} + +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden +} + +.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.embed-responsive-16by9 { + padding-bottom: 56.25% +} + +.embed-responsive.embed-responsive-4by3 { + padding-bottom: 75% +} + +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05) +} + +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, .15) +} + +.well-lg { + padding: 24px; + border-radius: 6px +} + +.well-sm { + padding: 9px; + border-radius: 3px +} + +.close { + float: right; + font-size: 21px; + font-weight: 700; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: .2 +} + +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: .5 +} + +button.close { + -webkit-appearance: none; + padding: 0; + cursor: pointer; + background: 0 0; + border: 0 +} + +.modal-open { + overflow: hidden +} + +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + display: none; + overflow: hidden; + -webkit-overflow-scrolling: touch; + outline: 0 +} + +.modal.fade .modal-dialog { + -webkit-transition: -webkit-transform .3s ease-out; + -o-transition: -o-transform .3s ease-out; + transition: transform .3s ease-out; + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); + transform: translate(0, -25%) +} + +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0) +} + +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto +} + +.modal-dialog { + position: relative; + width: auto; + margin: 10px +} + +.modal-content { + position: relative; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + outline: 0; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); + box-shadow: 0 3px 9px rgba(0, 0, 0, .5) +} + +.modal-backdrop { + position: absolute; + top: 0; + right: 0; + left: 0; + background-color: #000 +} + +.modal-backdrop.fade { + filter: alpha(opacity=0); + opacity: 0 +} + +.modal-backdrop.in { + filter: alpha(opacity=50); + opacity: .5 +} + +.modal-header { + min-height: 16.43px; + padding: 15px; + border-bottom: 1px solid #e5e5e5 +} + +.modal-header .close { + margin-top: -2px +} + +.modal-title { + margin: 0; + line-height: 1.42857143 +} + +.modal-body { + position: relative; + padding: 15px +} + +.modal-footer { + padding: 15px; + text-align: right; + border-top: 1px solid #e5e5e5 +} + +.modal-footer .btn+.btn { + margin-bottom: 0; + margin-left: 5px +} + +.modal-footer .btn-group .btn+.btn { + margin-left: -1px +} + +.modal-footer .btn-block+.btn-block { + margin-left: 0 +} + +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll +} + +@media (min-width:768px) { + .modal-dialog { + width: 600px; + margin: 30px auto + } + + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + box-shadow: 0 5px 15px rgba(0, 0, 0, .5) + } + + .modal-sm { + width: 300px + } +} + +@media (min-width:992px) { + .modal-lg { + width: 900px + } +} + +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 12px; + font-weight: 400; + line-height: 1.4; + visibility: visible; + filter: alpha(opacity=0); + opacity: 0 +} + +.tooltip.in { + filter: alpha(opacity=90); + opacity: .9 +} + +.tooltip.top { + padding: 5px 0; + margin-top: -3px +} + +.tooltip.right { + padding: 0 5px; + margin-left: 3px +} + +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px +} + +.tooltip.left { + padding: 0 5px; + margin-left: -3px +} + +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + text-decoration: none; + background-color: #000; + border-radius: 4px +} + +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid +} + +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000 +} + +.tooltip.top-left .tooltip-arrow { + right: 5px; + bottom: 0; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000 +} + +.tooltip.top-right .tooltip-arrow { + bottom: 0; + left: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000 +} + +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000 +} + +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000 +} + +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000 +} + +.tooltip.bottom-left .tooltip-arrow { + top: 0; + right: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000 +} + +.tooltip.bottom-right .tooltip-arrow { + top: 0; + left: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000 +} + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + font-weight: 400; + line-height: 1.42857143; + text-align: left; + white-space: normal; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2) +} + +.popover.top { + margin-top: -10px +} + +.popover.right { + margin-left: 10px +} + +.popover.bottom { + margin-top: 10px +} + +.popover.left { + margin-left: -10px +} + +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0 +} + +.popover-content { + padding: 9px 14px +} + +.popover>.arrow, +.popover>.arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid +} + +.popover>.arrow { + border-width: 11px +} + +.popover>.arrow:after { + content: ""; + border-width: 10px +} + +.popover.top>.arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(0, 0, 0, .25); + border-bottom-width: 0 +} + +.popover.top>.arrow:after { + bottom: 1px; + margin-left: -10px; + content: " "; + border-top-color: #fff; + border-bottom-width: 0 +} + +.popover.right>.arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999; + border-right-color: rgba(0, 0, 0, .25); + border-left-width: 0 +} + +.popover.right>.arrow:after { + bottom: -10px; + left: 1px; + content: " "; + border-right-color: #fff; + border-left-width: 0 +} + +.popover.bottom>.arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999; + border-bottom-color: rgba(0, 0, 0, .25) +} + +.popover.bottom>.arrow:after { + top: 1px; + margin-left: -10px; + content: " "; + border-top-width: 0; + border-bottom-color: #fff +} + +.popover.left>.arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999; + border-left-color: rgba(0, 0, 0, .25) +} + +.popover.left>.arrow:after { + right: 1px; + bottom: -10px; + content: " "; + border-right-width: 0; + border-left-color: #fff +} + +.carousel { + position: relative +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden +} + +.carousel-inner>.item { + position: relative; + display: none; + -webkit-transition: .6s ease-in-out left; + -o-transition: .6s ease-in-out left; + transition: .6s ease-in-out left +} + +.carousel-inner>.item>img, +.carousel-inner>.item>a>img { + line-height: 1 +} + +@media all and (transform-3d), +(-webkit-transform-3d) { + .carousel-inner>.item { + -webkit-transition: -webkit-transform .6s ease-in-out; + -o-transition: -o-transform .6s ease-in-out; + transition: transform .6s ease-in-out; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000; + perspective: 1000 + } + + .carousel-inner>.item.next, + .carousel-inner>.item.active.right { + left: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0) + } + + .carousel-inner>.item.prev, + .carousel-inner>.item.active.left { + left: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0) + } + + .carousel-inner>.item.next.left, + .carousel-inner>.item.prev.right, + .carousel-inner>.item.active { + left: 0; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0) + } +} + +.carousel-inner>.active, +.carousel-inner>.next, +.carousel-inner>.prev { + display: block +} + +.carousel-inner>.active { + left: 0 +} + +.carousel-inner>.next, +.carousel-inner>.prev { + position: absolute; + top: 0; + width: 100% +} + +.carousel-inner>.next { + left: 100% +} + +.carousel-inner>.prev { + left: -100% +} + +.carousel-inner>.next.left, +.carousel-inner>.prev.right { + left: 0 +} + +.carousel-inner>.active.left { + left: -100% +} + +.carousel-inner>.active.right { + left: 100% +} + +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); + filter: alpha(opacity=50); + opacity: .5 +} + +.carousel-control.left { + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0, rgba(0, 0, 0, .0001) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0, rgba(0, 0, 0, .0001) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0, rgba(0, 0, 0, .0001) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); + background-repeat: repeat-x +} + +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0, rgba(0, 0, 0, .5) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0, rgba(0, 0, 0, .5) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0, rgba(0, 0, 0, .5) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); + background-repeat: repeat-x +} + +.carousel-control:hover, +.carousel-control:focus { + color: #fff; + text-decoration: none; + filter: alpha(opacity=90); + outline: 0; + opacity: .9 +} + +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block +} + +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px +} + +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px +} + +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + margin-top: -10px; + font-family: serif +} + +.carousel-control .icon-prev:before { + content: '\2039' +} + +.carousel-control .icon-next:before { + content: '\203a' +} + +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none +} + +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); + border: 1px solid #fff; + border-radius: 10px +} + +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + 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; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6) +} + +.carousel-caption .btn { + text-shadow: none +} + +@media screen and (min-width:768px) { + + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + font-size: 30px + } + + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -15px + } + + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -15px + } + + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px + } + + .carousel-indicators { + bottom: 20px + } +} + +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical>.btn-group:before, +.btn-group-vertical>.btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-footer:before, +.modal-footer:after { + display: table; + content: " " +} + +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical>.btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-footer:after { + clear: both +} + +.center-block { + display: block; + margin-right: auto; + margin-left: auto +} + +.pull-right { + float: right !important +} + +.pull-left { + float: left !important +} + +.hide { + display: none !important +} + +.show { + display: block !important +} + +.invisible { + visibility: hidden +} + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0 +} + +.hidden { + display: none !important; + visibility: hidden !important +} + +.affix { + position: fixed +} + +@-ms-viewport { + width: device-width +} + +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important +} + +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important +} + +@media (max-width:767px) { + .visible-xs { + display: block !important + } + + table.visible-xs { + display: table + } + + tr.visible-xs { + display: table-row !important + } + + th.visible-xs, + td.visible-xs { + display: table-cell !important + } +} + +@media (max-width:767px) { + .visible-xs-block { + display: block !important + } +} + +@media (max-width:767px) { + .visible-xs-inline { + display: inline !important + } +} + +@media (max-width:767px) { + .visible-xs-inline-block { + display: inline-block !important + } +} + +@media (min-width:768px) and (max-width:991px) { + .visible-sm { + display: block !important + } + + table.visible-sm { + display: table + } + + tr.visible-sm { + display: table-row !important + } + + th.visible-sm, + td.visible-sm { + display: table-cell !important + } +} + +@media (min-width:768px) and (max-width:991px) { + .visible-sm-block { + display: block !important + } +} + +@media (min-width:768px) and (max-width:991px) { + .visible-sm-inline { + display: inline !important + } +} + +@media (min-width:768px) and (max-width:991px) { + .visible-sm-inline-block { + display: inline-block !important + } +} + +@media (min-width:992px) and (max-width:1199px) { + .visible-md { + display: block !important + } + + table.visible-md { + display: table + } + + tr.visible-md { + display: table-row !important + } + + th.visible-md, + td.visible-md { + display: table-cell !important + } +} + +@media (min-width:992px) and (max-width:1199px) { + .visible-md-block { + display: block !important + } +} + +@media (min-width:992px) and (max-width:1199px) { + .visible-md-inline { + display: inline !important + } +} + +@media (min-width:992px) and (max-width:1199px) { + .visible-md-inline-block { + display: inline-block !important + } +} + +@media (min-width:1200px) { + .visible-lg { + display: block !important + } + + table.visible-lg { + display: table + } + + tr.visible-lg { + display: table-row !important + } + + th.visible-lg, + td.visible-lg { + display: table-cell !important + } +} + +@media (min-width:1200px) { + .visible-lg-block { + display: block !important + } +} + +@media (min-width:1200px) { + .visible-lg-inline { + display: inline !important + } +} + +@media (min-width:1200px) { + .visible-lg-inline-block { + display: inline-block !important + } +} + +@media (max-width:767px) { + .hidden-xs { + display: none !important + } +} + +@media (min-width:768px) and (max-width:991px) { + .hidden-sm { + display: none !important + } +} + +@media (min-width:992px) and (max-width:1199px) { + .hidden-md { + display: none !important + } +} + +@media (min-width:1200px) { + .hidden-lg { + display: none !important + } +} + +.visible-print { + display: none !important +} + +@media print { + .visible-print { + display: block !important + } + + table.visible-print { + display: table + } + + tr.visible-print { + display: table-row !important + } + + th.visible-print, + td.visible-print { + display: table-cell !important + } +} + +.visible-print-block { + display: none !important +} + +@media print { + .visible-print-block { + display: block !important + } +} + +.visible-print-inline { + display: none !important +} + +@media print { + .visible-print-inline { + display: inline !important + } +} + +.visible-print-inline-block { + display: none !important +} + +@media print { + .visible-print-inline-block { + display: inline-block !important + } +} + +@media print { + .hidden-print { + display: none !important + } +} \ No newline at end of file diff --git a/carvekit/web/static/css/fancybox_loading.gif b/carvekit/web/static/css/fancybox_loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..a03a40c097ee728709f65d4ea7397903a389d484 Binary files /dev/null and b/carvekit/web/static/css/fancybox_loading.gif differ diff --git a/carvekit/web/static/css/fancybox_overlay.png b/carvekit/web/static/css/fancybox_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..a4391396a9d6b6d7ff3b781f16904732fea40bdd Binary files /dev/null and b/carvekit/web/static/css/fancybox_overlay.png differ diff --git a/carvekit/web/static/css/fancybox_sprite.png b/carvekit/web/static/css/fancybox_sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..fd8d5ca566d47a77d9562168617bb2f6482bf9be Binary files /dev/null and b/carvekit/web/static/css/fancybox_sprite.png differ diff --git a/carvekit/web/static/css/font-awesome.min.css b/carvekit/web/static/css/font-awesome.min.css new file mode 100644 index 0000000000000000000000000000000000000000..ec53d4d6d5bf13db8ca757ea991a9c7839f23c89 --- /dev/null +++ b/carvekit/web/static/css/font-awesome.min.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.2.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"} \ No newline at end of file diff --git a/carvekit/web/static/css/jquery.fancybox.css b/carvekit/web/static/css/jquery.fancybox.css new file mode 100644 index 0000000000000000000000000000000000000000..367890a4af658d073d2b79c06829337d45434b84 --- /dev/null +++ b/carvekit/web/static/css/jquery.fancybox.css @@ -0,0 +1,274 @@ +/*! fancyBox v2.1.5 fancyapps.com | fancyapps.com/fancybox/#license */ +.fancybox-wrap, +.fancybox-skin, +.fancybox-outer, +.fancybox-inner, +.fancybox-image, +.fancybox-wrap iframe, +.fancybox-wrap object, +.fancybox-nav, +.fancybox-nav span, +.fancybox-tmp +{ + padding: 0; + margin: 0; + border: 0; + outline: none; + vertical-align: top; +} + +.fancybox-wrap { + position: absolute; + top: 0; + left: 0; + z-index: 8020; +} + +.fancybox-skin { + position: relative; + background: #f9f9f9; + color: #444; + text-shadow: none; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.fancybox-opened { + z-index: 8030; +} + +.fancybox-opened .fancybox-skin { + -webkit-box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); + -moz-box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); +} + +.fancybox-outer, .fancybox-inner { + position: relative; +} + +.fancybox-inner { + overflow: hidden; +} + +.fancybox-type-iframe .fancybox-inner { + -webkit-overflow-scrolling: touch; +} + +.fancybox-error { + color: #444; + font: 14px/20px "Helvetica Neue",Helvetica,Arial,sans-serif; + margin: 0; + padding: 15px; + white-space: nowrap; +} + +.fancybox-image, .fancybox-iframe { + display: block; + width: 100%; + height: 100%; +} + +.fancybox-image { + max-width: 100%; + max-height: 100%; +} + +#fancybox-loading, .fancybox-close, .fancybox-prev span, .fancybox-next span { + background-image: url('fancybox_sprite.png'); +} + +#fancybox-loading { + position: fixed; + top: 50%; + left: 50%; + margin-top: -22px; + margin-left: -22px; + background-position: 0 -108px; + opacity: 0.8; + cursor: pointer; + z-index: 8060; +} + +#fancybox-loading div { + width: 44px; + height: 44px; + background: url('fancybox_loading.gif') center center no-repeat; +} + +.fancybox-close { + position: absolute; + top: -18px; + right: -18px; + width: 36px; + height: 36px; + cursor: pointer; + z-index: 8040; +} + +.fancybox-nav { + position: absolute; + top: 0; + width: 40%; + height: 100%; + cursor: pointer; + text-decoration: none; + background: transparent url('blank.gif'); /* helps IE */ + -webkit-tap-highlight-color: rgba(0,0,0,0); + z-index: 8040; +} + +.fancybox-prev { + left: 0; +} + +.fancybox-next { + right: 0; +} + +.fancybox-nav span { + position: absolute; + top: 50%; + width: 36px; + height: 34px; + margin-top: -18px; + cursor: pointer; + z-index: 8040; + visibility: hidden; +} + +.fancybox-prev span { + left: 10px; + background-position: 0 -36px; +} + +.fancybox-next span { + right: 10px; + background-position: 0 -72px; +} + +.fancybox-nav:hover span { + visibility: visible; +} + +.fancybox-tmp { + position: absolute; + top: -99999px; + left: -99999px; + visibility: hidden; + max-width: 99999px; + max-height: 99999px; + overflow: visible !important; +} + +/* Overlay helper */ + +.fancybox-lock { + overflow: hidden !important; + width: auto; +} + +.fancybox-lock body { + overflow: hidden !important; +} + +.fancybox-lock-test { + overflow-y: hidden !important; +} + +.fancybox-overlay { + position: absolute; + top: 0; + left: 0; + overflow: hidden; + display: none; + z-index: 8010; + background: url('fancybox_overlay.png'); +} + +.fancybox-overlay-fixed { + position: fixed; + bottom: 0; + right: 0; +} + +.fancybox-lock .fancybox-overlay { + overflow: auto; + overflow-y: scroll; +} + +/* Title helper */ + +.fancybox-title { + visibility: hidden; + font: normal 13px/20px "Helvetica Neue",Helvetica,Arial,sans-serif; + position: relative; + text-shadow: none; + z-index: 8050; +} + +.fancybox-opened .fancybox-title { + visibility: visible; +} + +.fancybox-title-float-wrap { + position: absolute; + bottom: 0; + right: 50%; + margin-bottom: -35px; + z-index: 8050; + text-align: center; +} + +.fancybox-title-float-wrap .child { + display: inline-block; + margin-right: -100%; + padding: 2px 20px; + background: transparent; /* Fallback for web browsers that doesn't support RGBa */ + background: rgba(0, 0, 0, 0.8); + -webkit-border-radius: 15px; + -moz-border-radius: 15px; + border-radius: 15px; + text-shadow: 0 1px 2px #222; + color: #FFF; + font-weight: bold; + line-height: 24px; + white-space: nowrap; +} + +.fancybox-title-outside-wrap { + position: relative; + margin-top: 10px; + color: #fff; +} + +.fancybox-title-inside-wrap { + padding-top: 10px; +} + +.fancybox-title-over-wrap { + position: absolute; + bottom: 0; + left: 0; + color: #fff; + padding: 10px; + background: #000; + background: rgba(0, 0, 0, .8); +} + +/*Retina graphics!*/ +@media only screen and (-webkit-min-device-pixel-ratio: 1.5), + only screen and (min--moz-device-pixel-ratio: 1.5), + only screen and (min-device-pixel-ratio: 1.5){ + + #fancybox-loading, .fancybox-close, .fancybox-prev span, .fancybox-next span { + background-image: url('fancybox_sprite@2x.png'); + background-size: 44px 152px; /*The size of the normal image, half the size of the hi-res image*/ + } + + #fancybox-loading div { + background-image: url('fancybox_loading@2x.gif'); + background-size: 24px 24px; /*The size of the normal image, half the size of the hi-res image*/ + } +} \ No newline at end of file diff --git a/carvekit/web/static/css/main.css b/carvekit/web/static/css/main.css new file mode 100644 index 0000000000000000000000000000000000000000..5e6668f39e44368ad179d902e69f41840f5cc994 --- /dev/null +++ b/carvekit/web/static/css/main.css @@ -0,0 +1,687 @@ + +/* =================================== */ +/* Basic Style +/* =================================== */ + +body { + + /*background:-moz-element(.particles-js-canvas-el);*/ + background-color: #061b2a; + font-family: 'Open Sans', sans-serif; + line-height: 21px; + font-size: 13px; + color: #6a737b; +} + +ol, ul { + margin: 0; + padding: 0; + list-style: none; +} + +figure, p { + margin: 0; +} + +a { + color: #fff; + + -webkit-transition: all .3s ease-in 0s; + -moz-transition: all .3s ease-in 0s; + -ms-transition: all .3s ease-in 0s; + -o-transition: all .3s ease-in 0s; + transition: all .3s ease-in 0s; +} + +iframe { + border: 0; +} + +a, a:focus, a:hover { + text-decoration: none; + outline: 0; +} + +a:focus, a:hover { + color: #6CB670; +} + +h1, h2, h3, +h4, h5, h6 { + font-weight: normal; + margin: 0; +} + +.clear:before, +.clear:after { + content: " "; + display: table; +} + +.clear:after { + clear: both; +} + +.clear { + *zoom: 1; +} + +span.color { + color: #ff9209; +} + +body > section, +.footer { + padding: 70px 0; +} + +.sec-title {} + +.sec-title h2 { + color: #ff9209; + font-size: 28px; + font-weight: 800; + text-transform: uppercase; +} + +.sec-sub-title { + margin: 35px 0 45px; +} + +.sec-sub-title p { + font-weight: 600; + line-height: 24px; + font-size: 18px; + color: #5b646e; +} + +.devider { + margin-top: 30px; +} + +.devider i { + color: #cccccc; +} + +.devider:before, +.devider:after { + content: "______________________"; + color: #e6e8ea; + position: relative; + bottom: 6px; +} + +.devider:before { + right: 10px; +} + +.devider:after { + left: 10px; +} + +.mb50 { + margin-bottom: 50px; +} + +#preloader { + background-color: #fff; + height: 100%; + position: fixed; + width: 100%; + z-index: 1100; +} + +#preloader > img { + left: 47%; + position: absolute; + top: 48%; +} + + +/*========================================= + Header +==========================================*/ + +#navigation { + border: 0 none; + margin: 0; + + -webkit-transition: background-color 800ms linear; + -moz-transition: background-color 800ms linear; + -ms-transition: background-color 800ms linear; + -o-transition: background-color 800ms linear; + transition: background-color 800ms linear; +} + +.navbar-toggle i { + color: #fff; +} + +.navbar-brand { + padding: 0; +} + +.navbar-nav li a { + border-top: 1px solid transparent; +} + +.navbar-nav li a.current, +.navbar-nav li a:focus, +.navbar-nav li a:hover { + background-color: transparent; + border-top: 3px solid #a8620a; + color: #fff; +} + + +/*========================================= + Top section +==========================================*/ + +#top { + /*background-image: url("../img/art.gif");*/ + background-attachment: fixed !important; + background-position: center center; + background-repeat: no-repeat; + background-size: cover; + padding: 0; +} + +.top .devider:before, +.top .devider:after { + color: #737C85; +} + +.top .devider i { + color: #fff; +} + +.parallax-overlay { + background-color: rgba(6,32,51,.8); + padding: 70px 0; +} + + +.caption h2 { + font-size:52px; + font-weight: 300; + margin: 20px 0 20px; +} + +.caption h2 span { + font-weight: 800; +} + +.caption h3 { + font-size: 18px; + font-weight: 300; + margin: 20px 0 20px; +} + +.caption p { + color: #fff; + font-size: 21px; + font-weight: 300; +} + + .caption p:before, + .caption p:after { + color: #e4720d; + content: "___"; + position: relative; + top: -8px; + } + + .caption p:before { + right: 20px; + } + + .caption p:after { + left: 20px; + } + +} + + + + +/*========================================= + Upload form +==========================================*/ + + +#panel{ + cursor: pointer + + margin: 10px; + padding: 0; +} +.img-cont { + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} +.drag-image { + cursor: pointer; + position: relative; + overflow: hidden; + display: inline-block; + padding: 10px; + border: 1px dashed #000; + height: auto; + width: auto; + border-radius: 13px; + font-weight: 400; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column +} + +.drag-image.active { + border: 1px solid #000 +} + +.drag-image .icon { + font-size: 30px; + color: #e4720d +} + +.drag-image h6 { + font-size: 20px; + font-weight: 300; + color: #000 +} + +.drag-image span { + font-size: 14px; + font-weight: 300; + color: #000; + margin: 10px 0 15px 0 +} + +.drag-image button { + padding: 10px 25px; + font-size: 14px; + font-weight: 300; + border: none; + outline: none; + background: transparent; + color: #e4720d; + border-radius: 5px; + cursor: pointer; + transition: all 0.5s +} + +.drag-image button:hover { + background-color: #fff; + color: #0EB493; + + border: 1px solid #0EB493; +} + +.drag-image img { + height: 100%; + width: 100%; + object-fit: cover; + border-radius: 10px +} +.panel-container{ + display:grid; + padding:15px; + justify-content:center; +} + +.form-group{ + display:flex; + flex-direction:column; +} +.form-control{ + padding:.5rem; +} +.form-control{ + border-radius: 4px; + border: 1px solid #8E919D; +} +.form-control:focus{ + outline:none; + border-color:#57D8C8; + box-shadow:0 0 2px 1px rgba(87, 216, 200,.6); +} + + +.drag-image input[type=file] { + cursor: pointer; + font-size: 100px; + position: absolute; + left: 0; + top: 0; + opacity: 0; +} + +/*========================================= + Our Works +==========================================*/ + +.work-filter { + margin-bottom: 35px; +} + +.work-filter ul li { + display: inline-block; +} + +.work-filter ul li a { + color: #062033; + display: block; + font-size: 14px; + font-weight: 700; + padding: 5px 17px; + border-radius: 6px; + text-transform: capitalize; +} + +.work-filter ul li a:hover, +.work-filter ul li a.active { + background-color: #0eb493; + border-radius: 6px; + color: #fff; + padding: 5px 17px; +} + +.mix { + display: none; +} + +.work-item { + float: left; + width: 25%; + position: relative; +} + +.work-item > img { + display: block; + height: auto; + width: 100%; +} + +.overlay { + background-color: rgba(0,0,0,.9); + text-align: center; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + color: #fff; + + opacity: 0; + filter: alpha(opacity=0); + + -webkit-transition: all 450ms ease-out 0s; + -moz-transition: all 450ms ease-out 0s; + -o-transition: all 450ms ease-out 0s; + transition: all 450ms ease-out 0s; + + -webkit-transform: rotateY(180deg) scale(0.5,0.5); + -moz-transform: rotateY(180deg) scale(0.5,0.5); + -ms-transform: rotateY(180deg) scale(0.5,0.5); + -o-transform: rotateY(180deg) scale(0.5,0.5); + transform: rotateY(180deg) scale(0.5,0.5); +} + +.work-item:hover .overlay { + opacity: 1; + filter: alpha(opacity=100); + + -webkit-transform: rotateY(0deg) scale(1,1); + -moz-transform: rotateY(0deg) scale(1,1); + -ms-transform: rotateY(0deg) scale(1,1); + -o-transform: rotateY(0deg) scale(1,1); + transform: rotateY(0deg) scale(1,1); +} + +.work-item .overlay a { + border: 1px solid #fff; + border-radius: 50%; + display: inline-block; + margin-top: 20%; + padding: 7px 10px; +} + +.work-item .overlay a:hover { + color: #fff; +} + +.work-item .overlay h4 { + font-size: 18px; + font-weight: 700; + line-height: 24px; + margin: 25px 0 8px; +} + +.work-item .overlay p { + font-size: 14px; + line-height: 24px; +} + +/*========================================= + Some fun facts +==========================================*/ + +#facts { + background-image: url("../img/art.gif"); + background-attachment: fixed !important; + background-position: center center; + background-repeat: no-repeat; + background-size: cover; + padding: 0; +} + +.facts .devider:before, +.facts .devider:after { + color: #737C85; +} + +.facts .devider i { + color: #fff; +} + +.parallax-overlay { + background-color: rgba(6,32,51,.8); + padding: 70px 0; +} + +.counters-item { + color: #fff; +} + +.counters-item i { + border: 1px solid #737C85; + border-radius: 50%; + color: #fff; + display: inline-block; + height: 120px; + margin: 0 0 35px; + padding: 40px 0 0; + width: 120px; +} + +.counters-item strong { + display: block; + font-size: 60px; + font-weight: 600; + line-height: 60px; +} + +.counters-item p { + font-size: 18px; + line-height: 24px; + margin-top: 15px; + text-transform: uppercase; +} + + +/*========================================= + Contact Us +==========================================*/ + +.contact { + padding-bottom: 0; +} + +.contact-address h3 { + color: #062033; + font-size: 22px; + line-height: 32px; + margin-bottom: 25px; +} + +.contact-address p { + line-height: 24px; +} + +.contact-form {} + +.contact-form h3 { + color: #062033; + font-size: 24px; + font-weight: 700; + line-height: 32px; + margin-bottom: 25px; +} + +.contact-form .input-group { + width: 100%; +} + +.contact-form .form-control { + border-color: -moz-use-text-color -moz-use-text-color #cccccc; + border-radius: 0; + border-style: none none solid; + border-width: 0 0 1px; + box-shadow: none; + margin-bottom: 10px; +} + +.contact-form .input-field { + width: 48%; + float: left; + margin-right: 4%; +} + +.contact-form .input-field:last-child { + margin-right: 0; +} + +.contact-form .form-control.error { +border-bottom-color: #c0392b; +} + +label.error { + color: #c0392b; + font-weight: normal; + text-transform: capitalize; +} + +.contact-form #form-submit { + background: url("../img/envelop.png") no-repeat scroll 0 15px transparent; + border: 0 none; + color: #000; + font-size: 16px; + line-height: 24px; + padding: 10px; + text-align: right; + width: 150px; +} + +.footer-social { + margin-top: 17px; +} + +.footer-social li a { + color: #cdd2d6; + display: block; + margin-bottom: 10px; +} + +#map_canvas { + height: 215px; + width: 100%; +} + + +/*========================================= + Footer +==========================================*/ + + +.footer { + background-color: #061b2a; + /*border-top: 5px solid #062033;*/ + color: #fff; +} + +.footer a:hover { + color: #062033; +} + +.footer h6 { + font-size: 14px; + font-weight: 700; + line-height: 24px; + margin-bottom: 30px; +} + + +.footer-single { + line-height: 24px; +} + +.footer-single ul { + } + +.footer-single li { + line-height: 32px; +} + +.footer-single p i { + margin: 0 10px; +} + +.copyright { + color: #fff; + margin-top: 20px; +} + +#back-top { + bottom: 20px; + position: fixed; + right: 25px; + z-index: 9; +} + + +.clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0;} +.clearfix:after{clear:both;} +.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;} +.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} +.btn-file{overflow:hidden;position:relative;vertical-align:middle;}.btn-file>input{position:absolute;top:0;right:0;margin:0;opacity:0;filter:alpha(opacity=0);transform:translate(-300px, 0) scale(4);font-size:23px;direction:ltr;cursor:pointer;} +.fileupload{margin-bottom:9px;}.fileupload .uneditable-input{display:inline-block;margin-bottom:0px;vertical-align:middle;cursor:text;} +.fileupload .thumbnail{overflow:hidden;display:inline-block;margin-bottom:5px;vertical-align:middle;text-align:center;}.fileupload .thumbnail>img{display:inline-block;vertical-align:middle;max-height:100%;} +.fileupload .btn{vertical-align:middle;} +.fileupload-exists .fileupload-new,.fileupload-new .fileupload-exists{display:none;} +.fileupload-inline .fileupload-controls{display:inline;} +.fileupload-new .input-append .btn-file{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.thumbnail-borderless .thumbnail{border:none;padding:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.fileupload-new.thumbnail-borderless .thumbnail{border:1px solid #ddd;} +.control-group.warning .fileupload .uneditable-input{color:#a47e3c;border-color:#a47e3c;} +.control-group.warning .fileupload .fileupload-preview{color:#a47e3c;} +.control-group.warning .fileupload .thumbnail{border-color:#a47e3c;} +.control-group.error .fileupload .uneditable-input{color:#b94a48;border-color:#b94a48;} +.control-group.error .fileupload .fileupload-preview{color:#b94a48;} +.control-group.error .fileupload .thumbnail{border-color:#b94a48;} +.control-group.success .fileupload .uneditable-input{color:#468847;border-color:#468847;} +.control-group.success .fileupload .fileupload-preview{color:#468847;} +.control-group.success .fileupload .thumbnail{border-color:#468847;} \ No newline at end of file diff --git a/carvekit/web/static/css/media-queries.css b/carvekit/web/static/css/media-queries.css new file mode 100644 index 0000000000000000000000000000000000000000..e4a22808ff5578677cc71d9f23dd91a7b0be4332 --- /dev/null +++ b/carvekit/web/static/css/media-queries.css @@ -0,0 +1,256 @@ +/*============================================================ + For Small Desktop +==============================================================*/ + +@media (min-width: 980px) and (max-width: 1150px) { + +/* slider */ +.carousel-caption h3 { + font-size: 45px; +} + +/* works */ + + +/* team */ + +.member-thumb { + width: auto; +} + +} + + +/*============================================================ + Tablet (Portrait) Design for a width of 768px +==============================================================*/ + +@media (min-width: 768px) and (max-width: 979px) { + + +/* slider */ + +.carousel-caption h2 { + font-size: 55px; +} + +.carousel-caption h3 { + font-size: 36px; +} + +/* services */ + +.service-item { + margin: 0 auto 30px; + text-align: center; + width: 325px; +} + +.service-icon { + float: none; + margin: 0 auto 15px; + text-align: center; + width: 50px; +} + +.service-desc { + margin-left: 0; + position: relative; + top: 0; +} + +/* works */ + +.work-item { + width: 33%; +} + +/* team */ + +.member-thumb .overlay h5 { + margin: 25px 0; +} + +.member-thumb { + margin: 0 auto; +} + +/* fatcs */ + +#facts { +background-position: center top !important; +} +.counters-item { + margin-bottom: 30px; +} + +.counters-item i { + margin: 0 0 15px; +} + +.counters-item strong { + font-size: 45px; +} + +/* contact */ + +.contact-form .name-email input { + margin-right: 0; + width: 100%; +} + +.footer-social { + margin-top: 45px; +} + +/* footer */ + +.footer-single { + margin-bottom: 30px; +} + +} + + +/*============================================================ + Mobile (Portrait) Design for a width of 320px +==============================================================*/ + +@media only screen and (max-width: 767px) { + +.sec-sub-title p { + font-size: 14px; +} + +/* slider */ +.carousel-caption h2 { + font-size: 35px; +} + +.carousel-caption h3 { + font-size: 22px; +} + +.carousel-caption p { + font-size: 14px; +} + +.social-links { + margin-top: 20%; +} + +/* services */ + +.service-item { + margin: 0 auto 30px; + text-align: center; + width: 280px; +} + +.service-icon { + float: none; + margin: 0 auto 15px; + text-align: center; + width: 50px; +} + +.service-desc { + margin-left: 0; + position: relative; + top: 0; +} + +/* works */ + +.work-item { + left: 5% !important; + width: 90%; +} + +/* team */ + +.team-member { + margin-bottom: 30px; +} + +.team-member:last-child { + margin-bottom: 0; +} + +.member-thumb { + margin: 0 auto; +} + +/* facts */ + +#facts { +background-position: center top !important; +} + +.counters-item { + margin-bottom: 30px; +} + +/* contact */ +.contact-address { + margin-bottom: 30px; +} + +.footer-social { + margin-top: 20px; + text-align: center; +} + +.footer-social li { + display: inline-block; +} + +.footer-social li a { + margin: 0 10px; +} + +/* footer */ + +.footer-single { + margin-bottom: 30px; +} + +} + + +/*============================================================ + Mobile (Landscape) Design for a width of 480px +==============================================================*/ + +@media only screen and (min-width: 480px) and (max-width: 767px) { + + +/* services */ + +.service-item { + margin: 0 auto 30px; + text-align: center; + width: 325px; +} + +.service-icon { + float: none; + margin: 0 auto 15px; + text-align: center; + width: 50px; +} + +.service-desc { + margin-left: 0; + position: relative; + top: 0; +} + +/* works */ + +.work-item { + left: inherit !important; + width: 50%; +} + +} \ No newline at end of file diff --git a/carvekit/web/static/css/normalize.min.css b/carvekit/web/static/css/normalize.min.css new file mode 100644 index 0000000000000000000000000000000000000000..f33b6e985837cb006a1ab15692c3c225ef894981 --- /dev/null +++ b/carvekit/web/static/css/normalize.min.css @@ -0,0 +1 @@ +/*! normalize.css v1.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0} \ No newline at end of file diff --git a/carvekit/web/static/css/particles.css b/carvekit/web/static/css/particles.css new file mode 100755 index 0000000000000000000000000000000000000000..9cf5ced010650c299b0b8128f99c7ec2e878222f --- /dev/null +++ b/carvekit/web/static/css/particles.css @@ -0,0 +1,55 @@ +/* ---- reset ---- */ + + +canvas { + + display: block; + vertical-align: bottom; +} + +/* ---- particles.js container ---- */ + +#particles-js { + + position: absolute; + width: 100%; + height: 100%; +} + +/* ---- stats.js ---- */ + +.count-particles{ + + background: #000022; + position: absolute; + top: 48px; + left: 0; + width: 80px; + color: #13E8E9; + font-size: .8em; + text-align: left; + text-indent: 4px; + line-height: 14px; + padding-bottom: 2px; + font-family: Helvetica, Arial, sans-serif; + font-weight: bold; +} + +.js-count-particles{ + + font-size: 1.1em; +} + +#stats, +.count-particles{ + -webkit-user-select: none; +} + +#stats{ + border-radius: 3px 3px 0 0; + overflow: hidden; +} + +.count-particles{ + border-radius: 0 0 3px 3px; +} \ No newline at end of file diff --git a/carvekit/web/static/fonts/FontAwesome.otf b/carvekit/web/static/fonts/FontAwesome.otf new file mode 100644 index 0000000000000000000000000000000000000000..81c9ad949b47f64afeca5642ee2494b6e3147f44 Binary files /dev/null and b/carvekit/web/static/fonts/FontAwesome.otf differ diff --git a/carvekit/web/static/fonts/fontawesome-webfont.eot b/carvekit/web/static/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..84677bc0c5f37f1fac9d87548c4554b5c91717cf Binary files /dev/null and b/carvekit/web/static/fonts/fontawesome-webfont.eot differ diff --git a/carvekit/web/static/fonts/fontawesome-webfont.svg b/carvekit/web/static/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000000000000000000000000000000000000..d907b25ae60ec7e3d32e4027aa6e6b7595de97af --- /dev/null +++ b/carvekit/web/static/fonts/fontawesome-webfont.svg @@ -0,0 +1,520 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/carvekit/web/static/fonts/fontawesome-webfont.ttf b/carvekit/web/static/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..96a3639cdde5e8ab459c6380e3b9524ee81641dc Binary files /dev/null and b/carvekit/web/static/fonts/fontawesome-webfont.ttf differ diff --git a/carvekit/web/static/fonts/fontawesome-webfont.woff b/carvekit/web/static/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..628b6a52a87e62c6f22426e17c01f6a303aa194e Binary files /dev/null and b/carvekit/web/static/fonts/fontawesome-webfont.woff differ diff --git a/carvekit/web/static/img/CarveKit_logo_main.png b/carvekit/web/static/img/CarveKit_logo_main.png new file mode 100644 index 0000000000000000000000000000000000000000..639e1528eed4e34c09a1e69e4b17e24c31780df6 Binary files /dev/null and b/carvekit/web/static/img/CarveKit_logo_main.png differ diff --git a/carvekit/web/static/img/art.gif b/carvekit/web/static/img/art.gif new file mode 100644 index 0000000000000000000000000000000000000000..2489611d7be7675d16be013babcb9e270c77c34f Binary files /dev/null and b/carvekit/web/static/img/art.gif differ diff --git a/carvekit/web/static/img/envelop.png b/carvekit/web/static/img/envelop.png new file mode 100644 index 0000000000000000000000000000000000000000..f9f2918bab65b2313465ed83d0275364c42b438d Binary files /dev/null and b/carvekit/web/static/img/envelop.png differ diff --git a/carvekit/web/static/img/icon.png b/carvekit/web/static/img/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2ad4e9710bad21492a3bbebaf277a19e6c0914f7 Binary files /dev/null and b/carvekit/web/static/img/icon.png differ diff --git a/carvekit/web/static/img/preloader.gif b/carvekit/web/static/img/preloader.gif new file mode 100644 index 0000000000000000000000000000000000000000..0eed4f0ff5a334df26b8fb02069db5daba5149f5 Binary files /dev/null and b/carvekit/web/static/img/preloader.gif differ diff --git a/carvekit/web/static/index.html b/carvekit/web/static/index.html new file mode 100644 index 0000000000000000000000000000000000000000..748a6db0032091952aee7b280cc3201a783c25ab --- /dev/null +++ b/carvekit/web/static/index.html @@ -0,0 +1,281 @@ + + + + + + + + + + + + + + + CarveKit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + Preloader +
+ + + + + + + + + +
+ +
+ +
+ +
+
+
+

+ Introducing CarveKit!

+

Background Removal Tool

+
+
+
+
+
+ + +
+ + + + + + + +
+ +
+
+
+ +
+

Try yourself

+
+
+ + +
+
+ +
+ +
+
+ Choose file + Choose other + + × +
+ + + + +
+
+ + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/carvekit/web/static/js/bootstrap.min.js b/carvekit/web/static/js/bootstrap.min.js new file mode 100644 index 0000000000000000000000000000000000000000..d839865900c1ab1245618347dcb0a0d4bb01b893 --- /dev/null +++ b/carvekit/web/static/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.1",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.1",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.1",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c="prev"==a?-1:1,d=this.getItemIndex(b),e=(d+c)%this.$items.length;return this.$items.eq(e)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i="next"==b?"first":"last",j=this;if(!f.length){if(!this.options.wrap)return;f=this.$element.find(".item")[i]()}if(f.hasClass("active"))return this.sliding=!1;var k=f[0],l=a.Event("slide.bs.carousel",{relatedTarget:k,direction:h});if(this.$element.trigger(l),!l.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var m=a(this.$indicators.children()[this.getItemIndex(f)]);m&&m.addClass("active")}var n=a.Event("slid.bs.carousel",{relatedTarget:k,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),j.sliding=!1,setTimeout(function(){j.$element.trigger(n)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(n)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a(this.options.trigger).filter('[href="#'+b.id+'"], [data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.1",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0,trigger:'[data-toggle="collapse"]'},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.find("> .panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":a.extend({},e.data(),{trigger:this});c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.1",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('