diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/index.html b/index.html new file mode 100644 index 0000000..2a70efc --- /dev/null +++ b/index.html @@ -0,0 +1,2584 @@ + + + + + + + + + +Kornia - Tutorials + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+
+
+

Tutorials

+
+
+ + +
+ + + + +
+ + +
+ + + + + \ No newline at end of file diff --git a/listings.json b/listings.json new file mode 100644 index 0000000..c17f939 --- /dev/null +++ b/listings.json @@ -0,0 +1,49 @@ +[ + { + "listing": "/index.html", + "items": [ + "/nbs/image_matching_lightglue.html", + "/nbs/image_prompter.html", + "/nbs/image_matching_disk.html", + "/nbs/data_augmentation_2d.html", + "/nbs/fit_plane.html", + "/nbs/image_points_transforms.html", + "/nbs/line_detection_and_matching_sold2.html", + "/nbs/data_augmentation_mosiac.html", + "/nbs/image_matching_adalam.html", + "/nbs/fit_line.html", + "/nbs/extract_combine_patches.html", + "/nbs/face_detection.html", + "/nbs/image_stitching.html", + "/nbs/color_raw_to_rgb.html", + "/nbs/color_yuv420_to_rgb.html", + "/nbs/connected_components.html", + "/nbs/image_matching.html", + "/nbs/image_registration.html", + "/nbs/image_histogram.html", + "/nbs/resize_antialias.html", + "/nbs/geometry_generate_patch.html", + "/nbs/descriptors_matching.html", + "/nbs/aliased_and_not_aliased_patch_extraction.html", + "/nbs/homography.html", + "/nbs/geometric_transforms.html", + "/nbs/rotate_affine.html", + "/nbs/filtering_operators.html", + "/nbs/filtering_edges.html", + "/nbs/image_enhancement.html", + "/nbs/data_patch_sequential.html", + "/nbs/canny.html", + "/nbs/total_variation_denoising.html", + "/nbs/zca_whitening.html", + "/nbs/unsharp_mask.html", + "/nbs/data_augmentation_sequential.html", + "/nbs/gaussian_blur.html", + "/nbs/data_augmentation_segmentation.html", + "/nbs/warp_perspective.html", + "/nbs/data_augmentation_kornia_lightning.html", + "/nbs/color_conversions.html", + "/nbs/morphology_101.html", + "/nbs/hello_world_tutorial.html" + ] + } +] \ No newline at end of file diff --git a/nbs/aliased_and_not_aliased_patch_extraction.html b/nbs/aliased_and_not_aliased_patch_extraction.html new file mode 100644 index 0000000..87bd699 --- /dev/null +++ b/nbs/aliased_and_not_aliased_patch_extraction.html @@ -0,0 +1,789 @@ + + + + + + + + + + + + +Kornia - Image anti-alias with local features + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Image anti-alias with local features

+
+
Basic
+
HardNet
+
Patches
+
Local features
+
kornia.feature
+
+
+ +
+
+ In this example we will show the benefits of using anti-aliased patch extraction with kornia. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

August 28, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://github.com/kornia/data/raw/main/drslump.jpg"
+download_image(url)
+
+
'drslump.jpg'
+
+
+

First, lets load some image.

+
+
%matplotlib inline
+import kornia as K
+import kornia.feature as KF
+import matplotlib.pyplot as plt
+import torch
+
+
+
device = torch.device("cpu")
+
+img_original = K.io.load_image("drslump.jpg", K.io.ImageLoadType.RGB32, device=device)[None, ...]
+
+plt.figure()
+plt.imshow(K.tensor_to_image(img_original))
+
+
<matplotlib.image.AxesImage>
+
+
+

+
+
+
+
B, CH, H, W = img_original.shape
+
+DOWNSAMPLE = 4
+img_small = K.geometry.resize(img_original, (H // DOWNSAMPLE, W // DOWNSAMPLE), interpolation="area")
+plt.figure()
+plt.imshow(K.tensor_to_image(img_small))
+
+
<matplotlib.image.AxesImage>
+
+
+

+
+
+

Now, lets define a keypoint with a large support region.

+
+
def show_lafs(img, lafs, idx=0, color="r", figsize=(10, 7)):
+    x, y = KF.laf.get_laf_pts_to_draw(lafs, idx)
+    plt.figure(figsize=figsize)
+    if isinstance(img, torch.Tensor):
+        img_show = K.tensor_to_image(img)
+    else:
+        img_show = img
+    plt.imshow(img_show)
+    plt.plot(x, y, color)
+    return
+
+
+laf_orig = torch.tensor([[150.0, 0, 180], [0, 150, 280]]).float().view(1, 1, 2, 3)
+laf_small = laf_orig / float(DOWNSAMPLE)
+
+show_lafs(img_original, laf_orig, figsize=(6, 4))
+show_lafs(img_small, laf_small, figsize=(6, 4))
+
+

+
+
+

+
+
+

Now lets compare how extracted patch would look like when extracted in a naive way and from scale pyramid.

+
+
PS = 32
+with torch.no_grad():
+    patches_pyr_orig = KF.extract_patches_from_pyramid(img_original, laf_orig.to(device), PS)
+    patches_simple_orig = KF.extract_patches_simple(img_original, laf_orig.to(device), PS)
+
+    patches_pyr_small = KF.extract_patches_from_pyramid(img_small, laf_small.to(device), PS)
+    patches_simple_small = KF.extract_patches_simple(img_small, laf_small.to(device), PS)
+
+# Now we will glue all the patches together:
+
+
+def vert_cat_with_margin(p1, p2, margin=3):
+    b, n, ch, h, w = p1.size()
+    return torch.cat([p1, torch.ones(b, n, ch, h, margin).to(device), p2], dim=4)
+
+
+def horiz_cat_with_margin(p1, p2, margin=3):
+    b, n, ch, h, w = p1.size()
+    return torch.cat([p1, torch.ones(b, n, ch, margin, w).to(device), p2], dim=3)
+
+
+patches_pyr = vert_cat_with_margin(patches_pyr_orig, patches_pyr_small)
+patches_naive = vert_cat_with_margin(patches_simple_orig, patches_simple_small)
+
+patches_all = horiz_cat_with_margin(patches_naive, patches_pyr)
+
+

Now lets show the result. Top row is what you get if you are extracting patches without any antialiasing - note how the patches extracted from the images of different sizes differ.

+

Bottom row is patches, which are extracted from images of different sizes using a scale pyramid. They are not yet exactly the same, but the difference is much smaller.

+
+
plt.figure(figsize=(10, 10))
+plt.imshow(K.tensor_to_image(patches_all[0, 0]))
+
+
<matplotlib.image.AxesImage>
+
+
+

+
+
+

Lets check how much it influences local descriptor performance such as HardNet

+
+
hardnet = KF.HardNet(True).eval()
+all_patches = (
+    torch.cat([patches_pyr_orig, patches_pyr_small, patches_simple_orig, patches_simple_small], dim=0)
+    .squeeze(1)
+    .mean(dim=1, keepdim=True)
+)
+with torch.no_grad():
+    descs = hardnet(all_patches)
+    distances = torch.cdist(descs, descs)
+    print(distances.cpu().detach().numpy())
+
+
[[0.         0.16867691 0.8070452  0.52112377]
+ [0.16867691 0.         0.7973113  0.48472866]
+ [0.8070452  0.7973113  0.         0.59267515]
+ [0.52112377 0.48472866 0.59267515 0.        ]]
+
+
+

So the descriptor difference between antialiased patches is 0.09 and between naively extracted – 0.44

+ + + +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/aliased_and_not_aliased_patch_extraction_files/figure-html/cell-5-output-2.png b/nbs/aliased_and_not_aliased_patch_extraction_files/figure-html/cell-5-output-2.png new file mode 100644 index 0000000..b720a1c Binary files /dev/null and b/nbs/aliased_and_not_aliased_patch_extraction_files/figure-html/cell-5-output-2.png differ diff --git a/nbs/aliased_and_not_aliased_patch_extraction_files/figure-html/cell-6-output-2.png b/nbs/aliased_and_not_aliased_patch_extraction_files/figure-html/cell-6-output-2.png new file mode 100644 index 0000000..45b6bc8 Binary files /dev/null and b/nbs/aliased_and_not_aliased_patch_extraction_files/figure-html/cell-6-output-2.png differ diff --git a/nbs/aliased_and_not_aliased_patch_extraction_files/figure-html/cell-7-output-1.png b/nbs/aliased_and_not_aliased_patch_extraction_files/figure-html/cell-7-output-1.png new file mode 100644 index 0000000..ad6fbff Binary files /dev/null and b/nbs/aliased_and_not_aliased_patch_extraction_files/figure-html/cell-7-output-1.png differ diff --git a/nbs/aliased_and_not_aliased_patch_extraction_files/figure-html/cell-7-output-2.png b/nbs/aliased_and_not_aliased_patch_extraction_files/figure-html/cell-7-output-2.png new file mode 100644 index 0000000..3d065f3 Binary files /dev/null and b/nbs/aliased_and_not_aliased_patch_extraction_files/figure-html/cell-7-output-2.png differ diff --git a/nbs/aliased_and_not_aliased_patch_extraction_files/figure-html/cell-9-output-2.png b/nbs/aliased_and_not_aliased_patch_extraction_files/figure-html/cell-9-output-2.png new file mode 100644 index 0000000..34cd0d7 Binary files /dev/null and b/nbs/aliased_and_not_aliased_patch_extraction_files/figure-html/cell-9-output-2.png differ diff --git a/nbs/canny.html b/nbs/canny.html new file mode 100644 index 0000000..9e68dc3 --- /dev/null +++ b/nbs/canny.html @@ -0,0 +1,776 @@ + + + + + + + + + + + + +Kornia - Obtaining Edges using the Canny operator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Obtaining Edges using the Canny operator

+
+
Basic
+
Edge Detection
+
kornia.filters
+
+
+ +
+
+ In this tutorial we show how easily one can apply the typical canny edge detection using Kornia +
+
+ + +
+ +
+
Author
+
+

Pau Riba

+
+
+ +
+
Published
+
+

June 8, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+

Preparation

+

We first install Kornia.

+
+
%%capture
+%matplotlib inline
+!pip install kornia
+!pip install kornia-rs
+
+
+
import kornia
+
+kornia.__version__
+
+
'0.6.12-dev'
+
+
+

Now we download the example image.

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://ih1.redbubble.net/image.675644909.6235/flat,800x800,075,f.u3.jpg"
+download_image(url, "paranoia_agent.jpg")
+
+
'paranoia_agent.jpg'
+
+
+
+
+

Example

+

We first import the required libraries and load the data.

+
+
import kornia
+import matplotlib.pyplot as plt
+import torch
+
+# read the image with Kornia
+img_tensor = kornia.io.load_image("paranoia_agent.jpg", kornia.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+img_array = kornia.tensor_to_image(img_tensor)
+
+plt.axis("off")
+plt.imshow(img_array)
+plt.show()
+
+

+
+
+

To apply a filter, we create the Canny operator object and apply it to the data. It will provide the magnitudes as well as the edges after the hysteresis process. Note that the edges are a binary image which is not differentiable!

+
+
# create the operator
+canny = kornia.filters.Canny()
+
+# blur the image
+x_magnitude, x_canny = canny(img_tensor)
+
+

That’s it! We can compare the source image and the results from the magnitude as well as the edges:

+
+
# convert back to numpy
+img_magnitude = kornia.tensor_to_image(x_magnitude.byte())
+img_canny = kornia.tensor_to_image(x_canny.byte())
+
+# Create the plot
+fig, axs = plt.subplots(1, 3, figsize=(16, 16))
+axs = axs.ravel()
+
+axs[0].axis("off")
+axs[0].set_title("image source")
+axs[0].imshow(img_array)
+
+axs[1].axis("off")
+axs[1].set_title("canny magnitude")
+axs[1].imshow(img_magnitude, cmap="Greys")
+
+axs[2].axis("off")
+axs[2].set_title("canny edges")
+axs[2].imshow(img_canny, cmap="Greys")
+
+plt.show()
+
+

+
+
+

Note that our final result still recovers some edges whose magnitude is quite low. Let us increase the thresholds and compare the final edges.

+
+
# create the operator
+canny = kornia.filters.Canny(low_threshold=0.4, high_threshold=0.5)
+
+# blur the image
+_, x_canny_threshold = canny(img_tensor)
+
+
+
import torch.nn.functional as F
+
+# convert back to numpy
+img_canny_threshold = kornia.tensor_to_image(x_canny_threshold.byte())
+
+# Create the plot
+fig, axs = plt.subplots(1, 3, figsize=(16, 16))
+axs = axs.ravel()
+
+axs[0].axis("off")
+axs[0].set_title("image source")
+axs[0].imshow(img_array)
+
+axs[1].axis("off")
+axs[1].set_title("canny default")
+axs[1].imshow(img_canny, cmap="Greys")
+
+axs[2].axis("off")
+axs[2].set_title("canny defined thresholds")
+axs[2].imshow(img_canny_threshold, cmap="Greys")
+
+plt.show()
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/canny_files/figure-html/cell-5-output-1.png b/nbs/canny_files/figure-html/cell-5-output-1.png new file mode 100644 index 0000000..853ffdb Binary files /dev/null and b/nbs/canny_files/figure-html/cell-5-output-1.png differ diff --git a/nbs/canny_files/figure-html/cell-7-output-1.png b/nbs/canny_files/figure-html/cell-7-output-1.png new file mode 100644 index 0000000..db569ac Binary files /dev/null and b/nbs/canny_files/figure-html/cell-7-output-1.png differ diff --git a/nbs/canny_files/figure-html/cell-9-output-1.png b/nbs/canny_files/figure-html/cell-9-output-1.png new file mode 100644 index 0000000..0c3c33a Binary files /dev/null and b/nbs/canny_files/figure-html/cell-9-output-1.png differ diff --git a/nbs/color_conversions.html b/nbs/color_conversions.html new file mode 100644 index 0000000..9c1f172 --- /dev/null +++ b/nbs/color_conversions.html @@ -0,0 +1,762 @@ + + + + + + + + + + + + +Kornia - Color space conversion + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Color space conversion

+
+
Basic
+
Color spaces
+
kornia.color
+
+
+ +
+
+ In this tutorial we are going to learn how to convert image from different color spaces using kornia.color. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

March 18, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+

Explanation

+

Images are asumed to be loaded either in RGB or Grayscale space.

+
    +
  1. We will use OpenCV to load images.
  2. +
  3. Convert from BGR to RGB (note that OpenCV loads images in BGR format).
  4. +
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://github.com/kornia/data/raw/main/simba.png"
+download_image(url)
+
+
'simba.png'
+
+
+
+
import kornia as K
+import numpy as np
+import torch
+import torchvision
+from matplotlib import pyplot as plt
+
+
+
# read the image with Kornia
+img_tensor = K.io.load_image("simba.png", K.io.ImageLoadType.RGB32)  # CxHxW
+img_array = K.tensor_to_image(img_tensor)
+
+plt.axis("off")
+plt.imshow(img_array)
+plt.show()
+
+

+
+
+

Alternatively we can use use kornia.color to perform the color transformation.

+
    +
  1. Convert the tensor to RGB
  2. +
  3. Convert back the tensor to numpy for visualisation.
  4. +
+
+
x_rgb: torch.Tensor = img_tensor
+
+# to BGR
+x_bgr: torch.Tensor = K.color.rgb_to_bgr(x_rgb)
+
+# convert back to numpy and visualize
+img_np: np.array = K.tensor_to_image(x_bgr)
+plt.imshow(img_np)
+plt.axis("off");
+
+

+
+
+

Using kornia we easily perform color transformation in batch mode.

+
+
def imshow(input: torch.Tensor):
+    out: torch.Tensor = torchvision.utils.make_grid(input, nrow=2, padding=5)
+    out_np: np.array = K.tensor_to_image(out)
+    plt.imshow(out_np)
+    plt.axis("off")
+    plt.show()
+
+
+# create a batch of images
+xb_bgr = torch.stack([x_bgr, K.geometry.hflip(x_bgr), K.geometry.vflip(x_bgr), K.geometry.rot180(x_bgr)])
+imshow(xb_bgr)
+
+

+
+
+
+
# convert to back to RGB
+xb_rgb = K.color.bgr_to_rgb(xb_bgr)
+imshow(xb_rgb)
+
+

+
+
+
+
# convert to grayscale
+# NOTE: image comes in torch.uint8, and kornia assumes floating point type
+xb_gray = K.color.rgb_to_grayscale(xb_rgb)
+imshow(xb_gray)
+
+

+
+
+
+
# convert to HSV
+xb_hsv = K.color.rgb_to_hsv(xb_rgb)
+imshow(xb_hsv)
+
+
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
+
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/color_conversions_files/figure-html/cell-10-output-2.png b/nbs/color_conversions_files/figure-html/cell-10-output-2.png new file mode 100644 index 0000000..2f58754 Binary files /dev/null and b/nbs/color_conversions_files/figure-html/cell-10-output-2.png differ diff --git a/nbs/color_conversions_files/figure-html/cell-5-output-1.png b/nbs/color_conversions_files/figure-html/cell-5-output-1.png new file mode 100644 index 0000000..35750ca Binary files /dev/null and b/nbs/color_conversions_files/figure-html/cell-5-output-1.png differ diff --git a/nbs/color_conversions_files/figure-html/cell-6-output-1.png b/nbs/color_conversions_files/figure-html/cell-6-output-1.png new file mode 100644 index 0000000..433e9e5 Binary files /dev/null and b/nbs/color_conversions_files/figure-html/cell-6-output-1.png differ diff --git a/nbs/color_conversions_files/figure-html/cell-7-output-1.png b/nbs/color_conversions_files/figure-html/cell-7-output-1.png new file mode 100644 index 0000000..323afe3 Binary files /dev/null and b/nbs/color_conversions_files/figure-html/cell-7-output-1.png differ diff --git a/nbs/color_conversions_files/figure-html/cell-8-output-1.png b/nbs/color_conversions_files/figure-html/cell-8-output-1.png new file mode 100644 index 0000000..bb9ee7e Binary files /dev/null and b/nbs/color_conversions_files/figure-html/cell-8-output-1.png differ diff --git a/nbs/color_conversions_files/figure-html/cell-9-output-1.png b/nbs/color_conversions_files/figure-html/cell-9-output-1.png new file mode 100644 index 0000000..1acec78 Binary files /dev/null and b/nbs/color_conversions_files/figure-html/cell-9-output-1.png differ diff --git a/nbs/color_raw_to_rgb.html b/nbs/color_raw_to_rgb.html new file mode 100644 index 0000000..d9276d9 --- /dev/null +++ b/nbs/color_raw_to_rgb.html @@ -0,0 +1,779 @@ + + + + + + + + + + + + +Kornia - Convert RGB to RAW + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Convert RGB to RAW

+
+
Basic
+
Color spaces
+
kornia.color
+
+
+ +
+
+ In this tutorial we are going to learn how to convert image from raw color using kornia.color. +
+
+ + +
+ +
+
Author
+
+

Oskar Flordal

+
+
+ +
+
Published
+
+

October 13, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+

Download necessary files and libraries

+
+
%%capture
+!pip install kornia
+!pip install rawpy
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://docs.google.com/uc?export=download&id=1nSM_FYJ7i9-_57ecPY5sCG2s8zt9dRhF"
+download_image(url, "raw.dng")
+
+
'raw.dng'
+
+
+
+
+

Import necessary libraries

+
+
import kornia
+import matplotlib.pyplot as plt
+import numpy as np
+import rawpy
+import torch
+
+
+
+

Prepare the raw file through rawpy

+
+
path = "raw.dng"
+raw = rawpy.imread(path)
+cfa = "".join([chr(raw.color_desc[i]) for i in raw.raw_pattern.flatten()])
+
+# Figure out which cfa we are using by looking at the component order from rawpy
+# if we do this directly from a camera this would of course be known ahead
+# of time
+if cfa == "GRBG":
+    korniacfa = kornia.color.CFA.GB
+elif cfa == "GBRG":
+    korniacfa = kornia.color.CFA.GR
+elif cfa == "BGGR":
+    korniacfa = kornia.color.CFA.RG
+elif cfa == "RGGB":
+    korniacfa = kornia.color.CFA.BG
+
+# This is a GB file i.e. top left pixel is Green follow by Red (and the pair
+# starting at (1,1) is Green, Blue)
+print(cfa)
+print(korniacfa)
+
+
GRBG
+CFA.GB
+
+
+
+
+

Get the data into kornia by doing the conversion

+
+
# We find the data inside raw.raw_image
+rawdata = raw.raw_image
+# white level gives maximum value for a pixel
+rawtensor = torch.Tensor(rawdata.astype(np.float32) / raw.white_level).reshape(
+    1, 1, raw.raw_image.shape[0], raw.raw_image.shape[1]
+)
+rgbtensor = kornia.color.raw.raw_to_rgb(rawtensor, korniacfa)
+
+
+
+

Visualize

+
+
npimg = np.moveaxis(np.squeeze((rgbtensor * 255.0).numpy().astype(np.uint8)), 0, 2)
+plt.figure()
+
+# Show the image
+# Colors will look a little funky because they need to be balanced properly, but
+# the leaves are supposed to be redm berries blue and grass green
+plt.imshow(npimg)
+
+
<matplotlib.image.AxesImage>
+
+
+

+
+
+
+
+

Gotchas: Rotation gives a different cfa

+
+
# if we do a pipeline were we first rotate the image, it will end up with a
+# different cfa that isn't possible to describe since we are assuming all red
+# samples are on t.he same row while they would not be rotated
+rgbtensor = kornia.color.raw.raw_to_rgb(torch.rot90(rawtensor, 1, [2, 3]), korniacfa)
+npimg = np.moveaxis(np.squeeze((rgbtensor * 255.0).numpy().astype(np.uint8)), 0, 2)
+plt.figure()
+plt.imshow(npimg)
+
+
<matplotlib.image.AxesImage>
+
+
+

+
+
+
+
# If we crop, we can adjust for this by using a different cfa
+rgbtensor = kornia.color.raw.raw_to_rgb(rawtensor[:, :, 1:1023, 1:1023], kornia.color.raw.CFA.GR)
+npimg = np.moveaxis(np.squeeze((rgbtensor * 255.0).numpy().astype(np.uint8)), 0, 2)
+plt.figure()
+plt.imshow(npimg)
+
+
<matplotlib.image.AxesImage>
+
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/color_raw_to_rgb_files/figure-html/cell-7-output-2.png b/nbs/color_raw_to_rgb_files/figure-html/cell-7-output-2.png new file mode 100644 index 0000000..ae4411a Binary files /dev/null and b/nbs/color_raw_to_rgb_files/figure-html/cell-7-output-2.png differ diff --git a/nbs/color_raw_to_rgb_files/figure-html/cell-8-output-2.png b/nbs/color_raw_to_rgb_files/figure-html/cell-8-output-2.png new file mode 100644 index 0000000..94c941d Binary files /dev/null and b/nbs/color_raw_to_rgb_files/figure-html/cell-8-output-2.png differ diff --git a/nbs/color_raw_to_rgb_files/figure-html/cell-9-output-2.png b/nbs/color_raw_to_rgb_files/figure-html/cell-9-output-2.png new file mode 100644 index 0000000..1537045 Binary files /dev/null and b/nbs/color_raw_to_rgb_files/figure-html/cell-9-output-2.png differ diff --git a/nbs/color_yuv420_to_rgb.html b/nbs/color_yuv420_to_rgb.html new file mode 100644 index 0000000..23fa402 --- /dev/null +++ b/nbs/color_yuv420_to_rgb.html @@ -0,0 +1,778 @@ + + + + + + + + + + + + +Kornia - Convert RGB to YUV420 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Convert RGB to YUV420

+
+
Basic
+
Color spaces
+
kornia.color
+
+
+ +
+
+ In this tutorial we are going to learn how to convert image from RGB color to YUV420 using kornia.color. +
+
+ + +
+ +
+
Author
+
+

Oskar Flordal

+
+
+ +
+
Published
+
+

October 10, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+

Get data and libraries to work with

+
+
%%capture
+!pip install kornia
+!pip install py7zr
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://github.com/kornia/data/raw/main/foreman_qcif.7z"
+download_image(url)
+
+
'foreman_qcif.7z'
+
+
+
+
+

Import needed libs

+
+
import kornia
+import numpy as np
+
+# prepare the data, decompress so we have a foreman_qcif.yuv ready
+import py7zr
+import torch
+
+with py7zr.SevenZipFile("foreman_qcif.7z", mode="r") as z:
+    z.extractall()
+
+
+
+

Define functions for reading the yuv file to torch tensor for use in Kornia

+
+
import matplotlib.pyplot as plt
+
+
+def read_frame(fname, framenum):
+    # A typical 420 yuv file is 3 planes Y, u then v with u/v a quartyer the size of Y
+    # Build rgb png images from foreman that is 3 plane yuv420
+    yuvnp = np.fromfile(fname, dtype=np.uint8, count=int(176 * 144 * 1.5), offset=int(176 * 144 * 1.5) * framenum)
+    y = torch.from_numpy(yuvnp[0 : 176 * 144].reshape((1, 1, 144, 176)).astype(np.float32) / 255.0)
+
+    uv_tmp = yuvnp[176 * 144 : int(144 * 176 * 3 / 2)].reshape((1, 2, int(144 / 2), int(176 / 2)))
+    # uv (chroma) is typically defined from -0.5 to 0.5 (or -128 to 128 for 8-bit)
+    uv = torch.from_numpy(uv_tmp.astype(np.float32) / 255.0) - 0.5
+    return (y, uv)
+
+
+
+

Sample what the images look like Y, u, v channels separaatly and then converted to rgn through kornia (and back to numpy in this case)

+
+
(y, uv) = read_frame("foreman_qcif.yuv", 0)  # using compression classic foreman
+plt.imshow((y.numpy()[0, 0, :, :] * 255.0).astype(np.uint8), cmap="gray")
+plt.figure()
+plt.imshow(((uv.numpy()[0, 0, :, :] + 0.5) * 255.0).astype(np.uint8), cmap="gray")
+plt.figure()
+plt.imshow(((uv.numpy()[0, 1, :, :] + 0.5) * 255.0).astype(np.uint8), cmap="gray")
+
+rgb = np.moveaxis(kornia.color.yuv420_to_rgb(y, uv).numpy(), 1, 3).reshape((144, 176, 3))
+
+print("as converted through kornia")
+plt.figure()
+plt.imshow((rgb * 255).astype(np.uint8))
+
+
as converted through kornia
+
+
+
<matplotlib.image.AxesImage>
+
+
+

+
+
+

+
+
+

+
+
+

+
+
+
+
+

We can use these in some internal Kornia algorithm implementations. Lets pretend we want to do LoFTR on the red channel

+
+
import cv2
+
+loftr = kornia.feature.LoFTR("outdoor")
+(y0, uv0) = read_frame("foreman_qcif.yuv", 175)
+(y1, uv1) = read_frame("foreman_qcif.yuv", 185)
+rgb0 = kornia.color.yuv420_to_rgb(y0, uv0)
+rgb1 = kornia.color.yuv420_to_rgb(y1, uv1)
+
+with torch.no_grad():
+    matches = loftr({"image0": rgb0[:, 0:1, :, :], "image1": rgb1[:, 0:1, :, :]})
+
+matched_image = cv2.drawMatches(
+    np.moveaxis(rgb0.numpy()[0, :, :, :] * 255.0, 0, 2).astype(np.uint8),
+    [cv2.KeyPoint(x[0], x[1], 0) for x in matches["keypoints0"].numpy()],
+    np.moveaxis(rgb1.numpy()[0, :, :, :] * 255.0, 0, 2).astype(np.uint8),
+    [cv2.KeyPoint(x[0], x[1], 0) for x in matches["keypoints1"].numpy()],
+    [cv2.DMatch(x, x, 0) for x in range(len(matches["keypoints1"].numpy()))],
+    None,
+)
+
+plt.figure(figsize=(30, 30))
+plt.imshow(matched_image)
+
+
<matplotlib.image.AxesImage>
+
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/color_yuv420_to_rgb_files/figure-html/cell-6-output-3.png b/nbs/color_yuv420_to_rgb_files/figure-html/cell-6-output-3.png new file mode 100644 index 0000000..18b11e7 Binary files /dev/null and b/nbs/color_yuv420_to_rgb_files/figure-html/cell-6-output-3.png differ diff --git a/nbs/color_yuv420_to_rgb_files/figure-html/cell-6-output-4.png b/nbs/color_yuv420_to_rgb_files/figure-html/cell-6-output-4.png new file mode 100644 index 0000000..a9893a9 Binary files /dev/null and b/nbs/color_yuv420_to_rgb_files/figure-html/cell-6-output-4.png differ diff --git a/nbs/color_yuv420_to_rgb_files/figure-html/cell-6-output-5.png b/nbs/color_yuv420_to_rgb_files/figure-html/cell-6-output-5.png new file mode 100644 index 0000000..250b654 Binary files /dev/null and b/nbs/color_yuv420_to_rgb_files/figure-html/cell-6-output-5.png differ diff --git a/nbs/color_yuv420_to_rgb_files/figure-html/cell-6-output-6.png b/nbs/color_yuv420_to_rgb_files/figure-html/cell-6-output-6.png new file mode 100644 index 0000000..d497758 Binary files /dev/null and b/nbs/color_yuv420_to_rgb_files/figure-html/cell-6-output-6.png differ diff --git a/nbs/color_yuv420_to_rgb_files/figure-html/cell-7-output-2.png b/nbs/color_yuv420_to_rgb_files/figure-html/cell-7-output-2.png new file mode 100644 index 0000000..7ec388e Binary files /dev/null and b/nbs/color_yuv420_to_rgb_files/figure-html/cell-7-output-2.png differ diff --git a/nbs/connected_components.html b/nbs/connected_components.html new file mode 100644 index 0000000..8c94627 --- /dev/null +++ b/nbs/connected_components.html @@ -0,0 +1,753 @@ + + + + + + + + + + + + +Kornia - Connected Components Algorithm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Connected Components Algorithm

+
+
Basic
+
Segmentation
+
Labeling
+
Unsupervised
+
kornia.contrib
+
+
+ +
+
+ In this tutorial we are going to learn how to segment small objects in the image using the kornia implementation of the classic Computer Vision technique called Connected-component labelling (CCL). +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

September 16, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://github.com/kornia/data/raw/main/cells_binary.png"
+download_image(url)
+
+
'cells_binary.png'
+
+
+
+
from __future__ import annotations
+
+import kornia as K
+import matplotlib.pyplot as plt
+import torch
+
+

We define utility functions to visualize the segmentation properly

+
+
def create_random_labels_map(classes: int) -> dict[int, tuple[int, int, int]]:
+    labels_map: Dict[int, Tuple[int, int, int]] = {}
+    for i in classes:
+        labels_map[i] = torch.randint(0, 255, (3,))
+    labels_map[0] = torch.zeros(3)
+    return labels_map
+
+
+def labels_to_image(img_labels: torch.Tensor, labels_map: Dict[int, Tuple[int, int, int]]) -> torch.Tensor:
+    """Function that given an image with labels ids and their pixels intrensity mapping, creates a RGB
+    representation for visualisation purposes."""
+    assert len(img_labels.shape) == 2, img_labels.shape
+    H, W = img_labels.shape
+    out = torch.empty(3, H, W, dtype=torch.uint8)
+    for label_id, label_val in labels_map.items():
+        mask = img_labels == label_id
+        for i in range(3):
+            out[i].masked_fill_(mask, label_val[i])
+    return out
+
+
+def show_components(img, labels):
+    color_ids = torch.unique(labels)
+    labels_map = create_random_labels_map(color_ids)
+    labels_img = labels_to_image(labels, labels_map)
+
+    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 12))
+
+    # Showing Original Image
+    ax1.imshow(img)
+    ax1.axis("off")
+    ax1.set_title("Orginal Image")
+
+    # Showing Image after Component Labeling
+    ax2.imshow(labels_img.permute(1, 2, 0).squeeze().numpy())
+    ax2.axis("off")
+    ax2.set_title("Component Labeling")
+
+    plt.show()
+
+

We load the image using Kornia

+
+
img_t = K.io.load_image("cells_binary.png", K.io.ImageLoadType.GRAY32)[None, ...]
+
+print(img_t.shape)
+
+
torch.Size([1, 1, 602, 602])
+
+
+

Apply the Connected-component labelling algorithm using the kornia.contrib.connected_components functionality. The num_iterations parameter will control the total number of iterations of the algorithm to finish until it converges to a solution.

+
+
labels_out = K.contrib.connected_components(img_t, num_iterations=150)
+print(labels_out.shape)
+
+
torch.Size([1, 1, 602, 602])
+
+
+
+
show_components(img_t.numpy().squeeze(), labels_out.squeeze())
+
+

+
+
+

We can also explore the labels

+
+
print(torch.unique(labels_out))
+
+
tensor([     0.,  13235.,  24739.,  31039.,  32177.,  44349.,  59745.,  61289.,
+         66209.,  69449.,  78869.,  94867., 101849., 102217., 102319., 115227.,
+        115407., 137951., 138405., 150047., 158715., 162179., 170433., 170965.,
+        174279., 177785., 182867., 210145., 212647., 215451., 216119., 221291.,
+        222367., 226183., 226955., 248757., 252823., 255153., 263337., 265505.,
+        270299., 270649., 277725., 282775., 296897., 298545., 299793., 300517.,
+        313961., 316217., 321259., 322235., 335599., 337037., 340289., 347363.,
+        352235., 352721., 360801., 360903., 360965., 361073., 361165., 361197.])
+
+
+ + + +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/connected_components_files/figure-html/cell-8-output-1.png b/nbs/connected_components_files/figure-html/cell-8-output-1.png new file mode 100644 index 0000000..25dad7b Binary files /dev/null and b/nbs/connected_components_files/figure-html/cell-8-output-1.png differ diff --git a/nbs/data_augmentation_2d.html b/nbs/data_augmentation_2d.html new file mode 100644 index 0000000..8213f7a --- /dev/null +++ b/nbs/data_augmentation_2d.html @@ -0,0 +1,1315 @@ + + + + + + + + + + + + +Kornia - Data Augmentation 2D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Data Augmentation 2D

+
+
Basic
+
2D
+
Data augmentation
+
kornia.augmentation
+
+
+ +
+
+ A show case of the Data Augmentation operation available on Kornia for images. +
+
+ + +
+ +
+
Author
+
+

João Gustavo A. Amorim

+
+
+ +
+
Published
+
+

February 4, 2023

+
+
+ + +
+ + +
+ +

Open in google colab

+

Just a simple examples showing the Augmentations available on Kornia.

+

For more information check the docs: https://kornia.readthedocs.io/en/latest/augmentation.module.html

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
import kornia
+import matplotlib.pyplot as plt
+from kornia.augmentation import (
+    CenterCrop,
+    ColorJiggle,
+    ColorJitter,
+    PadTo,
+    RandomAffine,
+    RandomBoxBlur,
+    RandomBrightness,
+    RandomChannelShuffle,
+    RandomContrast,
+    RandomCrop,
+    RandomCutMixV2,
+    RandomElasticTransform,
+    RandomEqualize,
+    RandomErasing,
+    RandomFisheye,
+    RandomGamma,
+    RandomGaussianBlur,
+    RandomGaussianNoise,
+    RandomGrayscale,
+    RandomHorizontalFlip,
+    RandomHue,
+    RandomInvert,
+    RandomJigsaw,
+    RandomMixUpV2,
+    RandomMosaic,
+    RandomMotionBlur,
+    RandomPerspective,
+    RandomPlanckianJitter,
+    RandomPlasmaBrightness,
+    RandomPlasmaContrast,
+    RandomPlasmaShadow,
+    RandomPosterize,
+    RandomResizedCrop,
+    RandomRGBShift,
+    RandomRotation,
+    RandomSaturation,
+    RandomSharpness,
+    RandomSolarize,
+    RandomThinPlateSpline,
+    RandomVerticalFlip,
+)
+
+
+

Load an Image

+

The augmentations expects an image with shape BxCxHxW

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://raw.githubusercontent.com/kornia/data/main/panda.jpg"
+download_image(url)
+
+
'panda.jpg'
+
+
+
+
img_type = kornia.io.ImageLoadType.RGB32
+img = kornia.io.load_image("panda.jpg", img_type, "cpu")[None]
+
+
+
def plot_tensor(data, title=""):
+    b, c, h, w = data.shape
+
+    fig, axes = plt.subplots(1, b, dpi=150, subplot_kw={"aspect": "equal"})
+    if b == 1:
+        axes = [axes]
+
+    for idx, ax in enumerate(axes):
+        ax.imshow(kornia.utils.tensor_to_image(data[idx, ...]))
+        ax.set_ylim(h, 0)
+        ax.set_xlim(0, w)
+        ax.tick_params(top=True, labeltop=True, bottom=False, labelbottom=False)
+    fig.suptitle(title)
+    plt.show()
+
+
+
plot_tensor(img, "panda")
+
+

+
+
+
+
+

2D transforms

+

Sometimes you may wish to apply the exact same transformations on all the elements in one batch. Here, we provided a same_on_batch keyword to all random generators for you to use. Instead of an element-wise parameter generating, it will generate exact same parameters across the whole batch.

+
+
# Create a batched input
+num_samples = 2
+
+inpt = img.repeat(num_samples, 1, 1, 1)
+
+
+

Intensity

+
+

Random Planckian Jitter

+
+
randomplanckianjitter = RandomPlanckianJitter("blackbody", same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomplanckianjitter(inpt), "Planckian Jitter")
+
+

+
+
+
+
+

Random Plasma Shadow

+
+
randomplasmashadow = RandomPlasmaShadow(
+    roughness=(0.1, 0.7), shade_intensity=(-1.0, 0.0), shade_quantity=(0.0, 1.0), same_on_batch=False, keepdim=False, p=1.0
+)
+
+plot_tensor(randomplasmashadow(inpt), "Plasma Shadow")
+
+

+
+
+
+
+

Random Plasma Brightness

+
+
randomplasmabrightness = RandomPlasmaBrightness(
+    roughness=(0.1, 0.7), intensity=(0.0, 1.0), same_on_batch=False, keepdim=False, p=1.0
+)
+plot_tensor(randomplasmabrightness(inpt), "Plasma Brightness")
+
+

+
+
+
+
+

Random Plasma Contrast

+
+
randomplasmacontrast = RandomPlasmaContrast(roughness=(0.1, 0.7), same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomplasmacontrast(inpt), "Plasma Contrast")
+
+

+
+
+
+
+

Color Jiggle

+
+
colorjiggle = ColorJiggle(0.3, 0.3, 0.3, 0.3, same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(colorjiggle(inpt), "Color Jiggle")
+
+

+
+
+
+
+

Color Jitter

+
+
colorjitter = ColorJitter(0.3, 0.3, 0.3, 0.3, same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(colorjitter(inpt), "Color Jitter")
+
+

+
+
+
+
+

Random Box Blur

+
+
randomboxblur = RandomBoxBlur((21, 5), "reflect", same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomboxblur(inpt), "Box Blur")
+
+

+
+
+
+
+

Random Brightness

+
+
randombrightness = RandomBrightness(brightness=(0.8, 1.2), clip_output=True, same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randombrightness(inpt), "Random Brightness")
+
+

+
+
+
+
+

Random Channel Shuffle

+
+
randomchannelshuffle = RandomChannelShuffle(same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomchannelshuffle(inpt), "Random Channel Shuffle")
+
+

+
+
+
+
+

Random Contrast

+
+
randomcontrast = RandomContrast(contrast=(0.8, 1.2), clip_output=True, same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomcontrast(inpt), "Random Contrast")
+
+

+
+
+
+
+

Random Equalize

+
+
randomequalize = RandomEqualize(same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomequalize(inpt), "Random Equalize")
+
+

+
+
+
+
+

Random Gamma

+
+
randomgamma = RandomGamma((0.2, 1.3), (1.0, 1.5), same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomgamma(inpt), "Random Gamma")
+
+

+
+
+
+
+

Random Grayscale

+
+
randomgrayscale = RandomGrayscale(same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomgrayscale(inpt), "Random Grayscale")
+
+

+
+
+
+
+

Random Gaussian Blur

+
+
randomgaussianblur = RandomGaussianBlur((21, 21), (0.2, 1.3), "reflect", same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomgaussianblur(inpt), "Random Gaussian Blur")
+
+
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
+
+
+

+
+
+
+
+

Random Gaussian Noise

+
+
randomgaussiannoise = RandomGaussianNoise(mean=0.2, std=0.7, same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomgaussiannoise(inpt), "Random Gaussian Noise")
+
+
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
+Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
+
+
+

+
+
+
+
+

Random Hue

+
+
randomhue = RandomHue((-0.2, 0.4), same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomhue(inpt), "Random Hue")
+
+

+
+
+
+
+

Random Motion Blur

+
+
randommotionblur = RandomMotionBlur((7, 7), 35.0, 0.5, "reflect", "nearest", same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randommotionblur(inpt), "Random Motion Blur")
+
+

+
+
+
+
+

Random Posterize

+
+
randomposterize = RandomPosterize(bits=3, same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomposterize(inpt), "Random Posterize")
+
+

+
+
+
+
+

Random RGB Shift

+
+
randomrgbshift = RandomRGBShift(
+    r_shift_limit=0.5, g_shift_limit=0.5, b_shift_limit=0.5, same_on_batch=False, keepdim=False, p=1.0
+)
+plot_tensor(randomrgbshift(inpt), "Random RGB Shift")
+
+

+
+
+
+
+

Random Saturation

+
+
randomsaturation = RandomSaturation((1.0, 1.0), same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomsaturation(inpt), "Random Saturation")
+
+

+
+
+
+
+

Random Sharpness

+
+
randomsharpness = RandomSharpness((0.5, 1.0), same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomsharpness(inpt), "Random Sharpness")
+
+

+
+
+
+
+

Random Solarize

+
+
randomsolarize = RandomSolarize(0.3, 0.1, same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomsolarize(inpt), "Random Solarize")
+
+

+
+
+
+
+
+

Geometric

+
+

Center Crop

+
+
centercrop = CenterCrop(150, resample="nearest", cropping_mode="resample", align_corners=True, keepdim=False, p=1.0)
+
+plot_tensor(centercrop(inpt), "Center Crop")
+
+

+
+
+
+
+

Pad To

+
+
padto = PadTo((500, 500), "constant", 1, keepdim=False)
+
+plot_tensor(padto(inpt), "Pad To")
+
+

+
+
+
+
+

Random Affine

+
+
randomaffine = RandomAffine(
+    (-15.0, 5.0),
+    (0.3, 1.0),
+    (0.4, 1.3),
+    0.5,
+    resample="nearest",
+    padding_mode="reflection",
+    align_corners=True,
+    same_on_batch=False,
+    keepdim=False,
+    p=1.0,
+)
+plot_tensor(randomaffine(inpt), "Random Affine")
+
+

+
+
+
+
+

Random Crop

+
+
randomcrop = RandomCrop(
+    (150, 150),
+    10,
+    True,
+    1,
+    "constant",
+    "nearest",
+    cropping_mode="resample",
+    same_on_batch=False,
+    align_corners=True,
+    keepdim=False,
+    p=1.0,
+)
+
+plot_tensor(randomcrop(inpt), "Random Crop")
+
+

+
+
+
+
+

Random Erasing

+
+
randomerasing = RandomErasing(scale=(0.02, 0.33), ratio=(0.3, 3.3), value=1, same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomerasing(inpt), "Random Erasing")
+
+

+
+
+
+
+

Random Elastic Transform

+
+
randomelastictransform = RandomElasticTransform(
+    (27, 27), (33, 31), (0.5, 1.5), align_corners=True, padding_mode="reflection", same_on_batch=False, keepdim=False, p=1.0
+)
+
+plot_tensor(randomelastictransform(inpt), "Random Elastic Transform")
+
+

+
+
+
+
+

Random Fish Eye

+
+
c = kornia.core.tensor([-0.3, 0.3])
+g = kornia.core.tensor([0.9, 1.0])
+randomfisheye = RandomFisheye(c, c, g, same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomfisheye(inpt), "Random Fish Eye")
+
+

+
+
+
+
+

Random Horizontal Flip

+
+
randomhorizontalflip = RandomHorizontalFlip(same_on_batch=False, keepdim=False, p=0.7)
+
+plot_tensor(randomhorizontalflip(inpt), "Random Horizontal Flip")
+
+

+
+
+
+
+

Random Invert

+
+
randominvert = RandomInvert(same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randominvert(inpt), "Random Invert")
+
+

+
+
+
+
+

Random Perspective

+
+
randomperspective = RandomPerspective(0.5, "nearest", align_corners=True, same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomperspective(inpt), "Random Perspective")
+
+

+
+
+
+
+

Random Resized Crop

+
+
randomresizedcrop = RandomResizedCrop(
+    (200, 200),
+    (0.4, 1.0),
+    (2.0, 2.0),
+    "nearest",
+    align_corners=True,
+    cropping_mode="resample",
+    same_on_batch=False,
+    keepdim=False,
+    p=1.0,
+)
+
+plot_tensor(randomresizedcrop(inpt), "Random Resized Crop")
+
+

+
+
+
+
+

Random Rotation

+
+
randomrotation = RandomRotation(15.0, "nearest", align_corners=True, same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomrotation(inpt), "Random Rotation")
+
+

+
+
+
+
+

Random Vertical Flip

+
+
randomverticalflip = RandomVerticalFlip(same_on_batch=False, keepdim=False, p=0.6, p_batch=1.0)
+
+plot_tensor(randomverticalflip(inpt), "Random Vertical Flip")
+
+

+
+
+
+
+

Random Thin Plate Spline

+
+
randomthinplatespline = RandomThinPlateSpline(0.6, align_corners=True, same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomverticalflip(inpt), "Random Thin Plate Spline")
+
+

+
+
+
+
+
+

Mix

+
+

Random Cut Mix

+
+
randomcutmixv2 = RandomCutMixV2(4, (0.2, 0.9), 0.1, same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randomcutmixv2(inpt), "Random Cut Mix")
+
+

+
+
+
+
+

Random Mix Up

+
+
randommixupv2 = RandomMixUpV2((0.1, 0.9), same_on_batch=False, keepdim=False, p=1.0)
+
+plot_tensor(randommixupv2(inpt), "Random Mix Up")
+
+

+
+
+
+
+

Random Mosaic

+
+
randommosaic = RandomMosaic(
+    (250, 125),
+    (4, 4),
+    (0.3, 0.7),
+    align_corners=True,
+    cropping_mode="resample",
+    padding_mode="reflect",
+    resample="nearest",
+    keepdim=False,
+    p=1.0,
+)
+plot_tensor(randommosaic(inpt), "Random Mosaic")
+
+

+
+
+
+
+

Random Jigsaw

+
+
# randomjigsaw = RandomJigsaw((2, 2), ensure_perm=False, same_on_batch=False, keepdim=False, p=1.0)
+
+
+# plot_tensor(randomjigsaw(inpt), "Random Jigsaw")
+
+ + +
+
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-10-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-10-output-1.png new file mode 100644 index 0000000..bfbe1a5 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-10-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-11-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-11-output-1.png new file mode 100644 index 0000000..00b6dc1 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-11-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-12-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-12-output-1.png new file mode 100644 index 0000000..2eff785 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-12-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-13-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-13-output-1.png new file mode 100644 index 0000000..6cb83fb Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-13-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-14-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-14-output-1.png new file mode 100644 index 0000000..eeca14d Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-14-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-15-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-15-output-1.png new file mode 100644 index 0000000..457d14e Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-15-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-16-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-16-output-1.png new file mode 100644 index 0000000..b56b14c Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-16-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-17-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-17-output-1.png new file mode 100644 index 0000000..3e76053 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-17-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-18-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-18-output-1.png new file mode 100644 index 0000000..ead34dd Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-18-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-19-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-19-output-1.png new file mode 100644 index 0000000..753dcc9 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-19-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-20-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-20-output-1.png new file mode 100644 index 0000000..1c0ca88 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-20-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-21-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-21-output-1.png new file mode 100644 index 0000000..387bd22 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-21-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-22-output-2.png b/nbs/data_augmentation_2d_files/figure-html/cell-22-output-2.png new file mode 100644 index 0000000..74fcce6 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-22-output-2.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-23-output-2.png b/nbs/data_augmentation_2d_files/figure-html/cell-23-output-2.png new file mode 100644 index 0000000..aa8cb85 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-23-output-2.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-24-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-24-output-1.png new file mode 100644 index 0000000..b6e5c82 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-24-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-25-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-25-output-1.png new file mode 100644 index 0000000..9f5ba2e Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-25-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-26-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-26-output-1.png new file mode 100644 index 0000000..7f80318 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-26-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-27-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-27-output-1.png new file mode 100644 index 0000000..a5d9089 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-27-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-28-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-28-output-1.png new file mode 100644 index 0000000..5e70fb3 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-28-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-29-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-29-output-1.png new file mode 100644 index 0000000..55b000c Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-29-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-30-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-30-output-1.png new file mode 100644 index 0000000..6003109 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-30-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-31-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-31-output-1.png new file mode 100644 index 0000000..ed6c93f Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-31-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-32-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-32-output-1.png new file mode 100644 index 0000000..f0eb4fe Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-32-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-33-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-33-output-1.png new file mode 100644 index 0000000..1f67e4d Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-33-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-34-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-34-output-1.png new file mode 100644 index 0000000..c018cd5 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-34-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-35-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-35-output-1.png new file mode 100644 index 0000000..f5481bf Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-35-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-36-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-36-output-1.png new file mode 100644 index 0000000..a80d3f3 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-36-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-37-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-37-output-1.png new file mode 100644 index 0000000..089591a Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-37-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-38-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-38-output-1.png new file mode 100644 index 0000000..431f7f6 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-38-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-39-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-39-output-1.png new file mode 100644 index 0000000..ee18c08 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-39-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-40-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-40-output-1.png new file mode 100644 index 0000000..c67facb Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-40-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-41-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-41-output-1.png new file mode 100644 index 0000000..a1683c8 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-41-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-42-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-42-output-1.png new file mode 100644 index 0000000..2200168 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-42-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-43-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-43-output-1.png new file mode 100644 index 0000000..85d91d0 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-43-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-44-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-44-output-1.png new file mode 100644 index 0000000..d6de292 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-44-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-45-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-45-output-1.png new file mode 100644 index 0000000..59f5c5f Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-45-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-46-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-46-output-1.png new file mode 100644 index 0000000..f0399cb Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-46-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-47-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-47-output-1.png new file mode 100644 index 0000000..3487817 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-47-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-7-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-7-output-1.png new file mode 100644 index 0000000..8e15994 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-7-output-1.png differ diff --git a/nbs/data_augmentation_2d_files/figure-html/cell-9-output-1.png b/nbs/data_augmentation_2d_files/figure-html/cell-9-output-1.png new file mode 100644 index 0000000..ee102c6 Binary files /dev/null and b/nbs/data_augmentation_2d_files/figure-html/cell-9-output-1.png differ diff --git a/nbs/data_augmentation_kornia_lightning.html b/nbs/data_augmentation_kornia_lightning.html new file mode 100644 index 0000000..c68bbe8 --- /dev/null +++ b/nbs/data_augmentation_kornia_lightning.html @@ -0,0 +1,853 @@ + + + + + + + + + + + + +Kornia - Kornia and PyTorch Lightning GPU data augmentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Kornia and PyTorch Lightning GPU data augmentation

+
+
Basic
+
Data augmentation
+
Pytorch lightning
+
kornia.augmentation
+
+
+ +
+
+ In this tutorial we show how one can combine both Kornia and PyTorch Lightning to perform data augmentation to train a model using CPUs and GPUs in batch mode without additional effort. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

March 18, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+

Install Kornia and PyTorch Lightning

+

We first install Kornia and PyTorch Lightning

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+!pip install pytorch_lightning torchmetrics
+
+

Import the needed libraries

+
+
import os
+
+import kornia as K
+import numpy as np
+import pytorch_lightning as pl
+import torch
+import torch.nn as nn
+import torchmetrics
+from PIL import Image
+from torch.nn import functional as F
+from torch.utils.data import DataLoader
+from torchvision.datasets import CIFAR10
+
+
+
+

Define Data Augmentations module

+
+
class DataAugmentation(nn.Module):
+    """Module to perform data augmentation using Kornia on torch tensors."""
+
+    def __init__(self, apply_color_jitter: bool = False) -> None:
+        super().__init__()
+        self._apply_color_jitter = apply_color_jitter
+
+        self._max_val: float = 255.0
+
+        self.transforms = nn.Sequential(K.enhance.Normalize(0.0, self._max_val), K.augmentation.RandomHorizontalFlip(p=0.5))
+
+        self.jitter = K.augmentation.ColorJitter(0.5, 0.5, 0.5, 0.5)
+
+    @torch.no_grad()  # disable gradients for effiency
+    def forward(self, x: torch.Tensor) -> torch.Tensor:
+        x_out = self.transforms(x)  # BxCxHxW
+        if self._apply_color_jitter:
+            x_out = self.jitter(x_out)
+        return x_out
+
+
+
+

Define a Pre-processing model

+
+
class PreProcess(nn.Module):
+    """Module to perform pre-process using Kornia on torch tensors."""
+
+    def __init__(self) -> None:
+        super().__init__()
+
+    @torch.no_grad()  # disable gradients for effiency
+    def forward(self, x: Image) -> torch.Tensor:
+        x_tmp: np.ndarray = np.array(x)  # HxWxC
+        x_out: torch.Tensor = K.image_to_tensor(x_tmp, keepdim=True)  # CxHxW
+        return x_out.float()
+
+
+
+

Define PyTorch Lightning model

+
+
class CoolSystem(pl.LightningModule):
+    def __init__(self):
+        super().__init__()
+        # not the best model...
+        self.l1 = torch.nn.Linear(3 * 32 * 32, 10)
+
+        self.preprocess = PreProcess()
+
+        self.transform = DataAugmentation()
+
+        self.accuracy = torchmetrics.Accuracy(task="multiclass", num_classes=10)
+
+    def forward(self, x):
+        return torch.relu(self.l1(x.view(x.size(0), -1)))
+
+    def training_step(self, batch, batch_idx):
+        # REQUIRED
+        x, y = batch
+        x_aug = self.transform(x)  # => we perform GPU/Batched data augmentation
+        logits = self.forward(x_aug)
+        loss = F.cross_entropy(logits, y)
+        self.log("train_acc_step", self.accuracy(logits.argmax(1), y))
+        self.log("train_loss", loss)
+        return loss
+
+    def validation_step(self, batch, batch_idx):
+        # OPTIONAL
+        x, y = batch
+        logits = self.forward(x)
+        self.log("val_acc_step", self.accuracy(logits.argmax(1), y))
+        return F.cross_entropy(logits, y)
+
+    def test_step(self, batch, batch_idx):
+        # OPTIONAL
+        x, y = batch
+        logits = self.forward(x)
+        acc = self.accuracy(logits.argmax(1), y)
+        self.log("test_acc_step", acc)
+        return acc
+
+    def configure_optimizers(self):
+        # REQUIRED
+        # can return multiple optimizers and learning_rate schedulers
+        # (LBFGS it is automatically supported, no need for closure function)
+        return torch.optim.Adam(self.parameters(), lr=0.0004)
+
+    def prepare_data(self):
+        CIFAR10(os.getcwd(), train=True, download=True, transform=self.preprocess)
+        CIFAR10(os.getcwd(), train=False, download=True, transform=self.preprocess)
+
+    def train_dataloader(self):
+        # REQUIRED
+        dataset = CIFAR10(os.getcwd(), train=True, download=False, transform=self.preprocess)
+        loader = DataLoader(dataset, batch_size=32, num_workers=1)
+        return loader
+
+    def val_dataloader(self):
+        dataset = CIFAR10(os.getcwd(), train=True, download=False, transform=self.preprocess)
+        loader = DataLoader(dataset, batch_size=32, num_workers=1)
+        return loader
+
+    def test_dataloader(self):
+        dataset = CIFAR10(os.getcwd(), train=False, download=False, transform=self.preprocess)
+        loader = DataLoader(dataset, batch_size=16, num_workers=1)
+        return loader
+
+
+
+

Run training

+
+
from pytorch_lightning import Trainer
+
+# init model
+model = CoolSystem()
+
+# Initialize a trainer
+accelerator = "cpu"  # can be 'gpu'
+
+trainer = Trainer(accelerator=accelerator, max_epochs=1, enable_progress_bar=False)
+
+# Train the model ⚡
+trainer.fit(model)
+
+
GPU available: True (cuda), used: False
+TPU available: False, using: 0 TPU cores
+IPU available: False, using: 0 IPUs
+HPU available: False, using: 0 HPUs
+
+  | Name       | Type               | Params
+--------------------------------------------------
+0 | l1         | Linear             | 30.7 K
+1 | preprocess | PreProcess         | 0     
+2 | transform  | DataAugmentation   | 0     
+3 | accuracy   | MulticlassAccuracy | 0     
+--------------------------------------------------
+30.7 K    Trainable params
+0         Non-trainable params
+30.7 K    Total params
+0.123     Total estimated model params size (MB)
+`Trainer.fit` stopped: `max_epochs=1` reached.
+
+
+
+
+
+
+
+

Test the model

+
+
trainer.test(model)
+
+
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+       Test metric             DataLoader 0
+────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+      test_acc_step         0.10000000149011612
+────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+
+
+
+
+
+
[{'test_acc_step': 0.10000000149011612}]
+
+
+
+

Visualize

+
+
# # Start tensorboard.
+# %load_ext tensorboard
+# %tensorboard --logdir lightning_logs/
+
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/data_augmentation_mosiac.html b/nbs/data_augmentation_mosiac.html new file mode 100644 index 0000000..b31f7eb --- /dev/null +++ b/nbs/data_augmentation_mosiac.html @@ -0,0 +1,738 @@ + + + + + + + + + + + + +Kornia - Random Mosaic Augmentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Random Mosaic Augmentation

+
+
Basic
+
2D
+
Data augmentation
+
kornia.augmentation
+
+
+ +
+
+ In this tutorial we will show how we can quickly perform mosaicing using the features provided by the kornia.augmentation.RandomMosaic API. Mosaicing means taking several input images and combine their random crops into mosaic. +
+
+ + +
+ +
+
Author
+
+

Jian Shi

+
+
+ +
+
Published
+
+

August 29, 2022

+
+
+ + +
+ + +
+ +

Open in google colab

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+

Install and get data

+

We install Kornia and some dependencies, and download a simple data sample

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://raw.githubusercontent.com/kornia/data/main/panda.jpg"
+download_image(url)
+
+
'panda.jpg'
+
+
+
+
import kornia as K
+import torch
+from matplotlib import pyplot as plt
+
+
+def plot(img, box):
+    img_vis = img.clone()
+    img_vis = K.utils.draw_rectangle(img_vis, box, color=torch.tensor([255, 0, 0]))
+    plt.imshow(K.tensor_to_image(img_vis))
+    plt.show()
+
+
+
img1 = K.io.load_image("panda.jpg", K.io.ImageLoadType.RGB32)
+img2 = K.augmentation.RandomEqualize(p=1.0, keepdim=True)(img1)
+img3 = K.augmentation.RandomInvert(p=1.0, keepdim=True)(img1)
+img4 = K.augmentation.RandomChannelShuffle(p=1.0, keepdim=True)(img1)
+
+plt.figure(figsize=(21, 9))
+plt.imshow(K.tensor_to_image(torch.cat([img1, img2, img3, img4], dim=-1)))
+plt.show()
+
+

+
+
+
+
import kornia as K
+import torch
+from kornia.augmentation import RandomMosaic
+
+x = K.core.concatenate(
+    [
+        K.geometry.resize(img1[None], (224, 224)),
+        K.geometry.resize(img2[None], (224, 224)),
+        K.geometry.resize(img3[None], (224, 224)),
+        K.geometry.resize(img4[None], (224, 224)),
+    ]
+)
+
+boxes = torch.tensor(
+    [
+        [
+            [70.0, 5, 150, 100],  # head
+            [60, 180, 175, 220],  # feet
+        ]
+    ]
+).repeat(4, 1, 1)
+
+aug = RandomMosaic(
+    (224, 224), mosaic_grid=(2, 2), start_ratio_range=(0.3, 0.5), p=1.0, min_bbox_size=300, data_keys=["input", "bbox_xyxy"]
+)
+
+y, y1 = aug(x, boxes)
+
+plot(y[:1], y1[:1])
+
+
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
+
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/data_augmentation_mosiac_files/figure-html/cell-5-output-1.png b/nbs/data_augmentation_mosiac_files/figure-html/cell-5-output-1.png new file mode 100644 index 0000000..2bb7c42 Binary files /dev/null and b/nbs/data_augmentation_mosiac_files/figure-html/cell-5-output-1.png differ diff --git a/nbs/data_augmentation_mosiac_files/figure-html/cell-6-output-2.png b/nbs/data_augmentation_mosiac_files/figure-html/cell-6-output-2.png new file mode 100644 index 0000000..ba37d4b Binary files /dev/null and b/nbs/data_augmentation_mosiac_files/figure-html/cell-6-output-2.png differ diff --git a/nbs/data_augmentation_segmentation.html b/nbs/data_augmentation_segmentation.html new file mode 100644 index 0000000..6c64d35 --- /dev/null +++ b/nbs/data_augmentation_segmentation.html @@ -0,0 +1,780 @@ + + + + + + + + + + + + +Kornia - Data Augmentation Semantic Segmentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Data Augmentation Semantic Segmentation

+
+
Basic
+
2D
+
Segmentation
+
Data augmentation
+
kornia.augmentation
+
+
+ +
+
+ In this tutorial we will show how we can quickly perform data augmentation for semantic segmentation using the kornia.augmentation API. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

March 27, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+

Install and get data

+

We install Kornia and some dependencies, and download a simple data sample

+
+
%%capture
+%matplotlib inline
+!pip install kornia
+!pip install kornia-rs
+!pip install opencv-python
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "http://www.zemris.fer.hr/~ssegvic/multiclod/images/causevic16semseg3.png"
+download_image(url)
+
+
'causevic16semseg3.png'
+
+
+
+
# import the libraries
+import kornia as K
+import matplotlib.pyplot as plt
+import torch
+import torch.nn as nn
+
+
+
+

Define Augmentation pipeline

+

We define a class to define our augmentation API using an nn.Module

+
+
class MyAugmentation(nn.Module):
+    def __init__(self):
+        super().__init__()
+        # we define and cache our operators as class members
+        self.k1 = K.augmentation.ColorJitter(0.15, 0.25, 0.25, 0.25)
+        self.k2 = K.augmentation.RandomAffine([-45.0, 45.0], [0.0, 0.15], [0.5, 1.5], [0.0, 0.15])
+
+    def forward(self, img: torch.Tensor, mask: torch.Tensor) -> torch.Tensor:
+        # 1. apply color only in image
+        # 2. apply geometric tranform
+        img_out = self.k2(self.k1(img))
+
+        # 3. infer geometry params to mask
+        # TODO: this will change in future so that no need to infer params
+        mask_out = self.k2(mask, self.k2._params)
+
+        return img_out, mask_out
+
+

Load the data and apply the transforms

+
+
def load_data(data_path: str) -> torch.Tensor:
+    data_t: torch.Tensor = K.io.load_image(data_path, K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+    img, labels = data_t[..., :571], data_t[..., 572:]
+    return img, labels
+
+
+# load data (B, C, H, W)
+img, labels = load_data("causevic16semseg3.png")
+
+# create augmentation instance
+aug = MyAugmentation()
+
+# apply the augmenation pipelone to our batch of data
+img_aug, labels_aug = aug(img, labels)
+
+# visualize
+img_out = torch.cat([img, labels], dim=-1)
+plt.imshow(K.tensor_to_image(img_out))
+plt.axis("off")
+
+# generate several samples
+num_samples: int = 10
+
+for img_id in range(num_samples):
+    # generate data
+    img_aug, labels_aug = aug(img, labels)
+    img_out = torch.cat([img_aug, labels_aug], dim=-1)
+
+    # save data
+    plt.figure()
+    plt.imshow(K.tensor_to_image(img_out))
+    plt.axis("off")
+    # plt.savefig(f"img_{img_id}.png", bbox_inches="tight")
+    plt.show()
+
+

+
+
+

+
+
+

+
+
+

+
+
+

+
+
+

+
+
+

+
+
+

+
+
+

+
+
+

+
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-1.png b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-1.png new file mode 100644 index 0000000..e061e96 Binary files /dev/null and b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-1.png differ diff --git a/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-10.png b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-10.png new file mode 100644 index 0000000..3a93cac Binary files /dev/null and b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-10.png differ diff --git a/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-11.png b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-11.png new file mode 100644 index 0000000..e54ecf7 Binary files /dev/null and b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-11.png differ diff --git a/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-2.png b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-2.png new file mode 100644 index 0000000..c9c183a Binary files /dev/null and b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-2.png differ diff --git a/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-3.png b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-3.png new file mode 100644 index 0000000..e084087 Binary files /dev/null and b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-3.png differ diff --git a/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-4.png b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-4.png new file mode 100644 index 0000000..822694b Binary files /dev/null and b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-4.png differ diff --git a/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-5.png b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-5.png new file mode 100644 index 0000000..707a2e8 Binary files /dev/null and b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-5.png differ diff --git a/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-6.png b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-6.png new file mode 100644 index 0000000..26d74c5 Binary files /dev/null and b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-6.png differ diff --git a/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-7.png b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-7.png new file mode 100644 index 0000000..d0222fa Binary files /dev/null and b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-7.png differ diff --git a/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-8.png b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-8.png new file mode 100644 index 0000000..aaee1bc Binary files /dev/null and b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-8.png differ diff --git a/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-9.png b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-9.png new file mode 100644 index 0000000..d725e6c Binary files /dev/null and b/nbs/data_augmentation_segmentation_files/figure-html/cell-6-output-9.png differ diff --git a/nbs/data_augmentation_sequential.html b/nbs/data_augmentation_sequential.html new file mode 100644 index 0000000..2e1b7c7 --- /dev/null +++ b/nbs/data_augmentation_sequential.html @@ -0,0 +1,767 @@ + + + + + + + + + + + + +Kornia - Augmentation Sequential + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Augmentation Sequential

+
+
Basic
+
2D
+
Data augmentation
+
kornia.augmentation
+
+
+ +
+
+ In this tutorial we will show how we can quickly perform data augmentation for various tasks (segmentation, detection, regression) using the features provided by the kornia.augmentation.AugmentationSequential API. +
+
+ + +
+ +
+
Author
+
+

Jian Shi

+
+
+ +
+
Published
+
+

May 30, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+

Install and get data

+

We install Kornia and some dependencies, and download a simple data sample

+
+
!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://raw.githubusercontent.com/kornia/data/main/panda.jpg"
+download_image(url)
+
+
+
import cv2
+import kornia as K
+import numpy as np
+import torch
+from kornia.augmentation import AugmentationSequential
+from kornia.geometry import bbox_to_mask
+from matplotlib import pyplot as plt
+
+
+
def plot_resulting_image(img, bbox, keypoints, mask):
+    img = img * mask
+    img_array = K.tensor_to_image(img.mul(255).byte()).copy()
+    img_draw = cv2.polylines(img_array, bbox.numpy(), isClosed=True, color=(255, 0, 0))
+    for k in keypoints[0]:
+        img_draw = cv2.circle(img_draw, tuple(k.numpy()[:2]), radius=6, color=(255, 0, 0), thickness=-1)
+    return img_draw
+
+
+img_tensor = K.io.load_image("panda.jpg", K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+h, w = img_tensor.shape[-2:]
+
+plt.axis("off")
+plt.imshow(K.tensor_to_image(img_tensor))
+plt.show()
+
+

+
+
+
+
+

Define Augmentation Sequential and Different Labels

+
+
aug_list = AugmentationSequential(
+    K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=1.0),
+    K.augmentation.RandomAffine(360, [0.1, 0.1], [0.7, 1.2], [30.0, 50.0], p=1.0),
+    K.augmentation.RandomPerspective(0.5, p=1.0),
+    data_keys=["input", "bbox", "keypoints", "mask"],
+    same_on_batch=False,
+)
+
+bbox = torch.tensor([[[[355, 10], [660, 10], [660, 250], [355, 250]]]])
+keypoints = torch.tensor([[[465, 115], [545, 116]]])
+mask = bbox_to_mask(torch.tensor([[[155, 0], [900, 0], [900, 400], [155, 400]]]), w, h)[None].float()
+
+img_out = plot_resulting_image(img_tensor, bbox[0], keypoints, mask[0])
+
+plt.axis("off")
+plt.imshow(img_out)
+plt.show()
+
+

+
+
+
+
+

Forward Computations

+
+
out_tensor = aug_list(img_tensor, bbox.float(), keypoints.float(), mask)
+img_out = plot_resulting_image(
+    out_tensor[0][0],
+    out_tensor[1].int(),
+    out_tensor[2].int(),
+    out_tensor[3][0],
+)
+
+plt.axis("off")
+plt.imshow(img_out)
+plt.show()
+
+

+
+
+
+
+

Inverse Transformations

+
+
out_tensor_inv = aug_list.inverse(*out_tensor)
+img_out = plot_resulting_image(
+    out_tensor_inv[0][0],
+    out_tensor_inv[1].int(),
+    out_tensor_inv[2].int(),
+    out_tensor_inv[3][0],
+)
+
+plt.axis("off")
+plt.imshow(img_out)
+plt.show()
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/data_augmentation_sequential_files/figure-html/cell-5-output-1.png b/nbs/data_augmentation_sequential_files/figure-html/cell-5-output-1.png new file mode 100644 index 0000000..928c3db Binary files /dev/null and b/nbs/data_augmentation_sequential_files/figure-html/cell-5-output-1.png differ diff --git a/nbs/data_augmentation_sequential_files/figure-html/cell-6-output-1.png b/nbs/data_augmentation_sequential_files/figure-html/cell-6-output-1.png new file mode 100644 index 0000000..2974db4 Binary files /dev/null and b/nbs/data_augmentation_sequential_files/figure-html/cell-6-output-1.png differ diff --git a/nbs/data_augmentation_sequential_files/figure-html/cell-7-output-1.png b/nbs/data_augmentation_sequential_files/figure-html/cell-7-output-1.png new file mode 100644 index 0000000..4cb91ed Binary files /dev/null and b/nbs/data_augmentation_sequential_files/figure-html/cell-7-output-1.png differ diff --git a/nbs/data_augmentation_sequential_files/figure-html/cell-8-output-1.png b/nbs/data_augmentation_sequential_files/figure-html/cell-8-output-1.png new file mode 100644 index 0000000..27619d8 Binary files /dev/null and b/nbs/data_augmentation_sequential_files/figure-html/cell-8-output-1.png differ diff --git a/nbs/data_patch_sequential.html b/nbs/data_patch_sequential.html new file mode 100644 index 0000000..f764050 --- /dev/null +++ b/nbs/data_patch_sequential.html @@ -0,0 +1,762 @@ + + + + + + + + + + + + +Kornia - Patch Sequential + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Patch Sequential

+
+
Intermediate
+
2D
+
Data augmentation
+
Patches
+
kornia.augmentation
+
+
+ +
+
+ In this tutorial we will show how we can quickly perform patch processing using the features provided by the kornia.augmentation.PatchSequential API. +
+
+ + +
+ +
+
Author
+
+

Jian Shi

+
+
+ +
+
Published
+
+

June 11, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+

Install and get data

+

We install Kornia and some dependencies, and download a simple data sample

+
+
!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://raw.githubusercontent.com/kornia/data/main/panda.jpg"
+download_image(url)
+
+
'panda.jpg'
+
+
+
+
import kornia as K
+import torch
+from kornia.augmentation import ImageSequential, PatchSequential
+from matplotlib import pyplot as plt
+
+img_tensor = K.io.load_image("panda.jpg", K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+h, w = img_tensor.shape[2:]
+
+plt.imshow(K.tensor_to_image(img_tensor))
+plt.axis("off")
+plt.show()
+
+

+
+
+
+
+

Patch Augmentation Sequential with patchwise_apply=True

+

patchwise_apply is a feature that used to define unique processing pipeline for each patch location. If patchwise_apply=True, the number of pipelines defined must be as same as the number of patches in an image.

+
+
pseq = PatchSequential(
+    ImageSequential(
+        K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=0.5),
+        K.augmentation.RandomPerspective(0.2, p=0.5),
+        K.augmentation.RandomSolarize(0.1, 0.1, p=0.5),
+    ),
+    K.augmentation.RandomAffine(15, [0.1, 0.1], [0.7, 1.2], [0.0, 20.0], p=0.5),
+    K.augmentation.RandomPerspective(0.2, p=0.5),
+    ImageSequential(
+        K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=0.5),
+        K.augmentation.RandomPerspective(0.2, p=0.5),
+        K.augmentation.RandomSolarize(0.1, 0.1, p=0.5),
+    ),
+    K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=0.5),
+    K.augmentation.RandomAffine(15, [0.1, 0.1], [0.7, 1.2], [0.0, 20.0], p=0.5),
+    K.augmentation.RandomPerspective(0.2, p=0.5),
+    K.augmentation.RandomSolarize(0.1, 0.1, p=0.5),
+    K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=0.5),
+    K.augmentation.RandomAffine(15, [0.1, 0.1], [0.7, 1.2], [0.0, 20.0], p=0.5),
+    ImageSequential(
+        K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=0.5),
+        K.augmentation.RandomPerspective(0.2, p=0.5),
+        K.augmentation.RandomSolarize(0.1, 0.1, p=0.5),
+    ),
+    K.augmentation.RandomSolarize(0.1, 0.1, p=0.5),
+    K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=0.5),
+    K.augmentation.RandomAffine(15, [0.1, 0.1], [0.7, 1.2], [0.0, 20.0], p=0.5),
+    K.augmentation.RandomPerspective(0.2, p=0.5),
+    K.augmentation.RandomSolarize(0.1, 0.1, p=0.5),
+    patchwise_apply=True,
+    same_on_batch=True,
+)
+out_tensor = pseq(img_tensor.repeat(2, 1, 1, 1))
+
+plt.figure(figsize=(21, 9))
+plt.imshow(K.tensor_to_image(torch.cat([out_tensor[0], out_tensor[1]], dim=2)))
+plt.axis("off")
+plt.show()
+
+

+
+
+
+
+

Patch Augmentation Sequential with patchwise_apply=False

+

If patchwise_apply=False, all the args will be combined and applied as one pipeline for each patch.

+
+
pseq = PatchSequential(
+    K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=0.75),
+    K.augmentation.RandomAffine(15, [0.1, 0.1], [0.7, 1.2], [0.0, 20.0], p=0.5),
+    patchwise_apply=False,
+    same_on_batch=False,
+)
+out_tensor = pseq(img_tensor.repeat(2, 1, 1, 1))
+
+plt.figure(figsize=(21, 9))
+plt.imshow(K.tensor_to_image(torch.cat([out_tensor[0], out_tensor[1]], dim=2)))
+plt.axis("off")
+plt.show()
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/data_patch_sequential_files/figure-html/cell-4-output-1.png b/nbs/data_patch_sequential_files/figure-html/cell-4-output-1.png new file mode 100644 index 0000000..928c3db Binary files /dev/null and b/nbs/data_patch_sequential_files/figure-html/cell-4-output-1.png differ diff --git a/nbs/data_patch_sequential_files/figure-html/cell-5-output-1.png b/nbs/data_patch_sequential_files/figure-html/cell-5-output-1.png new file mode 100644 index 0000000..02a7366 Binary files /dev/null and b/nbs/data_patch_sequential_files/figure-html/cell-5-output-1.png differ diff --git a/nbs/data_patch_sequential_files/figure-html/cell-6-output-1.png b/nbs/data_patch_sequential_files/figure-html/cell-6-output-1.png new file mode 100644 index 0000000..fdf871d Binary files /dev/null and b/nbs/data_patch_sequential_files/figure-html/cell-6-output-1.png differ diff --git a/nbs/descriptors_matching.html b/nbs/descriptors_matching.html new file mode 100644 index 0000000..515b9ab --- /dev/null +++ b/nbs/descriptors_matching.html @@ -0,0 +1,908 @@ + + + + + + + + + + + + +Kornia - Image matching example with kornia local features: TFeat, MKD, OriNet, HyNet and OpenCV detector + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Image matching example with kornia local features: TFeat, MKD, OriNet, HyNet and OpenCV detector

+
+
Intermediate
+
Local features
+
kornia.feature
+
+
+ +
+
+ In this tutorial we will show how we can perform image matching using kornia local features +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

August 28, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+

It is possible to use OpenCV local features, such as SIFT with kornia via kornia_moons library.

+

First, we will install everything needed:

+ +
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+!pip install kornia_moons
+!pip install opencv-python --upgrade
+
+

Now let’s download an image pair

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url_a = "https://github.com/kornia/data/raw/main/matching/kn_church-2.jpg"
+url_b = "https://github.com/kornia/data/raw/main/matching/kn_church-8.jpg"
+download_image(url_a)
+download_image(url_b)
+
+
'kn_church-8.jpg'
+
+
+

First, we will define image matching pipeline with OpenCV SIFT features. We will also use kornia for the state-of-the-art match filtering – Lowe ratio + mutual nearest neighbor check.

+
+

Imports

+
+
import cv2
+import kornia as K
+import kornia.feature as KF
+import matplotlib.pyplot as plt
+import numpy as np
+import torch
+from kornia_moons.feature import *
+from kornia_moons.viz import *
+
+
+
+

Using OpenCV SIFT as is and converting it manually

+
+
def sift_matching(fname1, fname2):
+    img1 = cv2.cvtColor(cv2.imread(fname1), cv2.COLOR_BGR2RGB)
+    print(img1.shape)
+    img2 = cv2.cvtColor(cv2.imread(fname2), cv2.COLOR_BGR2RGB)
+
+    # OpenCV SIFT
+    sift = cv2.SIFT_create(8000)
+    kps1, descs1 = sift.detectAndCompute(img1, None)
+    kps2, descs2 = sift.detectAndCompute(img2, None)
+
+    # Converting to kornia for matching via AdaLAM
+    lafs1 = laf_from_opencv_SIFT_kpts(kps1)
+    lafs2 = laf_from_opencv_SIFT_kpts(kps2)
+    dists, idxs = KF.match_adalam(
+        torch.from_numpy(descs1), torch.from_numpy(descs2), lafs1, lafs2, hw1=img1.shape[:2], hw2=img2.shape[:2]
+    )
+
+    # Converting back to kornia via to use OpenCV MAGSAC++
+    tentatives = cv2_matches_from_kornia(dists, idxs)
+    src_pts = np.float32([kps1[m.queryIdx].pt for m in tentatives]).reshape(-1, 2)
+    dst_pts = np.float32([kps2[m.trainIdx].pt for m in tentatives]).reshape(-1, 2)
+
+    F, inliers_mask = cv2.findFundamentalMat(src_pts, dst_pts, cv2.USAC_MAGSAC, 0.25, 0.999, 100000)
+    # Drawing matches using kornia_moons
+    draw_LAF_matches(
+        lafs1,
+        lafs2,
+        idxs,
+        img1,
+        img2,
+        inliers_mask.astype(bool).reshape(-1),
+        draw_dict={"inlier_color": (0.2, 1, 0.2), "tentative_color": None, "feature_color": None, "vertical": False},
+    )
+    print(f"{inliers_mask.sum()} inliers found")
+    return
+
+
+
fname1 = "kn_church-2.jpg"
+fname2 = "kn_church-8.jpg"
+sift_matching(fname1, fname2)
+
+
11 inliers found
+
+
+

+
+
+
+
+

Using OpenCV SIFT with kornia matcher

+

Now we need to define a function to feed the OpenCV keypoints into local descriptors from kornia. Luckily, that is easy with the help of kornia_moons.

+
+
def get_matching_kpts(lafs1, lafs2, idxs):
+    src_pts = KF.get_laf_center(lafs1).view(-1, 2)[idxs[:, 0]].detach().cpu().numpy()
+    dst_pts = KF.get_laf_center(lafs2).view(-1, 2)[idxs[:, 1]].detach().cpu().numpy()
+    return src_pts, dst_pts
+
+
+def sift_korniadesc_matching(fname1, fname2, descriptor):
+    timg1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+    timg2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+
+    sift = OpenCVDetectorKornia(cv2.SIFT_create(8000))
+    local_feature = KF.LocalFeature(sift, KF.LAFDescriptor(descriptor))
+
+    lafs1, resps1, descs1 = local_feature(K.color.rgb_to_grayscale(timg1))
+    lafs2, resps2, descs2 = local_feature(K.color.rgb_to_grayscale(timg2))
+
+    dists, idxs = KF.match_adalam(descs1[0], descs2[0], lafs1, lafs2, hw1=timg1.shape[2:], hw2=timg2.shape[2:])
+
+    src_pts, dst_pts = get_matching_kpts(lafs1, lafs2, idxs)
+    F, inliers_mask = cv2.findFundamentalMat(src_pts, dst_pts, cv2.USAC_MAGSAC, 0.25, 0.999, 100000)
+    draw_LAF_matches(
+        lafs1,
+        lafs2,
+        idxs,
+        K.tensor_to_image(timg1),
+        K.tensor_to_image(timg2),
+        inliers_mask.astype(bool),
+        draw_dict={"inlier_color": (0.2, 1, 0.2), "tentative_color": None, "feature_color": None, "vertical": False},
+    )
+    print(f"{inliers_mask.sum()} inliers found")
+
+

Now let’s try kornia new descriptors – MKD and TFeat. MKD is one of the best handcrafted local feature descriptors, presented in IJCV 2018 paper “Understanding and Improving Kernel Local Descriptors”.

+
+
mkd = KF.MKDDescriptor(32)
+with torch.inference_mode():
+    sift_korniadesc_matching(fname1, fname2, mkd)
+
+
12 inliers found
+
+
+

+
+
+

Result seems 2 inliers better than with SIFTs. Let’s try TFeat - lightweight deep learning-based descriptor from BMVC 2016 paper “Learning local feature descriptors with triplets and shallow convolutional neural networks

+
+
tfeat = KF.TFeat(True)
+with torch.inference_mode():
+    sift_korniadesc_matching(fname1, fname2, tfeat)
+
+
22 inliers found
+
+
+

+
+
+
+
+

Good old HardNet

+

In the worst-case we can always fall back to the HardNet – more robust, but also slower than TFeat and MKD, descriptor

+
+
device = torch.device("cpu")
+hardnet = KF.HardNet(True).eval()
+with torch.inference_mode():
+    sift_korniadesc_matching(fname1, fname2, hardnet)
+
+
26 inliers found
+
+
+

+
+
+
+
+

AffNet

+

We haven’t done yet! SIFT detector is a great tool, but we can improve it by using deep learned affine shape estimation – AffNet. You can do it, using a single function wrapper - OpenCVDetectorWithAffNetKornia.

+
+
def siftaffnet_korniadesc_matching(fname1, fname2, descriptor):
+    timg1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+    timg2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+
+    # Magic is here
+    sift = OpenCVDetectorWithAffNetKornia(cv2.SIFT_create(8000))
+
+    local_feature = KF.LocalFeature(sift, KF.LAFDescriptor(descriptor))
+    with torch.inference_mode():
+        lafs1, resps1, descs1 = local_feature(K.color.rgb_to_grayscale(timg1))
+        lafs2, resps2, descs2 = local_feature(K.color.rgb_to_grayscale(timg2))
+        dists, idxs = KF.match_adalam(descs1[0], descs2[0], lafs1, lafs2, hw1=timg1.shape[2:], hw2=timg2.shape[2:])
+
+    src_pts, dst_pts = get_matching_kpts(lafs1, lafs2, idxs)
+
+    F, inliers_mask = cv2.findFundamentalMat(src_pts, dst_pts, cv2.USAC_MAGSAC, 0.25, 0.999, 100000)
+    draw_LAF_matches(
+        lafs1,
+        lafs2,
+        idxs,
+        K.tensor_to_image(timg1),
+        K.tensor_to_image(timg2),
+        inliers_mask.astype(bool),
+        draw_dict={"inlier_color": (0.2, 1, 0.2), "tentative_color": None, "feature_color": None, "vertical": False},
+    )
+    print(f"{inliers_mask.sum()} inliers found")
+
+
+
siftaffnet_korniadesc_matching(fname1, fname2, hardnet)
+
+
39 inliers found
+
+
+

+
+
+
+
+

HyNet

+
+
siftaffnet_korniadesc_matching(fname1, fname2, KF.HyNet(True).eval())
+
+
47 inliers found
+
+
+

+
+
+
+
+

SOSNet

+
+
siftaffnet_korniadesc_matching(fname1, fname2, KF.SOSNet(True).eval())
+
+
48 inliers found
+
+
+

+
+
+
+
+

HardNet8

+
+
siftaffnet_korniadesc_matching(fname1, fname2, KF.HardNet8(True).eval())
+
+
38 inliers found
+
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/descriptors_matching_files/figure-html/cell-10-output-2.png b/nbs/descriptors_matching_files/figure-html/cell-10-output-2.png new file mode 100644 index 0000000..aee9cce Binary files /dev/null and b/nbs/descriptors_matching_files/figure-html/cell-10-output-2.png differ diff --git a/nbs/descriptors_matching_files/figure-html/cell-11-output-2.png b/nbs/descriptors_matching_files/figure-html/cell-11-output-2.png new file mode 100644 index 0000000..fcad7bf Binary files /dev/null and b/nbs/descriptors_matching_files/figure-html/cell-11-output-2.png differ diff --git a/nbs/descriptors_matching_files/figure-html/cell-13-output-2.png b/nbs/descriptors_matching_files/figure-html/cell-13-output-2.png new file mode 100644 index 0000000..032b747 Binary files /dev/null and b/nbs/descriptors_matching_files/figure-html/cell-13-output-2.png differ diff --git a/nbs/descriptors_matching_files/figure-html/cell-14-output-2.png b/nbs/descriptors_matching_files/figure-html/cell-14-output-2.png new file mode 100644 index 0000000..c970875 Binary files /dev/null and b/nbs/descriptors_matching_files/figure-html/cell-14-output-2.png differ diff --git a/nbs/descriptors_matching_files/figure-html/cell-15-output-2.png b/nbs/descriptors_matching_files/figure-html/cell-15-output-2.png new file mode 100644 index 0000000..1057ab7 Binary files /dev/null and b/nbs/descriptors_matching_files/figure-html/cell-15-output-2.png differ diff --git a/nbs/descriptors_matching_files/figure-html/cell-16-output-2.png b/nbs/descriptors_matching_files/figure-html/cell-16-output-2.png new file mode 100644 index 0000000..87246cb Binary files /dev/null and b/nbs/descriptors_matching_files/figure-html/cell-16-output-2.png differ diff --git a/nbs/descriptors_matching_files/figure-html/cell-7-output-2.png b/nbs/descriptors_matching_files/figure-html/cell-7-output-2.png new file mode 100644 index 0000000..c5c959c Binary files /dev/null and b/nbs/descriptors_matching_files/figure-html/cell-7-output-2.png differ diff --git a/nbs/descriptors_matching_files/figure-html/cell-9-output-2.png b/nbs/descriptors_matching_files/figure-html/cell-9-output-2.png new file mode 100644 index 0000000..84ad31a Binary files /dev/null and b/nbs/descriptors_matching_files/figure-html/cell-9-output-2.png differ diff --git a/nbs/extract_combine_patches.html b/nbs/extract_combine_patches.html new file mode 100644 index 0000000..2b6ac26 --- /dev/null +++ b/nbs/extract_combine_patches.html @@ -0,0 +1,853 @@ + + + + + + + + + + + + +Kornia - Extracting and Combining Tensor Patches + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Extracting and Combining Tensor Patches

+
+
Basic
+
Patches
+
kornia.contrib
+
+
+ +
+
+ In this tutorial we will show how you can extract and combine tensor patches using kornia +
+
+ + +
+ +
+
Author
+
+

Ashwin Nair

+
+
+ +
+
Published
+
+

March 7, 2022

+
+
+ + +
+ + +
+ +

Open in google colab

+
+

Install and get data

+
+
%%capture
+%matplotlib inline
+# Install latest kornia
+!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://raw.githubusercontent.com/kornia/data/main/panda.jpg"
+download_image(url)
+
+
'panda.jpg'
+
+
+
+
import kornia as K
+import matplotlib.pyplot as plt
+import torch
+from kornia.contrib import (
+    CombineTensorPatches,
+    ExtractTensorPatches,
+    combine_tensor_patches,
+    compute_padding,
+    extract_tensor_patches,
+)
+
+
+
+

Using Modules

+
+
h, w = 8, 8
+win = 4
+pad = 2
+
+image = torch.randn(2, 3, h, w)
+print(image.shape)
+tiler = ExtractTensorPatches(window_size=win, stride=win, padding=pad)
+merger = CombineTensorPatches(original_size=(h, w), window_size=win, stride=win, unpadding=pad)
+image_tiles = tiler(image)
+print(image_tiles.shape)
+new_image = merger(image_tiles)
+print(new_image.shape)
+assert torch.allclose(image, new_image)
+
+
torch.Size([2, 3, 8, 8])
+torch.Size([2, 9, 3, 4, 4])
+torch.Size([2, 3, 8, 8])
+
+
+
+
+

Using Functions

+
+
h, w = 8, 8
+win = 4
+pad = 2
+
+image = torch.randn(1, 1, h, w)
+print(image.shape)
+patches = extract_tensor_patches(image, window_size=win, stride=win, padding=pad)
+print(patches.shape)
+restored_img = combine_tensor_patches(patches, original_size=(h, w), window_size=win, stride=win, unpadding=pad)
+print(restored_img.shape)
+assert torch.allclose(image, restored_img)
+
+
torch.Size([1, 1, 8, 8])
+torch.Size([1, 9, 1, 4, 4])
+torch.Size([1, 1, 8, 8])
+
+
+
+
+

Padding

+

All parameters of extract and combine functions accept a single int or tuple of two ints. Since padding is an integral part of these functions, it’s important to note the following:

+
    +
  • If padding is p -> it means both height and width are padded by 2*p
  • +
  • If padding is (ph, pw) -> it means height is padded by 2*ph and width is padded by 2*pw
  • +
+

It is recommended to use the existing function compute_padding to ensure the required padding is added.

+
+

Examples

+
+
def extract_and_combine(image, window_size, padding):
+    h, w = image.shape[-2:]
+    tiler = ExtractTensorPatches(window_size=window_size, stride=window_size, padding=padding)
+    merger = CombineTensorPatches(original_size=(h, w), window_size=window_size, stride=window_size, unpadding=padding)
+    image_tiles = tiler(image)
+    print(f"Shape of tensor patches = {image_tiles.shape}")
+    merged_image = merger(image_tiles)
+    print(f"Shape of merged image = {merged_image.shape}")
+    assert torch.allclose(image, merged_image)
+    return merged_image
+
+
+
image = torch.randn(2, 3, 9, 9)
+_ = extract_and_combine(image, window_size=(4, 4), padding=(2, 2))
+
+
Shape of tensor patches = torch.Size([2, 9, 3, 4, 4])
+Shape of merged image = torch.Size([2, 3, 9, 9])
+
+
+

These functions also work with rectangular images

+
+
rect_image = torch.randn(1, 1, 8, 6)
+print(rect_image.shape)
+
+
torch.Size([1, 1, 8, 6])
+
+
+
+
restored_image = extract_and_combine(rect_image, window_size=(4, 4), padding=compute_padding((8, 6), 4))
+
+
Shape of tensor patches = torch.Size([1, 4, 1, 4, 4])
+Shape of merged image = torch.Size([1, 1, 8, 6])
+
+
+

Recall that when padding is a tuple of ints (ph, pw), the height and width are padded by 2*ph and 2*pw respectively.

+
+
# Confirm that the original image and restored image are the same
+assert (restored_image == rect_image).all()
+
+

Let’s now visualize how extraction and combining works.

+
+
# Load sample image
+img_tensor = K.io.load_image("panda.jpg", K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+h, w = img_tensor.shape[-2:]
+print(f"Shape of image = {img_tensor.shape}")
+
+plt.axis("off")
+plt.imshow(K.tensor_to_image(img_tensor))
+plt.show()
+
+
Shape of image = torch.Size([1, 3, 510, 1020])
+
+
+

+
+
+

We will use window_size = (400, 400) with stride = 200 to extract 15 overlapping tiles of shape (400, 400) and visualize them.

+
+
# Set window size
+win = 400
+# Set stride
+stride = 200
+# Calculate required padding
+pad = compute_padding(original_size=(510, 1020), window_size=win)
+
+tiler = ExtractTensorPatches(window_size=win, stride=stride, padding=pad)
+image_tiles = tiler(img_tensor)
+print(f"Shape of image tiles = {image_tiles.shape}")
+
+
Shape of image tiles = torch.Size([1, 15, 3, 400, 400])
+
+
+
+
# Create the plot
+fig, axs = plt.subplots(5, 3, figsize=(8, 8))
+axs = axs.ravel()
+
+for i in range(len(image_tiles[0])):
+    axs[i].axis("off")
+    axs[i].imshow(K.tensor_to_image(image_tiles[0][i]))
+
+plt.show()
+
+

+
+
+

Finally, let’s combine the patches and visualize the resulting image

+
+
merger = CombineTensorPatches(original_size=(h, w), window_size=win, stride=stride, unpadding=pad)
+merged_image = merger(image_tiles)
+print(f"Shape of restored image = {merged_image.shape}")
+
+plt.imshow(K.tensor_to_image(merged_image[0]))
+plt.axis("off")
+plt.show()
+
+
Shape of restored image = torch.Size([1, 3, 510, 1020])
+
+
+

+
+
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/extract_combine_patches_files/figure-html/cell-12-output-2.png b/nbs/extract_combine_patches_files/figure-html/cell-12-output-2.png new file mode 100644 index 0000000..8525d5c Binary files /dev/null and b/nbs/extract_combine_patches_files/figure-html/cell-12-output-2.png differ diff --git a/nbs/extract_combine_patches_files/figure-html/cell-14-output-1.png b/nbs/extract_combine_patches_files/figure-html/cell-14-output-1.png new file mode 100644 index 0000000..54a5430 Binary files /dev/null and b/nbs/extract_combine_patches_files/figure-html/cell-14-output-1.png differ diff --git a/nbs/extract_combine_patches_files/figure-html/cell-15-output-2.png b/nbs/extract_combine_patches_files/figure-html/cell-15-output-2.png new file mode 100644 index 0000000..8525d5c Binary files /dev/null and b/nbs/extract_combine_patches_files/figure-html/cell-15-output-2.png differ diff --git a/nbs/face_detection.html b/nbs/face_detection.html new file mode 100644 index 0000000..8bf489c --- /dev/null +++ b/nbs/face_detection.html @@ -0,0 +1,769 @@ + + + + + + + + + + + + +Kornia - Face Detection and blurring + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Face Detection and blurring

+
+
Intermediate
+
Face detection
+
Blur
+
kornia.contrib
+
+
+ +
+
+ In this tutorial we will show how to use the Kornia Face Detection and how we can blurring these detected faces. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

November 30, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://raw.githubusercontent.com/kornia/data/main/crowd.jpg"
+download_image(url)
+
+

Import the needed libraries

+
+
import cv2
+import kornia as K
+import matplotlib.pyplot as plt
+import numpy as np
+import torch
+from kornia.contrib import FaceDetector, FaceDetectorResult
+
+# select the device and type
+device = torch.device("cpu")  # use 'cuda:0'
+dtype = torch.float32
+
+

Read the image with kornia

+
+
# load the image (face detector expects a image in rage 0-255 (8 bits))
+img = K.io.load_image("crowd.jpg", K.io.ImageLoadType.RGB8, device=device)[None, ...].to(dtype=dtype)  # BxCxHxW
+img_vis = K.tensor_to_image(img.byte())  # to later visualize
+
+
+
plt.figure(figsize=(8, 8))
+plt.imshow(img_vis)
+plt.axis("off")
+plt.show()
+
+

+
+
+

Create the FaceDetector object and apply to the image

+
+
# create the detector and find the faces !
+face_detection = FaceDetector().to(device, dtype)
+
+with torch.no_grad():
+    dets = face_detection(img)
+
+# to decode later the detections
+dets = [FaceDetectorResult(o) for o in dets]
+
+

Create a function to crop the faces from the original image and apply blurring using the gaussian_blurd2d operator.

+

Alternatively, explore other blur operator in kornia.filters.

+
+
# blurring paramters
+k: int = 21  # kernel_size
+s: float = 35.0  # sigma
+
+
+def apply_blur_face(img: torch.Tensor, img_vis: np.ndarray, x1, y1, x2, y2):
+    # crop the face
+    roi = img[..., y1:y2, x1:x2]
+
+    # apply blurring and put back to the visualisation image
+    roi = K.filters.gaussian_blur2d(roi, (k, k), (s, s))
+    img_vis[y1:y2, x1:x2] = K.tensor_to_image(roi)
+
+

Let draw the detections and save/visualize the image

+
+
for b in dets:
+    # draw face bounding box around each detected face
+    top_left = b.top_left.int().tolist()
+    bottom_right = b.bottom_right.int().tolist()
+    scores = b.score.tolist()
+
+    for score, tp, br in zip(scores, top_left, bottom_right):
+        x1, y1 = tp
+        x2, y2 = br
+
+        if score < 0.7:
+            continue  # skip detection with low score
+        img_vis = cv2.rectangle(img_vis, (x1, y1), (x2, y2), (0, 255, 0), 2)
+
+        # blur the detected faces
+        apply_blur_face(img, img_vis, x1, y1, x2, y2)
+
+plt.figure(figsize=(8, 8))
+plt.imshow(img_vis)
+plt.axis("off")
+plt.show()
+
+

+
+
+
+

Play with the Real Time Demo

+

You can achieve 60 FPS in CPU using a standard WebCam.

+

See: https://github.com/kornia/kornia/blob/master/examples/face_detection/main_video.py

+
+
from IPython.display import YouTubeVideo
+
+YouTubeVideo("hzQroGp5FSQ")
+
+ + + +
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/face_detection_files/figure-html/cell-6-output-1.png b/nbs/face_detection_files/figure-html/cell-6-output-1.png new file mode 100644 index 0000000..bb60ac8 Binary files /dev/null and b/nbs/face_detection_files/figure-html/cell-6-output-1.png differ diff --git a/nbs/face_detection_files/figure-html/cell-9-output-1.png b/nbs/face_detection_files/figure-html/cell-9-output-1.png new file mode 100644 index 0000000..d6720ac Binary files /dev/null and b/nbs/face_detection_files/figure-html/cell-9-output-1.png differ diff --git a/nbs/filtering_edges.html b/nbs/filtering_edges.html new file mode 100644 index 0000000..0d560ac --- /dev/null +++ b/nbs/filtering_edges.html @@ -0,0 +1,779 @@ + + + + + + + + + + + + +Kornia - Edge Detection + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Edge Detection

+
+
Basic
+
Edge Detection
+
kornia.filters
+
+
+ +
+
+ In this tutorial we are going to learn how to detect edges in images with kornia.filters components. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

July 6, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+

Open in HF Spaces

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://github.com/kornia/data/raw/main/doraemon.png"
+download_image(url)
+
+
'doraemon.png'
+
+
+
+
import cv2
+import kornia as K
+import numpy as np
+import torch
+import torchvision
+from matplotlib import pyplot as plt
+
+

We use Kornia to load an image to memory represented in a torch.tensor

+
+
x_rgb: torch.Tensor = K.io.load_image("doraemon.png", K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+
+x_gray = K.color.rgb_to_grayscale(x_rgb)
+
+
+
def imshow(input: torch.Tensor):
+    out = torchvision.utils.make_grid(input, nrow=2, padding=5)
+    out_np: np.ndarray = K.utils.tensor_to_image(out)
+    plt.imshow(out_np)
+    plt.axis("off")
+    plt.show()
+
+
+
imshow(x_gray)
+
+

+
+
+
+

1st order derivates

+
+
grads: torch.Tensor = K.filters.spatial_gradient(x_gray, order=1)  # BxCx2xHxW
+grads_x = grads[:, :, 0]
+grads_y = grads[:, :, 1]
+
+
+
# Show first derivatives in x
+imshow(1.0 - grads_x.clamp(0.0, 1.0))
+
+

+
+
+
+
# Show first derivatives in y
+imshow(1.0 - grads_y.clamp(0.0, 1.0))
+
+

+
+
+
+
+

2nd order derivatives

+
+
grads: torch.Tensor = K.filters.spatial_gradient(x_gray, order=2)  # BxCx2xHxW
+grads_x = grads[:, :, 0]
+grads_y = grads[:, :, 1]
+
+
+
# Show second derivatives in x
+imshow(1.0 - grads_x.clamp(0.0, 1.0))
+
+

+
+
+
+
# Show second derivatives in y
+imshow(1.0 - grads_y.clamp(0.0, 1.0))
+
+

+
+
+
+
+

Sobel Edges

+

Once with the gradients in the two directions we can computet the Sobel edges. However, in kornia we already have it implemented.

+
+
x_sobel: torch.Tensor = K.filters.sobel(x_gray)
+imshow(1.0 - x_sobel)
+
+

+
+
+
+
+

Laplacian edges

+
+
x_laplacian: torch.Tensor = K.filters.laplacian(x_gray, kernel_size=5)
+imshow(1.0 - x_laplacian.clamp(0.0, 1.0))
+
+

+
+
+
+
+

Canny edges

+
+
x_laplacian: torch.Tensor = K.filters.canny(x_gray)[0]
+imshow(1.0 - x_laplacian.clamp(0.0, 1.0))
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/filtering_edges_files/figure-html/cell-10-output-1.png b/nbs/filtering_edges_files/figure-html/cell-10-output-1.png new file mode 100644 index 0000000..8905f7d Binary files /dev/null and b/nbs/filtering_edges_files/figure-html/cell-10-output-1.png differ diff --git a/nbs/filtering_edges_files/figure-html/cell-12-output-1.png b/nbs/filtering_edges_files/figure-html/cell-12-output-1.png new file mode 100644 index 0000000..7f8186f Binary files /dev/null and b/nbs/filtering_edges_files/figure-html/cell-12-output-1.png differ diff --git a/nbs/filtering_edges_files/figure-html/cell-13-output-1.png b/nbs/filtering_edges_files/figure-html/cell-13-output-1.png new file mode 100644 index 0000000..f4c1cdd Binary files /dev/null and b/nbs/filtering_edges_files/figure-html/cell-13-output-1.png differ diff --git a/nbs/filtering_edges_files/figure-html/cell-14-output-1.png b/nbs/filtering_edges_files/figure-html/cell-14-output-1.png new file mode 100644 index 0000000..da29b0c Binary files /dev/null and b/nbs/filtering_edges_files/figure-html/cell-14-output-1.png differ diff --git a/nbs/filtering_edges_files/figure-html/cell-15-output-1.png b/nbs/filtering_edges_files/figure-html/cell-15-output-1.png new file mode 100644 index 0000000..c9a5ed0 Binary files /dev/null and b/nbs/filtering_edges_files/figure-html/cell-15-output-1.png differ diff --git a/nbs/filtering_edges_files/figure-html/cell-16-output-1.png b/nbs/filtering_edges_files/figure-html/cell-16-output-1.png new file mode 100644 index 0000000..a6314b9 Binary files /dev/null and b/nbs/filtering_edges_files/figure-html/cell-16-output-1.png differ diff --git a/nbs/filtering_edges_files/figure-html/cell-7-output-1.png b/nbs/filtering_edges_files/figure-html/cell-7-output-1.png new file mode 100644 index 0000000..d5a7e48 Binary files /dev/null and b/nbs/filtering_edges_files/figure-html/cell-7-output-1.png differ diff --git a/nbs/filtering_edges_files/figure-html/cell-9-output-1.png b/nbs/filtering_edges_files/figure-html/cell-9-output-1.png new file mode 100644 index 0000000..2289e24 Binary files /dev/null and b/nbs/filtering_edges_files/figure-html/cell-9-output-1.png differ diff --git a/nbs/filtering_operators.html b/nbs/filtering_operators.html new file mode 100644 index 0000000..e1ea120 --- /dev/null +++ b/nbs/filtering_operators.html @@ -0,0 +1,767 @@ + + + + + + + + + + + + +Kornia - Filtering Operators + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Filtering Operators

+
+
Basic
+
Filters
+
Blur
+
kornia.filters
+
+
+ +
+
+ In this tutorial we are going to learn how to apply blurring filters to images with kornia.filters components. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

July 6, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+

Open in HF Spaces

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://github.com/kornia/data/raw/main/drslump.jpg"
+download_image(url)
+
+
'drslump.jpg'
+
+
+
+
import kornia as K
+import torch
+import torchvision
+from matplotlib import pyplot as plt
+
+

We use Kornia to load an image to memory represented directly in a tensor

+
+
x_rgb: torch.Tensor = K.io.load_image("doraemon.png", K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+
+x_gray = K.color.rgb_to_grayscale(x_rgb)
+
+
+
def imshow(input: torch.Tensor):
+    if input.shape != x_rgb.shape:
+        input = K.geometry.resize(input, size=(x_rgb.shape[-2:]))
+    out = torch.cat([x_rgb, input], dim=-1)
+    out = torchvision.utils.make_grid(out, nrow=2, padding=5)
+    out_np = K.utils.tensor_to_image(out)
+    plt.imshow(out_np)
+    plt.axis("off")
+    plt.show()
+
+
+
imshow(x_rgb)
+
+

+
+
+
+

Box Blur

+
+
x_blur: torch.Tensor = K.filters.box_blur(x_rgb, (9, 9))
+imshow(x_blur)
+
+

+
+
+
+
+

Blur Pool

+
+
x_blur: torch.Tensor = K.filters.blur_pool2d(x_rgb, kernel_size=9)
+imshow(x_blur)
+
+

+
+
+
+
+

Gaussian Blur

+
+
x_blur: torch.Tensor = K.filters.gaussian_blur2d(x_rgb, (11, 11), (11.0, 11.0))
+imshow(x_blur)
+
+

+
+
+
+
+

Max Pool

+
+
x_blur: torch.Tensor = K.filters.max_blur_pool2d(x_rgb, kernel_size=11)
+imshow(x_blur)
+
+

+
+
+
+
+

Median Blur

+
+
x_blur: torch.Tensor = K.filters.median_blur(x_rgb, (5, 5))
+imshow(x_blur)
+
+

+
+
+
+
+

Motion Blur

+
+
x_blur: torch.Tensor = K.filters.motion_blur(x_rgb, 9, 90.0, 1)
+imshow(x_blur)
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/filtering_operators_files/figure-html/cell-10-output-1.png b/nbs/filtering_operators_files/figure-html/cell-10-output-1.png new file mode 100644 index 0000000..c2e3e12 Binary files /dev/null and b/nbs/filtering_operators_files/figure-html/cell-10-output-1.png differ diff --git a/nbs/filtering_operators_files/figure-html/cell-11-output-1.png b/nbs/filtering_operators_files/figure-html/cell-11-output-1.png new file mode 100644 index 0000000..3b37f4c Binary files /dev/null and b/nbs/filtering_operators_files/figure-html/cell-11-output-1.png differ diff --git a/nbs/filtering_operators_files/figure-html/cell-12-output-1.png b/nbs/filtering_operators_files/figure-html/cell-12-output-1.png new file mode 100644 index 0000000..cfc7a13 Binary files /dev/null and b/nbs/filtering_operators_files/figure-html/cell-12-output-1.png differ diff --git a/nbs/filtering_operators_files/figure-html/cell-13-output-1.png b/nbs/filtering_operators_files/figure-html/cell-13-output-1.png new file mode 100644 index 0000000..1c69ce3 Binary files /dev/null and b/nbs/filtering_operators_files/figure-html/cell-13-output-1.png differ diff --git a/nbs/filtering_operators_files/figure-html/cell-7-output-1.png b/nbs/filtering_operators_files/figure-html/cell-7-output-1.png new file mode 100644 index 0000000..ad21f5d Binary files /dev/null and b/nbs/filtering_operators_files/figure-html/cell-7-output-1.png differ diff --git a/nbs/filtering_operators_files/figure-html/cell-8-output-1.png b/nbs/filtering_operators_files/figure-html/cell-8-output-1.png new file mode 100644 index 0000000..d3620e7 Binary files /dev/null and b/nbs/filtering_operators_files/figure-html/cell-8-output-1.png differ diff --git a/nbs/filtering_operators_files/figure-html/cell-9-output-1.png b/nbs/filtering_operators_files/figure-html/cell-9-output-1.png new file mode 100644 index 0000000..af8e86d Binary files /dev/null and b/nbs/filtering_operators_files/figure-html/cell-9-output-1.png differ diff --git a/nbs/fit_line.html b/nbs/fit_line.html new file mode 100644 index 0000000..8122c00 --- /dev/null +++ b/nbs/fit_line.html @@ -0,0 +1,699 @@ + + + + + + + + + + + + +Kornia - Fit line tutorial + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Fit line tutorial

+
+
Basic
+
Line
+
kornia.geometry
+
+
+ +
+
+ This tutorial use shows how to generate a line based on points. Using the ParametrizedLine and fit_line from kornia.gemetry.line +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

July 15, 2022

+
+
+ + +
+ + +
+ +

Open in google colab

+
+
import matplotlib.pyplot as plt
+import torch
+from kornia.core import concatenate, stack
+from kornia.geometry.line import ParametrizedLine, fit_line
+
+
+
std = 1.2  # standard deviation for the points
+num_points = 50  # total number of points
+
+
+
# create a baseline
+p0 = torch.tensor([0.0, 0.0])
+p1 = torch.tensor([1.0, 1.0])
+
+l1 = ParametrizedLine.through(p0, p1)
+print(l1)
+
+
Origin: Parameter containing:
+tensor([0., 0.], requires_grad=True)
+Direction: Parameter containing:
+tensor([0.7071, 0.7071], requires_grad=True)
+
+
+
+
# sample some points and weights
+pts, w = [], []
+for t in torch.linspace(-10, 10, num_points):
+    p2 = l1.point_at(t)
+    p2_noise = torch.rand_like(p2) * std
+    p2 += p2_noise
+    pts.append(p2)
+    w.append(1 - p2_noise.mean())
+pts = stack(pts)
+w = stack(w)
+
+
+
# fit the the line
+l2 = fit_line(pts[None, ...], w[None, ...])
+print(l2)
+
+# project some points along the estimated line
+p3 = l2.point_at(-10)
+p4 = l2.point_at(10)
+
+
Origin: Parameter containing:
+tensor([[0.5933, 0.5888]], requires_grad=True)
+Direction: Parameter containing:
+tensor([[-0.7146, -0.6995]], requires_grad=True)
+
+
+
+
X = concatenate((p3, p4), dim=0).detach().numpy()
+X_pts = pts.detach().numpy()
+
+plt.plot(X_pts[..., :, 0], X_pts[:, 1], "ro")
+plt.plot(X[:, 0], X[:, 1])
+plt.show()
+
+

+
+
+ + + +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/fit_line_files/figure-html/cell-7-output-1.png b/nbs/fit_line_files/figure-html/cell-7-output-1.png new file mode 100644 index 0000000..c54f892 Binary files /dev/null and b/nbs/fit_line_files/figure-html/cell-7-output-1.png differ diff --git a/nbs/fit_plane.html b/nbs/fit_plane.html new file mode 100644 index 0000000..32d5257 --- /dev/null +++ b/nbs/fit_plane.html @@ -0,0 +1,847 @@ + + + + + + + + + + + + +Kornia - Fit plane tutorial + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Fit plane tutorial

+
+
Basic
+
Plane
+
kornia.geometry
+
+
+ +
+
+ This tutorial use shows how to generate a plane based on a mesh. Using the Hyperplane and Hyperplane from kornia.gemetry.plane. As data structure we use kornia.geometry.liegroup.So3 e kornia.geometry.vector.Vector3 +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

December 16, 2022

+
+
+ + +
+ + +
+ +

Open in google colab

+
+
import plotly.express as px
+import plotly.io as pio
+import torch
+from kornia.core import stack
+from kornia.geometry.liegroup import So3
+from kornia.geometry.plane import Hyperplane, fit_plane
+from kornia.geometry.vector import Vector3
+from kornia.utils import create_meshgrid
+
+
+
# define the plane
+plane_h = 25
+plane_w = 50
+
+# create a base mesh in the ground z == 0
+mesh = create_meshgrid(plane_h, plane_w, normalized_coordinates=True)
+X, Y = mesh[..., 0], mesh[..., 1]
+Z = 0 * X
+
+mesh_pts = Vector3.from_coords(X, Y, Z)
+
+
+
# add noise to the mesh
+rand_pts = Vector3.random((plane_h, plane_w))
+rand_pts.z.clamp_(min=-0.1, max=0.1)
+
+mesh_view: Vector3 = mesh_pts + rand_pts
+
+
+
x_view = mesh_view.x.ravel().detach().cpu().numpy().tolist()
+y_view = mesh_view.y.ravel().detach().cpu().numpy().tolist()
+z_view = mesh_view.z.ravel().detach().cpu().numpy().tolist()
+fig = px.scatter_3d(dict(x=x_view, y=y_view, z=z_view, category=["view"] * len(x_view)), "x", "y", "z", color="category")
+fig.show()
+
+ +
+
+
+
+
# create rotation
+angle_rad = torch.tensor(3.141616 / 4)
+rot_x = So3.rot_x(angle_rad)
+rot_z = So3.rot_z(angle_rad)
+rot = rot_x * rot_z
+print(rot)
+
+
Parameter containing:
+tensor([ 0.8536,  0.3536, -0.1464,  0.3536], requires_grad=True)
+
+
+
+
# apply the rotation to the mesh points
+# TODO: this should work as `rot * mesh_view`
+points_rot = stack([rot * x for x in mesh_view.view(-1, 3)]).detach()
+points_rot = Vector3(points_rot)
+
+
+
x_rot = points_rot.x.ravel().detach().cpu().numpy().tolist()
+y_rot = points_rot.y.ravel().detach().cpu().numpy().tolist()
+z_rot = points_rot.z.ravel().detach().cpu().numpy().tolist()
+
+fig = px.scatter_3d(
+    dict(
+        x=x_view + x_rot,
+        y=y_view + y_rot,
+        z=z_view + z_rot,
+        category=["view"] * len(x_view) + ["rotated"] * len(x_rot),
+    ),
+    "x",
+    "y",
+    "z",
+    color="category",
+)
+fig.show()
+
+ +
+
+
+
+
# estimate the plane from the rotated points
+plane_in_ground_fit: Hyperplane = fit_plane(points_rot)
+print(plane_in_ground_fit)
+
+
Normal: x: -0.0002799616486299783
+y: 0.7073221206665039
+z: -0.7068911790847778
+Offset: 0.094654381275177
+
+
+
+
# project the original points to the estimated plane
+points_proj: Vector3 = plane_in_ground_fit.projection(mesh_view.view(-1, 3))
+
+
+
x_proj = points_proj.x.ravel().detach().cpu().numpy().tolist()
+y_proj = points_proj.y.ravel().detach().cpu().numpy().tolist()
+z_proj = points_proj.z.ravel().detach().cpu().numpy().tolist()
+categories = ["view"] * len(x_view) + ["rotated"] * len(x_rot) + ["projection"] * len(x_proj)
+fig = px.scatter_3d(
+    dict(
+        x=x_view + x_rot + x_proj,
+        y=y_view + y_rot + y_proj,
+        z=z_view + z_rot + z_proj,
+        category=categories,
+    ),
+    "x",
+    "y",
+    "z",
+    color="category",
+)
+fig.show()
+
+ +
+
+
+ + + +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/gaussian_blur.html b/nbs/gaussian_blur.html new file mode 100644 index 0000000..9a70346 --- /dev/null +++ b/nbs/gaussian_blur.html @@ -0,0 +1,727 @@ + + + + + + + + + + + + +Kornia - Blur image using GaussianBlur operator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Blur image using GaussianBlur operator

+
+
Basic
+
Blur
+
kornia.filters
+
+
+ +
+
+ In this tutorial we show how easily one can apply typical image transformations using Kornia. +
+
+ + +
+ +
+
Author
+
+

Takeshi Teshima

+
+
+ +
+
Published
+
+

May 18, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+

Preparation

+

We first install Kornia.

+
+
%%capture
+%matplotlib inline
+!pip install kornia
+!pip install kornia-rs
+
+
+
import kornia
+
+kornia.__version__
+
+
'0.6.12'
+
+
+

Now we download the example image.

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://github.com/kornia/data/raw/main/bennett_aden.png"
+download_image(url)
+
+
'bennett_aden.png'
+
+
+
+
+

Example

+

We first import the required libraries and load the data.

+
+
import matplotlib.pyplot as plt
+import torch
+
+# read the image with kornia
+data = kornia.io.load_image("./bennett_aden.png", kornia.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+
+

To apply a filter, we create the Gaussian Blur filter object and apply it to the data:

+
+
# create the operator
+gauss = kornia.filters.GaussianBlur2d((11, 11), (10.5, 10.5))
+
+# blur the image
+x_blur: torch.tensor = gauss(data)
+
+

That’s it! We can compare the pre-transform image and the post-transform image:

+
+
# convert back to numpy
+img_blur = kornia.tensor_to_image(x_blur)
+
+# Create the plot
+fig, axs = plt.subplots(1, 2, figsize=(16, 10))
+axs = axs.ravel()
+
+axs[0].axis("off")
+axs[0].set_title("image source")
+axs[0].imshow(kornia.tensor_to_image(data))
+
+axs[1].axis("off")
+axs[1].set_title("image blurred")
+axs[1].imshow(img_blur)
+
+pass
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/gaussian_blur_files/figure-html/cell-7-output-1.png b/nbs/gaussian_blur_files/figure-html/cell-7-output-1.png new file mode 100644 index 0000000..4d00d89 Binary files /dev/null and b/nbs/gaussian_blur_files/figure-html/cell-7-output-1.png differ diff --git a/nbs/geometric_transforms.html b/nbs/geometric_transforms.html new file mode 100644 index 0000000..6c99f83 --- /dev/null +++ b/nbs/geometric_transforms.html @@ -0,0 +1,941 @@ + + + + + + + + + + + + +Kornia - Geometric image and points transformations + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Geometric image and points transformations

+
+
Intermediate
+
Keypoints
+
kornia.augmentation
+
kornia.geometry
+
+
+ +
+
+ In this tutorial we will learn how to generate and manipulate geometrically synthetic images and use their transformations to manipulate 2D points and how to combine with torch components to perform data augmention. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

August 28, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+ +
+
+

Kornia recently introduced a module called kornia.augmentation which among other functionalities, provides a set of operators to perform geometric data augmentation with the option to retrieve the applied transformation to the original image in order to perform transformations of additional data such keypoints, bound boxes, or others.

+

Our geometric transformations API is compliant with torchvision including a few extras such as the flag return_transform that returns to the user the applied transformation to the original.

+

Additionally, our API inherits from nn.Module meaning that can be combined with nn.Sequential and chain the different applied transformations, when this last one is used. Moreover, we can compute in batches of images using different devices such CPU/GPU (and TPU in future).

+

Finally, all the operators are fully differentiable, a topic that we will cover in future tutorials so that users can make use of this feature.

+
+

\

+

In brief, in this tutorial we will learn how to:

+
    +
  1. Use kornia.augmentation.RandomAffine to generate random views and retrieve the transformation.
  2. +
  3. Use kornia.geometry.transform_points to manipulate points between views.
  4. +
  5. Combine the above in a nn.Module with other kornia.augmenation components to generate a complete augmentation pipeline.
  6. +
+
+

Installation

+

We first install Kornia v0.2.0 and Matplotlib for visualisation.

+

To play with data we will use some samples from HPatches dataset [1].

+
+

[1] HPatches: A benchmark and evaluation of handcrafted and learned local descriptors, Vassileios Balntas, Karel Lenc, Andrea Vedaldi and Krystian Mikolajczyk, CVPR 2017.

+
+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+download_image("https://github.com/kornia/data/raw/main/homography/img1.ppm")
+download_image("https://github.com/kornia/data/raw/main/v_dogman.ppm")
+download_image("https://github.com/kornia/data/raw/main/v_maskedman.ppm")
+download_image("https://miro.medium.com/max/6064/1*Fl89R-emhz-OLH9OZIQKUg.png", "delorean.png")
+
+
'delorean.png'
+
+
+
+
+

Setup

+

We will import the needed libraries and create a small functionalities to make use of OpenCV I/O.

+
+
%matplotlib inline
+import cv2
+import kornia as K
+import matplotlib.pyplot as plt
+import numpy as np
+import torch
+import torch.nn as nn
+
+

Define a function for visualisation using Matplotlib.

+
+
def imshow(image: np.ndarray, height: int, width: int):
+    """Utility function to plot images."""
+    plt.figure(figsize=(height, width))
+    plt.imshow(image)
+    plt.axis("off")
+    plt.show()
+
+

Since Kornia don’t provide render functionalities, let’s use OpenCV cv2.circle to draw points.

+
+
def draw_points(img_t: torch.Tensor, points: torch.Tensor) -> np.ndarray:
+    """Utility function to draw a set of points in an image."""
+
+    # cast image to numpy (HxWxC)
+    img: np.ndarray = K.utils.tensor_to_image(img_t)
+
+    # using cv2.circle() method
+    # draw a circle with blue line borders of thickness of 2 px
+    img_out: np.ndarray = img.copy()
+
+    for pt in points:
+        x, y = int(pt[0]), int(pt[1])
+        img_out = cv2.circle(img_out, (x, y), radius=10, color=(0, 0, 255), thickness=5)
+    return np.clip(img_out, 0, 1)
+
+
+
+

Transform single image

+

In this section we show how to open a single image, generate 2d random points and plot them using OpenCV and Matplotlib.

+

Next, we will use kornia.augmentation.RandomAffine to gerenate a random synthetic view of the given image and show how to retrieve the generated transformation to later be used to transform the points between images.

+
+
# load original image
+img1: torch.Tensor = K.io.load_image("img1.ppm", K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+
+# generate N random points within the image
+N: int = 10  # the number of points
+B, CH, H, W = img1.shape
+
+points1: torch.Tensor = torch.rand(1, N, 2)
+points1[..., 0] *= W
+points1[..., 1] *= H
+
+# draw points and show
+img1_vis: np.ndarray = draw_points(img1[0], points1[0])
+
+imshow(img1_vis, 10, 10)
+
+

+
+
+

Now lets move to a bit more complex example and start to use the kornia.augmentation API to transform an image and retrieve the applied transformation. We’ll show how to reuse this transformation to project the 2d points between images.

+
+
# declare an instance of our random affine generation eith `return_transform`
+# set to True, so that we recieve a tuple with the transformed image and the
+# transformation applied to the original image.
+transform: nn.Module = K.augmentation.RandomAffine(degrees=[-45.0, 45.0], p=1.0)
+
+# tranform image and retrieve transformation
+img2 = transform(img1, transform=transform)
+trans = transform.get_transformation_matrix(img1)
+
+# transform the original points
+points2: torch.Tensor = K.geometry.transform_points(trans, points1)
+
+img2_vis: np.ndarray = draw_points(img2, points2[0])
+
+
+imshow(img2_vis, 15, 15)
+
+

+
+
+
+
+

Transform batch of images

+

In the introduction we explained about the capability of kornia.augmentation to be integrated with other torch components such as nn.Module and nn.Sequential.

+

We will create a small component to perform data augmentation on batched images reusing the same ideas showed before to transform images and points.

+

First, lets define a class that will generate samples of synthetic views with a small color augmentation using the kornia.augmentation.ColorJitter and kornia.augmentation.RandomAffine components.

+

NOTE: we set the forward pass to have no gradients with the decorator @torch.no_grad() to make it more memory efficient.

+
+
from typing import Dict
+
+
+class DataAugmentator(nn.Module):
+    def __init__(self) -> None:
+        super().__init__()
+        # declare kornia components as class members
+        self.k1 = K.augmentation.RandomAffine([-60, 60], p=1.0)
+        self.k2 = K.augmentation.ColorJitter(0.5, 0.5, p=1.0)
+
+    @torch.no_grad()
+    def forward(self, img1: torch.Tensor, pts1: torch.Tensor) -> Dict[str, torch.Tensor]:
+        assert len(img1.shape) == 4, img1.shape
+
+        # apply geometric transform the transform matrix
+        img2 = self.k1(img1)
+        trans = self.k1.get_transformation_matrix(img1)
+
+        # apply color transform
+        img1, img2 = self.k2(img1), self.k2(img2)
+
+        # finally, lets use the transform to project the points
+        pts2: torch.Tensor = K.geometry.transform_points(trans, pts1)
+
+        return dict(img1=img1, img2=img2, pts1=pts1, pts2=pts2)
+
+

Lets use the defined component and generate some syntethic data !

+
+
# load data and make a batch
+img1: torch.Tensor = K.io.load_image("v_dogman.ppm", K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+img2: torch.Tensor = K.io.load_image("v_maskedman.ppm", K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+
+# crop data to make it homogeneous
+crop = K.augmentation.CenterCrop((512, 786))
+
+img1, img2 = crop(img1), crop(img2)
+
+# visualize
+img_vis = torch.cat([img1, img2], dim=-1)
+imshow(K.tensor_to_image(img_vis), 15, 15)
+
+

+
+
+
+
# create an instance of the augmentation pipeline
+# NOTE: remember that this is a nn.Module and could be
+# placed inside any network, pytorch-lighting module, etc.
+aug: nn.Module = DataAugmentator()
+
+for _ in range(5):  # create some samples
+    # generate batch
+    img_batch = torch.cat([img1, img2], dim=0)
+
+    # generate random points (or from a network)
+    N: int = 25
+    B, CH, H, W = img_batch.shape
+
+    points: torch.Tensor = torch.rand(B, N, 2)
+    points[..., 0] *= W
+    points[..., 1] *= H
+
+    # sample data
+    batch_data = aug(img_batch, points)
+
+    # plot and show
+    # visualize both images
+
+    img_vis_list = []
+
+    for i in range(2):
+        img1_vis: np.ndarray = draw_points(batch_data["img1"][i], batch_data["pts1"][i])
+        img_vis_list.append(img1_vis)
+
+        img2_vis: np.ndarray = draw_points(batch_data["img2"][i], batch_data["pts2"][i])
+        img_vis_list.append(img2_vis)
+
+    img_vis = np.concatenate(img_vis_list, axis=1)
+
+    imshow(img_vis, 20, 20)
+
+

+
+
+

+
+
+

+
+
+

+
+
+

+
+
+
+
+

BONUS: Backprop to the future

+

One of the main motivations during the desing for the kornia.augmentation API was to give to the user the flexibility to retrieve the applied transformation in order to achieve one of the main purposes of Kornia - the reverse engineering.

+

In this case we will show how easy one can combine Kornia and PyTorch components to undo the transformations and go back to the original data.

+

“Wait a minute, Doc. Are you telling me you built a time machine…out of a PyTorch?” - Marty McFLy

+
+
# lets start the Delorean engine
+delorean: torch.Tensor = K.io.load_image("delorean.png", K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+
+imshow(K.utils.tensor_to_image(delorean), 10, 10)
+
+

+
+
+

“If my calculations are correct, when this baby hits 88 miles per hour, you’re gonna see some serious shit.” - Doc. Brown

+
+
# turn on the time machine panel (TMP)
+
+TMP = K.augmentation.RandomHorizontalFlip(p=1.0)
+
+delorean_past = TMP(delorean)  # go !
+time_coords_past = TMP.get_transformation_matrix(delorean)
+
+imshow(K.utils.tensor_to_image(delorean_past), 10, 10)
+
+

+
+
+
+

Let’s go back to the future !

+

“Marty! You’ve gotta come back with me!” - Doc. Brown

+
+
# lets go back to the past
+
+time_coords_future: torch.Tensor = torch.inverse(time_coords_past)
+
+H, W = delorean_past.shape[-2:]
+delorean_future = K.geometry.warp_perspective(delorean_past, time_coords_future, (H, W))
+
+imshow(K.utils.tensor_to_image(delorean_future), 10, 10)
+
+

+
+
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/geometric_transforms_files/figure-html/cell-10-output-1.png b/nbs/geometric_transforms_files/figure-html/cell-10-output-1.png new file mode 100644 index 0000000..68b8c75 Binary files /dev/null and b/nbs/geometric_transforms_files/figure-html/cell-10-output-1.png differ diff --git a/nbs/geometric_transforms_files/figure-html/cell-11-output-1.png b/nbs/geometric_transforms_files/figure-html/cell-11-output-1.png new file mode 100644 index 0000000..be06304 Binary files /dev/null and b/nbs/geometric_transforms_files/figure-html/cell-11-output-1.png differ diff --git a/nbs/geometric_transforms_files/figure-html/cell-11-output-2.png b/nbs/geometric_transforms_files/figure-html/cell-11-output-2.png new file mode 100644 index 0000000..2616f6a Binary files /dev/null and b/nbs/geometric_transforms_files/figure-html/cell-11-output-2.png differ diff --git a/nbs/geometric_transforms_files/figure-html/cell-11-output-3.png b/nbs/geometric_transforms_files/figure-html/cell-11-output-3.png new file mode 100644 index 0000000..9a6092c Binary files /dev/null and b/nbs/geometric_transforms_files/figure-html/cell-11-output-3.png differ diff --git a/nbs/geometric_transforms_files/figure-html/cell-11-output-4.png b/nbs/geometric_transforms_files/figure-html/cell-11-output-4.png new file mode 100644 index 0000000..fad336a Binary files /dev/null and b/nbs/geometric_transforms_files/figure-html/cell-11-output-4.png differ diff --git a/nbs/geometric_transforms_files/figure-html/cell-11-output-5.png b/nbs/geometric_transforms_files/figure-html/cell-11-output-5.png new file mode 100644 index 0000000..705e69c Binary files /dev/null and b/nbs/geometric_transforms_files/figure-html/cell-11-output-5.png differ diff --git a/nbs/geometric_transforms_files/figure-html/cell-12-output-1.png b/nbs/geometric_transforms_files/figure-html/cell-12-output-1.png new file mode 100644 index 0000000..c95cd0a Binary files /dev/null and b/nbs/geometric_transforms_files/figure-html/cell-12-output-1.png differ diff --git a/nbs/geometric_transforms_files/figure-html/cell-13-output-1.png b/nbs/geometric_transforms_files/figure-html/cell-13-output-1.png new file mode 100644 index 0000000..0d0d092 Binary files /dev/null and b/nbs/geometric_transforms_files/figure-html/cell-13-output-1.png differ diff --git a/nbs/geometric_transforms_files/figure-html/cell-14-output-1.png b/nbs/geometric_transforms_files/figure-html/cell-14-output-1.png new file mode 100644 index 0000000..cd6650e Binary files /dev/null and b/nbs/geometric_transforms_files/figure-html/cell-14-output-1.png differ diff --git a/nbs/geometric_transforms_files/figure-html/cell-7-output-1.png b/nbs/geometric_transforms_files/figure-html/cell-7-output-1.png new file mode 100644 index 0000000..d5a1738 Binary files /dev/null and b/nbs/geometric_transforms_files/figure-html/cell-7-output-1.png differ diff --git a/nbs/geometric_transforms_files/figure-html/cell-8-output-1.png b/nbs/geometric_transforms_files/figure-html/cell-8-output-1.png new file mode 100644 index 0000000..ea88a2b Binary files /dev/null and b/nbs/geometric_transforms_files/figure-html/cell-8-output-1.png differ diff --git a/nbs/geometry_generate_patch.html b/nbs/geometry_generate_patch.html new file mode 100644 index 0000000..97e6627 --- /dev/null +++ b/nbs/geometry_generate_patch.html @@ -0,0 +1,726 @@ + + + + + + + + + + + + +Kornia - Image patch generation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Image patch generation

+
+
Intermediate
+
Patches
+
kornia.geometry
+
+
+ +
+
+ In this tutorial we are going to learn how to generate image patches using kornia.geometry components. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

August 28, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+download_image("https://github.com/kornia/data/raw/main/homography/img1.ppm")
+
+
'img1.ppm'
+
+
+

First load libraries and images

+
+
%matplotlib inline
+import kornia as K
+import matplotlib.pyplot as plt
+import torch
+
+
+
def imshow(image: torch.tensor, height: int = 10, width: int = 10):
+    """Utility function to plot images."""
+    plt.figure(figsize=(height, width))
+    plt.imshow(K.tensor_to_image(image))
+    plt.axis("off")
+    plt.show()
+
+

Load and show the original image

+
+
torch.manual_seed(0)
+
+timg: torch.Tensor = K.io.load_image("img1.ppm", K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+
+imshow(timg, 10, 10)
+
+

+
+
+

In the following section we are going to take the original image and generate random crops of a given size.

+
+
random_crop = K.augmentation.RandomCrop((64, 64))
+
+patch = torch.cat([random_crop(timg) for _ in range(15)], dim=-1)
+
+imshow(patch[0], 22, 22)
+
+

+
+
+

Next, we will show how to crop patches and apply forth and back random geometric transformations.

+
+
# transform a patch
+
+random_crop = K.augmentation.RandomCrop((64, 64))
+random_affine = K.augmentation.RandomAffine([-15, 15], [0.0, 0.25])
+
+# crop
+patch = random_crop(timg)
+
+# transform and retrieve transformation
+patch_affine = random_affine(patch)
+transformation = random_affine.get_transformation_matrix(patch)
+
+# invert patch
+_, _, H, W = patch.shape
+patch_inv = K.geometry.warp_perspective(patch_affine, torch.inverse(transformation), (H, W))
+
+# visualise - (original, transformed, reconstructed)
+patches_vis = torch.cat([patch, patch_affine, patch_inv], dim=-1)
+imshow(patches_vis, 15, 15)
+
+

+
+
+ + + +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/geometry_generate_patch_files/figure-html/cell-6-output-1.png b/nbs/geometry_generate_patch_files/figure-html/cell-6-output-1.png new file mode 100644 index 0000000..e5f43aa Binary files /dev/null and b/nbs/geometry_generate_patch_files/figure-html/cell-6-output-1.png differ diff --git a/nbs/geometry_generate_patch_files/figure-html/cell-7-output-1.png b/nbs/geometry_generate_patch_files/figure-html/cell-7-output-1.png new file mode 100644 index 0000000..10a1fbc Binary files /dev/null and b/nbs/geometry_generate_patch_files/figure-html/cell-7-output-1.png differ diff --git a/nbs/geometry_generate_patch_files/figure-html/cell-8-output-1.png b/nbs/geometry_generate_patch_files/figure-html/cell-8-output-1.png new file mode 100644 index 0000000..421174a Binary files /dev/null and b/nbs/geometry_generate_patch_files/figure-html/cell-8-output-1.png differ diff --git a/nbs/hello_world_tutorial.html b/nbs/hello_world_tutorial.html new file mode 100644 index 0000000..4a00c09 --- /dev/null +++ b/nbs/hello_world_tutorial.html @@ -0,0 +1,779 @@ + + + + + + + + + + + + +Kornia - Hello world: Planet Kornia + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Hello world: Planet Kornia

+
+
Basic
+
kornia.io
+
+
+ +
+
+ Welcome to Planet Kornia: a set of tutorials to learn about Computer Vision in PyTorch. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

January 21, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+

Welcome to Planet Kornia: a set of tutorials to learn about Computer Vision in PyTorch.

+

This is the first tutorial that show how one can simply start loading images with Kornia and OpenCV.

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
import cv2
+import kornia as K
+import numpy as np
+import torch
+from matplotlib import pyplot as plt
+
+

Download first an image form internet to start to work.

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+download_image("https://github.com/kornia/data/raw/main/arturito.jpg")
+
+
'arturito.jpg'
+
+
+
+

Load an image with Kornia

+

With Kornia, we can read the image which returns the images in a torch.Tensor in the shape (C,H,W). Also, we can convert a image (array) into a Tensor.

+

We have a couple of utilities to cast the image to a torch.Tensor to make it compliant to the other Kornia components and arrange the data in (B,C,H,W).

+

The read function is kornia.io.load_image, to use it, you need to pip install kornia_rs.

+

The package internally implements kornia_rs which contains a low level implementation for Computer Vision in the Rust language. In addition, we implement the DLPack protocol natively in Rust to reduce the memory footprint during the decoding and types conversion.

+

You can define the type you can load into the tensor, with K.io.ImageLoadType.RGB32 mode the image will be loaded as float32 with values between 0~1.0. Also can define the desired device you want to read the image.

+
+
img_bgr_tensor = K.io.load_image("arturito.jpg", K.io.ImageLoadType.RGB32, device="cpu")
+
+img_bgr_tensor
+
+
tensor([[[0.9412, 0.9412, 0.9412,  ..., 0.9412, 0.9412, 0.9412],
+         [0.9961, 0.9961, 0.9961,  ..., 0.9961, 0.9961, 0.9961],
+         [1.0000, 1.0000, 1.0000,  ..., 1.0000, 1.0000, 1.0000],
+         ...,
+         [0.6471, 0.6471, 0.6471,  ..., 0.6471, 0.6471, 0.6471],
+         [0.6392, 0.6392, 0.6392,  ..., 0.6392, 0.6392, 0.6392],
+         [0.6510, 0.6510, 0.6510,  ..., 0.6510, 0.6510, 0.6510]],
+
+        [[0.9412, 0.9412, 0.9412,  ..., 0.9412, 0.9412, 0.9412],
+         [0.9961, 0.9961, 0.9961,  ..., 0.9961, 0.9961, 0.9961],
+         [1.0000, 1.0000, 1.0000,  ..., 1.0000, 1.0000, 1.0000],
+         ...,
+         [0.6471, 0.6471, 0.6471,  ..., 0.6471, 0.6471, 0.6471],
+         [0.6392, 0.6392, 0.6392,  ..., 0.6392, 0.6392, 0.6392],
+         [0.6510, 0.6510, 0.6510,  ..., 0.6510, 0.6510, 0.6510]],
+
+        [[0.9412, 0.9412, 0.9412,  ..., 0.9412, 0.9412, 0.9412],
+         [0.9961, 0.9961, 0.9961,  ..., 0.9961, 0.9961, 0.9961],
+         [1.0000, 1.0000, 1.0000,  ..., 1.0000, 1.0000, 1.0000],
+         ...,
+         [0.6471, 0.6471, 0.6471,  ..., 0.6471, 0.6471, 0.6471],
+         [0.6392, 0.6392, 0.6392,  ..., 0.6392, 0.6392, 0.6392],
+         [0.6510, 0.6510, 0.6510,  ..., 0.6510, 0.6510, 0.6510]]])
+
+
+
+
plt.imshow(K.tensor_to_image(img_bgr_tensor))
+plt.axis("off");
+
+

+
+
+
+
+

Load an image with OpenCV

+

We can use OpenCV to load an image. By default, OpenCV loads images in BGR format and casts to a numpy.ndarray with the data layout (H,W,C).

+

However, because matplotlib saves an image in RGB format, in OpenCV you need to change the BGR to RGB so that an image is displayed properly.

+
+
img_bgr: np.array = cv2.imread("arturito.jpg")  # HxWxC / np.uint8
+img_rgb: np.array = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
+
+plt.imshow(img_rgb)
+plt.axis("off");
+
+

+
+
+

The utility is kornia.image_to_tensor which casts a numpy.ndarray to a torch.Tensor and permutes the channels to leave the image ready for being used with any other PyTorch or Kornia component.

+

The image is casted into a 4D torch.Tensor with zero-copy.

+
+
x_bgr: torch.tensor = K.image_to_tensor(img_bgr)  # CxHxW / torch.uint8
+x_bgr = x_bgr.unsqueeze(0)  # 1xCxHxW
+print(f"convert from '{img_bgr.shape}' to '{x_bgr.shape}'")
+
+
convert from '(144, 256, 3)' to 'torch.Size([1, 3, 144, 256])'
+
+
+

We can convert from BGR to RGB with a kornia.color component.

+
+
x_rgb: torch.tensor = K.color.bgr_to_rgb(x_bgr)  # 1xCxHxW / torch.uint8
+
+
+
+

Visualize an image with Matplotib

+

We will use Matplotlib for the visualisation inside the notebook. Matplotlib requires a numpy.ndarray in the (H,W,C) format, and for doing so we will go back with kornia.tensor_to_image which will convert the image to the correct format.

+
+
img_bgr: np.array = K.tensor_to_image(x_bgr)
+img_rgb: np.array = K.tensor_to_image(x_rgb)
+
+

Create a subplot to visualize the original an a modified image

+
+
fig, axs = plt.subplots(1, 2, figsize=(32, 16))
+axs = axs.ravel()
+
+axs[0].axis("off")
+axs[0].imshow(img_rgb)
+
+axs[1].axis("off")
+axs[1].imshow(img_bgr)
+
+plt.show()
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/hello_world_tutorial_files/figure-html/cell-11-output-1.png b/nbs/hello_world_tutorial_files/figure-html/cell-11-output-1.png new file mode 100644 index 0000000..100ceae Binary files /dev/null and b/nbs/hello_world_tutorial_files/figure-html/cell-11-output-1.png differ diff --git a/nbs/hello_world_tutorial_files/figure-html/cell-6-output-1.png b/nbs/hello_world_tutorial_files/figure-html/cell-6-output-1.png new file mode 100644 index 0000000..55a0ecb Binary files /dev/null and b/nbs/hello_world_tutorial_files/figure-html/cell-6-output-1.png differ diff --git a/nbs/hello_world_tutorial_files/figure-html/cell-7-output-1.png b/nbs/hello_world_tutorial_files/figure-html/cell-7-output-1.png new file mode 100644 index 0000000..cd856b8 Binary files /dev/null and b/nbs/hello_world_tutorial_files/figure-html/cell-7-output-1.png differ diff --git a/nbs/homography.html b/nbs/homography.html new file mode 100644 index 0000000..feefa60 --- /dev/null +++ b/nbs/homography.html @@ -0,0 +1,931 @@ + + + + + + + + + + + + +Kornia - Image Alignment by Homography Optimization + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Image Alignment by Homography Optimization

+
+
Advanced
+
Homography
+
kornia.geometry
+
+
+ +
+
+ In this tutorial we are going to learn how to perform the task of image alignment by optimising the homography transformation between two images. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

August 28, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+download_image("https://github.com/kornia/data/raw/main/homography/H1to2p")
+download_image("https://github.com/kornia/data/raw/main/homography/img1.ppm")
+download_image("https://github.com/kornia/data/raw/main/homography/img2.ppm")
+
+
'img2.ppm'
+
+
+

Import needed libraries

+
+
import os
+from typing import List
+
+import kornia as K
+import matplotlib.pyplot as plt
+import numpy as np
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+import torch.optim as optim
+from kornia.geometry import resize
+
+# computer vision libs :D
+
+

Define the hyper parameters to perform the online optimisation

+
+
learning_rate: float = 1e-3  # the gradient optimisation update step
+num_iterations: int = 100  # the number of iterations until convergence
+num_levels: int = 6  # the total number of image pyramid levels
+error_tol: float = 1e-8  # the optimisation error tolerance
+
+log_interval: int = 100  # print log every N iterations
+device = K.utils.get_cuda_or_mps_device_if_available()
+print("Using ", device)
+
+
Using  cpu
+
+
+

Define a container to hold the homography as a nn.Parameter so that cen be used by the autograd within the torch.optim framework.

+

We initialize the homography with the identity transformation.

+
+
class MyHomography(nn.Module):
+    def __init__(self) -> None:
+        super().__init__()
+        self.homography = nn.Parameter(torch.Tensor(3, 3))
+        self.reset_parameters()
+
+    def reset_parameters(self):
+        torch.nn.init.eye_(self.homography)
+
+    def forward(self) -> torch.Tensor:
+        return torch.unsqueeze(self.homography, dim=0)  # 1x3x3
+
+

Read the images and the ground truth homograpy to convert to tensor. In addition, we normalize the homography in order to smooth the gradiens during the optimisation process.

+
+
img_src: torch.Tensor = K.io.load_image("img1.ppm", K.io.ImageLoadType.RGB32, device=device)[None, ...]
+img_dst: torch.Tensor = K.io.load_image("img2.ppm", K.io.ImageLoadType.RGB32, device=device)[None, ...]
+print(img_src.shape)
+print(img_dst.shape)
+
+dst_homo_src_gt = np.loadtxt("H1to2p")
+dst_homo_src_gt = torch.from_numpy(dst_homo_src_gt)[None].float().to(device)
+print(dst_homo_src_gt.shape)
+print(dst_homo_src_gt)
+
+height, width = img_src.shape[-2:]
+
+# warp image in normalized coordinates
+normal_transform_pixel: torch.Tensor = K.geometry.normal_transform_pixel(height, width, device=device)
+
+dst_homo_src_gt_norm: torch.Tensor = normal_transform_pixel @ dst_homo_src_gt @ torch.inverse(normal_transform_pixel)
+
+img_src_to_dst_gt: torch.Tensor = K.geometry.homography_warp(img_src, torch.inverse(dst_homo_src_gt_norm), (height, width))
+
+img_src_vis: np.ndarray = K.utils.tensor_to_image(K.color.bgr_to_rgb(img_src))
+img_dst_vis: np.ndarray = K.utils.tensor_to_image(K.color.bgr_to_rgb(img_dst))
+img_src_to_dst_gt_vis: np.ndarray = K.utils.tensor_to_image(K.color.bgr_to_rgb(img_src_to_dst_gt))
+
+
torch.Size([1, 3, 640, 800])
+torch.Size([1, 3, 640, 800])
+torch.Size([1, 3, 3])
+tensor([[[ 8.7977e-01,  3.1245e-01, -3.9431e+01],
+         [-1.8389e-01,  9.3847e-01,  1.5316e+02],
+         [ 1.9641e-04, -1.6015e-05,  1.0000e+00]]])
+
+
+

Show the source image, the target and the source image warped to the target using the ground truth homography transformation.

+
+
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, sharey=True)
+fig.set_figheight(15)
+fig.set_figwidth(15)
+
+ax1.imshow(img_src_vis)
+ax1.set_title("Source image")
+
+ax2.imshow(img_dst_vis)
+ax2.set_title("Destination image")
+
+ax3.imshow(img_src_to_dst_gt_vis)
+ax3.set_title("Source to Destination image")
+plt.show()
+
+

+
+
+

Initialize the homography warper and pass the parameters to the torch.optim.Adam optimizer to perform an online gradient descent optimisation to approximate the mapping transformation between the two images.

+
+
# create homography parameters
+dst_homo_src = MyHomography().to(device)
+
+# create optimizer
+optimizer = optim.Adam(dst_homo_src.parameters(), lr=learning_rate)
+
+# send data to device
+img_src, img_dst = img_src.to(device), img_dst.to(device)
+
+

In order to perform the online optimisation, we will apply a know fine-to-coarse strategy. For this reason, we precompute a gaussian pyramid from each image with a certain number of levels.

+
+
### compute Gaussian Pyramid
+
+
+def get_gaussian_pyramid(img: torch.Tensor, num_levels: int) -> List[torch.Tensor]:
+    r"""Utility function to compute a gaussian pyramid."""
+    pyramid = []
+    pyramid.append(img)
+    for _ in range(num_levels - 1):
+        img_curr = pyramid[-1]
+        img_down = K.geometry.pyrdown(img_curr)
+        pyramid.append(img_down)
+    return pyramid
+
+
+# compute the gaussian pyramids
+img_src_pyr: List[torch.Tensor] = get_gaussian_pyramid(img_src, num_levels)
+img_dst_pyr: List[torch.Tensor] = get_gaussian_pyramid(img_dst, num_levels)
+
+
+

Main optimization loop

+

Define the loss function to minimize the photometric error at each pyramid level:

+

$ L = |I_{ref} - (I_{dst}, H_{ref}^{dst}))|$

+
+
def compute_scale_loss(
+    img_src: torch.Tensor,
+    img_dst: torch.Tensor,
+    dst_homo_src: nn.Module,
+    optimizer: torch.optim,
+    num_iterations: int,
+    error_tol: float,
+) -> torch.Tensor:
+    assert len(img_src.shape) == len(img_dst.shape), (img_src.shape, img_dst.shape)
+
+    # init loop parameters
+    loss_tol = torch.tensor(error_tol)
+    loss_prev = torch.finfo(img_src.dtype).max
+
+    for i in range(num_iterations):
+        # create homography warper
+        src_homo_dst: torch.Tensor = torch.inverse(dst_homo_src)
+
+        _height, _width = img_src.shape[-2:]
+        warper = K.geometry.HomographyWarper(_height, _width)
+        img_src_to_dst = warper(img_src, src_homo_dst)
+
+        # compute and mask loss
+        loss = F.l1_loss(img_src_to_dst, img_dst, reduction="none")  # 1x3xHxW
+
+        ones = warper(torch.ones_like(img_src), src_homo_dst)
+        loss = loss.masked_select(ones > 0.9).mean()
+
+        # compute gradient and update optimizer parameters
+        optimizer.zero_grad()
+        loss.backward()
+        optimizer.step()
+
+

Run the main body loop to warp the images from each pyramid level and evaluate the loss to perform gradient update.

+
+
# pyramid loop
+
+
+for iter_idx in range(num_levels):
+    # get current pyramid data
+    scale: int = (num_levels - 1) - iter_idx
+    img_src = img_src_pyr[scale]
+    img_dst = img_dst_pyr[scale]
+
+    # compute scale loss
+    compute_scale_loss(img_src, img_dst, dst_homo_src(), optimizer, num_iterations, error_tol)
+
+    print(f"Optimization iteration: {iter_idx}/{num_levels}")
+
+    # merge warped and target image for visualization
+    h, w = img_src.shape[-2:]
+    warper = K.geometry.HomographyWarper(h, w)
+    img_src_to_dst = warper(img_src, torch.inverse(dst_homo_src()))
+    img_src_to_dst_gt = warper(img_src, torch.inverse(dst_homo_src_gt_norm))
+
+    # compute the reprojection error
+    error = F.l1_loss(img_src_to_dst, img_src_to_dst_gt, reduction="none")
+    print(f"Reprojection error: {error.mean()}")
+
+    # show data
+    img_src_vis = K.utils.tensor_to_image(K.color.bgr_to_rgb(img_src))
+    img_dst_vis = K.utils.tensor_to_image(K.color.bgr_to_rgb(img_dst))
+    img_src_to_dst_merge = 0.65 * img_src_to_dst + 0.35 * img_dst
+    img_src_to_dst_vis = K.utils.tensor_to_image(K.color.bgr_to_rgb(img_src_to_dst_merge))
+    img_src_to_dst_gt_vis = K.utils.tensor_to_image(K.color.bgr_to_rgb(img_src_to_dst_gt))
+
+    error_sum = error.mean(dim=1, keepdim=True)
+    error_vis = K.utils.tensor_to_image(error_sum)
+
+    # show the original images at each scale level, the result of warping using
+    # the homography at moment, and the estimated error against the GT homography.
+
+    %matplotlib inline
+    fig, (ax1, ax2, ax3, ax4, ax5) = plt.subplots(1, 5, sharey=False)
+    fig.set_figheight(15)
+    fig.set_figwidth(15)
+
+    ax1.imshow(img_src_vis)
+    ax1.set_title("Source image")
+
+    ax2.imshow(img_dst_vis)
+    ax2.set_title("Destination image")
+
+    ax3.imshow(img_src_to_dst_vis)
+    ax3.set_title("Source to Destination image")
+
+    ax4.imshow(img_src_to_dst_gt_vis)
+    ax4.set_title("Source to Destination image GT")
+
+    ax5.imshow(error_vis, cmap="gray", vmin=0, vmax=1)
+    ax5.set_title("Error")
+    plt.show()
+
+
Optimization iteration: 0/6
+Reprojection error: 0.17220830917358398
+Optimization iteration: 1/6
+Reprojection error: 0.11565501242876053
+Optimization iteration: 2/6
+Reprojection error: 0.018368173390626907
+Optimization iteration: 3/6
+Reprojection error: 0.013175368309020996
+Optimization iteration: 4/6
+Reprojection error: 0.008068887516856194
+Optimization iteration: 5/6
+Reprojection error: 0.005315570626407862
+
+
+

+
+
+

+
+
+

+
+
+

+
+
+

+
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/homography_files/figure-html/cell-12-output-2.png b/nbs/homography_files/figure-html/cell-12-output-2.png new file mode 100644 index 0000000..74bb632 Binary files /dev/null and b/nbs/homography_files/figure-html/cell-12-output-2.png differ diff --git a/nbs/homography_files/figure-html/cell-12-output-3.png b/nbs/homography_files/figure-html/cell-12-output-3.png new file mode 100644 index 0000000..13b5a34 Binary files /dev/null and b/nbs/homography_files/figure-html/cell-12-output-3.png differ diff --git a/nbs/homography_files/figure-html/cell-12-output-4.png b/nbs/homography_files/figure-html/cell-12-output-4.png new file mode 100644 index 0000000..25d52c7 Binary files /dev/null and b/nbs/homography_files/figure-html/cell-12-output-4.png differ diff --git a/nbs/homography_files/figure-html/cell-12-output-5.png b/nbs/homography_files/figure-html/cell-12-output-5.png new file mode 100644 index 0000000..d3aeedb Binary files /dev/null and b/nbs/homography_files/figure-html/cell-12-output-5.png differ diff --git a/nbs/homography_files/figure-html/cell-12-output-6.png b/nbs/homography_files/figure-html/cell-12-output-6.png new file mode 100644 index 0000000..c7865e1 Binary files /dev/null and b/nbs/homography_files/figure-html/cell-12-output-6.png differ diff --git a/nbs/homography_files/figure-html/cell-12-output-7.png b/nbs/homography_files/figure-html/cell-12-output-7.png new file mode 100644 index 0000000..ab19766 Binary files /dev/null and b/nbs/homography_files/figure-html/cell-12-output-7.png differ diff --git a/nbs/homography_files/figure-html/cell-8-output-1.png b/nbs/homography_files/figure-html/cell-8-output-1.png new file mode 100644 index 0000000..169b8f1 Binary files /dev/null and b/nbs/homography_files/figure-html/cell-8-output-1.png differ diff --git a/nbs/image_enhancement.html b/nbs/image_enhancement.html new file mode 100644 index 0000000..7b72ea8 --- /dev/null +++ b/nbs/image_enhancement.html @@ -0,0 +1,752 @@ + + + + + + + + + + + + +Kornia - Image Enhancement + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Image Enhancement

+
+
Basic
+
kornia.enhance
+
+
+ +
+
+ In this tutorial we are going to learn how to tweak image properties using the compoments from kornia.enhance. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

July 5, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+download_image("https://github.com/kornia/data/raw/main/ninja_turtles.jpg")
+
+
'ninja_turtles.jpg'
+
+
+
+
import kornia as K
+import numpy as np
+import torch
+import torchvision
+from matplotlib import pyplot as plt
+
+
+
def imshow(input: torch.Tensor):
+    out: torch.Tensor = torchvision.utils.make_grid(input, nrow=2, padding=5)
+    out_np: np.ndarray = K.utils.tensor_to_image(out)
+    plt.imshow(out_np)
+    plt.axis("off")
+    plt.show()
+
+

We use Kornia to load an image to memory represented as a tensor

+
+
x_rgb = K.io.load_image("ninja_turtles.jpg", K.io.ImageLoadType.RGB32)[None, ...]
+
+

Create batch

+
+
x_rgb = x_rgb.expand(4, -1, -1, -1)  # 4xCxHxW
+
+
+
imshow(x_rgb)
+
+

+
+
+
+

Adjust brightness

+
+
x_out: torch.Tensor = K.enhance.adjust_brightness(x_rgb, torch.linspace(0.2, 0.8, 4))
+imshow(x_out)
+
+

+
+
+
+
+

Adjust Contrast

+
+
x_out: torch.Tensor = K.enhance.adjust_contrast(x_rgb, torch.linspace(0.5, 1.0, 4))
+imshow(x_out)
+
+

+
+
+
+
+

Adjust Saturation

+
+
x_out: torch.Tensor = K.enhance.adjust_saturation(x_rgb, torch.linspace(0.0, 1.0, 4))
+imshow(x_out)
+
+

+
+
+
+
+

Adjust Gamma

+
+
x_out: torch.Tensor = K.enhance.adjust_gamma(x_rgb, torch.tensor([0.2, 0.4, 0.5, 0.6]))
+imshow(x_out)
+
+

+
+
+
+
+

Adjust Hue

+
+
x_out: torch.Tensor = K.enhance.adjust_hue(x_rgb, torch.linspace(0.0, 3.14159, 4))
+imshow(x_out)
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/image_enhancement_files/figure-html/cell-10-output-1.png b/nbs/image_enhancement_files/figure-html/cell-10-output-1.png new file mode 100644 index 0000000..4cedda7 Binary files /dev/null and b/nbs/image_enhancement_files/figure-html/cell-10-output-1.png differ diff --git a/nbs/image_enhancement_files/figure-html/cell-11-output-1.png b/nbs/image_enhancement_files/figure-html/cell-11-output-1.png new file mode 100644 index 0000000..bc4938c Binary files /dev/null and b/nbs/image_enhancement_files/figure-html/cell-11-output-1.png differ diff --git a/nbs/image_enhancement_files/figure-html/cell-12-output-1.png b/nbs/image_enhancement_files/figure-html/cell-12-output-1.png new file mode 100644 index 0000000..4c38fa5 Binary files /dev/null and b/nbs/image_enhancement_files/figure-html/cell-12-output-1.png differ diff --git a/nbs/image_enhancement_files/figure-html/cell-13-output-1.png b/nbs/image_enhancement_files/figure-html/cell-13-output-1.png new file mode 100644 index 0000000..f64bdf2 Binary files /dev/null and b/nbs/image_enhancement_files/figure-html/cell-13-output-1.png differ diff --git a/nbs/image_enhancement_files/figure-html/cell-8-output-1.png b/nbs/image_enhancement_files/figure-html/cell-8-output-1.png new file mode 100644 index 0000000..ba8171f Binary files /dev/null and b/nbs/image_enhancement_files/figure-html/cell-8-output-1.png differ diff --git a/nbs/image_enhancement_files/figure-html/cell-9-output-1.png b/nbs/image_enhancement_files/figure-html/cell-9-output-1.png new file mode 100644 index 0000000..74cddf5 Binary files /dev/null and b/nbs/image_enhancement_files/figure-html/cell-9-output-1.png differ diff --git a/nbs/image_histogram.html b/nbs/image_histogram.html new file mode 100644 index 0000000..23446a3 --- /dev/null +++ b/nbs/image_histogram.html @@ -0,0 +1,994 @@ + + + + + + + + + + + + +Kornia - Image histogram and equalizations techniques + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Image histogram and equalizations techniques

+
+
Basic
+
Color spaces
+
kornia.enhance
+
+
+ +
+
+ In this tutorial we are going to learn how using kornia components and Matplotlib we can visualize image histograms and later use kornia functionality to equalize images in batch and using the gpu. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

August 31, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+

Install Kornia

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
+

Prepare the data

+

The low contrast color image used in this tutorial can be downloaded here (By Biem (Own work) [Public domain], via Wikimedia Commons)

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+download_image("https://github.com/cvg/SOLD2/raw/main/assets/images/terrace0.JPG")
+download_image("http://www.ic.unicamp.br/~helio/imagens_registro/foto1B.jpg")
+download_image("https://imagemagick.org/image/mountains.jpg", "mountains.jpg")
+
+
'mountains.jpg'
+
+
+
+
from typing import List, Tuple
+
+import cv2
+import kornia as K
+import numpy as np
+import torch
+import torchvision
+from matplotlib import pyplot as plt
+
+

Image show functionality

+
+
def imshow(input: torch.Tensor, height: int, width: int):
+    out: torch.Tensor = torchvision.utils.make_grid(input, nrow=2)
+    out_np: np.array = K.utils.tensor_to_image(out)
+    plt.figure(figsize=(height, width))
+    plt.imshow(out_np)
+    plt.axis("off");
+
+

Image read functionality

+
+
def imread(data_path: str) -> torch.Tensor:
+    """Utility function that load an image an convert to torch."""
+    img_t = K.io.load_image(data_path, K.io.ImageLoadType.RGB32)
+    img_t = K.geometry.resize(img_t, 1200, side="long", align_corners=True)[..., :600, :]
+
+    return img_t[None, ...]
+
+

Image and histogram plot functionality

+
+
def histogram_img(img_t: torch.Tensor, size: Tuple[int, int] = (16, 4)):
+    CH, H, W = img_t.shape
+    img = K.utils.tensor_to_image(img_t.mul(255.0).byte())
+
+    plt.figure(figsize=size)
+    ax1 = plt.subplot(1, 2, 1)
+    ax2 = plt.subplot(1, 2, 2)
+
+    colors = ("b", "g", "r")
+    kwargs = dict(histtype="stepfilled", alpha=0.3, density=True, ec="k")
+
+    for i in range(CH):
+        img_vec = img[..., i].flatten()
+        ax2.hist(img_vec, range=(0, 255), bins=256, color=colors[i], **kwargs)
+
+    ax1.imshow(img, cmap=(None if CH > 1 else "gray"))
+    ax1.grid(False)
+    ax1.axis("off")
+
+    plt.show()
+
+

Load the images in batch using OpenCV and show them as a grid.

+
+
img_rgb_list: List[torch.Tensor] = []
+img_rgb_list.append(imread("terrace0.JPG"))
+img_rgb_list.append(imread("mountains.jpg"))
+img_rgb_list.append(imread("foto1B.jpg"))
+
+
+# cast to torch.Tensor
+img_rgb: torch.Tensor = torch.cat(img_rgb_list, dim=0)
+print(f"Image tensor shape: {img_rgb.shape}")
+
+# Disable the line below to make everything happen in the GPU !
+# img_rbg = img_rbg.cuda()
+
+imshow(img_rgb, 10, 10)  # plot grid !
+
+
Image tensor shape: torch.Size([3, 3, 600, 1200])
+
+
+

+
+
+
+
+

Image Histogram

+

Definition - An image histogram is a type of histogram that acts as a graphical representation of the tonal distribution in a digital image.[1] It plots the number of pixels for each tonal value. By looking at the histogram for a specific image a viewer will be able to judge the entire tonal distribution at a glance Read more - Wikipedia.

+

In short - An image histogram is: - It is a graphical representation of the intensity distribution of an image. - It quantifies the number of pixels for each intensity value considered.

+

See also OpenCV tutorial: https://docs.opencv.org/master/d4/d1b/tutorial_histogram_equalization.html

+
+

Lightness with Kornia

+

We first will compute the histogram of the lightness of the image. To do so, we compute first the color space Lab and take the first component known as luminance that reflects the lightness of the scene.

+

Notice that kornia Lab representation is in the range of [0, 100] and for convenience to plot the histogram we will normalize the image between [0, 1].

+

Note: kornia computes in batch, for convenience we show only one image result. That’s it - modify below the plot_indices variable to explore the results of the batch.

+
+
plot_indices: int = 0  # try: [0, 1, 2, 3]
+
+

Tip: replace K.color.rgb_to_lab by K.color.rgb_to_grayscale to see the pixel distribution in the grayscale color space. Explore also kornia.color for more exotic color spaces.

+
+
img_lab: torch.Tensor = K.color.rgb_to_lab(img_rgb)
+lightness: torch.Tensor = img_lab[..., :1, :, :] / 100.0  # L in lab is in range [0, 100]
+
+histogram_img(lightness[plot_indices])
+
+

+
+
+
+
+

RGB histogram with Kornia

+

Similar to above - you can just visualize the three (red, green, blue) channels pixel distribution.

+
+
Tip - Use as follows to visualize a single channel (green)
+
+histogram_img(img_rgb[plot_indices, 1:2]) +
+
+
+
histogram_img(img_rgb[plot_indices])
+
+

+
+
+
+
+
+

Histogram stretching

+

Sometimes our images have a pixel distribution that is not suitable for our application, being biased to a certain range depending on the illumination of the scene.

+

In the next sections, we are going to show a couple of techniques to solve those issues. We will start with a basic technique to normalize the image by its minimum and maximum values with the objective to strecth the image histrogram.

+
+

on the lightness with Kornia

+

We use kornina.enhance.normalize_min_max to normalize the image Luminance. Note: compare the histrograms with the original image.

+

Tip - play with the other functions from kornia.enhance to modify the intensity values of the image and thus its histograms.

+
+
lightness_stretched = K.enhance.normalize_min_max(lightness)
+histogram_img(lightness_stretched[plot_indices])
+
+

+
+
+

In order to properly visualize the effect of the normalization in the color histogram, we take the normalized Luminance and use it to cast back to RGB.

+
+
img_rgb_new = K.color.lab_to_rgb(torch.cat([lightness_stretched * 100.0, img_lab[:, 1:]], dim=1))
+histogram_img(img_rgb_new[plot_indices])
+
+

+
+
+
+
+

on the RGB with Kornia

+

In this case we normalize each channel independently where we can see that resulting image is not as clear as the one only stretching the Luminance.

+
+
rgb_stretched = K.enhance.normalize_min_max(img_rgb)
+histogram_img(rgb_stretched[plot_indices])
+
+

+
+
+
+
+
+

Histogram Equalization

+

A more advanced technique to improve the pixel distribution is the so called Histogram equalization - a method in image processing of contrast adjustment using the image’s histogram [Read more - Wikipedia].

+

In kornia we have implemented in terms of torch tensor to equalize the images in batch and the gpu very easily.

+
+

on the lightness with Kornia

+
+
lightness_equalized = K.enhance.equalize(lightness)
+histogram_img(lightness_equalized[plot_indices])
+
+

+
+
+

We convert back from Lab to RGB using the equalized Luminance and visualize the histogram of the RGB.

+
+
img_rgb_new = K.color.lab_to_rgb(torch.cat([lightness_equalized * 100.0, img_lab[:, 1:]], dim=1))
+histogram_img(img_rgb_new[plot_indices])
+
+

+
+
+
+
+

on the RGB with Kornia

+
+
rgb_equalized = K.enhance.equalize(img_rgb)
+histogram_img(rgb_equalized[plot_indices])
+
+

+
+
+
+
+

on the RGB with OpenCV

+

Just to compare against OpenCV - close results :)

+
+
rgb_equalized_cv = []
+for img in img_rgb:
+    equ00 = torch.tensor(cv2.equalizeHist(K.utils.tensor_to_image(img[0].mul(255).clamp(0, 255).byte())))
+    equ01 = torch.tensor(cv2.equalizeHist(K.utils.tensor_to_image(img[1].mul(255).clamp(0, 255).byte())))
+    equ02 = torch.tensor(cv2.equalizeHist(K.utils.tensor_to_image(img[2].mul(255).clamp(0, 255).byte())))
+    rgb_equalized_cv.append(torch.stack([equ00, equ01, equ02]))
+rgb_equalized_cv = torch.stack(rgb_equalized_cv)
+
+histogram_img(rgb_equalized_cv[plot_indices] / 255.0)
+
+

+
+
+
+
+
+

Adaptive Histogram Equalization

+

Adaptive histogram equalization (AHE) is a computer image processing technique used to improve contrast in images. It differs from ordinary histogram equalization in the respect that the adaptive method computes several histograms, each corresponding to a distinct section of the image, and uses them to redistribute the lightness values of the image. It is therefore suitable for improving the local contrast and enhancing the definitions of edges in each region of an image [Read more - Wikipedia].

+
+

on the lightness with Kornia

+

We will use kornia.enhance.equalize_clahe and by playing with the clip_limit and grid_size variables to produce different effects to the image.

+
+
lightness_equalized = K.enhance.equalize_clahe(lightness, clip_limit=0.0)
+histogram_img(lightness_equalized[plot_indices])
+
+

+
+
+

We convert back from Lab to RGB using the equalized Luminance and visualize the histogram of the RGB.

+
+
img_rgb_new = K.color.lab_to_rgb(torch.cat([lightness_equalized * 100.0, img_lab[:, 1:]], dim=1))
+histogram_img(img_rgb_new[plot_indices])
+
+

+
+
+
+
+

on the RGB with Kornia

+
+
rgb_equalized = K.enhance.equalize_clahe(img_rgb, clip_limit=0.0)
+histogram_img(rgb_equalized[plot_indices])
+
+

+
+
+
+
+
+

Contrast Limited Adaptive Histogram Equalization (CLAHE)

+

An improvement of the algorithm is CLAHE that divides the image into small blocks and controlled by the variable grid_size. This means, that the equalization is performed locally in each of the NxM sublocks to obtain a better distribution of the pixel values.

+
+

on the lightness with Kornia

+
+
lightness_equalized = K.enhance.equalize_clahe(lightness, clip_limit=20.0, grid_size=(8, 8))
+histogram_img(lightness_equalized[plot_indices])
+
+

+
+
+

We convert back from Lab to RGB using the equalized Luminance and visualize the histogram of the RGB.

+
+
img_rgb_new = K.color.lab_to_rgb(torch.cat([lightness_equalized * 100.0, img_lab[:, 1:]], dim=1))
+histogram_img(img_rgb_new[plot_indices])
+
+

+
+
+
+
+

on the RGB with Kornia

+

We directly equalize all the RGB channels at once

+
+
rgb_equalized = K.enhance.equalize_clahe(img_rgb, clip_limit=20.0, grid_size=(8, 8))
+histogram_img(rgb_equalized[plot_indices])
+
+

+
+
+
+
+

on the RGB with OpenCV

+
+
imgs = []
+clahe = cv2.createCLAHE(clipLimit=20.0, tileGridSize=(8, 8))
+for im in img_rgb:
+    # equalize channels independently as gray scale images
+    equ00 = torch.tensor(clahe.apply(K.utils.tensor_to_image(im[0].mul(255).clamp(0, 255).byte())))
+    equ01 = torch.tensor(clahe.apply(K.utils.tensor_to_image(im[1].mul(255).clamp(0, 255).byte())))
+    equ02 = torch.tensor(clahe.apply(K.utils.tensor_to_image(im[2].mul(255).clamp(0, 255).byte())))
+    imgs.append(torch.stack([equ00, equ01, equ02]))
+imgs = torch.stack(imgs)
+
+histogram_img(imgs[plot_indices] / 255.0)
+
+

+
+
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/image_histogram_files/figure-html/cell-10-output-1.png b/nbs/image_histogram_files/figure-html/cell-10-output-1.png new file mode 100644 index 0000000..eb7a2f6 Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-10-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-11-output-1.png b/nbs/image_histogram_files/figure-html/cell-11-output-1.png new file mode 100644 index 0000000..4f8d85d Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-11-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-12-output-1.png b/nbs/image_histogram_files/figure-html/cell-12-output-1.png new file mode 100644 index 0000000..4075473 Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-12-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-13-output-1.png b/nbs/image_histogram_files/figure-html/cell-13-output-1.png new file mode 100644 index 0000000..bef86d9 Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-13-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-14-output-1.png b/nbs/image_histogram_files/figure-html/cell-14-output-1.png new file mode 100644 index 0000000..15dca7a Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-14-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-15-output-1.png b/nbs/image_histogram_files/figure-html/cell-15-output-1.png new file mode 100644 index 0000000..1f8c951 Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-15-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-16-output-1.png b/nbs/image_histogram_files/figure-html/cell-16-output-1.png new file mode 100644 index 0000000..3610aee Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-16-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-17-output-1.png b/nbs/image_histogram_files/figure-html/cell-17-output-1.png new file mode 100644 index 0000000..b3061e2 Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-17-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-18-output-1.png b/nbs/image_histogram_files/figure-html/cell-18-output-1.png new file mode 100644 index 0000000..043e6c9 Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-18-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-19-output-1.png b/nbs/image_histogram_files/figure-html/cell-19-output-1.png new file mode 100644 index 0000000..af02ad1 Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-19-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-20-output-1.png b/nbs/image_histogram_files/figure-html/cell-20-output-1.png new file mode 100644 index 0000000..767d6fb Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-20-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-21-output-1.png b/nbs/image_histogram_files/figure-html/cell-21-output-1.png new file mode 100644 index 0000000..d8d0d59 Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-21-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-22-output-1.png b/nbs/image_histogram_files/figure-html/cell-22-output-1.png new file mode 100644 index 0000000..85e068d Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-22-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-23-output-1.png b/nbs/image_histogram_files/figure-html/cell-23-output-1.png new file mode 100644 index 0000000..89bb257 Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-23-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-24-output-1.png b/nbs/image_histogram_files/figure-html/cell-24-output-1.png new file mode 100644 index 0000000..8e83bfe Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-24-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-25-output-1.png b/nbs/image_histogram_files/figure-html/cell-25-output-1.png new file mode 100644 index 0000000..3dbc1e2 Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-25-output-1.png differ diff --git a/nbs/image_histogram_files/figure-html/cell-8-output-2.png b/nbs/image_histogram_files/figure-html/cell-8-output-2.png new file mode 100644 index 0000000..5440320 Binary files /dev/null and b/nbs/image_histogram_files/figure-html/cell-8-output-2.png differ diff --git a/nbs/image_matching.html b/nbs/image_matching.html new file mode 100644 index 0000000..7a9e21e --- /dev/null +++ b/nbs/image_matching.html @@ -0,0 +1,820 @@ + + + + + + + + + + + + +Kornia - Image matching example with LoFTR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Image matching example with LoFTR

+
+
Intermediate
+
LoFTR
+
LAF
+
Image matching
+
kornia.feature
+
+
+ +
+
+ In this tutorial we are going to show how to perform image matching using a LoFTR algorithm +
+
+ + +
+ +
+
Author
+
+

Dmytro Mishkin

+
+
+ +
+
Published
+
+

September 11, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+

First, we will install everything needed:

+ +
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+!pip install kornia_moons
+!pip install opencv-python --upgrade
+
+

Now let’s download an image pair

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+
url_a = "https://github.com/kornia/data/raw/main/matching/kn_church-2.jpg"
+url_b = "https://github.com/kornia/data/raw/main/matching/kn_church-8.jpg"
+
+download_image(url_a)
+download_image(url_b)
+
+
'kn_church-8.jpg'
+
+
+

First, we will define image matching pipeline with OpenCV SIFT features. We will also use kornia for the state-of-the-art match filtering – Lowe ratio + mutual nearest neighbor check and MAGSAC++ as RANSAC.

+
+
import cv2
+import kornia as K
+import kornia.feature as KF
+import matplotlib.pyplot as plt
+import numpy as np
+import torch
+from kornia_moons.viz import draw_LAF_matches
+
+
+
%%capture
+fname1 = "kn_church-2.jpg"
+fname2 = "kn_church-8.jpg"
+
+img1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32)[None, ...]
+img2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32)[None, ...]
+
+img1 = K.geometry.resize(img1, (600, 375), antialias=True)
+img2 = K.geometry.resize(img2, (600, 375), antialias=True)
+
+
+matcher = KF.LoFTR(pretrained="outdoor")
+
+input_dict = {
+    "image0": K.color.rgb_to_grayscale(img1),  # LofTR works on grayscale images only
+    "image1": K.color.rgb_to_grayscale(img2),
+}
+
+with torch.inference_mode():
+    correspondences = matcher(input_dict)
+
+
+
for k, v in correspondences.items():
+    print(k)
+
+
keypoints0
+keypoints1
+confidence
+batch_indexes
+
+
+

Now let’s clean-up the correspondences with modern RANSAC and estimate fundamental matrix between two images

+
+
mkpts0 = correspondences["keypoints0"].cpu().numpy()
+mkpts1 = correspondences["keypoints1"].cpu().numpy()
+Fm, inliers = cv2.findFundamentalMat(mkpts0, mkpts1, cv2.USAC_MAGSAC, 0.5, 0.999, 100000)
+inliers = inliers > 0
+
+

Finally, let’s draw the matches with a function from kornia_moons. The correct matches are in green and imprecise matches - in blue

+
+
draw_LAF_matches(
+    KF.laf_from_center_scale_ori(
+        torch.from_numpy(mkpts0).view(1, -1, 2),
+        torch.ones(mkpts0.shape[0]).view(1, -1, 1, 1),
+        torch.ones(mkpts0.shape[0]).view(1, -1, 1),
+    ),
+    KF.laf_from_center_scale_ori(
+        torch.from_numpy(mkpts1).view(1, -1, 2),
+        torch.ones(mkpts1.shape[0]).view(1, -1, 1, 1),
+        torch.ones(mkpts1.shape[0]).view(1, -1, 1),
+    ),
+    torch.arange(mkpts0.shape[0]).view(-1, 1).repeat(1, 2),
+    K.tensor_to_image(img1),
+    K.tensor_to_image(img2),
+    inliers,
+    draw_dict={"inlier_color": (0.2, 1, 0.2), "tentative_color": None, "feature_color": (0.2, 0.5, 1), "vertical": False},
+)
+
+

+
+
+
+

LoFTR Indoor

+

We recommend to use KF.LoFTR(pretrained='indoor_new') and resize images to be not bigger than 640x480 pixels for the indoor model.

+
+
%%capture
+
+download_image("https://github.com/zju3dv/LoFTR/raw/master/assets/scannet_sample_images/scene0711_00_frame-001680.jpg")
+download_image("https://github.com/zju3dv/LoFTR/raw/master/assets/scannet_sample_images/scene0711_00_frame-001995.jpg")
+
+matcher = KF.LoFTR(pretrained="indoor_new")
+
+
+
fname1 = "scene0711_00_frame-001680.jpg"
+fname2 = "scene0711_00_frame-001995.jpg"
+
+img1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32)[None, ...]
+img2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32)[None, ...]
+
+img1 = K.geometry.resize(img1, (480, 640), antialias=True)
+img2 = K.geometry.resize(img2, (480, 640), antialias=True)
+
+matcher = KF.LoFTR(pretrained="indoor_new")
+
+input_dict = {
+    "image0": K.color.rgb_to_grayscale(img1),  # LofTR works on grayscale images only
+    "image1": K.color.rgb_to_grayscale(img2),
+}
+
+with torch.inference_mode():
+    correspondences = matcher(input_dict)
+
+mkpts0 = correspondences["keypoints0"].cpu().numpy()
+mkpts1 = correspondences["keypoints1"].cpu().numpy()
+Fm, inliers = cv2.findFundamentalMat(mkpts0, mkpts1, cv2.USAC_MAGSAC, 1.0, 0.999, 100000)
+inliers = inliers > 0
+
+draw_LAF_matches(
+    KF.laf_from_center_scale_ori(
+        torch.from_numpy(mkpts0).view(1, -1, 2),
+        torch.ones(mkpts0.shape[0]).view(1, -1, 1, 1),
+        torch.ones(mkpts0.shape[0]).view(1, -1, 1),
+    ),
+    KF.laf_from_center_scale_ori(
+        torch.from_numpy(mkpts1).view(1, -1, 2),
+        torch.ones(mkpts1.shape[0]).view(1, -1, 1, 1),
+        torch.ones(mkpts1.shape[0]).view(1, -1, 1),
+    ),
+    torch.arange(mkpts0.shape[0]).view(-1, 1).repeat(1, 2),
+    K.tensor_to_image(img1),
+    K.tensor_to_image(img2),
+    inliers,
+    draw_dict={
+        "inlier_color": (0.2, 1, 0.2),
+        "tentative_color": (1.0, 0.5, 1),
+        "feature_color": (0.2, 0.5, 1),
+        "vertical": False,
+    },
+)
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/image_matching_adalam.html b/nbs/image_matching_adalam.html new file mode 100644 index 0000000..e3582b0 --- /dev/null +++ b/nbs/image_matching_adalam.html @@ -0,0 +1,763 @@ + + + + + + + + + + + + +Kornia - Image matching example with KeyNet-AdaLAM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Image matching example with KeyNet-AdaLAM

+
+
Intermediate
+
KeyNet
+
LAF
+
Adalam
+
Image matching
+
kornia.feature
+
+
+ +
+
+ In this tutorial we are going to show how to perform image matching using a KeyNet-Adalam Algorithm +
+
+ + +
+ +
+
Author
+
+

Dmytro Mishkin

+
+
+ +
+
Published
+
+

August 22, 2022

+
+
+ + +
+ + +
+ +

Open in google colab

+

First, we will install everything needed:

+ +

Docs: match_adalam

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+!pip install kornia_moons
+!pip install opencv-python --upgrade
+
+

Now let’s download an image pair

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url_a = "https://github.com/kornia/data/raw/main/matching/kn_church-2.jpg"
+url_b = "https://github.com/kornia/data/raw/main/matching/kn_church-8.jpg"
+download_image(url_a)
+download_image(url_b)
+
+
'kn_church-8.jpg'
+
+
+

First, imports.

+
+
import cv2
+import kornia as K
+import kornia.feature as KF
+import matplotlib.pyplot as plt
+import numpy as np
+import torch
+from kornia_moons.viz import *
+
+# device = K.utils.get_cuda_or_mps_device_if_available()
+device = torch.device("cpu")
+
+
+
%%capture
+fname1 = "kn_church-2.jpg"
+fname2 = "kn_church-8.jpg"
+
+img1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32, device=device)[None, ...]
+img2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32, device=device)[None, ...]
+
+feature = KF.KeyNetAffNetHardNet(5000, True).eval().to(device)
+
+input_dict = {
+    "image0": K.color.rgb_to_grayscale(img1),  # LofTR works on grayscale images only
+    "image1": K.color.rgb_to_grayscale(img2),
+}
+
+hw1 = torch.tensor(img1.shape[2:])
+hw2 = torch.tensor(img1.shape[2:])
+
+adalam_config = {"device": device}
+
+with torch.inference_mode():
+    lafs1, resps1, descs1 = feature(K.color.rgb_to_grayscale(img1))
+    lafs2, resps2, descs2 = feature(K.color.rgb_to_grayscale(img2))
+    dists, idxs = KF.match_adalam(
+        descs1.squeeze(0),
+        descs2.squeeze(0),
+        lafs1,
+        lafs2,  # Adalam takes into account also geometric information
+        config=adalam_config,
+        hw1=hw1,
+        hw2=hw2,  # Adalam also benefits from knowing image size
+    )
+
+
+
print(f"{idxs.shape[0]} tentative matches with AdaLAM")
+
+
405 tentative matches with AdaLAM
+
+
+
+
def get_matching_keypoints(lafs1, lafs2, idxs):
+    mkpts1 = KF.get_laf_center(lafs1).squeeze()[idxs[:, 0]].detach().cpu().numpy()
+    mkpts2 = KF.get_laf_center(lafs2).squeeze()[idxs[:, 1]].detach().cpu().numpy()
+    return mkpts1, mkpts2
+
+
+mkpts1, mkpts2 = get_matching_keypoints(lafs1, lafs2, idxs)
+
+Fm, inliers = cv2.findFundamentalMat(mkpts1, mkpts2, cv2.USAC_MAGSAC, 0.75, 0.999, 100000)
+inliers = inliers > 0
+print(f"{inliers.sum()} inliers with AdaLAM")
+
+
195 inliers with AdaLAM
+
+
+

Let’s draw the inliers in green and tentative correspondences in yellow

+
+
draw_LAF_matches(
+    lafs1,
+    lafs2,
+    idxs,
+    K.tensor_to_image(img1),
+    K.tensor_to_image(img2),
+    inliers,
+    draw_dict={"inlier_color": (0.2, 1, 0.2), "tentative_color": (1, 1, 0.2, 0.3), "feature_color": None, "vertical": False},
+)
+
+

+
+
+ + + +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/image_matching_adalam_files/figure-html/cell-8-output-1.png b/nbs/image_matching_adalam_files/figure-html/cell-8-output-1.png new file mode 100644 index 0000000..b4d55f8 Binary files /dev/null and b/nbs/image_matching_adalam_files/figure-html/cell-8-output-1.png differ diff --git a/nbs/image_matching_disk.html b/nbs/image_matching_disk.html new file mode 100644 index 0000000..d3088c8 --- /dev/null +++ b/nbs/image_matching_disk.html @@ -0,0 +1,772 @@ + + + + + + + + + + + + +Kornia - Image matching example with DISK local features + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Image matching example with DISK local features

+
+
Intermediate
+
DISK
+
LAF
+
Image matching
+
kornia.feature
+
+
+ +
+
+ In this tutorial we are going to show how to perform image matching using a DISK algorithm +
+
+ + +
+ +
+
Author
+
+

Dmytro Mishkin

+
+
+ +
+
Published
+
+

April 1, 2023

+
+
+ + +
+ + +
+ +

Open in google colab

+

First, we will install everything needed:

+ +

Docs: kornia.feature.DISK

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+!pip install kornia_moons --no-deps
+!pip install opencv-python --upgrade
+
+

Now let’s download an image pair

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url_a = "https://github.com/kornia/data/raw/main/matching/kn_church-2.jpg"
+url_b = "https://github.com/kornia/data/raw/main/matching/kn_church-8.jpg"
+download_image(url_a)
+download_image(url_b)
+
+
'kn_church-8.jpg'
+
+
+

First, imports.

+
+
import cv2
+import kornia as K
+import kornia.feature as KF
+import matplotlib.pyplot as plt
+import numpy as np
+import torch
+from kornia.feature.adalam import AdalamFilter
+from kornia_moons.viz import *
+
+device = K.utils.get_cuda_or_mps_device_if_available()
+print(device)
+
+
cuda:0
+
+
+
+
# %%capture
+fname1 = "kn_church-2.jpg"
+fname2 = "kn_church-8.jpg"
+
+adalam_config = KF.adalam.get_adalam_default_config()
+# adalam_config['orientation_difference_threshold'] = None
+# adalam_config['scale_rate_threshold'] = None
+adalam_config["force_seed_mnn"] = False
+adalam_config["search_expansion"] = 16
+adalam_config["ransac_iters"] = 256
+
+
+img1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32, device=device)[None, ...]
+img2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32, device=device)[None, ...]
+
+num_features = 2048
+disk = KF.DISK.from_pretrained("depth").to(device)
+
+hw1 = torch.tensor(img1.shape[2:], device=device)
+hw2 = torch.tensor(img2.shape[2:], device=device)
+
+match_with_adalam = True
+
+with torch.inference_mode():
+    inp = torch.cat([img1, img2], dim=0)
+    features1, features2 = disk(inp, num_features, pad_if_not_divisible=True)
+    kps1, descs1 = features1.keypoints, features1.descriptors
+    kps2, descs2 = features2.keypoints, features2.descriptors
+    if match_with_adalam:
+        lafs1 = KF.laf_from_center_scale_ori(kps1[None], 96 * torch.ones(1, len(kps1), 1, 1, device=device))
+        lafs2 = KF.laf_from_center_scale_ori(kps2[None], 96 * torch.ones(1, len(kps2), 1, 1, device=device))
+
+        dists, idxs = KF.match_adalam(descs1, descs2, lafs1, lafs2, hw1=hw1, hw2=hw2, config=adalam_config)
+    else:
+        dists, idxs = KF.match_smnn(descs1, descs2, 0.98)
+
+
+print(f"{idxs.shape[0]} tentative matches with DISK AdaLAM")
+
+
222 tentative matches with DISK AdaLAM
+
+
+
+
def get_matching_keypoints(kp1, kp2, idxs):
+    mkpts1 = kp1[idxs[:, 0]]
+    mkpts2 = kp2[idxs[:, 1]]
+    return mkpts1, mkpts2
+
+
+mkpts1, mkpts2 = get_matching_keypoints(kps1, kps2, idxs)
+
+Fm, inliers = cv2.findFundamentalMat(
+    mkpts1.detach().cpu().numpy(), mkpts2.detach().cpu().numpy(), cv2.USAC_MAGSAC, 1.0, 0.999, 100000
+)
+inliers = inliers > 0
+print(f"{inliers.sum()} inliers with DISK")
+
+
103 inliers with DISK
+
+
+

Let’s draw the inliers in green and tentative correspondences in yellow

+
+
draw_LAF_matches(
+    KF.laf_from_center_scale_ori(kps1[None].cpu()),
+    KF.laf_from_center_scale_ori(kps2[None].cpu()),
+    idxs.cpu(),
+    K.tensor_to_image(img1.cpu()),
+    K.tensor_to_image(img2.cpu()),
+    inliers,
+    draw_dict={"inlier_color": (0.2, 1, 0.2), "tentative_color": (1, 1, 0.2, 0.3), "feature_color": None, "vertical": False},
+)
+
+

+
+
+ + + +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/image_matching_disk_files/figure-html/cell-7-output-1.png b/nbs/image_matching_disk_files/figure-html/cell-7-output-1.png new file mode 100644 index 0000000..07751f8 Binary files /dev/null and b/nbs/image_matching_disk_files/figure-html/cell-7-output-1.png differ diff --git a/nbs/image_matching_files/figure-html/cell-11-output-1.png b/nbs/image_matching_files/figure-html/cell-11-output-1.png new file mode 100644 index 0000000..b3e45d2 Binary files /dev/null and b/nbs/image_matching_files/figure-html/cell-11-output-1.png differ diff --git a/nbs/image_matching_files/figure-html/cell-9-output-1.png b/nbs/image_matching_files/figure-html/cell-9-output-1.png new file mode 100644 index 0000000..1c772ff Binary files /dev/null and b/nbs/image_matching_files/figure-html/cell-9-output-1.png differ diff --git a/nbs/image_matching_lightglue.html b/nbs/image_matching_lightglue.html new file mode 100644 index 0000000..d674141 --- /dev/null +++ b/nbs/image_matching_lightglue.html @@ -0,0 +1,785 @@ + + + + + + + + + + + + +Kornia - Image matching example with LightGlue and DISK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Image matching example with LightGlue and DISK

+
+
Intermediate
+
LightGlue
+
Disk
+
LAF
+
Image matching
+
kornia.feature
+
+
+ +
+
+ In this tutorial we are going to show how to perform image matching using a LightGlue algorithm with DISK +
+
+ + +
+ +
+
Author
+
+

Dmytro Mishkin

+
+
+ +
+
Published
+
+

May 11, 2024

+
+
+ + +
+ + +
+ +

Open in google colab

+

First, we will install everything needed:

+ +

Docs: kornia.feature.DISK

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+!pip install kornia_moons --no-deps
+!pip install opencv-python --upgrade
+
+

Now let’s download an image pair

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url_a = "https://github.com/kornia/data/raw/main/matching/kn_church-2.jpg"
+url_b = "https://github.com/kornia/data/raw/main/matching/kn_church-8.jpg"
+download_image(url_a)
+download_image(url_b)
+
+
'kn_church-8.jpg'
+
+
+

First, imports.

+
+
import cv2
+import kornia as K
+import kornia.feature as KF
+import matplotlib.pyplot as plt
+import numpy as np
+import torch
+from kornia.feature.adalam import AdalamFilter
+from kornia_moons.viz import *
+
+device = K.utils.get_cuda_or_mps_device_if_available()
+print(device)
+
+
mps
+
+
+

Here we show how to use LightGlue with provided kornia LightGlueMatcher interface

+
+
# %%capture
+fname1 = "kn_church-2.jpg"
+fname2 = "kn_church-8.jpg"
+
+lg_matcher = KF.LightGlueMatcher("disk").eval().to(device)
+
+
+img1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32, device=device)[None, ...]
+img2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32, device=device)[None, ...]
+
+num_features = 2048
+disk = KF.DISK.from_pretrained("depth").to(device)
+
+hw1 = torch.tensor(img1.shape[2:], device=device)
+hw2 = torch.tensor(img2.shape[2:], device=device)
+
+
+with torch.inference_mode():
+    inp = torch.cat([img1, img2], dim=0)
+    features1, features2 = disk(inp, num_features, pad_if_not_divisible=True)
+    kps1, descs1 = features1.keypoints, features1.descriptors
+    kps2, descs2 = features2.keypoints, features2.descriptors
+    lafs1 = KF.laf_from_center_scale_ori(kps1[None], torch.ones(1, len(kps1), 1, 1, device=device))
+    lafs2 = KF.laf_from_center_scale_ori(kps2[None], torch.ones(1, len(kps2), 1, 1, device=device))
+    dists, idxs = lg_matcher(descs1, descs2, lafs1, lafs2, hw1=hw1, hw2=hw2)
+
+
+print(f"{idxs.shape[0]} tentative matches with DISK LightGlue")
+
+
Loaded LightGlue model
+
+
+

And here the same with original LightGlue object

+
+
lg = KF.LightGlue("disk").to(device).eval()
+
+image0 = {
+    "keypoints": features1.keypoints[None],
+    "descriptors": features1.descriptors[None],
+    "image_size": torch.tensor(img1.shape[-2:][::-1]).view(1, 2).to(device),
+}
+image1 = {
+    "keypoints": features2.keypoints[None],
+    "descriptors": features2.descriptors[None],
+    "image_size": torch.tensor(img2.shape[-2:][::-1]).view(1, 2).to(device),
+}
+
+with torch.inference_mode():
+    out = lg({"image0": image0, "image1": image1})
+    idxs = out["matches"][0]
+    print(f"{idxs.shape[0]} tentative matches with DISK LightGlue")
+
+
724 tentative matches with DISK LightGlue
+
+
+

RANSAC to get fundamental matrix

+
+
def get_matching_keypoints(kp1, kp2, idxs):
+    mkpts1 = kp1[idxs[:, 0]]
+    mkpts2 = kp2[idxs[:, 1]]
+    return mkpts1, mkpts2
+
+
+mkpts1, mkpts2 = get_matching_keypoints(kps1, kps2, idxs)
+
+Fm, inliers = cv2.findFundamentalMat(
+    mkpts1.detach().cpu().numpy(), mkpts2.detach().cpu().numpy(), cv2.USAC_MAGSAC, 1.0, 0.999, 100000
+)
+inliers = inliers > 0
+print(f"{inliers.sum()} inliers with DISK")
+
+

Let’s draw the inliers in green and tentative correspondences in yellow

+
+
draw_LAF_matches(
+    KF.laf_from_center_scale_ori(kps1[None].cpu()),
+    KF.laf_from_center_scale_ori(kps2[None].cpu()),
+    idxs.cpu(),
+    K.tensor_to_image(img1.cpu()),
+    K.tensor_to_image(img2.cpu()),
+    inliers,
+    draw_dict={"inlier_color": (0.2, 1, 0.2), "tentative_color": (1, 1, 0.2, 0.3), "feature_color": None, "vertical": False},
+)
+
+

+
+
+ + + +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/image_matching_lightglue_files/figure-html/cell-8-output-1.png b/nbs/image_matching_lightglue_files/figure-html/cell-8-output-1.png new file mode 100644 index 0000000..acaec63 Binary files /dev/null and b/nbs/image_matching_lightglue_files/figure-html/cell-8-output-1.png differ diff --git a/nbs/image_points_transforms.html b/nbs/image_points_transforms.html new file mode 100644 index 0000000..b5d177d --- /dev/null +++ b/nbs/image_points_transforms.html @@ -0,0 +1,767 @@ + + + + + + + + + + + + +Kornia - Image and Keypoints augmentations + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Image and Keypoints augmentations

+
+
Intermediate
+
Keypoints
+
Data augmentation
+
2D
+
Augmentation container
+
Augmentation Sequential
+
kornia.augmentation
+
+
+ +
+
+ In this tutorial we leverage kornia.augmentation.AugmentationSequential to apply augmentations to image and transform reusing the applied geometric transformation to a set of associated keypoints. This is useful for detection networks or geometric problems. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

October 14, 2022

+
+
+ + +
+ + +
+ +

Open in google colab

+
+

Install and get data

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs matplotlib
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+download_image("https://github.com/kornia/data/raw/main/arturito.jpg")
+
+
'arturito.jpg'
+
+
+
+
import kornia as K
+import torch
+from matplotlib import pyplot as plt
+
+
+
img = K.io.load_image("arturito.jpg", K.io.ImageLoadType.RGB32)
+img = img[None]  # 1xCxHxW / fp32 / [0, 1]
+print(img.shape)
+
+
torch.Size([1, 3, 144, 256])
+
+
+
+
+

Draw points and show image

+
+
coords = torch.tensor([[[125, 40.0], [185.0, 75.0]]])  # BxNx2 [x,y]
+
+fig, ax = plt.subplots()
+
+ax.add_patch(plt.Circle((coords[0, 0, 0], coords[0, 0, 1]), color="r"))
+ax.add_patch(plt.Circle((coords[0, 1, 0], coords[0, 1, 1]), color="r"))
+
+ax.imshow(K.tensor_to_image(img))
+
+
<matplotlib.image.AxesImage>
+
+
+

+
+
+
+
+

Resize points and show

+
+
resize_op = K.augmentation.AugmentationSequential(
+    K.augmentation.Resize((100, 200), antialias=True), data_keys=["input", "keypoints"]
+)
+
+print(resize_op.transform_matrix)
+
+img_resize, coords_resize = resize_op(img, coords)
+
+
+fig, ax = plt.subplots()
+
+ax.add_patch(plt.Circle((coords_resize[0, 0, 0], coords_resize[0, 0, 1]), color="r"))
+ax.add_patch(plt.Circle((coords_resize[0, 1, 0], coords_resize[0, 1, 1]), color="r"))
+
+ax.imshow(K.tensor_to_image(img_resize))
+
+
None
+
+
+
<matplotlib.image.AxesImage>
+
+
+

+
+
+
+
+

Crop image and points

+
+
crop_op = K.augmentation.AugmentationSequential(K.augmentation.CenterCrop((100, 200)), data_keys=["input", "keypoints"])
+print(crop_op.transform_matrix)
+
+img_resize, coords_resize = crop_op(img, coords)
+
+
+fig, ax = plt.subplots()
+
+ax.add_patch(plt.Circle((coords_resize[0, 0, 0], coords_resize[0, 0, 1]), color="r"))
+ax.add_patch(plt.Circle((coords_resize[0, 1, 0], coords_resize[0, 1, 1]), color="r"))
+
+ax.imshow(K.tensor_to_image(img_resize))
+
+
None
+
+
+
<matplotlib.image.AxesImage>
+
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/image_points_transforms_files/figure-html/cell-6-output-2.png b/nbs/image_points_transforms_files/figure-html/cell-6-output-2.png new file mode 100644 index 0000000..6465e13 Binary files /dev/null and b/nbs/image_points_transforms_files/figure-html/cell-6-output-2.png differ diff --git a/nbs/image_points_transforms_files/figure-html/cell-7-output-3.png b/nbs/image_points_transforms_files/figure-html/cell-7-output-3.png new file mode 100644 index 0000000..62aa5b4 Binary files /dev/null and b/nbs/image_points_transforms_files/figure-html/cell-7-output-3.png differ diff --git a/nbs/image_points_transforms_files/figure-html/cell-8-output-3.png b/nbs/image_points_transforms_files/figure-html/cell-8-output-3.png new file mode 100644 index 0000000..c73e2b1 Binary files /dev/null and b/nbs/image_points_transforms_files/figure-html/cell-8-output-3.png differ diff --git a/nbs/image_prompter.html b/nbs/image_prompter.html new file mode 100644 index 0000000..d875f7b --- /dev/null +++ b/nbs/image_prompter.html @@ -0,0 +1,1254 @@ + + + + + + + + + + + + +Kornia - Image Prompter: Segment Anything + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Image Prompter: Segment Anything

+
+
Intermediate
+
Segmentation
+
kornia.contrib
+
+
+ +
+
+ This tutorials shows how to use our high-level API Image Prompter. This API allow to set an image, and run multiple queries multiple times on this image. These query can be done with three types of prompts. +
+
+ + +
+ +
+
Author
+
+

João G. Atkinson

+
+
+ +
+
Published
+
+

April 18, 2023

+
+
+ + +
+ + +
+ +

Open in google colab

+

This tutorials shows how to use our high-level API Image Prompter. This API allow to set an image, and run multiple queries multiple times on this image. These query can be done with three types of prompt:

+
    +
  1. Points: Keypoints with (x, y) and a respective label. Where 0 indicates a background point; 1 indicates a foreground point;
  2. +
  3. Boxes: Boxes of different regions.
  4. +
  5. Masks: Logits generated by the model in a previous run.
  6. +
+

Read more on our docs: https://kornia.readthedocs.io/en/latest/models/segment_anything.html

+

This tutorials steps:

+
    +
  1. Setup the desired SAM model and import the necessary packages +
      +
    1. Utilities function to read and plot the data
    2. +
  2. +
  3. How to instantiate the Image Prompter +
      +
    1. How to set an image
    2. +
    3. The supported prompts type
    4. +
    5. Example how to query on the image
    6. +
    7. Prediction structure
    8. +
  4. +
  5. Using the Image Prompter +
      +
    1. Soccer player segmentation
    2. +
    3. Car parts segmentation
    4. +
    5. Satellite image - Sentinel-2
    6. +
  6. +
+
+

Setup

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+

First let’s choose the SAM type to be used on our Image Prompter.

+

The options are (smaller to bigger):

+ ++++ + + + + + + + + + + + + + + + + + + + + +
model_typecheckpoint official
vit_bhttps://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth
vit_lhttps://dl.fbaipublicfiles.com/segment_anything/sam_vit_l_0b3195.pth
vit_hhttps://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth
+
+
model_type = "vit_h"
+checkpoint = "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth"
+
+

Then let’s import all necessary packages and modules

+
+
from __future__ import annotations
+
+import os
+
+import matplotlib.pyplot as plt
+import torch
+from kornia.contrib.image_prompter import ImagePrompter
+from kornia.contrib.models.sam import SamConfig
+from kornia.geometry.boxes import Boxes
+from kornia.geometry.keypoints import Keypoints
+from kornia.io import ImageLoadType, load_image
+from kornia.utils import get_cuda_or_mps_device_if_available, tensor_to_image
+
+
+
device = get_cuda_or_mps_device_if_available()
+print(device)
+
+
cuda:0
+
+
+
+

Utilities functions

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+soccer_image_path = download_image("https://raw.githubusercontent.com/kornia/data/main/soccer.jpg")
+car_image_path = download_image("https://raw.githubusercontent.com/kornia/data/main/simple_car.jpg")
+satellite_image_path = download_image("https://raw.githubusercontent.com/kornia/data/main/satellite_sentinel2_example.tif")
+soccer_image_path, car_image_path, satellite_image_path
+
+
('soccer.jpg', 'simple_car.jpg', 'satellite_sentinel2_example.tif')
+
+
+
+
def colorize_masks(binary_masks: torch.Tensor, merge: bool = True, alpha: None | float = None) -> list[torch.Tensor]:
+    """Convert binary masks (B, C, H, W), boolean tensors, into masks with colors (B, (3, 4) , H, W) - RGB or RGBA. Where C refers to the number of masks.
+    Args:
+        binary_masks: a batched boolean tensor (B, C, H, W)
+        merge: If true, will join the batch dimension into a unique mask.
+        alpha: alpha channel value. If None, will generate RGB images
+
+    Returns:
+        A list of `C` colored masks.
+    """
+    B, C, H, W = binary_masks.shape
+    OUT_C = 4 if alpha else 3
+
+    output_masks = []
+
+    for idx in range(C):
+        _out = torch.zeros(B, OUT_C, H, W, device=binary_masks.device, dtype=torch.float32)
+        for b in range(B):
+            color = torch.rand(1, 3, 1, 1, device=binary_masks.device, dtype=torch.float32)
+            if alpha:
+                color = torch.cat([color, torch.tensor([[[[alpha]]]], device=binary_masks.device, dtype=torch.float32)], dim=1)
+
+            to_colorize = binary_masks[b, idx, ...].view(1, 1, H, W).repeat(1, OUT_C, 1, 1)
+            _out[b, ...] = torch.where(to_colorize, color, _out[b, ...])
+        output_masks.append(_out)
+
+    if merge:
+        output_masks = [c.max(dim=0)[0] for c in output_masks]
+
+    return output_masks
+
+
+def show_binary_masks(binary_masks: torch.Tensor, axes) -> None:
+    """plot binary masks, with shape (B, C, H, W), where C refers to the number of masks.
+
+    will merge the `B` channel into a unique mask.
+    Args:
+        binary_masks: a batched boolean tensor (B, C, H, W)
+        ax: a list of matplotlib axes with lenght of C
+    """
+    colored_masks = colorize_masks(binary_masks, True, 0.6)
+
+    for ax, mask in zip(axes, colored_masks):
+        ax.imshow(tensor_to_image(mask))
+
+
+def show_boxes(boxes: Boxes, ax) -> None:
+    boxes_tensor = boxes.to_tensor(mode="xywh").detach().cpu().numpy()
+    for batched_boxes in boxes_tensor:
+        for box in batched_boxes:
+            x0, y0, w, h = box
+            ax.add_patch(plt.Rectangle((x0, y0), w, h, edgecolor="orange", facecolor=(0, 0, 0, 0), lw=2))
+
+
+def show_points(points: tuple[Keypoints, torch.Tensor], ax, marker_size=200):
+    coords, labels = points
+    pos_points = coords[labels == 1].to_tensor().detach().cpu().numpy()
+    neg_points = coords[labels == 0].to_tensor().detach().cpu().numpy()
+
+    ax.scatter(pos_points[:, 0], pos_points[:, 1], color="green", marker="+", s=marker_size, linewidth=2)
+    ax.scatter(neg_points[:, 0], neg_points[:, 1], color="red", marker="x", s=marker_size, linewidth=2)
+
+
+
from kornia.contrib.models import SegmentationResults
+
+
+def show_image(image: torch.Tensor):
+    plt.imshow(tensor_to_image(image))
+    plt.axis("off")
+    plt.show()
+
+
+def show_predictions(
+    image: torch.Tensor,
+    predictions: SegmentationResults,
+    points: tuple[Keypoints, torch.Tensor] | None = None,
+    boxes: Boxes | None = None,
+) -> None:
+    n_masks = predictions.logits.shape[1]
+
+    fig, axes = plt.subplots(1, n_masks, figsize=(21, 16))
+    axes = [axes] if n_masks == 1 else axes
+
+    for idx, ax in enumerate(axes):
+        score = predictions.scores[:, idx, ...].mean()
+        ax.imshow(tensor_to_image(image))
+        ax.set_title(f"Mask {idx+1}, Score: {score:.3f}", fontsize=18)
+
+        if points:
+            show_points(points, ax)
+
+        if boxes:
+            show_boxes(boxes, ax)
+
+        ax.axis("off")
+
+    show_binary_masks(predictions.binary_masks, axes)
+    plt.show()
+
+
+
+
+

Exploring the Image Prompter

+

The ImagePrompter can be initialized from a ModelConfig structure, where now we just have support for the SAM model through the SamConfig. Through this config the ImagePrompter will initialize the SAM model and load the weights (from a path or a URL).

+

What the ImagePrompter can do? 1. Based on the ModelConfig, besides the model initialization, we will setup the required transformations for the images and prompts using the kornia.augmentation API within the Augmentation sequential container. 1. You can benefit from using the torch.compile(...) API (dynamo) for torch >= 2.0.0 versions. To compile with dynamo we provide the method ImagePrompter.compile(...) which will optimize the right parts of the backend model and the prompter itself. 1. Caching the image features and transformations. With the ImagePrompter.set_image(...) method, we transform the image and already encode it using the model, caching it’s embeddings to query later. 1. Query multiple times with multiple prompts. Using the ImagePrompter.predict(...), where we will query on our cached embeddings using Keypoints, Boxes and Masks as prompt.

+

What the ImagePrompter and Kornia provides? Easy high-levels structures to be used as prompt, also as the result of the prediction. Using the kornia geometry module you can easily encapsulate the Keypoints and Boxes, which allow the API to be more flexible about the desired mode (mainly for boxes, where we had multiple modes of represent it).

+

The Kornia ImagePrompter and model config for SAM can be imported as follow:

+
from kornia.contrib.image_prompter import ImagePrompter
+from kornia.contrib.models import SamConfig
+
+
# Setting up a SamConfig with the model type and checkpoint desired
+config = SamConfig(model_type, checkpoint)
+
+# Initialize the ImagePrompter
+prompter = ImagePrompter(config, device=device)
+
+
+

Set image

+

First, before adding the image to the prompter, we need to read the image. For that, we can use kornia.io, which internally uses kornia-rs. If you do not have kornia-rs installed, you can install it with pip install kornia_rs. This API implements the DLPack protocol natively in Rust to reduce the memory footprint during the decoding and type conversion. Allowing us to read the image from the disk directly to a tensor. Note that the image should be scaled within the range [0,1].

+
+
# Load the image
+image = load_image(soccer_image_path, ImageLoadType.RGB32, device)  # 3 x H x W
+
+# Display the loaded image
+show_image(image)
+
+

+
+
+

With the image loaded onto the same device as the model, and with the right shape 3xHxW, we can now set the image in our image prompter. Attention: when doing this, the model will compute the embeddings of this image; this means, we will pass this image through the encoder, which will use a lot of memory. It is possible to use the largest model (vit-h) with a graphic card (GPU) that has at least 8Gb of VRAM.

+
+
prompter.set_image(image)
+
+

If no error occurred, the features needed to run queries are now cached. If you want to check this, you can see the status of the prompter.is_image_set property.

+
+
prompter.is_image_set
+
+
True
+
+
+
+
+

Examples of prompts

+

The ImagePrompter output will have the same Batch Size that its prompts. Where the output shape will be (B, C, H, W). Where B is the number of input prompts, C is determined by multimask output parameter. If multimask_output is True than C=3, otherwise C=1

+
+

Keypoints

+

Keypoints prompts is a tensor or a Keypoint structure within coordinates into (x, y). With shape BxNx2.

+

For each coordinate pair, should have a corresponding label, where 0 indicates a background point; 1 indicates a foreground point; These labels should be in a tensor with shape BxN

+

The model will try to find a object within all the foreground points, and without the background points. In other words, the foreground points can be used to select the desired type of data, and the background point to exclude the type of data.

+
+
keypoints_tensor = torch.tensor([[[960, 540]]], device=device, dtype=torch.float32)
+keypoints = Keypoints(keypoints_tensor)
+
+labels = torch.tensor([[1]], device=device, dtype=torch.float32)
+
+
+
+

Boxes

+

Boxes prompts is a tensor a with boxes on “xyxy” format/mode, or a Boxes structure. Tensor should have a shape of BxNx4.

+
+
boxes_tensor = torch.tensor([[[1841.7, 739.0, 1906.5, 890.6]]], device=device, dtype=torch.float32)
+boxes = Boxes.from_tensor(boxes_tensor, mode="xyxy")
+
+
+
+

Masks

+

Masks prompts should be provide from a previous model output, with shape Bx1x256x256

+
# first run
+predictions = prompter.prediction(...)
+
+# use previous results as prompt
+predictions = prompter.prediction(..., mask=predictions.logits)
+
+
+
+

Example of prediction

+
+
# using keypoints
+prediction_by_keypoint = prompter.predict(keypoints, labels, multimask_output=False)
+
+show_image(prediction_by_keypoint.binary_masks)
+
+

+
+
+
+
# Using boxes
+prediction_by_boxes = prompter.predict(boxes=boxes, multimask_output=False)
+
+show_image(prediction_by_boxes.binary_masks)
+
+

+
+
+
+
+

Exploring the prediction result structure

+

The ImagePrompter prediction structure, is a SegmentationResults which has the upscaled (default) logits when output_original_size=True is passed on the predict.

+

The segmentation results have: - logits: Results logits with shape (B, C, H, W), where C refers to the number of predicted masks - scores: The scores from the logits. Shape (B,) - Binary mask generated from logits considering the mask_threshold. The size depends on original_res_logits=True, if false, the binary mask will have the same shape of the logits Bx1x256x256

+
+
prediction_by_boxes.scores
+
+
tensor([[0.9317]], device='cuda:0')
+
+
+
+
prediction_by_boxes.binary_masks.shape
+
+
torch.Size([1, 1, 1080, 1920])
+
+
+
+
prediction_by_boxes.logits.shape
+
+
torch.Size([1, 1, 256, 256])
+
+
+
+
+
+

Using the Image Prompter on examples

+
+

Soccer players

+

Using an example image from the dataset: https://www.kaggle.com/datasets/ihelon/football-player-segmentation

+

Lets segment the persons on the field using boxes

+
+
# Prompts
+boxes = Boxes.from_tensor(
+    torch.tensor(
+        [
+            [
+                [1841.7000, 739.0000, 1906.5000, 890.6000],
+                [879.3000, 545.9000, 948.2000, 669.2000],
+                [55.7000, 595.0000, 127.4000, 745.9000],
+                [1005.4000, 128.7000, 1031.5000, 212.0000],
+                [387.4000, 424.1000, 438.2000, 539.0000],
+                [921.0000, 377.7000, 963.3000, 483.0000],
+                [1213.2000, 885.8000, 1276.2000, 1060.1000],
+                [40.8900, 725.9600, 105.4100, 886.5800],
+                [848.9600, 283.6200, 896.0600, 368.6200],
+                [1109.6500, 499.0400, 1153.0400, 622.1700],
+                [576.3000, 860.8000, 671.7000, 1018.8000],
+                [1039.8000, 389.9000, 1072.5000, 493.2000],
+                [1647.1000, 315.1000, 1694.0000, 406.0000],
+                [1231.2000, 214.0000, 1294.1000, 297.3000],
+            ]
+        ],
+        device=device,
+    ),
+    mode="xyxy",
+)
+
+
+
# Load the image
+image = load_image(soccer_image_path, ImageLoadType.RGB32, device)  # 3 x H x W
+
+# Set the image
+prompter.set_image(image)
+
+
+
predictions = prompter.predict(boxes=boxes, multimask_output=True)
+
+

let’s see the results, since we used multimask_output=True, the model outputted 3 masks.

+
+
show_predictions(image, predictions, boxes=boxes)
+
+

+
+
+
+
+

Car parts

+

Segmenting car parts of an example from the dataset: https://www.kaggle.com/datasets/jessicali9530/stanford-cars-dataset

+
+
# Prompts
+boxes = Boxes.from_tensor(
+    torch.tensor(
+        [
+            [
+                [56.2800, 369.1100, 187.3000, 579.4300],
+                [412.5600, 426.5800, 592.9900, 608.1600],
+                [609.0800, 366.8200, 682.6400, 431.1700],
+                [925.1300, 366.8200, 959.6100, 423.1300],
+                [756.1900, 416.2300, 904.4400, 473.7000],
+                [489.5600, 285.2200, 676.8900, 343.8300],
+            ]
+        ],
+        device=device,
+    ),
+    mode="xyxy",
+)
+
+keypoints = Keypoints(
+    torch.tensor(
+        [[[535.0, 227.0], [349.0, 215.0], [237.0, 219.0], [301.0, 373.0], [641.0, 397.0], [489.0, 513.0]]], device=device
+    )
+)
+labels = torch.ones(keypoints.shape[:2], device=device, dtype=torch.float32)
+
+
+
# Image
+image = load_image(car_image_path, ImageLoadType.RGB32, device)
+
+# Set the image
+prompter.set_image(image)
+
+
+

Querying with boxes

+
+
predictions = prompter.predict(boxes=boxes, multimask_output=True)
+
+
+
show_predictions(image, predictions, boxes=boxes)
+
+

+
+
+
+
+

Querying with keypoints

+
+
Considering N points into 1 Batch
+

This way the model will kinda find the object within all the points

+
+
predictions = prompter.predict(keypoints=keypoints, keypoints_labels=labels)
+
+
+
show_predictions(image, predictions, points=(keypoints, labels))
+
+

+
+
+
+
+
Considering 1 point into N Batch
+

Prompter encoder not working for a batch of points :/

+
+
+
+

Considering 1 point for batch into N queries

+

This way the model will find an object for each point

+
+
k = 2  # number of times/points to query
+
+for idx in range(min(keypoints.data.size(1), k)):
+    print("-" * 79, f"\nQuery {idx}:")
+    _kpts = keypoints[:, idx, ...][None, ...]
+    _lbl = labels[:, idx, ...][None, ...]
+
+    predictions = prompter.predict(keypoints=_kpts, keypoints_labels=_lbl)
+
+    show_predictions(image, predictions, points=(_kpts, _lbl))
+
+
------------------------------------------------------------------------------- 
+Query 0:
+------------------------------------------------------------------------------- 
+Query 1:
+
+
+

+
+
+

+
+
+
+
+
+

Satellite image

+

Image from Sentinel-2

+

Product: A tile of the TCI (px of 10m). Product name: S2B_MSIL1C_20230324T130249_N0509_R095_T23KPQ_20230324T174312

+
+
# Prompts
+keypoints = Keypoints(
+    torch.tensor(
+        [
+            [
+                # Urban
+                [74.0, 104.5],
+                [335, 110],
+                [702, 65],
+                [636, 479],
+                [408, 820],
+                # Forest
+                [40, 425],
+                [680, 566],
+                [405, 439],
+                [73, 689],
+                [865, 460],
+                # Ocean/water
+                [981, 154],
+                [705, 714],
+                [357, 683],
+                [259, 908],
+                [1049, 510],
+            ]
+        ],
+        device=device,
+    )
+)
+labels = torch.ones(keypoints.shape[:2], device=device, dtype=torch.float32)
+
+
+
# Image
+image = load_image(satellite_image_path, ImageLoadType.RGB32, device)
+
+# Set the image
+prompter.set_image(image)
+
+
+

Query urban points

+
+
# Query the prompts
+labels_to_query = labels.clone()
+labels_to_query[..., 5:] = 0
+
+predictions = prompter.predict(keypoints=keypoints, keypoints_labels=labels_to_query)
+
+
+
show_predictions(image, predictions, points=(keypoints, labels_to_query))
+
+

+
+
+
+
+

Query Forest points

+
+
# Query the prompts
+labels_to_query = labels.clone()
+labels_to_query[..., :5] = 0
+labels_to_query[..., 10:] = 0
+
+predictions = prompter.predict(keypoints=keypoints, keypoints_labels=labels_to_query)
+
+
+
show_predictions(image, predictions, points=(keypoints, labels_to_query))
+
+

+
+
+
+
+

Query ocean/water points

+
+
# Query the prompts
+labels_to_query = labels.clone()
+labels_to_query[..., :10] = 0
+
+predictions = prompter.predict(keypoints=keypoints, keypoints_labels=labels_to_query)
+
+
+
show_predictions(image, predictions, points=(keypoints, labels_to_query))
+
+

+
+
+ + +
+
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/image_prompter_files/figure-html/cell-10-output-1.png b/nbs/image_prompter_files/figure-html/cell-10-output-1.png new file mode 100644 index 0000000..d91c070 Binary files /dev/null and b/nbs/image_prompter_files/figure-html/cell-10-output-1.png differ diff --git a/nbs/image_prompter_files/figure-html/cell-15-output-1.png b/nbs/image_prompter_files/figure-html/cell-15-output-1.png new file mode 100644 index 0000000..1a8abbf Binary files /dev/null and b/nbs/image_prompter_files/figure-html/cell-15-output-1.png differ diff --git a/nbs/image_prompter_files/figure-html/cell-16-output-1.png b/nbs/image_prompter_files/figure-html/cell-16-output-1.png new file mode 100644 index 0000000..21ad905 Binary files /dev/null and b/nbs/image_prompter_files/figure-html/cell-16-output-1.png differ diff --git a/nbs/image_prompter_files/figure-html/cell-23-output-1.png b/nbs/image_prompter_files/figure-html/cell-23-output-1.png new file mode 100644 index 0000000..a02c1fa Binary files /dev/null and b/nbs/image_prompter_files/figure-html/cell-23-output-1.png differ diff --git a/nbs/image_prompter_files/figure-html/cell-27-output-1.png b/nbs/image_prompter_files/figure-html/cell-27-output-1.png new file mode 100644 index 0000000..acc0cc0 Binary files /dev/null and b/nbs/image_prompter_files/figure-html/cell-27-output-1.png differ diff --git a/nbs/image_prompter_files/figure-html/cell-29-output-1.png b/nbs/image_prompter_files/figure-html/cell-29-output-1.png new file mode 100644 index 0000000..0e22dbb Binary files /dev/null and b/nbs/image_prompter_files/figure-html/cell-29-output-1.png differ diff --git a/nbs/image_prompter_files/figure-html/cell-30-output-2.png b/nbs/image_prompter_files/figure-html/cell-30-output-2.png new file mode 100644 index 0000000..14fce47 Binary files /dev/null and b/nbs/image_prompter_files/figure-html/cell-30-output-2.png differ diff --git a/nbs/image_prompter_files/figure-html/cell-30-output-3.png b/nbs/image_prompter_files/figure-html/cell-30-output-3.png new file mode 100644 index 0000000..7b598fa Binary files /dev/null and b/nbs/image_prompter_files/figure-html/cell-30-output-3.png differ diff --git a/nbs/image_prompter_files/figure-html/cell-34-output-1.png b/nbs/image_prompter_files/figure-html/cell-34-output-1.png new file mode 100644 index 0000000..d2f44af Binary files /dev/null and b/nbs/image_prompter_files/figure-html/cell-34-output-1.png differ diff --git a/nbs/image_prompter_files/figure-html/cell-36-output-1.png b/nbs/image_prompter_files/figure-html/cell-36-output-1.png new file mode 100644 index 0000000..7fa3794 Binary files /dev/null and b/nbs/image_prompter_files/figure-html/cell-36-output-1.png differ diff --git a/nbs/image_prompter_files/figure-html/cell-38-output-1.png b/nbs/image_prompter_files/figure-html/cell-38-output-1.png new file mode 100644 index 0000000..20ed65c Binary files /dev/null and b/nbs/image_prompter_files/figure-html/cell-38-output-1.png differ diff --git a/nbs/image_registration.html b/nbs/image_registration.html new file mode 100644 index 0000000..4191427 --- /dev/null +++ b/nbs/image_registration.html @@ -0,0 +1,863 @@ + + + + + + + + + + + + +Kornia - Image Registration by Direct Optimization + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Image Registration by Direct Optimization

+
+
Intermediate
+
Image Registration
+
kornia.geometry
+
+
+ +
+
+ In this tutorial we are going to learn how to perform the task of image alignment by optimizing the similarity transformation between two images in order to create a photo with wide in-focus area from set of narrow-focused images. +
+
+ + +
+ +
+
Author
+
+

Dmytro Mishkin

+
+
+ +
+
Published
+
+

September 6, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+

The images are courtesy of Dennis Sakva

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_data(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+download_data("http://cmp.felk.cvut.cz/~mishkdmy/bee.zip")
+
+
'bee.zip'
+
+
+
+
%%capture
+!unzip bee.zip
+
+

Import needed libraries

+
+
import os
+from copy import deepcopy
+from typing import List
+
+import imageio
+import kornia as K
+import kornia.geometry as KG
+import matplotlib.pyplot as plt
+import numpy as np
+import torch
+import torch.nn.functional as F
+from tqdm import tqdm
+
+
+def get_data_directory(base):
+    path = os.path.join("../", base)
+    if os.path.isdir(os.path.join(path, "data")):
+        return os.path.join(path, "data/")
+    return get_data_directory(path)
+
+
+

Images preview

+

Let’s check our images. There are almost 100 of them, so we will show only each 10th

+
+
fnames = os.listdir("bee")
+fnames = [f"bee/{x}" for x in sorted(fnames) if x.endswith("JPG")]
+fig, axis = plt.subplots(2, 5, figsize=(12, 4), sharex="all", sharey="all", frameon=False)
+for i, fname in enumerate(fnames):
+    if i % 10 != 0:
+        continue
+    j = i // 10
+    img = K.io.load_image(fname, K.io.ImageLoadType.RGB8)
+    axis[j // 5][j % 5].imshow(K.tensor_to_image(img), aspect="auto")
+plt.subplots_adjust(wspace=0.05, hspace=0.05)
+fig.tight_layout()
+
+

+
+
+

So the focus goes from back to the front, so we have to match and merge them in the same order.

+
+
+

Image registration

+

We will need ImageRegistrator object to do the matching. Because the photos are takes so that only slight rotation, shift and scale change is possible, we will use similarity mode, which does exactly this.

+
+
use_cuda: bool = torch.cuda.is_available()
+device = torch.device("cuda" if use_cuda else "cpu")
+registrator = KG.ImageRegistrator("similarity", loss_fn=F.mse_loss, lr=8e-4, pyramid_levels=3, num_iterations=500).to(device)
+print(device)
+
+
cuda
+
+
+

We will register images sequentially with ImageRegistrator.

+
+
%%capture
+models = []
+for i, fname in tqdm(enumerate(fnames)):
+    if i == 0:
+        continue
+    prev_img = K.io.load_image(fnames[i - 1], K.io.ImageLoadType.RGB32, device=device)[None, ...]
+    curr_img = K.io.load_image(fname, K.io.ImageLoadType.RGB32, device=device)[None, ...]
+    model = registrator.register(prev_img, curr_img)
+    models.append(deepcopy(model.detach()))
+
+

Let’s take the final (the most close-focused) image as the reference - this means that we have to convert our image transforms from (between i and i+1) mode into (between i and last). We can do it by matrix multiplication.

+
+
models_to_final = [torch.eye(3, device=device)[None]]
+for m in models[::-1]:
+    models_to_final.append(m @ models_to_final[-1])
+models_to_final = models_to_final[::-1]
+
+

Let’s check what do we got.

+
+
fig, axis = plt.subplots(2, 5, figsize=(12, 4), sharex="all", sharey="all", frameon=False)
+for i, fname in enumerate(fnames):
+    if i % 10 != 0:
+        continue
+    timg = K.io.load_image(fname, K.io.ImageLoadType.RGB32, device=device)[None, ...]
+    j = i // 10
+    timg_dst = KG.homography_warp(timg, models_to_final[i], timg.shape[-2:])
+    axis[j // 5][j % 5].imshow(K.tensor_to_image(timg_dst * 255.0).astype(np.uint8), aspect="auto")
+plt.subplots_adjust(wspace=0.05, hspace=0.05)
+fig.tight_layout()
+
+

+
+
+

Finally we will merge the image sequence into single image. The idea is to detect the image parts, which are in focus from the current image and blend them into the final images. To get the sharp image part we can use kornia.filters.laplacian. Then we reproject image1 into image2, and merge them using mask we created.

+
+
def merge_sharp1_into2(timg1, timg2, trans1to2, verbose=False):
+    curr_img = timg2.clone()
+    warped = KG.homography_warp(timg1, torch.inverse(trans1to2), timg.shape[-2:])
+    mask1 = K.filters.laplacian(K.color.rgb_to_grayscale(timg1), 7).abs()
+    mask1_norm = (mask1 - mask1.min()) / (mask1.max() - mask1.min())
+    mask1_blur = K.filters.gaussian_blur2d(mask1_norm, (9, 9), (1.6, 1.6))
+    mask1_blur = mask1_blur / mask1_blur.max()
+    warped_mask = KG.homography_warp(mask1_blur.float(), torch.inverse(models_to_final[i]), timg1.shape[-2:])
+    curr_img = warped_mask * warped + (1 - warped_mask) * curr_img
+    if verbose:
+        fig, axis = plt.subplots(1, 4, figsize=(15, 6), sharex="all", sharey="all", frameon=False)
+        axis[0].imshow(K.tensor_to_image(timg1))
+        axis[1].imshow(K.tensor_to_image(mask1_blur))
+        axis[2].imshow(K.tensor_to_image(timg2))
+        axis[3].imshow(K.tensor_to_image(curr_img))
+        axis[0].set_title("Img1")
+        axis[1].set_title("Sharp mask on img1")
+        axis[2].set_title("Img2")
+        axis[3].set_title("Blended image")
+    return curr_img
+
+
+timg1 = K.io.load_image(fnames[50], K.io.ImageLoadType.RGB32, device=device)[None, ...]
+timg2 = K.io.load_image(fnames[-1], K.io.ImageLoadType.RGB32, device=device)[None, ...]
+out = merge_sharp1_into2(timg1, timg2, models_to_final[50], True)
+
+

+
+
+

The blending does not look really good, but that is because we are trying to merge non-consequtive images with very different focus. Let’s try to apply it sequentially and see, what happens.

+

We will also create a video of our sharpening process.

+
+
%%capture
+base_img = K.io.load_image(fnames[-1], K.io.ImageLoadType.RGB32, device=device)[None, ...]
+curr_img = deepcopy(base_img)
+
+try:
+    video_writer = imageio.get_writer("sharpening.avi", fps=8)
+    video_writer.append_data((K.tensor_to_image(curr_img) * 255.0).astype(np.uint8))
+    video_ok = True
+except:
+    video_ok = False
+
+
+with torch.no_grad():
+    for i, fname in tqdm(enumerate(fnames)):
+        timg = K.io.load_image(fname, K.io.ImageLoadType.RGB32, device=device)[None, ...]
+        curr_img = merge_sharp1_into2(timg.to(device), curr_img.to(device), models_to_final[i].to(device))
+        if video_ok:
+            video_writer.append_data((K.tensor_to_image(curr_img) * 255.0).astype(np.uint8))
+if video_ok:
+    video_writer.close()
+
+
+
plt.imshow(K.tensor_to_image(curr_img.float()))
+plt.title("Final result")
+
+
Text(0.5, 1.0, 'Final result')
+
+
+

+
+
+

Now we can play the video of our sharpening. The code is ugly to allow running from Google Colab (as shown here)

+
+
from base64 import b64encode
+
+from IPython.display import HTML
+
+if video_ok:
+    mp4 = open("sharpening.avi", "rb").read()
+else:
+    mp4 = open(get_data_directory("") + "sharpening.mp4", "rb").read()
+data_url = "data:video/mp4;base64," + b64encode(mp4).decode()
+
+
+HTML(
+    f"""
+<video width=400 controls>
+      <source src="{data_url}" type="video/mp4">
+</video>
+"""
+)
+
+ + +
+
+

Result looks quite nice and more detailed, although a bit soft. You can try yourself different blending parameters yourself (e.g. blur kernel size) in order to improve the final result.

+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/image_registration_files/figure-html/cell-10-output-1.png b/nbs/image_registration_files/figure-html/cell-10-output-1.png new file mode 100644 index 0000000..c26a36c Binary files /dev/null and b/nbs/image_registration_files/figure-html/cell-10-output-1.png differ diff --git a/nbs/image_registration_files/figure-html/cell-11-output-1.png b/nbs/image_registration_files/figure-html/cell-11-output-1.png new file mode 100644 index 0000000..1943631 Binary files /dev/null and b/nbs/image_registration_files/figure-html/cell-11-output-1.png differ diff --git a/nbs/image_registration_files/figure-html/cell-13-output-2.png b/nbs/image_registration_files/figure-html/cell-13-output-2.png new file mode 100644 index 0000000..7cca48d Binary files /dev/null and b/nbs/image_registration_files/figure-html/cell-13-output-2.png differ diff --git a/nbs/image_registration_files/figure-html/cell-6-output-1.png b/nbs/image_registration_files/figure-html/cell-6-output-1.png new file mode 100644 index 0000000..2ad9867 Binary files /dev/null and b/nbs/image_registration_files/figure-html/cell-6-output-1.png differ diff --git a/nbs/image_stitching.html b/nbs/image_stitching.html new file mode 100644 index 0000000..602b3a5 --- /dev/null +++ b/nbs/image_stitching.html @@ -0,0 +1,755 @@ + + + + + + + + + + + + +Kornia - Image stitching example with LoFTR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Image stitching example with LoFTR

+
+
Intermediate
+
LoFTR
+
kornia.feature
+
+
+ +
+
+ A show case of how to do image stitching using LoFTR from Kornia. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

November 19, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+

Open in HF Spaces

+

First, we will install everything needed:

+ +
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+

Now let’s download an image pair

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1].split("?")[0] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+download_image("http://www.ic.unicamp.br/~helio/imagens_registro/foto1B.jpg")
+download_image("http://www.ic.unicamp.br/~helio/imagens_registro/foto1A.jpg")
+
+
'foto1A.jpg'
+
+
+
+
%%capture
+import kornia as K
+import kornia.feature as KF
+import matplotlib.pyplot as plt
+import numpy as np
+import torch
+
+
+def load_images(fnames):
+    return [K.io.load_image(fn, K.io.ImageLoadType.RGB32)[None, ...] for fn in fnames]
+
+
+imgs = load_images(["foto1A.jpg", "foto1B.jpg"])
+
+
+

Stitch them together

+
+
from kornia.contrib import ImageStitcher
+
+IS = ImageStitcher(KF.LoFTR(pretrained="outdoor"), estimator="ransac")
+
+with torch.no_grad():
+    out = IS(*imgs)
+
+plt.imshow(K.tensor_to_image(out))
+plt.show()
+
+

+
+
+
+
+

Another example

+
+
download_image("https://github.com/daeyun/Image-Stitching/blob/master/img/hill/1.JPG?raw=true")
+download_image("https://github.com/daeyun/Image-Stitching/blob/master/img/hill/2.JPG?raw=true")
+download_image("https://github.com/daeyun/Image-Stitching/blob/master/img/hill/3.JPG?raw=true")
+
+
'3.JPG'
+
+
+
+
imgs = load_images(["1.JPG", "2.JPG", "3.JPG"])
+
+
+
f, axarr = plt.subplots(1, 3, figsize=(16, 6))
+
+axarr[0].imshow(K.tensor_to_image(imgs[0]))
+axarr[0].tick_params(left=False, right=False, labelleft=False, labelbottom=False, bottom=False)
+axarr[1].imshow(K.tensor_to_image(imgs[1]))
+axarr[1].tick_params(left=False, right=False, labelleft=False, labelbottom=False, bottom=False)
+axarr[2].imshow(K.tensor_to_image(imgs[2]))
+axarr[2].tick_params(left=False, right=False, labelleft=False, labelbottom=False, bottom=False)
+
+

+
+
+
+
matcher = KF.LocalFeatureMatcher(KF.GFTTAffNetHardNet(100), KF.DescriptorMatcher("snn", 0.8))
+IS = ImageStitcher(matcher, estimator="ransac")
+
+with torch.no_grad():
+    out = IS(*imgs)
+
+
+
plt.figure(figsize=(16, 16))
+plt.imshow(K.tensor_to_image(out))
+
+
<matplotlib.image.AxesImage>
+
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/image_stitching_files/figure-html/cell-10-output-2.png b/nbs/image_stitching_files/figure-html/cell-10-output-2.png new file mode 100644 index 0000000..ae8ec87 Binary files /dev/null and b/nbs/image_stitching_files/figure-html/cell-10-output-2.png differ diff --git a/nbs/image_stitching_files/figure-html/cell-5-output-1.png b/nbs/image_stitching_files/figure-html/cell-5-output-1.png new file mode 100644 index 0000000..3b64bac Binary files /dev/null and b/nbs/image_stitching_files/figure-html/cell-5-output-1.png differ diff --git a/nbs/image_stitching_files/figure-html/cell-8-output-1.png b/nbs/image_stitching_files/figure-html/cell-8-output-1.png new file mode 100644 index 0000000..ad778cb Binary files /dev/null and b/nbs/image_stitching_files/figure-html/cell-8-output-1.png differ diff --git a/nbs/line_detection_and_matching_sold2.html b/nbs/line_detection_and_matching_sold2.html new file mode 100644 index 0000000..b86b541 --- /dev/null +++ b/nbs/line_detection_and_matching_sold2.html @@ -0,0 +1,940 @@ + + + + + + + + + + + + +Kornia - Line detection and matching example with SOLD2: Self-supervised Occlusion-aware Line Description and Detection + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Line detection and matching example with SOLD2: Self-supervised Occlusion-aware Line Description and Detection

+
+
Intermediate
+
Line detection
+
Line matching
+
SOLD2
+
Self-supervised
+
kornia.feature
+
+
+ +
+
+ In this tutorial we will show how we can quickly perform line detection, and matching using kornia.feature.sold2 API. +
+
+ + +
+ +
+
Author
+
+

João G. Atkinson

+
+
+ +
+
Published
+
+

August 31, 2022

+
+
+ + +
+ + +
+ +

Open in google colab

+
+

Setup

+

Install the libraries:

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+!pip install opencv-python --upgrade # Just for windows
+!pip install matplotlib
+
+

Now let’s download an image

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1].split("?")[0] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+download_image("https://github.com/cvg/SOLD2/raw/main/assets/images/terrace0.JPG")
+download_image("https://github.com/cvg/SOLD2/raw/main/assets/images/terrace1.JPG")
+
+
'terrace1.JPG'
+
+
+

Then, we will load the libraries

+
+
import kornia as K
+import kornia.feature as KF
+import torch
+
+

Load the images and convert into torch tensors.

+
+
fname1 = "terrace0.JPG"
+fname2 = "terrace1.JPG"
+
+torch_img1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32)[None, ...]
+torch_img2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32)[None, ...]
+
+torch_img1.shape, torch_img2.shape
+
+
(torch.Size([1, 3, 496, 744]), torch.Size([1, 3, 496, 744]))
+
+
+

Prepare the data for the model, which is expected a batch of images in gray scale (shape: (Batch size, 1, Height, Width)).

+

The SOLD2 model was tuned for images in the range 400~800px when using config=None.

+
+
# First, convert the images to gray scale
+torch_img1_gray = K.color.rgb_to_grayscale(torch_img1)
+torch_img2_gray = K.color.rgb_to_grayscale(torch_img2)
+
+
+
torch_img1_gray.shape, torch_img2_gray.shape
+
+
(torch.Size([1, 1, 496, 744]), torch.Size([1, 1, 496, 744]))
+
+
+
+
# then, stack the images to create/simulate a batch
+imgs = torch.cat([torch_img1_gray, torch_img2_gray], dim=0)
+
+imgs.shape
+
+
torch.Size([2, 1, 496, 744])
+
+
+
+
+

Performs line detection and matching

+

Load the sold2 model with pre-trained=True, which will download and set pre-trained weights to the model.

+
+
%%capture
+sold2 = KF.SOLD2(pretrained=True, config=None)
+
+
+

Perform the model prediction

+
+
%%capture
+with torch.inference_mode():
+    outputs = sold2(imgs)
+
+

Organize the outputs for demo.

+

Attention: The detected line segments is in ij coordinates convention.

+
+
outputs.keys()
+
+
dict_keys(['junction_heatmap', 'line_heatmap', 'dense_desc', 'line_segments'])
+
+
+
+
line_seg1 = outputs["line_segments"][0]
+line_seg2 = outputs["line_segments"][1]
+desc1 = outputs["dense_desc"][0]
+desc2 = outputs["dense_desc"][1]
+
+
+
+

Perform line matching

+
+
with torch.inference_mode():
+    matches = sold2.match(line_seg1, line_seg2, desc1[None], desc2[None])
+
+
+
valid_matches = matches != -1
+match_indices = matches[valid_matches]
+
+matched_lines1 = line_seg1[valid_matches]
+matched_lines2 = line_seg2[match_indices]
+
+
+
+
+

Plot lines detected and also the match

+

Plot functions adapted from original code.

+
+
import copy
+
+import matplotlib
+import matplotlib.colors as mcolors
+import matplotlib.pyplot as plt
+import numpy as np
+
+
+def plot_images(imgs, titles=None, cmaps="gray", dpi=100, size=6, pad=0.5):
+    """Plot a set of images horizontally.
+    Args:
+        imgs: a list of NumPy or PyTorch images, RGB (H, W, 3) or mono (H, W).
+        titles: a list of strings, as titles for each image.
+        cmaps: colormaps for monochrome images.
+    """
+    n = len(imgs)
+    if not isinstance(cmaps, (list, tuple)):
+        cmaps = [cmaps] * n
+    figsize = (size * n, size * 3 / 4) if size is not None else None
+    fig, ax = plt.subplots(1, n, figsize=figsize, dpi=dpi)
+    if n == 1:
+        ax = [ax]
+    for i in range(n):
+        ax[i].imshow(imgs[i], cmap=plt.get_cmap(cmaps[i]))
+        ax[i].get_yaxis().set_ticks([])
+        ax[i].get_xaxis().set_ticks([])
+        ax[i].set_axis_off()
+        for spine in ax[i].spines.values():  # remove frame
+            spine.set_visible(False)
+        if titles:
+            ax[i].set_title(titles[i])
+    fig.tight_layout(pad=pad)
+
+
+def plot_lines(lines, line_colors="orange", point_colors="cyan", ps=4, lw=2, indices=(0, 1)):
+    """Plot lines and endpoints for existing images.
+    Args:
+        lines: list of ndarrays of size (N, 2, 2).
+        colors: string, or list of list of tuples (one for each keypoints).
+        ps: size of the keypoints as float pixels.
+        lw: line width as float pixels.
+        indices: indices of the images to draw the matches on.
+    """
+    if not isinstance(line_colors, list):
+        line_colors = [line_colors] * len(lines)
+    if not isinstance(point_colors, list):
+        point_colors = [point_colors] * len(lines)
+
+    fig = plt.gcf()
+    ax = fig.axes
+    assert len(ax) > max(indices)
+    axes = [ax[i] for i in indices]
+    fig.canvas.draw()
+
+    # Plot the lines and junctions
+    for a, l, lc, pc in zip(axes, lines, line_colors, point_colors):
+        for i in range(len(l)):
+            line = matplotlib.lines.Line2D(
+                (l[i, 1, 1], l[i, 0, 1]),
+                (l[i, 1, 0], l[i, 0, 0]),
+                zorder=1,
+                c=lc,
+                linewidth=lw,
+            )
+            a.add_line(line)
+        pts = l.reshape(-1, 2)
+        a.scatter(pts[:, 1], pts[:, 0], c=pc, s=ps, linewidths=0, zorder=2)
+
+
+def plot_color_line_matches(lines, lw=2, indices=(0, 1)):
+    """Plot line matches for existing images with multiple colors.
+    Args:
+        lines: list of ndarrays of size (N, 2, 2).
+        lw: line width as float pixels.
+        indices: indices of the images to draw the matches on.
+    """
+    n_lines = len(lines[0])
+
+    cmap = plt.get_cmap("nipy_spectral", lut=n_lines)
+    colors = np.array([mcolors.rgb2hex(cmap(i)) for i in range(cmap.N)])
+
+    np.random.shuffle(colors)
+
+    fig = plt.gcf()
+    ax = fig.axes
+    assert len(ax) > max(indices)
+    axes = [ax[i] for i in indices]
+    fig.canvas.draw()
+
+    # Plot the lines
+    for a, l in zip(axes, lines):
+        for i in range(len(l)):
+            line = matplotlib.lines.Line2D(
+                (l[i, 1, 1], l[i, 0, 1]),
+                (l[i, 1, 0], l[i, 0, 0]),
+                zorder=1,
+                c=colors[i],
+                linewidth=lw,
+            )
+            a.add_line(line)
+
+
+
imgs_to_plot = [K.tensor_to_image(torch_img1), K.tensor_to_image(torch_img2)]
+lines_to_plot = [line_seg1.numpy(), line_seg2.numpy()]
+
+plot_images(imgs_to_plot, ["Image 1 - detected lines", "Image 2 - detected lines"])
+plot_lines(lines_to_plot, ps=3, lw=2, indices={0, 1})
+
+

+
+
+
+
plot_images(imgs_to_plot, ["Image 1 - matched lines", "Image 2 - matched lines"])
+plot_color_line_matches([matched_lines1, matched_lines2], lw=2)
+
+

+
+
+
+
+

Example of homography from line segment correspondences from SOLD2

+

Robust geometry estimation with Random sample consensus (RANSAC)

+

Load the model:

+
+
ransac = K.geometry.RANSAC(model_type="homography_from_linesegments", inl_th=3.0)
+
+
+

Perform the model correspondencies

+
+
H_ransac, correspondence_mask = ransac(matched_lines1.flip(dims=(2,)), matched_lines2.flip(dims=(2,)))
+
+

Wrap the image 1 to image 2

+
+
img1_warp_to2 = K.geometry.warp_perspective(torch_img1, H_ransac[None], (torch_img1.shape[-2:]))
+
+
+
+

Plot the matched lines and wrapped image

+
+
plot_images(
+    imgs_to_plot,
+    ["Image 1 - lines with correspondence", "Image 2 - lines with correspondence"],
+)
+plot_color_line_matches([matched_lines1[correspondence_mask], matched_lines2[correspondence_mask]], lw=2)
+
+

+
+
+
+
plot_images(
+    [K.tensor_to_image(torch_img2), K.tensor_to_image(img1_warp_to2)],
+    ["Image 2", "Image 1 wrapped to 2"],
+)
+
+

+
+
+ + +
+
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/line_detection_and_matching_sold2_files/figure-html/cell-16-output-1.png b/nbs/line_detection_and_matching_sold2_files/figure-html/cell-16-output-1.png new file mode 100644 index 0000000..96daea4 Binary files /dev/null and b/nbs/line_detection_and_matching_sold2_files/figure-html/cell-16-output-1.png differ diff --git a/nbs/line_detection_and_matching_sold2_files/figure-html/cell-17-output-1.png b/nbs/line_detection_and_matching_sold2_files/figure-html/cell-17-output-1.png new file mode 100644 index 0000000..29c2c72 Binary files /dev/null and b/nbs/line_detection_and_matching_sold2_files/figure-html/cell-17-output-1.png differ diff --git a/nbs/line_detection_and_matching_sold2_files/figure-html/cell-21-output-1.png b/nbs/line_detection_and_matching_sold2_files/figure-html/cell-21-output-1.png new file mode 100644 index 0000000..3e38279 Binary files /dev/null and b/nbs/line_detection_and_matching_sold2_files/figure-html/cell-21-output-1.png differ diff --git a/nbs/line_detection_and_matching_sold2_files/figure-html/cell-22-output-1.png b/nbs/line_detection_and_matching_sold2_files/figure-html/cell-22-output-1.png new file mode 100644 index 0000000..fe8fad5 Binary files /dev/null and b/nbs/line_detection_and_matching_sold2_files/figure-html/cell-22-output-1.png differ diff --git a/nbs/morphology_101.html b/nbs/morphology_101.html new file mode 100644 index 0000000..d3e61d6 --- /dev/null +++ b/nbs/morphology_101.html @@ -0,0 +1,826 @@ + + + + + + + + + + + + +Kornia - Introduction to Morphological Operators + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Introduction to Morphological Operators

+
+
Basic
+
Morphology
+
kornia.morphology
+
+
+ +
+
+ In this tutorial you are gonna explore kornia.morphology, that’s Kornia’s module for differentiable Morphological Operators. +
+
+ + +
+ +
+
Author
+
+

Juclique

+
+
+ +
+
Published
+
+

March 8, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+

By the end, you will be able to use morphological operations as easy as:

+

new_image = morph.operation(original_image, structuring_element)

+

But first things first, let’s prepare the environment.

+
+

Download Kornia

+

If you don’t have Kornia installed, you can download it using pip.

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
+

Prepare the image

+

With kornia.morphology, you can apply morphological operators in 3 channel color images. Besides, all operators are differentiable. Let’s download the image

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1].split("?")[0] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+download_image(
+    "https://image.shutterstock.com/image-photo/portrait-surprised-cat-scottish-straight-260nw-499196506.jpg", "img.jpg"
+)
+
+
'img.jpg'
+
+
+
+

Imports and read the image

+
+
import kornia as K
+import torch
+
+device = "cpu"  # 'cuda:0' for GPU
+
+
+
img_t = K.io.load_image("img.jpg", K.io.ImageLoadType.RGB32, device=device)[None, ...]
+
+
+
+

Structuring element

+

We have the original image ready to go, now we need the second part in the operation, the structuring element (aka Kernel).

+

The kernel must be a 2-dim tensor with odd sides, i.e. 3x3.

+
+
kernel = torch.tensor([[0, 1, 0], [1, 1, 1], [0, 1, 0]]).to(device)
+
+
+
+

Making plots!

+

In this tutorial we are gonna compare the images before and after transforming them.

+

It make sense to create a function to plot and see the changes!

+
+
import matplotlib.pyplot as plt
+from matplotlib import rcParams
+
+
+def plot_morph_image(tensor):
+    # kornia.tensor_to_image
+    image = K.tensor_to_image(tensor.squeeze(0))  # Tensor to image
+
+    # Plot before-after
+    rcParams["figure.figsize"] = 20, 20
+    fig, ax = plt.subplots(1, 2)
+    ax[0].axis("off")
+    ax[0].imshow(K.tensor_to_image(img_t))
+    ax[1].axis("off")
+    ax[1].imshow(image)
+
+
+
+
+

Morphology

+

The main goal of kornia.morphology is that you could easily implement several morphological operator as follows:

+

new_image = morph.operation(original_image, structuring_element)

+

Let’s check them all!

+
+

Dilation

+
+
from kornia import morphology as morph
+
+dilated_image = morph.dilation(img_t, kernel)  # Dilation
+plot_morph_image(dilated_image)  # Plot
+
+

+
+
+
+
+

Erosion

+
+
eroded_image = morph.erosion(img_t, kernel)  # Erosion
+plot_morph_image(eroded_image)  # Plot
+
+

+
+
+
+
+

Open

+
+
opened_image = morph.opening(img_t, kernel)  # Open
+plot_morph_image(opened_image)
+
+

+
+
+
+
+

Close

+
+
closed_image = morph.closing(img_t, kernel)  # Close
+plot_morph_image(closed_image)  # Plot
+
+

+
+
+
+
+

Morphological Gradient

+
+
graded_image = morph.gradient(img_t, kernel)  # Morphological gradient
+plot_morph_image(1.0 - graded_image)
+
+

+
+
+
+
+

Bottom Hat

+
+
bottom_image = morph.bottom_hat(img_t, kernel)  # Black Hat
+plot_morph_image(1.0 - bottom_image)
+
+

+
+
+
+
+

Top Hat

+
+
toph_image = morph.top_hat(img_t, kernel)  # Top Hat
+plot_morph_image(1.0 - toph_image)
+
+

+
+
+
+
+
+

Conclusion

+

And that’s it!

+

Now you know how to use Kornia to apply differentiable morphological operations in your PyTorch pipeline.

+

Many thanks for using Kornia, and have fun!

+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/morphology_101_files/figure-html/cell-10-output-1.png b/nbs/morphology_101_files/figure-html/cell-10-output-1.png new file mode 100644 index 0000000..1408415 Binary files /dev/null and b/nbs/morphology_101_files/figure-html/cell-10-output-1.png differ diff --git a/nbs/morphology_101_files/figure-html/cell-11-output-1.png b/nbs/morphology_101_files/figure-html/cell-11-output-1.png new file mode 100644 index 0000000..07c06b4 Binary files /dev/null and b/nbs/morphology_101_files/figure-html/cell-11-output-1.png differ diff --git a/nbs/morphology_101_files/figure-html/cell-12-output-1.png b/nbs/morphology_101_files/figure-html/cell-12-output-1.png new file mode 100644 index 0000000..5e68b47 Binary files /dev/null and b/nbs/morphology_101_files/figure-html/cell-12-output-1.png differ diff --git a/nbs/morphology_101_files/figure-html/cell-13-output-1.png b/nbs/morphology_101_files/figure-html/cell-13-output-1.png new file mode 100644 index 0000000..584b8e1 Binary files /dev/null and b/nbs/morphology_101_files/figure-html/cell-13-output-1.png differ diff --git a/nbs/morphology_101_files/figure-html/cell-14-output-1.png b/nbs/morphology_101_files/figure-html/cell-14-output-1.png new file mode 100644 index 0000000..0c7dda8 Binary files /dev/null and b/nbs/morphology_101_files/figure-html/cell-14-output-1.png differ diff --git a/nbs/morphology_101_files/figure-html/cell-8-output-1.png b/nbs/morphology_101_files/figure-html/cell-8-output-1.png new file mode 100644 index 0000000..2d7a566 Binary files /dev/null and b/nbs/morphology_101_files/figure-html/cell-8-output-1.png differ diff --git a/nbs/morphology_101_files/figure-html/cell-9-output-1.png b/nbs/morphology_101_files/figure-html/cell-9-output-1.png new file mode 100644 index 0000000..53aaa9f Binary files /dev/null and b/nbs/morphology_101_files/figure-html/cell-9-output-1.png differ diff --git a/nbs/resize_antialias.html b/nbs/resize_antialias.html new file mode 100644 index 0000000..efd163b --- /dev/null +++ b/nbs/resize_antialias.html @@ -0,0 +1,715 @@ + + + + + + + + + + + + +Kornia - Resize anti-alias + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Resize anti-alias

+
+
Basic
+
Rescale
+
kornia.geometry
+
+
+ +
+
+ In this tutorial we are going to learn how to resize an image with anti-alias. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

August 28, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+

Install Kornia

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
+

Prepare the data

+

Download an example image

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://github.com/kornia/data/raw/main/drslump.jpg"
+download_image(url)
+
+
'drslump.jpg'
+
+
+
+
import kornia as K
+import torch
+from matplotlib import pyplot as plt
+
+
+
def imshow(input: torch.Tensor):
+    B = input.shape[0]
+    fig, axes = plt.subplots(ncols=B, nrows=1, figsize=(20, 10))
+    axes = axes if B > 1 else [axes]
+    for idx, ax in enumerate(axes):
+        ax.imshow(K.utils.tensor_to_image(input[idx]))
+        ax.axis("off")
+
+
+data: torch.Tensor = K.io.load_image("drslump.jpg", K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+
+# plot
+imshow(data)
+
+

+
+
+
+
+

Plain resize vs Antializased resize

+
+
x_025: torch.Tensor = K.geometry.rescale(data, (0.125, 0.125))
+x_025AA: torch.Tensor = K.geometry.rescale(data, (0.125, 0.125), antialias=True)
+out = torch.stack([x_025, x_025AA], dim=0)
+imshow(out)
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/resize_antialias_files/figure-html/cell-5-output-1.png b/nbs/resize_antialias_files/figure-html/cell-5-output-1.png new file mode 100644 index 0000000..fcaf487 Binary files /dev/null and b/nbs/resize_antialias_files/figure-html/cell-5-output-1.png differ diff --git a/nbs/resize_antialias_files/figure-html/cell-6-output-1.png b/nbs/resize_antialias_files/figure-html/cell-6-output-1.png new file mode 100644 index 0000000..446571d Binary files /dev/null and b/nbs/resize_antialias_files/figure-html/cell-6-output-1.png differ diff --git a/nbs/rotate_affine.html b/nbs/rotate_affine.html new file mode 100644 index 0000000..d08b821 --- /dev/null +++ b/nbs/rotate_affine.html @@ -0,0 +1,743 @@ + + + + + + + + + + + + +Kornia - Rotate image using warp affine transform + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Rotate image using warp affine transform

+
+
Basic
+
Affine
+
kornia.geometry
+
+
+ +
+
+ In this tutorial we are going to learn how to rotate an image using the kornia.gemetry components. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

July 6, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://github.com/kornia/data/raw/main/bennett_aden.png"
+download_image(url)
+
+
'bennett_aden.png'
+
+
+
+
import cv2
+import kornia as K
+import numpy as np
+import torch
+import torchvision
+from matplotlib import pyplot as plt
+
+

load the image using kornia

+
+
x_img = K.io.load_image("bennett_aden.png", K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+
+
+
def imshow(input: torch.Tensor, size: tuple = None):
+    out = torchvision.utils.make_grid(input, nrow=4, padding=5)
+    out_np: np.ndarray = K.utils.tensor_to_image(out)
+    plt.figure(figsize=size)
+    plt.imshow(out_np)
+    plt.axis("off")
+    plt.show()
+
+
+
imshow(x_img)
+
+

+
+
+
+

Define the rotation matrix

+
+
# create transformation (rotation)
+alpha: float = 45.0  # in degrees
+angle: torch.tensor = torch.ones(1) * alpha
+
+# define the rotation center
+center: torch.tensor = torch.ones(1, 2)
+center[..., 0] = x_img.shape[3] / 2  # x
+center[..., 1] = x_img.shape[2] / 2  # y
+
+# define the scale factor
+scale: torch.tensor = torch.ones(1, 2)
+
+# compute the transformation matrix
+M: torch.tensor = K.geometry.get_rotation_matrix2d(center, angle, scale)  # 1x2x3
+
+
+
+

Apply the transformation to the original image

+
+
_, _, h, w = x_img.shape
+x_warped: torch.tensor = K.geometry.warp_affine(x_img, M, dsize=(h, w))
+
+imshow(x_warped)
+
+

+
+
+
+
+

Rotate a batch of images

+
+
x_batch = x_img.repeat(16, 1, 1, 1)
+x_rot = K.geometry.rotate(x_batch, torch.linspace(0.0, 360.0, 16))
+
+imshow(x_rot, (16, 16))
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/rotate_affine_files/figure-html/cell-10-output-1.png b/nbs/rotate_affine_files/figure-html/cell-10-output-1.png new file mode 100644 index 0000000..90ae298 Binary files /dev/null and b/nbs/rotate_affine_files/figure-html/cell-10-output-1.png differ diff --git a/nbs/rotate_affine_files/figure-html/cell-7-output-1.png b/nbs/rotate_affine_files/figure-html/cell-7-output-1.png new file mode 100644 index 0000000..8e01486 Binary files /dev/null and b/nbs/rotate_affine_files/figure-html/cell-7-output-1.png differ diff --git a/nbs/rotate_affine_files/figure-html/cell-9-output-1.png b/nbs/rotate_affine_files/figure-html/cell-9-output-1.png new file mode 100644 index 0000000..5cbeebb Binary files /dev/null and b/nbs/rotate_affine_files/figure-html/cell-9-output-1.png differ diff --git a/nbs/total_variation_denoising.html b/nbs/total_variation_denoising.html new file mode 100644 index 0000000..b8b4849 --- /dev/null +++ b/nbs/total_variation_denoising.html @@ -0,0 +1,760 @@ + + + + + + + + + + + + +Kornia - Denoise image using total variation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Denoise image using total variation

+
+
Advanced
+
Denoising
+
+
+ +
+
+ In this tutorial we are going to learn how to denoise an image using the differentiable total_variation loss. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

June 7, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+

Open in HF Spaces

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://github.com/kornia/data/raw/main/doraemon.png"
+download_image(url)
+
+
'doraemon.png'
+
+
+
+
import kornia as K
+import matplotlib.pyplot as plt
+import torch
+import torchvision
+
+
+
def imshow(input: torch.Tensor):
+    out = torchvision.utils.make_grid(input, nrow=2, padding=5)
+    out_np = K.utils.tensor_to_image(out)
+    plt.imshow(out_np)
+    plt.axis("off")
+    plt.show()
+
+
+
# read the image with kornia and add a random noise to it
+img = K.io.load_image("doraemon.png", K.io.ImageLoadType.RGB32)  # CxHxW
+
+noisy_image = (img + torch.normal(torch.zeros_like(img), 0.1)).clamp(0, 1)
+imshow(noisy_image)
+
+

+
+
+

We define the total variation denoising network and the optimizer

+
+
# define the total variation denoising network
+
+
+class TVDenoise(torch.nn.Module):
+    def __init__(self, noisy_image):
+        super().__init__()
+        self.l2_term = torch.nn.MSELoss(reduction="mean")
+        self.regularization_term = K.losses.TotalVariation()
+        # create the variable which will be optimized to produce the noise free image
+        self.clean_image = torch.nn.Parameter(data=noisy_image.clone(), requires_grad=True)
+        self.noisy_image = noisy_image
+
+    def forward(self):
+        return self.l2_term(self.clean_image, self.noisy_image) + 0.0001 * self.regularization_term(self.clean_image)
+
+    def get_clean_image(self):
+        return self.clean_image
+
+
+tv_denoiser = TVDenoise(noisy_image)
+
+# define the optimizer to optimize the 1 parameter of tv_denoiser
+optimizer = torch.optim.SGD(tv_denoiser.parameters(), lr=0.1, momentum=0.9)
+
+

Run the the optimization loop

+
+
num_iters: int = 500
+for i in range(num_iters):
+    optimizer.zero_grad()
+    loss = tv_denoiser().sum()
+    if i % 50 == 0:
+        print(f"Loss in iteration {i} of {num_iters}: {loss.item():.3f}")
+    loss.backward()
+    optimizer.step()
+
+
Loss in iteration 0 of 500: 3.081
+Loss in iteration 50 of 500: 2.723
+Loss in iteration 100 of 500: 2.359
+Loss in iteration 150 of 500: 2.064
+Loss in iteration 200 of 500: 1.828
+Loss in iteration 250 of 500: 1.642
+Loss in iteration 300 of 500: 1.497
+Loss in iteration 350 of 500: 1.384
+Loss in iteration 400 of 500: 1.297
+Loss in iteration 450 of 500: 1.229
+
+
+

Visualize the noisy and resulting cleaned image

+
+
# convert back to numpy
+img_clean = K.utils.tensor_to_image(tv_denoiser.get_clean_image())
+
+# Create the plot
+fig, axs = plt.subplots(1, 2, figsize=(16, 10))
+axs = axs.ravel()
+
+axs[0].axis("off")
+axs[0].set_title("Noisy image")
+axs[0].imshow(K.tensor_to_image(noisy_image))
+
+axs[1].axis("off")
+axs[1].set_title("Cleaned image")
+axs[1].imshow(img_clean)
+
+plt.show()
+
+

+
+
+ + + +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/total_variation_denoising_files/figure-html/cell-6-output-1.png b/nbs/total_variation_denoising_files/figure-html/cell-6-output-1.png new file mode 100644 index 0000000..f2a3c88 Binary files /dev/null and b/nbs/total_variation_denoising_files/figure-html/cell-6-output-1.png differ diff --git a/nbs/total_variation_denoising_files/figure-html/cell-9-output-1.png b/nbs/total_variation_denoising_files/figure-html/cell-9-output-1.png new file mode 100644 index 0000000..eb4f9ac Binary files /dev/null and b/nbs/total_variation_denoising_files/figure-html/cell-9-output-1.png differ diff --git a/nbs/unsharp_mask.html b/nbs/unsharp_mask.html new file mode 100644 index 0000000..9276693 --- /dev/null +++ b/nbs/unsharp_mask.html @@ -0,0 +1,723 @@ + + + + + + + + + + + + +Kornia - Sharpen image using unsharp mask + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Sharpen image using unsharp mask

+
+
Basic
+
Filters
+
kornia.filters
+
+
+ +
+
+ In this tutorial we are going to learn how to use the unsharp mask +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

May 30, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+

We first install kornia

+
+
%%capture
+%matplotlib inline
+!pip install kornia
+!pip install kornia-rs
+
+
+
import kornia
+import matplotlib.pyplot as plt
+
+kornia.__version__
+
+
'0.6.12'
+
+
+

Downloading the example image.

+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://github.com/kornia/data/raw/main/squirrel.jpg"
+download_image(url)
+
+
'squirrel.jpg'
+
+
+
+
# Read the image with Kornia
+data = kornia.io.load_image("squirrel.jpg", kornia.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+
+

We create Unsharp Mask filter object and apply it to data. The unsharp mask filter is initialized with the format kornia.filters.UnsharpMask(kernel_size, sigma). You can tune these parametres and experiment!

+
+
sharpen = kornia.filters.UnsharpMask((9, 9), (2.5, 2.5))
+sharpened_tensor = sharpen(data)
+difference = (sharpened_tensor - data).abs()
+
+
+
# Converting the sharpened tensor to image
+sharpened_image = kornia.utils.tensor_to_image(sharpened_tensor)
+difference_image = kornia.utils.tensor_to_image(difference)
+
+

So, let us understand how we arrived till here.

+
    +
  1. In the unsharp mask technique, first a gaussian blur is applied to the data.
  2. +
  3. Then the blur is subtracted from the orignal data.
  4. +
  5. The resultant is added to the origanl data.
  6. +
  7. So, what do we get? Sharpened data!
  8. +
+
+
# To display the input image, sharpened image and the difference image
+fig, axs = plt.subplots(1, 3, figsize=(16, 10))
+axs = axs.ravel()
+
+axs[0].axis("off")
+axs[0].set_title("image source")
+axs[0].imshow(kornia.tensor_to_image(data))
+
+axs[1].axis("off")
+axs[1].set_title("sharpened")
+axs[1].imshow(sharpened_image)
+
+axs[2].axis("off")
+axs[2].set_title("difference")
+axs[2].imshow(difference_image)
+plt.show()
+
+
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
+
+
+

+
+
+ + + +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/unsharp_mask_files/figure-html/cell-8-output-2.png b/nbs/unsharp_mask_files/figure-html/cell-8-output-2.png new file mode 100644 index 0000000..a14cb8c Binary files /dev/null and b/nbs/unsharp_mask_files/figure-html/cell-8-output-2.png differ diff --git a/nbs/warp_perspective.html b/nbs/warp_perspective.html new file mode 100644 index 0000000..bddaac8 --- /dev/null +++ b/nbs/warp_perspective.html @@ -0,0 +1,767 @@ + + + + + + + + + + + + +Kornia - Warp image using perspective transform + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

Warp image using perspective transform

+
+
Intermediate
+
Warp image
+
kornia.geometry
+
+
+ +
+
+ In this tutorial we are going to learn how to use the functions kornia.get_perspective_transform and kornia.warp_perspective. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

March 18, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+

Install libraries and get the data

+
+
%%capture
+!pip install kornia
+!pip install kornia-rs
+
+
+
import io
+
+import requests
+
+
+def download_image(url: str, filename: str = "") -> str:
+    filename = url.split("/")[-1] if len(filename) == 0 else filename
+    # Download
+    bytesio = io.BytesIO(requests.get(url).content)
+    # Save file
+    with open(filename, "wb") as outfile:
+        outfile.write(bytesio.getbuffer())
+
+    return filename
+
+
+url = "https://github.com/kornia/data/raw/main/bruce.png"
+download_image(url)
+
+
'bruce.png'
+
+
+
+
+

Import libraries and load the data

+
+
import cv2
+import kornia as K
+import matplotlib.pyplot as plt
+import torch
+
+
+
img = K.io.load_image("bruce.png", K.io.ImageLoadType.RGB32)[None, ...]  # BxCxHxW
+print(img.shape)
+
+
torch.Size([1, 3, 372, 600])
+
+
+
+
+

Define the points to warp, compute the homography and warp

+
+
# the source points are the region to crop corners
+points_src = torch.tensor(
+    [
+        [
+            [125.0, 150.0],
+            [562.0, 40.0],
+            [562.0, 282.0],
+            [54.0, 328.0],
+        ]
+    ]
+)
+
+# the destination points are the image vertexes
+h, w = 64, 128  # destination size
+points_dst = torch.tensor(
+    [
+        [
+            [0.0, 0.0],
+            [w - 1.0, 0.0],
+            [w - 1.0, h - 1.0],
+            [0.0, h - 1.0],
+        ]
+    ]
+)
+
+# compute perspective transform
+M: torch.tensor = K.geometry.get_perspective_transform(points_src, points_dst)
+
+# warp the original image by the found transform
+img_warp: torch.tensor = K.geometry.warp_perspective(img.float(), M, dsize=(h, w))
+print(img_warp.shape)
+
+
torch.Size([1, 3, 64, 128])
+
+
+
+
+

Plot the warped data

+
+
# convert back to numpy
+img_np = K.tensor_to_image(img)
+img_warp_np = K.tensor_to_image(img_warp)
+
+# draw points into original image
+for i in range(4):
+    center = tuple(points_src[0, i].long().numpy())
+    img_np = cv2.circle(img_np.copy(), center, 5, (0, 255, 0), -1)
+
+# create the plot
+fig, axs = plt.subplots(1, 2, figsize=(16, 10))
+axs = axs.ravel()
+
+axs[0].axis("off")
+axs[0].set_title("image source")
+axs[0].imshow(img_np)
+
+axs[1].axis("off")
+axs[1].set_title("image destination")
+axs[1].imshow(img_warp_np)
+plt.show()
+
+
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
+
+
+
<matplotlib.image.AxesImage>
+
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/warp_perspective_files/figure-html/cell-7-output-3.png b/nbs/warp_perspective_files/figure-html/cell-7-output-3.png new file mode 100644 index 0000000..323a892 Binary files /dev/null and b/nbs/warp_perspective_files/figure-html/cell-7-output-3.png differ diff --git a/nbs/zca_whitening.html b/nbs/zca_whitening.html new file mode 100644 index 0000000..ed0f9a9 --- /dev/null +++ b/nbs/zca_whitening.html @@ -0,0 +1,847 @@ + + + + + + + + + + + + +Kornia - ZCA Whitening + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ + + + +
+ +
+
+

ZCA Whitening

+
+
Advanced
+
kornia.enhance
+
+
+ +
+
+ The following tutorial will show you how to perform ZCA data whitening on a dataset using kornia.enhance.zca. The documentation for ZCA whitening can be found here. +
+
+ + +
+ +
+
Author
+
+

Edgar Riba

+
+
+ +
+
Published
+
+

May 30, 2021

+
+
+ + +
+ + +
+ +

Open in google colab

+
+

Install necessary packages

+
+
%%capture
+!pip install kornia numpy matplotlib
+
+
+
# Import required libraries
+import kornia as K
+import matplotlib.pyplot as plt
+import numpy as np
+import torch
+from torchvision import datasets, transforms
+from torchvision.utils import make_grid
+
+
+
# Select runtime device
+device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
+print(f"Using {device}")
+
+
Using cuda:0
+
+
+
+
+

ZCA on MNIST

+

Download and load the MNIST dataset.

+
+
%%capture
+dataset = datasets.MNIST("./data/mnist", train=True, download=True, transform=transforms.Compose([transforms.ToTensor()]))
+
+

Stack whole dataset in order to fit ZCA on whole dataset.

+
+
images = []
+for i in range(len(dataset)):
+    im, _ = dataset[i]
+    images.append(im)
+images = torch.stack(images, dim=0).to(device)
+
+

Create an ZCA object and fit the transformation in the forward pass. Setting include_fit is necessary if you need to include the ZCA fitting processing the backwards pass.

+
+
zca = K.enhance.ZCAWhitening(eps=0.1)
+images_zca = zca(images, include_fit=True)
+
+

The result shown should enhance the edges of the MNIST digits because the regularization parameter \(\epsilon\) increases the importance of the higher frequencies which typically correspond to the lowest eigenvalues in ZCA. The result looks similar to the demo from the Stanford ZCA tutorial

+
+
grid_im = make_grid(images[0:30], nrow=5, normalize=True).cpu().numpy()
+grid_zca = make_grid(images_zca[0:30], nrow=5, normalize=True).cpu().numpy()
+
+
+plt.figure(figsize=(15, 15))
+plt.subplot(1, 2, 1)
+plt.imshow(np.transpose(grid_im, [1, 2, 0]))
+plt.title("Input Images")
+plt.xticks([])
+plt.yticks([])
+plt.subplot(1, 2, 2)
+plt.imshow(np.transpose(grid_zca, [1, 2, 0]))
+plt.title(r"ZCA Images $\epsilon = 0.1$")
+plt.xticks([])
+plt.yticks([])
+plt.show()
+
+

+
+
+
+
+

ZCA on CIFAR-10

+

In the next example, we explore using ZCA on the CIFAR 10 dataset, which is a dataset of color images (e.g 4-D tensor \([B, C, H, W]\)). In the cell below, we prepare the dataset.

+
+
%%capture
+dataset = datasets.CIFAR10("./data/cifar", train=True, download=True, transform=transforms.Compose([transforms.ToTensor()]))
+images = []
+for i in range(len(dataset)):
+    im, _ = dataset[i]
+    images.append(im)
+images = torch.stack(images, dim=0).to(device)
+
+

We show another way to fit the ZCA transform using the fit method useful when ZCA is included in data augumentation pipelines. Also if compute_inv = True, this enables the computation of inverse ZCA transform in case a reconstruction is required.

+
+
zca = K.enhance.ZCAWhitening(eps=0.1, compute_inv=True)
+zca.fit(images)
+zca_images = zca(images)
+image_re = zca.inverse_transform(zca_images)
+
+

Note how the higher frequency details are more present in the ZCA normalized images for CIFAR-10 dataset.

+
+
grid_im = make_grid(images[0:30], nrow=5, normalize=True).cpu().numpy()
+grid_zca = make_grid(zca_images[0:30], nrow=5, normalize=True).cpu().numpy()
+grid_re = make_grid(image_re[0:30], nrow=5, normalize=True).cpu().numpy()
+
+
+err_grid = grid_re - grid_im  # Compute error image
+
+plt.figure(figsize=(15, 15))
+plt.subplot(2, 2, 1)
+plt.imshow(np.transpose(grid_im, [1, 2, 0]))
+plt.title("Input Images")
+plt.xticks([])
+plt.yticks([])
+plt.subplot(2, 2, 2)
+plt.imshow(np.transpose(grid_zca, [1, 2, 0]))
+plt.title(r"ZCA Images $\epsilon = 0.1$")
+plt.xticks([])
+plt.yticks([])
+plt.subplot(2, 2, 3)
+plt.imshow(np.transpose(grid_re, [1, 2, 0]))
+plt.title(r"Reconstructed Images")
+plt.xticks([])
+plt.yticks([])
+plt.subplot(2, 2, 4)
+plt.imshow(np.sum(abs(np.transpose(err_grid, [1, 2, 0])), axis=-1))
+plt.colorbar()
+plt.title("Error Image")
+plt.xticks([])
+plt.yticks([])
+plt.show()
+
+

+
+
+
+
+

Differentiability of ZCA

+

We will as simple Gaussian dataset with a mean (3,3) and a diagonal covariance of 1 and 9 to explore the differentiability of ZCA.

+
+
num_data = 100  # Number of points in the dataset
+torch.manual_seed(1234)
+x = torch.cat([torch.randn((num_data, 1), requires_grad=True), 3 * torch.randn((num_data, 1), requires_grad=True)], dim=1) + 3
+
+plt.scatter(x.detach().numpy()[:, 0], x.detach().numpy()[:, 1])
+plt.xlim([-10, 10])
+plt.ylim([-10, 10])
+plt.show()
+
+

+
+
+

Here we explore the affect of the detach_transform option when computing the backwards pass.

+
+
zca_detach = K.enhance.ZCAWhitening(eps=1e-6, detach_transforms=True)
+zca_grad = K.enhance.ZCAWhitening(eps=1e-6, detach_transforms=False)
+
+

As a sanity check, the Jacobian between the input and output of the ZCA transform should be same for all data points in the detached case since the transform acts as linear transform (e.g \(T(X-\mu)\)). In the non-detached case, the Jacobian should vary across datapoints since the input affects the computation of the ZCA transformation matrix (e.g. \(T(X)(X-\mu(X))\)). As the number of samples increases, the Jacobians in the detached and non-detached cases should be roughly the same since the influence of a single datapoint decreases. You can test this by changing num_data . Also note that include_fit=True is included in the forward pass since creation of the transform matrix needs to be included in the forward pass in order to correctly compute the backwards pass.

+
+
import torch.autograd as autograd
+
+J = autograd.functional.jacobian(lambda x: zca_detach(x, include_fit=True), x)
+
+num_disp = 5
+print(f"Jacobian matrices detached for the first {num_disp} points")
+for i in range(num_disp):
+    print(J[i, :, i, :])
+
+print("\n")
+
+J = autograd.functional.jacobian(lambda x: zca_grad(x, include_fit=True), x)
+print(f"Jacobian matrices attached for the first {num_disp} points")
+for i in range(num_disp):
+    print(J[i, :, i, :])
+
+
Jacobian matrices detached for the first 5 points
+tensor([[ 1.0177, -0.0018],
+        [-0.0018,  0.3618]])
+tensor([[ 1.0177, -0.0018],
+        [-0.0018,  0.3618]])
+tensor([[ 1.0177, -0.0018],
+        [-0.0018,  0.3618]])
+tensor([[ 1.0177, -0.0018],
+        [-0.0018,  0.3618]])
+tensor([[ 1.0177, -0.0018],
+        [-0.0018,  0.3618]])
+
+
+Jacobian matrices attached for the first 5 points
+tensor([[ 1.0003, -0.0018],
+        [-0.0018,  0.3547]])
+tensor([[ 1.0006, -0.0027],
+        [-0.0027,  0.3555]])
+tensor([[ 0.9911, -0.0028],
+        [-0.0028,  0.3506]])
+tensor([[9.9281e-01, 4.4671e-04],
+        [4.4671e-04, 3.5368e-01]])
+tensor([[ 1.0072, -0.0019],
+        [-0.0019,  0.3581]])
+
+
+

Lastly, we plot the ZCA whitened data. Note that setting the include_fit to True stores the resulting transformations for future use.

+
+
x_zca = zca_detach(x).detach().numpy()
+plt.scatter(x_zca[:, 0], x_zca[:, 1])
+plt.ylim([-4, 4])
+plt.xlim([-4, 4])
+plt.show()
+
+

+
+
+ + +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/nbs/zca_whitening_files/figure-html/cell-11-output-1.png b/nbs/zca_whitening_files/figure-html/cell-11-output-1.png new file mode 100644 index 0000000..f14a08e Binary files /dev/null and b/nbs/zca_whitening_files/figure-html/cell-11-output-1.png differ diff --git a/nbs/zca_whitening_files/figure-html/cell-12-output-1.png b/nbs/zca_whitening_files/figure-html/cell-12-output-1.png new file mode 100644 index 0000000..f6a0b34 Binary files /dev/null and b/nbs/zca_whitening_files/figure-html/cell-12-output-1.png differ diff --git a/nbs/zca_whitening_files/figure-html/cell-15-output-1.png b/nbs/zca_whitening_files/figure-html/cell-15-output-1.png new file mode 100644 index 0000000..c7f4dc9 Binary files /dev/null and b/nbs/zca_whitening_files/figure-html/cell-15-output-1.png differ diff --git a/nbs/zca_whitening_files/figure-html/cell-8-output-1.png b/nbs/zca_whitening_files/figure-html/cell-8-output-1.png new file mode 100644 index 0000000..5850b25 Binary files /dev/null and b/nbs/zca_whitening_files/figure-html/cell-8-output-1.png differ diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..c34ba1f --- /dev/null +++ b/robots.txt @@ -0,0 +1 @@ +Sitemap: https://kornia.github.io/tutorials/sitemap.xml diff --git a/search.json b/search.json new file mode 100644 index 0000000..6944844 --- /dev/null +++ b/search.json @@ -0,0 +1,954 @@ +[ + { + "objectID": "nbs/extract_combine_patches.html#install-and-get-data", + "href": "nbs/extract_combine_patches.html#install-and-get-data", + "title": "Extracting and Combining Tensor Patches", + "section": "Install and get data", + "text": "Install and get data\n\n%%capture\n%matplotlib inline\n# Install latest kornia\n!pip install kornia\n!pip install kornia-rs\n\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://raw.githubusercontent.com/kornia/data/main/panda.jpg\"\ndownload_image(url)\n\n'panda.jpg'\n\n\n\nimport kornia as K\nimport matplotlib.pyplot as plt\nimport torch\nfrom kornia.contrib import (\n CombineTensorPatches,\n ExtractTensorPatches,\n combine_tensor_patches,\n compute_padding,\n extract_tensor_patches,\n)" + }, + { + "objectID": "nbs/extract_combine_patches.html#using-modules", + "href": "nbs/extract_combine_patches.html#using-modules", + "title": "Extracting and Combining Tensor Patches", + "section": "Using Modules", + "text": "Using Modules\n\nh, w = 8, 8\nwin = 4\npad = 2\n\nimage = torch.randn(2, 3, h, w)\nprint(image.shape)\ntiler = ExtractTensorPatches(window_size=win, stride=win, padding=pad)\nmerger = CombineTensorPatches(original_size=(h, w), window_size=win, stride=win, unpadding=pad)\nimage_tiles = tiler(image)\nprint(image_tiles.shape)\nnew_image = merger(image_tiles)\nprint(new_image.shape)\nassert torch.allclose(image, new_image)\n\ntorch.Size([2, 3, 8, 8])\ntorch.Size([2, 9, 3, 4, 4])\ntorch.Size([2, 3, 8, 8])" + }, + { + "objectID": "nbs/extract_combine_patches.html#using-functions", + "href": "nbs/extract_combine_patches.html#using-functions", + "title": "Extracting and Combining Tensor Patches", + "section": "Using Functions", + "text": "Using Functions\n\nh, w = 8, 8\nwin = 4\npad = 2\n\nimage = torch.randn(1, 1, h, w)\nprint(image.shape)\npatches = extract_tensor_patches(image, window_size=win, stride=win, padding=pad)\nprint(patches.shape)\nrestored_img = combine_tensor_patches(patches, original_size=(h, w), window_size=win, stride=win, unpadding=pad)\nprint(restored_img.shape)\nassert torch.allclose(image, restored_img)\n\ntorch.Size([1, 1, 8, 8])\ntorch.Size([1, 9, 1, 4, 4])\ntorch.Size([1, 1, 8, 8])" + }, + { + "objectID": "nbs/extract_combine_patches.html#padding", + "href": "nbs/extract_combine_patches.html#padding", + "title": "Extracting and Combining Tensor Patches", + "section": "Padding", + "text": "Padding\nAll parameters of extract and combine functions accept a single int or tuple of two ints. Since padding is an integral part of these functions, it’s important to note the following:\n\nIf padding is p -> it means both height and width are padded by 2*p\nIf padding is (ph, pw) -> it means height is padded by 2*ph and width is padded by 2*pw\n\nIt is recommended to use the existing function compute_padding to ensure the required padding is added.\n\nExamples\n\ndef extract_and_combine(image, window_size, padding):\n h, w = image.shape[-2:]\n tiler = ExtractTensorPatches(window_size=window_size, stride=window_size, padding=padding)\n merger = CombineTensorPatches(original_size=(h, w), window_size=window_size, stride=window_size, unpadding=padding)\n image_tiles = tiler(image)\n print(f\"Shape of tensor patches = {image_tiles.shape}\")\n merged_image = merger(image_tiles)\n print(f\"Shape of merged image = {merged_image.shape}\")\n assert torch.allclose(image, merged_image)\n return merged_image\n\n\nimage = torch.randn(2, 3, 9, 9)\n_ = extract_and_combine(image, window_size=(4, 4), padding=(2, 2))\n\nShape of tensor patches = torch.Size([2, 9, 3, 4, 4])\nShape of merged image = torch.Size([2, 3, 9, 9])\n\n\nThese functions also work with rectangular images\n\nrect_image = torch.randn(1, 1, 8, 6)\nprint(rect_image.shape)\n\ntorch.Size([1, 1, 8, 6])\n\n\n\nrestored_image = extract_and_combine(rect_image, window_size=(4, 4), padding=compute_padding((8, 6), 4))\n\nShape of tensor patches = torch.Size([1, 4, 1, 4, 4])\nShape of merged image = torch.Size([1, 1, 8, 6])\n\n\nRecall that when padding is a tuple of ints (ph, pw), the height and width are padded by 2*ph and 2*pw respectively.\n\n# Confirm that the original image and restored image are the same\nassert (restored_image == rect_image).all()\n\nLet’s now visualize how extraction and combining works.\n\n# Load sample image\nimg_tensor = K.io.load_image(\"panda.jpg\", K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\nh, w = img_tensor.shape[-2:]\nprint(f\"Shape of image = {img_tensor.shape}\")\n\nplt.axis(\"off\")\nplt.imshow(K.tensor_to_image(img_tensor))\nplt.show()\n\nShape of image = torch.Size([1, 3, 510, 1020])\n\n\n\n\n\nWe will use window_size = (400, 400) with stride = 200 to extract 15 overlapping tiles of shape (400, 400) and visualize them.\n\n# Set window size\nwin = 400\n# Set stride\nstride = 200\n# Calculate required padding\npad = compute_padding(original_size=(510, 1020), window_size=win)\n\ntiler = ExtractTensorPatches(window_size=win, stride=stride, padding=pad)\nimage_tiles = tiler(img_tensor)\nprint(f\"Shape of image tiles = {image_tiles.shape}\")\n\nShape of image tiles = torch.Size([1, 15, 3, 400, 400])\n\n\n\n# Create the plot\nfig, axs = plt.subplots(5, 3, figsize=(8, 8))\naxs = axs.ravel()\n\nfor i in range(len(image_tiles[0])):\n axs[i].axis(\"off\")\n axs[i].imshow(K.tensor_to_image(image_tiles[0][i]))\n\nplt.show()\n\n\n\n\nFinally, let’s combine the patches and visualize the resulting image\n\nmerger = CombineTensorPatches(original_size=(h, w), window_size=win, stride=stride, unpadding=pad)\nmerged_image = merger(image_tiles)\nprint(f\"Shape of restored image = {merged_image.shape}\")\n\nplt.imshow(K.tensor_to_image(merged_image[0]))\nplt.axis(\"off\")\nplt.show()\n\nShape of restored image = torch.Size([1, 3, 510, 1020])" + }, + { + "objectID": "nbs/data_augmentation_mosiac.html", + "href": "nbs/data_augmentation_mosiac.html", + "title": "Random Mosaic Augmentation", + "section": "", + "text": "%%capture\n!pip install kornia\n!pip install kornia-rs" + }, + { + "objectID": "nbs/data_augmentation_mosiac.html#install-and-get-data", + "href": "nbs/data_augmentation_mosiac.html#install-and-get-data", + "title": "Random Mosaic Augmentation", + "section": "Install and get data", + "text": "Install and get data\nWe install Kornia and some dependencies, and download a simple data sample\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://raw.githubusercontent.com/kornia/data/main/panda.jpg\"\ndownload_image(url)\n\n'panda.jpg'\n\n\n\nimport kornia as K\nimport torch\nfrom matplotlib import pyplot as plt\n\n\ndef plot(img, box):\n img_vis = img.clone()\n img_vis = K.utils.draw_rectangle(img_vis, box, color=torch.tensor([255, 0, 0]))\n plt.imshow(K.tensor_to_image(img_vis))\n plt.show()\n\n\nimg1 = K.io.load_image(\"panda.jpg\", K.io.ImageLoadType.RGB32)\nimg2 = K.augmentation.RandomEqualize(p=1.0, keepdim=True)(img1)\nimg3 = K.augmentation.RandomInvert(p=1.0, keepdim=True)(img1)\nimg4 = K.augmentation.RandomChannelShuffle(p=1.0, keepdim=True)(img1)\n\nplt.figure(figsize=(21, 9))\nplt.imshow(K.tensor_to_image(torch.cat([img1, img2, img3, img4], dim=-1)))\nplt.show()\n\n\n\n\n\nimport kornia as K\nimport torch\nfrom kornia.augmentation import RandomMosaic\n\nx = K.core.concatenate(\n [\n K.geometry.resize(img1[None], (224, 224)),\n K.geometry.resize(img2[None], (224, 224)),\n K.geometry.resize(img3[None], (224, 224)),\n K.geometry.resize(img4[None], (224, 224)),\n ]\n)\n\nboxes = torch.tensor(\n [\n [\n [70.0, 5, 150, 100], # head\n [60, 180, 175, 220], # feet\n ]\n ]\n).repeat(4, 1, 1)\n\naug = RandomMosaic(\n (224, 224), mosaic_grid=(2, 2), start_ratio_range=(0.3, 0.5), p=1.0, min_bbox_size=300, data_keys=[\"input\", \"bbox_xyxy\"]\n)\n\ny, y1 = aug(x, boxes)\n\nplot(y[:1], y1[:1])\n\nClipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers)." + }, + { + "objectID": "nbs/hello_world_tutorial.html", + "href": "nbs/hello_world_tutorial.html", + "title": "Hello world: Planet Kornia", + "section": "", + "text": "Welcome to Planet Kornia: a set of tutorials to learn about Computer Vision in PyTorch.\nThis is the first tutorial that show how one can simply start loading images with Kornia and OpenCV.\n%%capture\n!pip install kornia\n!pip install kornia-rs\nimport cv2\nimport kornia as K\nimport numpy as np\nimport torch\nfrom matplotlib import pyplot as plt\nDownload first an image form internet to start to work.\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\ndownload_image(\"https://github.com/kornia/data/raw/main/arturito.jpg\")\n\n'arturito.jpg'" + }, + { + "objectID": "nbs/hello_world_tutorial.html#load-an-image-with-kornia", + "href": "nbs/hello_world_tutorial.html#load-an-image-with-kornia", + "title": "Hello world: Planet Kornia", + "section": "Load an image with Kornia", + "text": "Load an image with Kornia\nWith Kornia, we can read the image which returns the images in a torch.Tensor in the shape (C,H,W). Also, we can convert a image (array) into a Tensor.\nWe have a couple of utilities to cast the image to a torch.Tensor to make it compliant to the other Kornia components and arrange the data in (B,C,H,W).\nThe read function is kornia.io.load_image, to use it, you need to pip install kornia_rs.\nThe package internally implements kornia_rs which contains a low level implementation for Computer Vision in the Rust language. In addition, we implement the DLPack protocol natively in Rust to reduce the memory footprint during the decoding and types conversion.\nYou can define the type you can load into the tensor, with K.io.ImageLoadType.RGB32 mode the image will be loaded as float32 with values between 0~1.0. Also can define the desired device you want to read the image.\n\nimg_bgr_tensor = K.io.load_image(\"arturito.jpg\", K.io.ImageLoadType.RGB32, device=\"cpu\")\n\nimg_bgr_tensor\n\ntensor([[[0.9412, 0.9412, 0.9412, ..., 0.9412, 0.9412, 0.9412],\n [0.9961, 0.9961, 0.9961, ..., 0.9961, 0.9961, 0.9961],\n [1.0000, 1.0000, 1.0000, ..., 1.0000, 1.0000, 1.0000],\n ...,\n [0.6471, 0.6471, 0.6471, ..., 0.6471, 0.6471, 0.6471],\n [0.6392, 0.6392, 0.6392, ..., 0.6392, 0.6392, 0.6392],\n [0.6510, 0.6510, 0.6510, ..., 0.6510, 0.6510, 0.6510]],\n\n [[0.9412, 0.9412, 0.9412, ..., 0.9412, 0.9412, 0.9412],\n [0.9961, 0.9961, 0.9961, ..., 0.9961, 0.9961, 0.9961],\n [1.0000, 1.0000, 1.0000, ..., 1.0000, 1.0000, 1.0000],\n ...,\n [0.6471, 0.6471, 0.6471, ..., 0.6471, 0.6471, 0.6471],\n [0.6392, 0.6392, 0.6392, ..., 0.6392, 0.6392, 0.6392],\n [0.6510, 0.6510, 0.6510, ..., 0.6510, 0.6510, 0.6510]],\n\n [[0.9412, 0.9412, 0.9412, ..., 0.9412, 0.9412, 0.9412],\n [0.9961, 0.9961, 0.9961, ..., 0.9961, 0.9961, 0.9961],\n [1.0000, 1.0000, 1.0000, ..., 1.0000, 1.0000, 1.0000],\n ...,\n [0.6471, 0.6471, 0.6471, ..., 0.6471, 0.6471, 0.6471],\n [0.6392, 0.6392, 0.6392, ..., 0.6392, 0.6392, 0.6392],\n [0.6510, 0.6510, 0.6510, ..., 0.6510, 0.6510, 0.6510]]])\n\n\n\nplt.imshow(K.tensor_to_image(img_bgr_tensor))\nplt.axis(\"off\");" + }, + { + "objectID": "nbs/hello_world_tutorial.html#load-an-image-with-opencv", + "href": "nbs/hello_world_tutorial.html#load-an-image-with-opencv", + "title": "Hello world: Planet Kornia", + "section": "Load an image with OpenCV", + "text": "Load an image with OpenCV\nWe can use OpenCV to load an image. By default, OpenCV loads images in BGR format and casts to a numpy.ndarray with the data layout (H,W,C).\nHowever, because matplotlib saves an image in RGB format, in OpenCV you need to change the BGR to RGB so that an image is displayed properly.\n\nimg_bgr: np.array = cv2.imread(\"arturito.jpg\") # HxWxC / np.uint8\nimg_rgb: np.array = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)\n\nplt.imshow(img_rgb)\nplt.axis(\"off\");\n\n\n\n\nThe utility is kornia.image_to_tensor which casts a numpy.ndarray to a torch.Tensor and permutes the channels to leave the image ready for being used with any other PyTorch or Kornia component.\nThe image is casted into a 4D torch.Tensor with zero-copy.\n\nx_bgr: torch.tensor = K.image_to_tensor(img_bgr) # CxHxW / torch.uint8\nx_bgr = x_bgr.unsqueeze(0) # 1xCxHxW\nprint(f\"convert from '{img_bgr.shape}' to '{x_bgr.shape}'\")\n\nconvert from '(144, 256, 3)' to 'torch.Size([1, 3, 144, 256])'\n\n\nWe can convert from BGR to RGB with a kornia.color component.\n\nx_rgb: torch.tensor = K.color.bgr_to_rgb(x_bgr) # 1xCxHxW / torch.uint8" + }, + { + "objectID": "nbs/hello_world_tutorial.html#visualize-an-image-with-matplotib", + "href": "nbs/hello_world_tutorial.html#visualize-an-image-with-matplotib", + "title": "Hello world: Planet Kornia", + "section": "Visualize an image with Matplotib", + "text": "Visualize an image with Matplotib\nWe will use Matplotlib for the visualisation inside the notebook. Matplotlib requires a numpy.ndarray in the (H,W,C) format, and for doing so we will go back with kornia.tensor_to_image which will convert the image to the correct format.\n\nimg_bgr: np.array = K.tensor_to_image(x_bgr)\nimg_rgb: np.array = K.tensor_to_image(x_rgb)\n\nCreate a subplot to visualize the original an a modified image\n\nfig, axs = plt.subplots(1, 2, figsize=(32, 16))\naxs = axs.ravel()\n\naxs[0].axis(\"off\")\naxs[0].imshow(img_rgb)\n\naxs[1].axis(\"off\")\naxs[1].imshow(img_bgr)\n\nplt.show()" + }, + { + "objectID": "nbs/filtering_operators.html", + "href": "nbs/filtering_operators.html", + "title": "Filtering Operators", + "section": "", + "text": "%%capture\n!pip install kornia\n!pip install kornia-rs\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://github.com/kornia/data/raw/main/drslump.jpg\"\ndownload_image(url)\n\n'drslump.jpg'\nimport kornia as K\nimport torch\nimport torchvision\nfrom matplotlib import pyplot as plt\nWe use Kornia to load an image to memory represented directly in a tensor\nx_rgb: torch.Tensor = K.io.load_image(\"doraemon.png\", K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\n\nx_gray = K.color.rgb_to_grayscale(x_rgb)\ndef imshow(input: torch.Tensor):\n if input.shape != x_rgb.shape:\n input = K.geometry.resize(input, size=(x_rgb.shape[-2:]))\n out = torch.cat([x_rgb, input], dim=-1)\n out = torchvision.utils.make_grid(out, nrow=2, padding=5)\n out_np = K.utils.tensor_to_image(out)\n plt.imshow(out_np)\n plt.axis(\"off\")\n plt.show()\nimshow(x_rgb)" + }, + { + "objectID": "nbs/filtering_operators.html#box-blur", + "href": "nbs/filtering_operators.html#box-blur", + "title": "Filtering Operators", + "section": "Box Blur", + "text": "Box Blur\n\nx_blur: torch.Tensor = K.filters.box_blur(x_rgb, (9, 9))\nimshow(x_blur)" + }, + { + "objectID": "nbs/filtering_operators.html#blur-pool", + "href": "nbs/filtering_operators.html#blur-pool", + "title": "Filtering Operators", + "section": "Blur Pool", + "text": "Blur Pool\n\nx_blur: torch.Tensor = K.filters.blur_pool2d(x_rgb, kernel_size=9)\nimshow(x_blur)" + }, + { + "objectID": "nbs/filtering_operators.html#gaussian-blur", + "href": "nbs/filtering_operators.html#gaussian-blur", + "title": "Filtering Operators", + "section": "Gaussian Blur", + "text": "Gaussian Blur\n\nx_blur: torch.Tensor = K.filters.gaussian_blur2d(x_rgb, (11, 11), (11.0, 11.0))\nimshow(x_blur)" + }, + { + "objectID": "nbs/filtering_operators.html#max-pool", + "href": "nbs/filtering_operators.html#max-pool", + "title": "Filtering Operators", + "section": "Max Pool", + "text": "Max Pool\n\nx_blur: torch.Tensor = K.filters.max_blur_pool2d(x_rgb, kernel_size=11)\nimshow(x_blur)" + }, + { + "objectID": "nbs/filtering_operators.html#median-blur", + "href": "nbs/filtering_operators.html#median-blur", + "title": "Filtering Operators", + "section": "Median Blur", + "text": "Median Blur\n\nx_blur: torch.Tensor = K.filters.median_blur(x_rgb, (5, 5))\nimshow(x_blur)" + }, + { + "objectID": "nbs/filtering_operators.html#motion-blur", + "href": "nbs/filtering_operators.html#motion-blur", + "title": "Filtering Operators", + "section": "Motion Blur", + "text": "Motion Blur\n\nx_blur: torch.Tensor = K.filters.motion_blur(x_rgb, 9, 90.0, 1)\nimshow(x_blur)" + }, + { + "objectID": "nbs/data_patch_sequential.html#install-and-get-data", + "href": "nbs/data_patch_sequential.html#install-and-get-data", + "title": "Patch Sequential", + "section": "Install and get data", + "text": "Install and get data\nWe install Kornia and some dependencies, and download a simple data sample\n\n!pip install kornia\n!pip install kornia-rs\n\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://raw.githubusercontent.com/kornia/data/main/panda.jpg\"\ndownload_image(url)\n\n'panda.jpg'\n\n\n\nimport kornia as K\nimport torch\nfrom kornia.augmentation import ImageSequential, PatchSequential\nfrom matplotlib import pyplot as plt\n\nimg_tensor = K.io.load_image(\"panda.jpg\", K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\nh, w = img_tensor.shape[2:]\n\nplt.imshow(K.tensor_to_image(img_tensor))\nplt.axis(\"off\")\nplt.show()" + }, + { + "objectID": "nbs/data_patch_sequential.html#patch-augmentation-sequential-with-patchwise_applytrue", + "href": "nbs/data_patch_sequential.html#patch-augmentation-sequential-with-patchwise_applytrue", + "title": "Patch Sequential", + "section": "Patch Augmentation Sequential with patchwise_apply=True", + "text": "Patch Augmentation Sequential with patchwise_apply=True\npatchwise_apply is a feature that used to define unique processing pipeline for each patch location. If patchwise_apply=True, the number of pipelines defined must be as same as the number of patches in an image.\n\npseq = PatchSequential(\n ImageSequential(\n K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=0.5),\n K.augmentation.RandomPerspective(0.2, p=0.5),\n K.augmentation.RandomSolarize(0.1, 0.1, p=0.5),\n ),\n K.augmentation.RandomAffine(15, [0.1, 0.1], [0.7, 1.2], [0.0, 20.0], p=0.5),\n K.augmentation.RandomPerspective(0.2, p=0.5),\n ImageSequential(\n K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=0.5),\n K.augmentation.RandomPerspective(0.2, p=0.5),\n K.augmentation.RandomSolarize(0.1, 0.1, p=0.5),\n ),\n K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=0.5),\n K.augmentation.RandomAffine(15, [0.1, 0.1], [0.7, 1.2], [0.0, 20.0], p=0.5),\n K.augmentation.RandomPerspective(0.2, p=0.5),\n K.augmentation.RandomSolarize(0.1, 0.1, p=0.5),\n K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=0.5),\n K.augmentation.RandomAffine(15, [0.1, 0.1], [0.7, 1.2], [0.0, 20.0], p=0.5),\n ImageSequential(\n K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=0.5),\n K.augmentation.RandomPerspective(0.2, p=0.5),\n K.augmentation.RandomSolarize(0.1, 0.1, p=0.5),\n ),\n K.augmentation.RandomSolarize(0.1, 0.1, p=0.5),\n K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=0.5),\n K.augmentation.RandomAffine(15, [0.1, 0.1], [0.7, 1.2], [0.0, 20.0], p=0.5),\n K.augmentation.RandomPerspective(0.2, p=0.5),\n K.augmentation.RandomSolarize(0.1, 0.1, p=0.5),\n patchwise_apply=True,\n same_on_batch=True,\n)\nout_tensor = pseq(img_tensor.repeat(2, 1, 1, 1))\n\nplt.figure(figsize=(21, 9))\nplt.imshow(K.tensor_to_image(torch.cat([out_tensor[0], out_tensor[1]], dim=2)))\nplt.axis(\"off\")\nplt.show()" + }, + { + "objectID": "nbs/data_patch_sequential.html#patch-augmentation-sequential-with-patchwise_applyfalse", + "href": "nbs/data_patch_sequential.html#patch-augmentation-sequential-with-patchwise_applyfalse", + "title": "Patch Sequential", + "section": "Patch Augmentation Sequential with patchwise_apply=False", + "text": "Patch Augmentation Sequential with patchwise_apply=False\nIf patchwise_apply=False, all the args will be combined and applied as one pipeline for each patch.\n\npseq = PatchSequential(\n K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=0.75),\n K.augmentation.RandomAffine(15, [0.1, 0.1], [0.7, 1.2], [0.0, 20.0], p=0.5),\n patchwise_apply=False,\n same_on_batch=False,\n)\nout_tensor = pseq(img_tensor.repeat(2, 1, 1, 1))\n\nplt.figure(figsize=(21, 9))\nplt.imshow(K.tensor_to_image(torch.cat([out_tensor[0], out_tensor[1]], dim=2)))\nplt.axis(\"off\")\nplt.show()" + }, + { + "objectID": "nbs/color_conversions.html", + "href": "nbs/color_conversions.html", + "title": "Color space conversion", + "section": "", + "text": "%%capture\n!pip install kornia\n!pip install kornia-rs" + }, + { + "objectID": "nbs/color_conversions.html#explanation", + "href": "nbs/color_conversions.html#explanation", + "title": "Color space conversion", + "section": "Explanation", + "text": "Explanation\nImages are asumed to be loaded either in RGB or Grayscale space.\n\nWe will use OpenCV to load images.\nConvert from BGR to RGB (note that OpenCV loads images in BGR format).\n\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://github.com/kornia/data/raw/main/simba.png\"\ndownload_image(url)\n\n'simba.png'\n\n\n\nimport kornia as K\nimport numpy as np\nimport torch\nimport torchvision\nfrom matplotlib import pyplot as plt\n\n\n# read the image with Kornia\nimg_tensor = K.io.load_image(\"simba.png\", K.io.ImageLoadType.RGB32) # CxHxW\nimg_array = K.tensor_to_image(img_tensor)\n\nplt.axis(\"off\")\nplt.imshow(img_array)\nplt.show()\n\n\n\n\nAlternatively we can use use kornia.color to perform the color transformation.\n\nConvert the tensor to RGB\nConvert back the tensor to numpy for visualisation.\n\n\nx_rgb: torch.Tensor = img_tensor\n\n# to BGR\nx_bgr: torch.Tensor = K.color.rgb_to_bgr(x_rgb)\n\n# convert back to numpy and visualize\nimg_np: np.array = K.tensor_to_image(x_bgr)\nplt.imshow(img_np)\nplt.axis(\"off\");\n\n\n\n\nUsing kornia we easily perform color transformation in batch mode.\n\ndef imshow(input: torch.Tensor):\n out: torch.Tensor = torchvision.utils.make_grid(input, nrow=2, padding=5)\n out_np: np.array = K.tensor_to_image(out)\n plt.imshow(out_np)\n plt.axis(\"off\")\n plt.show()\n\n\n# create a batch of images\nxb_bgr = torch.stack([x_bgr, K.geometry.hflip(x_bgr), K.geometry.vflip(x_bgr), K.geometry.rot180(x_bgr)])\nimshow(xb_bgr)\n\n\n\n\n\n# convert to back to RGB\nxb_rgb = K.color.bgr_to_rgb(xb_bgr)\nimshow(xb_rgb)\n\n\n\n\n\n# convert to grayscale\n# NOTE: image comes in torch.uint8, and kornia assumes floating point type\nxb_gray = K.color.rgb_to_grayscale(xb_rgb)\nimshow(xb_gray)\n\n\n\n\n\n# convert to HSV\nxb_hsv = K.color.rgb_to_hsv(xb_rgb)\nimshow(xb_hsv)\n\nClipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers)." + }, + { + "objectID": "nbs/warp_perspective.html#install-libraries-and-get-the-data", + "href": "nbs/warp_perspective.html#install-libraries-and-get-the-data", + "title": "Warp image using perspective transform", + "section": "Install libraries and get the data", + "text": "Install libraries and get the data\n\n%%capture\n!pip install kornia\n!pip install kornia-rs\n\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://github.com/kornia/data/raw/main/bruce.png\"\ndownload_image(url)\n\n'bruce.png'" + }, + { + "objectID": "nbs/warp_perspective.html#import-libraries-and-load-the-data", + "href": "nbs/warp_perspective.html#import-libraries-and-load-the-data", + "title": "Warp image using perspective transform", + "section": "Import libraries and load the data", + "text": "Import libraries and load the data\n\nimport cv2\nimport kornia as K\nimport matplotlib.pyplot as plt\nimport torch\n\n\nimg = K.io.load_image(\"bruce.png\", K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\nprint(img.shape)\n\ntorch.Size([1, 3, 372, 600])" + }, + { + "objectID": "nbs/warp_perspective.html#define-the-points-to-warp-compute-the-homography-and-warp", + "href": "nbs/warp_perspective.html#define-the-points-to-warp-compute-the-homography-and-warp", + "title": "Warp image using perspective transform", + "section": "Define the points to warp, compute the homography and warp", + "text": "Define the points to warp, compute the homography and warp\n\n# the source points are the region to crop corners\npoints_src = torch.tensor(\n [\n [\n [125.0, 150.0],\n [562.0, 40.0],\n [562.0, 282.0],\n [54.0, 328.0],\n ]\n ]\n)\n\n# the destination points are the image vertexes\nh, w = 64, 128 # destination size\npoints_dst = torch.tensor(\n [\n [\n [0.0, 0.0],\n [w - 1.0, 0.0],\n [w - 1.0, h - 1.0],\n [0.0, h - 1.0],\n ]\n ]\n)\n\n# compute perspective transform\nM: torch.tensor = K.geometry.get_perspective_transform(points_src, points_dst)\n\n# warp the original image by the found transform\nimg_warp: torch.tensor = K.geometry.warp_perspective(img.float(), M, dsize=(h, w))\nprint(img_warp.shape)\n\ntorch.Size([1, 3, 64, 128])" + }, + { + "objectID": "nbs/warp_perspective.html#plot-the-warped-data", + "href": "nbs/warp_perspective.html#plot-the-warped-data", + "title": "Warp image using perspective transform", + "section": "Plot the warped data", + "text": "Plot the warped data\n\n# convert back to numpy\nimg_np = K.tensor_to_image(img)\nimg_warp_np = K.tensor_to_image(img_warp)\n\n# draw points into original image\nfor i in range(4):\n center = tuple(points_src[0, i].long().numpy())\n img_np = cv2.circle(img_np.copy(), center, 5, (0, 255, 0), -1)\n\n# create the plot\nfig, axs = plt.subplots(1, 2, figsize=(16, 10))\naxs = axs.ravel()\n\naxs[0].axis(\"off\")\naxs[0].set_title(\"image source\")\naxs[0].imshow(img_np)\n\naxs[1].axis(\"off\")\naxs[1].set_title(\"image destination\")\naxs[1].imshow(img_warp_np)\nplt.show()\n\nClipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n\n\n<matplotlib.image.AxesImage>" + }, + { + "objectID": "nbs/rotate_affine.html", + "href": "nbs/rotate_affine.html", + "title": "Rotate image using warp affine transform", + "section": "", + "text": "%%capture\n!pip install kornia\n!pip install kornia-rs\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://github.com/kornia/data/raw/main/bennett_aden.png\"\ndownload_image(url)\n\n'bennett_aden.png'\nimport cv2\nimport kornia as K\nimport numpy as np\nimport torch\nimport torchvision\nfrom matplotlib import pyplot as plt\nload the image using kornia\nx_img = K.io.load_image(\"bennett_aden.png\", K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\ndef imshow(input: torch.Tensor, size: tuple = None):\n out = torchvision.utils.make_grid(input, nrow=4, padding=5)\n out_np: np.ndarray = K.utils.tensor_to_image(out)\n plt.figure(figsize=size)\n plt.imshow(out_np)\n plt.axis(\"off\")\n plt.show()\nimshow(x_img)" + }, + { + "objectID": "nbs/rotate_affine.html#define-the-rotation-matrix", + "href": "nbs/rotate_affine.html#define-the-rotation-matrix", + "title": "Rotate image using warp affine transform", + "section": "Define the rotation matrix", + "text": "Define the rotation matrix\n\n# create transformation (rotation)\nalpha: float = 45.0 # in degrees\nangle: torch.tensor = torch.ones(1) * alpha\n\n# define the rotation center\ncenter: torch.tensor = torch.ones(1, 2)\ncenter[..., 0] = x_img.shape[3] / 2 # x\ncenter[..., 1] = x_img.shape[2] / 2 # y\n\n# define the scale factor\nscale: torch.tensor = torch.ones(1, 2)\n\n# compute the transformation matrix\nM: torch.tensor = K.geometry.get_rotation_matrix2d(center, angle, scale) # 1x2x3" + }, + { + "objectID": "nbs/rotate_affine.html#apply-the-transformation-to-the-original-image", + "href": "nbs/rotate_affine.html#apply-the-transformation-to-the-original-image", + "title": "Rotate image using warp affine transform", + "section": "Apply the transformation to the original image", + "text": "Apply the transformation to the original image\n\n_, _, h, w = x_img.shape\nx_warped: torch.tensor = K.geometry.warp_affine(x_img, M, dsize=(h, w))\n\nimshow(x_warped)" + }, + { + "objectID": "nbs/rotate_affine.html#rotate-a-batch-of-images", + "href": "nbs/rotate_affine.html#rotate-a-batch-of-images", + "title": "Rotate image using warp affine transform", + "section": "Rotate a batch of images", + "text": "Rotate a batch of images\n\nx_batch = x_img.repeat(16, 1, 1, 1)\nx_rot = K.geometry.rotate(x_batch, torch.linspace(0.0, 360.0, 16))\n\nimshow(x_rot, (16, 16))" + }, + { + "objectID": "nbs/resize_antialias.html#install-kornia", + "href": "nbs/resize_antialias.html#install-kornia", + "title": "Resize anti-alias", + "section": "Install Kornia", + "text": "Install Kornia\n\n%%capture\n!pip install kornia\n!pip install kornia-rs" + }, + { + "objectID": "nbs/resize_antialias.html#prepare-the-data", + "href": "nbs/resize_antialias.html#prepare-the-data", + "title": "Resize anti-alias", + "section": "Prepare the data", + "text": "Prepare the data\nDownload an example image\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://github.com/kornia/data/raw/main/drslump.jpg\"\ndownload_image(url)\n\n'drslump.jpg'\n\n\n\nimport kornia as K\nimport torch\nfrom matplotlib import pyplot as plt\n\n\ndef imshow(input: torch.Tensor):\n B = input.shape[0]\n fig, axes = plt.subplots(ncols=B, nrows=1, figsize=(20, 10))\n axes = axes if B > 1 else [axes]\n for idx, ax in enumerate(axes):\n ax.imshow(K.utils.tensor_to_image(input[idx]))\n ax.axis(\"off\")\n\n\ndata: torch.Tensor = K.io.load_image(\"drslump.jpg\", K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\n\n# plot\nimshow(data)" + }, + { + "objectID": "nbs/resize_antialias.html#plain-resize-vs-antializased-resize", + "href": "nbs/resize_antialias.html#plain-resize-vs-antializased-resize", + "title": "Resize anti-alias", + "section": "Plain resize vs Antializased resize", + "text": "Plain resize vs Antializased resize\n\nx_025: torch.Tensor = K.geometry.rescale(data, (0.125, 0.125))\nx_025AA: torch.Tensor = K.geometry.rescale(data, (0.125, 0.125), antialias=True)\nout = torch.stack([x_025, x_025AA], dim=0)\nimshow(out)" + }, + { + "objectID": "nbs/gaussian_blur.html#preparation", + "href": "nbs/gaussian_blur.html#preparation", + "title": "Blur image using GaussianBlur operator", + "section": "Preparation", + "text": "Preparation\nWe first install Kornia.\n\n%%capture\n%matplotlib inline\n!pip install kornia\n!pip install kornia-rs\n\n\nimport kornia\n\nkornia.__version__\n\n'0.6.12'\n\n\nNow we download the example image.\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://github.com/kornia/data/raw/main/bennett_aden.png\"\ndownload_image(url)\n\n'bennett_aden.png'" + }, + { + "objectID": "nbs/gaussian_blur.html#example", + "href": "nbs/gaussian_blur.html#example", + "title": "Blur image using GaussianBlur operator", + "section": "Example", + "text": "Example\nWe first import the required libraries and load the data.\n\nimport matplotlib.pyplot as plt\nimport torch\n\n# read the image with kornia\ndata = kornia.io.load_image(\"./bennett_aden.png\", kornia.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\n\nTo apply a filter, we create the Gaussian Blur filter object and apply it to the data:\n\n# create the operator\ngauss = kornia.filters.GaussianBlur2d((11, 11), (10.5, 10.5))\n\n# blur the image\nx_blur: torch.tensor = gauss(data)\n\nThat’s it! We can compare the pre-transform image and the post-transform image:\n\n# convert back to numpy\nimg_blur = kornia.tensor_to_image(x_blur)\n\n# Create the plot\nfig, axs = plt.subplots(1, 2, figsize=(16, 10))\naxs = axs.ravel()\n\naxs[0].axis(\"off\")\naxs[0].set_title(\"image source\")\naxs[0].imshow(kornia.tensor_to_image(data))\n\naxs[1].axis(\"off\")\naxs[1].set_title(\"image blurred\")\naxs[1].imshow(img_blur)\n\npass" + }, + { + "objectID": "nbs/unsharp_mask.html", + "href": "nbs/unsharp_mask.html", + "title": "Sharpen image using unsharp mask", + "section": "", + "text": "We first install kornia\n\n%%capture\n%matplotlib inline\n!pip install kornia\n!pip install kornia-rs\n\n\nimport kornia\nimport matplotlib.pyplot as plt\n\nkornia.__version__\n\n'0.6.12'\n\n\nDownloading the example image.\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://github.com/kornia/data/raw/main/squirrel.jpg\"\ndownload_image(url)\n\n'squirrel.jpg'\n\n\n\n# Read the image with Kornia\ndata = kornia.io.load_image(\"squirrel.jpg\", kornia.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\n\nWe create Unsharp Mask filter object and apply it to data. The unsharp mask filter is initialized with the format kornia.filters.UnsharpMask(kernel_size, sigma). You can tune these parametres and experiment!\n\nsharpen = kornia.filters.UnsharpMask((9, 9), (2.5, 2.5))\nsharpened_tensor = sharpen(data)\ndifference = (sharpened_tensor - data).abs()\n\n\n# Converting the sharpened tensor to image\nsharpened_image = kornia.utils.tensor_to_image(sharpened_tensor)\ndifference_image = kornia.utils.tensor_to_image(difference)\n\nSo, let us understand how we arrived till here.\n\nIn the unsharp mask technique, first a gaussian blur is applied to the data.\nThen the blur is subtracted from the orignal data.\nThe resultant is added to the origanl data.\nSo, what do we get? Sharpened data!\n\n\n# To display the input image, sharpened image and the difference image\nfig, axs = plt.subplots(1, 3, figsize=(16, 10))\naxs = axs.ravel()\n\naxs[0].axis(\"off\")\naxs[0].set_title(\"image source\")\naxs[0].imshow(kornia.tensor_to_image(data))\n\naxs[1].axis(\"off\")\naxs[1].set_title(\"sharpened\")\naxs[1].imshow(sharpened_image)\n\naxs[2].axis(\"off\")\naxs[2].set_title(\"difference\")\naxs[2].imshow(difference_image)\nplt.show()\n\nClipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers)." + }, + { + "objectID": "nbs/image_registration.html", + "href": "nbs/image_registration.html", + "title": "Image Registration by Direct Optimization", + "section": "", + "text": "The images are courtesy of Dennis Sakva\n%%capture\n!pip install kornia\n!pip install kornia-rs\nimport io\n\nimport requests\n\n\ndef download_data(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\ndownload_data(\"http://cmp.felk.cvut.cz/~mishkdmy/bee.zip\")\n\n'bee.zip'\n%%capture\n!unzip bee.zip\nImport needed libraries\nimport os\nfrom copy import deepcopy\nfrom typing import List\n\nimport imageio\nimport kornia as K\nimport kornia.geometry as KG\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport torch\nimport torch.nn.functional as F\nfrom tqdm import tqdm\n\n\ndef get_data_directory(base):\n path = os.path.join(\"../\", base)\n if os.path.isdir(os.path.join(path, \"data\")):\n return os.path.join(path, \"data/\")\n return get_data_directory(path)" + }, + { + "objectID": "nbs/image_registration.html#images-preview", + "href": "nbs/image_registration.html#images-preview", + "title": "Image Registration by Direct Optimization", + "section": "Images preview", + "text": "Images preview\nLet’s check our images. There are almost 100 of them, so we will show only each 10th\n\nfnames = os.listdir(\"bee\")\nfnames = [f\"bee/{x}\" for x in sorted(fnames) if x.endswith(\"JPG\")]\nfig, axis = plt.subplots(2, 5, figsize=(12, 4), sharex=\"all\", sharey=\"all\", frameon=False)\nfor i, fname in enumerate(fnames):\n if i % 10 != 0:\n continue\n j = i // 10\n img = K.io.load_image(fname, K.io.ImageLoadType.RGB8)\n axis[j // 5][j % 5].imshow(K.tensor_to_image(img), aspect=\"auto\")\nplt.subplots_adjust(wspace=0.05, hspace=0.05)\nfig.tight_layout()\n\n\n\n\nSo the focus goes from back to the front, so we have to match and merge them in the same order." + }, + { + "objectID": "nbs/image_registration.html#image-registration", + "href": "nbs/image_registration.html#image-registration", + "title": "Image Registration by Direct Optimization", + "section": "Image registration", + "text": "Image registration\nWe will need ImageRegistrator object to do the matching. Because the photos are takes so that only slight rotation, shift and scale change is possible, we will use similarity mode, which does exactly this.\n\nuse_cuda: bool = torch.cuda.is_available()\ndevice = torch.device(\"cuda\" if use_cuda else \"cpu\")\nregistrator = KG.ImageRegistrator(\"similarity\", loss_fn=F.mse_loss, lr=8e-4, pyramid_levels=3, num_iterations=500).to(device)\nprint(device)\n\ncuda\n\n\nWe will register images sequentially with ImageRegistrator.\n\n%%capture\nmodels = []\nfor i, fname in tqdm(enumerate(fnames)):\n if i == 0:\n continue\n prev_img = K.io.load_image(fnames[i - 1], K.io.ImageLoadType.RGB32, device=device)[None, ...]\n curr_img = K.io.load_image(fname, K.io.ImageLoadType.RGB32, device=device)[None, ...]\n model = registrator.register(prev_img, curr_img)\n models.append(deepcopy(model.detach()))\n\nLet’s take the final (the most close-focused) image as the reference - this means that we have to convert our image transforms from (between i and i+1) mode into (between i and last). We can do it by matrix multiplication.\n\nmodels_to_final = [torch.eye(3, device=device)[None]]\nfor m in models[::-1]:\n models_to_final.append(m @ models_to_final[-1])\nmodels_to_final = models_to_final[::-1]\n\nLet’s check what do we got.\n\nfig, axis = plt.subplots(2, 5, figsize=(12, 4), sharex=\"all\", sharey=\"all\", frameon=False)\nfor i, fname in enumerate(fnames):\n if i % 10 != 0:\n continue\n timg = K.io.load_image(fname, K.io.ImageLoadType.RGB32, device=device)[None, ...]\n j = i // 10\n timg_dst = KG.homography_warp(timg, models_to_final[i], timg.shape[-2:])\n axis[j // 5][j % 5].imshow(K.tensor_to_image(timg_dst * 255.0).astype(np.uint8), aspect=\"auto\")\nplt.subplots_adjust(wspace=0.05, hspace=0.05)\nfig.tight_layout()\n\n\n\n\nFinally we will merge the image sequence into single image. The idea is to detect the image parts, which are in focus from the current image and blend them into the final images. To get the sharp image part we can use kornia.filters.laplacian. Then we reproject image1 into image2, and merge them using mask we created.\n\ndef merge_sharp1_into2(timg1, timg2, trans1to2, verbose=False):\n curr_img = timg2.clone()\n warped = KG.homography_warp(timg1, torch.inverse(trans1to2), timg.shape[-2:])\n mask1 = K.filters.laplacian(K.color.rgb_to_grayscale(timg1), 7).abs()\n mask1_norm = (mask1 - mask1.min()) / (mask1.max() - mask1.min())\n mask1_blur = K.filters.gaussian_blur2d(mask1_norm, (9, 9), (1.6, 1.6))\n mask1_blur = mask1_blur / mask1_blur.max()\n warped_mask = KG.homography_warp(mask1_blur.float(), torch.inverse(models_to_final[i]), timg1.shape[-2:])\n curr_img = warped_mask * warped + (1 - warped_mask) * curr_img\n if verbose:\n fig, axis = plt.subplots(1, 4, figsize=(15, 6), sharex=\"all\", sharey=\"all\", frameon=False)\n axis[0].imshow(K.tensor_to_image(timg1))\n axis[1].imshow(K.tensor_to_image(mask1_blur))\n axis[2].imshow(K.tensor_to_image(timg2))\n axis[3].imshow(K.tensor_to_image(curr_img))\n axis[0].set_title(\"Img1\")\n axis[1].set_title(\"Sharp mask on img1\")\n axis[2].set_title(\"Img2\")\n axis[3].set_title(\"Blended image\")\n return curr_img\n\n\ntimg1 = K.io.load_image(fnames[50], K.io.ImageLoadType.RGB32, device=device)[None, ...]\ntimg2 = K.io.load_image(fnames[-1], K.io.ImageLoadType.RGB32, device=device)[None, ...]\nout = merge_sharp1_into2(timg1, timg2, models_to_final[50], True)\n\n\n\n\nThe blending does not look really good, but that is because we are trying to merge non-consequtive images with very different focus. Let’s try to apply it sequentially and see, what happens.\nWe will also create a video of our sharpening process.\n\n%%capture\nbase_img = K.io.load_image(fnames[-1], K.io.ImageLoadType.RGB32, device=device)[None, ...]\ncurr_img = deepcopy(base_img)\n\ntry:\n video_writer = imageio.get_writer(\"sharpening.avi\", fps=8)\n video_writer.append_data((K.tensor_to_image(curr_img) * 255.0).astype(np.uint8))\n video_ok = True\nexcept:\n video_ok = False\n\n\nwith torch.no_grad():\n for i, fname in tqdm(enumerate(fnames)):\n timg = K.io.load_image(fname, K.io.ImageLoadType.RGB32, device=device)[None, ...]\n curr_img = merge_sharp1_into2(timg.to(device), curr_img.to(device), models_to_final[i].to(device))\n if video_ok:\n video_writer.append_data((K.tensor_to_image(curr_img) * 255.0).astype(np.uint8))\nif video_ok:\n video_writer.close()\n\n\nplt.imshow(K.tensor_to_image(curr_img.float()))\nplt.title(\"Final result\")\n\nText(0.5, 1.0, 'Final result')\n\n\n\n\n\nNow we can play the video of our sharpening. The code is ugly to allow running from Google Colab (as shown here)\n\nfrom base64 import b64encode\n\nfrom IPython.display import HTML\n\nif video_ok:\n mp4 = open(\"sharpening.avi\", \"rb\").read()\nelse:\n mp4 = open(get_data_directory(\"\") + \"sharpening.mp4\", \"rb\").read()\ndata_url = \"data:video/mp4;base64,\" + b64encode(mp4).decode()\n\n\nHTML(\n f\"\"\"\n<video width=400 controls>\n <source src=\"{data_url}\" type=\"video/mp4\">\n</video>\n\"\"\"\n)\n\n\n\n \n\n\n\nResult looks quite nice and more detailed, although a bit soft. You can try yourself different blending parameters yourself (e.g. blur kernel size) in order to improve the final result." + }, + { + "objectID": "nbs/image_matching_adalam.html", + "href": "nbs/image_matching_adalam.html", + "title": "Image matching example with KeyNet-AdaLAM", + "section": "", + "text": "First, we will install everything needed:\n\nfresh version of kornia for AdaLAM\nfresh version of OpenCV for MAGSAC++ geometry estimation\nkornia_moons for the conversions and visualization\n\nDocs: match_adalam\n\n%%capture\n!pip install kornia\n!pip install kornia-rs\n!pip install kornia_moons\n!pip install opencv-python --upgrade\n\nNow let’s download an image pair\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl_a = \"https://github.com/kornia/data/raw/main/matching/kn_church-2.jpg\"\nurl_b = \"https://github.com/kornia/data/raw/main/matching/kn_church-8.jpg\"\ndownload_image(url_a)\ndownload_image(url_b)\n\n'kn_church-8.jpg'\n\n\nFirst, imports.\n\nimport cv2\nimport kornia as K\nimport kornia.feature as KF\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport torch\nfrom kornia_moons.viz import *\n\n# device = K.utils.get_cuda_or_mps_device_if_available()\ndevice = torch.device(\"cpu\")\n\n\n%%capture\nfname1 = \"kn_church-2.jpg\"\nfname2 = \"kn_church-8.jpg\"\n\nimg1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32, device=device)[None, ...]\nimg2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32, device=device)[None, ...]\n\nfeature = KF.KeyNetAffNetHardNet(5000, True).eval().to(device)\n\ninput_dict = {\n \"image0\": K.color.rgb_to_grayscale(img1), # LofTR works on grayscale images only\n \"image1\": K.color.rgb_to_grayscale(img2),\n}\n\nhw1 = torch.tensor(img1.shape[2:])\nhw2 = torch.tensor(img1.shape[2:])\n\nadalam_config = {\"device\": device}\n\nwith torch.inference_mode():\n lafs1, resps1, descs1 = feature(K.color.rgb_to_grayscale(img1))\n lafs2, resps2, descs2 = feature(K.color.rgb_to_grayscale(img2))\n dists, idxs = KF.match_adalam(\n descs1.squeeze(0),\n descs2.squeeze(0),\n lafs1,\n lafs2, # Adalam takes into account also geometric information\n config=adalam_config,\n hw1=hw1,\n hw2=hw2, # Adalam also benefits from knowing image size\n )\n\n\nprint(f\"{idxs.shape[0]} tentative matches with AdaLAM\")\n\n405 tentative matches with AdaLAM\n\n\n\ndef get_matching_keypoints(lafs1, lafs2, idxs):\n mkpts1 = KF.get_laf_center(lafs1).squeeze()[idxs[:, 0]].detach().cpu().numpy()\n mkpts2 = KF.get_laf_center(lafs2).squeeze()[idxs[:, 1]].detach().cpu().numpy()\n return mkpts1, mkpts2\n\n\nmkpts1, mkpts2 = get_matching_keypoints(lafs1, lafs2, idxs)\n\nFm, inliers = cv2.findFundamentalMat(mkpts1, mkpts2, cv2.USAC_MAGSAC, 0.75, 0.999, 100000)\ninliers = inliers > 0\nprint(f\"{inliers.sum()} inliers with AdaLAM\")\n\n195 inliers with AdaLAM\n\n\nLet’s draw the inliers in green and tentative correspondences in yellow\n\ndraw_LAF_matches(\n lafs1,\n lafs2,\n idxs,\n K.tensor_to_image(img1),\n K.tensor_to_image(img2),\n inliers,\n draw_dict={\"inlier_color\": (0.2, 1, 0.2), \"tentative_color\": (1, 1, 0.2, 0.3), \"feature_color\": None, \"vertical\": False},\n)" + }, + { + "objectID": "nbs/image_matching_disk.html", + "href": "nbs/image_matching_disk.html", + "title": "Image matching example with DISK local features", + "section": "", + "text": "First, we will install everything needed:\n\nfresh version of kornia for DISK\nfresh version of OpenCV for MAGSAC++ geometry estimation\nkornia_moons for the conversions and visualization\n\nDocs: kornia.feature.DISK\n\n%%capture\n!pip install kornia\n!pip install kornia-rs\n!pip install kornia_moons --no-deps\n!pip install opencv-python --upgrade\n\nNow let’s download an image pair\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl_a = \"https://github.com/kornia/data/raw/main/matching/kn_church-2.jpg\"\nurl_b = \"https://github.com/kornia/data/raw/main/matching/kn_church-8.jpg\"\ndownload_image(url_a)\ndownload_image(url_b)\n\n'kn_church-8.jpg'\n\n\nFirst, imports.\n\nimport cv2\nimport kornia as K\nimport kornia.feature as KF\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport torch\nfrom kornia.feature.adalam import AdalamFilter\nfrom kornia_moons.viz import *\n\ndevice = K.utils.get_cuda_or_mps_device_if_available()\nprint(device)\n\ncuda:0\n\n\n\n# %%capture\nfname1 = \"kn_church-2.jpg\"\nfname2 = \"kn_church-8.jpg\"\n\nadalam_config = KF.adalam.get_adalam_default_config()\n# adalam_config['orientation_difference_threshold'] = None\n# adalam_config['scale_rate_threshold'] = None\nadalam_config[\"force_seed_mnn\"] = False\nadalam_config[\"search_expansion\"] = 16\nadalam_config[\"ransac_iters\"] = 256\n\n\nimg1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32, device=device)[None, ...]\nimg2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32, device=device)[None, ...]\n\nnum_features = 2048\ndisk = KF.DISK.from_pretrained(\"depth\").to(device)\n\nhw1 = torch.tensor(img1.shape[2:], device=device)\nhw2 = torch.tensor(img2.shape[2:], device=device)\n\nmatch_with_adalam = True\n\nwith torch.inference_mode():\n inp = torch.cat([img1, img2], dim=0)\n features1, features2 = disk(inp, num_features, pad_if_not_divisible=True)\n kps1, descs1 = features1.keypoints, features1.descriptors\n kps2, descs2 = features2.keypoints, features2.descriptors\n if match_with_adalam:\n lafs1 = KF.laf_from_center_scale_ori(kps1[None], 96 * torch.ones(1, len(kps1), 1, 1, device=device))\n lafs2 = KF.laf_from_center_scale_ori(kps2[None], 96 * torch.ones(1, len(kps2), 1, 1, device=device))\n\n dists, idxs = KF.match_adalam(descs1, descs2, lafs1, lafs2, hw1=hw1, hw2=hw2, config=adalam_config)\n else:\n dists, idxs = KF.match_smnn(descs1, descs2, 0.98)\n\n\nprint(f\"{idxs.shape[0]} tentative matches with DISK AdaLAM\")\n\n222 tentative matches with DISK AdaLAM\n\n\n\ndef get_matching_keypoints(kp1, kp2, idxs):\n mkpts1 = kp1[idxs[:, 0]]\n mkpts2 = kp2[idxs[:, 1]]\n return mkpts1, mkpts2\n\n\nmkpts1, mkpts2 = get_matching_keypoints(kps1, kps2, idxs)\n\nFm, inliers = cv2.findFundamentalMat(\n mkpts1.detach().cpu().numpy(), mkpts2.detach().cpu().numpy(), cv2.USAC_MAGSAC, 1.0, 0.999, 100000\n)\ninliers = inliers > 0\nprint(f\"{inliers.sum()} inliers with DISK\")\n\n103 inliers with DISK\n\n\nLet’s draw the inliers in green and tentative correspondences in yellow\n\ndraw_LAF_matches(\n KF.laf_from_center_scale_ori(kps1[None].cpu()),\n KF.laf_from_center_scale_ori(kps2[None].cpu()),\n idxs.cpu(),\n K.tensor_to_image(img1.cpu()),\n K.tensor_to_image(img2.cpu()),\n inliers,\n draw_dict={\"inlier_color\": (0.2, 1, 0.2), \"tentative_color\": (1, 1, 0.2, 0.3), \"feature_color\": None, \"vertical\": False},\n)" + }, + { + "objectID": "nbs/fit_line.html", + "href": "nbs/fit_line.html", + "title": "Fit line tutorial", + "section": "", + "text": "import matplotlib.pyplot as plt\nimport torch\nfrom kornia.core import concatenate, stack\nfrom kornia.geometry.line import ParametrizedLine, fit_line\n\n\nstd = 1.2 # standard deviation for the points\nnum_points = 50 # total number of points\n\n\n# create a baseline\np0 = torch.tensor([0.0, 0.0])\np1 = torch.tensor([1.0, 1.0])\n\nl1 = ParametrizedLine.through(p0, p1)\nprint(l1)\n\nOrigin: Parameter containing:\ntensor([0., 0.], requires_grad=True)\nDirection: Parameter containing:\ntensor([0.7071, 0.7071], requires_grad=True)\n\n\n\n# sample some points and weights\npts, w = [], []\nfor t in torch.linspace(-10, 10, num_points):\n p2 = l1.point_at(t)\n p2_noise = torch.rand_like(p2) * std\n p2 += p2_noise\n pts.append(p2)\n w.append(1 - p2_noise.mean())\npts = stack(pts)\nw = stack(w)\n\n\n# fit the the line\nl2 = fit_line(pts[None, ...], w[None, ...])\nprint(l2)\n\n# project some points along the estimated line\np3 = l2.point_at(-10)\np4 = l2.point_at(10)\n\nOrigin: Parameter containing:\ntensor([[0.5933, 0.5888]], requires_grad=True)\nDirection: Parameter containing:\ntensor([[-0.7146, -0.6995]], requires_grad=True)\n\n\n\nX = concatenate((p3, p4), dim=0).detach().numpy()\nX_pts = pts.detach().numpy()\n\nplt.plot(X_pts[..., :, 0], X_pts[:, 1], \"ro\")\nplt.plot(X[:, 0], X[:, 1])\nplt.show()" + }, + { + "objectID": "nbs/aliased_and_not_aliased_patch_extraction.html", + "href": "nbs/aliased_and_not_aliased_patch_extraction.html", + "title": "Image anti-alias with local features", + "section": "", + "text": "%%capture\n!pip install kornia\n!pip install kornia-rs\n\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://github.com/kornia/data/raw/main/drslump.jpg\"\ndownload_image(url)\n\n'drslump.jpg'\n\n\nFirst, lets load some image.\n\n%matplotlib inline\nimport kornia as K\nimport kornia.feature as KF\nimport matplotlib.pyplot as plt\nimport torch\n\n\ndevice = torch.device(\"cpu\")\n\nimg_original = K.io.load_image(\"drslump.jpg\", K.io.ImageLoadType.RGB32, device=device)[None, ...]\n\nplt.figure()\nplt.imshow(K.tensor_to_image(img_original))\n\n<matplotlib.image.AxesImage>\n\n\n\n\n\n\nB, CH, H, W = img_original.shape\n\nDOWNSAMPLE = 4\nimg_small = K.geometry.resize(img_original, (H // DOWNSAMPLE, W // DOWNSAMPLE), interpolation=\"area\")\nplt.figure()\nplt.imshow(K.tensor_to_image(img_small))\n\n<matplotlib.image.AxesImage>\n\n\n\n\n\nNow, lets define a keypoint with a large support region.\n\ndef show_lafs(img, lafs, idx=0, color=\"r\", figsize=(10, 7)):\n x, y = KF.laf.get_laf_pts_to_draw(lafs, idx)\n plt.figure(figsize=figsize)\n if isinstance(img, torch.Tensor):\n img_show = K.tensor_to_image(img)\n else:\n img_show = img\n plt.imshow(img_show)\n plt.plot(x, y, color)\n return\n\n\nlaf_orig = torch.tensor([[150.0, 0, 180], [0, 150, 280]]).float().view(1, 1, 2, 3)\nlaf_small = laf_orig / float(DOWNSAMPLE)\n\nshow_lafs(img_original, laf_orig, figsize=(6, 4))\nshow_lafs(img_small, laf_small, figsize=(6, 4))\n\n\n\n\n\n\n\nNow lets compare how extracted patch would look like when extracted in a naive way and from scale pyramid.\n\nPS = 32\nwith torch.no_grad():\n patches_pyr_orig = KF.extract_patches_from_pyramid(img_original, laf_orig.to(device), PS)\n patches_simple_orig = KF.extract_patches_simple(img_original, laf_orig.to(device), PS)\n\n patches_pyr_small = KF.extract_patches_from_pyramid(img_small, laf_small.to(device), PS)\n patches_simple_small = KF.extract_patches_simple(img_small, laf_small.to(device), PS)\n\n# Now we will glue all the patches together:\n\n\ndef vert_cat_with_margin(p1, p2, margin=3):\n b, n, ch, h, w = p1.size()\n return torch.cat([p1, torch.ones(b, n, ch, h, margin).to(device), p2], dim=4)\n\n\ndef horiz_cat_with_margin(p1, p2, margin=3):\n b, n, ch, h, w = p1.size()\n return torch.cat([p1, torch.ones(b, n, ch, margin, w).to(device), p2], dim=3)\n\n\npatches_pyr = vert_cat_with_margin(patches_pyr_orig, patches_pyr_small)\npatches_naive = vert_cat_with_margin(patches_simple_orig, patches_simple_small)\n\npatches_all = horiz_cat_with_margin(patches_naive, patches_pyr)\n\nNow lets show the result. Top row is what you get if you are extracting patches without any antialiasing - note how the patches extracted from the images of different sizes differ.\nBottom row is patches, which are extracted from images of different sizes using a scale pyramid. They are not yet exactly the same, but the difference is much smaller.\n\nplt.figure(figsize=(10, 10))\nplt.imshow(K.tensor_to_image(patches_all[0, 0]))\n\n<matplotlib.image.AxesImage>\n\n\n\n\n\nLets check how much it influences local descriptor performance such as HardNet\n\nhardnet = KF.HardNet(True).eval()\nall_patches = (\n torch.cat([patches_pyr_orig, patches_pyr_small, patches_simple_orig, patches_simple_small], dim=0)\n .squeeze(1)\n .mean(dim=1, keepdim=True)\n)\nwith torch.no_grad():\n descs = hardnet(all_patches)\n distances = torch.cdist(descs, descs)\n print(distances.cpu().detach().numpy())\n\n[[0. 0.16867691 0.8070452 0.52112377]\n [0.16867691 0. 0.7973113 0.48472866]\n [0.8070452 0.7973113 0. 0.59267515]\n [0.52112377 0.48472866 0.59267515 0. ]]\n\n\nSo the descriptor difference between antialiased patches is 0.09 and between naively extracted – 0.44" + }, + { + "objectID": "nbs/geometry_generate_patch.html", + "href": "nbs/geometry_generate_patch.html", + "title": "Image patch generation", + "section": "", + "text": "%%capture\n!pip install kornia\n!pip install kornia-rs\n\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\ndownload_image(\"https://github.com/kornia/data/raw/main/homography/img1.ppm\")\n\n'img1.ppm'\n\n\nFirst load libraries and images\n\n%matplotlib inline\nimport kornia as K\nimport matplotlib.pyplot as plt\nimport torch\n\n\ndef imshow(image: torch.tensor, height: int = 10, width: int = 10):\n \"\"\"Utility function to plot images.\"\"\"\n plt.figure(figsize=(height, width))\n plt.imshow(K.tensor_to_image(image))\n plt.axis(\"off\")\n plt.show()\n\nLoad and show the original image\n\ntorch.manual_seed(0)\n\ntimg: torch.Tensor = K.io.load_image(\"img1.ppm\", K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\n\nimshow(timg, 10, 10)\n\n\n\n\nIn the following section we are going to take the original image and generate random crops of a given size.\n\nrandom_crop = K.augmentation.RandomCrop((64, 64))\n\npatch = torch.cat([random_crop(timg) for _ in range(15)], dim=-1)\n\nimshow(patch[0], 22, 22)\n\n\n\n\nNext, we will show how to crop patches and apply forth and back random geometric transformations.\n\n# transform a patch\n\nrandom_crop = K.augmentation.RandomCrop((64, 64))\nrandom_affine = K.augmentation.RandomAffine([-15, 15], [0.0, 0.25])\n\n# crop\npatch = random_crop(timg)\n\n# transform and retrieve transformation\npatch_affine = random_affine(patch)\ntransformation = random_affine.get_transformation_matrix(patch)\n\n# invert patch\n_, _, H, W = patch.shape\npatch_inv = K.geometry.warp_perspective(patch_affine, torch.inverse(transformation), (H, W))\n\n# visualise - (original, transformed, reconstructed)\npatches_vis = torch.cat([patch, patch_affine, patch_inv], dim=-1)\nimshow(patches_vis, 15, 15)" + }, + { + "objectID": "nbs/data_augmentation_segmentation.html#install-and-get-data", + "href": "nbs/data_augmentation_segmentation.html#install-and-get-data", + "title": "Data Augmentation Semantic Segmentation", + "section": "Install and get data", + "text": "Install and get data\nWe install Kornia and some dependencies, and download a simple data sample\n\n%%capture\n%matplotlib inline\n!pip install kornia\n!pip install kornia-rs\n!pip install opencv-python\n\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"http://www.zemris.fer.hr/~ssegvic/multiclod/images/causevic16semseg3.png\"\ndownload_image(url)\n\n'causevic16semseg3.png'\n\n\n\n# import the libraries\nimport kornia as K\nimport matplotlib.pyplot as plt\nimport torch\nimport torch.nn as nn" + }, + { + "objectID": "nbs/data_augmentation_segmentation.html#define-augmentation-pipeline", + "href": "nbs/data_augmentation_segmentation.html#define-augmentation-pipeline", + "title": "Data Augmentation Semantic Segmentation", + "section": "Define Augmentation pipeline", + "text": "Define Augmentation pipeline\nWe define a class to define our augmentation API using an nn.Module\n\nclass MyAugmentation(nn.Module):\n def __init__(self):\n super().__init__()\n # we define and cache our operators as class members\n self.k1 = K.augmentation.ColorJitter(0.15, 0.25, 0.25, 0.25)\n self.k2 = K.augmentation.RandomAffine([-45.0, 45.0], [0.0, 0.15], [0.5, 1.5], [0.0, 0.15])\n\n def forward(self, img: torch.Tensor, mask: torch.Tensor) -> torch.Tensor:\n # 1. apply color only in image\n # 2. apply geometric tranform\n img_out = self.k2(self.k1(img))\n\n # 3. infer geometry params to mask\n # TODO: this will change in future so that no need to infer params\n mask_out = self.k2(mask, self.k2._params)\n\n return img_out, mask_out\n\nLoad the data and apply the transforms\n\ndef load_data(data_path: str) -> torch.Tensor:\n data_t: torch.Tensor = K.io.load_image(data_path, K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\n img, labels = data_t[..., :571], data_t[..., 572:]\n return img, labels\n\n\n# load data (B, C, H, W)\nimg, labels = load_data(\"causevic16semseg3.png\")\n\n# create augmentation instance\naug = MyAugmentation()\n\n# apply the augmenation pipelone to our batch of data\nimg_aug, labels_aug = aug(img, labels)\n\n# visualize\nimg_out = torch.cat([img, labels], dim=-1)\nplt.imshow(K.tensor_to_image(img_out))\nplt.axis(\"off\")\n\n# generate several samples\nnum_samples: int = 10\n\nfor img_id in range(num_samples):\n # generate data\n img_aug, labels_aug = aug(img, labels)\n img_out = torch.cat([img_aug, labels_aug], dim=-1)\n\n # save data\n plt.figure()\n plt.imshow(K.tensor_to_image(img_out))\n plt.axis(\"off\")\n # plt.savefig(f\"img_{img_id}.png\", bbox_inches=\"tight\")\n plt.show()" + }, + { + "objectID": "nbs/data_augmentation_2d.html", + "href": "nbs/data_augmentation_2d.html", + "title": "Data Augmentation 2D", + "section": "", + "text": "Just a simple examples showing the Augmentations available on Kornia.\nFor more information check the docs: https://kornia.readthedocs.io/en/latest/augmentation.module.html\n%%capture\n!pip install kornia\n!pip install kornia-rs\nimport kornia\nimport matplotlib.pyplot as plt\nfrom kornia.augmentation import (\n CenterCrop,\n ColorJiggle,\n ColorJitter,\n PadTo,\n RandomAffine,\n RandomBoxBlur,\n RandomBrightness,\n RandomChannelShuffle,\n RandomContrast,\n RandomCrop,\n RandomCutMixV2,\n RandomElasticTransform,\n RandomEqualize,\n RandomErasing,\n RandomFisheye,\n RandomGamma,\n RandomGaussianBlur,\n RandomGaussianNoise,\n RandomGrayscale,\n RandomHorizontalFlip,\n RandomHue,\n RandomInvert,\n RandomJigsaw,\n RandomMixUpV2,\n RandomMosaic,\n RandomMotionBlur,\n RandomPerspective,\n RandomPlanckianJitter,\n RandomPlasmaBrightness,\n RandomPlasmaContrast,\n RandomPlasmaShadow,\n RandomPosterize,\n RandomResizedCrop,\n RandomRGBShift,\n RandomRotation,\n RandomSaturation,\n RandomSharpness,\n RandomSolarize,\n RandomThinPlateSpline,\n RandomVerticalFlip,\n)" + }, + { + "objectID": "nbs/data_augmentation_2d.html#load-an-image", + "href": "nbs/data_augmentation_2d.html#load-an-image", + "title": "Data Augmentation 2D", + "section": "Load an Image", + "text": "Load an Image\nThe augmentations expects an image with shape BxCxHxW\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://raw.githubusercontent.com/kornia/data/main/panda.jpg\"\ndownload_image(url)\n\n'panda.jpg'\n\n\n\nimg_type = kornia.io.ImageLoadType.RGB32\nimg = kornia.io.load_image(\"panda.jpg\", img_type, \"cpu\")[None]\n\n\ndef plot_tensor(data, title=\"\"):\n b, c, h, w = data.shape\n\n fig, axes = plt.subplots(1, b, dpi=150, subplot_kw={\"aspect\": \"equal\"})\n if b == 1:\n axes = [axes]\n\n for idx, ax in enumerate(axes):\n ax.imshow(kornia.utils.tensor_to_image(data[idx, ...]))\n ax.set_ylim(h, 0)\n ax.set_xlim(0, w)\n ax.tick_params(top=True, labeltop=True, bottom=False, labelbottom=False)\n fig.suptitle(title)\n plt.show()\n\n\nplot_tensor(img, \"panda\")" + }, + { + "objectID": "nbs/data_augmentation_2d.html#d-transforms", + "href": "nbs/data_augmentation_2d.html#d-transforms", + "title": "Data Augmentation 2D", + "section": "2D transforms", + "text": "2D transforms\nSometimes you may wish to apply the exact same transformations on all the elements in one batch. Here, we provided a same_on_batch keyword to all random generators for you to use. Instead of an element-wise parameter generating, it will generate exact same parameters across the whole batch.\n\n# Create a batched input\nnum_samples = 2\n\ninpt = img.repeat(num_samples, 1, 1, 1)\n\n\nIntensity\n\nRandom Planckian Jitter\n\nrandomplanckianjitter = RandomPlanckianJitter(\"blackbody\", same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomplanckianjitter(inpt), \"Planckian Jitter\")\n\n\n\n\n\n\nRandom Plasma Shadow\n\nrandomplasmashadow = RandomPlasmaShadow(\n roughness=(0.1, 0.7), shade_intensity=(-1.0, 0.0), shade_quantity=(0.0, 1.0), same_on_batch=False, keepdim=False, p=1.0\n)\n\nplot_tensor(randomplasmashadow(inpt), \"Plasma Shadow\")\n\n\n\n\n\n\nRandom Plasma Brightness\n\nrandomplasmabrightness = RandomPlasmaBrightness(\n roughness=(0.1, 0.7), intensity=(0.0, 1.0), same_on_batch=False, keepdim=False, p=1.0\n)\nplot_tensor(randomplasmabrightness(inpt), \"Plasma Brightness\")\n\n\n\n\n\n\nRandom Plasma Contrast\n\nrandomplasmacontrast = RandomPlasmaContrast(roughness=(0.1, 0.7), same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomplasmacontrast(inpt), \"Plasma Contrast\")\n\n\n\n\n\n\nColor Jiggle\n\ncolorjiggle = ColorJiggle(0.3, 0.3, 0.3, 0.3, same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(colorjiggle(inpt), \"Color Jiggle\")\n\n\n\n\n\n\nColor Jitter\n\ncolorjitter = ColorJitter(0.3, 0.3, 0.3, 0.3, same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(colorjitter(inpt), \"Color Jitter\")\n\n\n\n\n\n\nRandom Box Blur\n\nrandomboxblur = RandomBoxBlur((21, 5), \"reflect\", same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomboxblur(inpt), \"Box Blur\")\n\n\n\n\n\n\nRandom Brightness\n\nrandombrightness = RandomBrightness(brightness=(0.8, 1.2), clip_output=True, same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randombrightness(inpt), \"Random Brightness\")\n\n\n\n\n\n\nRandom Channel Shuffle\n\nrandomchannelshuffle = RandomChannelShuffle(same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomchannelshuffle(inpt), \"Random Channel Shuffle\")\n\n\n\n\n\n\nRandom Contrast\n\nrandomcontrast = RandomContrast(contrast=(0.8, 1.2), clip_output=True, same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomcontrast(inpt), \"Random Contrast\")\n\n\n\n\n\n\nRandom Equalize\n\nrandomequalize = RandomEqualize(same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomequalize(inpt), \"Random Equalize\")\n\n\n\n\n\n\nRandom Gamma\n\nrandomgamma = RandomGamma((0.2, 1.3), (1.0, 1.5), same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomgamma(inpt), \"Random Gamma\")\n\n\n\n\n\n\nRandom Grayscale\n\nrandomgrayscale = RandomGrayscale(same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomgrayscale(inpt), \"Random Grayscale\")\n\n\n\n\n\n\nRandom Gaussian Blur\n\nrandomgaussianblur = RandomGaussianBlur((21, 21), (0.2, 1.3), \"reflect\", same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomgaussianblur(inpt), \"Random Gaussian Blur\")\n\nClipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n\n\n\n\n\n\n\nRandom Gaussian Noise\n\nrandomgaussiannoise = RandomGaussianNoise(mean=0.2, std=0.7, same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomgaussiannoise(inpt), \"Random Gaussian Noise\")\n\nClipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\nClipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n\n\n\n\n\n\n\nRandom Hue\n\nrandomhue = RandomHue((-0.2, 0.4), same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomhue(inpt), \"Random Hue\")\n\n\n\n\n\n\nRandom Motion Blur\n\nrandommotionblur = RandomMotionBlur((7, 7), 35.0, 0.5, \"reflect\", \"nearest\", same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randommotionblur(inpt), \"Random Motion Blur\")\n\n\n\n\n\n\nRandom Posterize\n\nrandomposterize = RandomPosterize(bits=3, same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomposterize(inpt), \"Random Posterize\")\n\n\n\n\n\n\nRandom RGB Shift\n\nrandomrgbshift = RandomRGBShift(\n r_shift_limit=0.5, g_shift_limit=0.5, b_shift_limit=0.5, same_on_batch=False, keepdim=False, p=1.0\n)\nplot_tensor(randomrgbshift(inpt), \"Random RGB Shift\")\n\n\n\n\n\n\nRandom Saturation\n\nrandomsaturation = RandomSaturation((1.0, 1.0), same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomsaturation(inpt), \"Random Saturation\")\n\n\n\n\n\n\nRandom Sharpness\n\nrandomsharpness = RandomSharpness((0.5, 1.0), same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomsharpness(inpt), \"Random Sharpness\")\n\n\n\n\n\n\nRandom Solarize\n\nrandomsolarize = RandomSolarize(0.3, 0.1, same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomsolarize(inpt), \"Random Solarize\")\n\n\n\n\n\n\n\nGeometric\n\nCenter Crop\n\ncentercrop = CenterCrop(150, resample=\"nearest\", cropping_mode=\"resample\", align_corners=True, keepdim=False, p=1.0)\n\nplot_tensor(centercrop(inpt), \"Center Crop\")\n\n\n\n\n\n\nPad To\n\npadto = PadTo((500, 500), \"constant\", 1, keepdim=False)\n\nplot_tensor(padto(inpt), \"Pad To\")\n\n\n\n\n\n\nRandom Affine\n\nrandomaffine = RandomAffine(\n (-15.0, 5.0),\n (0.3, 1.0),\n (0.4, 1.3),\n 0.5,\n resample=\"nearest\",\n padding_mode=\"reflection\",\n align_corners=True,\n same_on_batch=False,\n keepdim=False,\n p=1.0,\n)\nplot_tensor(randomaffine(inpt), \"Random Affine\")\n\n\n\n\n\n\nRandom Crop\n\nrandomcrop = RandomCrop(\n (150, 150),\n 10,\n True,\n 1,\n \"constant\",\n \"nearest\",\n cropping_mode=\"resample\",\n same_on_batch=False,\n align_corners=True,\n keepdim=False,\n p=1.0,\n)\n\nplot_tensor(randomcrop(inpt), \"Random Crop\")\n\n\n\n\n\n\nRandom Erasing\n\nrandomerasing = RandomErasing(scale=(0.02, 0.33), ratio=(0.3, 3.3), value=1, same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomerasing(inpt), \"Random Erasing\")\n\n\n\n\n\n\nRandom Elastic Transform\n\nrandomelastictransform = RandomElasticTransform(\n (27, 27), (33, 31), (0.5, 1.5), align_corners=True, padding_mode=\"reflection\", same_on_batch=False, keepdim=False, p=1.0\n)\n\nplot_tensor(randomelastictransform(inpt), \"Random Elastic Transform\")\n\n\n\n\n\n\nRandom Fish Eye\n\nc = kornia.core.tensor([-0.3, 0.3])\ng = kornia.core.tensor([0.9, 1.0])\nrandomfisheye = RandomFisheye(c, c, g, same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomfisheye(inpt), \"Random Fish Eye\")\n\n\n\n\n\n\nRandom Horizontal Flip\n\nrandomhorizontalflip = RandomHorizontalFlip(same_on_batch=False, keepdim=False, p=0.7)\n\nplot_tensor(randomhorizontalflip(inpt), \"Random Horizontal Flip\")\n\n\n\n\n\n\nRandom Invert\n\nrandominvert = RandomInvert(same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randominvert(inpt), \"Random Invert\")\n\n\n\n\n\n\nRandom Perspective\n\nrandomperspective = RandomPerspective(0.5, \"nearest\", align_corners=True, same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomperspective(inpt), \"Random Perspective\")\n\n\n\n\n\n\nRandom Resized Crop\n\nrandomresizedcrop = RandomResizedCrop(\n (200, 200),\n (0.4, 1.0),\n (2.0, 2.0),\n \"nearest\",\n align_corners=True,\n cropping_mode=\"resample\",\n same_on_batch=False,\n keepdim=False,\n p=1.0,\n)\n\nplot_tensor(randomresizedcrop(inpt), \"Random Resized Crop\")\n\n\n\n\n\n\nRandom Rotation\n\nrandomrotation = RandomRotation(15.0, \"nearest\", align_corners=True, same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomrotation(inpt), \"Random Rotation\")\n\n\n\n\n\n\nRandom Vertical Flip\n\nrandomverticalflip = RandomVerticalFlip(same_on_batch=False, keepdim=False, p=0.6, p_batch=1.0)\n\nplot_tensor(randomverticalflip(inpt), \"Random Vertical Flip\")\n\n\n\n\n\n\nRandom Thin Plate Spline\n\nrandomthinplatespline = RandomThinPlateSpline(0.6, align_corners=True, same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomverticalflip(inpt), \"Random Thin Plate Spline\")\n\n\n\n\n\n\n\nMix\n\nRandom Cut Mix\n\nrandomcutmixv2 = RandomCutMixV2(4, (0.2, 0.9), 0.1, same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randomcutmixv2(inpt), \"Random Cut Mix\")\n\n\n\n\n\n\nRandom Mix Up\n\nrandommixupv2 = RandomMixUpV2((0.1, 0.9), same_on_batch=False, keepdim=False, p=1.0)\n\nplot_tensor(randommixupv2(inpt), \"Random Mix Up\")\n\n\n\n\n\n\nRandom Mosaic\n\nrandommosaic = RandomMosaic(\n (250, 125),\n (4, 4),\n (0.3, 0.7),\n align_corners=True,\n cropping_mode=\"resample\",\n padding_mode=\"reflect\",\n resample=\"nearest\",\n keepdim=False,\n p=1.0,\n)\nplot_tensor(randommosaic(inpt), \"Random Mosaic\")\n\n\n\n\n\n\nRandom Jigsaw\n\n# randomjigsaw = RandomJigsaw((2, 2), ensure_perm=False, same_on_batch=False, keepdim=False, p=1.0)\n\n\n# plot_tensor(randomjigsaw(inpt), \"Random Jigsaw\")" + }, + { + "objectID": "nbs/geometric_transforms.html", + "href": "nbs/geometric_transforms.html", + "title": "Geometric image and points transformations", + "section": "", + "text": "\\\nIn brief, in this tutorial we will learn how to:" + }, + { + "objectID": "nbs/geometric_transforms.html#installation", + "href": "nbs/geometric_transforms.html#installation", + "title": "Geometric image and points transformations", + "section": "Installation", + "text": "Installation\nWe first install Kornia v0.2.0 and Matplotlib for visualisation.\nTo play with data we will use some samples from HPatches dataset [1].\n\n[1] HPatches: A benchmark and evaluation of handcrafted and learned local descriptors, Vassileios Balntas, Karel Lenc, Andrea Vedaldi and Krystian Mikolajczyk, CVPR 2017.\n\n\n%%capture\n!pip install kornia\n!pip install kornia-rs\n\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\ndownload_image(\"https://github.com/kornia/data/raw/main/homography/img1.ppm\")\ndownload_image(\"https://github.com/kornia/data/raw/main/v_dogman.ppm\")\ndownload_image(\"https://github.com/kornia/data/raw/main/v_maskedman.ppm\")\ndownload_image(\"https://miro.medium.com/max/6064/1*Fl89R-emhz-OLH9OZIQKUg.png\", \"delorean.png\")\n\n'delorean.png'" + }, + { + "objectID": "nbs/geometric_transforms.html#setup", + "href": "nbs/geometric_transforms.html#setup", + "title": "Geometric image and points transformations", + "section": "Setup", + "text": "Setup\nWe will import the needed libraries and create a small functionalities to make use of OpenCV I/O.\n\n%matplotlib inline\nimport cv2\nimport kornia as K\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport torch\nimport torch.nn as nn\n\nDefine a function for visualisation using Matplotlib.\n\ndef imshow(image: np.ndarray, height: int, width: int):\n \"\"\"Utility function to plot images.\"\"\"\n plt.figure(figsize=(height, width))\n plt.imshow(image)\n plt.axis(\"off\")\n plt.show()\n\nSince Kornia don’t provide render functionalities, let’s use OpenCV cv2.circle to draw points.\n\ndef draw_points(img_t: torch.Tensor, points: torch.Tensor) -> np.ndarray:\n \"\"\"Utility function to draw a set of points in an image.\"\"\"\n\n # cast image to numpy (HxWxC)\n img: np.ndarray = K.utils.tensor_to_image(img_t)\n\n # using cv2.circle() method\n # draw a circle with blue line borders of thickness of 2 px\n img_out: np.ndarray = img.copy()\n\n for pt in points:\n x, y = int(pt[0]), int(pt[1])\n img_out = cv2.circle(img_out, (x, y), radius=10, color=(0, 0, 255), thickness=5)\n return np.clip(img_out, 0, 1)" + }, + { + "objectID": "nbs/geometric_transforms.html#transform-single-image", + "href": "nbs/geometric_transforms.html#transform-single-image", + "title": "Geometric image and points transformations", + "section": "Transform single image", + "text": "Transform single image\nIn this section we show how to open a single image, generate 2d random points and plot them using OpenCV and Matplotlib.\nNext, we will use kornia.augmentation.RandomAffine to gerenate a random synthetic view of the given image and show how to retrieve the generated transformation to later be used to transform the points between images.\n\n# load original image\nimg1: torch.Tensor = K.io.load_image(\"img1.ppm\", K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\n\n# generate N random points within the image\nN: int = 10 # the number of points\nB, CH, H, W = img1.shape\n\npoints1: torch.Tensor = torch.rand(1, N, 2)\npoints1[..., 0] *= W\npoints1[..., 1] *= H\n\n# draw points and show\nimg1_vis: np.ndarray = draw_points(img1[0], points1[0])\n\nimshow(img1_vis, 10, 10)\n\n\n\n\nNow lets move to a bit more complex example and start to use the kornia.augmentation API to transform an image and retrieve the applied transformation. We’ll show how to reuse this transformation to project the 2d points between images.\n\n# declare an instance of our random affine generation eith `return_transform`\n# set to True, so that we recieve a tuple with the transformed image and the\n# transformation applied to the original image.\ntransform: nn.Module = K.augmentation.RandomAffine(degrees=[-45.0, 45.0], p=1.0)\n\n# tranform image and retrieve transformation\nimg2 = transform(img1, transform=transform)\ntrans = transform.get_transformation_matrix(img1)\n\n# transform the original points\npoints2: torch.Tensor = K.geometry.transform_points(trans, points1)\n\nimg2_vis: np.ndarray = draw_points(img2, points2[0])\n\n\nimshow(img2_vis, 15, 15)" + }, + { + "objectID": "nbs/geometric_transforms.html#transform-batch-of-images", + "href": "nbs/geometric_transforms.html#transform-batch-of-images", + "title": "Geometric image and points transformations", + "section": "Transform batch of images", + "text": "Transform batch of images\nIn the introduction we explained about the capability of kornia.augmentation to be integrated with other torch components such as nn.Module and nn.Sequential.\nWe will create a small component to perform data augmentation on batched images reusing the same ideas showed before to transform images and points.\nFirst, lets define a class that will generate samples of synthetic views with a small color augmentation using the kornia.augmentation.ColorJitter and kornia.augmentation.RandomAffine components.\nNOTE: we set the forward pass to have no gradients with the decorator @torch.no_grad() to make it more memory efficient.\n\nfrom typing import Dict\n\n\nclass DataAugmentator(nn.Module):\n def __init__(self) -> None:\n super().__init__()\n # declare kornia components as class members\n self.k1 = K.augmentation.RandomAffine([-60, 60], p=1.0)\n self.k2 = K.augmentation.ColorJitter(0.5, 0.5, p=1.0)\n\n @torch.no_grad()\n def forward(self, img1: torch.Tensor, pts1: torch.Tensor) -> Dict[str, torch.Tensor]:\n assert len(img1.shape) == 4, img1.shape\n\n # apply geometric transform the transform matrix\n img2 = self.k1(img1)\n trans = self.k1.get_transformation_matrix(img1)\n\n # apply color transform\n img1, img2 = self.k2(img1), self.k2(img2)\n\n # finally, lets use the transform to project the points\n pts2: torch.Tensor = K.geometry.transform_points(trans, pts1)\n\n return dict(img1=img1, img2=img2, pts1=pts1, pts2=pts2)\n\nLets use the defined component and generate some syntethic data !\n\n# load data and make a batch\nimg1: torch.Tensor = K.io.load_image(\"v_dogman.ppm\", K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\nimg2: torch.Tensor = K.io.load_image(\"v_maskedman.ppm\", K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\n\n# crop data to make it homogeneous\ncrop = K.augmentation.CenterCrop((512, 786))\n\nimg1, img2 = crop(img1), crop(img2)\n\n# visualize\nimg_vis = torch.cat([img1, img2], dim=-1)\nimshow(K.tensor_to_image(img_vis), 15, 15)\n\n\n\n\n\n# create an instance of the augmentation pipeline\n# NOTE: remember that this is a nn.Module and could be\n# placed inside any network, pytorch-lighting module, etc.\naug: nn.Module = DataAugmentator()\n\nfor _ in range(5): # create some samples\n # generate batch\n img_batch = torch.cat([img1, img2], dim=0)\n\n # generate random points (or from a network)\n N: int = 25\n B, CH, H, W = img_batch.shape\n\n points: torch.Tensor = torch.rand(B, N, 2)\n points[..., 0] *= W\n points[..., 1] *= H\n\n # sample data\n batch_data = aug(img_batch, points)\n\n # plot and show\n # visualize both images\n\n img_vis_list = []\n\n for i in range(2):\n img1_vis: np.ndarray = draw_points(batch_data[\"img1\"][i], batch_data[\"pts1\"][i])\n img_vis_list.append(img1_vis)\n\n img2_vis: np.ndarray = draw_points(batch_data[\"img2\"][i], batch_data[\"pts2\"][i])\n img_vis_list.append(img2_vis)\n\n img_vis = np.concatenate(img_vis_list, axis=1)\n\n imshow(img_vis, 20, 20)" + }, + { + "objectID": "nbs/geometric_transforms.html#bonus-backprop-to-the-future", + "href": "nbs/geometric_transforms.html#bonus-backprop-to-the-future", + "title": "Geometric image and points transformations", + "section": "BONUS: Backprop to the future", + "text": "BONUS: Backprop to the future\nOne of the main motivations during the desing for the kornia.augmentation API was to give to the user the flexibility to retrieve the applied transformation in order to achieve one of the main purposes of Kornia - the reverse engineering.\nIn this case we will show how easy one can combine Kornia and PyTorch components to undo the transformations and go back to the original data.\n“Wait a minute, Doc. Are you telling me you built a time machine…out of a PyTorch?” - Marty McFLy\n\n# lets start the Delorean engine\ndelorean: torch.Tensor = K.io.load_image(\"delorean.png\", K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\n\nimshow(K.utils.tensor_to_image(delorean), 10, 10)\n\n\n\n\n“If my calculations are correct, when this baby hits 88 miles per hour, you’re gonna see some serious shit.” - Doc. Brown\n\n# turn on the time machine panel (TMP)\n\nTMP = K.augmentation.RandomHorizontalFlip(p=1.0)\n\ndelorean_past = TMP(delorean) # go !\ntime_coords_past = TMP.get_transformation_matrix(delorean)\n\nimshow(K.utils.tensor_to_image(delorean_past), 10, 10)\n\n\n\n\n\nLet’s go back to the future !\n“Marty! You’ve gotta come back with me!” - Doc. Brown\n\n# lets go back to the past\n\ntime_coords_future: torch.Tensor = torch.inverse(time_coords_past)\n\nH, W = delorean_past.shape[-2:]\ndelorean_future = K.geometry.warp_perspective(delorean_past, time_coords_future, (H, W))\n\nimshow(K.utils.tensor_to_image(delorean_future), 10, 10)" + }, + { + "objectID": "nbs/data_augmentation_sequential.html#install-and-get-data", + "href": "nbs/data_augmentation_sequential.html#install-and-get-data", + "title": "Augmentation Sequential", + "section": "Install and get data", + "text": "Install and get data\nWe install Kornia and some dependencies, and download a simple data sample\n\n!pip install kornia\n!pip install kornia-rs\n\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://raw.githubusercontent.com/kornia/data/main/panda.jpg\"\ndownload_image(url)\n\n\nimport cv2\nimport kornia as K\nimport numpy as np\nimport torch\nfrom kornia.augmentation import AugmentationSequential\nfrom kornia.geometry import bbox_to_mask\nfrom matplotlib import pyplot as plt\n\n\ndef plot_resulting_image(img, bbox, keypoints, mask):\n img = img * mask\n img_array = K.tensor_to_image(img.mul(255).byte()).copy()\n img_draw = cv2.polylines(img_array, bbox.numpy(), isClosed=True, color=(255, 0, 0))\n for k in keypoints[0]:\n img_draw = cv2.circle(img_draw, tuple(k.numpy()[:2]), radius=6, color=(255, 0, 0), thickness=-1)\n return img_draw\n\n\nimg_tensor = K.io.load_image(\"panda.jpg\", K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\nh, w = img_tensor.shape[-2:]\n\nplt.axis(\"off\")\nplt.imshow(K.tensor_to_image(img_tensor))\nplt.show()" + }, + { + "objectID": "nbs/data_augmentation_sequential.html#define-augmentation-sequential-and-different-labels", + "href": "nbs/data_augmentation_sequential.html#define-augmentation-sequential-and-different-labels", + "title": "Augmentation Sequential", + "section": "Define Augmentation Sequential and Different Labels", + "text": "Define Augmentation Sequential and Different Labels\n\naug_list = AugmentationSequential(\n K.augmentation.ColorJitter(0.1, 0.1, 0.1, 0.1, p=1.0),\n K.augmentation.RandomAffine(360, [0.1, 0.1], [0.7, 1.2], [30.0, 50.0], p=1.0),\n K.augmentation.RandomPerspective(0.5, p=1.0),\n data_keys=[\"input\", \"bbox\", \"keypoints\", \"mask\"],\n same_on_batch=False,\n)\n\nbbox = torch.tensor([[[[355, 10], [660, 10], [660, 250], [355, 250]]]])\nkeypoints = torch.tensor([[[465, 115], [545, 116]]])\nmask = bbox_to_mask(torch.tensor([[[155, 0], [900, 0], [900, 400], [155, 400]]]), w, h)[None].float()\n\nimg_out = plot_resulting_image(img_tensor, bbox[0], keypoints, mask[0])\n\nplt.axis(\"off\")\nplt.imshow(img_out)\nplt.show()" + }, + { + "objectID": "nbs/data_augmentation_sequential.html#forward-computations", + "href": "nbs/data_augmentation_sequential.html#forward-computations", + "title": "Augmentation Sequential", + "section": "Forward Computations", + "text": "Forward Computations\n\nout_tensor = aug_list(img_tensor, bbox.float(), keypoints.float(), mask)\nimg_out = plot_resulting_image(\n out_tensor[0][0],\n out_tensor[1].int(),\n out_tensor[2].int(),\n out_tensor[3][0],\n)\n\nplt.axis(\"off\")\nplt.imshow(img_out)\nplt.show()" + }, + { + "objectID": "nbs/data_augmentation_sequential.html#inverse-transformations", + "href": "nbs/data_augmentation_sequential.html#inverse-transformations", + "title": "Augmentation Sequential", + "section": "Inverse Transformations", + "text": "Inverse Transformations\n\nout_tensor_inv = aug_list.inverse(*out_tensor)\nimg_out = plot_resulting_image(\n out_tensor_inv[0][0],\n out_tensor_inv[1].int(),\n out_tensor_inv[2].int(),\n out_tensor_inv[3][0],\n)\n\nplt.axis(\"off\")\nplt.imshow(img_out)\nplt.show()" + }, + { + "objectID": "index.html", + "href": "index.html", + "title": "Tutorials", + "section": "", + "text": "Order By\n Default\n \n Title\n \n \n Date - Oldest\n \n \n Date - Newest\n \n \n Author\n \n \n Modified - Oldest\n \n \n Modified - Newest\n \n \n \n \n \n \n \n\n\n\n\n \n\n\n\n\nImage matching example with LightGlue and DISK\n\n\n\n\n\n\n\nIntermediate\n\n\nLightGlue\n\n\nDisk\n\n\nLAF\n\n\nImage matching\n\n\nkornia.feature\n\n\n\n\nIn this tutorial we are going to show how to perform image matching using a LightGlue algorithm with DISK\n\n\n\n\n\n\nMay 11, 2024\n\n\nDmytro Mishkin\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nImage Prompter: Segment Anything\n\n\n\n\n\n\n\nIntermediate\n\n\nSegmentation\n\n\nkornia.contrib\n\n\n\n\nThis tutorials shows how to use our high-level API Image Prompter. This API allow to set an image, and run multiple queries multiple times on this image. These query can be done with three types of prompts.\n\n\n\n\n\n\nApr 18, 2023\n\n\nJoão G. Atkinson\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nImage matching example with DISK local features\n\n\n\n\n\n\n\nIntermediate\n\n\nDISK\n\n\nLAF\n\n\nImage matching\n\n\nkornia.feature\n\n\n\n\nIn this tutorial we are going to show how to perform image matching using a DISK algorithm\n\n\n\n\n\n\nApr 1, 2023\n\n\nDmytro Mishkin\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nData Augmentation 2D\n\n\n\n\n\n\n\nBasic\n\n\n2D\n\n\nData augmentation\n\n\nkornia.augmentation\n\n\n\n\nA show case of the Data Augmentation operation available on Kornia for images.\n\n\n\n\n\n\nFeb 4, 2023\n\n\nJoão Gustavo A. Amorim\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nFit plane tutorial\n\n\n\n\n\n\n\nBasic\n\n\nPlane\n\n\nkornia.geometry\n\n\n\n\nThis tutorial use shows how to generate a plane based on a mesh. Using the Hyperplane and Hyperplane from kornia.gemetry.plane. As data structure we use kornia.geometry.liegroup.So3 e kornia.geometry.vector.Vector3\n\n\n\n\n\n\nDec 16, 2022\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nImage and Keypoints augmentations\n\n\n\n\n\n\n\nIntermediate\n\n\nKeypoints\n\n\nData augmentation\n\n\n2D\n\n\nAugmentation container\n\n\nAugmentation Sequential\n\n\nkornia.augmentation\n\n\n\n\nIn this tutorial we leverage kornia.augmentation.AugmentationSequential to apply augmentations to image and transform reusing the applied geometric transformation to a set of associated keypoints. This is useful for detection networks or geometric problems.\n\n\n\n\n\n\nOct 14, 2022\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nLine detection and matching example with SOLD2: Self-supervised Occlusion-aware Line Description and Detection\n\n\n\n\n\n\n\nIntermediate\n\n\nLine detection\n\n\nLine matching\n\n\nSOLD2\n\n\nSelf-supervised\n\n\nkornia.feature\n\n\n\n\nIn this tutorial we will show how we can quickly perform line detection, and matching using kornia.feature.sold2 API.\n\n\n\n\n\n\nAug 31, 2022\n\n\nJoão G. Atkinson\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nRandom Mosaic Augmentation\n\n\n\n\n\n\n\nBasic\n\n\n2D\n\n\nData augmentation\n\n\nkornia.augmentation\n\n\n\n\nIn this tutorial we will show how we can quickly perform mosaicing using the features provided by the kornia.augmentation.RandomMosaic API. Mosaicing means taking several input images and combine their random crops into mosaic.\n\n\n\n\n\n\nAug 29, 2022\n\n\nJian Shi\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nImage matching example with KeyNet-AdaLAM\n\n\n\n\n\n\n\nIntermediate\n\n\nKeyNet\n\n\nLAF\n\n\nAdalam\n\n\nImage matching\n\n\nkornia.feature\n\n\n\n\nIn this tutorial we are going to show how to perform image matching using a KeyNet-Adalam Algorithm\n\n\n\n\n\n\nAug 22, 2022\n\n\nDmytro Mishkin\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nFit line tutorial\n\n\n\n\n\n\n\nBasic\n\n\nLine\n\n\nkornia.geometry\n\n\n\n\nThis tutorial use shows how to generate a line based on points. Using the ParametrizedLine and fit_line from kornia.gemetry.line\n\n\n\n\n\n\nJul 15, 2022\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nExtracting and Combining Tensor Patches\n\n\n\n\n\n\n\nBasic\n\n\nPatches\n\n\nkornia.contrib\n\n\n\n\nIn this tutorial we will show how you can extract and combine tensor patches using kornia\n\n\n\n\n\n\nMar 7, 2022\n\n\nAshwin Nair\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nFace Detection and blurring\n\n\n\n\n\n\n\nIntermediate\n\n\nFace detection\n\n\nBlur\n\n\nkornia.contrib\n\n\n\n\nIn this tutorial we will show how to use the Kornia Face Detection and how we can blurring these detected faces.\n\n\n\n\n\n\nNov 30, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nImage stitching example with LoFTR\n\n\n\n\n\n\n\nIntermediate\n\n\nLoFTR\n\n\nkornia.feature\n\n\n\n\nA show case of how to do image stitching using LoFTR from Kornia.\n\n\n\n\n\n\nNov 19, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nConvert RGB to RAW\n\n\n\n\n\n\n\nBasic\n\n\nColor spaces\n\n\nkornia.color\n\n\n\n\nIn this tutorial we are going to learn how to convert image from raw color using kornia.color.\n\n\n\n\n\n\nOct 13, 2021\n\n\nOskar Flordal\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nConvert RGB to YUV420\n\n\n\n\n\n\n\nBasic\n\n\nColor spaces\n\n\nkornia.color\n\n\n\n\nIn this tutorial we are going to learn how to convert image from RGB color to YUV420 using kornia.color.\n\n\n\n\n\n\nOct 10, 2021\n\n\nOskar Flordal\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nConnected Components Algorithm\n\n\n\n\n\n\n\nBasic\n\n\nSegmentation\n\n\nLabeling\n\n\nUnsupervised\n\n\nkornia.contrib\n\n\n\n\nIn this tutorial we are going to learn how to segment small objects in the image using the kornia implementation of the classic Computer Vision technique called Connected-component labelling (CCL).\n\n\n\n\n\n\nSep 16, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nImage matching example with LoFTR\n\n\n\n\n\n\n\nIntermediate\n\n\nLoFTR\n\n\nLAF\n\n\nImage matching\n\n\nkornia.feature\n\n\n\n\nIn this tutorial we are going to show how to perform image matching using a LoFTR algorithm\n\n\n\n\n\n\nSep 11, 2021\n\n\nDmytro Mishkin\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nImage Registration by Direct Optimization\n\n\n\n\n\n\n\nIntermediate\n\n\nImage Registration\n\n\nkornia.geometry\n\n\n\n\nIn this tutorial we are going to learn how to perform the task of image alignment by optimizing the similarity transformation between two images in order to create a photo with wide in-focus area from set of narrow-focused images.\n\n\n\n\n\n\nSep 6, 2021\n\n\nDmytro Mishkin\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nImage histogram and equalizations techniques\n\n\n\n\n\n\n\nBasic\n\n\nColor spaces\n\n\nkornia.enhance\n\n\n\n\nIn this tutorial we are going to learn how using kornia components and Matplotlib we can visualize image histograms and later use kornia functionality to equalize images in batch and using the gpu.\n\n\n\n\n\n\nAug 31, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nResize anti-alias\n\n\n\n\n\n\n\nBasic\n\n\nRescale\n\n\nkornia.geometry\n\n\n\n\nIn this tutorial we are going to learn how to resize an image with anti-alias.\n\n\n\n\n\n\nAug 28, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nImage patch generation\n\n\n\n\n\n\n\nIntermediate\n\n\nPatches\n\n\nkornia.geometry\n\n\n\n\nIn this tutorial we are going to learn how to generate image patches using kornia.geometry components.\n\n\n\n\n\n\nAug 28, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nImage matching example with kornia local features: TFeat, MKD, OriNet, HyNet and OpenCV detector\n\n\n\n\n\n\n\nIntermediate\n\n\nLocal features\n\n\nkornia.feature\n\n\n\n\nIn this tutorial we will show how we can perform image matching using kornia local features\n\n\n\n\n\n\nAug 28, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nImage anti-alias with local features\n\n\n\n\n\n\n\nBasic\n\n\nHardNet\n\n\nPatches\n\n\nLocal features\n\n\nkornia.feature\n\n\n\n\nIn this example we will show the benefits of using anti-aliased patch extraction with kornia.\n\n\n\n\n\n\nAug 28, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nImage Alignment by Homography Optimization\n\n\n\n\n\n\n\nAdvanced\n\n\nHomography\n\n\nkornia.geometry\n\n\n\n\nIn this tutorial we are going to learn how to perform the task of image alignment by optimising the homography transformation between two images.\n\n\n\n\n\n\nAug 28, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nGeometric image and points transformations\n\n\n\n\n\n\n\nIntermediate\n\n\nKeypoints\n\n\nkornia.augmentation\n\n\nkornia.geometry\n\n\n\n\nIn this tutorial we will learn how to generate and manipulate geometrically synthetic images and use their transformations to manipulate 2D points and how to combine with torch components to perform data augmention.\n\n\n\n\n\n\nAug 28, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nRotate image using warp affine transform\n\n\n\n\n\n\n\nBasic\n\n\nAffine\n\n\nkornia.geometry\n\n\n\n\nIn this tutorial we are going to learn how to rotate an image using the kornia.gemetry components.\n\n\n\n\n\n\nJul 6, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nFiltering Operators\n\n\n\n\n\n\n\nBasic\n\n\nFilters\n\n\nBlur\n\n\nkornia.filters\n\n\n\n\nIn this tutorial we are going to learn how to apply blurring filters to images with kornia.filters components.\n\n\n\n\n\n\nJul 6, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nEdge Detection\n\n\n\n\n\n\n\nBasic\n\n\nEdge Detection\n\n\nkornia.filters\n\n\n\n\nIn this tutorial we are going to learn how to detect edges in images with kornia.filters components.\n\n\n\n\n\n\nJul 6, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nImage Enhancement\n\n\n\n\n\n\n\nBasic\n\n\nkornia.enhance\n\n\n\n\nIn this tutorial we are going to learn how to tweak image properties using the compoments from kornia.enhance.\n\n\n\n\n\n\nJul 5, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nPatch Sequential\n\n\n\n\n\n\n\nIntermediate\n\n\n2D\n\n\nData augmentation\n\n\nPatches\n\n\nkornia.augmentation\n\n\n\n\nIn this tutorial we will show how we can quickly perform patch processing using the features provided by the kornia.augmentation.PatchSequential API.\n\n\n\n\n\n\nJun 11, 2021\n\n\nJian Shi\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nObtaining Edges using the Canny operator\n\n\n\n\n\n\n\nBasic\n\n\nEdge Detection\n\n\nkornia.filters\n\n\n\n\nIn this tutorial we show how easily one can apply the typical canny edge detection using Kornia\n\n\n\n\n\n\nJun 8, 2021\n\n\nPau Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nDenoise image using total variation\n\n\n\n\n\n\n\nAdvanced\n\n\nDenoising\n\n\n\n\nIn this tutorial we are going to learn how to denoise an image using the differentiable total_variation loss.\n\n\n\n\n\n\nJun 7, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nZCA Whitening\n\n\n\n\n\n\n\nAdvanced\n\n\nkornia.enhance\n\n\n\n\nThe following tutorial will show you how to perform ZCA data whitening on a dataset using kornia.enhance.zca. The documentation for ZCA whitening can be found here.\n\n\n\n\n\n\nMay 30, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nSharpen image using unsharp mask\n\n\n\n\n\n\n\nBasic\n\n\nFilters\n\n\nkornia.filters\n\n\n\n\nIn this tutorial we are going to learn how to use the unsharp mask\n\n\n\n\n\n\nMay 30, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nAugmentation Sequential\n\n\n\n\n\n\n\nBasic\n\n\n2D\n\n\nData augmentation\n\n\nkornia.augmentation\n\n\n\n\nIn this tutorial we will show how we can quickly perform data augmentation for various tasks (segmentation, detection, regression) using the features provided by the kornia.augmentation.AugmentationSequential API.\n\n\n\n\n\n\nMay 30, 2021\n\n\nJian Shi\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nBlur image using GaussianBlur operator\n\n\n\n\n\n\n\nBasic\n\n\nBlur\n\n\nkornia.filters\n\n\n\n\nIn this tutorial we show how easily one can apply typical image transformations using Kornia.\n\n\n\n\n\n\nMay 18, 2021\n\n\nTakeshi Teshima\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nData Augmentation Semantic Segmentation\n\n\n\n\n\n\n\nBasic\n\n\n2D\n\n\nSegmentation\n\n\nData augmentation\n\n\nkornia.augmentation\n\n\n\n\nIn this tutorial we will show how we can quickly perform data augmentation for semantic segmentation using the kornia.augmentation API.\n\n\n\n\n\n\nMar 27, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nWarp image using perspective transform\n\n\n\n\n\n\n\nIntermediate\n\n\nWarp image\n\n\nkornia.geometry\n\n\n\n\nIn this tutorial we are going to learn how to use the functions kornia.get_perspective_transform and kornia.warp_perspective.\n\n\n\n\n\n\nMar 18, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nKornia and PyTorch Lightning GPU data augmentation\n\n\n\n\n\n\n\nBasic\n\n\nData augmentation\n\n\nPytorch lightning\n\n\nkornia.augmentation\n\n\n\n\nIn this tutorial we show how one can combine both Kornia and PyTorch Lightning to perform data augmentation to train a model using CPUs and GPUs in batch mode without additional effort.\n\n\n\n\n\n\nMar 18, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nColor space conversion\n\n\n\n\n\n\n\nBasic\n\n\nColor spaces\n\n\nkornia.color\n\n\n\n\nIn this tutorial we are going to learn how to convert image from different color spaces using kornia.color.\n\n\n\n\n\n\nMar 18, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nIntroduction to Morphological Operators\n\n\n\n\n\n\n\nBasic\n\n\nMorphology\n\n\nkornia.morphology\n\n\n\n\nIn this tutorial you are gonna explore kornia.morphology, that’s Kornia’s module for differentiable Morphological Operators.\n\n\n\n\n\n\nMar 8, 2021\n\n\nJuclique\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\n \n\n\n\n\nHello world: Planet Kornia\n\n\n\n\n\n\n\nBasic\n\n\nkornia.io\n\n\n\n\nWelcome to Planet Kornia: a set of tutorials to learn about Computer Vision in PyTorch.\n\n\n\n\n\n\nJan 21, 2021\n\n\nEdgar Riba\n\n\n1/6/24, 10:18:08 PM\n\n\n\n\n\n\nNo matching items" + }, + { + "objectID": "nbs/image_histogram.html#install-kornia", + "href": "nbs/image_histogram.html#install-kornia", + "title": "Image histogram and equalizations techniques", + "section": "Install Kornia", + "text": "Install Kornia\n\n%%capture\n!pip install kornia\n!pip install kornia-rs" + }, + { + "objectID": "nbs/image_histogram.html#prepare-the-data", + "href": "nbs/image_histogram.html#prepare-the-data", + "title": "Image histogram and equalizations techniques", + "section": "Prepare the data", + "text": "Prepare the data\nThe low contrast color image used in this tutorial can be downloaded here (By Biem (Own work) [Public domain], via Wikimedia Commons)\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\ndownload_image(\"https://github.com/cvg/SOLD2/raw/main/assets/images/terrace0.JPG\")\ndownload_image(\"http://www.ic.unicamp.br/~helio/imagens_registro/foto1B.jpg\")\ndownload_image(\"https://imagemagick.org/image/mountains.jpg\", \"mountains.jpg\")\n\n'mountains.jpg'\n\n\n\nfrom typing import List, Tuple\n\nimport cv2\nimport kornia as K\nimport numpy as np\nimport torch\nimport torchvision\nfrom matplotlib import pyplot as plt\n\nImage show functionality\n\ndef imshow(input: torch.Tensor, height: int, width: int):\n out: torch.Tensor = torchvision.utils.make_grid(input, nrow=2)\n out_np: np.array = K.utils.tensor_to_image(out)\n plt.figure(figsize=(height, width))\n plt.imshow(out_np)\n plt.axis(\"off\");\n\nImage read functionality\n\ndef imread(data_path: str) -> torch.Tensor:\n \"\"\"Utility function that load an image an convert to torch.\"\"\"\n img_t = K.io.load_image(data_path, K.io.ImageLoadType.RGB32)\n img_t = K.geometry.resize(img_t, 1200, side=\"long\", align_corners=True)[..., :600, :]\n\n return img_t[None, ...]\n\nImage and histogram plot functionality\n\ndef histogram_img(img_t: torch.Tensor, size: Tuple[int, int] = (16, 4)):\n CH, H, W = img_t.shape\n img = K.utils.tensor_to_image(img_t.mul(255.0).byte())\n\n plt.figure(figsize=size)\n ax1 = plt.subplot(1, 2, 1)\n ax2 = plt.subplot(1, 2, 2)\n\n colors = (\"b\", \"g\", \"r\")\n kwargs = dict(histtype=\"stepfilled\", alpha=0.3, density=True, ec=\"k\")\n\n for i in range(CH):\n img_vec = img[..., i].flatten()\n ax2.hist(img_vec, range=(0, 255), bins=256, color=colors[i], **kwargs)\n\n ax1.imshow(img, cmap=(None if CH > 1 else \"gray\"))\n ax1.grid(False)\n ax1.axis(\"off\")\n\n plt.show()\n\nLoad the images in batch using OpenCV and show them as a grid.\n\nimg_rgb_list: List[torch.Tensor] = []\nimg_rgb_list.append(imread(\"terrace0.JPG\"))\nimg_rgb_list.append(imread(\"mountains.jpg\"))\nimg_rgb_list.append(imread(\"foto1B.jpg\"))\n\n\n# cast to torch.Tensor\nimg_rgb: torch.Tensor = torch.cat(img_rgb_list, dim=0)\nprint(f\"Image tensor shape: {img_rgb.shape}\")\n\n# Disable the line below to make everything happen in the GPU !\n# img_rbg = img_rbg.cuda()\n\nimshow(img_rgb, 10, 10) # plot grid !\n\nImage tensor shape: torch.Size([3, 3, 600, 1200])" + }, + { + "objectID": "nbs/image_histogram.html#image-histogram", + "href": "nbs/image_histogram.html#image-histogram", + "title": "Image histogram and equalizations techniques", + "section": "Image Histogram", + "text": "Image Histogram\nDefinition - An image histogram is a type of histogram that acts as a graphical representation of the tonal distribution in a digital image.[1] It plots the number of pixels for each tonal value. By looking at the histogram for a specific image a viewer will be able to judge the entire tonal distribution at a glance Read more - Wikipedia.\nIn short - An image histogram is: - It is a graphical representation of the intensity distribution of an image. - It quantifies the number of pixels for each intensity value considered.\nSee also OpenCV tutorial: https://docs.opencv.org/master/d4/d1b/tutorial_histogram_equalization.html\n\nLightness with Kornia\nWe first will compute the histogram of the lightness of the image. To do so, we compute first the color space Lab and take the first component known as luminance that reflects the lightness of the scene.\nNotice that kornia Lab representation is in the range of [0, 100] and for convenience to plot the histogram we will normalize the image between [0, 1].\nNote: kornia computes in batch, for convenience we show only one image result. That’s it - modify below the plot_indices variable to explore the results of the batch.\n\nplot_indices: int = 0 # try: [0, 1, 2, 3]\n\nTip: replace K.color.rgb_to_lab by K.color.rgb_to_grayscale to see the pixel distribution in the grayscale color space. Explore also kornia.color for more exotic color spaces.\n\nimg_lab: torch.Tensor = K.color.rgb_to_lab(img_rgb)\nlightness: torch.Tensor = img_lab[..., :1, :, :] / 100.0 # L in lab is in range [0, 100]\n\nhistogram_img(lightness[plot_indices])\n\n\n\n\n\n\nRGB histogram with Kornia\nSimilar to above - you can just visualize the three (red, green, blue) channels pixel distribution.\n\nTip - Use as follows to visualize a single channel (green)\n\nhistogram_img(img_rgb[plot_indices, 1:2])\n\n\n\nhistogram_img(img_rgb[plot_indices])" + }, + { + "objectID": "nbs/image_histogram.html#histogram-stretching", + "href": "nbs/image_histogram.html#histogram-stretching", + "title": "Image histogram and equalizations techniques", + "section": "Histogram stretching", + "text": "Histogram stretching\nSometimes our images have a pixel distribution that is not suitable for our application, being biased to a certain range depending on the illumination of the scene.\nIn the next sections, we are going to show a couple of techniques to solve those issues. We will start with a basic technique to normalize the image by its minimum and maximum values with the objective to strecth the image histrogram.\n\non the lightness with Kornia\nWe use kornina.enhance.normalize_min_max to normalize the image Luminance. Note: compare the histrograms with the original image.\nTip - play with the other functions from kornia.enhance to modify the intensity values of the image and thus its histograms.\n\nlightness_stretched = K.enhance.normalize_min_max(lightness)\nhistogram_img(lightness_stretched[plot_indices])\n\n\n\n\nIn order to properly visualize the effect of the normalization in the color histogram, we take the normalized Luminance and use it to cast back to RGB.\n\nimg_rgb_new = K.color.lab_to_rgb(torch.cat([lightness_stretched * 100.0, img_lab[:, 1:]], dim=1))\nhistogram_img(img_rgb_new[plot_indices])\n\n\n\n\n\n\non the RGB with Kornia\nIn this case we normalize each channel independently where we can see that resulting image is not as clear as the one only stretching the Luminance.\n\nrgb_stretched = K.enhance.normalize_min_max(img_rgb)\nhistogram_img(rgb_stretched[plot_indices])" + }, + { + "objectID": "nbs/image_histogram.html#histogram-equalization", + "href": "nbs/image_histogram.html#histogram-equalization", + "title": "Image histogram and equalizations techniques", + "section": "Histogram Equalization", + "text": "Histogram Equalization\nA more advanced technique to improve the pixel distribution is the so called Histogram equalization - a method in image processing of contrast adjustment using the image’s histogram [Read more - Wikipedia].\nIn kornia we have implemented in terms of torch tensor to equalize the images in batch and the gpu very easily.\n\non the lightness with Kornia\n\nlightness_equalized = K.enhance.equalize(lightness)\nhistogram_img(lightness_equalized[plot_indices])\n\n\n\n\nWe convert back from Lab to RGB using the equalized Luminance and visualize the histogram of the RGB.\n\nimg_rgb_new = K.color.lab_to_rgb(torch.cat([lightness_equalized * 100.0, img_lab[:, 1:]], dim=1))\nhistogram_img(img_rgb_new[plot_indices])\n\n\n\n\n\n\non the RGB with Kornia\n\nrgb_equalized = K.enhance.equalize(img_rgb)\nhistogram_img(rgb_equalized[plot_indices])\n\n\n\n\n\n\non the RGB with OpenCV\nJust to compare against OpenCV - close results :)\n\nrgb_equalized_cv = []\nfor img in img_rgb:\n equ00 = torch.tensor(cv2.equalizeHist(K.utils.tensor_to_image(img[0].mul(255).clamp(0, 255).byte())))\n equ01 = torch.tensor(cv2.equalizeHist(K.utils.tensor_to_image(img[1].mul(255).clamp(0, 255).byte())))\n equ02 = torch.tensor(cv2.equalizeHist(K.utils.tensor_to_image(img[2].mul(255).clamp(0, 255).byte())))\n rgb_equalized_cv.append(torch.stack([equ00, equ01, equ02]))\nrgb_equalized_cv = torch.stack(rgb_equalized_cv)\n\nhistogram_img(rgb_equalized_cv[plot_indices] / 255.0)" + }, + { + "objectID": "nbs/image_histogram.html#adaptive-histogram-equalization", + "href": "nbs/image_histogram.html#adaptive-histogram-equalization", + "title": "Image histogram and equalizations techniques", + "section": "Adaptive Histogram Equalization", + "text": "Adaptive Histogram Equalization\nAdaptive histogram equalization (AHE) is a computer image processing technique used to improve contrast in images. It differs from ordinary histogram equalization in the respect that the adaptive method computes several histograms, each corresponding to a distinct section of the image, and uses them to redistribute the lightness values of the image. It is therefore suitable for improving the local contrast and enhancing the definitions of edges in each region of an image [Read more - Wikipedia].\n\non the lightness with Kornia\nWe will use kornia.enhance.equalize_clahe and by playing with the clip_limit and grid_size variables to produce different effects to the image.\n\nlightness_equalized = K.enhance.equalize_clahe(lightness, clip_limit=0.0)\nhistogram_img(lightness_equalized[plot_indices])\n\n\n\n\nWe convert back from Lab to RGB using the equalized Luminance and visualize the histogram of the RGB.\n\nimg_rgb_new = K.color.lab_to_rgb(torch.cat([lightness_equalized * 100.0, img_lab[:, 1:]], dim=1))\nhistogram_img(img_rgb_new[plot_indices])\n\n\n\n\n\n\non the RGB with Kornia\n\nrgb_equalized = K.enhance.equalize_clahe(img_rgb, clip_limit=0.0)\nhistogram_img(rgb_equalized[plot_indices])" + }, + { + "objectID": "nbs/image_histogram.html#contrast-limited-adaptive-histogram-equalization-clahe", + "href": "nbs/image_histogram.html#contrast-limited-adaptive-histogram-equalization-clahe", + "title": "Image histogram and equalizations techniques", + "section": "Contrast Limited Adaptive Histogram Equalization (CLAHE)", + "text": "Contrast Limited Adaptive Histogram Equalization (CLAHE)\nAn improvement of the algorithm is CLAHE that divides the image into small blocks and controlled by the variable grid_size. This means, that the equalization is performed locally in each of the NxM sublocks to obtain a better distribution of the pixel values.\n\non the lightness with Kornia\n\nlightness_equalized = K.enhance.equalize_clahe(lightness, clip_limit=20.0, grid_size=(8, 8))\nhistogram_img(lightness_equalized[plot_indices])\n\n\n\n\nWe convert back from Lab to RGB using the equalized Luminance and visualize the histogram of the RGB.\n\nimg_rgb_new = K.color.lab_to_rgb(torch.cat([lightness_equalized * 100.0, img_lab[:, 1:]], dim=1))\nhistogram_img(img_rgb_new[plot_indices])\n\n\n\n\n\n\non the RGB with Kornia\nWe directly equalize all the RGB channels at once\n\nrgb_equalized = K.enhance.equalize_clahe(img_rgb, clip_limit=20.0, grid_size=(8, 8))\nhistogram_img(rgb_equalized[plot_indices])\n\n\n\n\n\n\non the RGB with OpenCV\n\nimgs = []\nclahe = cv2.createCLAHE(clipLimit=20.0, tileGridSize=(8, 8))\nfor im in img_rgb:\n # equalize channels independently as gray scale images\n equ00 = torch.tensor(clahe.apply(K.utils.tensor_to_image(im[0].mul(255).clamp(0, 255).byte())))\n equ01 = torch.tensor(clahe.apply(K.utils.tensor_to_image(im[1].mul(255).clamp(0, 255).byte())))\n equ02 = torch.tensor(clahe.apply(K.utils.tensor_to_image(im[2].mul(255).clamp(0, 255).byte())))\n imgs.append(torch.stack([equ00, equ01, equ02]))\nimgs = torch.stack(imgs)\n\nhistogram_img(imgs[plot_indices] / 255.0)" + }, + { + "objectID": "nbs/image_prompter.html", + "href": "nbs/image_prompter.html", + "title": "Image Prompter: Segment Anything", + "section": "", + "text": "This tutorials shows how to use our high-level API Image Prompter. This API allow to set an image, and run multiple queries multiple times on this image. These query can be done with three types of prompt:\nRead more on our docs: https://kornia.readthedocs.io/en/latest/models/segment_anything.html\nThis tutorials steps:" + }, + { + "objectID": "nbs/image_prompter.html#setup", + "href": "nbs/image_prompter.html#setup", + "title": "Image Prompter: Segment Anything", + "section": "Setup", + "text": "Setup\n\n%%capture\n!pip install kornia\n!pip install kornia-rs\n\nFirst let’s choose the SAM type to be used on our Image Prompter.\nThe options are (smaller to bigger):\n\n\n\n\n\n\n\nmodel_type\ncheckpoint official\n\n\n\n\nvit_b\nhttps://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth\n\n\nvit_l\nhttps://dl.fbaipublicfiles.com/segment_anything/sam_vit_l_0b3195.pth\n\n\nvit_h\nhttps://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth\n\n\n\n\nmodel_type = \"vit_h\"\ncheckpoint = \"https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth\"\n\nThen let’s import all necessary packages and modules\n\nfrom __future__ import annotations\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport torch\nfrom kornia.contrib.image_prompter import ImagePrompter\nfrom kornia.contrib.models.sam import SamConfig\nfrom kornia.geometry.boxes import Boxes\nfrom kornia.geometry.keypoints import Keypoints\nfrom kornia.io import ImageLoadType, load_image\nfrom kornia.utils import get_cuda_or_mps_device_if_available, tensor_to_image\n\n\ndevice = get_cuda_or_mps_device_if_available()\nprint(device)\n\ncuda:0\n\n\n\nUtilities functions\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nsoccer_image_path = download_image(\"https://raw.githubusercontent.com/kornia/data/main/soccer.jpg\")\ncar_image_path = download_image(\"https://raw.githubusercontent.com/kornia/data/main/simple_car.jpg\")\nsatellite_image_path = download_image(\"https://raw.githubusercontent.com/kornia/data/main/satellite_sentinel2_example.tif\")\nsoccer_image_path, car_image_path, satellite_image_path\n\n('soccer.jpg', 'simple_car.jpg', 'satellite_sentinel2_example.tif')\n\n\n\ndef colorize_masks(binary_masks: torch.Tensor, merge: bool = True, alpha: None | float = None) -> list[torch.Tensor]:\n \"\"\"Convert binary masks (B, C, H, W), boolean tensors, into masks with colors (B, (3, 4) , H, W) - RGB or RGBA. Where C refers to the number of masks.\n Args:\n binary_masks: a batched boolean tensor (B, C, H, W)\n merge: If true, will join the batch dimension into a unique mask.\n alpha: alpha channel value. If None, will generate RGB images\n\n Returns:\n A list of `C` colored masks.\n \"\"\"\n B, C, H, W = binary_masks.shape\n OUT_C = 4 if alpha else 3\n\n output_masks = []\n\n for idx in range(C):\n _out = torch.zeros(B, OUT_C, H, W, device=binary_masks.device, dtype=torch.float32)\n for b in range(B):\n color = torch.rand(1, 3, 1, 1, device=binary_masks.device, dtype=torch.float32)\n if alpha:\n color = torch.cat([color, torch.tensor([[[[alpha]]]], device=binary_masks.device, dtype=torch.float32)], dim=1)\n\n to_colorize = binary_masks[b, idx, ...].view(1, 1, H, W).repeat(1, OUT_C, 1, 1)\n _out[b, ...] = torch.where(to_colorize, color, _out[b, ...])\n output_masks.append(_out)\n\n if merge:\n output_masks = [c.max(dim=0)[0] for c in output_masks]\n\n return output_masks\n\n\ndef show_binary_masks(binary_masks: torch.Tensor, axes) -> None:\n \"\"\"plot binary masks, with shape (B, C, H, W), where C refers to the number of masks.\n\n will merge the `B` channel into a unique mask.\n Args:\n binary_masks: a batched boolean tensor (B, C, H, W)\n ax: a list of matplotlib axes with lenght of C\n \"\"\"\n colored_masks = colorize_masks(binary_masks, True, 0.6)\n\n for ax, mask in zip(axes, colored_masks):\n ax.imshow(tensor_to_image(mask))\n\n\ndef show_boxes(boxes: Boxes, ax) -> None:\n boxes_tensor = boxes.to_tensor(mode=\"xywh\").detach().cpu().numpy()\n for batched_boxes in boxes_tensor:\n for box in batched_boxes:\n x0, y0, w, h = box\n ax.add_patch(plt.Rectangle((x0, y0), w, h, edgecolor=\"orange\", facecolor=(0, 0, 0, 0), lw=2))\n\n\ndef show_points(points: tuple[Keypoints, torch.Tensor], ax, marker_size=200):\n coords, labels = points\n pos_points = coords[labels == 1].to_tensor().detach().cpu().numpy()\n neg_points = coords[labels == 0].to_tensor().detach().cpu().numpy()\n\n ax.scatter(pos_points[:, 0], pos_points[:, 1], color=\"green\", marker=\"+\", s=marker_size, linewidth=2)\n ax.scatter(neg_points[:, 0], neg_points[:, 1], color=\"red\", marker=\"x\", s=marker_size, linewidth=2)\n\n\nfrom kornia.contrib.models import SegmentationResults\n\n\ndef show_image(image: torch.Tensor):\n plt.imshow(tensor_to_image(image))\n plt.axis(\"off\")\n plt.show()\n\n\ndef show_predictions(\n image: torch.Tensor,\n predictions: SegmentationResults,\n points: tuple[Keypoints, torch.Tensor] | None = None,\n boxes: Boxes | None = None,\n) -> None:\n n_masks = predictions.logits.shape[1]\n\n fig, axes = plt.subplots(1, n_masks, figsize=(21, 16))\n axes = [axes] if n_masks == 1 else axes\n\n for idx, ax in enumerate(axes):\n score = predictions.scores[:, idx, ...].mean()\n ax.imshow(tensor_to_image(image))\n ax.set_title(f\"Mask {idx+1}, Score: {score:.3f}\", fontsize=18)\n\n if points:\n show_points(points, ax)\n\n if boxes:\n show_boxes(boxes, ax)\n\n ax.axis(\"off\")\n\n show_binary_masks(predictions.binary_masks, axes)\n plt.show()" + }, + { + "objectID": "nbs/image_prompter.html#exploring-the-image-prompter", + "href": "nbs/image_prompter.html#exploring-the-image-prompter", + "title": "Image Prompter: Segment Anything", + "section": "Exploring the Image Prompter", + "text": "Exploring the Image Prompter\nThe ImagePrompter can be initialized from a ModelConfig structure, where now we just have support for the SAM model through the SamConfig. Through this config the ImagePrompter will initialize the SAM model and load the weights (from a path or a URL).\nWhat the ImagePrompter can do? 1. Based on the ModelConfig, besides the model initialization, we will setup the required transformations for the images and prompts using the kornia.augmentation API within the Augmentation sequential container. 1. You can benefit from using the torch.compile(...) API (dynamo) for torch >= 2.0.0 versions. To compile with dynamo we provide the method ImagePrompter.compile(...) which will optimize the right parts of the backend model and the prompter itself. 1. Caching the image features and transformations. With the ImagePrompter.set_image(...) method, we transform the image and already encode it using the model, caching it’s embeddings to query later. 1. Query multiple times with multiple prompts. Using the ImagePrompter.predict(...), where we will query on our cached embeddings using Keypoints, Boxes and Masks as prompt.\nWhat the ImagePrompter and Kornia provides? Easy high-levels structures to be used as prompt, also as the result of the prediction. Using the kornia geometry module you can easily encapsulate the Keypoints and Boxes, which allow the API to be more flexible about the desired mode (mainly for boxes, where we had multiple modes of represent it).\nThe Kornia ImagePrompter and model config for SAM can be imported as follow:\nfrom kornia.contrib.image_prompter import ImagePrompter\nfrom kornia.contrib.models import SamConfig\n\n# Setting up a SamConfig with the model type and checkpoint desired\nconfig = SamConfig(model_type, checkpoint)\n\n# Initialize the ImagePrompter\nprompter = ImagePrompter(config, device=device)\n\n\nSet image\nFirst, before adding the image to the prompter, we need to read the image. For that, we can use kornia.io, which internally uses kornia-rs. If you do not have kornia-rs installed, you can install it with pip install kornia_rs. This API implements the DLPack protocol natively in Rust to reduce the memory footprint during the decoding and type conversion. Allowing us to read the image from the disk directly to a tensor. Note that the image should be scaled within the range [0,1].\n\n# Load the image\nimage = load_image(soccer_image_path, ImageLoadType.RGB32, device) # 3 x H x W\n\n# Display the loaded image\nshow_image(image)\n\n\n\n\nWith the image loaded onto the same device as the model, and with the right shape 3xHxW, we can now set the image in our image prompter. Attention: when doing this, the model will compute the embeddings of this image; this means, we will pass this image through the encoder, which will use a lot of memory. It is possible to use the largest model (vit-h) with a graphic card (GPU) that has at least 8Gb of VRAM.\n\nprompter.set_image(image)\n\nIf no error occurred, the features needed to run queries are now cached. If you want to check this, you can see the status of the prompter.is_image_set property.\n\nprompter.is_image_set\n\nTrue\n\n\n\n\nExamples of prompts\nThe ImagePrompter output will have the same Batch Size that its prompts. Where the output shape will be (B, C, H, W). Where B is the number of input prompts, C is determined by multimask output parameter. If multimask_output is True than C=3, otherwise C=1\n\nKeypoints\nKeypoints prompts is a tensor or a Keypoint structure within coordinates into (x, y). With shape BxNx2.\nFor each coordinate pair, should have a corresponding label, where 0 indicates a background point; 1 indicates a foreground point; These labels should be in a tensor with shape BxN\nThe model will try to find a object within all the foreground points, and without the background points. In other words, the foreground points can be used to select the desired type of data, and the background point to exclude the type of data.\n\nkeypoints_tensor = torch.tensor([[[960, 540]]], device=device, dtype=torch.float32)\nkeypoints = Keypoints(keypoints_tensor)\n\nlabels = torch.tensor([[1]], device=device, dtype=torch.float32)\n\n\n\nBoxes\nBoxes prompts is a tensor a with boxes on “xyxy” format/mode, or a Boxes structure. Tensor should have a shape of BxNx4.\n\nboxes_tensor = torch.tensor([[[1841.7, 739.0, 1906.5, 890.6]]], device=device, dtype=torch.float32)\nboxes = Boxes.from_tensor(boxes_tensor, mode=\"xyxy\")\n\n\n\nMasks\nMasks prompts should be provide from a previous model output, with shape Bx1x256x256\n# first run\npredictions = prompter.prediction(...)\n\n# use previous results as prompt\npredictions = prompter.prediction(..., mask=predictions.logits)\n\n\n\nExample of prediction\n\n# using keypoints\nprediction_by_keypoint = prompter.predict(keypoints, labels, multimask_output=False)\n\nshow_image(prediction_by_keypoint.binary_masks)\n\n\n\n\n\n# Using boxes\nprediction_by_boxes = prompter.predict(boxes=boxes, multimask_output=False)\n\nshow_image(prediction_by_boxes.binary_masks)\n\n\n\n\n\n\nExploring the prediction result structure\nThe ImagePrompter prediction structure, is a SegmentationResults which has the upscaled (default) logits when output_original_size=True is passed on the predict.\nThe segmentation results have: - logits: Results logits with shape (B, C, H, W), where C refers to the number of predicted masks - scores: The scores from the logits. Shape (B,) - Binary mask generated from logits considering the mask_threshold. The size depends on original_res_logits=True, if false, the binary mask will have the same shape of the logits Bx1x256x256\n\nprediction_by_boxes.scores\n\ntensor([[0.9317]], device='cuda:0')\n\n\n\nprediction_by_boxes.binary_masks.shape\n\ntorch.Size([1, 1, 1080, 1920])\n\n\n\nprediction_by_boxes.logits.shape\n\ntorch.Size([1, 1, 256, 256])" + }, + { + "objectID": "nbs/image_prompter.html#using-the-image-prompter-on-examples", + "href": "nbs/image_prompter.html#using-the-image-prompter-on-examples", + "title": "Image Prompter: Segment Anything", + "section": "Using the Image Prompter on examples", + "text": "Using the Image Prompter on examples\n\nSoccer players\nUsing an example image from the dataset: https://www.kaggle.com/datasets/ihelon/football-player-segmentation\nLets segment the persons on the field using boxes\n\n# Prompts\nboxes = Boxes.from_tensor(\n torch.tensor(\n [\n [\n [1841.7000, 739.0000, 1906.5000, 890.6000],\n [879.3000, 545.9000, 948.2000, 669.2000],\n [55.7000, 595.0000, 127.4000, 745.9000],\n [1005.4000, 128.7000, 1031.5000, 212.0000],\n [387.4000, 424.1000, 438.2000, 539.0000],\n [921.0000, 377.7000, 963.3000, 483.0000],\n [1213.2000, 885.8000, 1276.2000, 1060.1000],\n [40.8900, 725.9600, 105.4100, 886.5800],\n [848.9600, 283.6200, 896.0600, 368.6200],\n [1109.6500, 499.0400, 1153.0400, 622.1700],\n [576.3000, 860.8000, 671.7000, 1018.8000],\n [1039.8000, 389.9000, 1072.5000, 493.2000],\n [1647.1000, 315.1000, 1694.0000, 406.0000],\n [1231.2000, 214.0000, 1294.1000, 297.3000],\n ]\n ],\n device=device,\n ),\n mode=\"xyxy\",\n)\n\n\n# Load the image\nimage = load_image(soccer_image_path, ImageLoadType.RGB32, device) # 3 x H x W\n\n# Set the image\nprompter.set_image(image)\n\n\npredictions = prompter.predict(boxes=boxes, multimask_output=True)\n\nlet’s see the results, since we used multimask_output=True, the model outputted 3 masks.\n\nshow_predictions(image, predictions, boxes=boxes)\n\n\n\n\n\n\nCar parts\nSegmenting car parts of an example from the dataset: https://www.kaggle.com/datasets/jessicali9530/stanford-cars-dataset\n\n# Prompts\nboxes = Boxes.from_tensor(\n torch.tensor(\n [\n [\n [56.2800, 369.1100, 187.3000, 579.4300],\n [412.5600, 426.5800, 592.9900, 608.1600],\n [609.0800, 366.8200, 682.6400, 431.1700],\n [925.1300, 366.8200, 959.6100, 423.1300],\n [756.1900, 416.2300, 904.4400, 473.7000],\n [489.5600, 285.2200, 676.8900, 343.8300],\n ]\n ],\n device=device,\n ),\n mode=\"xyxy\",\n)\n\nkeypoints = Keypoints(\n torch.tensor(\n [[[535.0, 227.0], [349.0, 215.0], [237.0, 219.0], [301.0, 373.0], [641.0, 397.0], [489.0, 513.0]]], device=device\n )\n)\nlabels = torch.ones(keypoints.shape[:2], device=device, dtype=torch.float32)\n\n\n# Image\nimage = load_image(car_image_path, ImageLoadType.RGB32, device)\n\n# Set the image\nprompter.set_image(image)\n\n\nQuerying with boxes\n\npredictions = prompter.predict(boxes=boxes, multimask_output=True)\n\n\nshow_predictions(image, predictions, boxes=boxes)\n\n\n\n\n\n\nQuerying with keypoints\n\nConsidering N points into 1 Batch\nThis way the model will kinda find the object within all the points\n\npredictions = prompter.predict(keypoints=keypoints, keypoints_labels=labels)\n\n\nshow_predictions(image, predictions, points=(keypoints, labels))\n\n\n\n\n\n\nConsidering 1 point into N Batch\nPrompter encoder not working for a batch of points :/\n\n\n\nConsidering 1 point for batch into N queries\nThis way the model will find an object for each point\n\nk = 2 # number of times/points to query\n\nfor idx in range(min(keypoints.data.size(1), k)):\n print(\"-\" * 79, f\"\\nQuery {idx}:\")\n _kpts = keypoints[:, idx, ...][None, ...]\n _lbl = labels[:, idx, ...][None, ...]\n\n predictions = prompter.predict(keypoints=_kpts, keypoints_labels=_lbl)\n\n show_predictions(image, predictions, points=(_kpts, _lbl))\n\n------------------------------------------------------------------------------- \nQuery 0:\n------------------------------------------------------------------------------- \nQuery 1:\n\n\n\n\n\n\n\n\n\n\n\nSatellite image\nImage from Sentinel-2\nProduct: A tile of the TCI (px of 10m). Product name: S2B_MSIL1C_20230324T130249_N0509_R095_T23KPQ_20230324T174312\n\n# Prompts\nkeypoints = Keypoints(\n torch.tensor(\n [\n [\n # Urban\n [74.0, 104.5],\n [335, 110],\n [702, 65],\n [636, 479],\n [408, 820],\n # Forest\n [40, 425],\n [680, 566],\n [405, 439],\n [73, 689],\n [865, 460],\n # Ocean/water\n [981, 154],\n [705, 714],\n [357, 683],\n [259, 908],\n [1049, 510],\n ]\n ],\n device=device,\n )\n)\nlabels = torch.ones(keypoints.shape[:2], device=device, dtype=torch.float32)\n\n\n# Image\nimage = load_image(satellite_image_path, ImageLoadType.RGB32, device)\n\n# Set the image\nprompter.set_image(image)\n\n\nQuery urban points\n\n# Query the prompts\nlabels_to_query = labels.clone()\nlabels_to_query[..., 5:] = 0\n\npredictions = prompter.predict(keypoints=keypoints, keypoints_labels=labels_to_query)\n\n\nshow_predictions(image, predictions, points=(keypoints, labels_to_query))\n\n\n\n\n\n\nQuery Forest points\n\n# Query the prompts\nlabels_to_query = labels.clone()\nlabels_to_query[..., :5] = 0\nlabels_to_query[..., 10:] = 0\n\npredictions = prompter.predict(keypoints=keypoints, keypoints_labels=labels_to_query)\n\n\nshow_predictions(image, predictions, points=(keypoints, labels_to_query))\n\n\n\n\n\n\nQuery ocean/water points\n\n# Query the prompts\nlabels_to_query = labels.clone()\nlabels_to_query[..., :10] = 0\n\npredictions = prompter.predict(keypoints=keypoints, keypoints_labels=labels_to_query)\n\n\nshow_predictions(image, predictions, points=(keypoints, labels_to_query))" + }, + { + "objectID": "nbs/color_raw_to_rgb.html#download-necessary-files-and-libraries", + "href": "nbs/color_raw_to_rgb.html#download-necessary-files-and-libraries", + "title": "Convert RGB to RAW", + "section": "Download necessary files and libraries", + "text": "Download necessary files and libraries\n\n%%capture\n!pip install kornia\n!pip install rawpy\n\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://docs.google.com/uc?export=download&id=1nSM_FYJ7i9-_57ecPY5sCG2s8zt9dRhF\"\ndownload_image(url, \"raw.dng\")\n\n'raw.dng'" + }, + { + "objectID": "nbs/color_raw_to_rgb.html#import-necessary-libraries", + "href": "nbs/color_raw_to_rgb.html#import-necessary-libraries", + "title": "Convert RGB to RAW", + "section": "Import necessary libraries", + "text": "Import necessary libraries\n\nimport kornia\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport rawpy\nimport torch" + }, + { + "objectID": "nbs/color_raw_to_rgb.html#prepare-the-raw-file-through-rawpy", + "href": "nbs/color_raw_to_rgb.html#prepare-the-raw-file-through-rawpy", + "title": "Convert RGB to RAW", + "section": "Prepare the raw file through rawpy", + "text": "Prepare the raw file through rawpy\n\npath = \"raw.dng\"\nraw = rawpy.imread(path)\ncfa = \"\".join([chr(raw.color_desc[i]) for i in raw.raw_pattern.flatten()])\n\n# Figure out which cfa we are using by looking at the component order from rawpy\n# if we do this directly from a camera this would of course be known ahead\n# of time\nif cfa == \"GRBG\":\n korniacfa = kornia.color.CFA.GB\nelif cfa == \"GBRG\":\n korniacfa = kornia.color.CFA.GR\nelif cfa == \"BGGR\":\n korniacfa = kornia.color.CFA.RG\nelif cfa == \"RGGB\":\n korniacfa = kornia.color.CFA.BG\n\n# This is a GB file i.e. top left pixel is Green follow by Red (and the pair\n# starting at (1,1) is Green, Blue)\nprint(cfa)\nprint(korniacfa)\n\nGRBG\nCFA.GB" + }, + { + "objectID": "nbs/color_raw_to_rgb.html#get-the-data-into-kornia-by-doing-the-conversion", + "href": "nbs/color_raw_to_rgb.html#get-the-data-into-kornia-by-doing-the-conversion", + "title": "Convert RGB to RAW", + "section": "Get the data into kornia by doing the conversion", + "text": "Get the data into kornia by doing the conversion\n\n# We find the data inside raw.raw_image\nrawdata = raw.raw_image\n# white level gives maximum value for a pixel\nrawtensor = torch.Tensor(rawdata.astype(np.float32) / raw.white_level).reshape(\n 1, 1, raw.raw_image.shape[0], raw.raw_image.shape[1]\n)\nrgbtensor = kornia.color.raw.raw_to_rgb(rawtensor, korniacfa)" + }, + { + "objectID": "nbs/color_raw_to_rgb.html#visualize", + "href": "nbs/color_raw_to_rgb.html#visualize", + "title": "Convert RGB to RAW", + "section": "Visualize", + "text": "Visualize\n\nnpimg = np.moveaxis(np.squeeze((rgbtensor * 255.0).numpy().astype(np.uint8)), 0, 2)\nplt.figure()\n\n# Show the image\n# Colors will look a little funky because they need to be balanced properly, but\n# the leaves are supposed to be redm berries blue and grass green\nplt.imshow(npimg)\n\n<matplotlib.image.AxesImage>" + }, + { + "objectID": "nbs/color_raw_to_rgb.html#gotchas-rotation-gives-a-different-cfa", + "href": "nbs/color_raw_to_rgb.html#gotchas-rotation-gives-a-different-cfa", + "title": "Convert RGB to RAW", + "section": "Gotchas: Rotation gives a different cfa", + "text": "Gotchas: Rotation gives a different cfa\n\n# if we do a pipeline were we first rotate the image, it will end up with a\n# different cfa that isn't possible to describe since we are assuming all red\n# samples are on t.he same row while they would not be rotated\nrgbtensor = kornia.color.raw.raw_to_rgb(torch.rot90(rawtensor, 1, [2, 3]), korniacfa)\nnpimg = np.moveaxis(np.squeeze((rgbtensor * 255.0).numpy().astype(np.uint8)), 0, 2)\nplt.figure()\nplt.imshow(npimg)\n\n<matplotlib.image.AxesImage>\n\n\n\n\n\n\n# If we crop, we can adjust for this by using a different cfa\nrgbtensor = kornia.color.raw.raw_to_rgb(rawtensor[:, :, 1:1023, 1:1023], kornia.color.raw.CFA.GR)\nnpimg = np.moveaxis(np.squeeze((rgbtensor * 255.0).numpy().astype(np.uint8)), 0, 2)\nplt.figure()\nplt.imshow(npimg)\n\n<matplotlib.image.AxesImage>" + }, + { + "objectID": "nbs/line_detection_and_matching_sold2.html#setup", + "href": "nbs/line_detection_and_matching_sold2.html#setup", + "title": "Line detection and matching example with SOLD2: Self-supervised Occlusion-aware Line Description and Detection", + "section": "Setup", + "text": "Setup\nInstall the libraries:\n\n%%capture\n!pip install kornia\n!pip install kornia-rs\n!pip install opencv-python --upgrade # Just for windows\n!pip install matplotlib\n\nNow let’s download an image\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1].split(\"?\")[0] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\ndownload_image(\"https://github.com/cvg/SOLD2/raw/main/assets/images/terrace0.JPG\")\ndownload_image(\"https://github.com/cvg/SOLD2/raw/main/assets/images/terrace1.JPG\")\n\n'terrace1.JPG'\n\n\nThen, we will load the libraries\n\nimport kornia as K\nimport kornia.feature as KF\nimport torch\n\nLoad the images and convert into torch tensors.\n\nfname1 = \"terrace0.JPG\"\nfname2 = \"terrace1.JPG\"\n\ntorch_img1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32)[None, ...]\ntorch_img2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32)[None, ...]\n\ntorch_img1.shape, torch_img2.shape\n\n(torch.Size([1, 3, 496, 744]), torch.Size([1, 3, 496, 744]))\n\n\nPrepare the data for the model, which is expected a batch of images in gray scale (shape: (Batch size, 1, Height, Width)).\nThe SOLD2 model was tuned for images in the range 400~800px when using config=None.\n\n# First, convert the images to gray scale\ntorch_img1_gray = K.color.rgb_to_grayscale(torch_img1)\ntorch_img2_gray = K.color.rgb_to_grayscale(torch_img2)\n\n\ntorch_img1_gray.shape, torch_img2_gray.shape\n\n(torch.Size([1, 1, 496, 744]), torch.Size([1, 1, 496, 744]))\n\n\n\n# then, stack the images to create/simulate a batch\nimgs = torch.cat([torch_img1_gray, torch_img2_gray], dim=0)\n\nimgs.shape\n\ntorch.Size([2, 1, 496, 744])" + }, + { + "objectID": "nbs/line_detection_and_matching_sold2.html#performs-line-detection-and-matching", + "href": "nbs/line_detection_and_matching_sold2.html#performs-line-detection-and-matching", + "title": "Line detection and matching example with SOLD2: Self-supervised Occlusion-aware Line Description and Detection", + "section": "Performs line detection and matching", + "text": "Performs line detection and matching\nLoad the sold2 model with pre-trained=True, which will download and set pre-trained weights to the model.\n\n%%capture\nsold2 = KF.SOLD2(pretrained=True, config=None)\n\n\nPerform the model prediction\n\n%%capture\nwith torch.inference_mode():\n outputs = sold2(imgs)\n\nOrganize the outputs for demo.\nAttention: The detected line segments is in ij coordinates convention.\n\noutputs.keys()\n\ndict_keys(['junction_heatmap', 'line_heatmap', 'dense_desc', 'line_segments'])\n\n\n\nline_seg1 = outputs[\"line_segments\"][0]\nline_seg2 = outputs[\"line_segments\"][1]\ndesc1 = outputs[\"dense_desc\"][0]\ndesc2 = outputs[\"dense_desc\"][1]\n\n\n\nPerform line matching\n\nwith torch.inference_mode():\n matches = sold2.match(line_seg1, line_seg2, desc1[None], desc2[None])\n\n\nvalid_matches = matches != -1\nmatch_indices = matches[valid_matches]\n\nmatched_lines1 = line_seg1[valid_matches]\nmatched_lines2 = line_seg2[match_indices]" + }, + { + "objectID": "nbs/line_detection_and_matching_sold2.html#plot-lines-detected-and-also-the-match", + "href": "nbs/line_detection_and_matching_sold2.html#plot-lines-detected-and-also-the-match", + "title": "Line detection and matching example with SOLD2: Self-supervised Occlusion-aware Line Description and Detection", + "section": "Plot lines detected and also the match", + "text": "Plot lines detected and also the match\nPlot functions adapted from original code.\n\nimport copy\n\nimport matplotlib\nimport matplotlib.colors as mcolors\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n\ndef plot_images(imgs, titles=None, cmaps=\"gray\", dpi=100, size=6, pad=0.5):\n \"\"\"Plot a set of images horizontally.\n Args:\n imgs: a list of NumPy or PyTorch images, RGB (H, W, 3) or mono (H, W).\n titles: a list of strings, as titles for each image.\n cmaps: colormaps for monochrome images.\n \"\"\"\n n = len(imgs)\n if not isinstance(cmaps, (list, tuple)):\n cmaps = [cmaps] * n\n figsize = (size * n, size * 3 / 4) if size is not None else None\n fig, ax = plt.subplots(1, n, figsize=figsize, dpi=dpi)\n if n == 1:\n ax = [ax]\n for i in range(n):\n ax[i].imshow(imgs[i], cmap=plt.get_cmap(cmaps[i]))\n ax[i].get_yaxis().set_ticks([])\n ax[i].get_xaxis().set_ticks([])\n ax[i].set_axis_off()\n for spine in ax[i].spines.values(): # remove frame\n spine.set_visible(False)\n if titles:\n ax[i].set_title(titles[i])\n fig.tight_layout(pad=pad)\n\n\ndef plot_lines(lines, line_colors=\"orange\", point_colors=\"cyan\", ps=4, lw=2, indices=(0, 1)):\n \"\"\"Plot lines and endpoints for existing images.\n Args:\n lines: list of ndarrays of size (N, 2, 2).\n colors: string, or list of list of tuples (one for each keypoints).\n ps: size of the keypoints as float pixels.\n lw: line width as float pixels.\n indices: indices of the images to draw the matches on.\n \"\"\"\n if not isinstance(line_colors, list):\n line_colors = [line_colors] * len(lines)\n if not isinstance(point_colors, list):\n point_colors = [point_colors] * len(lines)\n\n fig = plt.gcf()\n ax = fig.axes\n assert len(ax) > max(indices)\n axes = [ax[i] for i in indices]\n fig.canvas.draw()\n\n # Plot the lines and junctions\n for a, l, lc, pc in zip(axes, lines, line_colors, point_colors):\n for i in range(len(l)):\n line = matplotlib.lines.Line2D(\n (l[i, 1, 1], l[i, 0, 1]),\n (l[i, 1, 0], l[i, 0, 0]),\n zorder=1,\n c=lc,\n linewidth=lw,\n )\n a.add_line(line)\n pts = l.reshape(-1, 2)\n a.scatter(pts[:, 1], pts[:, 0], c=pc, s=ps, linewidths=0, zorder=2)\n\n\ndef plot_color_line_matches(lines, lw=2, indices=(0, 1)):\n \"\"\"Plot line matches for existing images with multiple colors.\n Args:\n lines: list of ndarrays of size (N, 2, 2).\n lw: line width as float pixels.\n indices: indices of the images to draw the matches on.\n \"\"\"\n n_lines = len(lines[0])\n\n cmap = plt.get_cmap(\"nipy_spectral\", lut=n_lines)\n colors = np.array([mcolors.rgb2hex(cmap(i)) for i in range(cmap.N)])\n\n np.random.shuffle(colors)\n\n fig = plt.gcf()\n ax = fig.axes\n assert len(ax) > max(indices)\n axes = [ax[i] for i in indices]\n fig.canvas.draw()\n\n # Plot the lines\n for a, l in zip(axes, lines):\n for i in range(len(l)):\n line = matplotlib.lines.Line2D(\n (l[i, 1, 1], l[i, 0, 1]),\n (l[i, 1, 0], l[i, 0, 0]),\n zorder=1,\n c=colors[i],\n linewidth=lw,\n )\n a.add_line(line)\n\n\nimgs_to_plot = [K.tensor_to_image(torch_img1), K.tensor_to_image(torch_img2)]\nlines_to_plot = [line_seg1.numpy(), line_seg2.numpy()]\n\nplot_images(imgs_to_plot, [\"Image 1 - detected lines\", \"Image 2 - detected lines\"])\nplot_lines(lines_to_plot, ps=3, lw=2, indices={0, 1})\n\n\n\n\n\nplot_images(imgs_to_plot, [\"Image 1 - matched lines\", \"Image 2 - matched lines\"])\nplot_color_line_matches([matched_lines1, matched_lines2], lw=2)" + }, + { + "objectID": "nbs/line_detection_and_matching_sold2.html#example-of-homography-from-line-segment-correspondences-from-sold2", + "href": "nbs/line_detection_and_matching_sold2.html#example-of-homography-from-line-segment-correspondences-from-sold2", + "title": "Line detection and matching example with SOLD2: Self-supervised Occlusion-aware Line Description and Detection", + "section": "Example of homography from line segment correspondences from SOLD2", + "text": "Example of homography from line segment correspondences from SOLD2\nRobust geometry estimation with Random sample consensus (RANSAC)\nLoad the model:\n\nransac = K.geometry.RANSAC(model_type=\"homography_from_linesegments\", inl_th=3.0)\n\n\nPerform the model correspondencies\n\nH_ransac, correspondence_mask = ransac(matched_lines1.flip(dims=(2,)), matched_lines2.flip(dims=(2,)))\n\nWrap the image 1 to image 2\n\nimg1_warp_to2 = K.geometry.warp_perspective(torch_img1, H_ransac[None], (torch_img1.shape[-2:]))\n\n\n\nPlot the matched lines and wrapped image\n\nplot_images(\n imgs_to_plot,\n [\"Image 1 - lines with correspondence\", \"Image 2 - lines with correspondence\"],\n)\nplot_color_line_matches([matched_lines1[correspondence_mask], matched_lines2[correspondence_mask]], lw=2)\n\n\n\n\n\nplot_images(\n [K.tensor_to_image(torch_img2), K.tensor_to_image(img1_warp_to2)],\n [\"Image 2\", \"Image 1 wrapped to 2\"],\n)" + }, + { + "objectID": "nbs/color_yuv420_to_rgb.html", + "href": "nbs/color_yuv420_to_rgb.html", + "title": "Convert RGB to YUV420", + "section": "", + "text": "Get data and libraries to work with\n\n%%capture\n!pip install kornia\n!pip install py7zr\n\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://github.com/kornia/data/raw/main/foreman_qcif.7z\"\ndownload_image(url)\n\n'foreman_qcif.7z'\n\n\n\n\nImport needed libs\n\nimport kornia\nimport numpy as np\n\n# prepare the data, decompress so we have a foreman_qcif.yuv ready\nimport py7zr\nimport torch\n\nwith py7zr.SevenZipFile(\"foreman_qcif.7z\", mode=\"r\") as z:\n z.extractall()\n\n\n\nDefine functions for reading the yuv file to torch tensor for use in Kornia\n\nimport matplotlib.pyplot as plt\n\n\ndef read_frame(fname, framenum):\n # A typical 420 yuv file is 3 planes Y, u then v with u/v a quartyer the size of Y\n # Build rgb png images from foreman that is 3 plane yuv420\n yuvnp = np.fromfile(fname, dtype=np.uint8, count=int(176 * 144 * 1.5), offset=int(176 * 144 * 1.5) * framenum)\n y = torch.from_numpy(yuvnp[0 : 176 * 144].reshape((1, 1, 144, 176)).astype(np.float32) / 255.0)\n\n uv_tmp = yuvnp[176 * 144 : int(144 * 176 * 3 / 2)].reshape((1, 2, int(144 / 2), int(176 / 2)))\n # uv (chroma) is typically defined from -0.5 to 0.5 (or -128 to 128 for 8-bit)\n uv = torch.from_numpy(uv_tmp.astype(np.float32) / 255.0) - 0.5\n return (y, uv)\n\n\n\nSample what the images look like Y, u, v channels separaatly and then converted to rgn through kornia (and back to numpy in this case)\n\n(y, uv) = read_frame(\"foreman_qcif.yuv\", 0) # using compression classic foreman\nplt.imshow((y.numpy()[0, 0, :, :] * 255.0).astype(np.uint8), cmap=\"gray\")\nplt.figure()\nplt.imshow(((uv.numpy()[0, 0, :, :] + 0.5) * 255.0).astype(np.uint8), cmap=\"gray\")\nplt.figure()\nplt.imshow(((uv.numpy()[0, 1, :, :] + 0.5) * 255.0).astype(np.uint8), cmap=\"gray\")\n\nrgb = np.moveaxis(kornia.color.yuv420_to_rgb(y, uv).numpy(), 1, 3).reshape((144, 176, 3))\n\nprint(\"as converted through kornia\")\nplt.figure()\nplt.imshow((rgb * 255).astype(np.uint8))\n\nas converted through kornia\n\n\n<matplotlib.image.AxesImage>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nWe can use these in some internal Kornia algorithm implementations. Lets pretend we want to do LoFTR on the red channel\n\nimport cv2\n\nloftr = kornia.feature.LoFTR(\"outdoor\")\n(y0, uv0) = read_frame(\"foreman_qcif.yuv\", 175)\n(y1, uv1) = read_frame(\"foreman_qcif.yuv\", 185)\nrgb0 = kornia.color.yuv420_to_rgb(y0, uv0)\nrgb1 = kornia.color.yuv420_to_rgb(y1, uv1)\n\nwith torch.no_grad():\n matches = loftr({\"image0\": rgb0[:, 0:1, :, :], \"image1\": rgb1[:, 0:1, :, :]})\n\nmatched_image = cv2.drawMatches(\n np.moveaxis(rgb0.numpy()[0, :, :, :] * 255.0, 0, 2).astype(np.uint8),\n [cv2.KeyPoint(x[0], x[1], 0) for x in matches[\"keypoints0\"].numpy()],\n np.moveaxis(rgb1.numpy()[0, :, :, :] * 255.0, 0, 2).astype(np.uint8),\n [cv2.KeyPoint(x[0], x[1], 0) for x in matches[\"keypoints1\"].numpy()],\n [cv2.DMatch(x, x, 0) for x in range(len(matches[\"keypoints1\"].numpy()))],\n None,\n)\n\nplt.figure(figsize=(30, 30))\nplt.imshow(matched_image)\n\n<matplotlib.image.AxesImage>" + }, + { + "objectID": "nbs/image_points_transforms.html#install-and-get-data", + "href": "nbs/image_points_transforms.html#install-and-get-data", + "title": "Image and Keypoints augmentations", + "section": "Install and get data", + "text": "Install and get data\n\n%%capture\n!pip install kornia\n!pip install kornia-rs matplotlib\n\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\ndownload_image(\"https://github.com/kornia/data/raw/main/arturito.jpg\")\n\n'arturito.jpg'\n\n\n\nimport kornia as K\nimport torch\nfrom matplotlib import pyplot as plt\n\n\nimg = K.io.load_image(\"arturito.jpg\", K.io.ImageLoadType.RGB32)\nimg = img[None] # 1xCxHxW / fp32 / [0, 1]\nprint(img.shape)\n\ntorch.Size([1, 3, 144, 256])" + }, + { + "objectID": "nbs/image_points_transforms.html#draw-points-and-show-image", + "href": "nbs/image_points_transforms.html#draw-points-and-show-image", + "title": "Image and Keypoints augmentations", + "section": "Draw points and show image", + "text": "Draw points and show image\n\ncoords = torch.tensor([[[125, 40.0], [185.0, 75.0]]]) # BxNx2 [x,y]\n\nfig, ax = plt.subplots()\n\nax.add_patch(plt.Circle((coords[0, 0, 0], coords[0, 0, 1]), color=\"r\"))\nax.add_patch(plt.Circle((coords[0, 1, 0], coords[0, 1, 1]), color=\"r\"))\n\nax.imshow(K.tensor_to_image(img))\n\n<matplotlib.image.AxesImage>" + }, + { + "objectID": "nbs/image_points_transforms.html#resize-points-and-show", + "href": "nbs/image_points_transforms.html#resize-points-and-show", + "title": "Image and Keypoints augmentations", + "section": "Resize points and show", + "text": "Resize points and show\n\nresize_op = K.augmentation.AugmentationSequential(\n K.augmentation.Resize((100, 200), antialias=True), data_keys=[\"input\", \"keypoints\"]\n)\n\nprint(resize_op.transform_matrix)\n\nimg_resize, coords_resize = resize_op(img, coords)\n\n\nfig, ax = plt.subplots()\n\nax.add_patch(plt.Circle((coords_resize[0, 0, 0], coords_resize[0, 0, 1]), color=\"r\"))\nax.add_patch(plt.Circle((coords_resize[0, 1, 0], coords_resize[0, 1, 1]), color=\"r\"))\n\nax.imshow(K.tensor_to_image(img_resize))\n\nNone\n\n\n<matplotlib.image.AxesImage>" + }, + { + "objectID": "nbs/image_points_transforms.html#crop-image-and-points", + "href": "nbs/image_points_transforms.html#crop-image-and-points", + "title": "Image and Keypoints augmentations", + "section": "Crop image and points", + "text": "Crop image and points\n\ncrop_op = K.augmentation.AugmentationSequential(K.augmentation.CenterCrop((100, 200)), data_keys=[\"input\", \"keypoints\"])\nprint(crop_op.transform_matrix)\n\nimg_resize, coords_resize = crop_op(img, coords)\n\n\nfig, ax = plt.subplots()\n\nax.add_patch(plt.Circle((coords_resize[0, 0, 0], coords_resize[0, 0, 1]), color=\"r\"))\nax.add_patch(plt.Circle((coords_resize[0, 1, 0], coords_resize[0, 1, 1]), color=\"r\"))\n\nax.imshow(K.tensor_to_image(img_resize))\n\nNone\n\n\n<matplotlib.image.AxesImage>" + }, + { + "objectID": "nbs/image_matching.html", + "href": "nbs/image_matching.html", + "title": "Image matching example with LoFTR", + "section": "", + "text": "First, we will install everything needed:\n%%capture\n!pip install kornia\n!pip install kornia-rs\n!pip install kornia_moons\n!pip install opencv-python --upgrade\nNow let’s download an image pair\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\nurl_a = \"https://github.com/kornia/data/raw/main/matching/kn_church-2.jpg\"\nurl_b = \"https://github.com/kornia/data/raw/main/matching/kn_church-8.jpg\"\n\ndownload_image(url_a)\ndownload_image(url_b)\n\n'kn_church-8.jpg'\nFirst, we will define image matching pipeline with OpenCV SIFT features. We will also use kornia for the state-of-the-art match filtering – Lowe ratio + mutual nearest neighbor check and MAGSAC++ as RANSAC.\nimport cv2\nimport kornia as K\nimport kornia.feature as KF\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport torch\nfrom kornia_moons.viz import draw_LAF_matches\n%%capture\nfname1 = \"kn_church-2.jpg\"\nfname2 = \"kn_church-8.jpg\"\n\nimg1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32)[None, ...]\nimg2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32)[None, ...]\n\nimg1 = K.geometry.resize(img1, (600, 375), antialias=True)\nimg2 = K.geometry.resize(img2, (600, 375), antialias=True)\n\n\nmatcher = KF.LoFTR(pretrained=\"outdoor\")\n\ninput_dict = {\n \"image0\": K.color.rgb_to_grayscale(img1), # LofTR works on grayscale images only\n \"image1\": K.color.rgb_to_grayscale(img2),\n}\n\nwith torch.inference_mode():\n correspondences = matcher(input_dict)\nfor k, v in correspondences.items():\n print(k)\n\nkeypoints0\nkeypoints1\nconfidence\nbatch_indexes\nNow let’s clean-up the correspondences with modern RANSAC and estimate fundamental matrix between two images\nmkpts0 = correspondences[\"keypoints0\"].cpu().numpy()\nmkpts1 = correspondences[\"keypoints1\"].cpu().numpy()\nFm, inliers = cv2.findFundamentalMat(mkpts0, mkpts1, cv2.USAC_MAGSAC, 0.5, 0.999, 100000)\ninliers = inliers > 0\nFinally, let’s draw the matches with a function from kornia_moons. The correct matches are in green and imprecise matches - in blue\ndraw_LAF_matches(\n KF.laf_from_center_scale_ori(\n torch.from_numpy(mkpts0).view(1, -1, 2),\n torch.ones(mkpts0.shape[0]).view(1, -1, 1, 1),\n torch.ones(mkpts0.shape[0]).view(1, -1, 1),\n ),\n KF.laf_from_center_scale_ori(\n torch.from_numpy(mkpts1).view(1, -1, 2),\n torch.ones(mkpts1.shape[0]).view(1, -1, 1, 1),\n torch.ones(mkpts1.shape[0]).view(1, -1, 1),\n ),\n torch.arange(mkpts0.shape[0]).view(-1, 1).repeat(1, 2),\n K.tensor_to_image(img1),\n K.tensor_to_image(img2),\n inliers,\n draw_dict={\"inlier_color\": (0.2, 1, 0.2), \"tentative_color\": None, \"feature_color\": (0.2, 0.5, 1), \"vertical\": False},\n)" + }, + { + "objectID": "nbs/image_matching.html#loftr-indoor", + "href": "nbs/image_matching.html#loftr-indoor", + "title": "Image matching example with LoFTR", + "section": "LoFTR Indoor", + "text": "LoFTR Indoor\nWe recommend to use KF.LoFTR(pretrained='indoor_new') and resize images to be not bigger than 640x480 pixels for the indoor model.\n\n%%capture\n\ndownload_image(\"https://github.com/zju3dv/LoFTR/raw/master/assets/scannet_sample_images/scene0711_00_frame-001680.jpg\")\ndownload_image(\"https://github.com/zju3dv/LoFTR/raw/master/assets/scannet_sample_images/scene0711_00_frame-001995.jpg\")\n\nmatcher = KF.LoFTR(pretrained=\"indoor_new\")\n\n\nfname1 = \"scene0711_00_frame-001680.jpg\"\nfname2 = \"scene0711_00_frame-001995.jpg\"\n\nimg1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32)[None, ...]\nimg2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32)[None, ...]\n\nimg1 = K.geometry.resize(img1, (480, 640), antialias=True)\nimg2 = K.geometry.resize(img2, (480, 640), antialias=True)\n\nmatcher = KF.LoFTR(pretrained=\"indoor_new\")\n\ninput_dict = {\n \"image0\": K.color.rgb_to_grayscale(img1), # LofTR works on grayscale images only\n \"image1\": K.color.rgb_to_grayscale(img2),\n}\n\nwith torch.inference_mode():\n correspondences = matcher(input_dict)\n\nmkpts0 = correspondences[\"keypoints0\"].cpu().numpy()\nmkpts1 = correspondences[\"keypoints1\"].cpu().numpy()\nFm, inliers = cv2.findFundamentalMat(mkpts0, mkpts1, cv2.USAC_MAGSAC, 1.0, 0.999, 100000)\ninliers = inliers > 0\n\ndraw_LAF_matches(\n KF.laf_from_center_scale_ori(\n torch.from_numpy(mkpts0).view(1, -1, 2),\n torch.ones(mkpts0.shape[0]).view(1, -1, 1, 1),\n torch.ones(mkpts0.shape[0]).view(1, -1, 1),\n ),\n KF.laf_from_center_scale_ori(\n torch.from_numpy(mkpts1).view(1, -1, 2),\n torch.ones(mkpts1.shape[0]).view(1, -1, 1, 1),\n torch.ones(mkpts1.shape[0]).view(1, -1, 1),\n ),\n torch.arange(mkpts0.shape[0]).view(-1, 1).repeat(1, 2),\n K.tensor_to_image(img1),\n K.tensor_to_image(img2),\n inliers,\n draw_dict={\n \"inlier_color\": (0.2, 1, 0.2),\n \"tentative_color\": (1.0, 0.5, 1),\n \"feature_color\": (0.2, 0.5, 1),\n \"vertical\": False,\n },\n)" + }, + { + "objectID": "nbs/canny.html#preparation", + "href": "nbs/canny.html#preparation", + "title": "Obtaining Edges using the Canny operator", + "section": "Preparation", + "text": "Preparation\nWe first install Kornia.\n\n%%capture\n%matplotlib inline\n!pip install kornia\n!pip install kornia-rs\n\n\nimport kornia\n\nkornia.__version__\n\n'0.6.12-dev'\n\n\nNow we download the example image.\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://ih1.redbubble.net/image.675644909.6235/flat,800x800,075,f.u3.jpg\"\ndownload_image(url, \"paranoia_agent.jpg\")\n\n'paranoia_agent.jpg'" + }, + { + "objectID": "nbs/canny.html#example", + "href": "nbs/canny.html#example", + "title": "Obtaining Edges using the Canny operator", + "section": "Example", + "text": "Example\nWe first import the required libraries and load the data.\n\nimport kornia\nimport matplotlib.pyplot as plt\nimport torch\n\n# read the image with Kornia\nimg_tensor = kornia.io.load_image(\"paranoia_agent.jpg\", kornia.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\nimg_array = kornia.tensor_to_image(img_tensor)\n\nplt.axis(\"off\")\nplt.imshow(img_array)\nplt.show()\n\n\n\n\nTo apply a filter, we create the Canny operator object and apply it to the data. It will provide the magnitudes as well as the edges after the hysteresis process. Note that the edges are a binary image which is not differentiable!\n\n# create the operator\ncanny = kornia.filters.Canny()\n\n# blur the image\nx_magnitude, x_canny = canny(img_tensor)\n\nThat’s it! We can compare the source image and the results from the magnitude as well as the edges:\n\n# convert back to numpy\nimg_magnitude = kornia.tensor_to_image(x_magnitude.byte())\nimg_canny = kornia.tensor_to_image(x_canny.byte())\n\n# Create the plot\nfig, axs = plt.subplots(1, 3, figsize=(16, 16))\naxs = axs.ravel()\n\naxs[0].axis(\"off\")\naxs[0].set_title(\"image source\")\naxs[0].imshow(img_array)\n\naxs[1].axis(\"off\")\naxs[1].set_title(\"canny magnitude\")\naxs[1].imshow(img_magnitude, cmap=\"Greys\")\n\naxs[2].axis(\"off\")\naxs[2].set_title(\"canny edges\")\naxs[2].imshow(img_canny, cmap=\"Greys\")\n\nplt.show()\n\n\n\n\nNote that our final result still recovers some edges whose magnitude is quite low. Let us increase the thresholds and compare the final edges.\n\n# create the operator\ncanny = kornia.filters.Canny(low_threshold=0.4, high_threshold=0.5)\n\n# blur the image\n_, x_canny_threshold = canny(img_tensor)\n\n\nimport torch.nn.functional as F\n\n# convert back to numpy\nimg_canny_threshold = kornia.tensor_to_image(x_canny_threshold.byte())\n\n# Create the plot\nfig, axs = plt.subplots(1, 3, figsize=(16, 16))\naxs = axs.ravel()\n\naxs[0].axis(\"off\")\naxs[0].set_title(\"image source\")\naxs[0].imshow(img_array)\n\naxs[1].axis(\"off\")\naxs[1].set_title(\"canny default\")\naxs[1].imshow(img_canny, cmap=\"Greys\")\n\naxs[2].axis(\"off\")\naxs[2].set_title(\"canny defined thresholds\")\naxs[2].imshow(img_canny_threshold, cmap=\"Greys\")\n\nplt.show()" + }, + { + "objectID": "nbs/image_enhancement.html", + "href": "nbs/image_enhancement.html", + "title": "Image Enhancement", + "section": "", + "text": "%%capture\n!pip install kornia\n!pip install kornia-rs\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\ndownload_image(\"https://github.com/kornia/data/raw/main/ninja_turtles.jpg\")\n\n'ninja_turtles.jpg'\nimport kornia as K\nimport numpy as np\nimport torch\nimport torchvision\nfrom matplotlib import pyplot as plt\ndef imshow(input: torch.Tensor):\n out: torch.Tensor = torchvision.utils.make_grid(input, nrow=2, padding=5)\n out_np: np.ndarray = K.utils.tensor_to_image(out)\n plt.imshow(out_np)\n plt.axis(\"off\")\n plt.show()\nWe use Kornia to load an image to memory represented as a tensor\nx_rgb = K.io.load_image(\"ninja_turtles.jpg\", K.io.ImageLoadType.RGB32)[None, ...]\nCreate batch\nx_rgb = x_rgb.expand(4, -1, -1, -1) # 4xCxHxW\nimshow(x_rgb)" + }, + { + "objectID": "nbs/image_enhancement.html#adjust-brightness", + "href": "nbs/image_enhancement.html#adjust-brightness", + "title": "Image Enhancement", + "section": "Adjust brightness", + "text": "Adjust brightness\n\nx_out: torch.Tensor = K.enhance.adjust_brightness(x_rgb, torch.linspace(0.2, 0.8, 4))\nimshow(x_out)" + }, + { + "objectID": "nbs/image_enhancement.html#adjust-contrast", + "href": "nbs/image_enhancement.html#adjust-contrast", + "title": "Image Enhancement", + "section": "Adjust Contrast", + "text": "Adjust Contrast\n\nx_out: torch.Tensor = K.enhance.adjust_contrast(x_rgb, torch.linspace(0.5, 1.0, 4))\nimshow(x_out)" + }, + { + "objectID": "nbs/image_enhancement.html#adjust-saturation", + "href": "nbs/image_enhancement.html#adjust-saturation", + "title": "Image Enhancement", + "section": "Adjust Saturation", + "text": "Adjust Saturation\n\nx_out: torch.Tensor = K.enhance.adjust_saturation(x_rgb, torch.linspace(0.0, 1.0, 4))\nimshow(x_out)" + }, + { + "objectID": "nbs/image_enhancement.html#adjust-gamma", + "href": "nbs/image_enhancement.html#adjust-gamma", + "title": "Image Enhancement", + "section": "Adjust Gamma", + "text": "Adjust Gamma\n\nx_out: torch.Tensor = K.enhance.adjust_gamma(x_rgb, torch.tensor([0.2, 0.4, 0.5, 0.6]))\nimshow(x_out)" + }, + { + "objectID": "nbs/image_enhancement.html#adjust-hue", + "href": "nbs/image_enhancement.html#adjust-hue", + "title": "Image Enhancement", + "section": "Adjust Hue", + "text": "Adjust Hue\n\nx_out: torch.Tensor = K.enhance.adjust_hue(x_rgb, torch.linspace(0.0, 3.14159, 4))\nimshow(x_out)" + }, + { + "objectID": "nbs/fit_plane.html", + "href": "nbs/fit_plane.html", + "title": "Fit plane tutorial", + "section": "", + "text": "import plotly.express as px\nimport plotly.io as pio\nimport torch\nfrom kornia.core import stack\nfrom kornia.geometry.liegroup import So3\nfrom kornia.geometry.plane import Hyperplane, fit_plane\nfrom kornia.geometry.vector import Vector3\nfrom kornia.utils import create_meshgrid\n\n\n# define the plane\nplane_h = 25\nplane_w = 50\n\n# create a base mesh in the ground z == 0\nmesh = create_meshgrid(plane_h, plane_w, normalized_coordinates=True)\nX, Y = mesh[..., 0], mesh[..., 1]\nZ = 0 * X\n\nmesh_pts = Vector3.from_coords(X, Y, Z)\n\n\n# add noise to the mesh\nrand_pts = Vector3.random((plane_h, plane_w))\nrand_pts.z.clamp_(min=-0.1, max=0.1)\n\nmesh_view: Vector3 = mesh_pts + rand_pts\n\n\nx_view = mesh_view.x.ravel().detach().cpu().numpy().tolist()\ny_view = mesh_view.y.ravel().detach().cpu().numpy().tolist()\nz_view = mesh_view.z.ravel().detach().cpu().numpy().tolist()\nfig = px.scatter_3d(dict(x=x_view, y=y_view, z=z_view, category=[\"view\"] * len(x_view)), \"x\", \"y\", \"z\", color=\"category\")\nfig.show()\n\n\n \n\n\n\n# create rotation\nangle_rad = torch.tensor(3.141616 / 4)\nrot_x = So3.rot_x(angle_rad)\nrot_z = So3.rot_z(angle_rad)\nrot = rot_x * rot_z\nprint(rot)\n\nParameter containing:\ntensor([ 0.8536, 0.3536, -0.1464, 0.3536], requires_grad=True)\n\n\n\n# apply the rotation to the mesh points\n# TODO: this should work as `rot * mesh_view`\npoints_rot = stack([rot * x for x in mesh_view.view(-1, 3)]).detach()\npoints_rot = Vector3(points_rot)\n\n\nx_rot = points_rot.x.ravel().detach().cpu().numpy().tolist()\ny_rot = points_rot.y.ravel().detach().cpu().numpy().tolist()\nz_rot = points_rot.z.ravel().detach().cpu().numpy().tolist()\n\nfig = px.scatter_3d(\n dict(\n x=x_view + x_rot,\n y=y_view + y_rot,\n z=z_view + z_rot,\n category=[\"view\"] * len(x_view) + [\"rotated\"] * len(x_rot),\n ),\n \"x\",\n \"y\",\n \"z\",\n color=\"category\",\n)\nfig.show()\n\n\n \n\n\n\n# estimate the plane from the rotated points\nplane_in_ground_fit: Hyperplane = fit_plane(points_rot)\nprint(plane_in_ground_fit)\n\nNormal: x: -0.0002799616486299783\ny: 0.7073221206665039\nz: -0.7068911790847778\nOffset: 0.094654381275177\n\n\n\n# project the original points to the estimated plane\npoints_proj: Vector3 = plane_in_ground_fit.projection(mesh_view.view(-1, 3))\n\n\nx_proj = points_proj.x.ravel().detach().cpu().numpy().tolist()\ny_proj = points_proj.y.ravel().detach().cpu().numpy().tolist()\nz_proj = points_proj.z.ravel().detach().cpu().numpy().tolist()\ncategories = [\"view\"] * len(x_view) + [\"rotated\"] * len(x_rot) + [\"projection\"] * len(x_proj)\nfig = px.scatter_3d(\n dict(\n x=x_view + x_rot + x_proj,\n y=y_view + y_rot + y_proj,\n z=z_view + z_rot + z_proj,\n category=categories,\n ),\n \"x\",\n \"y\",\n \"z\",\n color=\"category\",\n)\nfig.show()" + }, + { + "objectID": "nbs/descriptors_matching.html", + "href": "nbs/descriptors_matching.html", + "title": "Image matching example with kornia local features: TFeat, MKD, OriNet, HyNet and OpenCV detector", + "section": "", + "text": "It is possible to use OpenCV local features, such as SIFT with kornia via kornia_moons library.\nFirst, we will install everything needed:\n%%capture\n!pip install kornia\n!pip install kornia-rs\n!pip install kornia_moons\n!pip install opencv-python --upgrade\nNow let’s download an image pair\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl_a = \"https://github.com/kornia/data/raw/main/matching/kn_church-2.jpg\"\nurl_b = \"https://github.com/kornia/data/raw/main/matching/kn_church-8.jpg\"\ndownload_image(url_a)\ndownload_image(url_b)\n\n'kn_church-8.jpg'\nFirst, we will define image matching pipeline with OpenCV SIFT features. We will also use kornia for the state-of-the-art match filtering – Lowe ratio + mutual nearest neighbor check." + }, + { + "objectID": "nbs/descriptors_matching.html#using-opencv-sift-as-is-and-converting-it-manually", + "href": "nbs/descriptors_matching.html#using-opencv-sift-as-is-and-converting-it-manually", + "title": "Image matching example with kornia local features: TFeat, MKD, OriNet, HyNet and OpenCV detector", + "section": "Using OpenCV SIFT as is and converting it manually", + "text": "Using OpenCV SIFT as is and converting it manually\n\ndef sift_matching(fname1, fname2):\n img1 = cv2.cvtColor(cv2.imread(fname1), cv2.COLOR_BGR2RGB)\n print(img1.shape)\n img2 = cv2.cvtColor(cv2.imread(fname2), cv2.COLOR_BGR2RGB)\n\n # OpenCV SIFT\n sift = cv2.SIFT_create(8000)\n kps1, descs1 = sift.detectAndCompute(img1, None)\n kps2, descs2 = sift.detectAndCompute(img2, None)\n\n # Converting to kornia for matching via AdaLAM\n lafs1 = laf_from_opencv_SIFT_kpts(kps1)\n lafs2 = laf_from_opencv_SIFT_kpts(kps2)\n dists, idxs = KF.match_adalam(\n torch.from_numpy(descs1), torch.from_numpy(descs2), lafs1, lafs2, hw1=img1.shape[:2], hw2=img2.shape[:2]\n )\n\n # Converting back to kornia via to use OpenCV MAGSAC++\n tentatives = cv2_matches_from_kornia(dists, idxs)\n src_pts = np.float32([kps1[m.queryIdx].pt for m in tentatives]).reshape(-1, 2)\n dst_pts = np.float32([kps2[m.trainIdx].pt for m in tentatives]).reshape(-1, 2)\n\n F, inliers_mask = cv2.findFundamentalMat(src_pts, dst_pts, cv2.USAC_MAGSAC, 0.25, 0.999, 100000)\n # Drawing matches using kornia_moons\n draw_LAF_matches(\n lafs1,\n lafs2,\n idxs,\n img1,\n img2,\n inliers_mask.astype(bool).reshape(-1),\n draw_dict={\"inlier_color\": (0.2, 1, 0.2), \"tentative_color\": None, \"feature_color\": None, \"vertical\": False},\n )\n print(f\"{inliers_mask.sum()} inliers found\")\n return\n\n\nfname1 = \"kn_church-2.jpg\"\nfname2 = \"kn_church-8.jpg\"\nsift_matching(fname1, fname2)\n\n11 inliers found" + }, + { + "objectID": "nbs/descriptors_matching.html#using-opencv-sift-with-kornia-matcher", + "href": "nbs/descriptors_matching.html#using-opencv-sift-with-kornia-matcher", + "title": "Image matching example with kornia local features: TFeat, MKD, OriNet, HyNet and OpenCV detector", + "section": "Using OpenCV SIFT with kornia matcher", + "text": "Using OpenCV SIFT with kornia matcher\nNow we need to define a function to feed the OpenCV keypoints into local descriptors from kornia. Luckily, that is easy with the help of kornia_moons.\n\ndef get_matching_kpts(lafs1, lafs2, idxs):\n src_pts = KF.get_laf_center(lafs1).view(-1, 2)[idxs[:, 0]].detach().cpu().numpy()\n dst_pts = KF.get_laf_center(lafs2).view(-1, 2)[idxs[:, 1]].detach().cpu().numpy()\n return src_pts, dst_pts\n\n\ndef sift_korniadesc_matching(fname1, fname2, descriptor):\n timg1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\n timg2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\n\n sift = OpenCVDetectorKornia(cv2.SIFT_create(8000))\n local_feature = KF.LocalFeature(sift, KF.LAFDescriptor(descriptor))\n\n lafs1, resps1, descs1 = local_feature(K.color.rgb_to_grayscale(timg1))\n lafs2, resps2, descs2 = local_feature(K.color.rgb_to_grayscale(timg2))\n\n dists, idxs = KF.match_adalam(descs1[0], descs2[0], lafs1, lafs2, hw1=timg1.shape[2:], hw2=timg2.shape[2:])\n\n src_pts, dst_pts = get_matching_kpts(lafs1, lafs2, idxs)\n F, inliers_mask = cv2.findFundamentalMat(src_pts, dst_pts, cv2.USAC_MAGSAC, 0.25, 0.999, 100000)\n draw_LAF_matches(\n lafs1,\n lafs2,\n idxs,\n K.tensor_to_image(timg1),\n K.tensor_to_image(timg2),\n inliers_mask.astype(bool),\n draw_dict={\"inlier_color\": (0.2, 1, 0.2), \"tentative_color\": None, \"feature_color\": None, \"vertical\": False},\n )\n print(f\"{inliers_mask.sum()} inliers found\")\n\nNow let’s try kornia new descriptors – MKD and TFeat. MKD is one of the best handcrafted local feature descriptors, presented in IJCV 2018 paper “Understanding and Improving Kernel Local Descriptors”.\n\nmkd = KF.MKDDescriptor(32)\nwith torch.inference_mode():\n sift_korniadesc_matching(fname1, fname2, mkd)\n\n12 inliers found\n\n\n\n\n\nResult seems 2 inliers better than with SIFTs. Let’s try TFeat - lightweight deep learning-based descriptor from BMVC 2016 paper “Learning local feature descriptors with triplets and shallow convolutional neural networks”\n\ntfeat = KF.TFeat(True)\nwith torch.inference_mode():\n sift_korniadesc_matching(fname1, fname2, tfeat)\n\n22 inliers found" + }, + { + "objectID": "nbs/descriptors_matching.html#good-old-hardnet", + "href": "nbs/descriptors_matching.html#good-old-hardnet", + "title": "Image matching example with kornia local features: TFeat, MKD, OriNet, HyNet and OpenCV detector", + "section": "Good old HardNet", + "text": "Good old HardNet\nIn the worst-case we can always fall back to the HardNet – more robust, but also slower than TFeat and MKD, descriptor\n\ndevice = torch.device(\"cpu\")\nhardnet = KF.HardNet(True).eval()\nwith torch.inference_mode():\n sift_korniadesc_matching(fname1, fname2, hardnet)\n\n26 inliers found" + }, + { + "objectID": "nbs/descriptors_matching.html#affnet", + "href": "nbs/descriptors_matching.html#affnet", + "title": "Image matching example with kornia local features: TFeat, MKD, OriNet, HyNet and OpenCV detector", + "section": "AffNet", + "text": "AffNet\nWe haven’t done yet! SIFT detector is a great tool, but we can improve it by using deep learned affine shape estimation – AffNet. You can do it, using a single function wrapper - OpenCVDetectorWithAffNetKornia.\n\ndef siftaffnet_korniadesc_matching(fname1, fname2, descriptor):\n timg1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\n timg2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\n\n # Magic is here\n sift = OpenCVDetectorWithAffNetKornia(cv2.SIFT_create(8000))\n\n local_feature = KF.LocalFeature(sift, KF.LAFDescriptor(descriptor))\n with torch.inference_mode():\n lafs1, resps1, descs1 = local_feature(K.color.rgb_to_grayscale(timg1))\n lafs2, resps2, descs2 = local_feature(K.color.rgb_to_grayscale(timg2))\n dists, idxs = KF.match_adalam(descs1[0], descs2[0], lafs1, lafs2, hw1=timg1.shape[2:], hw2=timg2.shape[2:])\n\n src_pts, dst_pts = get_matching_kpts(lafs1, lafs2, idxs)\n\n F, inliers_mask = cv2.findFundamentalMat(src_pts, dst_pts, cv2.USAC_MAGSAC, 0.25, 0.999, 100000)\n draw_LAF_matches(\n lafs1,\n lafs2,\n idxs,\n K.tensor_to_image(timg1),\n K.tensor_to_image(timg2),\n inliers_mask.astype(bool),\n draw_dict={\"inlier_color\": (0.2, 1, 0.2), \"tentative_color\": None, \"feature_color\": None, \"vertical\": False},\n )\n print(f\"{inliers_mask.sum()} inliers found\")\n\n\nsiftaffnet_korniadesc_matching(fname1, fname2, hardnet)\n\n39 inliers found" + }, + { + "objectID": "nbs/descriptors_matching.html#hynet", + "href": "nbs/descriptors_matching.html#hynet", + "title": "Image matching example with kornia local features: TFeat, MKD, OriNet, HyNet and OpenCV detector", + "section": "HyNet", + "text": "HyNet\n\nsiftaffnet_korniadesc_matching(fname1, fname2, KF.HyNet(True).eval())\n\n47 inliers found" + }, + { + "objectID": "nbs/descriptors_matching.html#sosnet", + "href": "nbs/descriptors_matching.html#sosnet", + "title": "Image matching example with kornia local features: TFeat, MKD, OriNet, HyNet and OpenCV detector", + "section": "SOSNet", + "text": "SOSNet\n\nsiftaffnet_korniadesc_matching(fname1, fname2, KF.SOSNet(True).eval())\n\n48 inliers found" + }, + { + "objectID": "nbs/descriptors_matching.html#hardnet8", + "href": "nbs/descriptors_matching.html#hardnet8", + "title": "Image matching example with kornia local features: TFeat, MKD, OriNet, HyNet and OpenCV detector", + "section": "HardNet8", + "text": "HardNet8\n\nsiftaffnet_korniadesc_matching(fname1, fname2, KF.HardNet8(True).eval())\n\n38 inliers found" + }, + { + "objectID": "nbs/morphology_101.html", + "href": "nbs/morphology_101.html", + "title": "Introduction to Morphological Operators", + "section": "", + "text": "By the end, you will be able to use morphological operations as easy as:\nnew_image = morph.operation(original_image, structuring_element)\nBut first things first, let’s prepare the environment." + }, + { + "objectID": "nbs/morphology_101.html#download-kornia", + "href": "nbs/morphology_101.html#download-kornia", + "title": "Introduction to Morphological Operators", + "section": "Download Kornia", + "text": "Download Kornia\nIf you don’t have Kornia installed, you can download it using pip.\n\n%%capture\n!pip install kornia\n!pip install kornia-rs" + }, + { + "objectID": "nbs/morphology_101.html#prepare-the-image", + "href": "nbs/morphology_101.html#prepare-the-image", + "title": "Introduction to Morphological Operators", + "section": "Prepare the image", + "text": "Prepare the image\nWith kornia.morphology, you can apply morphological operators in 3 channel color images. Besides, all operators are differentiable. Let’s download the image\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1].split(\"?\")[0] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\ndownload_image(\n \"https://image.shutterstock.com/image-photo/portrait-surprised-cat-scottish-straight-260nw-499196506.jpg\", \"img.jpg\"\n)\n\n'img.jpg'\n\n\n\nImports and read the image\n\nimport kornia as K\nimport torch\n\ndevice = \"cpu\" # 'cuda:0' for GPU\n\n\nimg_t = K.io.load_image(\"img.jpg\", K.io.ImageLoadType.RGB32, device=device)[None, ...]\n\n\n\nStructuring element\nWe have the original image ready to go, now we need the second part in the operation, the structuring element (aka Kernel).\nThe kernel must be a 2-dim tensor with odd sides, i.e. 3x3.\n\nkernel = torch.tensor([[0, 1, 0], [1, 1, 1], [0, 1, 0]]).to(device)\n\n\n\nMaking plots!\nIn this tutorial we are gonna compare the images before and after transforming them.\nIt make sense to create a function to plot and see the changes!\n\nimport matplotlib.pyplot as plt\nfrom matplotlib import rcParams\n\n\ndef plot_morph_image(tensor):\n # kornia.tensor_to_image\n image = K.tensor_to_image(tensor.squeeze(0)) # Tensor to image\n\n # Plot before-after\n rcParams[\"figure.figsize\"] = 20, 20\n fig, ax = plt.subplots(1, 2)\n ax[0].axis(\"off\")\n ax[0].imshow(K.tensor_to_image(img_t))\n ax[1].axis(\"off\")\n ax[1].imshow(image)" + }, + { + "objectID": "nbs/morphology_101.html#morphology", + "href": "nbs/morphology_101.html#morphology", + "title": "Introduction to Morphological Operators", + "section": "Morphology", + "text": "Morphology\nThe main goal of kornia.morphology is that you could easily implement several morphological operator as follows:\nnew_image = morph.operation(original_image, structuring_element)\nLet’s check them all!\n\nDilation\n\nfrom kornia import morphology as morph\n\ndilated_image = morph.dilation(img_t, kernel) # Dilation\nplot_morph_image(dilated_image) # Plot\n\n\n\n\n\n\nErosion\n\neroded_image = morph.erosion(img_t, kernel) # Erosion\nplot_morph_image(eroded_image) # Plot\n\n\n\n\n\n\nOpen\n\nopened_image = morph.opening(img_t, kernel) # Open\nplot_morph_image(opened_image)\n\n\n\n\n\n\nClose\n\nclosed_image = morph.closing(img_t, kernel) # Close\nplot_morph_image(closed_image) # Plot\n\n\n\n\n\n\nMorphological Gradient\n\ngraded_image = morph.gradient(img_t, kernel) # Morphological gradient\nplot_morph_image(1.0 - graded_image)\n\n\n\n\n\n\nBottom Hat\n\nbottom_image = morph.bottom_hat(img_t, kernel) # Black Hat\nplot_morph_image(1.0 - bottom_image)\n\n\n\n\n\n\nTop Hat\n\ntoph_image = morph.top_hat(img_t, kernel) # Top Hat\nplot_morph_image(1.0 - toph_image)" + }, + { + "objectID": "nbs/morphology_101.html#conclusion", + "href": "nbs/morphology_101.html#conclusion", + "title": "Introduction to Morphological Operators", + "section": "Conclusion", + "text": "Conclusion\nAnd that’s it!\nNow you know how to use Kornia to apply differentiable morphological operations in your PyTorch pipeline.\nMany thanks for using Kornia, and have fun!" + }, + { + "objectID": "nbs/homography.html", + "href": "nbs/homography.html", + "title": "Image Alignment by Homography Optimization", + "section": "", + "text": "%%capture\n!pip install kornia\n!pip install kornia-rs\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\ndownload_image(\"https://github.com/kornia/data/raw/main/homography/H1to2p\")\ndownload_image(\"https://github.com/kornia/data/raw/main/homography/img1.ppm\")\ndownload_image(\"https://github.com/kornia/data/raw/main/homography/img2.ppm\")\n\n'img2.ppm'\nImport needed libraries\nimport os\nfrom typing import List\n\nimport kornia as K\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom kornia.geometry import resize\n\n# computer vision libs :D\nDefine the hyper parameters to perform the online optimisation\nlearning_rate: float = 1e-3 # the gradient optimisation update step\nnum_iterations: int = 100 # the number of iterations until convergence\nnum_levels: int = 6 # the total number of image pyramid levels\nerror_tol: float = 1e-8 # the optimisation error tolerance\n\nlog_interval: int = 100 # print log every N iterations\ndevice = K.utils.get_cuda_or_mps_device_if_available()\nprint(\"Using \", device)\n\nUsing cpu\nDefine a container to hold the homography as a nn.Parameter so that cen be used by the autograd within the torch.optim framework.\nWe initialize the homography with the identity transformation.\nclass MyHomography(nn.Module):\n def __init__(self) -> None:\n super().__init__()\n self.homography = nn.Parameter(torch.Tensor(3, 3))\n self.reset_parameters()\n\n def reset_parameters(self):\n torch.nn.init.eye_(self.homography)\n\n def forward(self) -> torch.Tensor:\n return torch.unsqueeze(self.homography, dim=0) # 1x3x3\nRead the images and the ground truth homograpy to convert to tensor. In addition, we normalize the homography in order to smooth the gradiens during the optimisation process.\nimg_src: torch.Tensor = K.io.load_image(\"img1.ppm\", K.io.ImageLoadType.RGB32, device=device)[None, ...]\nimg_dst: torch.Tensor = K.io.load_image(\"img2.ppm\", K.io.ImageLoadType.RGB32, device=device)[None, ...]\nprint(img_src.shape)\nprint(img_dst.shape)\n\ndst_homo_src_gt = np.loadtxt(\"H1to2p\")\ndst_homo_src_gt = torch.from_numpy(dst_homo_src_gt)[None].float().to(device)\nprint(dst_homo_src_gt.shape)\nprint(dst_homo_src_gt)\n\nheight, width = img_src.shape[-2:]\n\n# warp image in normalized coordinates\nnormal_transform_pixel: torch.Tensor = K.geometry.normal_transform_pixel(height, width, device=device)\n\ndst_homo_src_gt_norm: torch.Tensor = normal_transform_pixel @ dst_homo_src_gt @ torch.inverse(normal_transform_pixel)\n\nimg_src_to_dst_gt: torch.Tensor = K.geometry.homography_warp(img_src, torch.inverse(dst_homo_src_gt_norm), (height, width))\n\nimg_src_vis: np.ndarray = K.utils.tensor_to_image(K.color.bgr_to_rgb(img_src))\nimg_dst_vis: np.ndarray = K.utils.tensor_to_image(K.color.bgr_to_rgb(img_dst))\nimg_src_to_dst_gt_vis: np.ndarray = K.utils.tensor_to_image(K.color.bgr_to_rgb(img_src_to_dst_gt))\n\ntorch.Size([1, 3, 640, 800])\ntorch.Size([1, 3, 640, 800])\ntorch.Size([1, 3, 3])\ntensor([[[ 8.7977e-01, 3.1245e-01, -3.9431e+01],\n [-1.8389e-01, 9.3847e-01, 1.5316e+02],\n [ 1.9641e-04, -1.6015e-05, 1.0000e+00]]])\nShow the source image, the target and the source image warped to the target using the ground truth homography transformation.\nfig, (ax1, ax2, ax3) = plt.subplots(1, 3, sharey=True)\nfig.set_figheight(15)\nfig.set_figwidth(15)\n\nax1.imshow(img_src_vis)\nax1.set_title(\"Source image\")\n\nax2.imshow(img_dst_vis)\nax2.set_title(\"Destination image\")\n\nax3.imshow(img_src_to_dst_gt_vis)\nax3.set_title(\"Source to Destination image\")\nplt.show()\nInitialize the homography warper and pass the parameters to the torch.optim.Adam optimizer to perform an online gradient descent optimisation to approximate the mapping transformation between the two images.\n# create homography parameters\ndst_homo_src = MyHomography().to(device)\n\n# create optimizer\noptimizer = optim.Adam(dst_homo_src.parameters(), lr=learning_rate)\n\n# send data to device\nimg_src, img_dst = img_src.to(device), img_dst.to(device)\nIn order to perform the online optimisation, we will apply a know fine-to-coarse strategy. For this reason, we precompute a gaussian pyramid from each image with a certain number of levels.\n### compute Gaussian Pyramid\n\n\ndef get_gaussian_pyramid(img: torch.Tensor, num_levels: int) -> List[torch.Tensor]:\n r\"\"\"Utility function to compute a gaussian pyramid.\"\"\"\n pyramid = []\n pyramid.append(img)\n for _ in range(num_levels - 1):\n img_curr = pyramid[-1]\n img_down = K.geometry.pyrdown(img_curr)\n pyramid.append(img_down)\n return pyramid\n\n\n# compute the gaussian pyramids\nimg_src_pyr: List[torch.Tensor] = get_gaussian_pyramid(img_src, num_levels)\nimg_dst_pyr: List[torch.Tensor] = get_gaussian_pyramid(img_dst, num_levels)" + }, + { + "objectID": "nbs/homography.html#main-optimization-loop", + "href": "nbs/homography.html#main-optimization-loop", + "title": "Image Alignment by Homography Optimization", + "section": "Main optimization loop", + "text": "Main optimization loop\nDefine the loss function to minimize the photometric error at each pyramid level:\n$ L = |I_{ref} - (I_{dst}, H_{ref}^{dst}))|$\n\ndef compute_scale_loss(\n img_src: torch.Tensor,\n img_dst: torch.Tensor,\n dst_homo_src: nn.Module,\n optimizer: torch.optim,\n num_iterations: int,\n error_tol: float,\n) -> torch.Tensor:\n assert len(img_src.shape) == len(img_dst.shape), (img_src.shape, img_dst.shape)\n\n # init loop parameters\n loss_tol = torch.tensor(error_tol)\n loss_prev = torch.finfo(img_src.dtype).max\n\n for i in range(num_iterations):\n # create homography warper\n src_homo_dst: torch.Tensor = torch.inverse(dst_homo_src)\n\n _height, _width = img_src.shape[-2:]\n warper = K.geometry.HomographyWarper(_height, _width)\n img_src_to_dst = warper(img_src, src_homo_dst)\n\n # compute and mask loss\n loss = F.l1_loss(img_src_to_dst, img_dst, reduction=\"none\") # 1x3xHxW\n\n ones = warper(torch.ones_like(img_src), src_homo_dst)\n loss = loss.masked_select(ones > 0.9).mean()\n\n # compute gradient and update optimizer parameters\n optimizer.zero_grad()\n loss.backward()\n optimizer.step()\n\nRun the main body loop to warp the images from each pyramid level and evaluate the loss to perform gradient update.\n\n# pyramid loop\n\n\nfor iter_idx in range(num_levels):\n # get current pyramid data\n scale: int = (num_levels - 1) - iter_idx\n img_src = img_src_pyr[scale]\n img_dst = img_dst_pyr[scale]\n\n # compute scale loss\n compute_scale_loss(img_src, img_dst, dst_homo_src(), optimizer, num_iterations, error_tol)\n\n print(f\"Optimization iteration: {iter_idx}/{num_levels}\")\n\n # merge warped and target image for visualization\n h, w = img_src.shape[-2:]\n warper = K.geometry.HomographyWarper(h, w)\n img_src_to_dst = warper(img_src, torch.inverse(dst_homo_src()))\n img_src_to_dst_gt = warper(img_src, torch.inverse(dst_homo_src_gt_norm))\n\n # compute the reprojection error\n error = F.l1_loss(img_src_to_dst, img_src_to_dst_gt, reduction=\"none\")\n print(f\"Reprojection error: {error.mean()}\")\n\n # show data\n img_src_vis = K.utils.tensor_to_image(K.color.bgr_to_rgb(img_src))\n img_dst_vis = K.utils.tensor_to_image(K.color.bgr_to_rgb(img_dst))\n img_src_to_dst_merge = 0.65 * img_src_to_dst + 0.35 * img_dst\n img_src_to_dst_vis = K.utils.tensor_to_image(K.color.bgr_to_rgb(img_src_to_dst_merge))\n img_src_to_dst_gt_vis = K.utils.tensor_to_image(K.color.bgr_to_rgb(img_src_to_dst_gt))\n\n error_sum = error.mean(dim=1, keepdim=True)\n error_vis = K.utils.tensor_to_image(error_sum)\n\n # show the original images at each scale level, the result of warping using\n # the homography at moment, and the estimated error against the GT homography.\n\n %matplotlib inline\n fig, (ax1, ax2, ax3, ax4, ax5) = plt.subplots(1, 5, sharey=False)\n fig.set_figheight(15)\n fig.set_figwidth(15)\n\n ax1.imshow(img_src_vis)\n ax1.set_title(\"Source image\")\n\n ax2.imshow(img_dst_vis)\n ax2.set_title(\"Destination image\")\n\n ax3.imshow(img_src_to_dst_vis)\n ax3.set_title(\"Source to Destination image\")\n\n ax4.imshow(img_src_to_dst_gt_vis)\n ax4.set_title(\"Source to Destination image GT\")\n\n ax5.imshow(error_vis, cmap=\"gray\", vmin=0, vmax=1)\n ax5.set_title(\"Error\")\n plt.show()\n\nOptimization iteration: 0/6\nReprojection error: 0.17220830917358398\nOptimization iteration: 1/6\nReprojection error: 0.11565501242876053\nOptimization iteration: 2/6\nReprojection error: 0.018368173390626907\nOptimization iteration: 3/6\nReprojection error: 0.013175368309020996\nOptimization iteration: 4/6\nReprojection error: 0.008068887516856194\nOptimization iteration: 5/6\nReprojection error: 0.005315570626407862" + }, + { + "objectID": "nbs/image_matching_lightglue.html", + "href": "nbs/image_matching_lightglue.html", + "title": "Image matching example with LightGlue and DISK", + "section": "", + "text": "First, we will install everything needed:\n\nfresh version of kornia for DISK\nfresh version of OpenCV for MAGSAC++ geometry estimation\nkornia_moons for the conversions and visualization\n\nDocs: kornia.feature.DISK\n\n%%capture\n!pip install kornia\n!pip install kornia-rs\n!pip install kornia_moons --no-deps\n!pip install opencv-python --upgrade\n\nNow let’s download an image pair\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl_a = \"https://github.com/kornia/data/raw/main/matching/kn_church-2.jpg\"\nurl_b = \"https://github.com/kornia/data/raw/main/matching/kn_church-8.jpg\"\ndownload_image(url_a)\ndownload_image(url_b)\n\n'kn_church-8.jpg'\n\n\nFirst, imports.\n\nimport cv2\nimport kornia as K\nimport kornia.feature as KF\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport torch\nfrom kornia.feature.adalam import AdalamFilter\nfrom kornia_moons.viz import *\n\ndevice = K.utils.get_cuda_or_mps_device_if_available()\nprint(device)\n\nmps\n\n\nHere we show how to use LightGlue with provided kornia LightGlueMatcher interface\n\n# %%capture\nfname1 = \"kn_church-2.jpg\"\nfname2 = \"kn_church-8.jpg\"\n\nlg_matcher = KF.LightGlueMatcher(\"disk\").eval().to(device)\n\n\nimg1 = K.io.load_image(fname1, K.io.ImageLoadType.RGB32, device=device)[None, ...]\nimg2 = K.io.load_image(fname2, K.io.ImageLoadType.RGB32, device=device)[None, ...]\n\nnum_features = 2048\ndisk = KF.DISK.from_pretrained(\"depth\").to(device)\n\nhw1 = torch.tensor(img1.shape[2:], device=device)\nhw2 = torch.tensor(img2.shape[2:], device=device)\n\n\nwith torch.inference_mode():\n inp = torch.cat([img1, img2], dim=0)\n features1, features2 = disk(inp, num_features, pad_if_not_divisible=True)\n kps1, descs1 = features1.keypoints, features1.descriptors\n kps2, descs2 = features2.keypoints, features2.descriptors\n lafs1 = KF.laf_from_center_scale_ori(kps1[None], torch.ones(1, len(kps1), 1, 1, device=device))\n lafs2 = KF.laf_from_center_scale_ori(kps2[None], torch.ones(1, len(kps2), 1, 1, device=device))\n dists, idxs = lg_matcher(descs1, descs2, lafs1, lafs2, hw1=hw1, hw2=hw2)\n\n\nprint(f\"{idxs.shape[0]} tentative matches with DISK LightGlue\")\n\nLoaded LightGlue model\n\n\nAnd here the same with original LightGlue object\n\nlg = KF.LightGlue(\"disk\").to(device).eval()\n\nimage0 = {\n \"keypoints\": features1.keypoints[None],\n \"descriptors\": features1.descriptors[None],\n \"image_size\": torch.tensor(img1.shape[-2:][::-1]).view(1, 2).to(device),\n}\nimage1 = {\n \"keypoints\": features2.keypoints[None],\n \"descriptors\": features2.descriptors[None],\n \"image_size\": torch.tensor(img2.shape[-2:][::-1]).view(1, 2).to(device),\n}\n\nwith torch.inference_mode():\n out = lg({\"image0\": image0, \"image1\": image1})\n idxs = out[\"matches\"][0]\n print(f\"{idxs.shape[0]} tentative matches with DISK LightGlue\")\n\n724 tentative matches with DISK LightGlue\n\n\nRANSAC to get fundamental matrix\n\ndef get_matching_keypoints(kp1, kp2, idxs):\n mkpts1 = kp1[idxs[:, 0]]\n mkpts2 = kp2[idxs[:, 1]]\n return mkpts1, mkpts2\n\n\nmkpts1, mkpts2 = get_matching_keypoints(kps1, kps2, idxs)\n\nFm, inliers = cv2.findFundamentalMat(\n mkpts1.detach().cpu().numpy(), mkpts2.detach().cpu().numpy(), cv2.USAC_MAGSAC, 1.0, 0.999, 100000\n)\ninliers = inliers > 0\nprint(f\"{inliers.sum()} inliers with DISK\")\n\nLet’s draw the inliers in green and tentative correspondences in yellow\n\ndraw_LAF_matches(\n KF.laf_from_center_scale_ori(kps1[None].cpu()),\n KF.laf_from_center_scale_ori(kps2[None].cpu()),\n idxs.cpu(),\n K.tensor_to_image(img1.cpu()),\n K.tensor_to_image(img2.cpu()),\n inliers,\n draw_dict={\"inlier_color\": (0.2, 1, 0.2), \"tentative_color\": (1, 1, 0.2, 0.3), \"feature_color\": None, \"vertical\": False},\n)" + }, + { + "objectID": "nbs/connected_components.html", + "href": "nbs/connected_components.html", + "title": "Connected Components Algorithm", + "section": "", + "text": "%%capture\n!pip install kornia\n!pip install kornia-rs\n\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://github.com/kornia/data/raw/main/cells_binary.png\"\ndownload_image(url)\n\n'cells_binary.png'\n\n\n\nfrom __future__ import annotations\n\nimport kornia as K\nimport matplotlib.pyplot as plt\nimport torch\n\nWe define utility functions to visualize the segmentation properly\n\ndef create_random_labels_map(classes: int) -> dict[int, tuple[int, int, int]]:\n labels_map: Dict[int, Tuple[int, int, int]] = {}\n for i in classes:\n labels_map[i] = torch.randint(0, 255, (3,))\n labels_map[0] = torch.zeros(3)\n return labels_map\n\n\ndef labels_to_image(img_labels: torch.Tensor, labels_map: Dict[int, Tuple[int, int, int]]) -> torch.Tensor:\n \"\"\"Function that given an image with labels ids and their pixels intrensity mapping, creates a RGB\n representation for visualisation purposes.\"\"\"\n assert len(img_labels.shape) == 2, img_labels.shape\n H, W = img_labels.shape\n out = torch.empty(3, H, W, dtype=torch.uint8)\n for label_id, label_val in labels_map.items():\n mask = img_labels == label_id\n for i in range(3):\n out[i].masked_fill_(mask, label_val[i])\n return out\n\n\ndef show_components(img, labels):\n color_ids = torch.unique(labels)\n labels_map = create_random_labels_map(color_ids)\n labels_img = labels_to_image(labels, labels_map)\n\n fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 12))\n\n # Showing Original Image\n ax1.imshow(img)\n ax1.axis(\"off\")\n ax1.set_title(\"Orginal Image\")\n\n # Showing Image after Component Labeling\n ax2.imshow(labels_img.permute(1, 2, 0).squeeze().numpy())\n ax2.axis(\"off\")\n ax2.set_title(\"Component Labeling\")\n\n plt.show()\n\nWe load the image using Kornia\n\nimg_t = K.io.load_image(\"cells_binary.png\", K.io.ImageLoadType.GRAY32)[None, ...]\n\nprint(img_t.shape)\n\ntorch.Size([1, 1, 602, 602])\n\n\nApply the Connected-component labelling algorithm using the kornia.contrib.connected_components functionality. The num_iterations parameter will control the total number of iterations of the algorithm to finish until it converges to a solution.\n\nlabels_out = K.contrib.connected_components(img_t, num_iterations=150)\nprint(labels_out.shape)\n\ntorch.Size([1, 1, 602, 602])\n\n\n\nshow_components(img_t.numpy().squeeze(), labels_out.squeeze())\n\n\n\n\nWe can also explore the labels\n\nprint(torch.unique(labels_out))\n\ntensor([ 0., 13235., 24739., 31039., 32177., 44349., 59745., 61289.,\n 66209., 69449., 78869., 94867., 101849., 102217., 102319., 115227.,\n 115407., 137951., 138405., 150047., 158715., 162179., 170433., 170965.,\n 174279., 177785., 182867., 210145., 212647., 215451., 216119., 221291.,\n 222367., 226183., 226955., 248757., 252823., 255153., 263337., 265505.,\n 270299., 270649., 277725., 282775., 296897., 298545., 299793., 300517.,\n 313961., 316217., 321259., 322235., 335599., 337037., 340289., 347363.,\n 352235., 352721., 360801., 360903., 360965., 361073., 361165., 361197.])" + }, + { + "objectID": "nbs/face_detection.html", + "href": "nbs/face_detection.html", + "title": "Face Detection and blurring", + "section": "", + "text": "%%capture\n!pip install kornia\n!pip install kornia-rs\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://raw.githubusercontent.com/kornia/data/main/crowd.jpg\"\ndownload_image(url)\nImport the needed libraries\nimport cv2\nimport kornia as K\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport torch\nfrom kornia.contrib import FaceDetector, FaceDetectorResult\n\n# select the device and type\ndevice = torch.device(\"cpu\") # use 'cuda:0'\ndtype = torch.float32\nRead the image with kornia\n# load the image (face detector expects a image in rage 0-255 (8 bits))\nimg = K.io.load_image(\"crowd.jpg\", K.io.ImageLoadType.RGB8, device=device)[None, ...].to(dtype=dtype) # BxCxHxW\nimg_vis = K.tensor_to_image(img.byte()) # to later visualize\nplt.figure(figsize=(8, 8))\nplt.imshow(img_vis)\nplt.axis(\"off\")\nplt.show()\nCreate the FaceDetector object and apply to the image\n# create the detector and find the faces !\nface_detection = FaceDetector().to(device, dtype)\n\nwith torch.no_grad():\n dets = face_detection(img)\n\n# to decode later the detections\ndets = [FaceDetectorResult(o) for o in dets]\nCreate a function to crop the faces from the original image and apply blurring using the gaussian_blurd2d operator.\nAlternatively, explore other blur operator in kornia.filters.\n# blurring paramters\nk: int = 21 # kernel_size\ns: float = 35.0 # sigma\n\n\ndef apply_blur_face(img: torch.Tensor, img_vis: np.ndarray, x1, y1, x2, y2):\n # crop the face\n roi = img[..., y1:y2, x1:x2]\n\n # apply blurring and put back to the visualisation image\n roi = K.filters.gaussian_blur2d(roi, (k, k), (s, s))\n img_vis[y1:y2, x1:x2] = K.tensor_to_image(roi)\nLet draw the detections and save/visualize the image\nfor b in dets:\n # draw face bounding box around each detected face\n top_left = b.top_left.int().tolist()\n bottom_right = b.bottom_right.int().tolist()\n scores = b.score.tolist()\n\n for score, tp, br in zip(scores, top_left, bottom_right):\n x1, y1 = tp\n x2, y2 = br\n\n if score < 0.7:\n continue # skip detection with low score\n img_vis = cv2.rectangle(img_vis, (x1, y1), (x2, y2), (0, 255, 0), 2)\n\n # blur the detected faces\n apply_blur_face(img, img_vis, x1, y1, x2, y2)\n\nplt.figure(figsize=(8, 8))\nplt.imshow(img_vis)\nplt.axis(\"off\")\nplt.show()" + }, + { + "objectID": "nbs/face_detection.html#play-with-the-real-time-demo", + "href": "nbs/face_detection.html#play-with-the-real-time-demo", + "title": "Face Detection and blurring", + "section": "Play with the Real Time Demo", + "text": "Play with the Real Time Demo\nYou can achieve 60 FPS in CPU using a standard WebCam.\nSee: https://github.com/kornia/kornia/blob/master/examples/face_detection/main_video.py\n\nfrom IPython.display import YouTubeVideo\n\nYouTubeVideo(\"hzQroGp5FSQ\")" + }, + { + "objectID": "nbs/filtering_edges.html", + "href": "nbs/filtering_edges.html", + "title": "Edge Detection", + "section": "", + "text": "%%capture\n!pip install kornia\n!pip install kornia-rs\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://github.com/kornia/data/raw/main/doraemon.png\"\ndownload_image(url)\n\n'doraemon.png'\nimport cv2\nimport kornia as K\nimport numpy as np\nimport torch\nimport torchvision\nfrom matplotlib import pyplot as plt\nWe use Kornia to load an image to memory represented in a torch.tensor\nx_rgb: torch.Tensor = K.io.load_image(\"doraemon.png\", K.io.ImageLoadType.RGB32)[None, ...] # BxCxHxW\n\nx_gray = K.color.rgb_to_grayscale(x_rgb)\ndef imshow(input: torch.Tensor):\n out = torchvision.utils.make_grid(input, nrow=2, padding=5)\n out_np: np.ndarray = K.utils.tensor_to_image(out)\n plt.imshow(out_np)\n plt.axis(\"off\")\n plt.show()\nimshow(x_gray)" + }, + { + "objectID": "nbs/filtering_edges.html#st-order-derivates", + "href": "nbs/filtering_edges.html#st-order-derivates", + "title": "Edge Detection", + "section": "1st order derivates", + "text": "1st order derivates\n\ngrads: torch.Tensor = K.filters.spatial_gradient(x_gray, order=1) # BxCx2xHxW\ngrads_x = grads[:, :, 0]\ngrads_y = grads[:, :, 1]\n\n\n# Show first derivatives in x\nimshow(1.0 - grads_x.clamp(0.0, 1.0))\n\n\n\n\n\n# Show first derivatives in y\nimshow(1.0 - grads_y.clamp(0.0, 1.0))" + }, + { + "objectID": "nbs/filtering_edges.html#nd-order-derivatives", + "href": "nbs/filtering_edges.html#nd-order-derivatives", + "title": "Edge Detection", + "section": "2nd order derivatives", + "text": "2nd order derivatives\n\ngrads: torch.Tensor = K.filters.spatial_gradient(x_gray, order=2) # BxCx2xHxW\ngrads_x = grads[:, :, 0]\ngrads_y = grads[:, :, 1]\n\n\n# Show second derivatives in x\nimshow(1.0 - grads_x.clamp(0.0, 1.0))\n\n\n\n\n\n# Show second derivatives in y\nimshow(1.0 - grads_y.clamp(0.0, 1.0))" + }, + { + "objectID": "nbs/filtering_edges.html#sobel-edges", + "href": "nbs/filtering_edges.html#sobel-edges", + "title": "Edge Detection", + "section": "Sobel Edges", + "text": "Sobel Edges\nOnce with the gradients in the two directions we can computet the Sobel edges. However, in kornia we already have it implemented.\n\nx_sobel: torch.Tensor = K.filters.sobel(x_gray)\nimshow(1.0 - x_sobel)" + }, + { + "objectID": "nbs/filtering_edges.html#laplacian-edges", + "href": "nbs/filtering_edges.html#laplacian-edges", + "title": "Edge Detection", + "section": "Laplacian edges", + "text": "Laplacian edges\n\nx_laplacian: torch.Tensor = K.filters.laplacian(x_gray, kernel_size=5)\nimshow(1.0 - x_laplacian.clamp(0.0, 1.0))" + }, + { + "objectID": "nbs/filtering_edges.html#canny-edges", + "href": "nbs/filtering_edges.html#canny-edges", + "title": "Edge Detection", + "section": "Canny edges", + "text": "Canny edges\n\nx_laplacian: torch.Tensor = K.filters.canny(x_gray)[0]\nimshow(1.0 - x_laplacian.clamp(0.0, 1.0))" + }, + { + "objectID": "nbs/image_stitching.html", + "href": "nbs/image_stitching.html", + "title": "Image stitching example with LoFTR", + "section": "", + "text": "First, we will install everything needed:\n%%capture\n!pip install kornia\n!pip install kornia-rs\nNow let’s download an image pair\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1].split(\"?\")[0] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\ndownload_image(\"http://www.ic.unicamp.br/~helio/imagens_registro/foto1B.jpg\")\ndownload_image(\"http://www.ic.unicamp.br/~helio/imagens_registro/foto1A.jpg\")\n\n'foto1A.jpg'\n%%capture\nimport kornia as K\nimport kornia.feature as KF\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport torch\n\n\ndef load_images(fnames):\n return [K.io.load_image(fn, K.io.ImageLoadType.RGB32)[None, ...] for fn in fnames]\n\n\nimgs = load_images([\"foto1A.jpg\", \"foto1B.jpg\"])" + }, + { + "objectID": "nbs/image_stitching.html#stitch-them-together", + "href": "nbs/image_stitching.html#stitch-them-together", + "title": "Image stitching example with LoFTR", + "section": "Stitch them together", + "text": "Stitch them together\n\nfrom kornia.contrib import ImageStitcher\n\nIS = ImageStitcher(KF.LoFTR(pretrained=\"outdoor\"), estimator=\"ransac\")\n\nwith torch.no_grad():\n out = IS(*imgs)\n\nplt.imshow(K.tensor_to_image(out))\nplt.show()" + }, + { + "objectID": "nbs/image_stitching.html#another-example", + "href": "nbs/image_stitching.html#another-example", + "title": "Image stitching example with LoFTR", + "section": "Another example", + "text": "Another example\n\ndownload_image(\"https://github.com/daeyun/Image-Stitching/blob/master/img/hill/1.JPG?raw=true\")\ndownload_image(\"https://github.com/daeyun/Image-Stitching/blob/master/img/hill/2.JPG?raw=true\")\ndownload_image(\"https://github.com/daeyun/Image-Stitching/blob/master/img/hill/3.JPG?raw=true\")\n\n'3.JPG'\n\n\n\nimgs = load_images([\"1.JPG\", \"2.JPG\", \"3.JPG\"])\n\n\nf, axarr = plt.subplots(1, 3, figsize=(16, 6))\n\naxarr[0].imshow(K.tensor_to_image(imgs[0]))\naxarr[0].tick_params(left=False, right=False, labelleft=False, labelbottom=False, bottom=False)\naxarr[1].imshow(K.tensor_to_image(imgs[1]))\naxarr[1].tick_params(left=False, right=False, labelleft=False, labelbottom=False, bottom=False)\naxarr[2].imshow(K.tensor_to_image(imgs[2]))\naxarr[2].tick_params(left=False, right=False, labelleft=False, labelbottom=False, bottom=False)\n\n\n\n\n\nmatcher = KF.LocalFeatureMatcher(KF.GFTTAffNetHardNet(100), KF.DescriptorMatcher(\"snn\", 0.8))\nIS = ImageStitcher(matcher, estimator=\"ransac\")\n\nwith torch.no_grad():\n out = IS(*imgs)\n\n\nplt.figure(figsize=(16, 16))\nplt.imshow(K.tensor_to_image(out))\n\n<matplotlib.image.AxesImage>" + }, + { + "objectID": "nbs/total_variation_denoising.html", + "href": "nbs/total_variation_denoising.html", + "title": "Denoise image using total variation", + "section": "", + "text": "%%capture\n!pip install kornia\n!pip install kornia-rs\n\n\nimport io\n\nimport requests\n\n\ndef download_image(url: str, filename: str = \"\") -> str:\n filename = url.split(\"/\")[-1] if len(filename) == 0 else filename\n # Download\n bytesio = io.BytesIO(requests.get(url).content)\n # Save file\n with open(filename, \"wb\") as outfile:\n outfile.write(bytesio.getbuffer())\n\n return filename\n\n\nurl = \"https://github.com/kornia/data/raw/main/doraemon.png\"\ndownload_image(url)\n\n'doraemon.png'\n\n\n\nimport kornia as K\nimport matplotlib.pyplot as plt\nimport torch\nimport torchvision\n\n\ndef imshow(input: torch.Tensor):\n out = torchvision.utils.make_grid(input, nrow=2, padding=5)\n out_np = K.utils.tensor_to_image(out)\n plt.imshow(out_np)\n plt.axis(\"off\")\n plt.show()\n\n\n# read the image with kornia and add a random noise to it\nimg = K.io.load_image(\"doraemon.png\", K.io.ImageLoadType.RGB32) # CxHxW\n\nnoisy_image = (img + torch.normal(torch.zeros_like(img), 0.1)).clamp(0, 1)\nimshow(noisy_image)\n\n\n\n\nWe define the total variation denoising network and the optimizer\n\n# define the total variation denoising network\n\n\nclass TVDenoise(torch.nn.Module):\n def __init__(self, noisy_image):\n super().__init__()\n self.l2_term = torch.nn.MSELoss(reduction=\"mean\")\n self.regularization_term = K.losses.TotalVariation()\n # create the variable which will be optimized to produce the noise free image\n self.clean_image = torch.nn.Parameter(data=noisy_image.clone(), requires_grad=True)\n self.noisy_image = noisy_image\n\n def forward(self):\n return self.l2_term(self.clean_image, self.noisy_image) + 0.0001 * self.regularization_term(self.clean_image)\n\n def get_clean_image(self):\n return self.clean_image\n\n\ntv_denoiser = TVDenoise(noisy_image)\n\n# define the optimizer to optimize the 1 parameter of tv_denoiser\noptimizer = torch.optim.SGD(tv_denoiser.parameters(), lr=0.1, momentum=0.9)\n\nRun the the optimization loop\n\nnum_iters: int = 500\nfor i in range(num_iters):\n optimizer.zero_grad()\n loss = tv_denoiser().sum()\n if i % 50 == 0:\n print(f\"Loss in iteration {i} of {num_iters}: {loss.item():.3f}\")\n loss.backward()\n optimizer.step()\n\nLoss in iteration 0 of 500: 3.081\nLoss in iteration 50 of 500: 2.723\nLoss in iteration 100 of 500: 2.359\nLoss in iteration 150 of 500: 2.064\nLoss in iteration 200 of 500: 1.828\nLoss in iteration 250 of 500: 1.642\nLoss in iteration 300 of 500: 1.497\nLoss in iteration 350 of 500: 1.384\nLoss in iteration 400 of 500: 1.297\nLoss in iteration 450 of 500: 1.229\n\n\nVisualize the noisy and resulting cleaned image\n\n# convert back to numpy\nimg_clean = K.utils.tensor_to_image(tv_denoiser.get_clean_image())\n\n# Create the plot\nfig, axs = plt.subplots(1, 2, figsize=(16, 10))\naxs = axs.ravel()\n\naxs[0].axis(\"off\")\naxs[0].set_title(\"Noisy image\")\naxs[0].imshow(K.tensor_to_image(noisy_image))\n\naxs[1].axis(\"off\")\naxs[1].set_title(\"Cleaned image\")\naxs[1].imshow(img_clean)\n\nplt.show()" + }, + { + "objectID": "nbs/data_augmentation_kornia_lightning.html#install-kornia-and-pytorch-lightning", + "href": "nbs/data_augmentation_kornia_lightning.html#install-kornia-and-pytorch-lightning", + "title": "Kornia and PyTorch Lightning GPU data augmentation", + "section": "Install Kornia and PyTorch Lightning", + "text": "Install Kornia and PyTorch Lightning\nWe first install Kornia and PyTorch Lightning\n\n%%capture\n!pip install kornia\n!pip install kornia-rs\n!pip install pytorch_lightning torchmetrics\n\nImport the needed libraries\n\nimport os\n\nimport kornia as K\nimport numpy as np\nimport pytorch_lightning as pl\nimport torch\nimport torch.nn as nn\nimport torchmetrics\nfrom PIL import Image\nfrom torch.nn import functional as F\nfrom torch.utils.data import DataLoader\nfrom torchvision.datasets import CIFAR10" + }, + { + "objectID": "nbs/data_augmentation_kornia_lightning.html#define-data-augmentations-module", + "href": "nbs/data_augmentation_kornia_lightning.html#define-data-augmentations-module", + "title": "Kornia and PyTorch Lightning GPU data augmentation", + "section": "Define Data Augmentations module", + "text": "Define Data Augmentations module\n\nclass DataAugmentation(nn.Module):\n \"\"\"Module to perform data augmentation using Kornia on torch tensors.\"\"\"\n\n def __init__(self, apply_color_jitter: bool = False) -> None:\n super().__init__()\n self._apply_color_jitter = apply_color_jitter\n\n self._max_val: float = 255.0\n\n self.transforms = nn.Sequential(K.enhance.Normalize(0.0, self._max_val), K.augmentation.RandomHorizontalFlip(p=0.5))\n\n self.jitter = K.augmentation.ColorJitter(0.5, 0.5, 0.5, 0.5)\n\n @torch.no_grad() # disable gradients for effiency\n def forward(self, x: torch.Tensor) -> torch.Tensor:\n x_out = self.transforms(x) # BxCxHxW\n if self._apply_color_jitter:\n x_out = self.jitter(x_out)\n return x_out" + }, + { + "objectID": "nbs/data_augmentation_kornia_lightning.html#define-a-pre-processing-model", + "href": "nbs/data_augmentation_kornia_lightning.html#define-a-pre-processing-model", + "title": "Kornia and PyTorch Lightning GPU data augmentation", + "section": "Define a Pre-processing model", + "text": "Define a Pre-processing model\n\nclass PreProcess(nn.Module):\n \"\"\"Module to perform pre-process using Kornia on torch tensors.\"\"\"\n\n def __init__(self) -> None:\n super().__init__()\n\n @torch.no_grad() # disable gradients for effiency\n def forward(self, x: Image) -> torch.Tensor:\n x_tmp: np.ndarray = np.array(x) # HxWxC\n x_out: torch.Tensor = K.image_to_tensor(x_tmp, keepdim=True) # CxHxW\n return x_out.float()" + }, + { + "objectID": "nbs/data_augmentation_kornia_lightning.html#define-pytorch-lightning-model", + "href": "nbs/data_augmentation_kornia_lightning.html#define-pytorch-lightning-model", + "title": "Kornia and PyTorch Lightning GPU data augmentation", + "section": "Define PyTorch Lightning model", + "text": "Define PyTorch Lightning model\n\nclass CoolSystem(pl.LightningModule):\n def __init__(self):\n super().__init__()\n # not the best model...\n self.l1 = torch.nn.Linear(3 * 32 * 32, 10)\n\n self.preprocess = PreProcess()\n\n self.transform = DataAugmentation()\n\n self.accuracy = torchmetrics.Accuracy(task=\"multiclass\", num_classes=10)\n\n def forward(self, x):\n return torch.relu(self.l1(x.view(x.size(0), -1)))\n\n def training_step(self, batch, batch_idx):\n # REQUIRED\n x, y = batch\n x_aug = self.transform(x) # => we perform GPU/Batched data augmentation\n logits = self.forward(x_aug)\n loss = F.cross_entropy(logits, y)\n self.log(\"train_acc_step\", self.accuracy(logits.argmax(1), y))\n self.log(\"train_loss\", loss)\n return loss\n\n def validation_step(self, batch, batch_idx):\n # OPTIONAL\n x, y = batch\n logits = self.forward(x)\n self.log(\"val_acc_step\", self.accuracy(logits.argmax(1), y))\n return F.cross_entropy(logits, y)\n\n def test_step(self, batch, batch_idx):\n # OPTIONAL\n x, y = batch\n logits = self.forward(x)\n acc = self.accuracy(logits.argmax(1), y)\n self.log(\"test_acc_step\", acc)\n return acc\n\n def configure_optimizers(self):\n # REQUIRED\n # can return multiple optimizers and learning_rate schedulers\n # (LBFGS it is automatically supported, no need for closure function)\n return torch.optim.Adam(self.parameters(), lr=0.0004)\n\n def prepare_data(self):\n CIFAR10(os.getcwd(), train=True, download=True, transform=self.preprocess)\n CIFAR10(os.getcwd(), train=False, download=True, transform=self.preprocess)\n\n def train_dataloader(self):\n # REQUIRED\n dataset = CIFAR10(os.getcwd(), train=True, download=False, transform=self.preprocess)\n loader = DataLoader(dataset, batch_size=32, num_workers=1)\n return loader\n\n def val_dataloader(self):\n dataset = CIFAR10(os.getcwd(), train=True, download=False, transform=self.preprocess)\n loader = DataLoader(dataset, batch_size=32, num_workers=1)\n return loader\n\n def test_dataloader(self):\n dataset = CIFAR10(os.getcwd(), train=False, download=False, transform=self.preprocess)\n loader = DataLoader(dataset, batch_size=16, num_workers=1)\n return loader" + }, + { + "objectID": "nbs/data_augmentation_kornia_lightning.html#run-training", + "href": "nbs/data_augmentation_kornia_lightning.html#run-training", + "title": "Kornia and PyTorch Lightning GPU data augmentation", + "section": "Run training", + "text": "Run training\n\nfrom pytorch_lightning import Trainer\n\n# init model\nmodel = CoolSystem()\n\n# Initialize a trainer\naccelerator = \"cpu\" # can be 'gpu'\n\ntrainer = Trainer(accelerator=accelerator, max_epochs=1, enable_progress_bar=False)\n\n# Train the model ⚡\ntrainer.fit(model)\n\nGPU available: True (cuda), used: False\nTPU available: False, using: 0 TPU cores\nIPU available: False, using: 0 IPUs\nHPU available: False, using: 0 HPUs\n\n | Name | Type | Params\n--------------------------------------------------\n0 | l1 | Linear | 30.7 K\n1 | preprocess | PreProcess | 0 \n2 | transform | DataAugmentation | 0 \n3 | accuracy | MulticlassAccuracy | 0 \n--------------------------------------------------\n30.7 K Trainable params\n0 Non-trainable params\n30.7 K Total params\n0.123 Total estimated model params size (MB)\n`Trainer.fit` stopped: `max_epochs=1` reached." + }, + { + "objectID": "nbs/data_augmentation_kornia_lightning.html#visualize", + "href": "nbs/data_augmentation_kornia_lightning.html#visualize", + "title": "Kornia and PyTorch Lightning GPU data augmentation", + "section": "Visualize", + "text": "Visualize\n\n# # Start tensorboard.\n# %load_ext tensorboard\n# %tensorboard --logdir lightning_logs/" + }, + { + "objectID": "nbs/zca_whitening.html#install-necessary-packages", + "href": "nbs/zca_whitening.html#install-necessary-packages", + "title": "ZCA Whitening", + "section": "Install necessary packages", + "text": "Install necessary packages\n\n%%capture\n!pip install kornia numpy matplotlib\n\n\n# Import required libraries\nimport kornia as K\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport torch\nfrom torchvision import datasets, transforms\nfrom torchvision.utils import make_grid\n\n\n# Select runtime device\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using {device}\")\n\nUsing cuda:0" + }, + { + "objectID": "nbs/zca_whitening.html#zca-on-mnist", + "href": "nbs/zca_whitening.html#zca-on-mnist", + "title": "ZCA Whitening", + "section": "ZCA on MNIST", + "text": "ZCA on MNIST\nDownload and load the MNIST dataset.\n\n%%capture\ndataset = datasets.MNIST(\"./data/mnist\", train=True, download=True, transform=transforms.Compose([transforms.ToTensor()]))\n\nStack whole dataset in order to fit ZCA on whole dataset.\n\nimages = []\nfor i in range(len(dataset)):\n im, _ = dataset[i]\n images.append(im)\nimages = torch.stack(images, dim=0).to(device)\n\nCreate an ZCA object and fit the transformation in the forward pass. Setting include_fit is necessary if you need to include the ZCA fitting processing the backwards pass.\n\nzca = K.enhance.ZCAWhitening(eps=0.1)\nimages_zca = zca(images, include_fit=True)\n\nThe result shown should enhance the edges of the MNIST digits because the regularization parameter \\(\\epsilon\\) increases the importance of the higher frequencies which typically correspond to the lowest eigenvalues in ZCA. The result looks similar to the demo from the Stanford ZCA tutorial\n\ngrid_im = make_grid(images[0:30], nrow=5, normalize=True).cpu().numpy()\ngrid_zca = make_grid(images_zca[0:30], nrow=5, normalize=True).cpu().numpy()\n\n\nplt.figure(figsize=(15, 15))\nplt.subplot(1, 2, 1)\nplt.imshow(np.transpose(grid_im, [1, 2, 0]))\nplt.title(\"Input Images\")\nplt.xticks([])\nplt.yticks([])\nplt.subplot(1, 2, 2)\nplt.imshow(np.transpose(grid_zca, [1, 2, 0]))\nplt.title(r\"ZCA Images $\\epsilon = 0.1$\")\nplt.xticks([])\nplt.yticks([])\nplt.show()" + }, + { + "objectID": "nbs/zca_whitening.html#zca-on-cifar-10", + "href": "nbs/zca_whitening.html#zca-on-cifar-10", + "title": "ZCA Whitening", + "section": "ZCA on CIFAR-10", + "text": "ZCA on CIFAR-10\nIn the next example, we explore using ZCA on the CIFAR 10 dataset, which is a dataset of color images (e.g 4-D tensor \\([B, C, H, W]\\)). In the cell below, we prepare the dataset.\n\n%%capture\ndataset = datasets.CIFAR10(\"./data/cifar\", train=True, download=True, transform=transforms.Compose([transforms.ToTensor()]))\nimages = []\nfor i in range(len(dataset)):\n im, _ = dataset[i]\n images.append(im)\nimages = torch.stack(images, dim=0).to(device)\n\nWe show another way to fit the ZCA transform using the fit method useful when ZCA is included in data augumentation pipelines. Also if compute_inv = True, this enables the computation of inverse ZCA transform in case a reconstruction is required.\n\nzca = K.enhance.ZCAWhitening(eps=0.1, compute_inv=True)\nzca.fit(images)\nzca_images = zca(images)\nimage_re = zca.inverse_transform(zca_images)\n\nNote how the higher frequency details are more present in the ZCA normalized images for CIFAR-10 dataset.\n\ngrid_im = make_grid(images[0:30], nrow=5, normalize=True).cpu().numpy()\ngrid_zca = make_grid(zca_images[0:30], nrow=5, normalize=True).cpu().numpy()\ngrid_re = make_grid(image_re[0:30], nrow=5, normalize=True).cpu().numpy()\n\n\nerr_grid = grid_re - grid_im # Compute error image\n\nplt.figure(figsize=(15, 15))\nplt.subplot(2, 2, 1)\nplt.imshow(np.transpose(grid_im, [1, 2, 0]))\nplt.title(\"Input Images\")\nplt.xticks([])\nplt.yticks([])\nplt.subplot(2, 2, 2)\nplt.imshow(np.transpose(grid_zca, [1, 2, 0]))\nplt.title(r\"ZCA Images $\\epsilon = 0.1$\")\nplt.xticks([])\nplt.yticks([])\nplt.subplot(2, 2, 3)\nplt.imshow(np.transpose(grid_re, [1, 2, 0]))\nplt.title(r\"Reconstructed Images\")\nplt.xticks([])\nplt.yticks([])\nplt.subplot(2, 2, 4)\nplt.imshow(np.sum(abs(np.transpose(err_grid, [1, 2, 0])), axis=-1))\nplt.colorbar()\nplt.title(\"Error Image\")\nplt.xticks([])\nplt.yticks([])\nplt.show()" + }, + { + "objectID": "nbs/zca_whitening.html#differentiability-of-zca", + "href": "nbs/zca_whitening.html#differentiability-of-zca", + "title": "ZCA Whitening", + "section": "Differentiability of ZCA", + "text": "Differentiability of ZCA\nWe will as simple Gaussian dataset with a mean (3,3) and a diagonal covariance of 1 and 9 to explore the differentiability of ZCA.\n\nnum_data = 100 # Number of points in the dataset\ntorch.manual_seed(1234)\nx = torch.cat([torch.randn((num_data, 1), requires_grad=True), 3 * torch.randn((num_data, 1), requires_grad=True)], dim=1) + 3\n\nplt.scatter(x.detach().numpy()[:, 0], x.detach().numpy()[:, 1])\nplt.xlim([-10, 10])\nplt.ylim([-10, 10])\nplt.show()\n\n\n\n\nHere we explore the affect of the detach_transform option when computing the backwards pass.\n\nzca_detach = K.enhance.ZCAWhitening(eps=1e-6, detach_transforms=True)\nzca_grad = K.enhance.ZCAWhitening(eps=1e-6, detach_transforms=False)\n\nAs a sanity check, the Jacobian between the input and output of the ZCA transform should be same for all data points in the detached case since the transform acts as linear transform (e.g \\(T(X-\\mu)\\)). In the non-detached case, the Jacobian should vary across datapoints since the input affects the computation of the ZCA transformation matrix (e.g. \\(T(X)(X-\\mu(X))\\)). As the number of samples increases, the Jacobians in the detached and non-detached cases should be roughly the same since the influence of a single datapoint decreases. You can test this by changing num_data . Also note that include_fit=True is included in the forward pass since creation of the transform matrix needs to be included in the forward pass in order to correctly compute the backwards pass.\n\nimport torch.autograd as autograd\n\nJ = autograd.functional.jacobian(lambda x: zca_detach(x, include_fit=True), x)\n\nnum_disp = 5\nprint(f\"Jacobian matrices detached for the first {num_disp} points\")\nfor i in range(num_disp):\n print(J[i, :, i, :])\n\nprint(\"\\n\")\n\nJ = autograd.functional.jacobian(lambda x: zca_grad(x, include_fit=True), x)\nprint(f\"Jacobian matrices attached for the first {num_disp} points\")\nfor i in range(num_disp):\n print(J[i, :, i, :])\n\nJacobian matrices detached for the first 5 points\ntensor([[ 1.0177, -0.0018],\n [-0.0018, 0.3618]])\ntensor([[ 1.0177, -0.0018],\n [-0.0018, 0.3618]])\ntensor([[ 1.0177, -0.0018],\n [-0.0018, 0.3618]])\ntensor([[ 1.0177, -0.0018],\n [-0.0018, 0.3618]])\ntensor([[ 1.0177, -0.0018],\n [-0.0018, 0.3618]])\n\n\nJacobian matrices attached for the first 5 points\ntensor([[ 1.0003, -0.0018],\n [-0.0018, 0.3547]])\ntensor([[ 1.0006, -0.0027],\n [-0.0027, 0.3555]])\ntensor([[ 0.9911, -0.0028],\n [-0.0028, 0.3506]])\ntensor([[9.9281e-01, 4.4671e-04],\n [4.4671e-04, 3.5368e-01]])\ntensor([[ 1.0072, -0.0019],\n [-0.0019, 0.3581]])\n\n\nLastly, we plot the ZCA whitened data. Note that setting the include_fit to True stores the resulting transformations for future use.\n\nx_zca = zca_detach(x).detach().numpy()\nplt.scatter(x_zca[:, 0], x_zca[:, 1])\nplt.ylim([-4, 4])\nplt.xlim([-4, 4])\nplt.show()" + } +] \ No newline at end of file diff --git a/site_libs/bootstrap/bootstrap-dark.min.css b/site_libs/bootstrap/bootstrap-dark.min.css new file mode 100644 index 0000000..0fa0b66 --- /dev/null +++ b/site_libs/bootstrap/bootstrap-dark.min.css @@ -0,0 +1,10 @@ +@import"https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap";.list div.quarto-post .listing-categories .listing-category{color:rgba(255,255,255,.8)}/*! + * Bootstrap v5.1.3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--bs-blue: #325d88;--bs-indigo: #6610f2;--bs-purple: #6f42c1;--bs-pink: #e83e8c;--bs-red: #d9534f;--bs-orange: #f47c3c;--bs-yellow: #ffc107;--bs-green: #93c54b;--bs-teal: #20c997;--bs-cyan: #29abe0;--bs-white: #fff;--bs-gray: #6c757d;--bs-gray-dark: #3e3f3a;--bs-gray-100: #f8f9fa;--bs-gray-200: #f8f5f0;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #495057;--bs-gray-800: #3e3f3a;--bs-gray-900: #212529;--bs-default: #6c757d;--bs-primary: #131416;--bs-secondary: #6c757d;--bs-success: #93c54b;--bs-info: #29abe0;--bs-warning: #f47c3c;--bs-danger: #d9534f;--bs-light: #f8f5f0;--bs-dark: #3e3f3a;--bs-default-rgb: 108, 117, 125;--bs-primary-rgb: 19, 20, 22;--bs-secondary-rgb: 108, 117, 125;--bs-success-rgb: 147, 197, 75;--bs-info-rgb: 41, 171, 224;--bs-warning-rgb: 244, 124, 60;--bs-danger-rgb: 217, 83, 79;--bs-light-rgb: 248, 245, 240;--bs-dark-rgb: 62, 63, 58;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-body-color-rgb: 255, 255, 255;--bs-body-bg-rgb: 19, 20, 22;--bs-font-sans-serif: Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-root-font-size: 18px;--bs-body-font-family: Open Sans, sans-serif;--bs-body-font-size: 1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: rgba(255, 255, 255, 0.8);--bs-body-bg: #131416}*,*::before,*::after{box-sizing:border-box}:root{font-size:var(--bs-root-font-size)}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2}h1,.h1{font-size:calc(1.325rem + 0.9vw)}@media(min-width: 1200px){h1,.h1{font-size:2rem}}h2,.h2{font-size:calc(1.29rem + 0.48vw)}@media(min-width: 1200px){h2,.h2{font-size:1.65rem}}h3,.h3{font-size:calc(1.27rem + 0.24vw)}@media(min-width: 1200px){h3,.h3{font-size:1.45rem}}h4,.h4{font-size:1.25rem}h5,.h5{font-size:1.1rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-bs-original-title]{text-decoration:underline dotted;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;-ms-text-decoration:underline dotted;-o-text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem;padding:.625rem 1.25rem;border-left:.25rem solid #f8f5f0}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}b,strong{font-weight:bolder}small,.small{font-size:0.875em}mark,.mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:0.75em;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}a{color:#2b8cee;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}a:hover{color:#2270be}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr /* rtl:ignore */;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#fff;background-color:rgba(25,26,28,.992);padding:.5rem;border:1px solid #dee2e6;border-radius:.25rem}pre code{background-color:rgba(0,0,0,0);font-size:inherit;color:inherit;word-break:normal}code{font-size:0.875em;color:#9753b8;background-color:rgba(25,26,28,.992);border-radius:.25rem;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#fff;background-color:#212529;border-radius:.2em}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + 0.3vw);line-height:inherit}@media(min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:0.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:0.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#131416;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:0.875em;color:#6c757d}.grid{display:grid;grid-template-rows:repeat(var(--bs-rows, 1), 1fr);grid-template-columns:repeat(var(--bs-columns, 12), 1fr);gap:var(--bs-gap, 1.5rem)}.grid .g-col-1{grid-column:auto/span 1}.grid .g-col-2{grid-column:auto/span 2}.grid .g-col-3{grid-column:auto/span 3}.grid .g-col-4{grid-column:auto/span 4}.grid .g-col-5{grid-column:auto/span 5}.grid .g-col-6{grid-column:auto/span 6}.grid .g-col-7{grid-column:auto/span 7}.grid .g-col-8{grid-column:auto/span 8}.grid .g-col-9{grid-column:auto/span 9}.grid .g-col-10{grid-column:auto/span 10}.grid .g-col-11{grid-column:auto/span 11}.grid .g-col-12{grid-column:auto/span 12}.grid .g-start-1{grid-column-start:1}.grid .g-start-2{grid-column-start:2}.grid .g-start-3{grid-column-start:3}.grid .g-start-4{grid-column-start:4}.grid .g-start-5{grid-column-start:5}.grid .g-start-6{grid-column-start:6}.grid .g-start-7{grid-column-start:7}.grid .g-start-8{grid-column-start:8}.grid .g-start-9{grid-column-start:9}.grid .g-start-10{grid-column-start:10}.grid .g-start-11{grid-column-start:11}@media(min-width: 576px){.grid .g-col-sm-1{grid-column:auto/span 1}.grid .g-col-sm-2{grid-column:auto/span 2}.grid .g-col-sm-3{grid-column:auto/span 3}.grid .g-col-sm-4{grid-column:auto/span 4}.grid .g-col-sm-5{grid-column:auto/span 5}.grid .g-col-sm-6{grid-column:auto/span 6}.grid .g-col-sm-7{grid-column:auto/span 7}.grid .g-col-sm-8{grid-column:auto/span 8}.grid .g-col-sm-9{grid-column:auto/span 9}.grid .g-col-sm-10{grid-column:auto/span 10}.grid .g-col-sm-11{grid-column:auto/span 11}.grid .g-col-sm-12{grid-column:auto/span 12}.grid .g-start-sm-1{grid-column-start:1}.grid .g-start-sm-2{grid-column-start:2}.grid .g-start-sm-3{grid-column-start:3}.grid .g-start-sm-4{grid-column-start:4}.grid .g-start-sm-5{grid-column-start:5}.grid .g-start-sm-6{grid-column-start:6}.grid .g-start-sm-7{grid-column-start:7}.grid .g-start-sm-8{grid-column-start:8}.grid .g-start-sm-9{grid-column-start:9}.grid .g-start-sm-10{grid-column-start:10}.grid .g-start-sm-11{grid-column-start:11}}@media(min-width: 768px){.grid .g-col-md-1{grid-column:auto/span 1}.grid .g-col-md-2{grid-column:auto/span 2}.grid .g-col-md-3{grid-column:auto/span 3}.grid .g-col-md-4{grid-column:auto/span 4}.grid .g-col-md-5{grid-column:auto/span 5}.grid .g-col-md-6{grid-column:auto/span 6}.grid .g-col-md-7{grid-column:auto/span 7}.grid .g-col-md-8{grid-column:auto/span 8}.grid .g-col-md-9{grid-column:auto/span 9}.grid .g-col-md-10{grid-column:auto/span 10}.grid .g-col-md-11{grid-column:auto/span 11}.grid .g-col-md-12{grid-column:auto/span 12}.grid .g-start-md-1{grid-column-start:1}.grid .g-start-md-2{grid-column-start:2}.grid .g-start-md-3{grid-column-start:3}.grid .g-start-md-4{grid-column-start:4}.grid .g-start-md-5{grid-column-start:5}.grid .g-start-md-6{grid-column-start:6}.grid .g-start-md-7{grid-column-start:7}.grid .g-start-md-8{grid-column-start:8}.grid .g-start-md-9{grid-column-start:9}.grid .g-start-md-10{grid-column-start:10}.grid .g-start-md-11{grid-column-start:11}}@media(min-width: 992px){.grid .g-col-lg-1{grid-column:auto/span 1}.grid .g-col-lg-2{grid-column:auto/span 2}.grid .g-col-lg-3{grid-column:auto/span 3}.grid .g-col-lg-4{grid-column:auto/span 4}.grid .g-col-lg-5{grid-column:auto/span 5}.grid .g-col-lg-6{grid-column:auto/span 6}.grid .g-col-lg-7{grid-column:auto/span 7}.grid .g-col-lg-8{grid-column:auto/span 8}.grid .g-col-lg-9{grid-column:auto/span 9}.grid .g-col-lg-10{grid-column:auto/span 10}.grid .g-col-lg-11{grid-column:auto/span 11}.grid .g-col-lg-12{grid-column:auto/span 12}.grid .g-start-lg-1{grid-column-start:1}.grid .g-start-lg-2{grid-column-start:2}.grid .g-start-lg-3{grid-column-start:3}.grid .g-start-lg-4{grid-column-start:4}.grid .g-start-lg-5{grid-column-start:5}.grid .g-start-lg-6{grid-column-start:6}.grid .g-start-lg-7{grid-column-start:7}.grid .g-start-lg-8{grid-column-start:8}.grid .g-start-lg-9{grid-column-start:9}.grid .g-start-lg-10{grid-column-start:10}.grid .g-start-lg-11{grid-column-start:11}}@media(min-width: 1200px){.grid .g-col-xl-1{grid-column:auto/span 1}.grid .g-col-xl-2{grid-column:auto/span 2}.grid .g-col-xl-3{grid-column:auto/span 3}.grid .g-col-xl-4{grid-column:auto/span 4}.grid .g-col-xl-5{grid-column:auto/span 5}.grid .g-col-xl-6{grid-column:auto/span 6}.grid .g-col-xl-7{grid-column:auto/span 7}.grid .g-col-xl-8{grid-column:auto/span 8}.grid .g-col-xl-9{grid-column:auto/span 9}.grid .g-col-xl-10{grid-column:auto/span 10}.grid .g-col-xl-11{grid-column:auto/span 11}.grid .g-col-xl-12{grid-column:auto/span 12}.grid .g-start-xl-1{grid-column-start:1}.grid .g-start-xl-2{grid-column-start:2}.grid .g-start-xl-3{grid-column-start:3}.grid .g-start-xl-4{grid-column-start:4}.grid .g-start-xl-5{grid-column-start:5}.grid .g-start-xl-6{grid-column-start:6}.grid .g-start-xl-7{grid-column-start:7}.grid .g-start-xl-8{grid-column-start:8}.grid .g-start-xl-9{grid-column-start:9}.grid .g-start-xl-10{grid-column-start:10}.grid .g-start-xl-11{grid-column-start:11}}@media(min-width: 1400px){.grid .g-col-xxl-1{grid-column:auto/span 1}.grid .g-col-xxl-2{grid-column:auto/span 2}.grid .g-col-xxl-3{grid-column:auto/span 3}.grid .g-col-xxl-4{grid-column:auto/span 4}.grid .g-col-xxl-5{grid-column:auto/span 5}.grid .g-col-xxl-6{grid-column:auto/span 6}.grid .g-col-xxl-7{grid-column:auto/span 7}.grid .g-col-xxl-8{grid-column:auto/span 8}.grid .g-col-xxl-9{grid-column:auto/span 9}.grid .g-col-xxl-10{grid-column:auto/span 10}.grid .g-col-xxl-11{grid-column:auto/span 11}.grid .g-col-xxl-12{grid-column:auto/span 12}.grid .g-start-xxl-1{grid-column-start:1}.grid .g-start-xxl-2{grid-column-start:2}.grid .g-start-xxl-3{grid-column-start:3}.grid .g-start-xxl-4{grid-column-start:4}.grid .g-start-xxl-5{grid-column-start:5}.grid .g-start-xxl-6{grid-column-start:6}.grid .g-start-xxl-7{grid-column-start:7}.grid .g-start-xxl-8{grid-column-start:8}.grid .g-start-xxl-9{grid-column-start:9}.grid .g-start-xxl-10{grid-column-start:10}.grid .g-start-xxl-11{grid-column-start:11}}.table{--bs-table-bg: transparent;--bs-table-accent-bg: transparent;--bs-table-striped-color: rgba(255, 255, 255, 0.8);--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: rgba(255, 255, 255, 0.8);--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: rgba(255, 255, 255, 0.8);--bs-table-hover-bg: rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:rgba(255,255,255,.8);vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:first-child){border-top:2px solid rgba(255,255,255,.8)}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-accent-bg: var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg: var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover>*{--bs-table-accent-bg: var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg: #d0d0d0;--bs-table-striped-bg: #c6c6c6;--bs-table-striped-color: #000;--bs-table-active-bg: #bbbbbb;--bs-table-active-color: #000;--bs-table-hover-bg: silver;--bs-table-hover-color: #000;color:#000;border-color:#bbb}.table-secondary{--bs-table-bg: #e2e3e5;--bs-table-striped-bg: #d7d8da;--bs-table-striped-color: #000;--bs-table-active-bg: #cbccce;--bs-table-active-color: #000;--bs-table-hover-bg: #d1d2d4;--bs-table-hover-color: #000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg: #e9f3db;--bs-table-striped-bg: #dde7d0;--bs-table-striped-color: #000;--bs-table-active-bg: #d2dbc5;--bs-table-active-color: #000;--bs-table-hover-bg: #d8e1cb;--bs-table-hover-color: #000;color:#000;border-color:#d2dbc5}.table-info{--bs-table-bg: #d4eef9;--bs-table-striped-bg: #c9e2ed;--bs-table-striped-color: #000;--bs-table-active-bg: #bfd6e0;--bs-table-active-color: #000;--bs-table-hover-bg: #c4dce6;--bs-table-hover-color: #000;color:#000;border-color:#bfd6e0}.table-warning{--bs-table-bg: #fde5d8;--bs-table-striped-bg: #f0dacd;--bs-table-striped-color: #000;--bs-table-active-bg: #e4cec2;--bs-table-active-color: #000;--bs-table-hover-bg: #ead4c8;--bs-table-hover-color: #000;color:#000;border-color:#e4cec2}.table-danger{--bs-table-bg: #f7dddc;--bs-table-striped-bg: #ebd2d1;--bs-table-striped-color: #000;--bs-table-active-bg: #dec7c6;--bs-table-active-color: #000;--bs-table-hover-bg: #e4cccc;--bs-table-hover-color: #000;color:#000;border-color:#dec7c6}.table-light{--bs-table-bg: #f8f5f0;--bs-table-striped-bg: #ece9e4;--bs-table-striped-color: #000;--bs-table-active-bg: #dfddd8;--bs-table-active-color: #000;--bs-table-hover-bg: #e5e3de;--bs-table-hover-color: #000;color:#000;border-color:#dfddd8}.table-dark{--bs-table-bg: #3e3f3a;--bs-table-striped-bg: #484944;--bs-table-striped-color: #fff;--bs-table-active-bg: #51524e;--bs-table-active-color: #fff;--bs-table-hover-bg: #4c4d49;--bs-table-hover-color: #fff;color:#fff;border-color:#51524e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label,.shiny-input-container .control-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(0.375rem + 1px);padding-bottom:calc(0.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + 1px);padding-bottom:calc(0.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(0.25rem + 1px);padding-bottom:calc(0.25rem + 1px);font-size:0.875rem}.form-text{margin-top:.25rem;font-size:0.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:rgba(255,255,255,.8);background-color:#131416;background-clip:padding-box;border:1px solid #ced4da;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:rgba(255,255,255,.8);background-color:#131416;border-color:#898a8b;outline:0;box-shadow:0 0 0 .25rem rgba(19,20,22,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#f8f5f0;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#000;background-color:#f8f5f0;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#ece9e4}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#000;background-color:#f8f5f0;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::-webkit-file-upload-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#ece9e4}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:rgba(255,255,255,.8);background-color:rgba(0,0,0,0);border:solid rgba(0,0,0,0);border-width:1px 0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px);padding:.25rem .5rem;font-size:0.875rem;border-radius:.2em}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 0.75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:rgba(255,255,255,.8);background-color:#131416;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%233e3f3a' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:#898a8b;outline:0;box-shadow:0 0 0 .25rem rgba(19,20,22,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#f8f5f0}.form-select:-moz-focusring{color:rgba(0,0,0,0);text-shadow:0 0 0 rgba(255,255,255,.8)}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.875rem;border-radius:.2em}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:.3rem}.form-check,.shiny-input-container .checkbox,.shiny-input-container .radio{display:block;min-height:1.5rem;padding-left:0;margin-bottom:.125rem}.form-check .form-check-input,.form-check .shiny-input-container .checkbox input,.form-check .shiny-input-container .radio input,.shiny-input-container .checkbox .form-check-input,.shiny-input-container .checkbox .shiny-input-container .checkbox input,.shiny-input-container .checkbox .shiny-input-container .radio input,.shiny-input-container .radio .form-check-input,.shiny-input-container .radio .shiny-input-container .checkbox input,.shiny-input-container .radio .shiny-input-container .radio input{float:left;margin-left:0}.form-check-input,.shiny-input-container .checkbox input,.shiny-input-container .checkbox-inline input,.shiny-input-container .radio input,.shiny-input-container .radio-inline input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#131416;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;color-adjust:exact;-webkit-print-color-adjust:exact}.form-check-input[type=checkbox],.shiny-input-container .checkbox input[type=checkbox],.shiny-input-container .checkbox-inline input[type=checkbox],.shiny-input-container .radio input[type=checkbox],.shiny-input-container .radio-inline input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio],.shiny-input-container .checkbox input[type=radio],.shiny-input-container .checkbox-inline input[type=radio],.shiny-input-container .radio input[type=radio],.shiny-input-container .radio-inline input[type=radio]{border-radius:50%}.form-check-input:active,.shiny-input-container .checkbox input:active,.shiny-input-container .checkbox-inline input:active,.shiny-input-container .radio input:active,.shiny-input-container .radio-inline input:active{filter:brightness(90%)}.form-check-input:focus,.shiny-input-container .checkbox input:focus,.shiny-input-container .checkbox-inline input:focus,.shiny-input-container .radio input:focus,.shiny-input-container .radio-inline input:focus{border-color:#898a8b;outline:0;box-shadow:0 0 0 .25rem rgba(19,20,22,.25)}.form-check-input:checked,.shiny-input-container .checkbox input:checked,.shiny-input-container .checkbox-inline input:checked,.shiny-input-container .radio input:checked,.shiny-input-container .radio-inline input:checked{background-color:#131416;border-color:#131416}.form-check-input:checked[type=checkbox],.shiny-input-container .checkbox input:checked[type=checkbox],.shiny-input-container .checkbox-inline input:checked[type=checkbox],.shiny-input-container .radio input:checked[type=checkbox],.shiny-input-container .radio-inline input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio],.shiny-input-container .checkbox input:checked[type=radio],.shiny-input-container .checkbox-inline input:checked[type=radio],.shiny-input-container .radio input:checked[type=radio],.shiny-input-container .radio-inline input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate,.shiny-input-container .checkbox input[type=checkbox]:indeterminate,.shiny-input-container .checkbox-inline input[type=checkbox]:indeterminate,.shiny-input-container .radio input[type=checkbox]:indeterminate,.shiny-input-container .radio-inline input[type=checkbox]:indeterminate{background-color:#131416;border-color:#131416;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled,.shiny-input-container .checkbox input:disabled,.shiny-input-container .checkbox-inline input:disabled,.shiny-input-container .radio input:disabled,.shiny-input-container .radio-inline input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input[disabled]~span,.form-check-input:disabled~.form-check-label,.form-check-input:disabled~span,.shiny-input-container .checkbox input[disabled]~.form-check-label,.shiny-input-container .checkbox input[disabled]~span,.shiny-input-container .checkbox input:disabled~.form-check-label,.shiny-input-container .checkbox input:disabled~span,.shiny-input-container .checkbox-inline input[disabled]~.form-check-label,.shiny-input-container .checkbox-inline input[disabled]~span,.shiny-input-container .checkbox-inline input:disabled~.form-check-label,.shiny-input-container .checkbox-inline input:disabled~span,.shiny-input-container .radio input[disabled]~.form-check-label,.shiny-input-container .radio input[disabled]~span,.shiny-input-container .radio input:disabled~.form-check-label,.shiny-input-container .radio input:disabled~span,.shiny-input-container .radio-inline input[disabled]~.form-check-label,.shiny-input-container .radio-inline input[disabled]~span,.shiny-input-container .radio-inline input:disabled~.form-check-label,.shiny-input-container .radio-inline input:disabled~span{opacity:.5}.form-check-label,.shiny-input-container .checkbox label,.shiny-input-container .checkbox-inline label,.shiny-input-container .radio label,.shiny-input-container .radio-inline label{cursor:pointer}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23898a8b'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline,.shiny-input-container .checkbox-inline,.shiny-input-container .radio-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:rgba(0,0,0,0);appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #131416,0 0 0 .25rem rgba(19,20,22,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #131416,0 0 0 .25rem rgba(19,20,22,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;background-color:#131416;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b8b9b9}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#dee2e6;border-color:rgba(0,0,0,0);border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#131416;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#b8b9b9}.form-range::-moz-range-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#dee2e6;border-color:rgba(0,0,0,0);border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media(prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::placeholder{color:rgba(0,0,0,0)}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.input-group{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:stretch;-webkit-align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#000;text-align:center;white-space:nowrap;background-color:#f8f5f0;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem;border-radius:.2em}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#93c54b}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(147,197,75,.9);border-radius:.25rem}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:#93c54b;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2393c54b' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#93c54b;box-shadow:0 0 0 .25rem rgba(147,197,75,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:#93c54b}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%233e3f3a' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2393c54b' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:#93c54b;box-shadow:0 0 0 .25rem rgba(147,197,75,.25)}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:#93c54b}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#93c54b}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(147,197,75,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#93c54b}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group .form-control:valid,.input-group .form-control.is-valid,.was-validated .input-group .form-select:valid,.input-group .form-select.is-valid{z-index:1}.was-validated .input-group .form-control:valid:focus,.input-group .form-control.is-valid:focus,.was-validated .input-group .form-select:valid:focus,.input-group .form-select.is-valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#d9534f}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(217,83,79,.9);border-radius:.25rem}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#d9534f;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23d9534f'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23d9534f' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#d9534f;box-shadow:0 0 0 .25rem rgba(217,83,79,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:#d9534f}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%233e3f3a' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23d9534f'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23d9534f' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:#d9534f;box-shadow:0 0 0 .25rem rgba(217,83,79,.25)}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:#d9534f}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#d9534f}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(217,83,79,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#d9534f}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group .form-control:invalid,.input-group .form-control.is-invalid,.was-validated .input-group .form-select:invalid,.input-group .form-select.is-invalid{z-index:2}.was-validated .input-group .form-control:invalid:focus,.input-group .form-control.is-invalid:focus,.was-validated .input-group .form-select:invalid:focus,.input-group .form-select.is-invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:rgba(255,255,255,.8);text-align:center;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;vertical-align:middle;cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:rgba(255,255,255,.8)}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(19,20,22,.25)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-default{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-default:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-default,.btn-default:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:checked+.btn-default,.btn-check:active+.btn-default,.btn-default:active,.btn-default.active,.show>.btn-default.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:checked+.btn-default:focus,.btn-check:active+.btn-default:focus,.btn-default:active:focus,.btn-default.active:focus,.show>.btn-default.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-default:disabled,.btn-default.disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-primary{color:#fff;background-color:#131416;border-color:#131416}.btn-primary:hover{color:#fff;background-color:#101113;border-color:#0f1012}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#101113;border-color:#0f1012;box-shadow:0 0 0 .25rem rgba(54,55,57,.5)}.btn-check:checked+.btn-primary,.btn-check:active+.btn-primary,.btn-primary:active,.btn-primary.active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0f1012;border-color:#0e0f11}.btn-check:checked+.btn-primary:focus,.btn-check:active+.btn-primary:focus,.btn-primary:active:focus,.btn-primary.active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(54,55,57,.5)}.btn-primary:disabled,.btn-primary.disabled{color:#fff;background-color:#131416;border-color:#131416}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:checked+.btn-secondary,.btn-check:active+.btn-secondary,.btn-secondary:active,.btn-secondary.active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:checked+.btn-secondary:focus,.btn-check:active+.btn-secondary:focus,.btn-secondary:active:focus,.btn-secondary.active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary:disabled,.btn-secondary.disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#93c54b;border-color:#93c54b}.btn-success:hover{color:#fff;background-color:#7da740;border-color:#769e3c}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#7da740;border-color:#769e3c;box-shadow:0 0 0 .25rem rgba(163,206,102,.5)}.btn-check:checked+.btn-success,.btn-check:active+.btn-success,.btn-success:active,.btn-success.active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#769e3c;border-color:#6e9438}.btn-check:checked+.btn-success:focus,.btn-check:active+.btn-success:focus,.btn-success:active:focus,.btn-success.active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(163,206,102,.5)}.btn-success:disabled,.btn-success.disabled{color:#fff;background-color:#93c54b;border-color:#93c54b}.btn-info{color:#fff;background-color:#29abe0;border-color:#29abe0}.btn-info:hover{color:#fff;background-color:#2391be;border-color:#2189b3}.btn-check:focus+.btn-info,.btn-info:focus{color:#fff;background-color:#2391be;border-color:#2189b3;box-shadow:0 0 0 .25rem rgba(73,184,229,.5)}.btn-check:checked+.btn-info,.btn-check:active+.btn-info,.btn-info:active,.btn-info.active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#2189b3;border-color:#1f80a8}.btn-check:checked+.btn-info:focus,.btn-check:active+.btn-info:focus,.btn-info:active:focus,.btn-info.active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(73,184,229,.5)}.btn-info:disabled,.btn-info.disabled{color:#fff;background-color:#29abe0;border-color:#29abe0}.btn-warning{color:#fff;background-color:#f47c3c;border-color:#f47c3c}.btn-warning:hover{color:#fff;background-color:#cf6933;border-color:#c36330}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#fff;background-color:#cf6933;border-color:#c36330;box-shadow:0 0 0 .25rem rgba(246,144,89,.5)}.btn-check:checked+.btn-warning,.btn-check:active+.btn-warning,.btn-warning:active,.btn-warning.active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#c36330;border-color:#b75d2d}.btn-check:checked+.btn-warning:focus,.btn-check:active+.btn-warning:focus,.btn-warning:active:focus,.btn-warning.active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(246,144,89,.5)}.btn-warning:disabled,.btn-warning.disabled{color:#fff;background-color:#f47c3c;border-color:#f47c3c}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-danger:hover{color:#fff;background-color:#b84743;border-color:#ae423f}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#b84743;border-color:#ae423f;box-shadow:0 0 0 .25rem rgba(223,109,105,.5)}.btn-check:checked+.btn-danger,.btn-check:active+.btn-danger,.btn-danger:active,.btn-danger.active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#ae423f;border-color:#a33e3b}.btn-check:checked+.btn-danger:focus,.btn-check:active+.btn-danger:focus,.btn-danger:active:focus,.btn-danger.active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(223,109,105,.5)}.btn-danger:disabled,.btn-danger.disabled{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-light{color:#000;background-color:#f8f5f0;border-color:#f8f5f0}.btn-light:hover{color:#000;background-color:#f9f7f2;border-color:#f9f6f2}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9f7f2;border-color:#f9f6f2;box-shadow:0 0 0 .25rem rgba(211,208,204,.5)}.btn-check:checked+.btn-light,.btn-check:active+.btn-light,.btn-light:active,.btn-light.active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9f7f3;border-color:#f9f6f2}.btn-check:checked+.btn-light:focus,.btn-check:active+.btn-light:focus,.btn-light:active:focus,.btn-light.active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,208,204,.5)}.btn-light:disabled,.btn-light.disabled{color:#000;background-color:#f8f5f0;border-color:#f8f5f0}.btn-dark{color:#fff;background-color:#3e3f3a;border-color:#3e3f3a}.btn-dark:hover{color:#fff;background-color:#353631;border-color:#32322e}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#353631;border-color:#32322e;box-shadow:0 0 0 .25rem rgba(91,92,88,.5)}.btn-check:checked+.btn-dark,.btn-check:active+.btn-dark,.btn-dark:active,.btn-dark.active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#32322e;border-color:#2f2f2c}.btn-check:checked+.btn-dark:focus,.btn-check:active+.btn-dark:focus,.btn-dark:active:focus,.btn-dark.active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(91,92,88,.5)}.btn-dark:disabled,.btn-dark.disabled{color:#fff;background-color:#3e3f3a;border-color:#3e3f3a}.btn-outline-default{color:#6c757d;border-color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-default:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-default,.btn-outline-default:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:checked+.btn-outline-default,.btn-check:active+.btn-outline-default,.btn-outline-default:active,.btn-outline-default.active,.btn-outline-default.dropdown-toggle.show{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:checked+.btn-outline-default:focus,.btn-check:active+.btn-outline-default:focus,.btn-outline-default:active:focus,.btn-outline-default.active:focus,.btn-outline-default.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-default:disabled,.btn-outline-default.disabled{color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-primary{color:#131416;border-color:#131416;background-color:rgba(0,0,0,0)}.btn-outline-primary:hover{color:#fff;background-color:#131416;border-color:#131416}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(19,20,22,.5)}.btn-check:checked+.btn-outline-primary,.btn-check:active+.btn-outline-primary,.btn-outline-primary:active,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show{color:#fff;background-color:#131416;border-color:#131416}.btn-check:checked+.btn-outline-primary:focus,.btn-check:active+.btn-outline-primary:focus,.btn-outline-primary:active:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(19,20,22,.5)}.btn-outline-primary:disabled,.btn-outline-primary.disabled{color:#131416;background-color:rgba(0,0,0,0)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:checked+.btn-outline-secondary,.btn-check:active+.btn-outline-secondary,.btn-outline-secondary:active,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:checked+.btn-outline-secondary:focus,.btn-check:active+.btn-outline-secondary:focus,.btn-outline-secondary:active:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary:disabled,.btn-outline-secondary.disabled{color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-success{color:#93c54b;border-color:#93c54b;background-color:rgba(0,0,0,0)}.btn-outline-success:hover{color:#fff;background-color:#93c54b;border-color:#93c54b}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(147,197,75,.5)}.btn-check:checked+.btn-outline-success,.btn-check:active+.btn-outline-success,.btn-outline-success:active,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show{color:#fff;background-color:#93c54b;border-color:#93c54b}.btn-check:checked+.btn-outline-success:focus,.btn-check:active+.btn-outline-success:focus,.btn-outline-success:active:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(147,197,75,.5)}.btn-outline-success:disabled,.btn-outline-success.disabled{color:#93c54b;background-color:rgba(0,0,0,0)}.btn-outline-info{color:#29abe0;border-color:#29abe0;background-color:rgba(0,0,0,0)}.btn-outline-info:hover{color:#fff;background-color:#29abe0;border-color:#29abe0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(41,171,224,.5)}.btn-check:checked+.btn-outline-info,.btn-check:active+.btn-outline-info,.btn-outline-info:active,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show{color:#fff;background-color:#29abe0;border-color:#29abe0}.btn-check:checked+.btn-outline-info:focus,.btn-check:active+.btn-outline-info:focus,.btn-outline-info:active:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(41,171,224,.5)}.btn-outline-info:disabled,.btn-outline-info.disabled{color:#29abe0;background-color:rgba(0,0,0,0)}.btn-outline-warning{color:#f47c3c;border-color:#f47c3c;background-color:rgba(0,0,0,0)}.btn-outline-warning:hover{color:#fff;background-color:#f47c3c;border-color:#f47c3c}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(244,124,60,.5)}.btn-check:checked+.btn-outline-warning,.btn-check:active+.btn-outline-warning,.btn-outline-warning:active,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show{color:#fff;background-color:#f47c3c;border-color:#f47c3c}.btn-check:checked+.btn-outline-warning:focus,.btn-check:active+.btn-outline-warning:focus,.btn-outline-warning:active:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(244,124,60,.5)}.btn-outline-warning:disabled,.btn-outline-warning.disabled{color:#f47c3c;background-color:rgba(0,0,0,0)}.btn-outline-danger{color:#d9534f;border-color:#d9534f;background-color:rgba(0,0,0,0)}.btn-outline-danger:hover{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(217,83,79,.5)}.btn-check:checked+.btn-outline-danger,.btn-check:active+.btn-outline-danger,.btn-outline-danger:active,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-check:checked+.btn-outline-danger:focus,.btn-check:active+.btn-outline-danger:focus,.btn-outline-danger:active:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(217,83,79,.5)}.btn-outline-danger:disabled,.btn-outline-danger.disabled{color:#d9534f;background-color:rgba(0,0,0,0)}.btn-outline-light{color:#f8f5f0;border-color:#f8f5f0;background-color:rgba(0,0,0,0)}.btn-outline-light:hover{color:#000;background-color:#f8f5f0;border-color:#f8f5f0}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,245,240,.5)}.btn-check:checked+.btn-outline-light,.btn-check:active+.btn-outline-light,.btn-outline-light:active,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show{color:#000;background-color:#f8f5f0;border-color:#f8f5f0}.btn-check:checked+.btn-outline-light:focus,.btn-check:active+.btn-outline-light:focus,.btn-outline-light:active:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(248,245,240,.5)}.btn-outline-light:disabled,.btn-outline-light.disabled{color:#f8f5f0;background-color:rgba(0,0,0,0)}.btn-outline-dark{color:#3e3f3a;border-color:#3e3f3a;background-color:rgba(0,0,0,0)}.btn-outline-dark:hover{color:#fff;background-color:#3e3f3a;border-color:#3e3f3a}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(62,63,58,.5)}.btn-check:checked+.btn-outline-dark,.btn-check:active+.btn-outline-dark,.btn-outline-dark:active,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show{color:#fff;background-color:#3e3f3a;border-color:#3e3f3a}.btn-check:checked+.btn-outline-dark:focus,.btn-check:active+.btn-outline-dark:focus,.btn-outline-dark:active:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(62,63,58,.5)}.btn-outline-dark:disabled,.btn-outline-dark.disabled{color:#3e3f3a;background-color:rgba(0,0,0,0)}.btn-link{font-weight:400;color:#2b8cee;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}.btn-link:hover{color:#2270be}.btn-link:disabled,.btn-link.disabled{color:#6c757d}.btn-lg,.btn-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-sm,.btn-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem;border-radius:.2em}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .2s ease}@media(prefers-reduced-motion: reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media(prefers-reduced-motion: reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid rgba(0,0,0,0);border-bottom:0;border-left:.3em solid rgba(0,0,0,0)}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:rgba(255,255,255,.8);text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media(min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid rgba(0,0,0,0);border-bottom:.3em solid;border-left:.3em solid rgba(0,0,0,0)}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:0;border-bottom:.3em solid rgba(0,0,0,0);border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:.3em solid;border-bottom:.3em solid rgba(0,0,0,0)}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#6c757d;text-align:inherit;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap;background-color:rgba(0,0,0,0);border:0}.dropdown-item:hover,.dropdown-item:focus{color:#6c757d;background-color:#f8f5f0}.dropdown-item.active,.dropdown-item:active{color:#6c757d;text-decoration:none;background-color:#f8f5f0}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:rgba(0,0,0,0)}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:0.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#6c757d}.dropdown-menu-dark{color:#dee2e6;background-color:#3e3f3a;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:hover,.dropdown-menu-dark .dropdown-item:focus{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#6c757d;background-color:#f8f5f0}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;justify-content:flex-start;-webkit-justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;-webkit-flex-direction:column;align-items:flex-start;-webkit-align-items:flex-start;justify-content:center;-webkit-justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn~.btn,.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem .9rem;color:#2b8cee;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media(prefers-reduced-motion: reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:#2270be}.nav-link.disabled{color:#dee2e6;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:none;border:1px solid rgba(0,0,0,0);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{border-color:#dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#dee2e6;background-color:rgba(0,0,0,0);border-color:rgba(0,0,0,0)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:none;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#6c757d;background-color:#f8f5f0}.nav-fill>.nav-link,.nav-fill .nav-item{flex:1 1 auto;-webkit-flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.nav-justified .nav-item{flex-basis:0;-webkit-flex-basis:0;flex-grow:1;-webkit-flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container-xxl,.navbar>.container-xl,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container,.navbar>.container-fluid{display:flex;display:-webkit-flex;flex-wrap:inherit;-webkit-flex-wrap:inherit;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;-webkit-flex-basis:100%;flex-grow:1;-webkit-flex-grow:1;align-items:center;-webkit-align-items:center}.navbar-toggler{padding:.25 0;font-size:1.25rem;line-height:1;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media(min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-top,.navbar-expand-sm .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-top,.navbar-expand-md .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-top,.navbar-expand-lg .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-top,.navbar-expand-xl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-top,.navbar-expand-xxl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-top,.navbar-expand .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}.navbar-light{background-color:#1a1c1e}.navbar-light .navbar-brand{color:rgba(255,255,255,.8)}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:#64abf3}.navbar-light .navbar-nav .nav-link{color:rgba(255,255,255,.8)}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .nav-link:focus{color:rgba(100,171,243,.8)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.75)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .nav-link.active{color:#64abf3}.navbar-light .navbar-toggler{color:rgba(255,255,255,.8);border-color:rgba(255,255,255,0)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.8%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(255,255,255,.8)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:#64abf3}.navbar-dark{background-color:#1a1c1e}.navbar-dark .navbar-brand{color:rgba(255,255,255,.8)}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#64abf3}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.8)}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:focus{color:rgba(100,171,243,.8)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active{color:#64abf3}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.8);border-color:rgba(255,255,255,0)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.8%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.8)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#64abf3}.card{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(222,226,230,.75);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-0.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:#adb5bd;border-bottom:1px solid rgba(222,226,230,.75)}.card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:#adb5bd;border-top:1px solid rgba(222,226,230,.75)}.card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media(min-width: 576px){.card-group{display:flex;display:-webkit-flex;flex-flow:row wrap;-webkit-flex-flow:row wrap}.card-group>.card{flex:1 0 0%;-webkit-flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:rgba(255,255,255,.8);text-align:left;background-color:#131416;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media(prefers-reduced-motion: reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#111214;background-color:#e7e8e8;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23111214'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;-webkit-flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgba%28255, 255, 255, 0.8%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media(prefers-reduced-motion: reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#898a8b;outline:0;box-shadow:0 0 0 .25rem rgba(19,20,22,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#131416;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding:.375rem .75rem;margin-bottom:1rem;list-style:none;background-color:#f8f5f0;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, ">") /* rtl: var(--bs-breadcrumb-divider, ">") */}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;display:-webkit-flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#6c757d;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#f8f5f0;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#6c757d;background-color:#f8f5f0;border-color:#dee2e6}.page-link:focus{z-index:3;color:#2270be;background-color:#f8f5f0;outline:0;box-shadow:0 0 0 .25rem rgba(19,20,22,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#6c757d;background-color:#dee2e6;border-color:#dee2e6}.page-item.disabled .page-link{color:#dee2e6;pointer-events:none;background-color:#f8f5f0;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:0.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2em;border-bottom-left-radius:.2em}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2em;border-bottom-right-radius:.2em}.badge{display:inline-block;padding:.35em .65em;font-size:0.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid rgba(0,0,0,0);border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-default{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-default .alert-link{color:#34383c}.alert-primary{color:#0b0c0d;background-color:#d0d0d0;border-color:#b8b9b9}.alert-primary .alert-link{color:#090a0a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#58762d;background-color:#e9f3db;border-color:#dfeec9}.alert-success .alert-link{color:#465e24}.alert-info{color:#196786;background-color:#d4eef9;border-color:#bfe6f6}.alert-info .alert-link{color:#14526b}.alert-warning{color:#924a24;background-color:#fde5d8;border-color:#fcd8c5}.alert-warning .alert-link{color:#753b1d}.alert-danger{color:#82322f;background-color:#f7dddc;border-color:#f4cbca}.alert-danger .alert-link{color:#682826}.alert-light{color:#959390;background-color:#fefdfc;border-color:#fdfcfb}.alert-light .alert-link{color:#777673}.alert-dark{color:#252623;background-color:#d8d9d8;border-color:#c5c5c4}.alert-dark .alert-link{color:#1e1e1c}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;display:-webkit-flex;height:1rem;overflow:hidden;font-size:0.75rem;background-color:#dee2e6;border-radius:10px}.progress-bar{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;justify-content:center;-webkit-justify-content:center;overflow:hidden;color:#131416;text-align:center;white-space:nowrap;background-color:#131416;transition:width .6s ease}@media(prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-size:1rem 1rem}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media(prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.list-group{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:rgba(255,255,255,.8);text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:rgba(255,255,255,.8);text-decoration:none;background-color:#f8f5f0}.list-group-item-action:active{color:rgba(255,255,255,.8);background-color:#dee2e6}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid #dee2e6}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#adb5bd;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:rgba(255,255,255,.8);background-color:#f8f5f0;border-color:#dee2e6}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media(min-width: 576px){.list-group-horizontal-sm{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 768px){.list-group-horizontal-md{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 992px){.list-group-horizontal-lg{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1200px){.list-group-horizontal-xl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-default{color:#41464b;background-color:#e2e3e5}.list-group-item-default.list-group-item-action:hover,.list-group-item-default.list-group-item-action:focus{color:#41464b;background-color:#cbccce}.list-group-item-default.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-primary{color:#0b0c0d;background-color:#d0d0d0}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#0b0c0d;background-color:#bbb}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#0b0c0d;border-color:#0b0c0d}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#58762d;background-color:#e9f3db}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#58762d;background-color:#d2dbc5}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#58762d;border-color:#58762d}.list-group-item-info{color:#196786;background-color:#d4eef9}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#196786;background-color:#bfd6e0}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#196786;border-color:#196786}.list-group-item-warning{color:#924a24;background-color:#fde5d8}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#924a24;background-color:#e4cec2}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#924a24;border-color:#924a24}.list-group-item-danger{color:#82322f;background-color:#f7dddc}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#82322f;background-color:#dec7c6}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#82322f;border-color:#82322f}.list-group-item-light{color:#959390;background-color:#fefdfc}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#959390;background-color:#e5e4e3}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#959390;border-color:#959390}.list-group-item-dark{color:#252623;background-color:#d8d9d8}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#252623;background-color:#c2c3c2}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#252623;border-color:#252623}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#fff;background:rgba(0,0,0,0) url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.8}.btn-close:hover{color:#fff;text-decoration:none;opacity:1}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(19,20,22,.25);opacity:1}.btn-close:disabled,.btn-close.disabled{pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:0.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:max-content;width:-webkit-max-content;width:-moz-max-content;width:-ms-max-content;width:-o-max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.toast-header .btn-close{margin-right:-0.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0, -50px)}@media(prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid #dee2e6;border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-0.5rem -0.5rem -0.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem}.modal-footer{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:flex-end;-webkit-justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(0.3rem - 1px);border-bottom-left-radius:calc(0.3rem - 1px)}.modal-footer>*{margin:.25rem}@media(min-width: 576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media(min-width: 992px){.modal-lg,.modal-xl{max-width:800px}}@media(min-width: 1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media(max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media(max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media(max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media(max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media(max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:"Open Sans",sans-serif;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[data-popper-placement^=top]{padding:.4rem 0}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:0}.bs-tooltip-top .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-end,.bs-tooltip-auto[data-popper-placement^=right]{padding:0 .4rem}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-end .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-bottom,.bs-tooltip-auto[data-popper-placement^=bottom]{padding:.4rem 0}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:0}.bs-tooltip-bottom .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-start,.bs-tooltip-auto[data-popper-placement^=left]{padding:0 .4rem}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-start .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0 /* rtl:ignore */;z-index:1070;display:block;max-width:276px;font-family:"Open Sans",sans-serif;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;background-color:#131416;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-0.5rem - 1px)}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#131416}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#131416}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-0.5rem - 1px)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#131416}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-0.5rem;content:"";border-bottom:1px solid #f8f5f0}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#131416}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f8f5f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:rgba(255,255,255,.8)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y;-webkit-touch-action:pan-y;-moz-touch-action:pan-y;-ms-touch-action:pan-y;-o-touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media(prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:center;-webkit-justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;display:-webkit-flex;justify-content:center;-webkit-justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;-webkit-flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid rgba(0,0,0,0);border-bottom:10px solid rgba(0,0,0,0);opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;border:.25em solid currentColor;border-right-color:rgba(0,0,0,0);border-radius:50%;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;background-color:currentColor;border-radius:50%;opacity:0;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media(prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{animation-duration:1.5s;-webkit-animation-duration:1.5s;-moz-animation-duration:1.5s;-ms-animation-duration:1.5s;-o-animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media(prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-0.5rem;margin-right:-0.5rem;margin-bottom:-0.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;-webkit-flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid #dee2e6;transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid #dee2e6;transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid #dee2e6;transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid #dee2e6;transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);-webkit-mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);mask-size:200% 100%;-webkit-mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{mask-position:-200% 0%;-webkit-mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-default{color:#6c757d}.link-default:hover,.link-default:focus{color:#565e64}.link-primary{color:#131416}.link-primary:hover,.link-primary:focus{color:#0f1012}.link-secondary{color:#6c757d}.link-secondary:hover,.link-secondary:focus{color:#565e64}.link-success{color:#93c54b}.link-success:hover,.link-success:focus{color:#769e3c}.link-info{color:#29abe0}.link-info:hover,.link-info:focus{color:#2189b3}.link-warning{color:#f47c3c}.link-warning:hover,.link-warning:focus{color:#c36330}.link-danger{color:#d9534f}.link-danger:hover,.link-danger:focus{color:#ae423f}.link-light{color:#f8f5f0}.link-light:hover,.link-light:focus{color:#f9f7f3}.link-dark{color:#3e3f3a}.link-dark:hover,.link-dark:focus{color:#32322e}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: 75%}.ratio-16x9{--bs-aspect-ratio: 56.25%}.ratio-21x9{--bs-aspect-ratio: 42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}@media(min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}}@media(min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}}@media(min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}}.hstack{display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;align-items:center;-webkit-align-items:center;align-self:stretch;-webkit-align-self:stretch}.vstack{display:flex;display:-webkit-flex;flex:1 1 auto;-webkit-flex:1 1 auto;flex-direction:column;-webkit-flex-direction:column;align-self:stretch;-webkit-align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;-webkit-align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.opacity-0{opacity:0 !important}.opacity-25{opacity:.25 !important}.opacity-50{opacity:.5 !important}.opacity-75{opacity:.75 !important}.opacity-100{opacity:1 !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:1px solid #dee2e6 !important}.border-0{border:0 !important}.border-top{border-top:1px solid #dee2e6 !important}.border-top-0{border-top:0 !important}.border-end{border-right:1px solid #dee2e6 !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:1px solid #dee2e6 !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:1px solid #dee2e6 !important}.border-start-0{border-left:0 !important}.border-default{border-color:#6c757d !important}.border-primary{border-color:#131416 !important}.border-secondary{border-color:#6c757d !important}.border-success{border-color:#93c54b !important}.border-info{border-color:#29abe0 !important}.border-warning{border-color:#f47c3c !important}.border-danger{border-color:#d9534f !important}.border-light{border-color:#f8f5f0 !important}.border-dark{border-color:#3e3f3a !important}.border-white{border-color:#fff !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.325rem + 0.9vw) !important}.fs-2{font-size:calc(1.29rem + 0.48vw) !important}.fs-3{font-size:calc(1.27rem + 0.24vw) !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1.1rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-light{font-weight:300 !important}.fw-lighter{font-weight:lighter !important}.fw-normal{font-weight:400 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.5 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-default{--bs-text-opacity: 1;color:rgba(var(--bs-default-rgb), var(--bs-text-opacity)) !important}.text-primary{--bs-text-opacity: 1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important}.text-secondary{--bs-text-opacity: 1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important}.text-success{--bs-text-opacity: 1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important}.text-info{--bs-text-opacity: 1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important}.text-warning{--bs-text-opacity: 1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important}.text-danger{--bs-text-opacity: 1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important}.text-light{--bs-text-opacity: 1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important}.text-dark{--bs-text-opacity: 1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important}.text-black{--bs-text-opacity: 1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important}.text-white{--bs-text-opacity: 1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important}.text-body{--bs-text-opacity: 1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important}.text-muted{--bs-text-opacity: 1;color:#6c757d !important}.text-black-50{--bs-text-opacity: 1;color:rgba(0,0,0,.5) !important}.text-white-50{--bs-text-opacity: 1;color:rgba(255,255,255,.5) !important}.text-reset{--bs-text-opacity: 1;color:inherit !important}.text-opacity-25{--bs-text-opacity: 0.25}.text-opacity-50{--bs-text-opacity: 0.5}.text-opacity-75{--bs-text-opacity: 0.75}.text-opacity-100{--bs-text-opacity: 1}.bg-default{--bs-bg-opacity: 1;background-color:rgba(var(--bs-default-rgb), var(--bs-bg-opacity)) !important}.bg-primary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important}.bg-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important}.bg-success{--bs-bg-opacity: 1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important}.bg-info{--bs-bg-opacity: 1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important}.bg-warning{--bs-bg-opacity: 1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important}.bg-danger{--bs-bg-opacity: 1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important}.bg-light{--bs-bg-opacity: 1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important}.bg-dark{--bs-bg-opacity: 1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important}.bg-black{--bs-bg-opacity: 1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important}.bg-white{--bs-bg-opacity: 1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important}.bg-body{--bs-bg-opacity: 1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important}.bg-transparent{--bs-bg-opacity: 1;background-color:rgba(0,0,0,0) !important}.bg-opacity-10{--bs-bg-opacity: 0.1}.bg-opacity-25{--bs-bg-opacity: 0.25}.bg-opacity-50{--bs-bg-opacity: 0.5}.bg-opacity-75{--bs-bg-opacity: 0.75}.bg-opacity-100{--bs-bg-opacity: 1}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{user-select:all !important}.user-select-auto{user-select:auto !important}.user-select-none{user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:.2em !important}.rounded-2{border-radius:.25rem !important}.rounded-3{border-radius:.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-top{border-top-left-radius:.25rem !important;border-top-right-radius:.25rem !important}.rounded-end{border-top-right-radius:.25rem !important;border-bottom-right-radius:.25rem !important}.rounded-bottom{border-bottom-right-radius:.25rem !important;border-bottom-left-radius:.25rem !important}.rounded-start{border-bottom-left-radius:.25rem !important;border-top-left-radius:.25rem !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media(min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media(min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media(min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media(min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media(min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}.bg-default{color:#fff}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#fff}.bg-warning{color:#fff}.bg-danger{color:#fff}.bg-light{color:#000}.bg-dark{color:#fff}@media(min-width: 1200px){.fs-1{font-size:2rem !important}.fs-2{font-size:1.65rem !important}.fs-3{font-size:1.45rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}}.quarto-container{min-height:calc(100vh - 132px)}footer.footer .nav-footer,#quarto-header>nav{padding-left:1em;padding-right:1em}nav[role=doc-toc]{padding-left:.5em}#quarto-content>*{padding-top:14px}@media(max-width: 991.98px){#quarto-content>*{padding-top:0}#quarto-content .subtitle{padding-top:14px}#quarto-content section:first-of-type h2:first-of-type,#quarto-content section:first-of-type .h2:first-of-type{margin-top:1rem}}.headroom-target,header.headroom{will-change:transform;transition:position 200ms linear;transition:all 200ms linear}header.headroom--pinned{transform:translateY(0%)}header.headroom--unpinned{transform:translateY(-100%)}.navbar-container{width:100%}.navbar-brand{overflow:hidden;text-overflow:ellipsis}.navbar-brand-container{max-width:calc(100% - 115px);min-width:0;display:flex;align-items:center}@media(min-width: 992px){.navbar-brand-container{margin-right:1em}}.navbar-brand.navbar-brand-logo{margin-right:4px;display:inline-flex}.navbar-toggler{flex-basis:content;flex-shrink:0}.navbar .navbar-toggler{order:-1;margin-right:.5em}.navbar-logo{max-height:24px;width:auto;padding-right:4px}nav .nav-item:not(.compact){padding-top:1px}nav .nav-link i,nav .dropdown-item i{padding-right:1px}.navbar-expand-lg .navbar-nav .nav-link{padding-left:.6rem;padding-right:.6rem}nav .nav-item.compact .nav-link{padding-left:.5rem;padding-right:.5rem;font-size:1.1rem}.navbar .quarto-navbar-tools div.dropdown{display:inline-block}.navbar .quarto-navbar-tools .quarto-navigation-tool{color:rgba(255,255,255,.8)}.navbar .quarto-navbar-tools .quarto-navigation-tool:hover{color:#64abf3}@media(max-width: 991.98px){.navbar .quarto-navbar-tools{margin-top:.25em;padding-top:.75em;display:block;color:solid rgba(128,128,128,.8) 1px;text-align:center;vertical-align:middle;margin-right:auto}}.navbar-nav .dropdown-menu{min-width:220px;font-size:.9rem}.navbar .navbar-nav .nav-link.dropdown-toggle::after{opacity:.75;vertical-align:.175em}.navbar ul.dropdown-menu{padding-top:0;padding-bottom:0}.navbar .dropdown-header{text-transform:uppercase;font-size:.8rem;padding:0 .5rem}.navbar .dropdown-item{padding:.4rem .5rem}.navbar .dropdown-item>i.bi{margin-left:.1rem;margin-right:.25em}.sidebar #quarto-search{margin-top:-1px}.sidebar #quarto-search svg.aa-SubmitIcon{width:16px;height:16px}.sidebar-navigation a{color:inherit}.sidebar-title{margin-top:.25rem;padding-bottom:.5rem;font-size:1.3rem;line-height:1.6rem;visibility:visible}.sidebar-title>a{font-size:inherit;text-decoration:none}.sidebar-title .sidebar-tools-main{margin-top:-6px}@media(max-width: 991.98px){#quarto-sidebar div.sidebar-header{padding-top:.2em}}.sidebar-header-stacked .sidebar-title{margin-top:.6rem}.sidebar-logo{max-width:90%;padding-bottom:.5rem}.sidebar-logo-link{text-decoration:none}.sidebar-navigation li a{text-decoration:none}.sidebar-navigation .quarto-navigation-tool{opacity:.7;font-size:.875rem}#quarto-sidebar>nav>.sidebar-tools-main{margin-left:14px}.sidebar-tools-main{display:inline-flex;margin-left:0px;order:2}.sidebar-tools-main:not(.tools-wide){vertical-align:middle}.sidebar-navigation .quarto-navigation-tool.dropdown-toggle::after{display:none}.sidebar.sidebar-navigation>*{padding-top:1em}.sidebar-item{margin-bottom:.2em}.sidebar-section{margin-top:.2em;padding-left:.5em;padding-bottom:.2em}.sidebar-item .sidebar-item-container{display:flex;justify-content:space-between}.sidebar-item-toggle:hover{cursor:pointer}.sidebar-item .sidebar-item-toggle .bi{font-size:.7rem;text-align:center}.sidebar-item .sidebar-item-toggle .bi-chevron-right::before{transition:transform 200ms ease}.sidebar-item .sidebar-item-toggle[aria-expanded=false] .bi-chevron-right::before{transform:none}.sidebar-item .sidebar-item-toggle[aria-expanded=true] .bi-chevron-right::before{transform:rotate(90deg)}.sidebar-navigation .sidebar-divider{margin-left:0;margin-right:0;margin-top:.5rem;margin-bottom:.5rem}@media(max-width: 991.98px){.quarto-secondary-nav{display:block}.quarto-secondary-nav button.quarto-search-button{padding-right:0em;padding-left:2em}.quarto-secondary-nav button.quarto-btn-toggle{margin-left:-0.75rem;margin-right:.15rem}.quarto-secondary-nav nav.quarto-page-breadcrumbs{display:flex;align-items:center;padding-right:1em;margin-left:-0.25em}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{text-decoration:none}.quarto-secondary-nav nav.quarto-page-breadcrumbs ol.breadcrumb{margin-bottom:0}}@media(min-width: 992px){.quarto-secondary-nav{display:none}}.quarto-secondary-nav .quarto-btn-toggle{color:#fff}.quarto-secondary-nav[aria-expanded=false] .quarto-btn-toggle .bi-chevron-right::before{transform:none}.quarto-secondary-nav[aria-expanded=true] .quarto-btn-toggle .bi-chevron-right::before{transform:rotate(90deg)}.quarto-secondary-nav .quarto-btn-toggle .bi-chevron-right::before{transition:transform 200ms ease}.quarto-secondary-nav{cursor:pointer}.quarto-secondary-nav-title{margin-top:.3em;color:#fff;padding-top:4px}.quarto-secondary-nav nav.quarto-page-breadcrumbs{color:#fff}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{color:#fff}.quarto-secondary-nav nav.quarto-page-breadcrumbs a:hover{color:rgba(88,164,242,.8)}.quarto-secondary-nav nav.quarto-page-breadcrumbs .breadcrumb-item::before{color:#ccc}div.sidebar-item-container{color:#fff}div.sidebar-item-container:hover,div.sidebar-item-container:focus{color:rgba(88,164,242,.8)}div.sidebar-item-container.disabled{color:rgba(255,255,255,.75)}div.sidebar-item-container .active,div.sidebar-item-container .show>.nav-link,div.sidebar-item-container .sidebar-link>code{color:#58a4f2}div.sidebar.sidebar-navigation.rollup.quarto-sidebar-toggle-contents,nav.sidebar.sidebar-navigation:not(.rollup){background-color:#131416}@media(max-width: 991.98px){.sidebar-navigation .sidebar-item a,.nav-page .nav-page-text,.sidebar-navigation{font-size:1rem}.sidebar-navigation ul.sidebar-section.depth1 .sidebar-section-item{font-size:1.1rem}.sidebar-logo{display:none}.sidebar.sidebar-navigation{position:static;border-bottom:1px solid #dee2e6}.sidebar.sidebar-navigation.collapsing{position:fixed;z-index:1000}.sidebar.sidebar-navigation.show{position:fixed;z-index:1000}.sidebar.sidebar-navigation{min-height:100%}nav.quarto-secondary-nav{background-color:#131416;border-bottom:1px solid #dee2e6}.sidebar .sidebar-footer{visibility:visible;padding-top:1rem;position:inherit}.sidebar-tools-collapse{display:block}}#quarto-sidebar{transition:width .15s ease-in}#quarto-sidebar>*{padding-right:1em}@media(max-width: 991.98px){#quarto-sidebar .sidebar-menu-container{white-space:nowrap;min-width:225px}#quarto-sidebar.show{transition:width .15s ease-out}}@media(min-width: 992px){#quarto-sidebar{display:flex;flex-direction:column}.nav-page .nav-page-text,.sidebar-navigation .sidebar-section .sidebar-item{font-size:.875rem}.sidebar-navigation .sidebar-item{font-size:.925rem}.sidebar.sidebar-navigation{display:block;position:sticky}.sidebar-search{width:100%}.sidebar .sidebar-footer{visibility:visible}}@media(max-width: 991.98px){#quarto-sidebar-glass{position:fixed;top:0;bottom:0;left:0;right:0;background-color:rgba(255,255,255,0);transition:background-color .15s ease-in;z-index:-1}#quarto-sidebar-glass.collapsing{z-index:1000}#quarto-sidebar-glass.show{transition:background-color .15s ease-out;background-color:rgba(102,102,102,.4);z-index:1000}}.sidebar .sidebar-footer{padding:.5rem 1rem;align-self:flex-end;color:#6c757d;width:100%}.quarto-page-breadcrumbs .breadcrumb-item+.breadcrumb-item,.quarto-page-breadcrumbs .breadcrumb-item{padding-right:.33em;padding-left:0}.quarto-page-breadcrumbs .breadcrumb-item::before{padding-right:.33em}.quarto-sidebar-footer{font-size:.875em}.sidebar-section .bi-chevron-right{vertical-align:middle}.sidebar-section .bi-chevron-right::before{font-size:.9em}.notransition{-webkit-transition:none !important;-moz-transition:none !important;-o-transition:none !important;transition:none !important}.btn:focus:not(:focus-visible){box-shadow:none}.page-navigation{display:flex;justify-content:space-between}.nav-page{padding-bottom:.75em}.nav-page .bi{font-size:1.8rem;vertical-align:middle}.nav-page .nav-page-text{padding-left:.25em;padding-right:.25em}.nav-page a{color:#6c757d;text-decoration:none;display:flex;align-items:center}.nav-page a:hover{color:#2270be}.toc-actions{display:flex}.toc-actions p{margin-block-start:0;margin-block-end:0}.toc-actions a{text-decoration:none;color:inherit;font-weight:400}.toc-actions a:hover{color:#2270be}.toc-actions .action-links{margin-left:4px}.sidebar nav[role=doc-toc] .toc-actions .bi{margin-left:-4px;font-size:.7rem;color:#6c757d}.sidebar nav[role=doc-toc] .toc-actions .bi:before{padding-top:3px}#quarto-margin-sidebar .toc-actions .bi:before{margin-top:.3rem;font-size:.7rem;color:#6c757d;vertical-align:top}.sidebar nav[role=doc-toc] .toc-actions>div:first-of-type{margin-top:-3px}#quarto-margin-sidebar .toc-actions p,.sidebar nav[role=doc-toc] .toc-actions p{font-size:.875rem}.nav-footer .toc-actions{padding-bottom:.5em;padding-top:.5em}.nav-footer .toc-actions :first-child{margin-left:auto}.nav-footer .toc-actions :last-child{margin-right:auto}.nav-footer .toc-actions .action-links{display:flex}.nav-footer .toc-actions .action-links p{padding-right:1.5em}.nav-footer .toc-actions .action-links p:last-of-type{padding-right:0}.nav-footer{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:baseline;text-align:center;padding-top:.5rem;padding-bottom:.5rem;background-color:#131416}body.nav-fixed{padding-top:63px}.nav-footer-contents{color:#6c757d;margin-top:.25rem}.nav-footer{min-height:3.5em;color:#7d7e7f}.nav-footer a{color:#7d7e7f}.nav-footer .nav-footer-left{font-size:.825em}.nav-footer .nav-footer-center{font-size:.825em}.nav-footer .nav-footer-right{font-size:.825em}.nav-footer-left .footer-items,.nav-footer-center .footer-items,.nav-footer-right .footer-items{display:inline-flex;padding-top:.3em;padding-bottom:.3em;margin-bottom:0em}.nav-footer-left .footer-items .nav-link,.nav-footer-center .footer-items .nav-link,.nav-footer-right .footer-items .nav-link{padding-left:.6em;padding-right:.6em}.nav-footer-left{flex:1 1 0px;text-align:left}.nav-footer-right{flex:1 1 0px;text-align:right}.nav-footer-center{flex:1 1 0px;min-height:3em;text-align:center}.nav-footer-center .footer-items{justify-content:center}@media(max-width: 767.98px){.nav-footer-center{margin-top:3em}}.navbar .quarto-reader-toggle.reader .quarto-reader-toggle-btn{background-color:rgba(255,255,255,.8);border-radius:3px}.quarto-reader-toggle.reader.quarto-navigation-tool .quarto-reader-toggle-btn{background-color:rgba(255,255,255,.8);border-radius:3px}.quarto-reader-toggle .quarto-reader-toggle-btn{display:inline-flex;padding-left:.2em;padding-right:.2em;margin-left:-0.2em;margin-right:-0.2em;text-align:center}.navbar .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}#quarto-back-to-top{display:none;position:fixed;bottom:50px;background-color:#131416;border-radius:.25rem;box-shadow:0 .2rem .5rem #6c757d,0 0 .05rem #6c757d;color:#6c757d;text-decoration:none;font-size:.9em;text-align:center;left:50%;padding:.4rem .8rem;transform:translate(-50%, 0)}.aa-DetachedOverlay ul.aa-List,#quarto-search-results ul.aa-List{list-style:none;padding-left:0}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{background-color:#131416;position:absolute;z-index:2000}#quarto-search-results .aa-Panel{max-width:400px}#quarto-search input{font-size:.925rem}@media(min-width: 992px){.navbar #quarto-search{margin-left:.25rem;order:999}}@media(max-width: 991.98px){#quarto-sidebar .sidebar-search{display:none}}#quarto-sidebar .sidebar-search .aa-Autocomplete{width:100%}.navbar .aa-Autocomplete .aa-Form{width:180px}.navbar #quarto-search.type-overlay .aa-Autocomplete{width:40px}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form{background-color:inherit;border:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form:focus-within{box-shadow:none;outline:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper{display:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper:focus-within{display:inherit}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-Label svg,.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-LoadingIndicator svg{width:26px;height:26px;color:rgba(255,255,255,.8);opacity:1}.navbar #quarto-search.type-overlay .aa-Autocomplete svg.aa-SubmitIcon{width:26px;height:26px;color:rgba(255,255,255,.8);opacity:1}.aa-Autocomplete .aa-Form,.aa-DetachedFormContainer .aa-Form{align-items:center;background-color:#131416;border:1px solid #ced4da;border-radius:.25rem;color:rgba(255,255,255,.8);display:flex;line-height:1em;margin:0;position:relative;width:100%}.aa-Autocomplete .aa-Form:focus-within,.aa-DetachedFormContainer .aa-Form:focus-within{box-shadow:rgba(19,20,22,.6) 0 0 0 1px;outline:currentColor none medium}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix{align-items:center;display:flex;flex-shrink:0;order:1}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{cursor:initial;flex-shrink:0;padding:0;text-align:left}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg{color:rgba(255,255,255,.8);opacity:.5}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton{appearance:none;background:none;border:0;margin:0}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{align-items:center;display:flex;justify-content:center}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapper,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper{order:3;position:relative;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input{appearance:none;background:none;border:0;color:rgba(255,255,255,.8);font:inherit;height:calc(1.5em + .1rem + 2px);padding:0;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::placeholder,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::placeholder{color:rgba(255,255,255,.8);opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input:focus{border-color:none;box-shadow:none;outline:none}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix{align-items:center;display:flex;order:4}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton{align-items:center;background:none;border:0;color:rgba(255,255,255,.8);opacity:.8;cursor:pointer;display:flex;margin:0;width:calc(1.5em + .1rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus{color:rgba(255,255,255,.8);opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg{width:calc(1.5em + 0.75rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton{border:none;align-items:center;background:none;color:rgba(255,255,255,.8);opacity:.4;font-size:.7rem;cursor:pointer;display:none;margin:0;width:calc(1em + .1rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus{color:rgba(255,255,255,.8);opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden]{display:none}.aa-PanelLayout:empty{display:none}.quarto-search-no-results.no-query{display:none}.aa-Source:has(.no-query){display:none}#quarto-search-results .aa-Panel{border:solid #ced4da 1px}#quarto-search-results .aa-SourceNoResults{width:398px}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{max-height:65vh;overflow-y:auto;font-size:.925rem}.aa-DetachedOverlay .aa-SourceNoResults,#quarto-search-results .aa-SourceNoResults{height:60px;display:flex;justify-content:center;align-items:center}.aa-DetachedOverlay .search-error,#quarto-search-results .search-error{padding-top:10px;padding-left:20px;padding-right:20px;cursor:default}.aa-DetachedOverlay .search-error .search-error-title,#quarto-search-results .search-error .search-error-title{font-size:1.1rem;margin-bottom:.5rem}.aa-DetachedOverlay .search-error .search-error-title .search-error-icon,#quarto-search-results .search-error .search-error-title .search-error-icon{margin-right:8px}.aa-DetachedOverlay .search-error .search-error-text,#quarto-search-results .search-error .search-error-text{font-weight:300}.aa-DetachedOverlay .search-result-text,#quarto-search-results .search-result-text{font-weight:300;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.2rem;max-height:2.4rem}.aa-DetachedOverlay .aa-SourceHeader .search-result-header,#quarto-search-results .aa-SourceHeader .search-result-header{font-size:.875rem;background-color:#1f2024;padding-left:14px;padding-bottom:4px;padding-top:4px}.aa-DetachedOverlay .aa-SourceHeader .search-result-header-no-results,#quarto-search-results .aa-SourceHeader .search-result-header-no-results{display:none}.aa-DetachedOverlay .aa-SourceFooter .algolia-search-logo,#quarto-search-results .aa-SourceFooter .algolia-search-logo{width:110px;opacity:.85;margin:8px;float:right}.aa-DetachedOverlay .search-result-section,#quarto-search-results .search-result-section{font-size:.925em}.aa-DetachedOverlay a.search-result-link,#quarto-search-results a.search-result-link{color:inherit;text-decoration:none}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item,#quarto-search-results li.aa-Item[aria-selected=true] .search-item{background-color:#131416}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text-container{color:#fff;background-color:#131416}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=true] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-match.mark{color:#fff;background-color:#000}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item,#quarto-search-results li.aa-Item[aria-selected=false] .search-item{background-color:#131416}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text-container{color:rgba(255,255,255,.8)}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=false] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-match.mark{color:inherit;background-color:#000}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container{background-color:#131416;color:rgba(255,255,255,.8)}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container{padding-top:0px}.aa-DetachedOverlay li.aa-Item .search-result-doc.document-selectable .search-result-text-container,#quarto-search-results li.aa-Item .search-result-doc.document-selectable .search-result-text-container{margin-top:-4px}.aa-DetachedOverlay .aa-Item,#quarto-search-results .aa-Item{cursor:pointer}.aa-DetachedOverlay .aa-Item .search-item,#quarto-search-results .aa-Item .search-item{border-left:none;border-right:none;border-top:none;background-color:#131416;border-color:#ced4da;color:rgba(255,255,255,.8)}.aa-DetachedOverlay .aa-Item .search-item p,#quarto-search-results .aa-Item .search-item p{margin-top:0;margin-bottom:0}.aa-DetachedOverlay .aa-Item .search-item i.bi,#quarto-search-results .aa-Item .search-item i.bi{padding-left:8px;padding-right:8px;font-size:1.3em}.aa-DetachedOverlay .aa-Item .search-item .search-result-title,#quarto-search-results .aa-Item .search-item .search-result-title{margin-top:.3em;margin-bottom:.1rem}.aa-DetachedOverlay .aa-Item .search-result-title-container,#quarto-search-results .aa-Item .search-result-title-container{font-size:1em;display:flex;padding:6px 4px 6px 4px}.aa-DetachedOverlay .aa-Item .search-result-text-container,#quarto-search-results .aa-Item .search-result-text-container{padding-bottom:8px;padding-right:8px;margin-left:44px}.aa-DetachedOverlay .aa-Item .search-result-doc-section,.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-doc-section,#quarto-search-results .aa-Item .search-result-more{padding-top:8px;padding-bottom:8px;padding-left:44px}.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-more{font-size:.8em;font-weight:400}.aa-DetachedOverlay .aa-Item .search-result-doc,#quarto-search-results .aa-Item .search-result-doc{border-top:1px solid #ced4da}.aa-DetachedSearchButton{background:none;border:none}.aa-DetachedSearchButton .aa-DetachedSearchButtonPlaceholder{display:none}.navbar .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:rgba(255,255,255,.8)}.sidebar-tools-collapse #quarto-search,.sidebar-tools-main #quarto-search{display:inline}.sidebar-tools-collapse #quarto-search .aa-Autocomplete,.sidebar-tools-main #quarto-search .aa-Autocomplete{display:inline}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton{padding-left:4px;padding-right:4px}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:rgba(255,255,255,.8)}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon{margin-top:-3px}.aa-DetachedContainer{background:rgba(19,20,22,.65);width:90%;bottom:0;box-shadow:rgba(206,212,218,.6) 0 0 0 1px;outline:currentColor none medium;display:flex;flex-direction:column;left:0;margin:0;overflow:hidden;padding:0;position:fixed;right:0;top:0;z-index:1101}.aa-DetachedContainer::after{height:32px}.aa-DetachedContainer .aa-SourceHeader{margin:var(--aa-spacing-half) 0 var(--aa-spacing-half) 2px}.aa-DetachedContainer .aa-Panel{background-color:#131416;border-radius:0;box-shadow:none;flex-grow:1;margin:0;padding:0;position:relative}.aa-DetachedContainer .aa-PanelLayout{bottom:0;box-shadow:none;left:0;margin:0;max-height:none;overflow-y:auto;position:absolute;right:0;top:0;width:100%}.aa-DetachedFormContainer{background-color:#131416;border-bottom:1px solid #ced4da;display:flex;flex-direction:row;justify-content:space-between;margin:0;padding:.5em}.aa-DetachedCancelButton{background:none;font-size:.8em;border:0;border-radius:3px;color:rgba(255,255,255,.8);cursor:pointer;margin:0 0 0 .5em;padding:0 .5em}.aa-DetachedCancelButton:hover,.aa-DetachedCancelButton:focus{box-shadow:rgba(19,20,22,.6) 0 0 0 1px;outline:currentColor none medium}.aa-DetachedContainer--modal{bottom:inherit;height:auto;margin:0 auto;position:absolute;top:100px;border-radius:6px;max-width:850px}@media(max-width: 575.98px){.aa-DetachedContainer--modal{width:100%;top:0px;border-radius:0px;border:none}}.aa-DetachedContainer--modal .aa-PanelLayout{max-height:var(--aa-detached-modal-max-height);padding-bottom:var(--aa-spacing-half);position:static}.aa-Detached{height:100vh;overflow:hidden}.aa-DetachedOverlay{background-color:rgba(255,255,255,.4);position:fixed;left:0;right:0;top:0;margin:0;padding:0;height:100vh;z-index:1100}.quarto-listing{padding-bottom:1em}.listing-pagination{padding-top:.5em}ul.pagination{float:right;padding-left:8px;padding-top:.5em}ul.pagination li{padding-right:.75em}ul.pagination li.disabled a,ul.pagination li.active a{color:rgba(255,255,255,.8);text-decoration:none}ul.pagination li:last-of-type{padding-right:0}.listing-actions-group{display:flex}.quarto-listing-filter{margin-bottom:1em;width:200px;margin-left:auto}.quarto-listing-sort{margin-bottom:1em;margin-right:auto;width:auto}.quarto-listing-sort .input-group-text{font-size:.8em}.input-group-text{border-right:none}.quarto-listing-sort select.form-select{font-size:.8em}.listing-no-matching{text-align:center;padding-top:2em;padding-bottom:3em;font-size:1em}#quarto-margin-sidebar .quarto-listing-category{padding-top:0;font-size:1rem}#quarto-margin-sidebar .quarto-listing-category-title{cursor:pointer;font-weight:600;font-size:1rem}.quarto-listing-category .category{cursor:pointer}.quarto-listing-category .category.active{font-weight:600}.quarto-listing-category.category-cloud{display:flex;flex-wrap:wrap;align-items:baseline}.quarto-listing-category.category-cloud .category{padding-right:5px}.quarto-listing-category.category-cloud .category-cloud-1{font-size:.75em}.quarto-listing-category.category-cloud .category-cloud-2{font-size:.95em}.quarto-listing-category.category-cloud .category-cloud-3{font-size:1.15em}.quarto-listing-category.category-cloud .category-cloud-4{font-size:1.35em}.quarto-listing-category.category-cloud .category-cloud-5{font-size:1.55em}.quarto-listing-category.category-cloud .category-cloud-6{font-size:1.75em}.quarto-listing-category.category-cloud .category-cloud-7{font-size:1.95em}.quarto-listing-category.category-cloud .category-cloud-8{font-size:2.15em}.quarto-listing-category.category-cloud .category-cloud-9{font-size:2.35em}.quarto-listing-category.category-cloud .category-cloud-10{font-size:2.55em}.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-1{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-2{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-3{grid-template-columns:repeat(3, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-3{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-3{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-4{grid-template-columns:repeat(4, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-4{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-4{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-5{grid-template-columns:repeat(5, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-5{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-5{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-6{grid-template-columns:repeat(6, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-6{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-6{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-7{grid-template-columns:repeat(7, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-7{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-7{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-8{grid-template-columns:repeat(8, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-8{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-8{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-9{grid-template-columns:repeat(9, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-9{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-9{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-10{grid-template-columns:repeat(10, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-10{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-10{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-11{grid-template-columns:repeat(11, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-11{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-11{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-12{grid-template-columns:repeat(12, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-12{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-12{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-grid{gap:1.5em}.quarto-grid-item.borderless{border:none}.quarto-grid-item.borderless .listing-categories .listing-category:last-of-type,.quarto-grid-item.borderless .listing-categories .listing-category:first-of-type{padding-left:0}.quarto-grid-item.borderless .listing-categories .listing-category{border:0}.quarto-grid-link{text-decoration:none;color:inherit}.quarto-grid-link:hover{text-decoration:none;color:inherit}.quarto-grid-item h5.title,.quarto-grid-item .title.h5{margin-top:0;margin-bottom:0}.quarto-grid-item .card-footer{display:flex;justify-content:space-between;font-size:.8em}.quarto-grid-item .card-footer p{margin-bottom:0}.quarto-grid-item p.card-img-top{margin-bottom:0}.quarto-grid-item p.card-img-top>img{object-fit:cover}.quarto-grid-item .card-other-values{margin-top:.5em;font-size:.8em}.quarto-grid-item .card-other-values tr{margin-bottom:.5em}.quarto-grid-item .card-other-values tr>td:first-of-type{font-weight:600;padding-right:1em;padding-left:1em;vertical-align:top}.quarto-grid-item div.post-contents{display:flex;flex-direction:column;text-decoration:none;height:100%}.quarto-grid-item .listing-item-img-placeholder{background-color:#adb5bd;flex-shrink:0}.quarto-grid-item .card-attribution{padding-top:1em;display:flex;gap:1em;text-transform:uppercase;color:#6c757d;font-weight:500;flex-grow:10;align-items:flex-end}.quarto-grid-item .description{padding-bottom:1em}.quarto-grid-item .card-attribution .date{align-self:flex-end}.quarto-grid-item .card-attribution.justify{justify-content:space-between}.quarto-grid-item .card-attribution.start{justify-content:flex-start}.quarto-grid-item .card-attribution.end{justify-content:flex-end}.quarto-grid-item .card-title{margin-bottom:.1em}.quarto-grid-item .card-subtitle{padding-top:.25em}.quarto-grid-item .card-text{font-size:.9em}.quarto-grid-item .listing-reading-time{padding-bottom:.25em}.quarto-grid-item .card-text-small{font-size:.8em}.quarto-grid-item .card-subtitle.subtitle{font-size:.9em;font-weight:600;padding-bottom:.5em}.quarto-grid-item .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}.quarto-grid-item .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}.quarto-grid-item.card-right{text-align:right}.quarto-grid-item.card-right .listing-categories{justify-content:flex-end}.quarto-grid-item.card-left{text-align:left}.quarto-grid-item.card-center{text-align:center}.quarto-grid-item.card-center .listing-description{text-align:justify}.quarto-grid-item.card-center .listing-categories{justify-content:center}table.quarto-listing-table td.image{padding:0px}table.quarto-listing-table td.image img{width:100%;max-width:50px;object-fit:contain}table.quarto-listing-table a{text-decoration:none}table.quarto-listing-table th a{color:inherit}table.quarto-listing-table th a.asc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table th a.desc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table.table-hover td{cursor:pointer}.quarto-post.image-left{flex-direction:row}.quarto-post.image-right{flex-direction:row-reverse}@media(max-width: 767.98px){.quarto-post.image-right,.quarto-post.image-left{gap:0em;flex-direction:column}.quarto-post .metadata{padding-bottom:1em;order:2}.quarto-post .body{order:1}.quarto-post .thumbnail{order:3}}.list.quarto-listing-default div:last-of-type{border-bottom:none}@media(min-width: 992px){.quarto-listing-container-default{margin-right:2em}}div.quarto-post{display:flex;gap:2em;margin-bottom:1.5em;border-bottom:1px solid #dee2e6}@media(max-width: 767.98px){div.quarto-post{padding-bottom:1em}}div.quarto-post .metadata{flex-basis:20%;flex-grow:0;margin-top:.2em;flex-shrink:10}div.quarto-post .thumbnail{flex-basis:30%;flex-grow:0;flex-shrink:0}div.quarto-post .thumbnail img{margin-top:.4em;width:100%;object-fit:cover}div.quarto-post .body{flex-basis:45%;flex-grow:1;flex-shrink:0}div.quarto-post .body h3.listing-title,div.quarto-post .body .listing-title.h3{margin-top:0px;margin-bottom:0px;border-bottom:none}div.quarto-post .body .listing-subtitle{font-size:.875em;margin-bottom:.5em;margin-top:.2em}div.quarto-post .body .description{font-size:.9em}div.quarto-post a{color:rgba(255,255,255,.8);display:flex;flex-direction:column;text-decoration:none}div.quarto-post a div.description{flex-shrink:0}div.quarto-post .metadata{display:flex;flex-direction:column;font-size:.8em;font-family:"Open Sans",sans-serif;flex-basis:33%}div.quarto-post .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}div.quarto-post .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}div.quarto-post .listing-description{margin-bottom:.5em}div.quarto-about-jolla{display:flex !important;flex-direction:column;align-items:center;margin-top:10%;padding-bottom:1em}div.quarto-about-jolla .about-image{object-fit:cover;margin-left:auto;margin-right:auto;margin-bottom:1.5em}div.quarto-about-jolla img.round{border-radius:50%}div.quarto-about-jolla img.rounded{border-radius:10px}div.quarto-about-jolla .quarto-title h1.title,div.quarto-about-jolla .quarto-title .title.h1{text-align:center}div.quarto-about-jolla .quarto-title .description{text-align:center}div.quarto-about-jolla h2,div.quarto-about-jolla .h2{border-bottom:none}div.quarto-about-jolla .about-sep{width:60%}div.quarto-about-jolla main{text-align:center}div.quarto-about-jolla .about-links{display:flex}@media(min-width: 992px){div.quarto-about-jolla .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-jolla .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-jolla .about-link{color:rgba(255,255,255,.8);text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-jolla .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-jolla .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-jolla .about-link:hover{color:#2b8cee}div.quarto-about-jolla .about-link i.bi{margin-right:.15em}div.quarto-about-solana{display:flex !important;flex-direction:column;padding-top:3em !important;padding-bottom:1em}div.quarto-about-solana .about-entity{display:flex !important;align-items:start;justify-content:space-between}@media(min-width: 992px){div.quarto-about-solana .about-entity{flex-direction:row}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity{flex-direction:column-reverse;align-items:center;text-align:center}}div.quarto-about-solana .about-entity .entity-contents{display:flex;flex-direction:column}@media(max-width: 767.98px){div.quarto-about-solana .about-entity .entity-contents{width:100%}}div.quarto-about-solana .about-entity .about-image{object-fit:cover}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-image{margin-bottom:1.5em}}div.quarto-about-solana .about-entity img.round{border-radius:50%}div.quarto-about-solana .about-entity img.rounded{border-radius:10px}div.quarto-about-solana .about-entity .about-links{display:flex;justify-content:left;padding-bottom:1.2em}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-solana .about-entity .about-link{color:rgba(255,255,255,.8);text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-solana .about-entity .about-link:hover{color:#2b8cee}div.quarto-about-solana .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-solana .about-contents{padding-right:1.5em;flex-basis:0;flex-grow:1}div.quarto-about-solana .about-contents main.content{margin-top:0}div.quarto-about-solana .about-contents h2,div.quarto-about-solana .about-contents .h2{border-bottom:none}div.quarto-about-trestles{display:flex !important;flex-direction:row;padding-top:3em !important;padding-bottom:1em}@media(max-width: 991.98px){div.quarto-about-trestles{flex-direction:column;padding-top:0em !important}}div.quarto-about-trestles .about-entity{display:flex !important;flex-direction:column;align-items:center;text-align:center;padding-right:1em}@media(min-width: 992px){div.quarto-about-trestles .about-entity{flex:0 0 42%}}div.quarto-about-trestles .about-entity .about-image{object-fit:cover;margin-bottom:1.5em}div.quarto-about-trestles .about-entity img.round{border-radius:50%}div.quarto-about-trestles .about-entity img.rounded{border-radius:10px}div.quarto-about-trestles .about-entity .about-links{display:flex;justify-content:center}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-trestles .about-entity .about-link{color:rgba(255,255,255,.8);text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-trestles .about-entity .about-link:hover{color:#2b8cee}div.quarto-about-trestles .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-trestles .about-contents{flex-basis:0;flex-grow:1}div.quarto-about-trestles .about-contents h2,div.quarto-about-trestles .about-contents .h2{border-bottom:none}@media(min-width: 992px){div.quarto-about-trestles .about-contents{border-left:solid 1px #dee2e6;padding-left:1.5em}}div.quarto-about-trestles .about-contents main.content{margin-top:0}div.quarto-about-marquee{padding-bottom:1em}div.quarto-about-marquee .about-contents{display:flex;flex-direction:column}div.quarto-about-marquee .about-image{max-height:550px;margin-bottom:1.5em;object-fit:cover}div.quarto-about-marquee img.round{border-radius:50%}div.quarto-about-marquee img.rounded{border-radius:10px}div.quarto-about-marquee h2,div.quarto-about-marquee .h2{border-bottom:none}div.quarto-about-marquee .about-links{display:flex;justify-content:center;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-marquee .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-marquee .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-marquee .about-link{color:rgba(255,255,255,.8);text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-marquee .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-marquee .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-marquee .about-link:hover{color:#2b8cee}div.quarto-about-marquee .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-marquee .about-link{border:none}}div.quarto-about-broadside{display:flex;flex-direction:column;padding-bottom:1em}div.quarto-about-broadside .about-main{display:flex !important;padding-top:0 !important}@media(min-width: 992px){div.quarto-about-broadside .about-main{flex-direction:row;align-items:flex-start}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main{flex-direction:column}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main .about-entity{flex-shrink:0;width:100%;height:450px;margin-bottom:1.5em;background-size:cover;background-repeat:no-repeat}}@media(min-width: 992px){div.quarto-about-broadside .about-main .about-entity{flex:0 10 50%;margin-right:1.5em;width:100%;height:100%;background-size:100%;background-repeat:no-repeat}}div.quarto-about-broadside .about-main .about-contents{padding-top:14px;flex:0 0 50%}div.quarto-about-broadside h2,div.quarto-about-broadside .h2{border-bottom:none}div.quarto-about-broadside .about-sep{margin-top:1.5em;width:60%;align-self:center}div.quarto-about-broadside .about-links{display:flex;justify-content:center;column-gap:20px;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-broadside .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-broadside .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-broadside .about-link{color:rgba(255,255,255,.8);text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-broadside .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-broadside .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-broadside .about-link:hover{color:#2b8cee}div.quarto-about-broadside .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-broadside .about-link{border:none}}.tippy-box[data-theme~=quarto]{background-color:#131416;border:solid 1px #dee2e6;border-radius:.25rem;color:rgba(255,255,255,.8);font-size:.875rem}.tippy-box[data-theme~=quarto]>.tippy-backdrop{background-color:#131416}.tippy-box[data-theme~=quarto]>.tippy-arrow:after,.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{content:"";position:absolute;z-index:-1}.tippy-box[data-theme~=quarto]>.tippy-arrow:after{border-color:rgba(0,0,0,0);border-style:solid}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-6px}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-6px}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-6px}.tippy-box[data-placement^=left]>.tippy-arrow:before{right:-6px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:before{border-top-color:#131416}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:after{border-top-color:#dee2e6;border-width:7px 7px 0;top:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow>svg{top:16px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow:after{top:17px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#131416;bottom:16px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:after{border-bottom-color:#dee2e6;border-width:0 7px 7px;bottom:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:15px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow:after{bottom:17px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:before{border-left-color:#131416}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:after{border-left-color:#dee2e6;border-width:7px 0 7px 7px;left:17px;top:1px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow>svg{left:11px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow:after{left:12px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:before{border-right-color:#131416;right:16px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:after{border-width:7px 7px 7px 0;right:17px;top:1px;border-right-color:#dee2e6}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow>svg{right:11px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow:after{right:12px}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow{fill:rgba(255,255,255,.8)}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiLz48L3N2Zz4=);background-size:16px 6px;width:16px;height:6px}.top-right{position:absolute;top:1em;right:1em}.hidden{display:none !important}.zindex-bottom{z-index:-1 !important}.quarto-layout-panel{margin-bottom:1em}.quarto-layout-panel>figure{width:100%}.quarto-layout-panel>figure>figcaption,.quarto-layout-panel>.panel-caption{margin-top:10pt}.quarto-layout-panel>.table-caption{margin-top:0px}.table-caption p{margin-bottom:.5em}.quarto-layout-row{display:flex;flex-direction:row;align-items:flex-start}.quarto-layout-valign-top{align-items:flex-start}.quarto-layout-valign-bottom{align-items:flex-end}.quarto-layout-valign-center{align-items:center}.quarto-layout-cell{position:relative;margin-right:20px}.quarto-layout-cell:last-child{margin-right:0}.quarto-layout-cell figure,.quarto-layout-cell>p{margin:.2em}.quarto-layout-cell img{max-width:100%}.quarto-layout-cell .html-widget{width:100% !important}.quarto-layout-cell div figure p{margin:0}.quarto-layout-cell figure{display:inline-block;margin-inline-start:0;margin-inline-end:0}.quarto-layout-cell table{display:inline-table}.quarto-layout-cell-subref figcaption,figure .quarto-layout-row figure figcaption{text-align:center;font-style:italic}.quarto-figure{position:relative;margin-bottom:1em}.quarto-figure>figure{width:100%;margin-bottom:0}.quarto-figure-left>figure>p,.quarto-figure-left>figure>div{text-align:left}.quarto-figure-center>figure>p,.quarto-figure-center>figure>div{text-align:center}.quarto-figure-right>figure>p,.quarto-figure-right>figure>div{text-align:right}figure>p:empty{display:none}figure>p:first-child{margin-top:0;margin-bottom:0}figure>figcaption{margin-top:.5em}div[id^=tbl-]{position:relative}.quarto-figure>.anchorjs-link{position:absolute;top:.6em;right:.5em}div[id^=tbl-]>.anchorjs-link{position:absolute;top:.7em;right:.3em}.quarto-figure:hover>.anchorjs-link,div[id^=tbl-]:hover>.anchorjs-link,h2:hover>.anchorjs-link,.h2:hover>.anchorjs-link,h3:hover>.anchorjs-link,.h3:hover>.anchorjs-link,h4:hover>.anchorjs-link,.h4:hover>.anchorjs-link,h5:hover>.anchorjs-link,.h5:hover>.anchorjs-link,h6:hover>.anchorjs-link,.h6:hover>.anchorjs-link,.reveal-anchorjs-link>.anchorjs-link{opacity:1}#title-block-header{margin-block-end:1rem;position:relative;margin-top:-1px}#title-block-header .abstract{margin-block-start:1rem}#title-block-header .abstract .abstract-title{font-weight:600}#title-block-header a{text-decoration:none}#title-block-header .author,#title-block-header .date,#title-block-header .doi{margin-block-end:.2rem}#title-block-header .quarto-title-block>div{display:flex}#title-block-header .quarto-title-block>div>h1,#title-block-header .quarto-title-block>div>.h1{flex-grow:1}#title-block-header .quarto-title-block>div>button{flex-shrink:0;height:2.25rem;margin-top:0}@media(min-width: 992px){#title-block-header .quarto-title-block>div>button{margin-top:5px}}tr.header>th>p:last-of-type{margin-bottom:0px}table,.table{caption-side:top;margin-bottom:1.5rem}caption,.table-caption{padding-top:.5rem;padding-bottom:.5rem;text-align:center}.utterances{max-width:none;margin-left:-8px}iframe{margin-bottom:1em}details{margin-bottom:1em}details[show]{margin-bottom:0}details>summary{color:#6c757d}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}p code:not(.sourceCode){white-space:pre-wrap}code{white-space:pre}@media print{code{white-space:pre-wrap}}pre>code{display:block}pre>code.sourceCode{white-space:pre}pre>code.sourceCode>span>a:first-child::before{text-decoration:none}pre.code-overflow-wrap>code.sourceCode{white-space:pre-wrap}pre.code-overflow-scroll>code.sourceCode{white-space:pre}code a:any-link{color:inherit;text-decoration:none}code a:hover{color:inherit;text-decoration:underline}ul.task-list{padding-left:1em}[data-tippy-root]{display:inline-block}.tippy-content .footnote-back{display:none}.quarto-embedded-source-code{display:none}.quarto-unresolved-ref{font-weight:600}.quarto-cover-image{max-width:35%;float:right;margin-left:30px}.cell-output-display .widget-subarea{margin-bottom:1em}.cell-output-display:not(.no-overflow-x),.knitsql-table:not(.no-overflow-x){overflow-x:auto}.panel-input{margin-bottom:1em}.panel-input>div,.panel-input>div>div{display:inline-block;vertical-align:top;padding-right:12px}.panel-input>p:last-child{margin-bottom:0}.layout-sidebar{margin-bottom:1em}.layout-sidebar .tab-content{border:none}.tab-content>.page-columns.active{display:grid}div.sourceCode>iframe{width:100%;height:300px;margin-bottom:-0.5em}div.ansi-escaped-output{font-family:monospace;display:block}/*! +* +* ansi colors from IPython notebook's +* +*/.ansi-black-fg{color:#3e424d}.ansi-black-bg{background-color:#3e424d}.ansi-black-intense-fg{color:#282c36}.ansi-black-intense-bg{background-color:#282c36}.ansi-red-fg{color:#e75c58}.ansi-red-bg{background-color:#e75c58}.ansi-red-intense-fg{color:#b22b31}.ansi-red-intense-bg{background-color:#b22b31}.ansi-green-fg{color:#00a250}.ansi-green-bg{background-color:#00a250}.ansi-green-intense-fg{color:#007427}.ansi-green-intense-bg{background-color:#007427}.ansi-yellow-fg{color:#ddb62b}.ansi-yellow-bg{background-color:#ddb62b}.ansi-yellow-intense-fg{color:#b27d12}.ansi-yellow-intense-bg{background-color:#b27d12}.ansi-blue-fg{color:#208ffb}.ansi-blue-bg{background-color:#208ffb}.ansi-blue-intense-fg{color:#0065ca}.ansi-blue-intense-bg{background-color:#0065ca}.ansi-magenta-fg{color:#d160c4}.ansi-magenta-bg{background-color:#d160c4}.ansi-magenta-intense-fg{color:#a03196}.ansi-magenta-intense-bg{background-color:#a03196}.ansi-cyan-fg{color:#60c6c8}.ansi-cyan-bg{background-color:#60c6c8}.ansi-cyan-intense-fg{color:#258f8f}.ansi-cyan-intense-bg{background-color:#258f8f}.ansi-white-fg{color:#c5c1b4}.ansi-white-bg{background-color:#c5c1b4}.ansi-white-intense-fg{color:#a1a6b2}.ansi-white-intense-bg{background-color:#a1a6b2}.ansi-default-inverse-fg{color:#fff}.ansi-default-inverse-bg{background-color:#000}.ansi-bold{font-weight:bold}.ansi-underline{text-decoration:underline}:root{--quarto-body-bg: #131416;--quarto-body-color: rgba(255, 255, 255, 0.8);--quarto-text-muted: #6c757d;--quarto-border-color: #dee2e6;--quarto-border-width: 1px;--quarto-border-radius: 0.25rem}table.gt_table{color:var(--quarto-body-color);font-size:1em;width:100%;background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_column_spanner_outer{color:var(--quarto-body-color);background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_col_heading{color:var(--quarto-body-color);font-weight:bold;background-color:rgba(0,0,0,0)}table.gt_table thead.gt_col_headings{border-bottom:1px solid currentColor;border-top-width:inherit;border-top-color:var(--quarto-border-color)}table.gt_table thead.gt_col_headings:not(:first-child){border-top-width:1px;border-top-color:var(--quarto-border-color)}table.gt_table td.gt_row{border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-width:0px}table.gt_table tbody.gt_table_body{border-top-width:1px;border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-color:currentColor}div.columns{display:initial;gap:initial}div.column{display:inline-block;overflow-x:initial;vertical-align:top;width:50%}.code-annotation-tip-content{word-wrap:break-word}.code-annotation-container-hidden{display:none !important}dl.code-annotation-container-grid{display:grid;grid-template-columns:min-content auto}dl.code-annotation-container-grid dt{grid-column:1}dl.code-annotation-container-grid dd{grid-column:2}pre.sourceCode.code-annotation-code{padding-right:0}code.sourceCode .code-annotation-anchor{z-index:100;position:absolute;right:.5em;left:inherit;background-color:rgba(0,0,0,0)}:root{--mermaid-bg-color: #131416;--mermaid-edge-color: #6c757d;--mermaid-node-fg-color: rgba(255, 255, 255, 0.8);--mermaid-fg-color: rgba(255, 255, 255, 0.8);--mermaid-fg-color--lighter: rgba(255, 255, 255, 0.8);--mermaid-fg-color--lightest: rgba(255, 255, 255, 0.8);--mermaid-font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica Neue, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;--mermaid-label-bg-color: #131416;--mermaid-label-fg-color: #131416;--mermaid-node-bg-color: rgba(19, 20, 22, 0.1);--mermaid-node-fg-color: rgba(255, 255, 255, 0.8)}@media print{:root{font-size:11pt}#quarto-sidebar,#TOC,.nav-page{display:none}.page-columns .content{grid-column-start:page-start}.fixed-top{position:relative}.panel-caption,.figure-caption,figcaption{color:#666}}.code-copy-button{position:absolute;top:0;right:0;border:0;margin-top:5px;margin-right:5px;background-color:rgba(0,0,0,0);z-index:3}.code-copy-button:focus{outline:none}.code-copy-button-tooltip{font-size:.75em}pre.sourceCode:hover>.code-copy-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}pre.sourceCode:hover>.code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button-checked:hover>.bi::before{background-image:url('data:image/svg+xml,')}main ol ol,main ul ul,main ol ul,main ul ol{margin-bottom:1em}ul>li:not(:has(>p))>ul,ol>li:not(:has(>p))>ul,ul>li:not(:has(>p))>ol,ol>li:not(:has(>p))>ol{margin-bottom:0}ul>li:not(:has(>p))>ul>li:has(>p),ol>li:not(:has(>p))>ul>li:has(>p),ul>li:not(:has(>p))>ol>li:has(>p),ol>li:not(:has(>p))>ol>li:has(>p){margin-top:1rem}body{margin:0}main.page-columns>header>h1.title,main.page-columns>header>.title.h1{margin-bottom:0}@media(min-width: 992px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1400px - 3em )) [body-content-end] 1.5em [body-end] 53.2px [body-end-outset] minmax(114px, 220.4px) [page-end-inset] 53.2px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1400px - 3em )) [body-content-end] 1.5em [body-end] 53.2px [body-end-outset] 53.2px [page-end-inset page-end] 5fr [screen-end-inset] 1.5em}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1400px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(0px, 304px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1400px - 3em )) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 380px) [page-end-inset] minmax(50px, 100px) [page-end] 1fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(0px, 304px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(0px, 304px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end] minmax(38px, 76px) [body-end-outset] minmax(76px, 228px) [page-end-inset] minmax(38px, 76px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1550px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(76px, 152px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1550px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(0px, 304px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1550px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(0px, 304px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(76px, 228px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end] minmax(38px, 76px) [body-end-outset] minmax(76px, 228px) [page-end-inset] minmax(38px, 76px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 991.98px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end] 53.2px [body-end-outset] minmax(114px, 220.4px) [page-end-inset] 53.2px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end] 53.2px [body-end-outset] minmax(114px, 220.4px) [page-end-inset] 53.2px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 1800px - 3em )) [body-content-end body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1.5em [body-content-start] minmax(500px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(114px, 228px) [page-end-inset] 38px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(38px, 76px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 1550px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(38px, 76px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(38px, 76px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 53.2px [body-end-outset] minmax(114px, 220.4px) [page-end-inset] 53.2px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(114px, 228px) [page-end-inset] 38px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 767.98px){body .page-columns,body.fullcontent:not(.floating):not(.docked) .page-columns,body.slimcontent:not(.floating):not(.docked) .page-columns,body.docked .page-columns,body.docked.slimcontent .page-columns,body.docked.fullcontent .page-columns,body.floating .page-columns,body.floating.slimcontent .page-columns,body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}nav[role=doc-toc]{display:none}}body,.page-row-navigation{grid-template-rows:[page-top] max-content [contents-top] max-content [contents-bottom] max-content [page-bottom]}.page-rows-contents{grid-template-rows:[content-top] minmax(max-content, 1fr) [content-bottom] minmax(60px, max-content) [page-bottom]}.page-full{grid-column:screen-start/screen-end !important}.page-columns>*{grid-column:body-content-start/body-content-end}.page-columns.column-page>*{grid-column:page-start/page-end}.page-columns.column-page-left>*{grid-column:page-start/body-content-end}.page-columns.column-page-right>*{grid-column:body-content-start/page-end}.page-rows{grid-auto-rows:auto}.header{grid-column:screen-start/screen-end;grid-row:page-top/contents-top}#quarto-content{padding:0;grid-column:screen-start/screen-end;grid-row:contents-top/contents-bottom}body.floating .sidebar.sidebar-navigation{grid-column:page-start/body-start;grid-row:content-top/page-bottom}body.docked .sidebar.sidebar-navigation{grid-column:screen-start/body-start;grid-row:content-top/page-bottom}.sidebar.toc-left{grid-column:page-start/body-start;grid-row:content-top/page-bottom}.sidebar.margin-sidebar{grid-column:body-end/page-end;grid-row:content-top/page-bottom}.page-columns .content{grid-column:body-content-start/body-content-end;grid-row:content-top/content-bottom;align-content:flex-start}.page-columns .page-navigation{grid-column:body-content-start/body-content-end;grid-row:content-bottom/page-bottom}.page-columns .footer{grid-column:screen-start/screen-end;grid-row:contents-bottom/page-bottom}.page-columns .column-body{grid-column:body-content-start/body-content-end}.page-columns .column-body-fullbleed{grid-column:body-start/body-end}.page-columns .column-body-outset{grid-column:body-start-outset/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset table{background:#131416}.page-columns .column-body-outset-left{grid-column:body-start-outset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-left table{background:#131416}.page-columns .column-body-outset-right{grid-column:body-content-start/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-right table{background:#131416}.page-columns .column-page{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page table{background:#131416}.page-columns .column-page-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset table{background:#131416}.page-columns .column-page-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-left table{background:#131416}.page-columns .column-page-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-right figcaption table{background:#131416}.page-columns .column-page-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-left table{background:#131416}.page-columns .column-page-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-right figcaption table{background:#131416}#quarto-content.page-columns #quarto-margin-sidebar,#quarto-content.page-columns #quarto-sidebar{z-index:1}@media(max-width: 991.98px){#quarto-content.page-columns #quarto-margin-sidebar.collapse,#quarto-content.page-columns #quarto-sidebar.collapse,#quarto-content.page-columns #quarto-margin-sidebar.collapsing,#quarto-content.page-columns #quarto-sidebar.collapsing{z-index:1055}}#quarto-content.page-columns main.column-page,#quarto-content.page-columns main.column-page-right,#quarto-content.page-columns main.column-page-left{z-index:0}.page-columns .column-screen-inset{grid-column:screen-start-inset/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#131416}.page-columns .column-screen-inset-left{grid-column:screen-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#131416}.page-columns .column-screen-inset-right{grid-column:body-content-start/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#131416}.page-columns .column-screen{grid-column:screen-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#131416}.page-columns .column-screen-left{grid-column:screen-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#131416}.page-columns .column-screen-right{grid-column:body-content-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#131416}.page-columns .column-screen-inset-shaded{grid-column:screen-start/screen-end;padding:1em;background:#f8f5f0;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}.zindex-content{z-index:998;transform:translate3d(0, 0, 0)}.zindex-modal{z-index:1055;transform:translate3d(0, 0, 0)}.zindex-over-content{z-index:999;transform:translate3d(0, 0, 0)}img.img-fluid.column-screen,img.img-fluid.column-screen-inset-shaded,img.img-fluid.column-screen-inset,img.img-fluid.column-screen-inset-left,img.img-fluid.column-screen-inset-right,img.img-fluid.column-screen-left,img.img-fluid.column-screen-right{width:100%}@media(min-width: 992px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.column-sidebar{grid-column:page-start/body-start !important;z-index:998}.column-leftmargin{grid-column:screen-start-inset/body-start !important;z-index:998}.no-row-height{height:1em;overflow:visible}}@media(max-width: 991.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.no-row-height{height:1em;overflow:visible}.page-columns.page-full{overflow:visible}.page-columns.toc-left .margin-caption,.page-columns.toc-left div.aside,.page-columns.toc-left aside,.page-columns.toc-left .column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.page-columns.toc-left .no-row-height{height:initial;overflow:initial}}@media(max-width: 767.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.no-row-height{height:initial;overflow:initial}#quarto-margin-sidebar{display:none}#quarto-sidebar-toc-left{display:none}.hidden-sm{display:none}}.panel-grid{display:grid;grid-template-rows:repeat(1, 1fr);grid-template-columns:repeat(24, 1fr);gap:1em}.panel-grid .g-col-1{grid-column:auto/span 1}.panel-grid .g-col-2{grid-column:auto/span 2}.panel-grid .g-col-3{grid-column:auto/span 3}.panel-grid .g-col-4{grid-column:auto/span 4}.panel-grid .g-col-5{grid-column:auto/span 5}.panel-grid .g-col-6{grid-column:auto/span 6}.panel-grid .g-col-7{grid-column:auto/span 7}.panel-grid .g-col-8{grid-column:auto/span 8}.panel-grid .g-col-9{grid-column:auto/span 9}.panel-grid .g-col-10{grid-column:auto/span 10}.panel-grid .g-col-11{grid-column:auto/span 11}.panel-grid .g-col-12{grid-column:auto/span 12}.panel-grid .g-col-13{grid-column:auto/span 13}.panel-grid .g-col-14{grid-column:auto/span 14}.panel-grid .g-col-15{grid-column:auto/span 15}.panel-grid .g-col-16{grid-column:auto/span 16}.panel-grid .g-col-17{grid-column:auto/span 17}.panel-grid .g-col-18{grid-column:auto/span 18}.panel-grid .g-col-19{grid-column:auto/span 19}.panel-grid .g-col-20{grid-column:auto/span 20}.panel-grid .g-col-21{grid-column:auto/span 21}.panel-grid .g-col-22{grid-column:auto/span 22}.panel-grid .g-col-23{grid-column:auto/span 23}.panel-grid .g-col-24{grid-column:auto/span 24}.panel-grid .g-start-1{grid-column-start:1}.panel-grid .g-start-2{grid-column-start:2}.panel-grid .g-start-3{grid-column-start:3}.panel-grid .g-start-4{grid-column-start:4}.panel-grid .g-start-5{grid-column-start:5}.panel-grid .g-start-6{grid-column-start:6}.panel-grid .g-start-7{grid-column-start:7}.panel-grid .g-start-8{grid-column-start:8}.panel-grid .g-start-9{grid-column-start:9}.panel-grid .g-start-10{grid-column-start:10}.panel-grid .g-start-11{grid-column-start:11}.panel-grid .g-start-12{grid-column-start:12}.panel-grid .g-start-13{grid-column-start:13}.panel-grid .g-start-14{grid-column-start:14}.panel-grid .g-start-15{grid-column-start:15}.panel-grid .g-start-16{grid-column-start:16}.panel-grid .g-start-17{grid-column-start:17}.panel-grid .g-start-18{grid-column-start:18}.panel-grid .g-start-19{grid-column-start:19}.panel-grid .g-start-20{grid-column-start:20}.panel-grid .g-start-21{grid-column-start:21}.panel-grid .g-start-22{grid-column-start:22}.panel-grid .g-start-23{grid-column-start:23}@media(min-width: 576px){.panel-grid .g-col-sm-1{grid-column:auto/span 1}.panel-grid .g-col-sm-2{grid-column:auto/span 2}.panel-grid .g-col-sm-3{grid-column:auto/span 3}.panel-grid .g-col-sm-4{grid-column:auto/span 4}.panel-grid .g-col-sm-5{grid-column:auto/span 5}.panel-grid .g-col-sm-6{grid-column:auto/span 6}.panel-grid .g-col-sm-7{grid-column:auto/span 7}.panel-grid .g-col-sm-8{grid-column:auto/span 8}.panel-grid .g-col-sm-9{grid-column:auto/span 9}.panel-grid .g-col-sm-10{grid-column:auto/span 10}.panel-grid .g-col-sm-11{grid-column:auto/span 11}.panel-grid .g-col-sm-12{grid-column:auto/span 12}.panel-grid .g-col-sm-13{grid-column:auto/span 13}.panel-grid .g-col-sm-14{grid-column:auto/span 14}.panel-grid .g-col-sm-15{grid-column:auto/span 15}.panel-grid .g-col-sm-16{grid-column:auto/span 16}.panel-grid .g-col-sm-17{grid-column:auto/span 17}.panel-grid .g-col-sm-18{grid-column:auto/span 18}.panel-grid .g-col-sm-19{grid-column:auto/span 19}.panel-grid .g-col-sm-20{grid-column:auto/span 20}.panel-grid .g-col-sm-21{grid-column:auto/span 21}.panel-grid .g-col-sm-22{grid-column:auto/span 22}.panel-grid .g-col-sm-23{grid-column:auto/span 23}.panel-grid .g-col-sm-24{grid-column:auto/span 24}.panel-grid .g-start-sm-1{grid-column-start:1}.panel-grid .g-start-sm-2{grid-column-start:2}.panel-grid .g-start-sm-3{grid-column-start:3}.panel-grid .g-start-sm-4{grid-column-start:4}.panel-grid .g-start-sm-5{grid-column-start:5}.panel-grid .g-start-sm-6{grid-column-start:6}.panel-grid .g-start-sm-7{grid-column-start:7}.panel-grid .g-start-sm-8{grid-column-start:8}.panel-grid .g-start-sm-9{grid-column-start:9}.panel-grid .g-start-sm-10{grid-column-start:10}.panel-grid .g-start-sm-11{grid-column-start:11}.panel-grid .g-start-sm-12{grid-column-start:12}.panel-grid .g-start-sm-13{grid-column-start:13}.panel-grid .g-start-sm-14{grid-column-start:14}.panel-grid .g-start-sm-15{grid-column-start:15}.panel-grid .g-start-sm-16{grid-column-start:16}.panel-grid .g-start-sm-17{grid-column-start:17}.panel-grid .g-start-sm-18{grid-column-start:18}.panel-grid .g-start-sm-19{grid-column-start:19}.panel-grid .g-start-sm-20{grid-column-start:20}.panel-grid .g-start-sm-21{grid-column-start:21}.panel-grid .g-start-sm-22{grid-column-start:22}.panel-grid .g-start-sm-23{grid-column-start:23}}@media(min-width: 768px){.panel-grid .g-col-md-1{grid-column:auto/span 1}.panel-grid .g-col-md-2{grid-column:auto/span 2}.panel-grid .g-col-md-3{grid-column:auto/span 3}.panel-grid .g-col-md-4{grid-column:auto/span 4}.panel-grid .g-col-md-5{grid-column:auto/span 5}.panel-grid .g-col-md-6{grid-column:auto/span 6}.panel-grid .g-col-md-7{grid-column:auto/span 7}.panel-grid .g-col-md-8{grid-column:auto/span 8}.panel-grid .g-col-md-9{grid-column:auto/span 9}.panel-grid .g-col-md-10{grid-column:auto/span 10}.panel-grid .g-col-md-11{grid-column:auto/span 11}.panel-grid .g-col-md-12{grid-column:auto/span 12}.panel-grid .g-col-md-13{grid-column:auto/span 13}.panel-grid .g-col-md-14{grid-column:auto/span 14}.panel-grid .g-col-md-15{grid-column:auto/span 15}.panel-grid .g-col-md-16{grid-column:auto/span 16}.panel-grid .g-col-md-17{grid-column:auto/span 17}.panel-grid .g-col-md-18{grid-column:auto/span 18}.panel-grid .g-col-md-19{grid-column:auto/span 19}.panel-grid .g-col-md-20{grid-column:auto/span 20}.panel-grid .g-col-md-21{grid-column:auto/span 21}.panel-grid .g-col-md-22{grid-column:auto/span 22}.panel-grid .g-col-md-23{grid-column:auto/span 23}.panel-grid .g-col-md-24{grid-column:auto/span 24}.panel-grid .g-start-md-1{grid-column-start:1}.panel-grid .g-start-md-2{grid-column-start:2}.panel-grid .g-start-md-3{grid-column-start:3}.panel-grid .g-start-md-4{grid-column-start:4}.panel-grid .g-start-md-5{grid-column-start:5}.panel-grid .g-start-md-6{grid-column-start:6}.panel-grid .g-start-md-7{grid-column-start:7}.panel-grid .g-start-md-8{grid-column-start:8}.panel-grid .g-start-md-9{grid-column-start:9}.panel-grid .g-start-md-10{grid-column-start:10}.panel-grid .g-start-md-11{grid-column-start:11}.panel-grid .g-start-md-12{grid-column-start:12}.panel-grid .g-start-md-13{grid-column-start:13}.panel-grid .g-start-md-14{grid-column-start:14}.panel-grid .g-start-md-15{grid-column-start:15}.panel-grid .g-start-md-16{grid-column-start:16}.panel-grid .g-start-md-17{grid-column-start:17}.panel-grid .g-start-md-18{grid-column-start:18}.panel-grid .g-start-md-19{grid-column-start:19}.panel-grid .g-start-md-20{grid-column-start:20}.panel-grid .g-start-md-21{grid-column-start:21}.panel-grid .g-start-md-22{grid-column-start:22}.panel-grid .g-start-md-23{grid-column-start:23}}@media(min-width: 992px){.panel-grid .g-col-lg-1{grid-column:auto/span 1}.panel-grid .g-col-lg-2{grid-column:auto/span 2}.panel-grid .g-col-lg-3{grid-column:auto/span 3}.panel-grid .g-col-lg-4{grid-column:auto/span 4}.panel-grid .g-col-lg-5{grid-column:auto/span 5}.panel-grid .g-col-lg-6{grid-column:auto/span 6}.panel-grid .g-col-lg-7{grid-column:auto/span 7}.panel-grid .g-col-lg-8{grid-column:auto/span 8}.panel-grid .g-col-lg-9{grid-column:auto/span 9}.panel-grid .g-col-lg-10{grid-column:auto/span 10}.panel-grid .g-col-lg-11{grid-column:auto/span 11}.panel-grid .g-col-lg-12{grid-column:auto/span 12}.panel-grid .g-col-lg-13{grid-column:auto/span 13}.panel-grid .g-col-lg-14{grid-column:auto/span 14}.panel-grid .g-col-lg-15{grid-column:auto/span 15}.panel-grid .g-col-lg-16{grid-column:auto/span 16}.panel-grid .g-col-lg-17{grid-column:auto/span 17}.panel-grid .g-col-lg-18{grid-column:auto/span 18}.panel-grid .g-col-lg-19{grid-column:auto/span 19}.panel-grid .g-col-lg-20{grid-column:auto/span 20}.panel-grid .g-col-lg-21{grid-column:auto/span 21}.panel-grid .g-col-lg-22{grid-column:auto/span 22}.panel-grid .g-col-lg-23{grid-column:auto/span 23}.panel-grid .g-col-lg-24{grid-column:auto/span 24}.panel-grid .g-start-lg-1{grid-column-start:1}.panel-grid .g-start-lg-2{grid-column-start:2}.panel-grid .g-start-lg-3{grid-column-start:3}.panel-grid .g-start-lg-4{grid-column-start:4}.panel-grid .g-start-lg-5{grid-column-start:5}.panel-grid .g-start-lg-6{grid-column-start:6}.panel-grid .g-start-lg-7{grid-column-start:7}.panel-grid .g-start-lg-8{grid-column-start:8}.panel-grid .g-start-lg-9{grid-column-start:9}.panel-grid .g-start-lg-10{grid-column-start:10}.panel-grid .g-start-lg-11{grid-column-start:11}.panel-grid .g-start-lg-12{grid-column-start:12}.panel-grid .g-start-lg-13{grid-column-start:13}.panel-grid .g-start-lg-14{grid-column-start:14}.panel-grid .g-start-lg-15{grid-column-start:15}.panel-grid .g-start-lg-16{grid-column-start:16}.panel-grid .g-start-lg-17{grid-column-start:17}.panel-grid .g-start-lg-18{grid-column-start:18}.panel-grid .g-start-lg-19{grid-column-start:19}.panel-grid .g-start-lg-20{grid-column-start:20}.panel-grid .g-start-lg-21{grid-column-start:21}.panel-grid .g-start-lg-22{grid-column-start:22}.panel-grid .g-start-lg-23{grid-column-start:23}}@media(min-width: 1200px){.panel-grid .g-col-xl-1{grid-column:auto/span 1}.panel-grid .g-col-xl-2{grid-column:auto/span 2}.panel-grid .g-col-xl-3{grid-column:auto/span 3}.panel-grid .g-col-xl-4{grid-column:auto/span 4}.panel-grid .g-col-xl-5{grid-column:auto/span 5}.panel-grid .g-col-xl-6{grid-column:auto/span 6}.panel-grid .g-col-xl-7{grid-column:auto/span 7}.panel-grid .g-col-xl-8{grid-column:auto/span 8}.panel-grid .g-col-xl-9{grid-column:auto/span 9}.panel-grid .g-col-xl-10{grid-column:auto/span 10}.panel-grid .g-col-xl-11{grid-column:auto/span 11}.panel-grid .g-col-xl-12{grid-column:auto/span 12}.panel-grid .g-col-xl-13{grid-column:auto/span 13}.panel-grid .g-col-xl-14{grid-column:auto/span 14}.panel-grid .g-col-xl-15{grid-column:auto/span 15}.panel-grid .g-col-xl-16{grid-column:auto/span 16}.panel-grid .g-col-xl-17{grid-column:auto/span 17}.panel-grid .g-col-xl-18{grid-column:auto/span 18}.panel-grid .g-col-xl-19{grid-column:auto/span 19}.panel-grid .g-col-xl-20{grid-column:auto/span 20}.panel-grid .g-col-xl-21{grid-column:auto/span 21}.panel-grid .g-col-xl-22{grid-column:auto/span 22}.panel-grid .g-col-xl-23{grid-column:auto/span 23}.panel-grid .g-col-xl-24{grid-column:auto/span 24}.panel-grid .g-start-xl-1{grid-column-start:1}.panel-grid .g-start-xl-2{grid-column-start:2}.panel-grid .g-start-xl-3{grid-column-start:3}.panel-grid .g-start-xl-4{grid-column-start:4}.panel-grid .g-start-xl-5{grid-column-start:5}.panel-grid .g-start-xl-6{grid-column-start:6}.panel-grid .g-start-xl-7{grid-column-start:7}.panel-grid .g-start-xl-8{grid-column-start:8}.panel-grid .g-start-xl-9{grid-column-start:9}.panel-grid .g-start-xl-10{grid-column-start:10}.panel-grid .g-start-xl-11{grid-column-start:11}.panel-grid .g-start-xl-12{grid-column-start:12}.panel-grid .g-start-xl-13{grid-column-start:13}.panel-grid .g-start-xl-14{grid-column-start:14}.panel-grid .g-start-xl-15{grid-column-start:15}.panel-grid .g-start-xl-16{grid-column-start:16}.panel-grid .g-start-xl-17{grid-column-start:17}.panel-grid .g-start-xl-18{grid-column-start:18}.panel-grid .g-start-xl-19{grid-column-start:19}.panel-grid .g-start-xl-20{grid-column-start:20}.panel-grid .g-start-xl-21{grid-column-start:21}.panel-grid .g-start-xl-22{grid-column-start:22}.panel-grid .g-start-xl-23{grid-column-start:23}}@media(min-width: 1400px){.panel-grid .g-col-xxl-1{grid-column:auto/span 1}.panel-grid .g-col-xxl-2{grid-column:auto/span 2}.panel-grid .g-col-xxl-3{grid-column:auto/span 3}.panel-grid .g-col-xxl-4{grid-column:auto/span 4}.panel-grid .g-col-xxl-5{grid-column:auto/span 5}.panel-grid .g-col-xxl-6{grid-column:auto/span 6}.panel-grid .g-col-xxl-7{grid-column:auto/span 7}.panel-grid .g-col-xxl-8{grid-column:auto/span 8}.panel-grid .g-col-xxl-9{grid-column:auto/span 9}.panel-grid .g-col-xxl-10{grid-column:auto/span 10}.panel-grid .g-col-xxl-11{grid-column:auto/span 11}.panel-grid .g-col-xxl-12{grid-column:auto/span 12}.panel-grid .g-col-xxl-13{grid-column:auto/span 13}.panel-grid .g-col-xxl-14{grid-column:auto/span 14}.panel-grid .g-col-xxl-15{grid-column:auto/span 15}.panel-grid .g-col-xxl-16{grid-column:auto/span 16}.panel-grid .g-col-xxl-17{grid-column:auto/span 17}.panel-grid .g-col-xxl-18{grid-column:auto/span 18}.panel-grid .g-col-xxl-19{grid-column:auto/span 19}.panel-grid .g-col-xxl-20{grid-column:auto/span 20}.panel-grid .g-col-xxl-21{grid-column:auto/span 21}.panel-grid .g-col-xxl-22{grid-column:auto/span 22}.panel-grid .g-col-xxl-23{grid-column:auto/span 23}.panel-grid .g-col-xxl-24{grid-column:auto/span 24}.panel-grid .g-start-xxl-1{grid-column-start:1}.panel-grid .g-start-xxl-2{grid-column-start:2}.panel-grid .g-start-xxl-3{grid-column-start:3}.panel-grid .g-start-xxl-4{grid-column-start:4}.panel-grid .g-start-xxl-5{grid-column-start:5}.panel-grid .g-start-xxl-6{grid-column-start:6}.panel-grid .g-start-xxl-7{grid-column-start:7}.panel-grid .g-start-xxl-8{grid-column-start:8}.panel-grid .g-start-xxl-9{grid-column-start:9}.panel-grid .g-start-xxl-10{grid-column-start:10}.panel-grid .g-start-xxl-11{grid-column-start:11}.panel-grid .g-start-xxl-12{grid-column-start:12}.panel-grid .g-start-xxl-13{grid-column-start:13}.panel-grid .g-start-xxl-14{grid-column-start:14}.panel-grid .g-start-xxl-15{grid-column-start:15}.panel-grid .g-start-xxl-16{grid-column-start:16}.panel-grid .g-start-xxl-17{grid-column-start:17}.panel-grid .g-start-xxl-18{grid-column-start:18}.panel-grid .g-start-xxl-19{grid-column-start:19}.panel-grid .g-start-xxl-20{grid-column-start:20}.panel-grid .g-start-xxl-21{grid-column-start:21}.panel-grid .g-start-xxl-22{grid-column-start:22}.panel-grid .g-start-xxl-23{grid-column-start:23}}main{margin-top:1em;margin-bottom:1em}h1,.h1,h2,.h2{opacity:.9;margin-top:2rem;margin-bottom:1rem;font-weight:600}h1.title,.title.h1{margin-top:0}h2,.h2{border-bottom:1px solid #dee2e6;padding-bottom:.5rem}h3,.h3{font-weight:600}h3,.h3,h4,.h4{opacity:.9;margin-top:1.5rem}h5,.h5,h6,.h6{opacity:.9}.header-section-number{color:rgba(191,191,191,.8)}.nav-link.active .header-section-number{color:inherit}mark,.mark{padding:0em}.panel-caption,caption,.figure-caption{font-size:.9rem}.panel-caption,.figure-caption,figcaption{color:rgba(191,191,191,.8)}.table-caption,caption{color:rgba(255,255,255,.8)}.quarto-layout-cell[data-ref-parent] caption{color:rgba(191,191,191,.8)}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:rgba(191,191,191,.8);font-size:.825rem}.panel-caption.margin-caption{text-align:inherit}.column-margin.column-container p{margin-bottom:0}.column-margin.column-container>*:not(.collapse){padding-top:.5em;padding-bottom:.5em;display:block}.column-margin.column-container>*.collapse:not(.show){display:none}@media(min-width: 768px){.column-margin.column-container .callout-margin-content:first-child{margin-top:4.5em}.column-margin.column-container .callout-margin-content-simple:first-child{margin-top:3.5em}}.margin-caption>*{padding-top:.5em;padding-bottom:.5em}@media(max-width: 767.98px){.quarto-layout-row{flex-direction:column}}.nav-tabs .nav-item{margin-top:1px;cursor:pointer}.tab-content{margin-top:0px;border-left:#dee2e6 1px solid;border-right:#dee2e6 1px solid;border-bottom:#dee2e6 1px solid;margin-left:0;padding:1em;margin-bottom:1em}@media(max-width: 767.98px){.layout-sidebar{margin-left:0;margin-right:0}}.panel-sidebar,.panel-sidebar .form-control,.panel-input,.panel-input .form-control,.selectize-dropdown{font-size:.9rem}.panel-sidebar .form-control,.panel-input .form-control{padding-top:.1rem}.tab-pane div.sourceCode{margin-top:0px}.tab-pane>p{padding-top:1em}.tab-content>.tab-pane:not(.active){display:none !important}div.sourceCode{background-color:#272822;border:1px solid #272822;border-radius:.25rem}pre.sourceCode{background-color:rgba(0,0,0,0)}pre.sourceCode{border:none;font-size:.875em;overflow:visible !important;padding:.4em}.callout pre.sourceCode{padding-left:0}div.sourceCode{overflow-y:hidden}.callout div.sourceCode{margin-left:initial}.blockquote{font-size:inherit;padding-left:1rem;padding-right:1.5rem;color:rgba(191,191,191,.8)}.blockquote h1:first-child,.blockquote .h1:first-child,.blockquote h2:first-child,.blockquote .h2:first-child,.blockquote h3:first-child,.blockquote .h3:first-child,.blockquote h4:first-child,.blockquote .h4:first-child,.blockquote h5:first-child,.blockquote .h5:first-child{margin-top:0}pre{background-color:initial;padding:initial;border:initial}p code:not(.sourceCode),li code:not(.sourceCode),td code:not(.sourceCode){background-color:rgba(25,26,28,.992);padding:.2em}nav p code:not(.sourceCode),nav li code:not(.sourceCode),nav td code:not(.sourceCode){background-color:rgba(0,0,0,0);padding:0}td code:not(.sourceCode){white-space:pre-wrap}#quarto-embedded-source-code-modal>.modal-dialog{max-width:1000px;padding-left:1.75rem;padding-right:1.75rem}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body{padding:0}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body div.sourceCode{margin:0;padding:.2rem .2rem;border-radius:0px;border:none}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-header{padding:.7rem}.code-tools-button{font-size:1rem;padding:.15rem .15rem;margin-left:5px;color:#6c757d;background-color:rgba(0,0,0,0);transition:initial;cursor:pointer}.code-tools-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}.code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}.sidebar{will-change:top;transition:top 200ms linear;position:sticky;overflow-y:auto;padding-top:1.2em;max-height:100vh}.sidebar.toc-left,.sidebar.margin-sidebar{top:0px;padding-top:1em}.sidebar.toc-left>*,.sidebar.margin-sidebar>*{padding-top:.5em}.sidebar.quarto-banner-title-block-sidebar>*{padding-top:1.65em}figure .quarto-notebook-link{margin-top:.5em}.quarto-notebook-link{font-size:.75em;color:#6c757d;margin-bottom:1em;text-decoration:none;display:block}.quarto-notebook-link:hover{text-decoration:underline;color:#2b8cee}.quarto-notebook-link::before{display:inline-block;height:.75rem;width:.75rem;margin-bottom:0em;margin-right:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}.quarto-alternate-notebooks i.bi,.quarto-alternate-formats i.bi{margin-right:.4em}.quarto-notebook .cell-container{display:flex}.quarto-notebook .cell-container .cell{flex-grow:4}.quarto-notebook .cell-container .cell-decorator{padding-top:1.5em;padding-right:1em;text-align:right}.quarto-notebook h2,.quarto-notebook .h2{border-bottom:none}.sidebar .quarto-alternate-formats a,.sidebar .quarto-alternate-notebooks a{text-decoration:none}.sidebar .quarto-alternate-formats a:hover,.sidebar .quarto-alternate-notebooks a:hover{color:#2b8cee}.sidebar .quarto-alternate-notebooks h2,.sidebar .quarto-alternate-notebooks .h2,.sidebar .quarto-alternate-formats h2,.sidebar .quarto-alternate-formats .h2,.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-size:.875rem;font-weight:400;margin-bottom:.5rem;margin-top:.3rem;font-family:inherit;border-bottom:0;padding-bottom:0;padding-top:0px}.sidebar .quarto-alternate-notebooks h2,.sidebar .quarto-alternate-notebooks .h2,.sidebar .quarto-alternate-formats h2,.sidebar .quarto-alternate-formats .h2{margin-top:1rem}.sidebar nav[role=doc-toc]>ul a{border-left:1px solid #f8f5f0;padding-left:.6rem}.sidebar .quarto-alternate-notebooks h2>ul a,.sidebar .quarto-alternate-notebooks .h2>ul a,.sidebar .quarto-alternate-formats h2>ul a,.sidebar .quarto-alternate-formats .h2>ul a{border-left:none;padding-left:.6rem}.sidebar .quarto-alternate-notebooks ul a:empty,.sidebar .quarto-alternate-formats ul a:empty,.sidebar nav[role=doc-toc]>ul a:empty{display:none}.sidebar .quarto-alternate-notebooks ul,.sidebar .quarto-alternate-formats ul,.sidebar nav[role=doc-toc] ul{padding-left:0;list-style:none;font-size:.875rem;font-weight:300}.sidebar .quarto-alternate-notebooks ul li a,.sidebar .quarto-alternate-formats ul li a,.sidebar nav[role=doc-toc]>ul li a{line-height:1.1rem;padding-bottom:.2rem;padding-top:.2rem;color:inherit}.sidebar nav[role=doc-toc] ul>li>ul>li>a{padding-left:1.2em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>a{padding-left:2.4em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>a{padding-left:3.6em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:4.8em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:6em}.sidebar nav[role=doc-toc] ul>li>a.active,.sidebar nav[role=doc-toc] ul>li>ul>li>a.active{border-left:1px solid #2b8cee;color:#2b8cee !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:#2b8cee !important}kbd,.kbd{color:rgba(255,255,255,.8);background-color:#f8f9fa;border:1px solid;border-radius:5px;border-color:#dee2e6}div.hanging-indent{margin-left:1em;text-indent:-1em}.citation a,.footnote-ref{text-decoration:none}.footnotes ol{padding-left:1em}.tippy-content>*{margin-bottom:.7em}.tippy-content>*:last-child{margin-bottom:0}.table a{word-break:break-word}.table>thead{border-top-width:1px;border-top-color:#dee2e6;border-bottom:1px solid rgba(255,255,255,.8)}.callout{margin-top:1.25rem;margin-bottom:1.25rem;border-radius:.25rem;overflow-wrap:break-word}.callout .callout-title-container{overflow-wrap:anywhere}.callout.callout-style-simple{padding:.4em .7em;border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout.callout-style-default{border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout .callout-body-container{flex-grow:1}.callout.callout-style-simple .callout-body{font-size:.9rem;font-weight:400}.callout.callout-style-default .callout-body{font-size:.9rem;font-weight:400}.callout.callout-titled .callout-body{margin-top:.2em}.callout:not(.no-icon).callout-titled.callout-style-simple .callout-body{padding-left:1.6em}.callout.callout-titled>.callout-header{padding-top:.2em;margin-bottom:-0.2em}.callout.callout-style-simple>div.callout-header{border-bottom:none;font-size:.9rem;font-weight:600;opacity:75%}.callout.callout-style-default>div.callout-header{border-bottom:none;font-weight:600;opacity:85%;font-size:.9rem;padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body{padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body>:first-child{margin-top:.5em}.callout>div.callout-header[data-bs-toggle=collapse]{cursor:pointer}.callout.callout-style-default .callout-header[aria-expanded=false],.callout.callout-style-default .callout-header[aria-expanded=true]{padding-top:0px;margin-bottom:0px;align-items:center}.callout.callout-titled .callout-body>:last-child:not(.sourceCode),.callout.callout-titled .callout-body>div>:last-child:not(.sourceCode){margin-bottom:.5rem}.callout:not(.callout-titled) .callout-body>:first-child,.callout:not(.callout-titled) .callout-body>div>:first-child{margin-top:.25rem}.callout:not(.callout-titled) .callout-body>:last-child,.callout:not(.callout-titled) .callout-body>div>:last-child{margin-bottom:.2rem}.callout.callout-style-simple .callout-icon::before,.callout.callout-style-simple .callout-toggle::before{height:1rem;width:1rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.callout.callout-style-default .callout-icon::before,.callout.callout-style-default .callout-toggle::before{height:.9rem;width:.9rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:.9rem .9rem}.callout.callout-style-default .callout-toggle::before{margin-top:5px}.callout .callout-btn-toggle .callout-toggle::before{transition:transform .2s linear}.callout .callout-header[aria-expanded=false] .callout-toggle::before{transform:rotate(-90deg)}.callout .callout-header[aria-expanded=true] .callout-toggle::before{transform:none}.callout.callout-style-simple:not(.no-icon) div.callout-icon-container{padding-top:.2em;padding-right:.55em}.callout.callout-style-default:not(.no-icon) div.callout-icon-container{padding-top:.1em;padding-right:.35em}.callout.callout-style-default:not(.no-icon) div.callout-title-container{margin-top:-1px}.callout.callout-style-default.callout-caution:not(.no-icon) div.callout-icon-container{padding-top:.3em;padding-right:.35em}.callout>.callout-body>.callout-icon-container>.no-icon,.callout>.callout-header>.callout-icon-container>.no-icon{display:none}div.callout.callout{border-left-color:#6c757d}div.callout.callout-style-default>.callout-header{background-color:#6c757d}div.callout-note.callout{border-left-color:#325d88}div.callout-note.callout-style-default>.callout-header{background-color:#0f1c29}div.callout-note:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-tip.callout{border-left-color:#93c54b}div.callout-tip.callout-style-default>.callout-header{background-color:#2c3b17}div.callout-tip:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-warning.callout{border-left-color:#ffc107}div.callout-warning.callout-style-default>.callout-header{background-color:#4d3a02}div.callout-warning:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-caution.callout{border-left-color:#f47c3c}div.callout-caution.callout-style-default>.callout-header{background-color:#492512}div.callout-caution:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-important.callout{border-left-color:#d9534f}div.callout-important.callout-style-default>.callout-header{background-color:#411918}div.callout-important:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important .callout-toggle::before{background-image:url('data:image/svg+xml,')}.quarto-toggle-container{display:flex;align-items:center}.quarto-reader-toggle .bi::before,.quarto-color-scheme-toggle .bi::before{display:inline-block;height:1rem;width:1rem;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.sidebar-navigation{padding-left:20px}.navbar .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.quarto-sidebar-toggle{border-color:#dee2e6;border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem;border-style:solid;border-width:1px;overflow:hidden;border-top-width:0px;padding-top:0px !important}.quarto-sidebar-toggle-title{cursor:pointer;padding-bottom:2px;margin-left:.25em;text-align:center;font-weight:400;font-size:.775em}#quarto-content .quarto-sidebar-toggle{background:#18191b}#quarto-content .quarto-sidebar-toggle-title{color:rgba(255,255,255,.8)}.quarto-sidebar-toggle-icon{color:#dee2e6;margin-right:.5em;float:right;transition:transform .2s ease}.quarto-sidebar-toggle-icon::before{padding-top:5px}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-icon{transform:rotate(-180deg)}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-title{border-bottom:solid #dee2e6 1px}.quarto-sidebar-toggle-contents{background-color:#131416;padding-right:10px;padding-left:10px;margin-top:0px !important;transition:max-height .5s ease}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-contents{padding-top:1em;padding-bottom:10px}.quarto-sidebar-toggle:not(.expanded) .quarto-sidebar-toggle-contents{padding-top:0px !important;padding-bottom:0px}nav[role=doc-toc]{z-index:1020}#quarto-sidebar>*,nav[role=doc-toc]>*{transition:opacity .1s ease,border .1s ease}#quarto-sidebar.slow>*,nav[role=doc-toc].slow>*{transition:opacity .4s ease,border .4s ease}.quarto-color-scheme-toggle:not(.alternate).top-right .bi::before{background-image:url('data:image/svg+xml,')}.quarto-color-scheme-toggle.alternate.top-right .bi::before{background-image:url('data:image/svg+xml,')}#quarto-appendix.default{border-top:1px solid #dee2e6}#quarto-appendix.default{background-color:#131416;padding-top:1.5em;margin-top:2em;z-index:998}#quarto-appendix.default .quarto-appendix-heading{margin-top:0;line-height:1.4em;font-weight:600;opacity:.9;border-bottom:none;margin-bottom:0}#quarto-appendix.default .footnotes ol,#quarto-appendix.default .footnotes ol li>p:last-of-type,#quarto-appendix.default .quarto-appendix-contents>p:last-of-type{margin-bottom:0}#quarto-appendix.default .quarto-appendix-secondary-label{margin-bottom:.4em}#quarto-appendix.default .quarto-appendix-bibtex{font-size:.7em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-bibtex code.sourceCode{white-space:pre-wrap}#quarto-appendix.default .quarto-appendix-citeas{font-size:.9em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-heading{font-size:1em !important}#quarto-appendix.default *[role=doc-endnotes]>ol,#quarto-appendix.default .quarto-appendix-contents>*:not(h2):not(.h2){font-size:.9em}#quarto-appendix.default section{padding-bottom:1.5em}#quarto-appendix.default section *[role=doc-endnotes],#quarto-appendix.default section>*:not(a){opacity:.9;word-wrap:break-word}.btn.btn-quarto,div.cell-output-display .btn-quarto{color:#fefefe;background-color:#6c757d;border-color:#6c757d}.btn.btn-quarto:hover,div.cell-output-display .btn-quarto:hover{color:#fefefe;background-color:#828a91;border-color:#7b838a}.btn-check:focus+.btn.btn-quarto,.btn.btn-quarto:focus,.btn-check:focus+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:focus{color:#fefefe;background-color:#828a91;border-color:#7b838a;box-shadow:0 0 0 .25rem rgba(130,138,144,.5)}.btn-check:checked+.btn.btn-quarto,.btn-check:active+.btn.btn-quarto,.btn.btn-quarto:active,.btn.btn-quarto.active,.show>.btn.btn-quarto.dropdown-toggle,.btn-check:checked+div.cell-output-display .btn-quarto,.btn-check:active+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:active,div.cell-output-display .btn-quarto.active,.show>div.cell-output-display .btn-quarto.dropdown-toggle{color:#fff;background-color:#899197;border-color:#7b838a}.btn-check:checked+.btn.btn-quarto:focus,.btn-check:active+.btn.btn-quarto:focus,.btn.btn-quarto:active:focus,.btn.btn-quarto.active:focus,.show>.btn.btn-quarto.dropdown-toggle:focus,.btn-check:checked+div.cell-output-display .btn-quarto:focus,.btn-check:active+div.cell-output-display .btn-quarto:focus,div.cell-output-display .btn-quarto:active:focus,div.cell-output-display .btn-quarto.active:focus,.show>div.cell-output-display .btn-quarto.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,144,.5)}.btn.btn-quarto:disabled,.btn.btn-quarto.disabled,div.cell-output-display .btn-quarto:disabled,div.cell-output-display .btn-quarto.disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}nav.quarto-secondary-nav.color-navbar{background-color:#1a1c1e;color:rgba(255,255,255,.8)}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:rgba(255,255,255,.8)}@media(max-width: 991.98px){body.nav-sidebar .quarto-title-banner{margin-bottom:0;padding-bottom:0}body.nav-sidebar #title-block-header{margin-block-end:0}}p.subtitle{margin-top:.25em;margin-bottom:.5em}code a:any-link{color:inherit;text-decoration-color:#6c757d}/*! dark */div.observablehq table thead tr th{background-color:var(--bs-body-bg)}input,button,select,optgroup,textarea{background-color:var(--bs-body-bg)}.code-annotated .code-copy-button{margin-right:1.25em;margin-top:0;padding-bottom:0;padding-top:3px}.code-annotation-gutter-bg{background-color:#131416}.code-annotation-gutter{background-color:#272822}.code-annotation-gutter,.code-annotation-gutter-bg{height:100%;width:calc(20px + .5em);position:absolute;top:0;right:0}dl.code-annotation-container-grid dt{margin-right:1em;margin-top:.25rem}dl.code-annotation-container-grid dt{font-family:var(--bs-font-monospace);color:rgba(230,230,230,.8);border:solid rgba(230,230,230,.8) 1px;border-radius:50%;height:22px;width:22px;line-height:22px;font-size:11px;text-align:center;vertical-align:middle;text-decoration:none}dl.code-annotation-container-grid dt[data-target-cell]{cursor:pointer}dl.code-annotation-container-grid dt[data-target-cell].code-annotation-active{color:#131416;border:solid #aaa 1px;background-color:#aaa}pre.code-annotation-code{padding-top:0;padding-bottom:0}pre.code-annotation-code code{z-index:3}#code-annotation-line-highlight-gutter{width:100%;border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}#code-annotation-line-highlight{margin-left:-4em;width:calc(100% + 4em);border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}code.sourceCode .code-annotation-anchor.code-annotation-active{background-color:var(--quarto-hl-normal-color, #aaaaaa);border:solid var(--quarto-hl-normal-color, #aaaaaa) 1px;color:#272822;font-weight:bolder}code.sourceCode .code-annotation-anchor{font-family:var(--bs-font-monospace);color:var(--quarto-hl-co-color);border:solid var(--quarto-hl-co-color) 1px;border-radius:50%;height:18px;width:18px;font-size:9px;margin-top:2px}code.sourceCode button.code-annotation-anchor{padding:2px}code.sourceCode a.code-annotation-anchor{line-height:18px;text-align:center;vertical-align:middle;cursor:default;text-decoration:none}@media print{.page-columns .column-screen-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#131416}.page-columns .column-screen-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#131416}.page-columns .column-screen-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#131416}.page-columns .column-screen{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#131416}.page-columns .column-screen-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#131416}.page-columns .column-screen-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#131416}.page-columns .column-screen-inset-shaded{grid-column:page-start-inset/page-end-inset;padding:1em;background:#f8f5f0;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}}.quarto-video{margin-bottom:1em}.table>thead{border-top-width:0}.table>:not(caption)>*:not(:last-child)>*{border-bottom-color:rgba(255,255,255,.8);border-bottom-style:solid;border-bottom-width:1px}.table>:not(:first-child){border-top:1px solid rgba(255,255,255,.8);border-bottom:1px solid inherit}.table tbody{border-bottom-color:rgba(255,255,255,.8)}a.external:after{display:inline-block;height:.75rem;width:.75rem;margin-bottom:.15em;margin-left:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}div.sourceCode code a.external:after{content:none}a.external:after:hover{cursor:pointer}.quarto-ext-icon{display:inline-block;font-size:.75em;padding-left:.3em}.code-with-filename .code-with-filename-file{margin-bottom:0;padding-bottom:2px;padding-top:2px;padding-left:.7em;border:var(--quarto-border-width) solid var(--quarto-border-color);border-radius:var(--quarto-border-radius);border-bottom:0;border-bottom-left-radius:0%;border-bottom-right-radius:0%}.code-with-filename div.sourceCode,.reveal .code-with-filename div.sourceCode{margin-top:0;border-top-left-radius:0%;border-top-right-radius:0%}.code-with-filename .code-with-filename-file pre{margin-bottom:0}.code-with-filename .code-with-filename-file,.code-with-filename .code-with-filename-file pre{background-color:rgba(219,219,219,.8)}.quarto-dark .code-with-filename .code-with-filename-file,.quarto-dark .code-with-filename .code-with-filename-file pre{background-color:#555}.code-with-filename .code-with-filename-file strong{font-weight:400}.bg-primary{background-color:#3e3f3a !important}.bg-dark{background-color:#6c757d !important}.bg-light{background-color:#f8f5f0 !important}.sandstone,.tooltip,.dropdown-menu .dropdown-item,.pagination,.breadcrumb,.nav-pills .nav-link,.nav-tabs .nav-link,.btn,.navbar .nav-link{font-size:13px;line-height:22px;font-weight:500;text-transform:uppercase}.navbar-form input,.navbar-form .form-control{border:none}.btn:hover{border-color:rgba(0,0,0,0)}.btn-success,.btn-warning{color:#fff}.table .thead-dark th{background-color:#3e3f3a}.nav-tabs .nav-link{background-color:#f8f5f0;border-color:#dee2e6}.nav-tabs .nav-link,.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{color:#6c757d}.nav-tabs .nav-link.disabled,.nav-tabs .nav-link.disabled:hover,.nav-tabs .nav-link.disabled:focus{background-color:#f8f5f0;border-color:#dee2e6;color:#dee2e6}.nav-pills .nav-link{border:1px solid rgba(0,0,0,0);color:#6c757d}.nav-pills .nav-link.active,.nav-pills .nav-link:hover,.nav-pills .nav-link:focus{background-color:#f8f5f0;border-color:#dee2e6}.nav-pills .nav-link.disabled,.nav-pills .nav-link.disabled:hover{background-color:rgba(0,0,0,0);border-color:rgba(0,0,0,0);color:#dee2e6}.breadcrumb{border:1px solid #dee2e6}.pagination a:hover{text-decoration:none}.alert{color:#fff}.alert a,.alert .alert-link{color:#fff;text-decoration:underline}.alert-primary,.alert-primary>th,.alert-primary>td{background-color:#131416}.alert-secondary,.alert-secondary>th,.alert-secondary>td{background-color:#6c757d}.alert-success,.alert-success>th,.alert-success>td{background-color:#93c54b}.alert-info,.alert-info>th,.alert-info>td{background-color:#29abe0}.alert-danger,.alert-danger>th,.alert-danger>td{background-color:#d9534f}.alert-warning,.alert-warning>th,.alert-warning>td{background-color:#f47c3c}.alert-dark,.alert-dark>th,.alert-dark>td{background-color:#3e3f3a}.alert-light,.alert-light>th,.alert-light>td{background-color:#f8f5f0}.alert-light,.alert-light a:not(.btn),.alert-light .alert-link{color:rgba(255,255,255,.8)}.badge.bg-light{color:#3e3f3a}.modal .btn-close,.toast .btn-close{background-image:url("data:image/svg+xml,")}.quarto-title-banner{margin-bottom:1em;color:rgba(255,255,255,.8);background:#1a1c1e}.quarto-title-banner .code-tools-button{color:rgba(204,204,204,.8)}.quarto-title-banner .code-tools-button:hover{color:rgba(255,255,255,.8)}.quarto-title-banner .code-tools-button>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .quarto-title .title{font-weight:600}.quarto-title-banner .quarto-categories{margin-top:.75em}@media(min-width: 992px){.quarto-title-banner{padding-top:2.5em;padding-bottom:2.5em}}@media(max-width: 991.98px){.quarto-title-banner{padding-top:1em;padding-bottom:1em}}main.quarto-banner-title-block>section:first-child>h2,main.quarto-banner-title-block>section:first-child>.h2,main.quarto-banner-title-block>section:first-child>h3,main.quarto-banner-title-block>section:first-child>.h3,main.quarto-banner-title-block>section:first-child>h4,main.quarto-banner-title-block>section:first-child>.h4{margin-top:0}.quarto-title .quarto-categories{display:flex;flex-wrap:wrap;row-gap:.5em;column-gap:.4em;padding-bottom:.5em;margin-top:.75em}.quarto-title .quarto-categories .quarto-category{padding:.25em .75em;font-size:.65em;text-transform:uppercase;border:solid 1px;border-radius:.25rem;opacity:.6}.quarto-title .quarto-categories .quarto-category a{color:inherit}#title-block-header.quarto-title-block.default .quarto-title-meta{display:grid;grid-template-columns:repeat(2, 1fr)}#title-block-header.quarto-title-block.default .quarto-title .title{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-author-orcid img{margin-top:-5px}#title-block-header.quarto-title-block.default .quarto-description p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p,#title-block-header.quarto-title-block.default .quarto-title-authors p,#title-block-header.quarto-title-block.default .quarto-title-affiliations p{margin-bottom:.1em}#title-block-header.quarto-title-block.default .quarto-title-meta-heading{text-transform:uppercase;margin-top:1em;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-contents{font-size:.9em}#title-block-header.quarto-title-block.default .quarto-title-meta-contents a{color:rgba(255,255,255,.8)}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p.affiliation:last-of-type{margin-bottom:.7em}#title-block-header.quarto-title-block.default p.affiliation{margin-bottom:.1em}#title-block-header.quarto-title-block.default .description,#title-block-header.quarto-title-block.default .abstract{margin-top:0}#title-block-header.quarto-title-block.default .description>p,#title-block-header.quarto-title-block.default .abstract>p{font-size:.9em}#title-block-header.quarto-title-block.default .description>p:last-of-type,#title-block-header.quarto-title-block.default .abstract>p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .description .abstract-title,#title-block-header.quarto-title-block.default .abstract .abstract-title{margin-top:1em;text-transform:uppercase;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-author{display:grid;grid-template-columns:1fr 1fr}.quarto-title-tools-only{display:flex;justify-content:right}/*# sourceMappingURL=945575463e70190d99eb671cb8520afc.css.map */ diff --git a/site_libs/bootstrap/bootstrap-icons.css b/site_libs/bootstrap/bootstrap-icons.css new file mode 100644 index 0000000..94f1940 --- /dev/null +++ b/site_libs/bootstrap/bootstrap-icons.css @@ -0,0 +1,2018 @@ +@font-face { + font-display: block; + font-family: "bootstrap-icons"; + src: +url("./bootstrap-icons.woff?2ab2cbbe07fcebb53bdaa7313bb290f2") format("woff"); +} + +.bi::before, +[class^="bi-"]::before, +[class*=" bi-"]::before { + display: inline-block; + font-family: bootstrap-icons !important; + font-style: normal; + font-weight: normal !important; + font-variant: normal; + text-transform: none; + line-height: 1; + vertical-align: -.125em; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.bi-123::before { content: "\f67f"; } +.bi-alarm-fill::before { content: "\f101"; } +.bi-alarm::before { content: "\f102"; } +.bi-align-bottom::before { content: "\f103"; } +.bi-align-center::before { content: "\f104"; } +.bi-align-end::before { content: "\f105"; } +.bi-align-middle::before { content: "\f106"; } +.bi-align-start::before { content: "\f107"; } +.bi-align-top::before { content: "\f108"; } +.bi-alt::before { content: "\f109"; } +.bi-app-indicator::before { content: "\f10a"; } +.bi-app::before { content: "\f10b"; } +.bi-archive-fill::before { content: "\f10c"; } +.bi-archive::before { content: "\f10d"; } +.bi-arrow-90deg-down::before { content: "\f10e"; } +.bi-arrow-90deg-left::before { content: "\f10f"; } +.bi-arrow-90deg-right::before { content: "\f110"; } +.bi-arrow-90deg-up::before { content: "\f111"; } +.bi-arrow-bar-down::before { content: "\f112"; } +.bi-arrow-bar-left::before { content: "\f113"; } +.bi-arrow-bar-right::before { content: "\f114"; } +.bi-arrow-bar-up::before { content: "\f115"; } +.bi-arrow-clockwise::before { content: "\f116"; } +.bi-arrow-counterclockwise::before { content: "\f117"; } +.bi-arrow-down-circle-fill::before { content: "\f118"; } +.bi-arrow-down-circle::before { content: "\f119"; } +.bi-arrow-down-left-circle-fill::before { content: "\f11a"; } +.bi-arrow-down-left-circle::before { content: "\f11b"; } +.bi-arrow-down-left-square-fill::before { content: "\f11c"; } +.bi-arrow-down-left-square::before { content: "\f11d"; } +.bi-arrow-down-left::before { content: "\f11e"; } +.bi-arrow-down-right-circle-fill::before { content: "\f11f"; } +.bi-arrow-down-right-circle::before { content: "\f120"; } +.bi-arrow-down-right-square-fill::before { content: "\f121"; } +.bi-arrow-down-right-square::before { content: "\f122"; } +.bi-arrow-down-right::before { content: "\f123"; } +.bi-arrow-down-short::before { content: "\f124"; } +.bi-arrow-down-square-fill::before { content: "\f125"; } +.bi-arrow-down-square::before { content: "\f126"; } +.bi-arrow-down-up::before { content: "\f127"; } +.bi-arrow-down::before { content: "\f128"; } +.bi-arrow-left-circle-fill::before { content: "\f129"; } +.bi-arrow-left-circle::before { content: "\f12a"; } +.bi-arrow-left-right::before { content: "\f12b"; } +.bi-arrow-left-short::before { content: "\f12c"; } +.bi-arrow-left-square-fill::before { content: "\f12d"; } +.bi-arrow-left-square::before { content: "\f12e"; } +.bi-arrow-left::before { content: "\f12f"; } +.bi-arrow-repeat::before { content: "\f130"; } +.bi-arrow-return-left::before { content: "\f131"; } +.bi-arrow-return-right::before { content: "\f132"; } +.bi-arrow-right-circle-fill::before { content: "\f133"; } +.bi-arrow-right-circle::before { content: "\f134"; } +.bi-arrow-right-short::before { content: "\f135"; } +.bi-arrow-right-square-fill::before { content: "\f136"; } +.bi-arrow-right-square::before { content: "\f137"; } +.bi-arrow-right::before { content: "\f138"; } +.bi-arrow-up-circle-fill::before { content: "\f139"; } +.bi-arrow-up-circle::before { content: "\f13a"; } +.bi-arrow-up-left-circle-fill::before { content: "\f13b"; } +.bi-arrow-up-left-circle::before { content: "\f13c"; } +.bi-arrow-up-left-square-fill::before { content: "\f13d"; } +.bi-arrow-up-left-square::before { content: "\f13e"; } +.bi-arrow-up-left::before { content: "\f13f"; } +.bi-arrow-up-right-circle-fill::before { content: "\f140"; } +.bi-arrow-up-right-circle::before { content: "\f141"; } +.bi-arrow-up-right-square-fill::before { content: "\f142"; } +.bi-arrow-up-right-square::before { content: "\f143"; } +.bi-arrow-up-right::before { content: "\f144"; } +.bi-arrow-up-short::before { content: "\f145"; } +.bi-arrow-up-square-fill::before { content: "\f146"; } +.bi-arrow-up-square::before { content: "\f147"; } +.bi-arrow-up::before { content: "\f148"; } +.bi-arrows-angle-contract::before { content: "\f149"; } +.bi-arrows-angle-expand::before { content: "\f14a"; } +.bi-arrows-collapse::before { content: "\f14b"; } +.bi-arrows-expand::before { content: "\f14c"; } +.bi-arrows-fullscreen::before { content: "\f14d"; } +.bi-arrows-move::before { content: "\f14e"; } +.bi-aspect-ratio-fill::before { content: "\f14f"; } +.bi-aspect-ratio::before { content: "\f150"; } +.bi-asterisk::before { content: "\f151"; } +.bi-at::before { content: "\f152"; } +.bi-award-fill::before { content: "\f153"; } +.bi-award::before { content: "\f154"; } +.bi-back::before { content: "\f155"; } +.bi-backspace-fill::before { content: "\f156"; } +.bi-backspace-reverse-fill::before { content: "\f157"; } +.bi-backspace-reverse::before { content: "\f158"; } +.bi-backspace::before { content: "\f159"; } +.bi-badge-3d-fill::before { content: "\f15a"; } +.bi-badge-3d::before { content: "\f15b"; } +.bi-badge-4k-fill::before { content: "\f15c"; } +.bi-badge-4k::before { content: "\f15d"; } +.bi-badge-8k-fill::before { content: "\f15e"; } +.bi-badge-8k::before { content: "\f15f"; } +.bi-badge-ad-fill::before { content: "\f160"; } +.bi-badge-ad::before { content: "\f161"; } +.bi-badge-ar-fill::before { content: "\f162"; } +.bi-badge-ar::before { content: "\f163"; } +.bi-badge-cc-fill::before { content: "\f164"; } +.bi-badge-cc::before { content: "\f165"; } +.bi-badge-hd-fill::before { content: "\f166"; } +.bi-badge-hd::before { content: "\f167"; } +.bi-badge-tm-fill::before { content: "\f168"; } +.bi-badge-tm::before { content: "\f169"; } +.bi-badge-vo-fill::before { content: "\f16a"; } +.bi-badge-vo::before { content: "\f16b"; } +.bi-badge-vr-fill::before { content: "\f16c"; } +.bi-badge-vr::before { content: "\f16d"; } +.bi-badge-wc-fill::before { content: "\f16e"; } +.bi-badge-wc::before { content: "\f16f"; } +.bi-bag-check-fill::before { content: "\f170"; } +.bi-bag-check::before { content: "\f171"; } +.bi-bag-dash-fill::before { content: "\f172"; } +.bi-bag-dash::before { content: "\f173"; } +.bi-bag-fill::before { content: "\f174"; } +.bi-bag-plus-fill::before { content: "\f175"; } +.bi-bag-plus::before { content: "\f176"; } +.bi-bag-x-fill::before { content: "\f177"; } +.bi-bag-x::before { content: "\f178"; } +.bi-bag::before { content: "\f179"; } +.bi-bar-chart-fill::before { content: "\f17a"; } +.bi-bar-chart-line-fill::before { content: "\f17b"; } +.bi-bar-chart-line::before { content: "\f17c"; } +.bi-bar-chart-steps::before { content: "\f17d"; } +.bi-bar-chart::before { content: "\f17e"; } +.bi-basket-fill::before { content: "\f17f"; } +.bi-basket::before { content: "\f180"; } +.bi-basket2-fill::before { content: "\f181"; } +.bi-basket2::before { content: "\f182"; } +.bi-basket3-fill::before { content: "\f183"; } +.bi-basket3::before { content: "\f184"; } +.bi-battery-charging::before { content: "\f185"; } +.bi-battery-full::before { content: "\f186"; } +.bi-battery-half::before { content: "\f187"; } +.bi-battery::before { content: "\f188"; } +.bi-bell-fill::before { content: "\f189"; } +.bi-bell::before { content: "\f18a"; } +.bi-bezier::before { content: "\f18b"; } +.bi-bezier2::before { content: "\f18c"; } +.bi-bicycle::before { content: "\f18d"; } +.bi-binoculars-fill::before { content: "\f18e"; } +.bi-binoculars::before { content: "\f18f"; } +.bi-blockquote-left::before { content: "\f190"; } +.bi-blockquote-right::before { content: "\f191"; } +.bi-book-fill::before { content: "\f192"; } +.bi-book-half::before { content: "\f193"; } +.bi-book::before { content: "\f194"; } +.bi-bookmark-check-fill::before { content: "\f195"; } +.bi-bookmark-check::before { content: "\f196"; } +.bi-bookmark-dash-fill::before { content: "\f197"; } +.bi-bookmark-dash::before { content: "\f198"; } +.bi-bookmark-fill::before { content: "\f199"; } +.bi-bookmark-heart-fill::before { content: "\f19a"; } +.bi-bookmark-heart::before { content: "\f19b"; } +.bi-bookmark-plus-fill::before { content: "\f19c"; } +.bi-bookmark-plus::before { content: "\f19d"; } +.bi-bookmark-star-fill::before { content: "\f19e"; } +.bi-bookmark-star::before { content: "\f19f"; } +.bi-bookmark-x-fill::before { content: "\f1a0"; } +.bi-bookmark-x::before { content: "\f1a1"; } +.bi-bookmark::before { content: "\f1a2"; } +.bi-bookmarks-fill::before { content: "\f1a3"; } +.bi-bookmarks::before { content: "\f1a4"; } +.bi-bookshelf::before { content: "\f1a5"; } +.bi-bootstrap-fill::before { content: "\f1a6"; } +.bi-bootstrap-reboot::before { content: "\f1a7"; } +.bi-bootstrap::before { content: "\f1a8"; } +.bi-border-all::before { content: "\f1a9"; } +.bi-border-bottom::before { content: "\f1aa"; } +.bi-border-center::before { content: "\f1ab"; } +.bi-border-inner::before { content: "\f1ac"; } +.bi-border-left::before { content: "\f1ad"; } +.bi-border-middle::before { content: "\f1ae"; } +.bi-border-outer::before { content: "\f1af"; } +.bi-border-right::before { content: "\f1b0"; } +.bi-border-style::before { content: "\f1b1"; } +.bi-border-top::before { content: "\f1b2"; } +.bi-border-width::before { content: "\f1b3"; } +.bi-border::before { content: "\f1b4"; } +.bi-bounding-box-circles::before { content: "\f1b5"; } +.bi-bounding-box::before { content: "\f1b6"; } +.bi-box-arrow-down-left::before { content: "\f1b7"; } +.bi-box-arrow-down-right::before { content: "\f1b8"; } +.bi-box-arrow-down::before { content: "\f1b9"; } +.bi-box-arrow-in-down-left::before { content: "\f1ba"; } +.bi-box-arrow-in-down-right::before { content: "\f1bb"; } +.bi-box-arrow-in-down::before { content: "\f1bc"; } +.bi-box-arrow-in-left::before { content: "\f1bd"; } +.bi-box-arrow-in-right::before { content: "\f1be"; } +.bi-box-arrow-in-up-left::before { content: "\f1bf"; } +.bi-box-arrow-in-up-right::before { content: "\f1c0"; } +.bi-box-arrow-in-up::before { content: "\f1c1"; } +.bi-box-arrow-left::before { content: "\f1c2"; } +.bi-box-arrow-right::before { content: "\f1c3"; } +.bi-box-arrow-up-left::before { content: "\f1c4"; } +.bi-box-arrow-up-right::before { content: "\f1c5"; } +.bi-box-arrow-up::before { content: "\f1c6"; } +.bi-box-seam::before { content: "\f1c7"; } +.bi-box::before { content: "\f1c8"; } +.bi-braces::before { content: "\f1c9"; } +.bi-bricks::before { content: "\f1ca"; } +.bi-briefcase-fill::before { content: "\f1cb"; } +.bi-briefcase::before { content: "\f1cc"; } +.bi-brightness-alt-high-fill::before { content: "\f1cd"; } +.bi-brightness-alt-high::before { content: "\f1ce"; } +.bi-brightness-alt-low-fill::before { content: "\f1cf"; } +.bi-brightness-alt-low::before { content: "\f1d0"; } +.bi-brightness-high-fill::before { content: "\f1d1"; } +.bi-brightness-high::before { content: "\f1d2"; } +.bi-brightness-low-fill::before { content: "\f1d3"; } +.bi-brightness-low::before { content: "\f1d4"; } +.bi-broadcast-pin::before { content: "\f1d5"; } +.bi-broadcast::before { content: "\f1d6"; } +.bi-brush-fill::before { content: "\f1d7"; } +.bi-brush::before { content: "\f1d8"; } +.bi-bucket-fill::before { content: "\f1d9"; } +.bi-bucket::before { content: "\f1da"; } +.bi-bug-fill::before { content: "\f1db"; } +.bi-bug::before { content: "\f1dc"; } +.bi-building::before { content: "\f1dd"; } +.bi-bullseye::before { content: "\f1de"; } +.bi-calculator-fill::before { content: "\f1df"; } +.bi-calculator::before { content: "\f1e0"; } +.bi-calendar-check-fill::before { content: "\f1e1"; } +.bi-calendar-check::before { content: "\f1e2"; } +.bi-calendar-date-fill::before { content: "\f1e3"; } +.bi-calendar-date::before { content: "\f1e4"; } +.bi-calendar-day-fill::before { content: "\f1e5"; } +.bi-calendar-day::before { content: "\f1e6"; } +.bi-calendar-event-fill::before { content: "\f1e7"; } +.bi-calendar-event::before { content: "\f1e8"; } +.bi-calendar-fill::before { content: "\f1e9"; } +.bi-calendar-minus-fill::before { content: "\f1ea"; } +.bi-calendar-minus::before { content: "\f1eb"; } +.bi-calendar-month-fill::before { content: "\f1ec"; } +.bi-calendar-month::before { content: "\f1ed"; } +.bi-calendar-plus-fill::before { content: "\f1ee"; } +.bi-calendar-plus::before { content: "\f1ef"; } +.bi-calendar-range-fill::before { content: "\f1f0"; } +.bi-calendar-range::before { content: "\f1f1"; } +.bi-calendar-week-fill::before { content: "\f1f2"; } +.bi-calendar-week::before { content: "\f1f3"; } +.bi-calendar-x-fill::before { content: "\f1f4"; } +.bi-calendar-x::before { content: "\f1f5"; } +.bi-calendar::before { content: "\f1f6"; } +.bi-calendar2-check-fill::before { content: "\f1f7"; } +.bi-calendar2-check::before { content: "\f1f8"; } +.bi-calendar2-date-fill::before { content: "\f1f9"; } +.bi-calendar2-date::before { content: "\f1fa"; } +.bi-calendar2-day-fill::before { content: "\f1fb"; } +.bi-calendar2-day::before { content: "\f1fc"; } +.bi-calendar2-event-fill::before { content: "\f1fd"; } +.bi-calendar2-event::before { content: "\f1fe"; } +.bi-calendar2-fill::before { content: "\f1ff"; } +.bi-calendar2-minus-fill::before { content: "\f200"; } +.bi-calendar2-minus::before { content: "\f201"; } +.bi-calendar2-month-fill::before { content: "\f202"; } +.bi-calendar2-month::before { content: "\f203"; } +.bi-calendar2-plus-fill::before { content: "\f204"; } +.bi-calendar2-plus::before { content: "\f205"; } +.bi-calendar2-range-fill::before { content: "\f206"; } +.bi-calendar2-range::before { content: "\f207"; } +.bi-calendar2-week-fill::before { content: "\f208"; } +.bi-calendar2-week::before { content: "\f209"; } +.bi-calendar2-x-fill::before { content: "\f20a"; } +.bi-calendar2-x::before { content: "\f20b"; } +.bi-calendar2::before { content: "\f20c"; } +.bi-calendar3-event-fill::before { content: "\f20d"; } +.bi-calendar3-event::before { content: "\f20e"; } +.bi-calendar3-fill::before { content: "\f20f"; } +.bi-calendar3-range-fill::before { content: "\f210"; } +.bi-calendar3-range::before { content: "\f211"; } +.bi-calendar3-week-fill::before { content: "\f212"; } +.bi-calendar3-week::before { content: "\f213"; } +.bi-calendar3::before { content: "\f214"; } +.bi-calendar4-event::before { content: "\f215"; } +.bi-calendar4-range::before { content: "\f216"; } +.bi-calendar4-week::before { content: "\f217"; } +.bi-calendar4::before { content: "\f218"; } +.bi-camera-fill::before { content: "\f219"; } +.bi-camera-reels-fill::before { content: "\f21a"; } +.bi-camera-reels::before { content: "\f21b"; } +.bi-camera-video-fill::before { content: "\f21c"; } +.bi-camera-video-off-fill::before { content: "\f21d"; } +.bi-camera-video-off::before { content: "\f21e"; } +.bi-camera-video::before { content: "\f21f"; } +.bi-camera::before { content: "\f220"; } +.bi-camera2::before { content: "\f221"; } +.bi-capslock-fill::before { content: "\f222"; } +.bi-capslock::before { content: "\f223"; } +.bi-card-checklist::before { content: "\f224"; } +.bi-card-heading::before { content: "\f225"; } +.bi-card-image::before { content: "\f226"; } +.bi-card-list::before { content: "\f227"; } +.bi-card-text::before { content: "\f228"; } +.bi-caret-down-fill::before { content: "\f229"; } +.bi-caret-down-square-fill::before { content: "\f22a"; } +.bi-caret-down-square::before { content: "\f22b"; } +.bi-caret-down::before { content: "\f22c"; } +.bi-caret-left-fill::before { content: "\f22d"; } +.bi-caret-left-square-fill::before { content: "\f22e"; } +.bi-caret-left-square::before { content: "\f22f"; } +.bi-caret-left::before { content: "\f230"; } +.bi-caret-right-fill::before { content: "\f231"; } +.bi-caret-right-square-fill::before { content: "\f232"; } +.bi-caret-right-square::before { content: "\f233"; } +.bi-caret-right::before { content: "\f234"; } +.bi-caret-up-fill::before { content: "\f235"; } +.bi-caret-up-square-fill::before { content: "\f236"; } +.bi-caret-up-square::before { content: "\f237"; } +.bi-caret-up::before { content: "\f238"; } +.bi-cart-check-fill::before { content: "\f239"; } +.bi-cart-check::before { content: "\f23a"; } +.bi-cart-dash-fill::before { content: "\f23b"; } +.bi-cart-dash::before { content: "\f23c"; } +.bi-cart-fill::before { content: "\f23d"; } +.bi-cart-plus-fill::before { content: "\f23e"; } +.bi-cart-plus::before { content: "\f23f"; } +.bi-cart-x-fill::before { content: "\f240"; } +.bi-cart-x::before { content: "\f241"; } +.bi-cart::before { content: "\f242"; } +.bi-cart2::before { content: "\f243"; } +.bi-cart3::before { content: "\f244"; } +.bi-cart4::before { content: "\f245"; } +.bi-cash-stack::before { content: "\f246"; } +.bi-cash::before { content: "\f247"; } +.bi-cast::before { content: "\f248"; } +.bi-chat-dots-fill::before { content: "\f249"; } +.bi-chat-dots::before { content: "\f24a"; } +.bi-chat-fill::before { content: "\f24b"; } +.bi-chat-left-dots-fill::before { content: "\f24c"; } +.bi-chat-left-dots::before { content: "\f24d"; } +.bi-chat-left-fill::before { content: "\f24e"; } +.bi-chat-left-quote-fill::before { content: "\f24f"; } +.bi-chat-left-quote::before { content: "\f250"; } +.bi-chat-left-text-fill::before { content: "\f251"; } +.bi-chat-left-text::before { content: "\f252"; } +.bi-chat-left::before { content: "\f253"; } +.bi-chat-quote-fill::before { content: "\f254"; } +.bi-chat-quote::before { content: "\f255"; } +.bi-chat-right-dots-fill::before { content: "\f256"; } +.bi-chat-right-dots::before { content: "\f257"; } +.bi-chat-right-fill::before { content: "\f258"; } +.bi-chat-right-quote-fill::before { content: "\f259"; } +.bi-chat-right-quote::before { content: "\f25a"; } +.bi-chat-right-text-fill::before { content: "\f25b"; } +.bi-chat-right-text::before { content: "\f25c"; } +.bi-chat-right::before { content: "\f25d"; } +.bi-chat-square-dots-fill::before { content: "\f25e"; } +.bi-chat-square-dots::before { content: "\f25f"; } +.bi-chat-square-fill::before { content: "\f260"; } +.bi-chat-square-quote-fill::before { content: "\f261"; } +.bi-chat-square-quote::before { content: "\f262"; } +.bi-chat-square-text-fill::before { content: "\f263"; } +.bi-chat-square-text::before { content: "\f264"; } +.bi-chat-square::before { content: "\f265"; } +.bi-chat-text-fill::before { content: "\f266"; } +.bi-chat-text::before { content: "\f267"; } +.bi-chat::before { content: "\f268"; } +.bi-check-all::before { content: "\f269"; } +.bi-check-circle-fill::before { content: "\f26a"; } +.bi-check-circle::before { content: "\f26b"; } +.bi-check-square-fill::before { content: "\f26c"; } +.bi-check-square::before { content: "\f26d"; } +.bi-check::before { content: "\f26e"; } +.bi-check2-all::before { content: "\f26f"; } +.bi-check2-circle::before { content: "\f270"; } +.bi-check2-square::before { content: "\f271"; } +.bi-check2::before { content: "\f272"; } +.bi-chevron-bar-contract::before { content: "\f273"; } +.bi-chevron-bar-down::before { content: "\f274"; } +.bi-chevron-bar-expand::before { content: "\f275"; } +.bi-chevron-bar-left::before { content: "\f276"; } +.bi-chevron-bar-right::before { content: "\f277"; } +.bi-chevron-bar-up::before { content: "\f278"; } +.bi-chevron-compact-down::before { content: "\f279"; } +.bi-chevron-compact-left::before { content: "\f27a"; } +.bi-chevron-compact-right::before { content: "\f27b"; } +.bi-chevron-compact-up::before { content: "\f27c"; } +.bi-chevron-contract::before { content: "\f27d"; } +.bi-chevron-double-down::before { content: "\f27e"; } +.bi-chevron-double-left::before { content: "\f27f"; } +.bi-chevron-double-right::before { content: "\f280"; } +.bi-chevron-double-up::before { content: "\f281"; } +.bi-chevron-down::before { content: "\f282"; } +.bi-chevron-expand::before { content: "\f283"; } +.bi-chevron-left::before { content: "\f284"; } +.bi-chevron-right::before { content: "\f285"; } +.bi-chevron-up::before { content: "\f286"; } +.bi-circle-fill::before { content: "\f287"; } +.bi-circle-half::before { content: "\f288"; } +.bi-circle-square::before { content: "\f289"; } +.bi-circle::before { content: "\f28a"; } +.bi-clipboard-check::before { content: "\f28b"; } +.bi-clipboard-data::before { content: "\f28c"; } +.bi-clipboard-minus::before { content: "\f28d"; } +.bi-clipboard-plus::before { content: "\f28e"; } +.bi-clipboard-x::before { content: "\f28f"; } +.bi-clipboard::before { content: "\f290"; } +.bi-clock-fill::before { content: "\f291"; } +.bi-clock-history::before { content: "\f292"; } +.bi-clock::before { content: "\f293"; } +.bi-cloud-arrow-down-fill::before { content: "\f294"; } +.bi-cloud-arrow-down::before { content: "\f295"; } +.bi-cloud-arrow-up-fill::before { content: "\f296"; } +.bi-cloud-arrow-up::before { content: "\f297"; } +.bi-cloud-check-fill::before { content: "\f298"; } +.bi-cloud-check::before { content: "\f299"; } +.bi-cloud-download-fill::before { content: "\f29a"; } +.bi-cloud-download::before { content: "\f29b"; } +.bi-cloud-drizzle-fill::before { content: "\f29c"; } +.bi-cloud-drizzle::before { content: "\f29d"; } +.bi-cloud-fill::before { content: "\f29e"; } +.bi-cloud-fog-fill::before { content: "\f29f"; } +.bi-cloud-fog::before { content: "\f2a0"; } +.bi-cloud-fog2-fill::before { content: "\f2a1"; } +.bi-cloud-fog2::before { content: "\f2a2"; } +.bi-cloud-hail-fill::before { content: "\f2a3"; } +.bi-cloud-hail::before { content: "\f2a4"; } +.bi-cloud-haze-1::before { content: "\f2a5"; } +.bi-cloud-haze-fill::before { content: "\f2a6"; } +.bi-cloud-haze::before { content: "\f2a7"; } +.bi-cloud-haze2-fill::before { content: "\f2a8"; } +.bi-cloud-lightning-fill::before { content: "\f2a9"; } +.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; } +.bi-cloud-lightning-rain::before { content: "\f2ab"; } +.bi-cloud-lightning::before { content: "\f2ac"; } +.bi-cloud-minus-fill::before { content: "\f2ad"; } +.bi-cloud-minus::before { content: "\f2ae"; } +.bi-cloud-moon-fill::before { content: "\f2af"; } +.bi-cloud-moon::before { content: "\f2b0"; } +.bi-cloud-plus-fill::before { content: "\f2b1"; } +.bi-cloud-plus::before { content: "\f2b2"; } +.bi-cloud-rain-fill::before { content: "\f2b3"; } +.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; } +.bi-cloud-rain-heavy::before { content: "\f2b5"; } +.bi-cloud-rain::before { content: "\f2b6"; } +.bi-cloud-slash-fill::before { content: "\f2b7"; } +.bi-cloud-slash::before { content: "\f2b8"; } +.bi-cloud-sleet-fill::before { content: "\f2b9"; } +.bi-cloud-sleet::before { content: "\f2ba"; } +.bi-cloud-snow-fill::before { content: "\f2bb"; } +.bi-cloud-snow::before { content: "\f2bc"; } +.bi-cloud-sun-fill::before { content: "\f2bd"; } +.bi-cloud-sun::before { content: "\f2be"; } +.bi-cloud-upload-fill::before { content: "\f2bf"; } +.bi-cloud-upload::before { content: "\f2c0"; } +.bi-cloud::before { content: "\f2c1"; } +.bi-clouds-fill::before { content: "\f2c2"; } +.bi-clouds::before { content: "\f2c3"; } +.bi-cloudy-fill::before { content: "\f2c4"; } +.bi-cloudy::before { content: "\f2c5"; } +.bi-code-slash::before { content: "\f2c6"; } +.bi-code-square::before { content: "\f2c7"; } +.bi-code::before { content: "\f2c8"; } +.bi-collection-fill::before { content: "\f2c9"; } +.bi-collection-play-fill::before { content: "\f2ca"; } +.bi-collection-play::before { content: "\f2cb"; } +.bi-collection::before { content: "\f2cc"; } +.bi-columns-gap::before { content: "\f2cd"; } +.bi-columns::before { content: "\f2ce"; } +.bi-command::before { content: "\f2cf"; } +.bi-compass-fill::before { content: "\f2d0"; } +.bi-compass::before { content: "\f2d1"; } +.bi-cone-striped::before { content: "\f2d2"; } +.bi-cone::before { content: "\f2d3"; } +.bi-controller::before { content: "\f2d4"; } +.bi-cpu-fill::before { content: "\f2d5"; } +.bi-cpu::before { content: "\f2d6"; } +.bi-credit-card-2-back-fill::before { content: "\f2d7"; } +.bi-credit-card-2-back::before { content: "\f2d8"; } +.bi-credit-card-2-front-fill::before { content: "\f2d9"; } +.bi-credit-card-2-front::before { content: "\f2da"; } +.bi-credit-card-fill::before { content: "\f2db"; } +.bi-credit-card::before { content: "\f2dc"; } +.bi-crop::before { content: "\f2dd"; } +.bi-cup-fill::before { content: "\f2de"; } +.bi-cup-straw::before { content: "\f2df"; } +.bi-cup::before { content: "\f2e0"; } +.bi-cursor-fill::before { content: "\f2e1"; } +.bi-cursor-text::before { content: "\f2e2"; } +.bi-cursor::before { content: "\f2e3"; } +.bi-dash-circle-dotted::before { content: "\f2e4"; } +.bi-dash-circle-fill::before { content: "\f2e5"; } +.bi-dash-circle::before { content: "\f2e6"; } +.bi-dash-square-dotted::before { content: "\f2e7"; } +.bi-dash-square-fill::before { content: "\f2e8"; } +.bi-dash-square::before { content: "\f2e9"; } +.bi-dash::before { content: "\f2ea"; } +.bi-diagram-2-fill::before { content: "\f2eb"; } +.bi-diagram-2::before { content: "\f2ec"; } +.bi-diagram-3-fill::before { content: "\f2ed"; } +.bi-diagram-3::before { content: "\f2ee"; } +.bi-diamond-fill::before { content: "\f2ef"; } +.bi-diamond-half::before { content: "\f2f0"; } +.bi-diamond::before { content: "\f2f1"; } +.bi-dice-1-fill::before { content: "\f2f2"; } +.bi-dice-1::before { content: "\f2f3"; } +.bi-dice-2-fill::before { content: "\f2f4"; } +.bi-dice-2::before { content: "\f2f5"; } +.bi-dice-3-fill::before { content: "\f2f6"; } +.bi-dice-3::before { content: "\f2f7"; } +.bi-dice-4-fill::before { content: "\f2f8"; } +.bi-dice-4::before { content: "\f2f9"; } +.bi-dice-5-fill::before { content: "\f2fa"; } +.bi-dice-5::before { content: "\f2fb"; } +.bi-dice-6-fill::before { content: "\f2fc"; } +.bi-dice-6::before { content: "\f2fd"; } +.bi-disc-fill::before { content: "\f2fe"; } +.bi-disc::before { content: "\f2ff"; } +.bi-discord::before { content: "\f300"; } +.bi-display-fill::before { content: "\f301"; } +.bi-display::before { content: "\f302"; } +.bi-distribute-horizontal::before { content: "\f303"; } +.bi-distribute-vertical::before { content: "\f304"; } +.bi-door-closed-fill::before { content: "\f305"; } +.bi-door-closed::before { content: "\f306"; } +.bi-door-open-fill::before { content: "\f307"; } +.bi-door-open::before { content: "\f308"; } +.bi-dot::before { content: "\f309"; } +.bi-download::before { content: "\f30a"; } +.bi-droplet-fill::before { content: "\f30b"; } +.bi-droplet-half::before { content: "\f30c"; } +.bi-droplet::before { content: "\f30d"; } +.bi-earbuds::before { content: "\f30e"; } +.bi-easel-fill::before { content: "\f30f"; } +.bi-easel::before { content: "\f310"; } +.bi-egg-fill::before { content: "\f311"; } +.bi-egg-fried::before { content: "\f312"; } +.bi-egg::before { content: "\f313"; } +.bi-eject-fill::before { content: "\f314"; } +.bi-eject::before { content: "\f315"; } +.bi-emoji-angry-fill::before { content: "\f316"; } +.bi-emoji-angry::before { content: "\f317"; } +.bi-emoji-dizzy-fill::before { content: "\f318"; } +.bi-emoji-dizzy::before { content: "\f319"; } +.bi-emoji-expressionless-fill::before { content: "\f31a"; } +.bi-emoji-expressionless::before { content: "\f31b"; } +.bi-emoji-frown-fill::before { content: "\f31c"; } +.bi-emoji-frown::before { content: "\f31d"; } +.bi-emoji-heart-eyes-fill::before { content: "\f31e"; } +.bi-emoji-heart-eyes::before { content: "\f31f"; } +.bi-emoji-laughing-fill::before { content: "\f320"; } +.bi-emoji-laughing::before { content: "\f321"; } +.bi-emoji-neutral-fill::before { content: "\f322"; } +.bi-emoji-neutral::before { content: "\f323"; } +.bi-emoji-smile-fill::before { content: "\f324"; } +.bi-emoji-smile-upside-down-fill::before { content: "\f325"; } +.bi-emoji-smile-upside-down::before { content: "\f326"; } +.bi-emoji-smile::before { content: "\f327"; } +.bi-emoji-sunglasses-fill::before { content: "\f328"; } +.bi-emoji-sunglasses::before { content: "\f329"; } +.bi-emoji-wink-fill::before { content: "\f32a"; } +.bi-emoji-wink::before { content: "\f32b"; } +.bi-envelope-fill::before { content: "\f32c"; } +.bi-envelope-open-fill::before { content: "\f32d"; } +.bi-envelope-open::before { content: "\f32e"; } +.bi-envelope::before { content: "\f32f"; } +.bi-eraser-fill::before { content: "\f330"; } +.bi-eraser::before { content: "\f331"; } +.bi-exclamation-circle-fill::before { content: "\f332"; } +.bi-exclamation-circle::before { content: "\f333"; } +.bi-exclamation-diamond-fill::before { content: "\f334"; } +.bi-exclamation-diamond::before { content: "\f335"; } +.bi-exclamation-octagon-fill::before { content: "\f336"; } +.bi-exclamation-octagon::before { content: "\f337"; } +.bi-exclamation-square-fill::before { content: "\f338"; } +.bi-exclamation-square::before { content: "\f339"; } +.bi-exclamation-triangle-fill::before { content: "\f33a"; } +.bi-exclamation-triangle::before { content: "\f33b"; } +.bi-exclamation::before { content: "\f33c"; } +.bi-exclude::before { content: "\f33d"; } +.bi-eye-fill::before { content: "\f33e"; } +.bi-eye-slash-fill::before { content: "\f33f"; } +.bi-eye-slash::before { content: "\f340"; } +.bi-eye::before { content: "\f341"; } +.bi-eyedropper::before { content: "\f342"; } +.bi-eyeglasses::before { content: "\f343"; } +.bi-facebook::before { content: "\f344"; } +.bi-file-arrow-down-fill::before { content: "\f345"; } +.bi-file-arrow-down::before { content: "\f346"; } +.bi-file-arrow-up-fill::before { content: "\f347"; } +.bi-file-arrow-up::before { content: "\f348"; } +.bi-file-bar-graph-fill::before { content: "\f349"; } +.bi-file-bar-graph::before { content: "\f34a"; } +.bi-file-binary-fill::before { content: "\f34b"; } +.bi-file-binary::before { content: "\f34c"; } +.bi-file-break-fill::before { content: "\f34d"; } +.bi-file-break::before { content: "\f34e"; } +.bi-file-check-fill::before { content: "\f34f"; } +.bi-file-check::before { content: "\f350"; } +.bi-file-code-fill::before { content: "\f351"; } +.bi-file-code::before { content: "\f352"; } +.bi-file-diff-fill::before { content: "\f353"; } +.bi-file-diff::before { content: "\f354"; } +.bi-file-earmark-arrow-down-fill::before { content: "\f355"; } +.bi-file-earmark-arrow-down::before { content: "\f356"; } +.bi-file-earmark-arrow-up-fill::before { content: "\f357"; } +.bi-file-earmark-arrow-up::before { content: "\f358"; } +.bi-file-earmark-bar-graph-fill::before { content: "\f359"; } +.bi-file-earmark-bar-graph::before { content: "\f35a"; } +.bi-file-earmark-binary-fill::before { content: "\f35b"; } +.bi-file-earmark-binary::before { content: "\f35c"; } +.bi-file-earmark-break-fill::before { content: "\f35d"; } +.bi-file-earmark-break::before { content: "\f35e"; } +.bi-file-earmark-check-fill::before { content: "\f35f"; } +.bi-file-earmark-check::before { content: "\f360"; } +.bi-file-earmark-code-fill::before { content: "\f361"; } +.bi-file-earmark-code::before { content: "\f362"; } +.bi-file-earmark-diff-fill::before { content: "\f363"; } +.bi-file-earmark-diff::before { content: "\f364"; } +.bi-file-earmark-easel-fill::before { content: "\f365"; } +.bi-file-earmark-easel::before { content: "\f366"; } +.bi-file-earmark-excel-fill::before { content: "\f367"; } +.bi-file-earmark-excel::before { content: "\f368"; } +.bi-file-earmark-fill::before { content: "\f369"; } +.bi-file-earmark-font-fill::before { content: "\f36a"; } +.bi-file-earmark-font::before { content: "\f36b"; } +.bi-file-earmark-image-fill::before { content: "\f36c"; } +.bi-file-earmark-image::before { content: "\f36d"; } +.bi-file-earmark-lock-fill::before { content: "\f36e"; } +.bi-file-earmark-lock::before { content: "\f36f"; } +.bi-file-earmark-lock2-fill::before { content: "\f370"; } +.bi-file-earmark-lock2::before { content: "\f371"; } +.bi-file-earmark-medical-fill::before { content: "\f372"; } +.bi-file-earmark-medical::before { content: "\f373"; } +.bi-file-earmark-minus-fill::before { content: "\f374"; } +.bi-file-earmark-minus::before { content: "\f375"; } +.bi-file-earmark-music-fill::before { content: "\f376"; } +.bi-file-earmark-music::before { content: "\f377"; } +.bi-file-earmark-person-fill::before { content: "\f378"; } +.bi-file-earmark-person::before { content: "\f379"; } +.bi-file-earmark-play-fill::before { content: "\f37a"; } +.bi-file-earmark-play::before { content: "\f37b"; } +.bi-file-earmark-plus-fill::before { content: "\f37c"; } +.bi-file-earmark-plus::before { content: "\f37d"; } +.bi-file-earmark-post-fill::before { content: "\f37e"; } +.bi-file-earmark-post::before { content: "\f37f"; } +.bi-file-earmark-ppt-fill::before { content: "\f380"; } +.bi-file-earmark-ppt::before { content: "\f381"; } +.bi-file-earmark-richtext-fill::before { content: "\f382"; } +.bi-file-earmark-richtext::before { content: "\f383"; } +.bi-file-earmark-ruled-fill::before { content: "\f384"; } +.bi-file-earmark-ruled::before { content: "\f385"; } +.bi-file-earmark-slides-fill::before { content: "\f386"; } +.bi-file-earmark-slides::before { content: "\f387"; } +.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; } +.bi-file-earmark-spreadsheet::before { content: "\f389"; } +.bi-file-earmark-text-fill::before { content: "\f38a"; } +.bi-file-earmark-text::before { content: "\f38b"; } +.bi-file-earmark-word-fill::before { content: "\f38c"; } +.bi-file-earmark-word::before { content: "\f38d"; } +.bi-file-earmark-x-fill::before { content: "\f38e"; } +.bi-file-earmark-x::before { content: "\f38f"; } +.bi-file-earmark-zip-fill::before { content: "\f390"; } +.bi-file-earmark-zip::before { content: "\f391"; } +.bi-file-earmark::before { content: "\f392"; } +.bi-file-easel-fill::before { content: "\f393"; } +.bi-file-easel::before { content: "\f394"; } +.bi-file-excel-fill::before { content: "\f395"; } +.bi-file-excel::before { content: "\f396"; } +.bi-file-fill::before { content: "\f397"; } +.bi-file-font-fill::before { content: "\f398"; } +.bi-file-font::before { content: "\f399"; } +.bi-file-image-fill::before { content: "\f39a"; } +.bi-file-image::before { content: "\f39b"; } +.bi-file-lock-fill::before { content: "\f39c"; } +.bi-file-lock::before { content: "\f39d"; } +.bi-file-lock2-fill::before { content: "\f39e"; } +.bi-file-lock2::before { content: "\f39f"; } +.bi-file-medical-fill::before { content: "\f3a0"; } +.bi-file-medical::before { content: "\f3a1"; } +.bi-file-minus-fill::before { content: "\f3a2"; } +.bi-file-minus::before { content: "\f3a3"; } +.bi-file-music-fill::before { content: "\f3a4"; } +.bi-file-music::before { content: "\f3a5"; } +.bi-file-person-fill::before { content: "\f3a6"; } +.bi-file-person::before { content: "\f3a7"; } +.bi-file-play-fill::before { content: "\f3a8"; } +.bi-file-play::before { content: "\f3a9"; } +.bi-file-plus-fill::before { content: "\f3aa"; } +.bi-file-plus::before { content: "\f3ab"; } +.bi-file-post-fill::before { content: "\f3ac"; } +.bi-file-post::before { content: "\f3ad"; } +.bi-file-ppt-fill::before { content: "\f3ae"; } +.bi-file-ppt::before { content: "\f3af"; } +.bi-file-richtext-fill::before { content: "\f3b0"; } +.bi-file-richtext::before { content: "\f3b1"; } +.bi-file-ruled-fill::before { content: "\f3b2"; } +.bi-file-ruled::before { content: "\f3b3"; } +.bi-file-slides-fill::before { content: "\f3b4"; } +.bi-file-slides::before { content: "\f3b5"; } +.bi-file-spreadsheet-fill::before { content: "\f3b6"; } +.bi-file-spreadsheet::before { content: "\f3b7"; } +.bi-file-text-fill::before { content: "\f3b8"; } +.bi-file-text::before { content: "\f3b9"; } +.bi-file-word-fill::before { content: "\f3ba"; } +.bi-file-word::before { content: "\f3bb"; } +.bi-file-x-fill::before { content: "\f3bc"; } +.bi-file-x::before { content: "\f3bd"; } +.bi-file-zip-fill::before { content: "\f3be"; } +.bi-file-zip::before { content: "\f3bf"; } +.bi-file::before { content: "\f3c0"; } +.bi-files-alt::before { content: "\f3c1"; } +.bi-files::before { content: "\f3c2"; } +.bi-film::before { content: "\f3c3"; } +.bi-filter-circle-fill::before { content: "\f3c4"; } +.bi-filter-circle::before { content: "\f3c5"; } +.bi-filter-left::before { content: "\f3c6"; } +.bi-filter-right::before { content: "\f3c7"; } +.bi-filter-square-fill::before { content: "\f3c8"; } +.bi-filter-square::before { content: "\f3c9"; } +.bi-filter::before { content: "\f3ca"; } +.bi-flag-fill::before { content: "\f3cb"; } +.bi-flag::before { content: "\f3cc"; } +.bi-flower1::before { content: "\f3cd"; } +.bi-flower2::before { content: "\f3ce"; } +.bi-flower3::before { content: "\f3cf"; } +.bi-folder-check::before { content: "\f3d0"; } +.bi-folder-fill::before { content: "\f3d1"; } +.bi-folder-minus::before { content: "\f3d2"; } +.bi-folder-plus::before { content: "\f3d3"; } +.bi-folder-symlink-fill::before { content: "\f3d4"; } +.bi-folder-symlink::before { content: "\f3d5"; } +.bi-folder-x::before { content: "\f3d6"; } +.bi-folder::before { content: "\f3d7"; } +.bi-folder2-open::before { content: "\f3d8"; } +.bi-folder2::before { content: "\f3d9"; } +.bi-fonts::before { content: "\f3da"; } +.bi-forward-fill::before { content: "\f3db"; } +.bi-forward::before { content: "\f3dc"; } +.bi-front::before { content: "\f3dd"; } +.bi-fullscreen-exit::before { content: "\f3de"; } +.bi-fullscreen::before { content: "\f3df"; } +.bi-funnel-fill::before { content: "\f3e0"; } +.bi-funnel::before { content: "\f3e1"; } +.bi-gear-fill::before { content: "\f3e2"; } +.bi-gear-wide-connected::before { content: "\f3e3"; } +.bi-gear-wide::before { content: "\f3e4"; } +.bi-gear::before { content: "\f3e5"; } +.bi-gem::before { content: "\f3e6"; } +.bi-geo-alt-fill::before { content: "\f3e7"; } +.bi-geo-alt::before { content: "\f3e8"; } +.bi-geo-fill::before { content: "\f3e9"; } +.bi-geo::before { content: "\f3ea"; } +.bi-gift-fill::before { content: "\f3eb"; } +.bi-gift::before { content: "\f3ec"; } +.bi-github::before { content: "\f3ed"; } +.bi-globe::before { content: "\f3ee"; } +.bi-globe2::before { content: "\f3ef"; } +.bi-google::before { content: "\f3f0"; } +.bi-graph-down::before { content: "\f3f1"; } +.bi-graph-up::before { content: "\f3f2"; } +.bi-grid-1x2-fill::before { content: "\f3f3"; } +.bi-grid-1x2::before { content: "\f3f4"; } +.bi-grid-3x2-gap-fill::before { content: "\f3f5"; } +.bi-grid-3x2-gap::before { content: "\f3f6"; } +.bi-grid-3x2::before { content: "\f3f7"; } +.bi-grid-3x3-gap-fill::before { content: "\f3f8"; } +.bi-grid-3x3-gap::before { content: "\f3f9"; } +.bi-grid-3x3::before { content: "\f3fa"; } +.bi-grid-fill::before { content: "\f3fb"; } +.bi-grid::before { content: "\f3fc"; } +.bi-grip-horizontal::before { content: "\f3fd"; } +.bi-grip-vertical::before { content: "\f3fe"; } +.bi-hammer::before { content: "\f3ff"; } +.bi-hand-index-fill::before { content: "\f400"; } +.bi-hand-index-thumb-fill::before { content: "\f401"; } +.bi-hand-index-thumb::before { content: "\f402"; } +.bi-hand-index::before { content: "\f403"; } +.bi-hand-thumbs-down-fill::before { content: "\f404"; } +.bi-hand-thumbs-down::before { content: "\f405"; } +.bi-hand-thumbs-up-fill::before { content: "\f406"; } +.bi-hand-thumbs-up::before { content: "\f407"; } +.bi-handbag-fill::before { content: "\f408"; } +.bi-handbag::before { content: "\f409"; } +.bi-hash::before { content: "\f40a"; } +.bi-hdd-fill::before { content: "\f40b"; } +.bi-hdd-network-fill::before { content: "\f40c"; } +.bi-hdd-network::before { content: "\f40d"; } +.bi-hdd-rack-fill::before { content: "\f40e"; } +.bi-hdd-rack::before { content: "\f40f"; } +.bi-hdd-stack-fill::before { content: "\f410"; } +.bi-hdd-stack::before { content: "\f411"; } +.bi-hdd::before { content: "\f412"; } +.bi-headphones::before { content: "\f413"; } +.bi-headset::before { content: "\f414"; } +.bi-heart-fill::before { content: "\f415"; } +.bi-heart-half::before { content: "\f416"; } +.bi-heart::before { content: "\f417"; } +.bi-heptagon-fill::before { content: "\f418"; } +.bi-heptagon-half::before { content: "\f419"; } +.bi-heptagon::before { content: "\f41a"; } +.bi-hexagon-fill::before { content: "\f41b"; } +.bi-hexagon-half::before { content: "\f41c"; } +.bi-hexagon::before { content: "\f41d"; } +.bi-hourglass-bottom::before { content: "\f41e"; } +.bi-hourglass-split::before { content: "\f41f"; } +.bi-hourglass-top::before { content: "\f420"; } +.bi-hourglass::before { content: "\f421"; } +.bi-house-door-fill::before { content: "\f422"; } +.bi-house-door::before { content: "\f423"; } +.bi-house-fill::before { content: "\f424"; } +.bi-house::before { content: "\f425"; } +.bi-hr::before { content: "\f426"; } +.bi-hurricane::before { content: "\f427"; } +.bi-image-alt::before { content: "\f428"; } +.bi-image-fill::before { content: "\f429"; } +.bi-image::before { content: "\f42a"; } +.bi-images::before { content: "\f42b"; } +.bi-inbox-fill::before { content: "\f42c"; } +.bi-inbox::before { content: "\f42d"; } +.bi-inboxes-fill::before { content: "\f42e"; } +.bi-inboxes::before { content: "\f42f"; } +.bi-info-circle-fill::before { content: "\f430"; } +.bi-info-circle::before { content: "\f431"; } +.bi-info-square-fill::before { content: "\f432"; } +.bi-info-square::before { content: "\f433"; } +.bi-info::before { content: "\f434"; } +.bi-input-cursor-text::before { content: "\f435"; } +.bi-input-cursor::before { content: "\f436"; } +.bi-instagram::before { content: "\f437"; } +.bi-intersect::before { content: "\f438"; } +.bi-journal-album::before { content: "\f439"; } +.bi-journal-arrow-down::before { content: "\f43a"; } +.bi-journal-arrow-up::before { content: "\f43b"; } +.bi-journal-bookmark-fill::before { content: "\f43c"; } +.bi-journal-bookmark::before { content: "\f43d"; } +.bi-journal-check::before { content: "\f43e"; } +.bi-journal-code::before { content: "\f43f"; } +.bi-journal-medical::before { content: "\f440"; } +.bi-journal-minus::before { content: "\f441"; } +.bi-journal-plus::before { content: "\f442"; } +.bi-journal-richtext::before { content: "\f443"; } +.bi-journal-text::before { content: "\f444"; } +.bi-journal-x::before { content: "\f445"; } +.bi-journal::before { content: "\f446"; } +.bi-journals::before { content: "\f447"; } +.bi-joystick::before { content: "\f448"; } +.bi-justify-left::before { content: "\f449"; } +.bi-justify-right::before { content: "\f44a"; } +.bi-justify::before { content: "\f44b"; } +.bi-kanban-fill::before { content: "\f44c"; } +.bi-kanban::before { content: "\f44d"; } +.bi-key-fill::before { content: "\f44e"; } +.bi-key::before { content: "\f44f"; } +.bi-keyboard-fill::before { content: "\f450"; } +.bi-keyboard::before { content: "\f451"; } +.bi-ladder::before { content: "\f452"; } +.bi-lamp-fill::before { content: "\f453"; } +.bi-lamp::before { content: "\f454"; } +.bi-laptop-fill::before { content: "\f455"; } +.bi-laptop::before { content: "\f456"; } +.bi-layer-backward::before { content: "\f457"; } +.bi-layer-forward::before { content: "\f458"; } +.bi-layers-fill::before { content: "\f459"; } +.bi-layers-half::before { content: "\f45a"; } +.bi-layers::before { content: "\f45b"; } +.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; } +.bi-layout-sidebar-inset::before { content: "\f45d"; } +.bi-layout-sidebar-reverse::before { content: "\f45e"; } +.bi-layout-sidebar::before { content: "\f45f"; } +.bi-layout-split::before { content: "\f460"; } +.bi-layout-text-sidebar-reverse::before { content: "\f461"; } +.bi-layout-text-sidebar::before { content: "\f462"; } +.bi-layout-text-window-reverse::before { content: "\f463"; } +.bi-layout-text-window::before { content: "\f464"; } +.bi-layout-three-columns::before { content: "\f465"; } +.bi-layout-wtf::before { content: "\f466"; } +.bi-life-preserver::before { content: "\f467"; } +.bi-lightbulb-fill::before { content: "\f468"; } +.bi-lightbulb-off-fill::before { content: "\f469"; } +.bi-lightbulb-off::before { content: "\f46a"; } +.bi-lightbulb::before { content: "\f46b"; } +.bi-lightning-charge-fill::before { content: "\f46c"; } +.bi-lightning-charge::before { content: "\f46d"; } +.bi-lightning-fill::before { content: "\f46e"; } +.bi-lightning::before { content: "\f46f"; } +.bi-link-45deg::before { content: "\f470"; } +.bi-link::before { content: "\f471"; } +.bi-linkedin::before { content: "\f472"; } +.bi-list-check::before { content: "\f473"; } +.bi-list-nested::before { content: "\f474"; } +.bi-list-ol::before { content: "\f475"; } +.bi-list-stars::before { content: "\f476"; } +.bi-list-task::before { content: "\f477"; } +.bi-list-ul::before { content: "\f478"; } +.bi-list::before { content: "\f479"; } +.bi-lock-fill::before { content: "\f47a"; } +.bi-lock::before { content: "\f47b"; } +.bi-mailbox::before { content: "\f47c"; } +.bi-mailbox2::before { content: "\f47d"; } +.bi-map-fill::before { content: "\f47e"; } +.bi-map::before { content: "\f47f"; } +.bi-markdown-fill::before { content: "\f480"; } +.bi-markdown::before { content: "\f481"; } +.bi-mask::before { content: "\f482"; } +.bi-megaphone-fill::before { content: "\f483"; } +.bi-megaphone::before { content: "\f484"; } +.bi-menu-app-fill::before { content: "\f485"; } +.bi-menu-app::before { content: "\f486"; } +.bi-menu-button-fill::before { content: "\f487"; } +.bi-menu-button-wide-fill::before { content: "\f488"; } +.bi-menu-button-wide::before { content: "\f489"; } +.bi-menu-button::before { content: "\f48a"; } +.bi-menu-down::before { content: "\f48b"; } +.bi-menu-up::before { content: "\f48c"; } +.bi-mic-fill::before { content: "\f48d"; } +.bi-mic-mute-fill::before { content: "\f48e"; } +.bi-mic-mute::before { content: "\f48f"; } +.bi-mic::before { content: "\f490"; } +.bi-minecart-loaded::before { content: "\f491"; } +.bi-minecart::before { content: "\f492"; } +.bi-moisture::before { content: "\f493"; } +.bi-moon-fill::before { content: "\f494"; } +.bi-moon-stars-fill::before { content: "\f495"; } +.bi-moon-stars::before { content: "\f496"; } +.bi-moon::before { content: "\f497"; } +.bi-mouse-fill::before { content: "\f498"; } +.bi-mouse::before { content: "\f499"; } +.bi-mouse2-fill::before { content: "\f49a"; } +.bi-mouse2::before { content: "\f49b"; } +.bi-mouse3-fill::before { content: "\f49c"; } +.bi-mouse3::before { content: "\f49d"; } +.bi-music-note-beamed::before { content: "\f49e"; } +.bi-music-note-list::before { content: "\f49f"; } +.bi-music-note::before { content: "\f4a0"; } +.bi-music-player-fill::before { content: "\f4a1"; } +.bi-music-player::before { content: "\f4a2"; } +.bi-newspaper::before { content: "\f4a3"; } +.bi-node-minus-fill::before { content: "\f4a4"; } +.bi-node-minus::before { content: "\f4a5"; } +.bi-node-plus-fill::before { content: "\f4a6"; } +.bi-node-plus::before { content: "\f4a7"; } +.bi-nut-fill::before { content: "\f4a8"; } +.bi-nut::before { content: "\f4a9"; } +.bi-octagon-fill::before { content: "\f4aa"; } +.bi-octagon-half::before { content: "\f4ab"; } +.bi-octagon::before { content: "\f4ac"; } +.bi-option::before { content: "\f4ad"; } +.bi-outlet::before { content: "\f4ae"; } +.bi-paint-bucket::before { content: "\f4af"; } +.bi-palette-fill::before { content: "\f4b0"; } +.bi-palette::before { content: "\f4b1"; } +.bi-palette2::before { content: "\f4b2"; } +.bi-paperclip::before { content: "\f4b3"; } +.bi-paragraph::before { content: "\f4b4"; } +.bi-patch-check-fill::before { content: "\f4b5"; } +.bi-patch-check::before { content: "\f4b6"; } +.bi-patch-exclamation-fill::before { content: "\f4b7"; } +.bi-patch-exclamation::before { content: "\f4b8"; } +.bi-patch-minus-fill::before { content: "\f4b9"; } +.bi-patch-minus::before { content: "\f4ba"; } +.bi-patch-plus-fill::before { content: "\f4bb"; } +.bi-patch-plus::before { content: "\f4bc"; } +.bi-patch-question-fill::before { content: "\f4bd"; } +.bi-patch-question::before { content: "\f4be"; } +.bi-pause-btn-fill::before { content: "\f4bf"; } +.bi-pause-btn::before { content: "\f4c0"; } +.bi-pause-circle-fill::before { content: "\f4c1"; } +.bi-pause-circle::before { content: "\f4c2"; } +.bi-pause-fill::before { content: "\f4c3"; } +.bi-pause::before { content: "\f4c4"; } +.bi-peace-fill::before { content: "\f4c5"; } +.bi-peace::before { content: "\f4c6"; } +.bi-pen-fill::before { content: "\f4c7"; } +.bi-pen::before { content: "\f4c8"; } +.bi-pencil-fill::before { content: "\f4c9"; } +.bi-pencil-square::before { content: "\f4ca"; } +.bi-pencil::before { content: "\f4cb"; } +.bi-pentagon-fill::before { content: "\f4cc"; } +.bi-pentagon-half::before { content: "\f4cd"; } +.bi-pentagon::before { content: "\f4ce"; } +.bi-people-fill::before { content: "\f4cf"; } +.bi-people::before { content: "\f4d0"; } +.bi-percent::before { content: "\f4d1"; } +.bi-person-badge-fill::before { content: "\f4d2"; } +.bi-person-badge::before { content: "\f4d3"; } +.bi-person-bounding-box::before { content: "\f4d4"; } +.bi-person-check-fill::before { content: "\f4d5"; } +.bi-person-check::before { content: "\f4d6"; } +.bi-person-circle::before { content: "\f4d7"; } +.bi-person-dash-fill::before { content: "\f4d8"; } +.bi-person-dash::before { content: "\f4d9"; } +.bi-person-fill::before { content: "\f4da"; } +.bi-person-lines-fill::before { content: "\f4db"; } +.bi-person-plus-fill::before { content: "\f4dc"; } +.bi-person-plus::before { content: "\f4dd"; } +.bi-person-square::before { content: "\f4de"; } +.bi-person-x-fill::before { content: "\f4df"; } +.bi-person-x::before { content: "\f4e0"; } +.bi-person::before { content: "\f4e1"; } +.bi-phone-fill::before { content: "\f4e2"; } +.bi-phone-landscape-fill::before { content: "\f4e3"; } +.bi-phone-landscape::before { content: "\f4e4"; } +.bi-phone-vibrate-fill::before { content: "\f4e5"; } +.bi-phone-vibrate::before { content: "\f4e6"; } +.bi-phone::before { content: "\f4e7"; } +.bi-pie-chart-fill::before { content: "\f4e8"; } +.bi-pie-chart::before { content: "\f4e9"; } +.bi-pin-angle-fill::before { content: "\f4ea"; } +.bi-pin-angle::before { content: "\f4eb"; } +.bi-pin-fill::before { content: "\f4ec"; } +.bi-pin::before { content: "\f4ed"; } +.bi-pip-fill::before { content: "\f4ee"; } +.bi-pip::before { content: "\f4ef"; } +.bi-play-btn-fill::before { content: "\f4f0"; } +.bi-play-btn::before { content: "\f4f1"; } +.bi-play-circle-fill::before { content: "\f4f2"; } +.bi-play-circle::before { content: "\f4f3"; } +.bi-play-fill::before { content: "\f4f4"; } +.bi-play::before { content: "\f4f5"; } +.bi-plug-fill::before { content: "\f4f6"; } +.bi-plug::before { content: "\f4f7"; } +.bi-plus-circle-dotted::before { content: "\f4f8"; } +.bi-plus-circle-fill::before { content: "\f4f9"; } +.bi-plus-circle::before { content: "\f4fa"; } +.bi-plus-square-dotted::before { content: "\f4fb"; } +.bi-plus-square-fill::before { content: "\f4fc"; } +.bi-plus-square::before { content: "\f4fd"; } +.bi-plus::before { content: "\f4fe"; } +.bi-power::before { content: "\f4ff"; } +.bi-printer-fill::before { content: "\f500"; } +.bi-printer::before { content: "\f501"; } +.bi-puzzle-fill::before { content: "\f502"; } +.bi-puzzle::before { content: "\f503"; } +.bi-question-circle-fill::before { content: "\f504"; } +.bi-question-circle::before { content: "\f505"; } +.bi-question-diamond-fill::before { content: "\f506"; } +.bi-question-diamond::before { content: "\f507"; } +.bi-question-octagon-fill::before { content: "\f508"; } +.bi-question-octagon::before { content: "\f509"; } +.bi-question-square-fill::before { content: "\f50a"; } +.bi-question-square::before { content: "\f50b"; } +.bi-question::before { content: "\f50c"; } +.bi-rainbow::before { content: "\f50d"; } +.bi-receipt-cutoff::before { content: "\f50e"; } +.bi-receipt::before { content: "\f50f"; } +.bi-reception-0::before { content: "\f510"; } +.bi-reception-1::before { content: "\f511"; } +.bi-reception-2::before { content: "\f512"; } +.bi-reception-3::before { content: "\f513"; } +.bi-reception-4::before { content: "\f514"; } +.bi-record-btn-fill::before { content: "\f515"; } +.bi-record-btn::before { content: "\f516"; } +.bi-record-circle-fill::before { content: "\f517"; } +.bi-record-circle::before { content: "\f518"; } +.bi-record-fill::before { content: "\f519"; } +.bi-record::before { content: "\f51a"; } +.bi-record2-fill::before { content: "\f51b"; } +.bi-record2::before { content: "\f51c"; } +.bi-reply-all-fill::before { content: "\f51d"; } +.bi-reply-all::before { content: "\f51e"; } +.bi-reply-fill::before { content: "\f51f"; } +.bi-reply::before { content: "\f520"; } +.bi-rss-fill::before { content: "\f521"; } +.bi-rss::before { content: "\f522"; } +.bi-rulers::before { content: "\f523"; } +.bi-save-fill::before { content: "\f524"; } +.bi-save::before { content: "\f525"; } +.bi-save2-fill::before { content: "\f526"; } +.bi-save2::before { content: "\f527"; } +.bi-scissors::before { content: "\f528"; } +.bi-screwdriver::before { content: "\f529"; } +.bi-search::before { content: "\f52a"; } +.bi-segmented-nav::before { content: "\f52b"; } +.bi-server::before { content: "\f52c"; } +.bi-share-fill::before { content: "\f52d"; } +.bi-share::before { content: "\f52e"; } +.bi-shield-check::before { content: "\f52f"; } +.bi-shield-exclamation::before { content: "\f530"; } +.bi-shield-fill-check::before { content: "\f531"; } +.bi-shield-fill-exclamation::before { content: "\f532"; } +.bi-shield-fill-minus::before { content: "\f533"; } +.bi-shield-fill-plus::before { content: "\f534"; } +.bi-shield-fill-x::before { content: "\f535"; } +.bi-shield-fill::before { content: "\f536"; } +.bi-shield-lock-fill::before { content: "\f537"; } +.bi-shield-lock::before { content: "\f538"; } +.bi-shield-minus::before { content: "\f539"; } +.bi-shield-plus::before { content: "\f53a"; } +.bi-shield-shaded::before { content: "\f53b"; } +.bi-shield-slash-fill::before { content: "\f53c"; } +.bi-shield-slash::before { content: "\f53d"; } +.bi-shield-x::before { content: "\f53e"; } +.bi-shield::before { content: "\f53f"; } +.bi-shift-fill::before { content: "\f540"; } +.bi-shift::before { content: "\f541"; } +.bi-shop-window::before { content: "\f542"; } +.bi-shop::before { content: "\f543"; } +.bi-shuffle::before { content: "\f544"; } +.bi-signpost-2-fill::before { content: "\f545"; } +.bi-signpost-2::before { content: "\f546"; } +.bi-signpost-fill::before { content: "\f547"; } +.bi-signpost-split-fill::before { content: "\f548"; } +.bi-signpost-split::before { content: "\f549"; } +.bi-signpost::before { content: "\f54a"; } +.bi-sim-fill::before { content: "\f54b"; } +.bi-sim::before { content: "\f54c"; } +.bi-skip-backward-btn-fill::before { content: "\f54d"; } +.bi-skip-backward-btn::before { content: "\f54e"; } +.bi-skip-backward-circle-fill::before { content: "\f54f"; } +.bi-skip-backward-circle::before { content: "\f550"; } +.bi-skip-backward-fill::before { content: "\f551"; } +.bi-skip-backward::before { content: "\f552"; } +.bi-skip-end-btn-fill::before { content: "\f553"; } +.bi-skip-end-btn::before { content: "\f554"; } +.bi-skip-end-circle-fill::before { content: "\f555"; } +.bi-skip-end-circle::before { content: "\f556"; } +.bi-skip-end-fill::before { content: "\f557"; } +.bi-skip-end::before { content: "\f558"; } +.bi-skip-forward-btn-fill::before { content: "\f559"; } +.bi-skip-forward-btn::before { content: "\f55a"; } +.bi-skip-forward-circle-fill::before { content: "\f55b"; } +.bi-skip-forward-circle::before { content: "\f55c"; } +.bi-skip-forward-fill::before { content: "\f55d"; } +.bi-skip-forward::before { content: "\f55e"; } +.bi-skip-start-btn-fill::before { content: "\f55f"; } +.bi-skip-start-btn::before { content: "\f560"; } +.bi-skip-start-circle-fill::before { content: "\f561"; } +.bi-skip-start-circle::before { content: "\f562"; } +.bi-skip-start-fill::before { content: "\f563"; } +.bi-skip-start::before { content: "\f564"; } +.bi-slack::before { content: "\f565"; } +.bi-slash-circle-fill::before { content: "\f566"; } +.bi-slash-circle::before { content: "\f567"; } +.bi-slash-square-fill::before { content: "\f568"; } +.bi-slash-square::before { content: "\f569"; } +.bi-slash::before { content: "\f56a"; } +.bi-sliders::before { content: "\f56b"; } +.bi-smartwatch::before { content: "\f56c"; } +.bi-snow::before { content: "\f56d"; } +.bi-snow2::before { content: "\f56e"; } +.bi-snow3::before { content: "\f56f"; } +.bi-sort-alpha-down-alt::before { content: "\f570"; } +.bi-sort-alpha-down::before { content: "\f571"; } +.bi-sort-alpha-up-alt::before { content: "\f572"; } +.bi-sort-alpha-up::before { content: "\f573"; } +.bi-sort-down-alt::before { content: "\f574"; } +.bi-sort-down::before { content: "\f575"; } +.bi-sort-numeric-down-alt::before { content: "\f576"; } +.bi-sort-numeric-down::before { content: "\f577"; } +.bi-sort-numeric-up-alt::before { content: "\f578"; } +.bi-sort-numeric-up::before { content: "\f579"; } +.bi-sort-up-alt::before { content: "\f57a"; } +.bi-sort-up::before { content: "\f57b"; } +.bi-soundwave::before { content: "\f57c"; } +.bi-speaker-fill::before { content: "\f57d"; } +.bi-speaker::before { content: "\f57e"; } +.bi-speedometer::before { content: "\f57f"; } +.bi-speedometer2::before { content: "\f580"; } +.bi-spellcheck::before { content: "\f581"; } +.bi-square-fill::before { content: "\f582"; } +.bi-square-half::before { content: "\f583"; } +.bi-square::before { content: "\f584"; } +.bi-stack::before { content: "\f585"; } +.bi-star-fill::before { content: "\f586"; } +.bi-star-half::before { content: "\f587"; } +.bi-star::before { content: "\f588"; } +.bi-stars::before { content: "\f589"; } +.bi-stickies-fill::before { content: "\f58a"; } +.bi-stickies::before { content: "\f58b"; } +.bi-sticky-fill::before { content: "\f58c"; } +.bi-sticky::before { content: "\f58d"; } +.bi-stop-btn-fill::before { content: "\f58e"; } +.bi-stop-btn::before { content: "\f58f"; } +.bi-stop-circle-fill::before { content: "\f590"; } +.bi-stop-circle::before { content: "\f591"; } +.bi-stop-fill::before { content: "\f592"; } +.bi-stop::before { content: "\f593"; } +.bi-stoplights-fill::before { content: "\f594"; } +.bi-stoplights::before { content: "\f595"; } +.bi-stopwatch-fill::before { content: "\f596"; } +.bi-stopwatch::before { content: "\f597"; } +.bi-subtract::before { content: "\f598"; } +.bi-suit-club-fill::before { content: "\f599"; } +.bi-suit-club::before { content: "\f59a"; } +.bi-suit-diamond-fill::before { content: "\f59b"; } +.bi-suit-diamond::before { content: "\f59c"; } +.bi-suit-heart-fill::before { content: "\f59d"; } +.bi-suit-heart::before { content: "\f59e"; } +.bi-suit-spade-fill::before { content: "\f59f"; } +.bi-suit-spade::before { content: "\f5a0"; } +.bi-sun-fill::before { content: "\f5a1"; } +.bi-sun::before { content: "\f5a2"; } +.bi-sunglasses::before { content: "\f5a3"; } +.bi-sunrise-fill::before { content: "\f5a4"; } +.bi-sunrise::before { content: "\f5a5"; } +.bi-sunset-fill::before { content: "\f5a6"; } +.bi-sunset::before { content: "\f5a7"; } +.bi-symmetry-horizontal::before { content: "\f5a8"; } +.bi-symmetry-vertical::before { content: "\f5a9"; } +.bi-table::before { content: "\f5aa"; } +.bi-tablet-fill::before { content: "\f5ab"; } +.bi-tablet-landscape-fill::before { content: "\f5ac"; } +.bi-tablet-landscape::before { content: "\f5ad"; } +.bi-tablet::before { content: "\f5ae"; } +.bi-tag-fill::before { content: "\f5af"; } +.bi-tag::before { content: "\f5b0"; } +.bi-tags-fill::before { content: "\f5b1"; } +.bi-tags::before { content: "\f5b2"; } +.bi-telegram::before { content: "\f5b3"; } +.bi-telephone-fill::before { content: "\f5b4"; } +.bi-telephone-forward-fill::before { content: "\f5b5"; } +.bi-telephone-forward::before { content: "\f5b6"; } +.bi-telephone-inbound-fill::before { content: "\f5b7"; } +.bi-telephone-inbound::before { content: "\f5b8"; } +.bi-telephone-minus-fill::before { content: "\f5b9"; } +.bi-telephone-minus::before { content: "\f5ba"; } +.bi-telephone-outbound-fill::before { content: "\f5bb"; } +.bi-telephone-outbound::before { content: "\f5bc"; } +.bi-telephone-plus-fill::before { content: "\f5bd"; } +.bi-telephone-plus::before { content: "\f5be"; } +.bi-telephone-x-fill::before { content: "\f5bf"; } +.bi-telephone-x::before { content: "\f5c0"; } +.bi-telephone::before { content: "\f5c1"; } +.bi-terminal-fill::before { content: "\f5c2"; } +.bi-terminal::before { content: "\f5c3"; } +.bi-text-center::before { content: "\f5c4"; } +.bi-text-indent-left::before { content: "\f5c5"; } +.bi-text-indent-right::before { content: "\f5c6"; } +.bi-text-left::before { content: "\f5c7"; } +.bi-text-paragraph::before { content: "\f5c8"; } +.bi-text-right::before { content: "\f5c9"; } +.bi-textarea-resize::before { content: "\f5ca"; } +.bi-textarea-t::before { content: "\f5cb"; } +.bi-textarea::before { content: "\f5cc"; } +.bi-thermometer-half::before { content: "\f5cd"; } +.bi-thermometer-high::before { content: "\f5ce"; } +.bi-thermometer-low::before { content: "\f5cf"; } +.bi-thermometer-snow::before { content: "\f5d0"; } +.bi-thermometer-sun::before { content: "\f5d1"; } +.bi-thermometer::before { content: "\f5d2"; } +.bi-three-dots-vertical::before { content: "\f5d3"; } +.bi-three-dots::before { content: "\f5d4"; } +.bi-toggle-off::before { content: "\f5d5"; } +.bi-toggle-on::before { content: "\f5d6"; } +.bi-toggle2-off::before { content: "\f5d7"; } +.bi-toggle2-on::before { content: "\f5d8"; } +.bi-toggles::before { content: "\f5d9"; } +.bi-toggles2::before { content: "\f5da"; } +.bi-tools::before { content: "\f5db"; } +.bi-tornado::before { content: "\f5dc"; } +.bi-trash-fill::before { content: "\f5dd"; } +.bi-trash::before { content: "\f5de"; } +.bi-trash2-fill::before { content: "\f5df"; } +.bi-trash2::before { content: "\f5e0"; } +.bi-tree-fill::before { content: "\f5e1"; } +.bi-tree::before { content: "\f5e2"; } +.bi-triangle-fill::before { content: "\f5e3"; } +.bi-triangle-half::before { content: "\f5e4"; } +.bi-triangle::before { content: "\f5e5"; } +.bi-trophy-fill::before { content: "\f5e6"; } +.bi-trophy::before { content: "\f5e7"; } +.bi-tropical-storm::before { content: "\f5e8"; } +.bi-truck-flatbed::before { content: "\f5e9"; } +.bi-truck::before { content: "\f5ea"; } +.bi-tsunami::before { content: "\f5eb"; } +.bi-tv-fill::before { content: "\f5ec"; } +.bi-tv::before { content: "\f5ed"; } +.bi-twitch::before { content: "\f5ee"; } +.bi-twitter::before { content: "\f5ef"; } +.bi-type-bold::before { content: "\f5f0"; } +.bi-type-h1::before { content: "\f5f1"; } +.bi-type-h2::before { content: "\f5f2"; } +.bi-type-h3::before { content: "\f5f3"; } +.bi-type-italic::before { content: "\f5f4"; } +.bi-type-strikethrough::before { content: "\f5f5"; } +.bi-type-underline::before { content: "\f5f6"; } +.bi-type::before { content: "\f5f7"; } +.bi-ui-checks-grid::before { content: "\f5f8"; } +.bi-ui-checks::before { content: "\f5f9"; } +.bi-ui-radios-grid::before { content: "\f5fa"; } +.bi-ui-radios::before { content: "\f5fb"; } +.bi-umbrella-fill::before { content: "\f5fc"; } +.bi-umbrella::before { content: "\f5fd"; } +.bi-union::before { content: "\f5fe"; } +.bi-unlock-fill::before { content: "\f5ff"; } +.bi-unlock::before { content: "\f600"; } +.bi-upc-scan::before { content: "\f601"; } +.bi-upc::before { content: "\f602"; } +.bi-upload::before { content: "\f603"; } +.bi-vector-pen::before { content: "\f604"; } +.bi-view-list::before { content: "\f605"; } +.bi-view-stacked::before { content: "\f606"; } +.bi-vinyl-fill::before { content: "\f607"; } +.bi-vinyl::before { content: "\f608"; } +.bi-voicemail::before { content: "\f609"; } +.bi-volume-down-fill::before { content: "\f60a"; } +.bi-volume-down::before { content: "\f60b"; } +.bi-volume-mute-fill::before { content: "\f60c"; } +.bi-volume-mute::before { content: "\f60d"; } +.bi-volume-off-fill::before { content: "\f60e"; } +.bi-volume-off::before { content: "\f60f"; } +.bi-volume-up-fill::before { content: "\f610"; } +.bi-volume-up::before { content: "\f611"; } +.bi-vr::before { content: "\f612"; } +.bi-wallet-fill::before { content: "\f613"; } +.bi-wallet::before { content: "\f614"; } +.bi-wallet2::before { content: "\f615"; } +.bi-watch::before { content: "\f616"; } +.bi-water::before { content: "\f617"; } +.bi-whatsapp::before { content: "\f618"; } +.bi-wifi-1::before { content: "\f619"; } +.bi-wifi-2::before { content: "\f61a"; } +.bi-wifi-off::before { content: "\f61b"; } +.bi-wifi::before { content: "\f61c"; } +.bi-wind::before { content: "\f61d"; } +.bi-window-dock::before { content: "\f61e"; } +.bi-window-sidebar::before { content: "\f61f"; } +.bi-window::before { content: "\f620"; } +.bi-wrench::before { content: "\f621"; } +.bi-x-circle-fill::before { content: "\f622"; } +.bi-x-circle::before { content: "\f623"; } +.bi-x-diamond-fill::before { content: "\f624"; } +.bi-x-diamond::before { content: "\f625"; } +.bi-x-octagon-fill::before { content: "\f626"; } +.bi-x-octagon::before { content: "\f627"; } +.bi-x-square-fill::before { content: "\f628"; } +.bi-x-square::before { content: "\f629"; } +.bi-x::before { content: "\f62a"; } +.bi-youtube::before { content: "\f62b"; } +.bi-zoom-in::before { content: "\f62c"; } +.bi-zoom-out::before { content: "\f62d"; } +.bi-bank::before { content: "\f62e"; } +.bi-bank2::before { content: "\f62f"; } +.bi-bell-slash-fill::before { content: "\f630"; } +.bi-bell-slash::before { content: "\f631"; } +.bi-cash-coin::before { content: "\f632"; } +.bi-check-lg::before { content: "\f633"; } +.bi-coin::before { content: "\f634"; } +.bi-currency-bitcoin::before { content: "\f635"; } +.bi-currency-dollar::before { content: "\f636"; } +.bi-currency-euro::before { content: "\f637"; } +.bi-currency-exchange::before { content: "\f638"; } +.bi-currency-pound::before { content: "\f639"; } +.bi-currency-yen::before { content: "\f63a"; } +.bi-dash-lg::before { content: "\f63b"; } +.bi-exclamation-lg::before { content: "\f63c"; } +.bi-file-earmark-pdf-fill::before { content: "\f63d"; } +.bi-file-earmark-pdf::before { content: "\f63e"; } +.bi-file-pdf-fill::before { content: "\f63f"; } +.bi-file-pdf::before { content: "\f640"; } +.bi-gender-ambiguous::before { content: "\f641"; } +.bi-gender-female::before { content: "\f642"; } +.bi-gender-male::before { content: "\f643"; } +.bi-gender-trans::before { content: "\f644"; } +.bi-headset-vr::before { content: "\f645"; } +.bi-info-lg::before { content: "\f646"; } +.bi-mastodon::before { content: "\f647"; } +.bi-messenger::before { content: "\f648"; } +.bi-piggy-bank-fill::before { content: "\f649"; } +.bi-piggy-bank::before { content: "\f64a"; } +.bi-pin-map-fill::before { content: "\f64b"; } +.bi-pin-map::before { content: "\f64c"; } +.bi-plus-lg::before { content: "\f64d"; } +.bi-question-lg::before { content: "\f64e"; } +.bi-recycle::before { content: "\f64f"; } +.bi-reddit::before { content: "\f650"; } +.bi-safe-fill::before { content: "\f651"; } +.bi-safe2-fill::before { content: "\f652"; } +.bi-safe2::before { content: "\f653"; } +.bi-sd-card-fill::before { content: "\f654"; } +.bi-sd-card::before { content: "\f655"; } +.bi-skype::before { content: "\f656"; } +.bi-slash-lg::before { content: "\f657"; } +.bi-translate::before { content: "\f658"; } +.bi-x-lg::before { content: "\f659"; } +.bi-safe::before { content: "\f65a"; } +.bi-apple::before { content: "\f65b"; } +.bi-microsoft::before { content: "\f65d"; } +.bi-windows::before { content: "\f65e"; } +.bi-behance::before { content: "\f65c"; } +.bi-dribbble::before { content: "\f65f"; } +.bi-line::before { content: "\f660"; } +.bi-medium::before { content: "\f661"; } +.bi-paypal::before { content: "\f662"; } +.bi-pinterest::before { content: "\f663"; } +.bi-signal::before { content: "\f664"; } +.bi-snapchat::before { content: "\f665"; } +.bi-spotify::before { content: "\f666"; } +.bi-stack-overflow::before { content: "\f667"; } +.bi-strava::before { content: "\f668"; } +.bi-wordpress::before { content: "\f669"; } +.bi-vimeo::before { content: "\f66a"; } +.bi-activity::before { content: "\f66b"; } +.bi-easel2-fill::before { content: "\f66c"; } +.bi-easel2::before { content: "\f66d"; } +.bi-easel3-fill::before { content: "\f66e"; } +.bi-easel3::before { content: "\f66f"; } +.bi-fan::before { content: "\f670"; } +.bi-fingerprint::before { content: "\f671"; } +.bi-graph-down-arrow::before { content: "\f672"; } +.bi-graph-up-arrow::before { content: "\f673"; } +.bi-hypnotize::before { content: "\f674"; } +.bi-magic::before { content: "\f675"; } +.bi-person-rolodex::before { content: "\f676"; } +.bi-person-video::before { content: "\f677"; } +.bi-person-video2::before { content: "\f678"; } +.bi-person-video3::before { content: "\f679"; } +.bi-person-workspace::before { content: "\f67a"; } +.bi-radioactive::before { content: "\f67b"; } +.bi-webcam-fill::before { content: "\f67c"; } +.bi-webcam::before { content: "\f67d"; } +.bi-yin-yang::before { content: "\f67e"; } +.bi-bandaid-fill::before { content: "\f680"; } +.bi-bandaid::before { content: "\f681"; } +.bi-bluetooth::before { content: "\f682"; } +.bi-body-text::before { content: "\f683"; } +.bi-boombox::before { content: "\f684"; } +.bi-boxes::before { content: "\f685"; } +.bi-dpad-fill::before { content: "\f686"; } +.bi-dpad::before { content: "\f687"; } +.bi-ear-fill::before { content: "\f688"; } +.bi-ear::before { content: "\f689"; } +.bi-envelope-check-1::before { content: "\f68a"; } +.bi-envelope-check-fill::before { content: "\f68b"; } +.bi-envelope-check::before { content: "\f68c"; } +.bi-envelope-dash-1::before { content: "\f68d"; } +.bi-envelope-dash-fill::before { content: "\f68e"; } +.bi-envelope-dash::before { content: "\f68f"; } +.bi-envelope-exclamation-1::before { content: "\f690"; } +.bi-envelope-exclamation-fill::before { content: "\f691"; } +.bi-envelope-exclamation::before { content: "\f692"; } +.bi-envelope-plus-fill::before { content: "\f693"; } +.bi-envelope-plus::before { content: "\f694"; } +.bi-envelope-slash-1::before { content: "\f695"; } +.bi-envelope-slash-fill::before { content: "\f696"; } +.bi-envelope-slash::before { content: "\f697"; } +.bi-envelope-x-1::before { content: "\f698"; } +.bi-envelope-x-fill::before { content: "\f699"; } +.bi-envelope-x::before { content: "\f69a"; } +.bi-explicit-fill::before { content: "\f69b"; } +.bi-explicit::before { content: "\f69c"; } +.bi-git::before { content: "\f69d"; } +.bi-infinity::before { content: "\f69e"; } +.bi-list-columns-reverse::before { content: "\f69f"; } +.bi-list-columns::before { content: "\f6a0"; } +.bi-meta::before { content: "\f6a1"; } +.bi-mortorboard-fill::before { content: "\f6a2"; } +.bi-mortorboard::before { content: "\f6a3"; } +.bi-nintendo-switch::before { content: "\f6a4"; } +.bi-pc-display-horizontal::before { content: "\f6a5"; } +.bi-pc-display::before { content: "\f6a6"; } +.bi-pc-horizontal::before { content: "\f6a7"; } +.bi-pc::before { content: "\f6a8"; } +.bi-playstation::before { content: "\f6a9"; } +.bi-plus-slash-minus::before { content: "\f6aa"; } +.bi-projector-fill::before { content: "\f6ab"; } +.bi-projector::before { content: "\f6ac"; } +.bi-qr-code-scan::before { content: "\f6ad"; } +.bi-qr-code::before { content: "\f6ae"; } +.bi-quora::before { content: "\f6af"; } +.bi-quote::before { content: "\f6b0"; } +.bi-robot::before { content: "\f6b1"; } +.bi-send-check-fill::before { content: "\f6b2"; } +.bi-send-check::before { content: "\f6b3"; } +.bi-send-dash-fill::before { content: "\f6b4"; } +.bi-send-dash::before { content: "\f6b5"; } +.bi-send-exclamation-1::before { content: "\f6b6"; } +.bi-send-exclamation-fill::before { content: "\f6b7"; } +.bi-send-exclamation::before { content: "\f6b8"; } +.bi-send-fill::before { content: "\f6b9"; } +.bi-send-plus-fill::before { content: "\f6ba"; } +.bi-send-plus::before { content: "\f6bb"; } +.bi-send-slash-fill::before { content: "\f6bc"; } +.bi-send-slash::before { content: "\f6bd"; } +.bi-send-x-fill::before { content: "\f6be"; } +.bi-send-x::before { content: "\f6bf"; } +.bi-send::before { content: "\f6c0"; } +.bi-steam::before { content: "\f6c1"; } +.bi-terminal-dash-1::before { content: "\f6c2"; } +.bi-terminal-dash::before { content: "\f6c3"; } +.bi-terminal-plus::before { content: "\f6c4"; } +.bi-terminal-split::before { content: "\f6c5"; } +.bi-ticket-detailed-fill::before { content: "\f6c6"; } +.bi-ticket-detailed::before { content: "\f6c7"; } +.bi-ticket-fill::before { content: "\f6c8"; } +.bi-ticket-perforated-fill::before { content: "\f6c9"; } +.bi-ticket-perforated::before { content: "\f6ca"; } +.bi-ticket::before { content: "\f6cb"; } +.bi-tiktok::before { content: "\f6cc"; } +.bi-window-dash::before { content: "\f6cd"; } +.bi-window-desktop::before { content: "\f6ce"; } +.bi-window-fullscreen::before { content: "\f6cf"; } +.bi-window-plus::before { content: "\f6d0"; } +.bi-window-split::before { content: "\f6d1"; } +.bi-window-stack::before { content: "\f6d2"; } +.bi-window-x::before { content: "\f6d3"; } +.bi-xbox::before { content: "\f6d4"; } +.bi-ethernet::before { content: "\f6d5"; } +.bi-hdmi-fill::before { content: "\f6d6"; } +.bi-hdmi::before { content: "\f6d7"; } +.bi-usb-c-fill::before { content: "\f6d8"; } +.bi-usb-c::before { content: "\f6d9"; } +.bi-usb-fill::before { content: "\f6da"; } +.bi-usb-plug-fill::before { content: "\f6db"; } +.bi-usb-plug::before { content: "\f6dc"; } +.bi-usb-symbol::before { content: "\f6dd"; } +.bi-usb::before { content: "\f6de"; } +.bi-boombox-fill::before { content: "\f6df"; } +.bi-displayport-1::before { content: "\f6e0"; } +.bi-displayport::before { content: "\f6e1"; } +.bi-gpu-card::before { content: "\f6e2"; } +.bi-memory::before { content: "\f6e3"; } +.bi-modem-fill::before { content: "\f6e4"; } +.bi-modem::before { content: "\f6e5"; } +.bi-motherboard-fill::before { content: "\f6e6"; } +.bi-motherboard::before { content: "\f6e7"; } +.bi-optical-audio-fill::before { content: "\f6e8"; } +.bi-optical-audio::before { content: "\f6e9"; } +.bi-pci-card::before { content: "\f6ea"; } +.bi-router-fill::before { content: "\f6eb"; } +.bi-router::before { content: "\f6ec"; } +.bi-ssd-fill::before { content: "\f6ed"; } +.bi-ssd::before { content: "\f6ee"; } +.bi-thunderbolt-fill::before { content: "\f6ef"; } +.bi-thunderbolt::before { content: "\f6f0"; } +.bi-usb-drive-fill::before { content: "\f6f1"; } +.bi-usb-drive::before { content: "\f6f2"; } +.bi-usb-micro-fill::before { content: "\f6f3"; } +.bi-usb-micro::before { content: "\f6f4"; } +.bi-usb-mini-fill::before { content: "\f6f5"; } +.bi-usb-mini::before { content: "\f6f6"; } +.bi-cloud-haze2::before { content: "\f6f7"; } +.bi-device-hdd-fill::before { content: "\f6f8"; } +.bi-device-hdd::before { content: "\f6f9"; } +.bi-device-ssd-fill::before { content: "\f6fa"; } +.bi-device-ssd::before { content: "\f6fb"; } +.bi-displayport-fill::before { content: "\f6fc"; } +.bi-mortarboard-fill::before { content: "\f6fd"; } +.bi-mortarboard::before { content: "\f6fe"; } +.bi-terminal-x::before { content: "\f6ff"; } +.bi-arrow-through-heart-fill::before { content: "\f700"; } +.bi-arrow-through-heart::before { content: "\f701"; } +.bi-badge-sd-fill::before { content: "\f702"; } +.bi-badge-sd::before { content: "\f703"; } +.bi-bag-heart-fill::before { content: "\f704"; } +.bi-bag-heart::before { content: "\f705"; } +.bi-balloon-fill::before { content: "\f706"; } +.bi-balloon-heart-fill::before { content: "\f707"; } +.bi-balloon-heart::before { content: "\f708"; } +.bi-balloon::before { content: "\f709"; } +.bi-box2-fill::before { content: "\f70a"; } +.bi-box2-heart-fill::before { content: "\f70b"; } +.bi-box2-heart::before { content: "\f70c"; } +.bi-box2::before { content: "\f70d"; } +.bi-braces-asterisk::before { content: "\f70e"; } +.bi-calendar-heart-fill::before { content: "\f70f"; } +.bi-calendar-heart::before { content: "\f710"; } +.bi-calendar2-heart-fill::before { content: "\f711"; } +.bi-calendar2-heart::before { content: "\f712"; } +.bi-chat-heart-fill::before { content: "\f713"; } +.bi-chat-heart::before { content: "\f714"; } +.bi-chat-left-heart-fill::before { content: "\f715"; } +.bi-chat-left-heart::before { content: "\f716"; } +.bi-chat-right-heart-fill::before { content: "\f717"; } +.bi-chat-right-heart::before { content: "\f718"; } +.bi-chat-square-heart-fill::before { content: "\f719"; } +.bi-chat-square-heart::before { content: "\f71a"; } +.bi-clipboard-check-fill::before { content: "\f71b"; } +.bi-clipboard-data-fill::before { content: "\f71c"; } +.bi-clipboard-fill::before { content: "\f71d"; } +.bi-clipboard-heart-fill::before { content: "\f71e"; } +.bi-clipboard-heart::before { content: "\f71f"; } +.bi-clipboard-minus-fill::before { content: "\f720"; } +.bi-clipboard-plus-fill::before { content: "\f721"; } +.bi-clipboard-pulse::before { content: "\f722"; } +.bi-clipboard-x-fill::before { content: "\f723"; } +.bi-clipboard2-check-fill::before { content: "\f724"; } +.bi-clipboard2-check::before { content: "\f725"; } +.bi-clipboard2-data-fill::before { content: "\f726"; } +.bi-clipboard2-data::before { content: "\f727"; } +.bi-clipboard2-fill::before { content: "\f728"; } +.bi-clipboard2-heart-fill::before { content: "\f729"; } +.bi-clipboard2-heart::before { content: "\f72a"; } +.bi-clipboard2-minus-fill::before { content: "\f72b"; } +.bi-clipboard2-minus::before { content: "\f72c"; } +.bi-clipboard2-plus-fill::before { content: "\f72d"; } +.bi-clipboard2-plus::before { content: "\f72e"; } +.bi-clipboard2-pulse-fill::before { content: "\f72f"; } +.bi-clipboard2-pulse::before { content: "\f730"; } +.bi-clipboard2-x-fill::before { content: "\f731"; } +.bi-clipboard2-x::before { content: "\f732"; } +.bi-clipboard2::before { content: "\f733"; } +.bi-emoji-kiss-fill::before { content: "\f734"; } +.bi-emoji-kiss::before { content: "\f735"; } +.bi-envelope-heart-fill::before { content: "\f736"; } +.bi-envelope-heart::before { content: "\f737"; } +.bi-envelope-open-heart-fill::before { content: "\f738"; } +.bi-envelope-open-heart::before { content: "\f739"; } +.bi-envelope-paper-fill::before { content: "\f73a"; } +.bi-envelope-paper-heart-fill::before { content: "\f73b"; } +.bi-envelope-paper-heart::before { content: "\f73c"; } +.bi-envelope-paper::before { content: "\f73d"; } +.bi-filetype-aac::before { content: "\f73e"; } +.bi-filetype-ai::before { content: "\f73f"; } +.bi-filetype-bmp::before { content: "\f740"; } +.bi-filetype-cs::before { content: "\f741"; } +.bi-filetype-css::before { content: "\f742"; } +.bi-filetype-csv::before { content: "\f743"; } +.bi-filetype-doc::before { content: "\f744"; } +.bi-filetype-docx::before { content: "\f745"; } +.bi-filetype-exe::before { content: "\f746"; } +.bi-filetype-gif::before { content: "\f747"; } +.bi-filetype-heic::before { content: "\f748"; } +.bi-filetype-html::before { content: "\f749"; } +.bi-filetype-java::before { content: "\f74a"; } +.bi-filetype-jpg::before { content: "\f74b"; } +.bi-filetype-js::before { content: "\f74c"; } +.bi-filetype-jsx::before { content: "\f74d"; } +.bi-filetype-key::before { content: "\f74e"; } +.bi-filetype-m4p::before { content: "\f74f"; } +.bi-filetype-md::before { content: "\f750"; } +.bi-filetype-mdx::before { content: "\f751"; } +.bi-filetype-mov::before { content: "\f752"; } +.bi-filetype-mp3::before { content: "\f753"; } +.bi-filetype-mp4::before { content: "\f754"; } +.bi-filetype-otf::before { content: "\f755"; } +.bi-filetype-pdf::before { content: "\f756"; } +.bi-filetype-php::before { content: "\f757"; } +.bi-filetype-png::before { content: "\f758"; } +.bi-filetype-ppt-1::before { content: "\f759"; } +.bi-filetype-ppt::before { content: "\f75a"; } +.bi-filetype-psd::before { content: "\f75b"; } +.bi-filetype-py::before { content: "\f75c"; } +.bi-filetype-raw::before { content: "\f75d"; } +.bi-filetype-rb::before { content: "\f75e"; } +.bi-filetype-sass::before { content: "\f75f"; } +.bi-filetype-scss::before { content: "\f760"; } +.bi-filetype-sh::before { content: "\f761"; } +.bi-filetype-svg::before { content: "\f762"; } +.bi-filetype-tiff::before { content: "\f763"; } +.bi-filetype-tsx::before { content: "\f764"; } +.bi-filetype-ttf::before { content: "\f765"; } +.bi-filetype-txt::before { content: "\f766"; } +.bi-filetype-wav::before { content: "\f767"; } +.bi-filetype-woff::before { content: "\f768"; } +.bi-filetype-xls-1::before { content: "\f769"; } +.bi-filetype-xls::before { content: "\f76a"; } +.bi-filetype-xml::before { content: "\f76b"; } +.bi-filetype-yml::before { content: "\f76c"; } +.bi-heart-arrow::before { content: "\f76d"; } +.bi-heart-pulse-fill::before { content: "\f76e"; } +.bi-heart-pulse::before { content: "\f76f"; } +.bi-heartbreak-fill::before { content: "\f770"; } +.bi-heartbreak::before { content: "\f771"; } +.bi-hearts::before { content: "\f772"; } +.bi-hospital-fill::before { content: "\f773"; } +.bi-hospital::before { content: "\f774"; } +.bi-house-heart-fill::before { content: "\f775"; } +.bi-house-heart::before { content: "\f776"; } +.bi-incognito::before { content: "\f777"; } +.bi-magnet-fill::before { content: "\f778"; } +.bi-magnet::before { content: "\f779"; } +.bi-person-heart::before { content: "\f77a"; } +.bi-person-hearts::before { content: "\f77b"; } +.bi-phone-flip::before { content: "\f77c"; } +.bi-plugin::before { content: "\f77d"; } +.bi-postage-fill::before { content: "\f77e"; } +.bi-postage-heart-fill::before { content: "\f77f"; } +.bi-postage-heart::before { content: "\f780"; } +.bi-postage::before { content: "\f781"; } +.bi-postcard-fill::before { content: "\f782"; } +.bi-postcard-heart-fill::before { content: "\f783"; } +.bi-postcard-heart::before { content: "\f784"; } +.bi-postcard::before { content: "\f785"; } +.bi-search-heart-fill::before { content: "\f786"; } +.bi-search-heart::before { content: "\f787"; } +.bi-sliders2-vertical::before { content: "\f788"; } +.bi-sliders2::before { content: "\f789"; } +.bi-trash3-fill::before { content: "\f78a"; } +.bi-trash3::before { content: "\f78b"; } +.bi-valentine::before { content: "\f78c"; } +.bi-valentine2::before { content: "\f78d"; } +.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; } +.bi-wrench-adjustable-circle::before { content: "\f78f"; } +.bi-wrench-adjustable::before { content: "\f790"; } +.bi-filetype-json::before { content: "\f791"; } +.bi-filetype-pptx::before { content: "\f792"; } +.bi-filetype-xlsx::before { content: "\f793"; } +.bi-1-circle-1::before { content: "\f794"; } +.bi-1-circle-fill-1::before { content: "\f795"; } +.bi-1-circle-fill::before { content: "\f796"; } +.bi-1-circle::before { content: "\f797"; } +.bi-1-square-fill::before { content: "\f798"; } +.bi-1-square::before { content: "\f799"; } +.bi-2-circle-1::before { content: "\f79a"; } +.bi-2-circle-fill-1::before { content: "\f79b"; } +.bi-2-circle-fill::before { content: "\f79c"; } +.bi-2-circle::before { content: "\f79d"; } +.bi-2-square-fill::before { content: "\f79e"; } +.bi-2-square::before { content: "\f79f"; } +.bi-3-circle-1::before { content: "\f7a0"; } +.bi-3-circle-fill-1::before { content: "\f7a1"; } +.bi-3-circle-fill::before { content: "\f7a2"; } +.bi-3-circle::before { content: "\f7a3"; } +.bi-3-square-fill::before { content: "\f7a4"; } +.bi-3-square::before { content: "\f7a5"; } +.bi-4-circle-1::before { content: "\f7a6"; } +.bi-4-circle-fill-1::before { content: "\f7a7"; } +.bi-4-circle-fill::before { content: "\f7a8"; } +.bi-4-circle::before { content: "\f7a9"; } +.bi-4-square-fill::before { content: "\f7aa"; } +.bi-4-square::before { content: "\f7ab"; } +.bi-5-circle-1::before { content: "\f7ac"; } +.bi-5-circle-fill-1::before { content: "\f7ad"; } +.bi-5-circle-fill::before { content: "\f7ae"; } +.bi-5-circle::before { content: "\f7af"; } +.bi-5-square-fill::before { content: "\f7b0"; } +.bi-5-square::before { content: "\f7b1"; } +.bi-6-circle-1::before { content: "\f7b2"; } +.bi-6-circle-fill-1::before { content: "\f7b3"; } +.bi-6-circle-fill::before { content: "\f7b4"; } +.bi-6-circle::before { content: "\f7b5"; } +.bi-6-square-fill::before { content: "\f7b6"; } +.bi-6-square::before { content: "\f7b7"; } +.bi-7-circle-1::before { content: "\f7b8"; } +.bi-7-circle-fill-1::before { content: "\f7b9"; } +.bi-7-circle-fill::before { content: "\f7ba"; } +.bi-7-circle::before { content: "\f7bb"; } +.bi-7-square-fill::before { content: "\f7bc"; } +.bi-7-square::before { content: "\f7bd"; } +.bi-8-circle-1::before { content: "\f7be"; } +.bi-8-circle-fill-1::before { content: "\f7bf"; } +.bi-8-circle-fill::before { content: "\f7c0"; } +.bi-8-circle::before { content: "\f7c1"; } +.bi-8-square-fill::before { content: "\f7c2"; } +.bi-8-square::before { content: "\f7c3"; } +.bi-9-circle-1::before { content: "\f7c4"; } +.bi-9-circle-fill-1::before { content: "\f7c5"; } +.bi-9-circle-fill::before { content: "\f7c6"; } +.bi-9-circle::before { content: "\f7c7"; } +.bi-9-square-fill::before { content: "\f7c8"; } +.bi-9-square::before { content: "\f7c9"; } +.bi-airplane-engines-fill::before { content: "\f7ca"; } +.bi-airplane-engines::before { content: "\f7cb"; } +.bi-airplane-fill::before { content: "\f7cc"; } +.bi-airplane::before { content: "\f7cd"; } +.bi-alexa::before { content: "\f7ce"; } +.bi-alipay::before { content: "\f7cf"; } +.bi-android::before { content: "\f7d0"; } +.bi-android2::before { content: "\f7d1"; } +.bi-box-fill::before { content: "\f7d2"; } +.bi-box-seam-fill::before { content: "\f7d3"; } +.bi-browser-chrome::before { content: "\f7d4"; } +.bi-browser-edge::before { content: "\f7d5"; } +.bi-browser-firefox::before { content: "\f7d6"; } +.bi-browser-safari::before { content: "\f7d7"; } +.bi-c-circle-1::before { content: "\f7d8"; } +.bi-c-circle-fill-1::before { content: "\f7d9"; } +.bi-c-circle-fill::before { content: "\f7da"; } +.bi-c-circle::before { content: "\f7db"; } +.bi-c-square-fill::before { content: "\f7dc"; } +.bi-c-square::before { content: "\f7dd"; } +.bi-capsule-pill::before { content: "\f7de"; } +.bi-capsule::before { content: "\f7df"; } +.bi-car-front-fill::before { content: "\f7e0"; } +.bi-car-front::before { content: "\f7e1"; } +.bi-cassette-fill::before { content: "\f7e2"; } +.bi-cassette::before { content: "\f7e3"; } +.bi-cc-circle-1::before { content: "\f7e4"; } +.bi-cc-circle-fill-1::before { content: "\f7e5"; } +.bi-cc-circle-fill::before { content: "\f7e6"; } +.bi-cc-circle::before { content: "\f7e7"; } +.bi-cc-square-fill::before { content: "\f7e8"; } +.bi-cc-square::before { content: "\f7e9"; } +.bi-cup-hot-fill::before { content: "\f7ea"; } +.bi-cup-hot::before { content: "\f7eb"; } +.bi-currency-rupee::before { content: "\f7ec"; } +.bi-dropbox::before { content: "\f7ed"; } +.bi-escape::before { content: "\f7ee"; } +.bi-fast-forward-btn-fill::before { content: "\f7ef"; } +.bi-fast-forward-btn::before { content: "\f7f0"; } +.bi-fast-forward-circle-fill::before { content: "\f7f1"; } +.bi-fast-forward-circle::before { content: "\f7f2"; } +.bi-fast-forward-fill::before { content: "\f7f3"; } +.bi-fast-forward::before { content: "\f7f4"; } +.bi-filetype-sql::before { content: "\f7f5"; } +.bi-fire::before { content: "\f7f6"; } +.bi-google-play::before { content: "\f7f7"; } +.bi-h-circle-1::before { content: "\f7f8"; } +.bi-h-circle-fill-1::before { content: "\f7f9"; } +.bi-h-circle-fill::before { content: "\f7fa"; } +.bi-h-circle::before { content: "\f7fb"; } +.bi-h-square-fill::before { content: "\f7fc"; } +.bi-h-square::before { content: "\f7fd"; } +.bi-indent::before { content: "\f7fe"; } +.bi-lungs-fill::before { content: "\f7ff"; } +.bi-lungs::before { content: "\f800"; } +.bi-microsoft-teams::before { content: "\f801"; } +.bi-p-circle-1::before { content: "\f802"; } +.bi-p-circle-fill-1::before { content: "\f803"; } +.bi-p-circle-fill::before { content: "\f804"; } +.bi-p-circle::before { content: "\f805"; } +.bi-p-square-fill::before { content: "\f806"; } +.bi-p-square::before { content: "\f807"; } +.bi-pass-fill::before { content: "\f808"; } +.bi-pass::before { content: "\f809"; } +.bi-prescription::before { content: "\f80a"; } +.bi-prescription2::before { content: "\f80b"; } +.bi-r-circle-1::before { content: "\f80c"; } +.bi-r-circle-fill-1::before { content: "\f80d"; } +.bi-r-circle-fill::before { content: "\f80e"; } +.bi-r-circle::before { content: "\f80f"; } +.bi-r-square-fill::before { content: "\f810"; } +.bi-r-square::before { content: "\f811"; } +.bi-repeat-1::before { content: "\f812"; } +.bi-repeat::before { content: "\f813"; } +.bi-rewind-btn-fill::before { content: "\f814"; } +.bi-rewind-btn::before { content: "\f815"; } +.bi-rewind-circle-fill::before { content: "\f816"; } +.bi-rewind-circle::before { content: "\f817"; } +.bi-rewind-fill::before { content: "\f818"; } +.bi-rewind::before { content: "\f819"; } +.bi-train-freight-front-fill::before { content: "\f81a"; } +.bi-train-freight-front::before { content: "\f81b"; } +.bi-train-front-fill::before { content: "\f81c"; } +.bi-train-front::before { content: "\f81d"; } +.bi-train-lightrail-front-fill::before { content: "\f81e"; } +.bi-train-lightrail-front::before { content: "\f81f"; } +.bi-truck-front-fill::before { content: "\f820"; } +.bi-truck-front::before { content: "\f821"; } +.bi-ubuntu::before { content: "\f822"; } +.bi-unindent::before { content: "\f823"; } +.bi-unity::before { content: "\f824"; } +.bi-universal-access-circle::before { content: "\f825"; } +.bi-universal-access::before { content: "\f826"; } +.bi-virus::before { content: "\f827"; } +.bi-virus2::before { content: "\f828"; } +.bi-wechat::before { content: "\f829"; } +.bi-yelp::before { content: "\f82a"; } +.bi-sign-stop-fill::before { content: "\f82b"; } +.bi-sign-stop-lights-fill::before { content: "\f82c"; } +.bi-sign-stop-lights::before { content: "\f82d"; } +.bi-sign-stop::before { content: "\f82e"; } +.bi-sign-turn-left-fill::before { content: "\f82f"; } +.bi-sign-turn-left::before { content: "\f830"; } +.bi-sign-turn-right-fill::before { content: "\f831"; } +.bi-sign-turn-right::before { content: "\f832"; } +.bi-sign-turn-slight-left-fill::before { content: "\f833"; } +.bi-sign-turn-slight-left::before { content: "\f834"; } +.bi-sign-turn-slight-right-fill::before { content: "\f835"; } +.bi-sign-turn-slight-right::before { content: "\f836"; } +.bi-sign-yield-fill::before { content: "\f837"; } +.bi-sign-yield::before { content: "\f838"; } +.bi-ev-station-fill::before { content: "\f839"; } +.bi-ev-station::before { content: "\f83a"; } +.bi-fuel-pump-diesel-fill::before { content: "\f83b"; } +.bi-fuel-pump-diesel::before { content: "\f83c"; } +.bi-fuel-pump-fill::before { content: "\f83d"; } +.bi-fuel-pump::before { content: "\f83e"; } +.bi-0-circle-fill::before { content: "\f83f"; } +.bi-0-circle::before { content: "\f840"; } +.bi-0-square-fill::before { content: "\f841"; } +.bi-0-square::before { content: "\f842"; } +.bi-rocket-fill::before { content: "\f843"; } +.bi-rocket-takeoff-fill::before { content: "\f844"; } +.bi-rocket-takeoff::before { content: "\f845"; } +.bi-rocket::before { content: "\f846"; } +.bi-stripe::before { content: "\f847"; } +.bi-subscript::before { content: "\f848"; } +.bi-superscript::before { content: "\f849"; } +.bi-trello::before { content: "\f84a"; } +.bi-envelope-at-fill::before { content: "\f84b"; } +.bi-envelope-at::before { content: "\f84c"; } +.bi-regex::before { content: "\f84d"; } +.bi-text-wrap::before { content: "\f84e"; } +.bi-sign-dead-end-fill::before { content: "\f84f"; } +.bi-sign-dead-end::before { content: "\f850"; } +.bi-sign-do-not-enter-fill::before { content: "\f851"; } +.bi-sign-do-not-enter::before { content: "\f852"; } +.bi-sign-intersection-fill::before { content: "\f853"; } +.bi-sign-intersection-side-fill::before { content: "\f854"; } +.bi-sign-intersection-side::before { content: "\f855"; } +.bi-sign-intersection-t-fill::before { content: "\f856"; } +.bi-sign-intersection-t::before { content: "\f857"; } +.bi-sign-intersection-y-fill::before { content: "\f858"; } +.bi-sign-intersection-y::before { content: "\f859"; } +.bi-sign-intersection::before { content: "\f85a"; } +.bi-sign-merge-left-fill::before { content: "\f85b"; } +.bi-sign-merge-left::before { content: "\f85c"; } +.bi-sign-merge-right-fill::before { content: "\f85d"; } +.bi-sign-merge-right::before { content: "\f85e"; } +.bi-sign-no-left-turn-fill::before { content: "\f85f"; } +.bi-sign-no-left-turn::before { content: "\f860"; } +.bi-sign-no-parking-fill::before { content: "\f861"; } +.bi-sign-no-parking::before { content: "\f862"; } +.bi-sign-no-right-turn-fill::before { content: "\f863"; } +.bi-sign-no-right-turn::before { content: "\f864"; } +.bi-sign-railroad-fill::before { content: "\f865"; } +.bi-sign-railroad::before { content: "\f866"; } +.bi-building-add::before { content: "\f867"; } +.bi-building-check::before { content: "\f868"; } +.bi-building-dash::before { content: "\f869"; } +.bi-building-down::before { content: "\f86a"; } +.bi-building-exclamation::before { content: "\f86b"; } +.bi-building-fill-add::before { content: "\f86c"; } +.bi-building-fill-check::before { content: "\f86d"; } +.bi-building-fill-dash::before { content: "\f86e"; } +.bi-building-fill-down::before { content: "\f86f"; } +.bi-building-fill-exclamation::before { content: "\f870"; } +.bi-building-fill-gear::before { content: "\f871"; } +.bi-building-fill-lock::before { content: "\f872"; } +.bi-building-fill-slash::before { content: "\f873"; } +.bi-building-fill-up::before { content: "\f874"; } +.bi-building-fill-x::before { content: "\f875"; } +.bi-building-fill::before { content: "\f876"; } +.bi-building-gear::before { content: "\f877"; } +.bi-building-lock::before { content: "\f878"; } +.bi-building-slash::before { content: "\f879"; } +.bi-building-up::before { content: "\f87a"; } +.bi-building-x::before { content: "\f87b"; } +.bi-buildings-fill::before { content: "\f87c"; } +.bi-buildings::before { content: "\f87d"; } +.bi-bus-front-fill::before { content: "\f87e"; } +.bi-bus-front::before { content: "\f87f"; } +.bi-ev-front-fill::before { content: "\f880"; } +.bi-ev-front::before { content: "\f881"; } +.bi-globe-americas::before { content: "\f882"; } +.bi-globe-asia-australia::before { content: "\f883"; } +.bi-globe-central-south-asia::before { content: "\f884"; } +.bi-globe-europe-africa::before { content: "\f885"; } +.bi-house-add-fill::before { content: "\f886"; } +.bi-house-add::before { content: "\f887"; } +.bi-house-check-fill::before { content: "\f888"; } +.bi-house-check::before { content: "\f889"; } +.bi-house-dash-fill::before { content: "\f88a"; } +.bi-house-dash::before { content: "\f88b"; } +.bi-house-down-fill::before { content: "\f88c"; } +.bi-house-down::before { content: "\f88d"; } +.bi-house-exclamation-fill::before { content: "\f88e"; } +.bi-house-exclamation::before { content: "\f88f"; } +.bi-house-gear-fill::before { content: "\f890"; } +.bi-house-gear::before { content: "\f891"; } +.bi-house-lock-fill::before { content: "\f892"; } +.bi-house-lock::before { content: "\f893"; } +.bi-house-slash-fill::before { content: "\f894"; } +.bi-house-slash::before { content: "\f895"; } +.bi-house-up-fill::before { content: "\f896"; } +.bi-house-up::before { content: "\f897"; } +.bi-house-x-fill::before { content: "\f898"; } +.bi-house-x::before { content: "\f899"; } +.bi-person-add::before { content: "\f89a"; } +.bi-person-down::before { content: "\f89b"; } +.bi-person-exclamation::before { content: "\f89c"; } +.bi-person-fill-add::before { content: "\f89d"; } +.bi-person-fill-check::before { content: "\f89e"; } +.bi-person-fill-dash::before { content: "\f89f"; } +.bi-person-fill-down::before { content: "\f8a0"; } +.bi-person-fill-exclamation::before { content: "\f8a1"; } +.bi-person-fill-gear::before { content: "\f8a2"; } +.bi-person-fill-lock::before { content: "\f8a3"; } +.bi-person-fill-slash::before { content: "\f8a4"; } +.bi-person-fill-up::before { content: "\f8a5"; } +.bi-person-fill-x::before { content: "\f8a6"; } +.bi-person-gear::before { content: "\f8a7"; } +.bi-person-lock::before { content: "\f8a8"; } +.bi-person-slash::before { content: "\f8a9"; } +.bi-person-up::before { content: "\f8aa"; } +.bi-scooter::before { content: "\f8ab"; } +.bi-taxi-front-fill::before { content: "\f8ac"; } +.bi-taxi-front::before { content: "\f8ad"; } +.bi-amd::before { content: "\f8ae"; } +.bi-database-add::before { content: "\f8af"; } +.bi-database-check::before { content: "\f8b0"; } +.bi-database-dash::before { content: "\f8b1"; } +.bi-database-down::before { content: "\f8b2"; } +.bi-database-exclamation::before { content: "\f8b3"; } +.bi-database-fill-add::before { content: "\f8b4"; } +.bi-database-fill-check::before { content: "\f8b5"; } +.bi-database-fill-dash::before { content: "\f8b6"; } +.bi-database-fill-down::before { content: "\f8b7"; } +.bi-database-fill-exclamation::before { content: "\f8b8"; } +.bi-database-fill-gear::before { content: "\f8b9"; } +.bi-database-fill-lock::before { content: "\f8ba"; } +.bi-database-fill-slash::before { content: "\f8bb"; } +.bi-database-fill-up::before { content: "\f8bc"; } +.bi-database-fill-x::before { content: "\f8bd"; } +.bi-database-fill::before { content: "\f8be"; } +.bi-database-gear::before { content: "\f8bf"; } +.bi-database-lock::before { content: "\f8c0"; } +.bi-database-slash::before { content: "\f8c1"; } +.bi-database-up::before { content: "\f8c2"; } +.bi-database-x::before { content: "\f8c3"; } +.bi-database::before { content: "\f8c4"; } +.bi-houses-fill::before { content: "\f8c5"; } +.bi-houses::before { content: "\f8c6"; } +.bi-nvidia::before { content: "\f8c7"; } +.bi-person-vcard-fill::before { content: "\f8c8"; } +.bi-person-vcard::before { content: "\f8c9"; } +.bi-sina-weibo::before { content: "\f8ca"; } +.bi-tencent-qq::before { content: "\f8cb"; } +.bi-wikipedia::before { content: "\f8cc"; } diff --git a/site_libs/bootstrap/bootstrap-icons.woff b/site_libs/bootstrap/bootstrap-icons.woff new file mode 100644 index 0000000..18d21d4 Binary files /dev/null and b/site_libs/bootstrap/bootstrap-icons.woff differ diff --git a/site_libs/bootstrap/bootstrap.min.css b/site_libs/bootstrap/bootstrap.min.css new file mode 100644 index 0000000..ddb027b --- /dev/null +++ b/site_libs/bootstrap/bootstrap.min.css @@ -0,0 +1,10 @@ +/*! + * Bootstrap v5.1.3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */@import"https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap";:root{--bs-blue: #325d88;--bs-indigo: #6610f2;--bs-purple: #6f42c1;--bs-pink: #e83e8c;--bs-red: #d9534f;--bs-orange: #f47c3c;--bs-yellow: #ffc107;--bs-green: #93c54b;--bs-teal: #20c997;--bs-cyan: #29abe0;--bs-white: #fff;--bs-gray: #6c757d;--bs-gray-dark: #3e3f3a;--bs-gray-100: #f8f9fa;--bs-gray-200: #f8f5f0;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #495057;--bs-gray-800: #3e3f3a;--bs-gray-900: #212529;--bs-default: #6c757d;--bs-primary: #3980F5;--bs-secondary: #6c757d;--bs-success: #93c54b;--bs-info: #29abe0;--bs-warning: #f47c3c;--bs-danger: #d9534f;--bs-light: #f8f5f0;--bs-dark: #3e3f3a;--bs-default-rgb: 108, 117, 125;--bs-primary-rgb: 57, 128, 245;--bs-secondary-rgb: 108, 117, 125;--bs-success-rgb: 147, 197, 75;--bs-info-rgb: 41, 171, 224;--bs-warning-rgb: 244, 124, 60;--bs-danger-rgb: 217, 83, 79;--bs-light-rgb: 248, 245, 240;--bs-dark-rgb: 62, 63, 58;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-body-color-rgb: 0, 0, 0;--bs-body-bg-rgb: 255, 255, 255;--bs-font-sans-serif: Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-root-font-size: 18px;--bs-body-font-family: Open Sans, sans-serif;--bs-body-font-size: 1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: black;--bs-body-bg: white}*,*::before,*::after{box-sizing:border-box}:root{font-size:var(--bs-root-font-size)}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2}h1,.h1{font-size:calc(1.325rem + 0.9vw)}@media(min-width: 1200px){h1,.h1{font-size:2rem}}h2,.h2{font-size:calc(1.29rem + 0.48vw)}@media(min-width: 1200px){h2,.h2{font-size:1.65rem}}h3,.h3{font-size:calc(1.27rem + 0.24vw)}@media(min-width: 1200px){h3,.h3{font-size:1.45rem}}h4,.h4{font-size:1.25rem}h5,.h5{font-size:1.1rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-bs-original-title]{text-decoration:underline dotted;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;-ms-text-decoration:underline dotted;-o-text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem;padding:.625rem 1.25rem;border-left:.25rem solid #f8f5f0}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}b,strong{font-weight:bolder}small,.small{font-size:0.875em}mark,.mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:0.75em;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}a{color:#2b8cee;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}a:hover{color:#2270be}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr /* rtl:ignore */;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:#f5f5f5;padding:.5rem;border:1px solid #dee2e6;border-radius:.25rem}pre code{background-color:rgba(0,0,0,0);font-size:inherit;color:inherit;word-break:normal}code{font-size:0.875em;color:#9753b8;background-color:#f5f5f5;border-radius:.25rem;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#fff;background-color:#212529;border-radius:.2em}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + 0.3vw);line-height:inherit}@media(min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:0.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:0.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:0.875em;color:#6c757d}.grid{display:grid;grid-template-rows:repeat(var(--bs-rows, 1), 1fr);grid-template-columns:repeat(var(--bs-columns, 12), 1fr);gap:var(--bs-gap, 1.5rem)}.grid .g-col-1{grid-column:auto/span 1}.grid .g-col-2{grid-column:auto/span 2}.grid .g-col-3{grid-column:auto/span 3}.grid .g-col-4{grid-column:auto/span 4}.grid .g-col-5{grid-column:auto/span 5}.grid .g-col-6{grid-column:auto/span 6}.grid .g-col-7{grid-column:auto/span 7}.grid .g-col-8{grid-column:auto/span 8}.grid .g-col-9{grid-column:auto/span 9}.grid .g-col-10{grid-column:auto/span 10}.grid .g-col-11{grid-column:auto/span 11}.grid .g-col-12{grid-column:auto/span 12}.grid .g-start-1{grid-column-start:1}.grid .g-start-2{grid-column-start:2}.grid .g-start-3{grid-column-start:3}.grid .g-start-4{grid-column-start:4}.grid .g-start-5{grid-column-start:5}.grid .g-start-6{grid-column-start:6}.grid .g-start-7{grid-column-start:7}.grid .g-start-8{grid-column-start:8}.grid .g-start-9{grid-column-start:9}.grid .g-start-10{grid-column-start:10}.grid .g-start-11{grid-column-start:11}@media(min-width: 576px){.grid .g-col-sm-1{grid-column:auto/span 1}.grid .g-col-sm-2{grid-column:auto/span 2}.grid .g-col-sm-3{grid-column:auto/span 3}.grid .g-col-sm-4{grid-column:auto/span 4}.grid .g-col-sm-5{grid-column:auto/span 5}.grid .g-col-sm-6{grid-column:auto/span 6}.grid .g-col-sm-7{grid-column:auto/span 7}.grid .g-col-sm-8{grid-column:auto/span 8}.grid .g-col-sm-9{grid-column:auto/span 9}.grid .g-col-sm-10{grid-column:auto/span 10}.grid .g-col-sm-11{grid-column:auto/span 11}.grid .g-col-sm-12{grid-column:auto/span 12}.grid .g-start-sm-1{grid-column-start:1}.grid .g-start-sm-2{grid-column-start:2}.grid .g-start-sm-3{grid-column-start:3}.grid .g-start-sm-4{grid-column-start:4}.grid .g-start-sm-5{grid-column-start:5}.grid .g-start-sm-6{grid-column-start:6}.grid .g-start-sm-7{grid-column-start:7}.grid .g-start-sm-8{grid-column-start:8}.grid .g-start-sm-9{grid-column-start:9}.grid .g-start-sm-10{grid-column-start:10}.grid .g-start-sm-11{grid-column-start:11}}@media(min-width: 768px){.grid .g-col-md-1{grid-column:auto/span 1}.grid .g-col-md-2{grid-column:auto/span 2}.grid .g-col-md-3{grid-column:auto/span 3}.grid .g-col-md-4{grid-column:auto/span 4}.grid .g-col-md-5{grid-column:auto/span 5}.grid .g-col-md-6{grid-column:auto/span 6}.grid .g-col-md-7{grid-column:auto/span 7}.grid .g-col-md-8{grid-column:auto/span 8}.grid .g-col-md-9{grid-column:auto/span 9}.grid .g-col-md-10{grid-column:auto/span 10}.grid .g-col-md-11{grid-column:auto/span 11}.grid .g-col-md-12{grid-column:auto/span 12}.grid .g-start-md-1{grid-column-start:1}.grid .g-start-md-2{grid-column-start:2}.grid .g-start-md-3{grid-column-start:3}.grid .g-start-md-4{grid-column-start:4}.grid .g-start-md-5{grid-column-start:5}.grid .g-start-md-6{grid-column-start:6}.grid .g-start-md-7{grid-column-start:7}.grid .g-start-md-8{grid-column-start:8}.grid .g-start-md-9{grid-column-start:9}.grid .g-start-md-10{grid-column-start:10}.grid .g-start-md-11{grid-column-start:11}}@media(min-width: 992px){.grid .g-col-lg-1{grid-column:auto/span 1}.grid .g-col-lg-2{grid-column:auto/span 2}.grid .g-col-lg-3{grid-column:auto/span 3}.grid .g-col-lg-4{grid-column:auto/span 4}.grid .g-col-lg-5{grid-column:auto/span 5}.grid .g-col-lg-6{grid-column:auto/span 6}.grid .g-col-lg-7{grid-column:auto/span 7}.grid .g-col-lg-8{grid-column:auto/span 8}.grid .g-col-lg-9{grid-column:auto/span 9}.grid .g-col-lg-10{grid-column:auto/span 10}.grid .g-col-lg-11{grid-column:auto/span 11}.grid .g-col-lg-12{grid-column:auto/span 12}.grid .g-start-lg-1{grid-column-start:1}.grid .g-start-lg-2{grid-column-start:2}.grid .g-start-lg-3{grid-column-start:3}.grid .g-start-lg-4{grid-column-start:4}.grid .g-start-lg-5{grid-column-start:5}.grid .g-start-lg-6{grid-column-start:6}.grid .g-start-lg-7{grid-column-start:7}.grid .g-start-lg-8{grid-column-start:8}.grid .g-start-lg-9{grid-column-start:9}.grid .g-start-lg-10{grid-column-start:10}.grid .g-start-lg-11{grid-column-start:11}}@media(min-width: 1200px){.grid .g-col-xl-1{grid-column:auto/span 1}.grid .g-col-xl-2{grid-column:auto/span 2}.grid .g-col-xl-3{grid-column:auto/span 3}.grid .g-col-xl-4{grid-column:auto/span 4}.grid .g-col-xl-5{grid-column:auto/span 5}.grid .g-col-xl-6{grid-column:auto/span 6}.grid .g-col-xl-7{grid-column:auto/span 7}.grid .g-col-xl-8{grid-column:auto/span 8}.grid .g-col-xl-9{grid-column:auto/span 9}.grid .g-col-xl-10{grid-column:auto/span 10}.grid .g-col-xl-11{grid-column:auto/span 11}.grid .g-col-xl-12{grid-column:auto/span 12}.grid .g-start-xl-1{grid-column-start:1}.grid .g-start-xl-2{grid-column-start:2}.grid .g-start-xl-3{grid-column-start:3}.grid .g-start-xl-4{grid-column-start:4}.grid .g-start-xl-5{grid-column-start:5}.grid .g-start-xl-6{grid-column-start:6}.grid .g-start-xl-7{grid-column-start:7}.grid .g-start-xl-8{grid-column-start:8}.grid .g-start-xl-9{grid-column-start:9}.grid .g-start-xl-10{grid-column-start:10}.grid .g-start-xl-11{grid-column-start:11}}@media(min-width: 1400px){.grid .g-col-xxl-1{grid-column:auto/span 1}.grid .g-col-xxl-2{grid-column:auto/span 2}.grid .g-col-xxl-3{grid-column:auto/span 3}.grid .g-col-xxl-4{grid-column:auto/span 4}.grid .g-col-xxl-5{grid-column:auto/span 5}.grid .g-col-xxl-6{grid-column:auto/span 6}.grid .g-col-xxl-7{grid-column:auto/span 7}.grid .g-col-xxl-8{grid-column:auto/span 8}.grid .g-col-xxl-9{grid-column:auto/span 9}.grid .g-col-xxl-10{grid-column:auto/span 10}.grid .g-col-xxl-11{grid-column:auto/span 11}.grid .g-col-xxl-12{grid-column:auto/span 12}.grid .g-start-xxl-1{grid-column-start:1}.grid .g-start-xxl-2{grid-column-start:2}.grid .g-start-xxl-3{grid-column-start:3}.grid .g-start-xxl-4{grid-column-start:4}.grid .g-start-xxl-5{grid-column-start:5}.grid .g-start-xxl-6{grid-column-start:6}.grid .g-start-xxl-7{grid-column-start:7}.grid .g-start-xxl-8{grid-column-start:8}.grid .g-start-xxl-9{grid-column-start:9}.grid .g-start-xxl-10{grid-column-start:10}.grid .g-start-xxl-11{grid-column-start:11}}.table{--bs-table-bg: transparent;--bs-table-accent-bg: transparent;--bs-table-striped-color: black;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: black;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: black;--bs-table-hover-bg: rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#000;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:first-child){border-top:2px solid gray}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-accent-bg: var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg: var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover>*{--bs-table-accent-bg: var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg: #d7e6fd;--bs-table-striped-bg: #ccdbf0;--bs-table-striped-color: #000;--bs-table-active-bg: #c2cfe4;--bs-table-active-color: #000;--bs-table-hover-bg: #c7d5ea;--bs-table-hover-color: #000;color:#000;border-color:#c2cfe4}.table-secondary{--bs-table-bg: #e2e3e5;--bs-table-striped-bg: #d7d8da;--bs-table-striped-color: #000;--bs-table-active-bg: #cbccce;--bs-table-active-color: #000;--bs-table-hover-bg: #d1d2d4;--bs-table-hover-color: #000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg: #e9f3db;--bs-table-striped-bg: #dde7d0;--bs-table-striped-color: #000;--bs-table-active-bg: #d2dbc5;--bs-table-active-color: #000;--bs-table-hover-bg: #d8e1cb;--bs-table-hover-color: #000;color:#000;border-color:#d2dbc5}.table-info{--bs-table-bg: #d4eef9;--bs-table-striped-bg: #c9e2ed;--bs-table-striped-color: #000;--bs-table-active-bg: #bfd6e0;--bs-table-active-color: #000;--bs-table-hover-bg: #c4dce6;--bs-table-hover-color: #000;color:#000;border-color:#bfd6e0}.table-warning{--bs-table-bg: #fde5d8;--bs-table-striped-bg: #f0dacd;--bs-table-striped-color: #000;--bs-table-active-bg: #e4cec2;--bs-table-active-color: #000;--bs-table-hover-bg: #ead4c8;--bs-table-hover-color: #000;color:#000;border-color:#e4cec2}.table-danger{--bs-table-bg: #f7dddc;--bs-table-striped-bg: #ebd2d1;--bs-table-striped-color: #000;--bs-table-active-bg: #dec7c6;--bs-table-active-color: #000;--bs-table-hover-bg: #e4cccc;--bs-table-hover-color: #000;color:#000;border-color:#dec7c6}.table-light{--bs-table-bg: #f8f5f0;--bs-table-striped-bg: #ece9e4;--bs-table-striped-color: #000;--bs-table-active-bg: #dfddd8;--bs-table-active-color: #000;--bs-table-hover-bg: #e5e3de;--bs-table-hover-color: #000;color:#000;border-color:#dfddd8}.table-dark{--bs-table-bg: #3e3f3a;--bs-table-striped-bg: #484944;--bs-table-striped-color: #fff;--bs-table-active-bg: #51524e;--bs-table-active-color: #fff;--bs-table-hover-bg: #4c4d49;--bs-table-hover-color: #fff;color:#fff;border-color:#51524e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label,.shiny-input-container .control-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(0.375rem + 1px);padding-bottom:calc(0.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + 1px);padding-bottom:calc(0.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(0.25rem + 1px);padding-bottom:calc(0.25rem + 1px);font-size:0.875rem}.form-text{margin-top:.25rem;font-size:0.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#000;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#000;background-color:#fff;border-color:#9cc0fa;outline:0;box-shadow:0 0 0 .25rem rgba(57,128,245,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#f8f5f0;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#000;background-color:#f8f5f0;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#ece9e4}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#000;background-color:#f8f5f0;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::-webkit-file-upload-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#ece9e4}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#000;background-color:rgba(0,0,0,0);border:solid rgba(0,0,0,0);border-width:1px 0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px);padding:.25rem .5rem;font-size:0.875rem;border-radius:.2em}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 0.75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#000;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%233e3f3a' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:#9cc0fa;outline:0;box-shadow:0 0 0 .25rem rgba(57,128,245,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#f8f5f0}.form-select:-moz-focusring{color:rgba(0,0,0,0);text-shadow:0 0 0 #000}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.875rem;border-radius:.2em}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:.3rem}.form-check,.shiny-input-container .checkbox,.shiny-input-container .radio{display:block;min-height:1.5rem;padding-left:0;margin-bottom:.125rem}.form-check .form-check-input,.form-check .shiny-input-container .checkbox input,.form-check .shiny-input-container .radio input,.shiny-input-container .checkbox .form-check-input,.shiny-input-container .checkbox .shiny-input-container .checkbox input,.shiny-input-container .checkbox .shiny-input-container .radio input,.shiny-input-container .radio .form-check-input,.shiny-input-container .radio .shiny-input-container .checkbox input,.shiny-input-container .radio .shiny-input-container .radio input{float:left;margin-left:0}.form-check-input,.shiny-input-container .checkbox input,.shiny-input-container .checkbox-inline input,.shiny-input-container .radio input,.shiny-input-container .radio-inline input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;color-adjust:exact;-webkit-print-color-adjust:exact}.form-check-input[type=checkbox],.shiny-input-container .checkbox input[type=checkbox],.shiny-input-container .checkbox-inline input[type=checkbox],.shiny-input-container .radio input[type=checkbox],.shiny-input-container .radio-inline input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio],.shiny-input-container .checkbox input[type=radio],.shiny-input-container .checkbox-inline input[type=radio],.shiny-input-container .radio input[type=radio],.shiny-input-container .radio-inline input[type=radio]{border-radius:50%}.form-check-input:active,.shiny-input-container .checkbox input:active,.shiny-input-container .checkbox-inline input:active,.shiny-input-container .radio input:active,.shiny-input-container .radio-inline input:active{filter:brightness(90%)}.form-check-input:focus,.shiny-input-container .checkbox input:focus,.shiny-input-container .checkbox-inline input:focus,.shiny-input-container .radio input:focus,.shiny-input-container .radio-inline input:focus{border-color:#9cc0fa;outline:0;box-shadow:0 0 0 .25rem rgba(57,128,245,.25)}.form-check-input:checked,.shiny-input-container .checkbox input:checked,.shiny-input-container .checkbox-inline input:checked,.shiny-input-container .radio input:checked,.shiny-input-container .radio-inline input:checked{background-color:#3980f5;border-color:#3980f5}.form-check-input:checked[type=checkbox],.shiny-input-container .checkbox input:checked[type=checkbox],.shiny-input-container .checkbox-inline input:checked[type=checkbox],.shiny-input-container .radio input:checked[type=checkbox],.shiny-input-container .radio-inline input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio],.shiny-input-container .checkbox input:checked[type=radio],.shiny-input-container .checkbox-inline input:checked[type=radio],.shiny-input-container .radio input:checked[type=radio],.shiny-input-container .radio-inline input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate,.shiny-input-container .checkbox input[type=checkbox]:indeterminate,.shiny-input-container .checkbox-inline input[type=checkbox]:indeterminate,.shiny-input-container .radio input[type=checkbox]:indeterminate,.shiny-input-container .radio-inline input[type=checkbox]:indeterminate{background-color:#3980f5;border-color:#3980f5;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled,.shiny-input-container .checkbox input:disabled,.shiny-input-container .checkbox-inline input:disabled,.shiny-input-container .radio input:disabled,.shiny-input-container .radio-inline input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input[disabled]~span,.form-check-input:disabled~.form-check-label,.form-check-input:disabled~span,.shiny-input-container .checkbox input[disabled]~.form-check-label,.shiny-input-container .checkbox input[disabled]~span,.shiny-input-container .checkbox input:disabled~.form-check-label,.shiny-input-container .checkbox input:disabled~span,.shiny-input-container .checkbox-inline input[disabled]~.form-check-label,.shiny-input-container .checkbox-inline input[disabled]~span,.shiny-input-container .checkbox-inline input:disabled~.form-check-label,.shiny-input-container .checkbox-inline input:disabled~span,.shiny-input-container .radio input[disabled]~.form-check-label,.shiny-input-container .radio input[disabled]~span,.shiny-input-container .radio input:disabled~.form-check-label,.shiny-input-container .radio input:disabled~span,.shiny-input-container .radio-inline input[disabled]~.form-check-label,.shiny-input-container .radio-inline input[disabled]~span,.shiny-input-container .radio-inline input:disabled~.form-check-label,.shiny-input-container .radio-inline input:disabled~span{opacity:.5}.form-check-label,.shiny-input-container .checkbox label,.shiny-input-container .checkbox-inline label,.shiny-input-container .radio label,.shiny-input-container .radio-inline label{cursor:pointer}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%239cc0fa'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline,.shiny-input-container .checkbox-inline,.shiny-input-container .radio-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:rgba(0,0,0,0);appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(57,128,245,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(57,128,245,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;background-color:#3980f5;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#c4d9fc}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#dee2e6;border-color:rgba(0,0,0,0);border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#3980f5;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#c4d9fc}.form-range::-moz-range-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#dee2e6;border-color:rgba(0,0,0,0);border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media(prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::placeholder{color:rgba(0,0,0,0)}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.input-group{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:stretch;-webkit-align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#000;text-align:center;white-space:nowrap;background-color:#f8f5f0;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem;border-radius:.2em}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#93c54b}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(147,197,75,.9);border-radius:.25rem}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:#93c54b;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2393c54b' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#93c54b;box-shadow:0 0 0 .25rem rgba(147,197,75,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:#93c54b}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%233e3f3a' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2393c54b' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:#93c54b;box-shadow:0 0 0 .25rem rgba(147,197,75,.25)}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:#93c54b}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#93c54b}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(147,197,75,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#93c54b}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group .form-control:valid,.input-group .form-control.is-valid,.was-validated .input-group .form-select:valid,.input-group .form-select.is-valid{z-index:1}.was-validated .input-group .form-control:valid:focus,.input-group .form-control.is-valid:focus,.was-validated .input-group .form-select:valid:focus,.input-group .form-select.is-valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#d9534f}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(217,83,79,.9);border-radius:.25rem}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#d9534f;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23d9534f'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23d9534f' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#d9534f;box-shadow:0 0 0 .25rem rgba(217,83,79,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:#d9534f}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%233e3f3a' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23d9534f'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23d9534f' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:#d9534f;box-shadow:0 0 0 .25rem rgba(217,83,79,.25)}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:#d9534f}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#d9534f}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(217,83,79,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#d9534f}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group .form-control:invalid,.input-group .form-control.is-invalid,.was-validated .input-group .form-select:invalid,.input-group .form-select.is-invalid{z-index:2}.was-validated .input-group .form-control:invalid:focus,.input-group .form-control.is-invalid:focus,.was-validated .input-group .form-select:invalid:focus,.input-group .form-select.is-invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#000;text-align:center;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;vertical-align:middle;cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:#000}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(57,128,245,.25)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-default{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-default:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-default,.btn-default:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:checked+.btn-default,.btn-check:active+.btn-default,.btn-default:active,.btn-default.active,.show>.btn-default.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:checked+.btn-default:focus,.btn-check:active+.btn-default:focus,.btn-default:active:focus,.btn-default.active:focus,.show>.btn-default.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-default:disabled,.btn-default.disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-primary{color:#fff;background-color:#3980f5;border-color:#3980f5}.btn-primary:hover{color:#fff;background-color:#306dd0;border-color:#2e66c4}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#306dd0;border-color:#2e66c4;box-shadow:0 0 0 .25rem rgba(87,147,247,.5)}.btn-check:checked+.btn-primary,.btn-check:active+.btn-primary,.btn-primary:active,.btn-primary.active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#2e66c4;border-color:#2b60b8}.btn-check:checked+.btn-primary:focus,.btn-check:active+.btn-primary:focus,.btn-primary:active:focus,.btn-primary.active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(87,147,247,.5)}.btn-primary:disabled,.btn-primary.disabled{color:#fff;background-color:#3980f5;border-color:#3980f5}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:checked+.btn-secondary,.btn-check:active+.btn-secondary,.btn-secondary:active,.btn-secondary.active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:checked+.btn-secondary:focus,.btn-check:active+.btn-secondary:focus,.btn-secondary:active:focus,.btn-secondary.active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary:disabled,.btn-secondary.disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#93c54b;border-color:#93c54b}.btn-success:hover{color:#fff;background-color:#7da740;border-color:#769e3c}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#7da740;border-color:#769e3c;box-shadow:0 0 0 .25rem rgba(163,206,102,.5)}.btn-check:checked+.btn-success,.btn-check:active+.btn-success,.btn-success:active,.btn-success.active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#769e3c;border-color:#6e9438}.btn-check:checked+.btn-success:focus,.btn-check:active+.btn-success:focus,.btn-success:active:focus,.btn-success.active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(163,206,102,.5)}.btn-success:disabled,.btn-success.disabled{color:#fff;background-color:#93c54b;border-color:#93c54b}.btn-info{color:#fff;background-color:#29abe0;border-color:#29abe0}.btn-info:hover{color:#fff;background-color:#2391be;border-color:#2189b3}.btn-check:focus+.btn-info,.btn-info:focus{color:#fff;background-color:#2391be;border-color:#2189b3;box-shadow:0 0 0 .25rem rgba(73,184,229,.5)}.btn-check:checked+.btn-info,.btn-check:active+.btn-info,.btn-info:active,.btn-info.active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#2189b3;border-color:#1f80a8}.btn-check:checked+.btn-info:focus,.btn-check:active+.btn-info:focus,.btn-info:active:focus,.btn-info.active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(73,184,229,.5)}.btn-info:disabled,.btn-info.disabled{color:#fff;background-color:#29abe0;border-color:#29abe0}.btn-warning{color:#fff;background-color:#f47c3c;border-color:#f47c3c}.btn-warning:hover{color:#fff;background-color:#cf6933;border-color:#c36330}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#fff;background-color:#cf6933;border-color:#c36330;box-shadow:0 0 0 .25rem rgba(246,144,89,.5)}.btn-check:checked+.btn-warning,.btn-check:active+.btn-warning,.btn-warning:active,.btn-warning.active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#c36330;border-color:#b75d2d}.btn-check:checked+.btn-warning:focus,.btn-check:active+.btn-warning:focus,.btn-warning:active:focus,.btn-warning.active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(246,144,89,.5)}.btn-warning:disabled,.btn-warning.disabled{color:#fff;background-color:#f47c3c;border-color:#f47c3c}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-danger:hover{color:#fff;background-color:#b84743;border-color:#ae423f}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#b84743;border-color:#ae423f;box-shadow:0 0 0 .25rem rgba(223,109,105,.5)}.btn-check:checked+.btn-danger,.btn-check:active+.btn-danger,.btn-danger:active,.btn-danger.active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#ae423f;border-color:#a33e3b}.btn-check:checked+.btn-danger:focus,.btn-check:active+.btn-danger:focus,.btn-danger:active:focus,.btn-danger.active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(223,109,105,.5)}.btn-danger:disabled,.btn-danger.disabled{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-light{color:#000;background-color:#f8f5f0;border-color:#f8f5f0}.btn-light:hover{color:#000;background-color:#f9f7f2;border-color:#f9f6f2}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9f7f2;border-color:#f9f6f2;box-shadow:0 0 0 .25rem rgba(211,208,204,.5)}.btn-check:checked+.btn-light,.btn-check:active+.btn-light,.btn-light:active,.btn-light.active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9f7f3;border-color:#f9f6f2}.btn-check:checked+.btn-light:focus,.btn-check:active+.btn-light:focus,.btn-light:active:focus,.btn-light.active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,208,204,.5)}.btn-light:disabled,.btn-light.disabled{color:#000;background-color:#f8f5f0;border-color:#f8f5f0}.btn-dark{color:#fff;background-color:#3e3f3a;border-color:#3e3f3a}.btn-dark:hover{color:#fff;background-color:#353631;border-color:#32322e}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#353631;border-color:#32322e;box-shadow:0 0 0 .25rem rgba(91,92,88,.5)}.btn-check:checked+.btn-dark,.btn-check:active+.btn-dark,.btn-dark:active,.btn-dark.active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#32322e;border-color:#2f2f2c}.btn-check:checked+.btn-dark:focus,.btn-check:active+.btn-dark:focus,.btn-dark:active:focus,.btn-dark.active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(91,92,88,.5)}.btn-dark:disabled,.btn-dark.disabled{color:#fff;background-color:#3e3f3a;border-color:#3e3f3a}.btn-outline-default{color:#6c757d;border-color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-default:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-default,.btn-outline-default:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:checked+.btn-outline-default,.btn-check:active+.btn-outline-default,.btn-outline-default:active,.btn-outline-default.active,.btn-outline-default.dropdown-toggle.show{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:checked+.btn-outline-default:focus,.btn-check:active+.btn-outline-default:focus,.btn-outline-default:active:focus,.btn-outline-default.active:focus,.btn-outline-default.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-default:disabled,.btn-outline-default.disabled{color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-primary{color:#3980f5;border-color:#3980f5;background-color:rgba(0,0,0,0)}.btn-outline-primary:hover{color:#fff;background-color:#3980f5;border-color:#3980f5}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(57,128,245,.5)}.btn-check:checked+.btn-outline-primary,.btn-check:active+.btn-outline-primary,.btn-outline-primary:active,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show{color:#fff;background-color:#3980f5;border-color:#3980f5}.btn-check:checked+.btn-outline-primary:focus,.btn-check:active+.btn-outline-primary:focus,.btn-outline-primary:active:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(57,128,245,.5)}.btn-outline-primary:disabled,.btn-outline-primary.disabled{color:#3980f5;background-color:rgba(0,0,0,0)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:checked+.btn-outline-secondary,.btn-check:active+.btn-outline-secondary,.btn-outline-secondary:active,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:checked+.btn-outline-secondary:focus,.btn-check:active+.btn-outline-secondary:focus,.btn-outline-secondary:active:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary:disabled,.btn-outline-secondary.disabled{color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-success{color:#93c54b;border-color:#93c54b;background-color:rgba(0,0,0,0)}.btn-outline-success:hover{color:#fff;background-color:#93c54b;border-color:#93c54b}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(147,197,75,.5)}.btn-check:checked+.btn-outline-success,.btn-check:active+.btn-outline-success,.btn-outline-success:active,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show{color:#fff;background-color:#93c54b;border-color:#93c54b}.btn-check:checked+.btn-outline-success:focus,.btn-check:active+.btn-outline-success:focus,.btn-outline-success:active:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(147,197,75,.5)}.btn-outline-success:disabled,.btn-outline-success.disabled{color:#93c54b;background-color:rgba(0,0,0,0)}.btn-outline-info{color:#29abe0;border-color:#29abe0;background-color:rgba(0,0,0,0)}.btn-outline-info:hover{color:#fff;background-color:#29abe0;border-color:#29abe0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(41,171,224,.5)}.btn-check:checked+.btn-outline-info,.btn-check:active+.btn-outline-info,.btn-outline-info:active,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show{color:#fff;background-color:#29abe0;border-color:#29abe0}.btn-check:checked+.btn-outline-info:focus,.btn-check:active+.btn-outline-info:focus,.btn-outline-info:active:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(41,171,224,.5)}.btn-outline-info:disabled,.btn-outline-info.disabled{color:#29abe0;background-color:rgba(0,0,0,0)}.btn-outline-warning{color:#f47c3c;border-color:#f47c3c;background-color:rgba(0,0,0,0)}.btn-outline-warning:hover{color:#fff;background-color:#f47c3c;border-color:#f47c3c}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(244,124,60,.5)}.btn-check:checked+.btn-outline-warning,.btn-check:active+.btn-outline-warning,.btn-outline-warning:active,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show{color:#fff;background-color:#f47c3c;border-color:#f47c3c}.btn-check:checked+.btn-outline-warning:focus,.btn-check:active+.btn-outline-warning:focus,.btn-outline-warning:active:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(244,124,60,.5)}.btn-outline-warning:disabled,.btn-outline-warning.disabled{color:#f47c3c;background-color:rgba(0,0,0,0)}.btn-outline-danger{color:#d9534f;border-color:#d9534f;background-color:rgba(0,0,0,0)}.btn-outline-danger:hover{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(217,83,79,.5)}.btn-check:checked+.btn-outline-danger,.btn-check:active+.btn-outline-danger,.btn-outline-danger:active,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-check:checked+.btn-outline-danger:focus,.btn-check:active+.btn-outline-danger:focus,.btn-outline-danger:active:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(217,83,79,.5)}.btn-outline-danger:disabled,.btn-outline-danger.disabled{color:#d9534f;background-color:rgba(0,0,0,0)}.btn-outline-light{color:#f8f5f0;border-color:#f8f5f0;background-color:rgba(0,0,0,0)}.btn-outline-light:hover{color:#000;background-color:#f8f5f0;border-color:#f8f5f0}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,245,240,.5)}.btn-check:checked+.btn-outline-light,.btn-check:active+.btn-outline-light,.btn-outline-light:active,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show{color:#000;background-color:#f8f5f0;border-color:#f8f5f0}.btn-check:checked+.btn-outline-light:focus,.btn-check:active+.btn-outline-light:focus,.btn-outline-light:active:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(248,245,240,.5)}.btn-outline-light:disabled,.btn-outline-light.disabled{color:#f8f5f0;background-color:rgba(0,0,0,0)}.btn-outline-dark{color:#3e3f3a;border-color:#3e3f3a;background-color:rgba(0,0,0,0)}.btn-outline-dark:hover{color:#fff;background-color:#3e3f3a;border-color:#3e3f3a}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(62,63,58,.5)}.btn-check:checked+.btn-outline-dark,.btn-check:active+.btn-outline-dark,.btn-outline-dark:active,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show{color:#fff;background-color:#3e3f3a;border-color:#3e3f3a}.btn-check:checked+.btn-outline-dark:focus,.btn-check:active+.btn-outline-dark:focus,.btn-outline-dark:active:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(62,63,58,.5)}.btn-outline-dark:disabled,.btn-outline-dark.disabled{color:#3e3f3a;background-color:rgba(0,0,0,0)}.btn-link{font-weight:400;color:#2b8cee;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}.btn-link:hover{color:#2270be}.btn-link:disabled,.btn-link.disabled{color:#6c757d}.btn-lg,.btn-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-sm,.btn-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem;border-radius:.2em}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .2s ease}@media(prefers-reduced-motion: reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media(prefers-reduced-motion: reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid rgba(0,0,0,0);border-bottom:0;border-left:.3em solid rgba(0,0,0,0)}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#000;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media(min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid rgba(0,0,0,0);border-bottom:.3em solid;border-left:.3em solid rgba(0,0,0,0)}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:0;border-bottom:.3em solid rgba(0,0,0,0);border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:.3em solid;border-bottom:.3em solid rgba(0,0,0,0)}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#6c757d;text-align:inherit;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap;background-color:rgba(0,0,0,0);border:0}.dropdown-item:hover,.dropdown-item:focus{color:#6c757d;background-color:#f8f5f0}.dropdown-item.active,.dropdown-item:active{color:#6c757d;text-decoration:none;background-color:#f8f5f0}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:rgba(0,0,0,0)}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:0.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#6c757d}.dropdown-menu-dark{color:#dee2e6;background-color:#3e3f3a;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:hover,.dropdown-menu-dark .dropdown-item:focus{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#6c757d;background-color:#f8f5f0}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;justify-content:flex-start;-webkit-justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;-webkit-flex-direction:column;align-items:flex-start;-webkit-align-items:flex-start;justify-content:center;-webkit-justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn~.btn,.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem .9rem;color:#2b8cee;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media(prefers-reduced-motion: reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:#2270be}.nav-link.disabled{color:#dee2e6;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:none;border:1px solid rgba(0,0,0,0);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{border-color:#dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#dee2e6;background-color:rgba(0,0,0,0);border-color:rgba(0,0,0,0)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:none;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#6c757d;background-color:#f8f5f0}.nav-fill>.nav-link,.nav-fill .nav-item{flex:1 1 auto;-webkit-flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.nav-justified .nav-item{flex-basis:0;-webkit-flex-basis:0;flex-grow:1;-webkit-flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container-xxl,.navbar>.container-xl,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container,.navbar>.container-fluid{display:flex;display:-webkit-flex;flex-wrap:inherit;-webkit-flex-wrap:inherit;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;-webkit-flex-basis:100%;flex-grow:1;-webkit-flex-grow:1;align-items:center;-webkit-align-items:center}.navbar-toggler{padding:.25 0;font-size:1.25rem;line-height:1;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media(min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-top,.navbar-expand-sm .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-top,.navbar-expand-md .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-top,.navbar-expand-lg .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-top,.navbar-expand-xl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-top,.navbar-expand-xxl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-top,.navbar-expand .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}.navbar-light{background-color:#3980f5}.navbar-light .navbar-brand{color:#fff}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:#fdfeff}.navbar-light .navbar-nav .nav-link{color:#fff}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .nav-link:focus{color:rgba(253,254,255,.8)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.75)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .nav-link.active{color:#fdfeff}.navbar-light .navbar-toggler{color:#fff;border-color:rgba(255,255,255,0)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='white' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:#fff}.navbar-light .navbar-text a,.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:#fdfeff}.navbar-dark{background-color:#3980f5}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#fdfeff}.navbar-dark .navbar-nav .nav-link{color:#fff}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:focus{color:rgba(253,254,255,.8)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active{color:#fdfeff}.navbar-dark .navbar-toggler{color:#fff;border-color:rgba(255,255,255,0)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='white' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:#fff}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#fdfeff}.card{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(222,226,230,.75);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-0.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:#adb5bd;border-bottom:1px solid rgba(222,226,230,.75)}.card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:#adb5bd;border-top:1px solid rgba(222,226,230,.75)}.card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media(min-width: 576px){.card-group{display:flex;display:-webkit-flex;flex-flow:row wrap;-webkit-flex-flow:row wrap}.card-group>.card{flex:1 0 0%;-webkit-flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#000;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media(prefers-reduced-motion: reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#3373dd;background-color:#ebf2fe;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%233373dd'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;-webkit-flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='black'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media(prefers-reduced-motion: reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#9cc0fa;outline:0;box-shadow:0 0 0 .25rem rgba(57,128,245,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding:.375rem .75rem;margin-bottom:1rem;list-style:none;background-color:#f8f5f0;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, ">") /* rtl: var(--bs-breadcrumb-divider, ">") */}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;display:-webkit-flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#6c757d;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#f8f5f0;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#6c757d;background-color:#f8f5f0;border-color:#dee2e6}.page-link:focus{z-index:3;color:#2270be;background-color:#f8f5f0;outline:0;box-shadow:0 0 0 .25rem rgba(57,128,245,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#6c757d;background-color:#dee2e6;border-color:#dee2e6}.page-item.disabled .page-link{color:#dee2e6;pointer-events:none;background-color:#f8f5f0;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:0.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2em;border-bottom-left-radius:.2em}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2em;border-bottom-right-radius:.2em}.badge{display:inline-block;padding:.35em .65em;font-size:0.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid rgba(0,0,0,0);border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-default{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-default .alert-link{color:#34383c}.alert-primary{color:#224d93;background-color:#d7e6fd;border-color:#c4d9fc}.alert-primary .alert-link{color:#1b3e76}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#58762d;background-color:#e9f3db;border-color:#dfeec9}.alert-success .alert-link{color:#465e24}.alert-info{color:#196786;background-color:#d4eef9;border-color:#bfe6f6}.alert-info .alert-link{color:#14526b}.alert-warning{color:#924a24;background-color:#fde5d8;border-color:#fcd8c5}.alert-warning .alert-link{color:#753b1d}.alert-danger{color:#82322f;background-color:#f7dddc;border-color:#f4cbca}.alert-danger .alert-link{color:#682826}.alert-light{color:#959390;background-color:#fefdfc;border-color:#fdfcfb}.alert-light .alert-link{color:#777673}.alert-dark{color:#252623;background-color:#d8d9d8;border-color:#c5c5c4}.alert-dark .alert-link{color:#1e1e1c}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;display:-webkit-flex;height:1rem;overflow:hidden;font-size:0.75rem;background-color:#dee2e6;border-radius:10px}.progress-bar{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;justify-content:center;-webkit-justify-content:center;overflow:hidden;color:#3980f5;text-align:center;white-space:nowrap;background-color:#3980f5;transition:width .6s ease}@media(prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-size:1rem 1rem}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media(prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.list-group{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#000;text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:#000;text-decoration:none;background-color:#f8f5f0}.list-group-item-action:active{color:#000;background-color:#dee2e6}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid #dee2e6}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#adb5bd;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#000;background-color:#f8f5f0;border-color:#dee2e6}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media(min-width: 576px){.list-group-horizontal-sm{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 768px){.list-group-horizontal-md{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 992px){.list-group-horizontal-lg{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1200px){.list-group-horizontal-xl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-default{color:#41464b;background-color:#e2e3e5}.list-group-item-default.list-group-item-action:hover,.list-group-item-default.list-group-item-action:focus{color:#41464b;background-color:#cbccce}.list-group-item-default.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-primary{color:#224d93;background-color:#d7e6fd}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#224d93;background-color:#c2cfe4}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#224d93;border-color:#224d93}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#58762d;background-color:#e9f3db}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#58762d;background-color:#d2dbc5}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#58762d;border-color:#58762d}.list-group-item-info{color:#196786;background-color:#d4eef9}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#196786;background-color:#bfd6e0}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#196786;border-color:#196786}.list-group-item-warning{color:#924a24;background-color:#fde5d8}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#924a24;background-color:#e4cec2}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#924a24;border-color:#924a24}.list-group-item-danger{color:#82322f;background-color:#f7dddc}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#82322f;background-color:#dec7c6}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#82322f;border-color:#82322f}.list-group-item-light{color:#959390;background-color:#fefdfc}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#959390;background-color:#e5e4e3}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#959390;border-color:#959390}.list-group-item-dark{color:#252623;background-color:#d8d9d8}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#252623;background-color:#c2c3c2}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#252623;border-color:#252623}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#fff;background:rgba(0,0,0,0) url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.8}.btn-close:hover{color:#fff;text-decoration:none;opacity:1}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(57,128,245,.25);opacity:1}.btn-close:disabled,.btn-close.disabled{pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:0.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:max-content;width:-webkit-max-content;width:-moz-max-content;width:-ms-max-content;width:-o-max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.toast-header .btn-close{margin-right:-0.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0, -50px)}@media(prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid #dee2e6;border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-0.5rem -0.5rem -0.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem}.modal-footer{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:flex-end;-webkit-justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(0.3rem - 1px);border-bottom-left-radius:calc(0.3rem - 1px)}.modal-footer>*{margin:.25rem}@media(min-width: 576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media(min-width: 992px){.modal-lg,.modal-xl{max-width:800px}}@media(min-width: 1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media(max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media(max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media(max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media(max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media(max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:"Open Sans",sans-serif;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[data-popper-placement^=top]{padding:.4rem 0}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:0}.bs-tooltip-top .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-end,.bs-tooltip-auto[data-popper-placement^=right]{padding:0 .4rem}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-end .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-bottom,.bs-tooltip-auto[data-popper-placement^=bottom]{padding:.4rem 0}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:0}.bs-tooltip-bottom .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-start,.bs-tooltip-auto[data-popper-placement^=left]{padding:0 .4rem}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-start .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0 /* rtl:ignore */;z-index:1070;display:block;max-width:276px;font-family:"Open Sans",sans-serif;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-0.5rem - 1px)}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-0.5rem - 1px)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-0.5rem;content:"";border-bottom:1px solid #f8f5f0}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f8f5f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#000}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y;-webkit-touch-action:pan-y;-moz-touch-action:pan-y;-ms-touch-action:pan-y;-o-touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media(prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:center;-webkit-justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;display:-webkit-flex;justify-content:center;-webkit-justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;-webkit-flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid rgba(0,0,0,0);border-bottom:10px solid rgba(0,0,0,0);opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;border:.25em solid currentColor;border-right-color:rgba(0,0,0,0);border-radius:50%;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;background-color:currentColor;border-radius:50%;opacity:0;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media(prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{animation-duration:1.5s;-webkit-animation-duration:1.5s;-moz-animation-duration:1.5s;-ms-animation-duration:1.5s;-o-animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media(prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-0.5rem;margin-right:-0.5rem;margin-bottom:-0.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;-webkit-flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid #dee2e6;transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid #dee2e6;transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid #dee2e6;transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid #dee2e6;transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);-webkit-mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);mask-size:200% 100%;-webkit-mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{mask-position:-200% 0%;-webkit-mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-default{color:#6c757d}.link-default:hover,.link-default:focus{color:#565e64}.link-primary{color:#3980f5}.link-primary:hover,.link-primary:focus{color:#2e66c4}.link-secondary{color:#6c757d}.link-secondary:hover,.link-secondary:focus{color:#565e64}.link-success{color:#93c54b}.link-success:hover,.link-success:focus{color:#769e3c}.link-info{color:#29abe0}.link-info:hover,.link-info:focus{color:#2189b3}.link-warning{color:#f47c3c}.link-warning:hover,.link-warning:focus{color:#c36330}.link-danger{color:#d9534f}.link-danger:hover,.link-danger:focus{color:#ae423f}.link-light{color:#f8f5f0}.link-light:hover,.link-light:focus{color:#f9f7f3}.link-dark{color:#3e3f3a}.link-dark:hover,.link-dark:focus{color:#32322e}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: 75%}.ratio-16x9{--bs-aspect-ratio: 56.25%}.ratio-21x9{--bs-aspect-ratio: 42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}@media(min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}}@media(min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}}@media(min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}}.hstack{display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;align-items:center;-webkit-align-items:center;align-self:stretch;-webkit-align-self:stretch}.vstack{display:flex;display:-webkit-flex;flex:1 1 auto;-webkit-flex:1 1 auto;flex-direction:column;-webkit-flex-direction:column;align-self:stretch;-webkit-align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;-webkit-align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.opacity-0{opacity:0 !important}.opacity-25{opacity:.25 !important}.opacity-50{opacity:.5 !important}.opacity-75{opacity:.75 !important}.opacity-100{opacity:1 !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:1px solid #dee2e6 !important}.border-0{border:0 !important}.border-top{border-top:1px solid #dee2e6 !important}.border-top-0{border-top:0 !important}.border-end{border-right:1px solid #dee2e6 !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:1px solid #dee2e6 !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:1px solid #dee2e6 !important}.border-start-0{border-left:0 !important}.border-default{border-color:#6c757d !important}.border-primary{border-color:#3980f5 !important}.border-secondary{border-color:#6c757d !important}.border-success{border-color:#93c54b !important}.border-info{border-color:#29abe0 !important}.border-warning{border-color:#f47c3c !important}.border-danger{border-color:#d9534f !important}.border-light{border-color:#f8f5f0 !important}.border-dark{border-color:#3e3f3a !important}.border-white{border-color:#fff !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.325rem + 0.9vw) !important}.fs-2{font-size:calc(1.29rem + 0.48vw) !important}.fs-3{font-size:calc(1.27rem + 0.24vw) !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1.1rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-light{font-weight:300 !important}.fw-lighter{font-weight:lighter !important}.fw-normal{font-weight:400 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.5 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-default{--bs-text-opacity: 1;color:rgba(var(--bs-default-rgb), var(--bs-text-opacity)) !important}.text-primary{--bs-text-opacity: 1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important}.text-secondary{--bs-text-opacity: 1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important}.text-success{--bs-text-opacity: 1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important}.text-info{--bs-text-opacity: 1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important}.text-warning{--bs-text-opacity: 1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important}.text-danger{--bs-text-opacity: 1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important}.text-light{--bs-text-opacity: 1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important}.text-dark{--bs-text-opacity: 1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important}.text-black{--bs-text-opacity: 1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important}.text-white{--bs-text-opacity: 1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important}.text-body{--bs-text-opacity: 1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important}.text-muted{--bs-text-opacity: 1;color:#6c757d !important}.text-black-50{--bs-text-opacity: 1;color:rgba(0,0,0,.5) !important}.text-white-50{--bs-text-opacity: 1;color:rgba(255,255,255,.5) !important}.text-reset{--bs-text-opacity: 1;color:inherit !important}.text-opacity-25{--bs-text-opacity: 0.25}.text-opacity-50{--bs-text-opacity: 0.5}.text-opacity-75{--bs-text-opacity: 0.75}.text-opacity-100{--bs-text-opacity: 1}.bg-default{--bs-bg-opacity: 1;background-color:rgba(var(--bs-default-rgb), var(--bs-bg-opacity)) !important}.bg-primary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important}.bg-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important}.bg-success{--bs-bg-opacity: 1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important}.bg-info{--bs-bg-opacity: 1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important}.bg-warning{--bs-bg-opacity: 1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important}.bg-danger{--bs-bg-opacity: 1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important}.bg-light{--bs-bg-opacity: 1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important}.bg-dark{--bs-bg-opacity: 1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important}.bg-black{--bs-bg-opacity: 1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important}.bg-white{--bs-bg-opacity: 1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important}.bg-body{--bs-bg-opacity: 1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important}.bg-transparent{--bs-bg-opacity: 1;background-color:rgba(0,0,0,0) !important}.bg-opacity-10{--bs-bg-opacity: 0.1}.bg-opacity-25{--bs-bg-opacity: 0.25}.bg-opacity-50{--bs-bg-opacity: 0.5}.bg-opacity-75{--bs-bg-opacity: 0.75}.bg-opacity-100{--bs-bg-opacity: 1}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{user-select:all !important}.user-select-auto{user-select:auto !important}.user-select-none{user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:.2em !important}.rounded-2{border-radius:.25rem !important}.rounded-3{border-radius:.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-top{border-top-left-radius:.25rem !important;border-top-right-radius:.25rem !important}.rounded-end{border-top-right-radius:.25rem !important;border-bottom-right-radius:.25rem !important}.rounded-bottom{border-bottom-right-radius:.25rem !important;border-bottom-left-radius:.25rem !important}.rounded-start{border-bottom-left-radius:.25rem !important;border-top-left-radius:.25rem !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media(min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media(min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media(min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media(min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media(min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}.bg-default{color:#fff}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#fff}.bg-warning{color:#fff}.bg-danger{color:#fff}.bg-light{color:#000}.bg-dark{color:#fff}@media(min-width: 1200px){.fs-1{font-size:2rem !important}.fs-2{font-size:1.65rem !important}.fs-3{font-size:1.45rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}}.quarto-container{min-height:calc(100vh - 132px)}footer.footer .nav-footer,#quarto-header>nav{padding-left:1em;padding-right:1em}nav[role=doc-toc]{padding-left:.5em}#quarto-content>*{padding-top:14px}@media(max-width: 991.98px){#quarto-content>*{padding-top:0}#quarto-content .subtitle{padding-top:14px}#quarto-content section:first-of-type h2:first-of-type,#quarto-content section:first-of-type .h2:first-of-type{margin-top:1rem}}.headroom-target,header.headroom{will-change:transform;transition:position 200ms linear;transition:all 200ms linear}header.headroom--pinned{transform:translateY(0%)}header.headroom--unpinned{transform:translateY(-100%)}.navbar-container{width:100%}.navbar-brand{overflow:hidden;text-overflow:ellipsis}.navbar-brand-container{max-width:calc(100% - 115px);min-width:0;display:flex;align-items:center}@media(min-width: 992px){.navbar-brand-container{margin-right:1em}}.navbar-brand.navbar-brand-logo{margin-right:4px;display:inline-flex}.navbar-toggler{flex-basis:content;flex-shrink:0}.navbar .navbar-toggler{order:-1;margin-right:.5em}.navbar-logo{max-height:24px;width:auto;padding-right:4px}nav .nav-item:not(.compact){padding-top:1px}nav .nav-link i,nav .dropdown-item i{padding-right:1px}.navbar-expand-lg .navbar-nav .nav-link{padding-left:.6rem;padding-right:.6rem}nav .nav-item.compact .nav-link{padding-left:.5rem;padding-right:.5rem;font-size:1.1rem}.navbar .quarto-navbar-tools div.dropdown{display:inline-block}.navbar .quarto-navbar-tools .quarto-navigation-tool{color:#fff}.navbar .quarto-navbar-tools .quarto-navigation-tool:hover{color:#fdfeff}@media(max-width: 991.98px){.navbar .quarto-navbar-tools{margin-top:.25em;padding-top:.75em;display:block;color:solid gray 1px;text-align:center;vertical-align:middle;margin-right:auto}}.navbar-nav .dropdown-menu{min-width:220px;font-size:.9rem}.navbar .navbar-nav .nav-link.dropdown-toggle::after{opacity:.75;vertical-align:.175em}.navbar ul.dropdown-menu{padding-top:0;padding-bottom:0}.navbar .dropdown-header{text-transform:uppercase;font-size:.8rem;padding:0 .5rem}.navbar .dropdown-item{padding:.4rem .5rem}.navbar .dropdown-item>i.bi{margin-left:.1rem;margin-right:.25em}.sidebar #quarto-search{margin-top:-1px}.sidebar #quarto-search svg.aa-SubmitIcon{width:16px;height:16px}.sidebar-navigation a{color:inherit}.sidebar-title{margin-top:.25rem;padding-bottom:.5rem;font-size:1.3rem;line-height:1.6rem;visibility:visible}.sidebar-title>a{font-size:inherit;text-decoration:none}.sidebar-title .sidebar-tools-main{margin-top:-6px}@media(max-width: 991.98px){#quarto-sidebar div.sidebar-header{padding-top:.2em}}.sidebar-header-stacked .sidebar-title{margin-top:.6rem}.sidebar-logo{max-width:90%;padding-bottom:.5rem}.sidebar-logo-link{text-decoration:none}.sidebar-navigation li a{text-decoration:none}.sidebar-navigation .quarto-navigation-tool{opacity:.7;font-size:.875rem}#quarto-sidebar>nav>.sidebar-tools-main{margin-left:14px}.sidebar-tools-main{display:inline-flex;margin-left:0px;order:2}.sidebar-tools-main:not(.tools-wide){vertical-align:middle}.sidebar-navigation .quarto-navigation-tool.dropdown-toggle::after{display:none}.sidebar.sidebar-navigation>*{padding-top:1em}.sidebar-item{margin-bottom:.2em}.sidebar-section{margin-top:.2em;padding-left:.5em;padding-bottom:.2em}.sidebar-item .sidebar-item-container{display:flex;justify-content:space-between}.sidebar-item-toggle:hover{cursor:pointer}.sidebar-item .sidebar-item-toggle .bi{font-size:.7rem;text-align:center}.sidebar-item .sidebar-item-toggle .bi-chevron-right::before{transition:transform 200ms ease}.sidebar-item .sidebar-item-toggle[aria-expanded=false] .bi-chevron-right::before{transform:none}.sidebar-item .sidebar-item-toggle[aria-expanded=true] .bi-chevron-right::before{transform:rotate(90deg)}.sidebar-navigation .sidebar-divider{margin-left:0;margin-right:0;margin-top:.5rem;margin-bottom:.5rem}@media(max-width: 991.98px){.quarto-secondary-nav{display:block}.quarto-secondary-nav button.quarto-search-button{padding-right:0em;padding-left:2em}.quarto-secondary-nav button.quarto-btn-toggle{margin-left:-0.75rem;margin-right:.15rem}.quarto-secondary-nav nav.quarto-page-breadcrumbs{display:flex;align-items:center;padding-right:1em;margin-left:-0.25em}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{text-decoration:none}.quarto-secondary-nav nav.quarto-page-breadcrumbs ol.breadcrumb{margin-bottom:0}}@media(min-width: 992px){.quarto-secondary-nav{display:none}}.quarto-secondary-nav .quarto-btn-toggle{color:#fff}.quarto-secondary-nav[aria-expanded=false] .quarto-btn-toggle .bi-chevron-right::before{transform:none}.quarto-secondary-nav[aria-expanded=true] .quarto-btn-toggle .bi-chevron-right::before{transform:rotate(90deg)}.quarto-secondary-nav .quarto-btn-toggle .bi-chevron-right::before{transition:transform 200ms ease}.quarto-secondary-nav{cursor:pointer}.quarto-secondary-nav-title{margin-top:.3em;color:#fff;padding-top:4px}.quarto-secondary-nav nav.quarto-page-breadcrumbs{color:#fff}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{color:#fff}.quarto-secondary-nav nav.quarto-page-breadcrumbs a:hover{color:rgba(253,254,255,.8)}.quarto-secondary-nav nav.quarto-page-breadcrumbs .breadcrumb-item::before{color:#ccc}div.sidebar-item-container{color:#fff}div.sidebar-item-container:hover,div.sidebar-item-container:focus{color:rgba(253,254,255,.8)}div.sidebar-item-container.disabled{color:rgba(255,255,255,.75)}div.sidebar-item-container .active,div.sidebar-item-container .show>.nav-link,div.sidebar-item-container .sidebar-link>code{color:#fdfeff}div.sidebar.sidebar-navigation.rollup.quarto-sidebar-toggle-contents,nav.sidebar.sidebar-navigation:not(.rollup){background-color:#3980f5}@media(max-width: 991.98px){.sidebar-navigation .sidebar-item a,.nav-page .nav-page-text,.sidebar-navigation{font-size:1rem}.sidebar-navigation ul.sidebar-section.depth1 .sidebar-section-item{font-size:1.1rem}.sidebar-logo{display:none}.sidebar.sidebar-navigation{position:static;border-bottom:1px solid #dee2e6}.sidebar.sidebar-navigation.collapsing{position:fixed;z-index:1000}.sidebar.sidebar-navigation.show{position:fixed;z-index:1000}.sidebar.sidebar-navigation{min-height:100%}nav.quarto-secondary-nav{background-color:#3980f5;border-bottom:1px solid #dee2e6}.sidebar .sidebar-footer{visibility:visible;padding-top:1rem;position:inherit}.sidebar-tools-collapse{display:block}}#quarto-sidebar{transition:width .15s ease-in}#quarto-sidebar>*{padding-right:1em}@media(max-width: 991.98px){#quarto-sidebar .sidebar-menu-container{white-space:nowrap;min-width:225px}#quarto-sidebar.show{transition:width .15s ease-out}}@media(min-width: 992px){#quarto-sidebar{display:flex;flex-direction:column}.nav-page .nav-page-text,.sidebar-navigation .sidebar-section .sidebar-item{font-size:.875rem}.sidebar-navigation .sidebar-item{font-size:.925rem}.sidebar.sidebar-navigation{display:block;position:sticky}.sidebar-search{width:100%}.sidebar .sidebar-footer{visibility:visible}}@media(max-width: 991.98px){#quarto-sidebar-glass{position:fixed;top:0;bottom:0;left:0;right:0;background-color:rgba(255,255,255,0);transition:background-color .15s ease-in;z-index:-1}#quarto-sidebar-glass.collapsing{z-index:1000}#quarto-sidebar-glass.show{transition:background-color .15s ease-out;background-color:rgba(102,102,102,.4);z-index:1000}}.sidebar .sidebar-footer{padding:.5rem 1rem;align-self:flex-end;color:#6c757d;width:100%}.quarto-page-breadcrumbs .breadcrumb-item+.breadcrumb-item,.quarto-page-breadcrumbs .breadcrumb-item{padding-right:.33em;padding-left:0}.quarto-page-breadcrumbs .breadcrumb-item::before{padding-right:.33em}.quarto-sidebar-footer{font-size:.875em}.sidebar-section .bi-chevron-right{vertical-align:middle}.sidebar-section .bi-chevron-right::before{font-size:.9em}.notransition{-webkit-transition:none !important;-moz-transition:none !important;-o-transition:none !important;transition:none !important}.btn:focus:not(:focus-visible){box-shadow:none}.page-navigation{display:flex;justify-content:space-between}.nav-page{padding-bottom:.75em}.nav-page .bi{font-size:1.8rem;vertical-align:middle}.nav-page .nav-page-text{padding-left:.25em;padding-right:.25em}.nav-page a{color:#6c757d;text-decoration:none;display:flex;align-items:center}.nav-page a:hover{color:#2270be}.toc-actions{display:flex}.toc-actions p{margin-block-start:0;margin-block-end:0}.toc-actions a{text-decoration:none;color:inherit;font-weight:400}.toc-actions a:hover{color:#2270be}.toc-actions .action-links{margin-left:4px}.sidebar nav[role=doc-toc] .toc-actions .bi{margin-left:-4px;font-size:.7rem;color:#6c757d}.sidebar nav[role=doc-toc] .toc-actions .bi:before{padding-top:3px}#quarto-margin-sidebar .toc-actions .bi:before{margin-top:.3rem;font-size:.7rem;color:#6c757d;vertical-align:top}.sidebar nav[role=doc-toc] .toc-actions>div:first-of-type{margin-top:-3px}#quarto-margin-sidebar .toc-actions p,.sidebar nav[role=doc-toc] .toc-actions p{font-size:.875rem}.nav-footer .toc-actions{padding-bottom:.5em;padding-top:.5em}.nav-footer .toc-actions :first-child{margin-left:auto}.nav-footer .toc-actions :last-child{margin-right:auto}.nav-footer .toc-actions .action-links{display:flex}.nav-footer .toc-actions .action-links p{padding-right:1.5em}.nav-footer .toc-actions .action-links p:last-of-type{padding-right:0}.nav-footer{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:baseline;text-align:center;padding-top:.5rem;padding-bottom:.5rem;background-color:#fff}body.nav-fixed{padding-top:63px}.nav-footer-contents{color:#6c757d;margin-top:.25rem}.nav-footer{min-height:3.5em;color:#757575}.nav-footer a{color:#757575}.nav-footer .nav-footer-left{font-size:.825em}.nav-footer .nav-footer-center{font-size:.825em}.nav-footer .nav-footer-right{font-size:.825em}.nav-footer-left .footer-items,.nav-footer-center .footer-items,.nav-footer-right .footer-items{display:inline-flex;padding-top:.3em;padding-bottom:.3em;margin-bottom:0em}.nav-footer-left .footer-items .nav-link,.nav-footer-center .footer-items .nav-link,.nav-footer-right .footer-items .nav-link{padding-left:.6em;padding-right:.6em}.nav-footer-left{flex:1 1 0px;text-align:left}.nav-footer-right{flex:1 1 0px;text-align:right}.nav-footer-center{flex:1 1 0px;min-height:3em;text-align:center}.nav-footer-center .footer-items{justify-content:center}@media(max-width: 767.98px){.nav-footer-center{margin-top:3em}}.navbar .quarto-reader-toggle.reader .quarto-reader-toggle-btn{background-color:#fff;border-radius:3px}.quarto-reader-toggle.reader.quarto-navigation-tool .quarto-reader-toggle-btn{background-color:#fff;border-radius:3px}.quarto-reader-toggle .quarto-reader-toggle-btn{display:inline-flex;padding-left:.2em;padding-right:.2em;margin-left:-0.2em;margin-right:-0.2em;text-align:center}.navbar .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}#quarto-back-to-top{display:none;position:fixed;bottom:50px;background-color:#fff;border-radius:.25rem;box-shadow:0 .2rem .5rem #6c757d,0 0 .05rem #6c757d;color:#6c757d;text-decoration:none;font-size:.9em;text-align:center;left:50%;padding:.4rem .8rem;transform:translate(-50%, 0)}.aa-DetachedOverlay ul.aa-List,#quarto-search-results ul.aa-List{list-style:none;padding-left:0}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{background-color:#fff;position:absolute;z-index:2000}#quarto-search-results .aa-Panel{max-width:400px}#quarto-search input{font-size:.925rem}@media(min-width: 992px){.navbar #quarto-search{margin-left:.25rem;order:999}}@media(max-width: 991.98px){#quarto-sidebar .sidebar-search{display:none}}#quarto-sidebar .sidebar-search .aa-Autocomplete{width:100%}.navbar .aa-Autocomplete .aa-Form{width:180px}.navbar #quarto-search.type-overlay .aa-Autocomplete{width:40px}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form{background-color:inherit;border:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form:focus-within{box-shadow:none;outline:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper{display:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper:focus-within{display:inherit}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-Label svg,.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-LoadingIndicator svg{width:26px;height:26px;color:#fff;opacity:1}.navbar #quarto-search.type-overlay .aa-Autocomplete svg.aa-SubmitIcon{width:26px;height:26px;color:#fff;opacity:1}.aa-Autocomplete .aa-Form,.aa-DetachedFormContainer .aa-Form{align-items:center;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;color:#000;display:flex;line-height:1em;margin:0;position:relative;width:100%}.aa-Autocomplete .aa-Form:focus-within,.aa-DetachedFormContainer .aa-Form:focus-within{box-shadow:rgba(57,128,245,.6) 0 0 0 1px;outline:currentColor none medium}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix{align-items:center;display:flex;flex-shrink:0;order:1}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{cursor:initial;flex-shrink:0;padding:0;text-align:left}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg{color:#000;opacity:.5}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton{appearance:none;background:none;border:0;margin:0}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{align-items:center;display:flex;justify-content:center}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapper,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper{order:3;position:relative;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input{appearance:none;background:none;border:0;color:#000;font:inherit;height:calc(1.5em + .1rem + 2px);padding:0;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::placeholder,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::placeholder{color:#000;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input:focus{border-color:none;box-shadow:none;outline:none}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix{align-items:center;display:flex;order:4}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton{align-items:center;background:none;border:0;color:#000;opacity:.8;cursor:pointer;display:flex;margin:0;width:calc(1.5em + .1rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus{color:#000;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg{width:calc(1.5em + 0.75rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton{border:none;align-items:center;background:none;color:#000;opacity:.4;font-size:.7rem;cursor:pointer;display:none;margin:0;width:calc(1em + .1rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus{color:#000;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden]{display:none}.aa-PanelLayout:empty{display:none}.quarto-search-no-results.no-query{display:none}.aa-Source:has(.no-query){display:none}#quarto-search-results .aa-Panel{border:solid #ced4da 1px}#quarto-search-results .aa-SourceNoResults{width:398px}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{max-height:65vh;overflow-y:auto;font-size:.925rem}.aa-DetachedOverlay .aa-SourceNoResults,#quarto-search-results .aa-SourceNoResults{height:60px;display:flex;justify-content:center;align-items:center}.aa-DetachedOverlay .search-error,#quarto-search-results .search-error{padding-top:10px;padding-left:20px;padding-right:20px;cursor:default}.aa-DetachedOverlay .search-error .search-error-title,#quarto-search-results .search-error .search-error-title{font-size:1.1rem;margin-bottom:.5rem}.aa-DetachedOverlay .search-error .search-error-title .search-error-icon,#quarto-search-results .search-error .search-error-title .search-error-icon{margin-right:8px}.aa-DetachedOverlay .search-error .search-error-text,#quarto-search-results .search-error .search-error-text{font-weight:300}.aa-DetachedOverlay .search-result-text,#quarto-search-results .search-result-text{font-weight:300;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.2rem;max-height:2.4rem}.aa-DetachedOverlay .aa-SourceHeader .search-result-header,#quarto-search-results .aa-SourceHeader .search-result-header{font-size:.875rem;background-color:#f2f2f2;padding-left:14px;padding-bottom:4px;padding-top:4px}.aa-DetachedOverlay .aa-SourceHeader .search-result-header-no-results,#quarto-search-results .aa-SourceHeader .search-result-header-no-results{display:none}.aa-DetachedOverlay .aa-SourceFooter .algolia-search-logo,#quarto-search-results .aa-SourceFooter .algolia-search-logo{width:110px;opacity:.85;margin:8px;float:right}.aa-DetachedOverlay .search-result-section,#quarto-search-results .search-result-section{font-size:.925em}.aa-DetachedOverlay a.search-result-link,#quarto-search-results a.search-result-link{color:inherit;text-decoration:none}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item,#quarto-search-results li.aa-Item[aria-selected=true] .search-item{background-color:#3980f5}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text-container{color:#fff;background-color:#3980f5}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=true] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-match.mark{color:#fff;background-color:#6099f7}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item,#quarto-search-results li.aa-Item[aria-selected=false] .search-item{background-color:#fff}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text-container{color:#000}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=false] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-match.mark{color:inherit;background-color:#fff}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container{background-color:#fff;color:#000}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container{padding-top:0px}.aa-DetachedOverlay li.aa-Item .search-result-doc.document-selectable .search-result-text-container,#quarto-search-results li.aa-Item .search-result-doc.document-selectable .search-result-text-container{margin-top:-4px}.aa-DetachedOverlay .aa-Item,#quarto-search-results .aa-Item{cursor:pointer}.aa-DetachedOverlay .aa-Item .search-item,#quarto-search-results .aa-Item .search-item{border-left:none;border-right:none;border-top:none;background-color:#fff;border-color:#ced4da;color:#000}.aa-DetachedOverlay .aa-Item .search-item p,#quarto-search-results .aa-Item .search-item p{margin-top:0;margin-bottom:0}.aa-DetachedOverlay .aa-Item .search-item i.bi,#quarto-search-results .aa-Item .search-item i.bi{padding-left:8px;padding-right:8px;font-size:1.3em}.aa-DetachedOverlay .aa-Item .search-item .search-result-title,#quarto-search-results .aa-Item .search-item .search-result-title{margin-top:.3em;margin-bottom:.1rem}.aa-DetachedOverlay .aa-Item .search-result-title-container,#quarto-search-results .aa-Item .search-result-title-container{font-size:1em;display:flex;padding:6px 4px 6px 4px}.aa-DetachedOverlay .aa-Item .search-result-text-container,#quarto-search-results .aa-Item .search-result-text-container{padding-bottom:8px;padding-right:8px;margin-left:44px}.aa-DetachedOverlay .aa-Item .search-result-doc-section,.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-doc-section,#quarto-search-results .aa-Item .search-result-more{padding-top:8px;padding-bottom:8px;padding-left:44px}.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-more{font-size:.8em;font-weight:400}.aa-DetachedOverlay .aa-Item .search-result-doc,#quarto-search-results .aa-Item .search-result-doc{border-top:1px solid #ced4da}.aa-DetachedSearchButton{background:none;border:none}.aa-DetachedSearchButton .aa-DetachedSearchButtonPlaceholder{display:none}.navbar .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#fff}.sidebar-tools-collapse #quarto-search,.sidebar-tools-main #quarto-search{display:inline}.sidebar-tools-collapse #quarto-search .aa-Autocomplete,.sidebar-tools-main #quarto-search .aa-Autocomplete{display:inline}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton{padding-left:4px;padding-right:4px}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#fff}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon{margin-top:-3px}.aa-DetachedContainer{background:rgba(255,255,255,.65);width:90%;bottom:0;box-shadow:rgba(206,212,218,.6) 0 0 0 1px;outline:currentColor none medium;display:flex;flex-direction:column;left:0;margin:0;overflow:hidden;padding:0;position:fixed;right:0;top:0;z-index:1101}.aa-DetachedContainer::after{height:32px}.aa-DetachedContainer .aa-SourceHeader{margin:var(--aa-spacing-half) 0 var(--aa-spacing-half) 2px}.aa-DetachedContainer .aa-Panel{background-color:#fff;border-radius:0;box-shadow:none;flex-grow:1;margin:0;padding:0;position:relative}.aa-DetachedContainer .aa-PanelLayout{bottom:0;box-shadow:none;left:0;margin:0;max-height:none;overflow-y:auto;position:absolute;right:0;top:0;width:100%}.aa-DetachedFormContainer{background-color:#fff;border-bottom:1px solid #ced4da;display:flex;flex-direction:row;justify-content:space-between;margin:0;padding:.5em}.aa-DetachedCancelButton{background:none;font-size:.8em;border:0;border-radius:3px;color:#000;cursor:pointer;margin:0 0 0 .5em;padding:0 .5em}.aa-DetachedCancelButton:hover,.aa-DetachedCancelButton:focus{box-shadow:rgba(57,128,245,.6) 0 0 0 1px;outline:currentColor none medium}.aa-DetachedContainer--modal{bottom:inherit;height:auto;margin:0 auto;position:absolute;top:100px;border-radius:6px;max-width:850px}@media(max-width: 575.98px){.aa-DetachedContainer--modal{width:100%;top:0px;border-radius:0px;border:none}}.aa-DetachedContainer--modal .aa-PanelLayout{max-height:var(--aa-detached-modal-max-height);padding-bottom:var(--aa-spacing-half);position:static}.aa-Detached{height:100vh;overflow:hidden}.aa-DetachedOverlay{background-color:rgba(0,0,0,.4);position:fixed;left:0;right:0;top:0;margin:0;padding:0;height:100vh;z-index:1100}.quarto-listing{padding-bottom:1em}.listing-pagination{padding-top:.5em}ul.pagination{float:right;padding-left:8px;padding-top:.5em}ul.pagination li{padding-right:.75em}ul.pagination li.disabled a,ul.pagination li.active a{color:#000;text-decoration:none}ul.pagination li:last-of-type{padding-right:0}.listing-actions-group{display:flex}.quarto-listing-filter{margin-bottom:1em;width:200px;margin-left:auto}.quarto-listing-sort{margin-bottom:1em;margin-right:auto;width:auto}.quarto-listing-sort .input-group-text{font-size:.8em}.input-group-text{border-right:none}.quarto-listing-sort select.form-select{font-size:.8em}.listing-no-matching{text-align:center;padding-top:2em;padding-bottom:3em;font-size:1em}#quarto-margin-sidebar .quarto-listing-category{padding-top:0;font-size:1rem}#quarto-margin-sidebar .quarto-listing-category-title{cursor:pointer;font-weight:600;font-size:1rem}.quarto-listing-category .category{cursor:pointer}.quarto-listing-category .category.active{font-weight:600}.quarto-listing-category.category-cloud{display:flex;flex-wrap:wrap;align-items:baseline}.quarto-listing-category.category-cloud .category{padding-right:5px}.quarto-listing-category.category-cloud .category-cloud-1{font-size:.75em}.quarto-listing-category.category-cloud .category-cloud-2{font-size:.95em}.quarto-listing-category.category-cloud .category-cloud-3{font-size:1.15em}.quarto-listing-category.category-cloud .category-cloud-4{font-size:1.35em}.quarto-listing-category.category-cloud .category-cloud-5{font-size:1.55em}.quarto-listing-category.category-cloud .category-cloud-6{font-size:1.75em}.quarto-listing-category.category-cloud .category-cloud-7{font-size:1.95em}.quarto-listing-category.category-cloud .category-cloud-8{font-size:2.15em}.quarto-listing-category.category-cloud .category-cloud-9{font-size:2.35em}.quarto-listing-category.category-cloud .category-cloud-10{font-size:2.55em}.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-1{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-2{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-3{grid-template-columns:repeat(3, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-3{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-3{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-4{grid-template-columns:repeat(4, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-4{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-4{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-5{grid-template-columns:repeat(5, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-5{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-5{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-6{grid-template-columns:repeat(6, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-6{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-6{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-7{grid-template-columns:repeat(7, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-7{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-7{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-8{grid-template-columns:repeat(8, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-8{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-8{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-9{grid-template-columns:repeat(9, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-9{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-9{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-10{grid-template-columns:repeat(10, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-10{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-10{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-11{grid-template-columns:repeat(11, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-11{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-11{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-12{grid-template-columns:repeat(12, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-12{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-12{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-grid{gap:1.5em}.quarto-grid-item.borderless{border:none}.quarto-grid-item.borderless .listing-categories .listing-category:last-of-type,.quarto-grid-item.borderless .listing-categories .listing-category:first-of-type{padding-left:0}.quarto-grid-item.borderless .listing-categories .listing-category{border:0}.quarto-grid-link{text-decoration:none;color:inherit}.quarto-grid-link:hover{text-decoration:none;color:inherit}.quarto-grid-item h5.title,.quarto-grid-item .title.h5{margin-top:0;margin-bottom:0}.quarto-grid-item .card-footer{display:flex;justify-content:space-between;font-size:.8em}.quarto-grid-item .card-footer p{margin-bottom:0}.quarto-grid-item p.card-img-top{margin-bottom:0}.quarto-grid-item p.card-img-top>img{object-fit:cover}.quarto-grid-item .card-other-values{margin-top:.5em;font-size:.8em}.quarto-grid-item .card-other-values tr{margin-bottom:.5em}.quarto-grid-item .card-other-values tr>td:first-of-type{font-weight:600;padding-right:1em;padding-left:1em;vertical-align:top}.quarto-grid-item div.post-contents{display:flex;flex-direction:column;text-decoration:none;height:100%}.quarto-grid-item .listing-item-img-placeholder{background-color:#adb5bd;flex-shrink:0}.quarto-grid-item .card-attribution{padding-top:1em;display:flex;gap:1em;text-transform:uppercase;color:#6c757d;font-weight:500;flex-grow:10;align-items:flex-end}.quarto-grid-item .description{padding-bottom:1em}.quarto-grid-item .card-attribution .date{align-self:flex-end}.quarto-grid-item .card-attribution.justify{justify-content:space-between}.quarto-grid-item .card-attribution.start{justify-content:flex-start}.quarto-grid-item .card-attribution.end{justify-content:flex-end}.quarto-grid-item .card-title{margin-bottom:.1em}.quarto-grid-item .card-subtitle{padding-top:.25em}.quarto-grid-item .card-text{font-size:.9em}.quarto-grid-item .listing-reading-time{padding-bottom:.25em}.quarto-grid-item .card-text-small{font-size:.8em}.quarto-grid-item .card-subtitle.subtitle{font-size:.9em;font-weight:600;padding-bottom:.5em}.quarto-grid-item .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}.quarto-grid-item .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}.quarto-grid-item.card-right{text-align:right}.quarto-grid-item.card-right .listing-categories{justify-content:flex-end}.quarto-grid-item.card-left{text-align:left}.quarto-grid-item.card-center{text-align:center}.quarto-grid-item.card-center .listing-description{text-align:justify}.quarto-grid-item.card-center .listing-categories{justify-content:center}table.quarto-listing-table td.image{padding:0px}table.quarto-listing-table td.image img{width:100%;max-width:50px;object-fit:contain}table.quarto-listing-table a{text-decoration:none}table.quarto-listing-table th a{color:inherit}table.quarto-listing-table th a.asc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table th a.desc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table.table-hover td{cursor:pointer}.quarto-post.image-left{flex-direction:row}.quarto-post.image-right{flex-direction:row-reverse}@media(max-width: 767.98px){.quarto-post.image-right,.quarto-post.image-left{gap:0em;flex-direction:column}.quarto-post .metadata{padding-bottom:1em;order:2}.quarto-post .body{order:1}.quarto-post .thumbnail{order:3}}.list.quarto-listing-default div:last-of-type{border-bottom:none}@media(min-width: 992px){.quarto-listing-container-default{margin-right:2em}}div.quarto-post{display:flex;gap:2em;margin-bottom:1.5em;border-bottom:1px solid #dee2e6}@media(max-width: 767.98px){div.quarto-post{padding-bottom:1em}}div.quarto-post .metadata{flex-basis:20%;flex-grow:0;margin-top:.2em;flex-shrink:10}div.quarto-post .thumbnail{flex-basis:30%;flex-grow:0;flex-shrink:0}div.quarto-post .thumbnail img{margin-top:.4em;width:100%;object-fit:cover}div.quarto-post .body{flex-basis:45%;flex-grow:1;flex-shrink:0}div.quarto-post .body h3.listing-title,div.quarto-post .body .listing-title.h3{margin-top:0px;margin-bottom:0px;border-bottom:none}div.quarto-post .body .listing-subtitle{font-size:.875em;margin-bottom:.5em;margin-top:.2em}div.quarto-post .body .description{font-size:.9em}div.quarto-post a{color:#000;display:flex;flex-direction:column;text-decoration:none}div.quarto-post a div.description{flex-shrink:0}div.quarto-post .metadata{display:flex;flex-direction:column;font-size:.8em;font-family:"Open Sans",sans-serif;flex-basis:33%}div.quarto-post .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}div.quarto-post .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}div.quarto-post .listing-description{margin-bottom:.5em}div.quarto-about-jolla{display:flex !important;flex-direction:column;align-items:center;margin-top:10%;padding-bottom:1em}div.quarto-about-jolla .about-image{object-fit:cover;margin-left:auto;margin-right:auto;margin-bottom:1.5em}div.quarto-about-jolla img.round{border-radius:50%}div.quarto-about-jolla img.rounded{border-radius:10px}div.quarto-about-jolla .quarto-title h1.title,div.quarto-about-jolla .quarto-title .title.h1{text-align:center}div.quarto-about-jolla .quarto-title .description{text-align:center}div.quarto-about-jolla h2,div.quarto-about-jolla .h2{border-bottom:none}div.quarto-about-jolla .about-sep{width:60%}div.quarto-about-jolla main{text-align:center}div.quarto-about-jolla .about-links{display:flex}@media(min-width: 992px){div.quarto-about-jolla .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-jolla .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-jolla .about-link{color:#333;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-jolla .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-jolla .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-jolla .about-link:hover{color:#2b8cee}div.quarto-about-jolla .about-link i.bi{margin-right:.15em}div.quarto-about-solana{display:flex !important;flex-direction:column;padding-top:3em !important;padding-bottom:1em}div.quarto-about-solana .about-entity{display:flex !important;align-items:start;justify-content:space-between}@media(min-width: 992px){div.quarto-about-solana .about-entity{flex-direction:row}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity{flex-direction:column-reverse;align-items:center;text-align:center}}div.quarto-about-solana .about-entity .entity-contents{display:flex;flex-direction:column}@media(max-width: 767.98px){div.quarto-about-solana .about-entity .entity-contents{width:100%}}div.quarto-about-solana .about-entity .about-image{object-fit:cover}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-image{margin-bottom:1.5em}}div.quarto-about-solana .about-entity img.round{border-radius:50%}div.quarto-about-solana .about-entity img.rounded{border-radius:10px}div.quarto-about-solana .about-entity .about-links{display:flex;justify-content:left;padding-bottom:1.2em}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-solana .about-entity .about-link{color:#333;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-solana .about-entity .about-link:hover{color:#2b8cee}div.quarto-about-solana .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-solana .about-contents{padding-right:1.5em;flex-basis:0;flex-grow:1}div.quarto-about-solana .about-contents main.content{margin-top:0}div.quarto-about-solana .about-contents h2,div.quarto-about-solana .about-contents .h2{border-bottom:none}div.quarto-about-trestles{display:flex !important;flex-direction:row;padding-top:3em !important;padding-bottom:1em}@media(max-width: 991.98px){div.quarto-about-trestles{flex-direction:column;padding-top:0em !important}}div.quarto-about-trestles .about-entity{display:flex !important;flex-direction:column;align-items:center;text-align:center;padding-right:1em}@media(min-width: 992px){div.quarto-about-trestles .about-entity{flex:0 0 42%}}div.quarto-about-trestles .about-entity .about-image{object-fit:cover;margin-bottom:1.5em}div.quarto-about-trestles .about-entity img.round{border-radius:50%}div.quarto-about-trestles .about-entity img.rounded{border-radius:10px}div.quarto-about-trestles .about-entity .about-links{display:flex;justify-content:center}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-trestles .about-entity .about-link{color:#333;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-trestles .about-entity .about-link:hover{color:#2b8cee}div.quarto-about-trestles .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-trestles .about-contents{flex-basis:0;flex-grow:1}div.quarto-about-trestles .about-contents h2,div.quarto-about-trestles .about-contents .h2{border-bottom:none}@media(min-width: 992px){div.quarto-about-trestles .about-contents{border-left:solid 1px #dee2e6;padding-left:1.5em}}div.quarto-about-trestles .about-contents main.content{margin-top:0}div.quarto-about-marquee{padding-bottom:1em}div.quarto-about-marquee .about-contents{display:flex;flex-direction:column}div.quarto-about-marquee .about-image{max-height:550px;margin-bottom:1.5em;object-fit:cover}div.quarto-about-marquee img.round{border-radius:50%}div.quarto-about-marquee img.rounded{border-radius:10px}div.quarto-about-marquee h2,div.quarto-about-marquee .h2{border-bottom:none}div.quarto-about-marquee .about-links{display:flex;justify-content:center;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-marquee .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-marquee .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-marquee .about-link{color:#333;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-marquee .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-marquee .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-marquee .about-link:hover{color:#2b8cee}div.quarto-about-marquee .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-marquee .about-link{border:none}}div.quarto-about-broadside{display:flex;flex-direction:column;padding-bottom:1em}div.quarto-about-broadside .about-main{display:flex !important;padding-top:0 !important}@media(min-width: 992px){div.quarto-about-broadside .about-main{flex-direction:row;align-items:flex-start}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main{flex-direction:column}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main .about-entity{flex-shrink:0;width:100%;height:450px;margin-bottom:1.5em;background-size:cover;background-repeat:no-repeat}}@media(min-width: 992px){div.quarto-about-broadside .about-main .about-entity{flex:0 10 50%;margin-right:1.5em;width:100%;height:100%;background-size:100%;background-repeat:no-repeat}}div.quarto-about-broadside .about-main .about-contents{padding-top:14px;flex:0 0 50%}div.quarto-about-broadside h2,div.quarto-about-broadside .h2{border-bottom:none}div.quarto-about-broadside .about-sep{margin-top:1.5em;width:60%;align-self:center}div.quarto-about-broadside .about-links{display:flex;justify-content:center;column-gap:20px;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-broadside .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-broadside .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-broadside .about-link{color:#333;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-broadside .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-broadside .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-broadside .about-link:hover{color:#2b8cee}div.quarto-about-broadside .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-broadside .about-link{border:none}}.tippy-box[data-theme~=quarto]{background-color:#fff;border:solid 1px #dee2e6;border-radius:.25rem;color:#000;font-size:.875rem}.tippy-box[data-theme~=quarto]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=quarto]>.tippy-arrow:after,.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{content:"";position:absolute;z-index:-1}.tippy-box[data-theme~=quarto]>.tippy-arrow:after{border-color:rgba(0,0,0,0);border-style:solid}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-6px}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-6px}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-6px}.tippy-box[data-placement^=left]>.tippy-arrow:before{right:-6px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:after{border-top-color:#dee2e6;border-width:7px 7px 0;top:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow>svg{top:16px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow:after{top:17px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff;bottom:16px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:after{border-bottom-color:#dee2e6;border-width:0 7px 7px;bottom:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:15px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow:after{bottom:17px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:after{border-left-color:#dee2e6;border-width:7px 0 7px 7px;left:17px;top:1px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow>svg{left:11px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow:after{left:12px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff;right:16px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:after{border-width:7px 7px 7px 0;right:17px;top:1px;border-right-color:#dee2e6}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow>svg{right:11px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow:after{right:12px}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow{fill:#000}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiLz48L3N2Zz4=);background-size:16px 6px;width:16px;height:6px}.top-right{position:absolute;top:1em;right:1em}.hidden{display:none !important}.zindex-bottom{z-index:-1 !important}.quarto-layout-panel{margin-bottom:1em}.quarto-layout-panel>figure{width:100%}.quarto-layout-panel>figure>figcaption,.quarto-layout-panel>.panel-caption{margin-top:10pt}.quarto-layout-panel>.table-caption{margin-top:0px}.table-caption p{margin-bottom:.5em}.quarto-layout-row{display:flex;flex-direction:row;align-items:flex-start}.quarto-layout-valign-top{align-items:flex-start}.quarto-layout-valign-bottom{align-items:flex-end}.quarto-layout-valign-center{align-items:center}.quarto-layout-cell{position:relative;margin-right:20px}.quarto-layout-cell:last-child{margin-right:0}.quarto-layout-cell figure,.quarto-layout-cell>p{margin:.2em}.quarto-layout-cell img{max-width:100%}.quarto-layout-cell .html-widget{width:100% !important}.quarto-layout-cell div figure p{margin:0}.quarto-layout-cell figure{display:inline-block;margin-inline-start:0;margin-inline-end:0}.quarto-layout-cell table{display:inline-table}.quarto-layout-cell-subref figcaption,figure .quarto-layout-row figure figcaption{text-align:center;font-style:italic}.quarto-figure{position:relative;margin-bottom:1em}.quarto-figure>figure{width:100%;margin-bottom:0}.quarto-figure-left>figure>p,.quarto-figure-left>figure>div{text-align:left}.quarto-figure-center>figure>p,.quarto-figure-center>figure>div{text-align:center}.quarto-figure-right>figure>p,.quarto-figure-right>figure>div{text-align:right}figure>p:empty{display:none}figure>p:first-child{margin-top:0;margin-bottom:0}figure>figcaption{margin-top:.5em}div[id^=tbl-]{position:relative}.quarto-figure>.anchorjs-link{position:absolute;top:.6em;right:.5em}div[id^=tbl-]>.anchorjs-link{position:absolute;top:.7em;right:.3em}.quarto-figure:hover>.anchorjs-link,div[id^=tbl-]:hover>.anchorjs-link,h2:hover>.anchorjs-link,.h2:hover>.anchorjs-link,h3:hover>.anchorjs-link,.h3:hover>.anchorjs-link,h4:hover>.anchorjs-link,.h4:hover>.anchorjs-link,h5:hover>.anchorjs-link,.h5:hover>.anchorjs-link,h6:hover>.anchorjs-link,.h6:hover>.anchorjs-link,.reveal-anchorjs-link>.anchorjs-link{opacity:1}#title-block-header{margin-block-end:1rem;position:relative;margin-top:-1px}#title-block-header .abstract{margin-block-start:1rem}#title-block-header .abstract .abstract-title{font-weight:600}#title-block-header a{text-decoration:none}#title-block-header .author,#title-block-header .date,#title-block-header .doi{margin-block-end:.2rem}#title-block-header .quarto-title-block>div{display:flex}#title-block-header .quarto-title-block>div>h1,#title-block-header .quarto-title-block>div>.h1{flex-grow:1}#title-block-header .quarto-title-block>div>button{flex-shrink:0;height:2.25rem;margin-top:0}@media(min-width: 992px){#title-block-header .quarto-title-block>div>button{margin-top:5px}}tr.header>th>p:last-of-type{margin-bottom:0px}table,.table{caption-side:top;margin-bottom:1.5rem}caption,.table-caption{padding-top:.5rem;padding-bottom:.5rem;text-align:center}.utterances{max-width:none;margin-left:-8px}iframe{margin-bottom:1em}details{margin-bottom:1em}details[show]{margin-bottom:0}details>summary{color:#6c757d}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}p code:not(.sourceCode){white-space:pre-wrap}code{white-space:pre}@media print{code{white-space:pre-wrap}}pre>code{display:block}pre>code.sourceCode{white-space:pre}pre>code.sourceCode>span>a:first-child::before{text-decoration:none}pre.code-overflow-wrap>code.sourceCode{white-space:pre-wrap}pre.code-overflow-scroll>code.sourceCode{white-space:pre}code a:any-link{color:inherit;text-decoration:none}code a:hover{color:inherit;text-decoration:underline}ul.task-list{padding-left:1em}[data-tippy-root]{display:inline-block}.tippy-content .footnote-back{display:none}.quarto-embedded-source-code{display:none}.quarto-unresolved-ref{font-weight:600}.quarto-cover-image{max-width:35%;float:right;margin-left:30px}.cell-output-display .widget-subarea{margin-bottom:1em}.cell-output-display:not(.no-overflow-x),.knitsql-table:not(.no-overflow-x){overflow-x:auto}.panel-input{margin-bottom:1em}.panel-input>div,.panel-input>div>div{display:inline-block;vertical-align:top;padding-right:12px}.panel-input>p:last-child{margin-bottom:0}.layout-sidebar{margin-bottom:1em}.layout-sidebar .tab-content{border:none}.tab-content>.page-columns.active{display:grid}div.sourceCode>iframe{width:100%;height:300px;margin-bottom:-0.5em}div.ansi-escaped-output{font-family:monospace;display:block}/*! +* +* ansi colors from IPython notebook's +* +*/.ansi-black-fg{color:#3e424d}.ansi-black-bg{background-color:#3e424d}.ansi-black-intense-fg{color:#282c36}.ansi-black-intense-bg{background-color:#282c36}.ansi-red-fg{color:#e75c58}.ansi-red-bg{background-color:#e75c58}.ansi-red-intense-fg{color:#b22b31}.ansi-red-intense-bg{background-color:#b22b31}.ansi-green-fg{color:#00a250}.ansi-green-bg{background-color:#00a250}.ansi-green-intense-fg{color:#007427}.ansi-green-intense-bg{background-color:#007427}.ansi-yellow-fg{color:#ddb62b}.ansi-yellow-bg{background-color:#ddb62b}.ansi-yellow-intense-fg{color:#b27d12}.ansi-yellow-intense-bg{background-color:#b27d12}.ansi-blue-fg{color:#208ffb}.ansi-blue-bg{background-color:#208ffb}.ansi-blue-intense-fg{color:#0065ca}.ansi-blue-intense-bg{background-color:#0065ca}.ansi-magenta-fg{color:#d160c4}.ansi-magenta-bg{background-color:#d160c4}.ansi-magenta-intense-fg{color:#a03196}.ansi-magenta-intense-bg{background-color:#a03196}.ansi-cyan-fg{color:#60c6c8}.ansi-cyan-bg{background-color:#60c6c8}.ansi-cyan-intense-fg{color:#258f8f}.ansi-cyan-intense-bg{background-color:#258f8f}.ansi-white-fg{color:#c5c1b4}.ansi-white-bg{background-color:#c5c1b4}.ansi-white-intense-fg{color:#a1a6b2}.ansi-white-intense-bg{background-color:#a1a6b2}.ansi-default-inverse-fg{color:#fff}.ansi-default-inverse-bg{background-color:#000}.ansi-bold{font-weight:bold}.ansi-underline{text-decoration:underline}:root{--quarto-body-bg: white;--quarto-body-color: black;--quarto-text-muted: #6c757d;--quarto-border-color: #dee2e6;--quarto-border-width: 1px;--quarto-border-radius: 0.25rem}table.gt_table{color:var(--quarto-body-color);font-size:1em;width:100%;background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_column_spanner_outer{color:var(--quarto-body-color);background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_col_heading{color:var(--quarto-body-color);font-weight:bold;background-color:rgba(0,0,0,0)}table.gt_table thead.gt_col_headings{border-bottom:1px solid currentColor;border-top-width:inherit;border-top-color:var(--quarto-border-color)}table.gt_table thead.gt_col_headings:not(:first-child){border-top-width:1px;border-top-color:var(--quarto-border-color)}table.gt_table td.gt_row{border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-width:0px}table.gt_table tbody.gt_table_body{border-top-width:1px;border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-color:currentColor}div.columns{display:initial;gap:initial}div.column{display:inline-block;overflow-x:initial;vertical-align:top;width:50%}.code-annotation-tip-content{word-wrap:break-word}.code-annotation-container-hidden{display:none !important}dl.code-annotation-container-grid{display:grid;grid-template-columns:min-content auto}dl.code-annotation-container-grid dt{grid-column:1}dl.code-annotation-container-grid dd{grid-column:2}pre.sourceCode.code-annotation-code{padding-right:0}code.sourceCode .code-annotation-anchor{z-index:100;position:absolute;right:.5em;left:inherit;background-color:rgba(0,0,0,0)}:root{--mermaid-bg-color: white;--mermaid-edge-color: #6c757d;--mermaid-node-fg-color: black;--mermaid-fg-color: black;--mermaid-fg-color--lighter: #1a1a1a;--mermaid-fg-color--lightest: #333333;--mermaid-font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica Neue, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;--mermaid-label-bg-color: white;--mermaid-label-fg-color: #3980F5;--mermaid-node-bg-color: rgba(57, 128, 245, 0.1);--mermaid-node-fg-color: black}@media print{:root{font-size:11pt}#quarto-sidebar,#TOC,.nav-page{display:none}.page-columns .content{grid-column-start:page-start}.fixed-top{position:relative}.panel-caption,.figure-caption,figcaption{color:#666}}.code-copy-button{position:absolute;top:0;right:0;border:0;margin-top:5px;margin-right:5px;background-color:rgba(0,0,0,0);z-index:3}.code-copy-button:focus{outline:none}.code-copy-button-tooltip{font-size:.75em}pre.sourceCode:hover>.code-copy-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}pre.sourceCode:hover>.code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button-checked:hover>.bi::before{background-image:url('data:image/svg+xml,')}main ol ol,main ul ul,main ol ul,main ul ol{margin-bottom:1em}ul>li:not(:has(>p))>ul,ol>li:not(:has(>p))>ul,ul>li:not(:has(>p))>ol,ol>li:not(:has(>p))>ol{margin-bottom:0}ul>li:not(:has(>p))>ul>li:has(>p),ol>li:not(:has(>p))>ul>li:has(>p),ul>li:not(:has(>p))>ol>li:has(>p),ol>li:not(:has(>p))>ol>li:has(>p){margin-top:1rem}body{margin:0}main.page-columns>header>h1.title,main.page-columns>header>.title.h1{margin-bottom:0}@media(min-width: 992px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1400px - 3em )) [body-content-end] 1.5em [body-end] 53.2px [body-end-outset] minmax(114px, 220.4px) [page-end-inset] 53.2px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1400px - 3em )) [body-content-end] 1.5em [body-end] 53.2px [body-end-outset] 53.2px [page-end-inset page-end] 5fr [screen-end-inset] 1.5em}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1400px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(0px, 304px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1400px - 3em )) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 380px) [page-end-inset] minmax(50px, 100px) [page-end] 1fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(0px, 304px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(0px, 304px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end] minmax(38px, 76px) [body-end-outset] minmax(76px, 228px) [page-end-inset] minmax(38px, 76px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1550px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(76px, 152px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1550px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(0px, 304px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1550px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(0px, 304px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(76px, 228px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end] minmax(38px, 76px) [body-end-outset] minmax(76px, 228px) [page-end-inset] minmax(38px, 76px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 991.98px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end] 53.2px [body-end-outset] minmax(114px, 220.4px) [page-end-inset] 53.2px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end] 53.2px [body-end-outset] minmax(114px, 220.4px) [page-end-inset] 53.2px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 1800px - 3em )) [body-content-end body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1.5em [body-content-start] minmax(500px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(114px, 228px) [page-end-inset] 38px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(38px, 76px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 1550px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc( 1350px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(38px, 76px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(38px, 76px) [page-end-inset] 76px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 53.2px [body-end-outset] minmax(114px, 220.4px) [page-end-inset] 53.2px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc( 1300px - 3em )) [body-content-end] 1.5em [body-end] 76px [body-end-outset] minmax(114px, 228px) [page-end-inset] 38px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 767.98px){body .page-columns,body.fullcontent:not(.floating):not(.docked) .page-columns,body.slimcontent:not(.floating):not(.docked) .page-columns,body.docked .page-columns,body.docked.slimcontent .page-columns,body.docked.fullcontent .page-columns,body.floating .page-columns,body.floating.slimcontent .page-columns,body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}nav[role=doc-toc]{display:none}}body,.page-row-navigation{grid-template-rows:[page-top] max-content [contents-top] max-content [contents-bottom] max-content [page-bottom]}.page-rows-contents{grid-template-rows:[content-top] minmax(max-content, 1fr) [content-bottom] minmax(60px, max-content) [page-bottom]}.page-full{grid-column:screen-start/screen-end !important}.page-columns>*{grid-column:body-content-start/body-content-end}.page-columns.column-page>*{grid-column:page-start/page-end}.page-columns.column-page-left>*{grid-column:page-start/body-content-end}.page-columns.column-page-right>*{grid-column:body-content-start/page-end}.page-rows{grid-auto-rows:auto}.header{grid-column:screen-start/screen-end;grid-row:page-top/contents-top}#quarto-content{padding:0;grid-column:screen-start/screen-end;grid-row:contents-top/contents-bottom}body.floating .sidebar.sidebar-navigation{grid-column:page-start/body-start;grid-row:content-top/page-bottom}body.docked .sidebar.sidebar-navigation{grid-column:screen-start/body-start;grid-row:content-top/page-bottom}.sidebar.toc-left{grid-column:page-start/body-start;grid-row:content-top/page-bottom}.sidebar.margin-sidebar{grid-column:body-end/page-end;grid-row:content-top/page-bottom}.page-columns .content{grid-column:body-content-start/body-content-end;grid-row:content-top/content-bottom;align-content:flex-start}.page-columns .page-navigation{grid-column:body-content-start/body-content-end;grid-row:content-bottom/page-bottom}.page-columns .footer{grid-column:screen-start/screen-end;grid-row:contents-bottom/page-bottom}.page-columns .column-body{grid-column:body-content-start/body-content-end}.page-columns .column-body-fullbleed{grid-column:body-start/body-end}.page-columns .column-body-outset{grid-column:body-start-outset/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset table{background:#fff}.page-columns .column-body-outset-left{grid-column:body-start-outset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-left table{background:#fff}.page-columns .column-body-outset-right{grid-column:body-content-start/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-right table{background:#fff}.page-columns .column-page{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page table{background:#fff}.page-columns .column-page-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset table{background:#fff}.page-columns .column-page-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-left table{background:#fff}.page-columns .column-page-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-right figcaption table{background:#fff}.page-columns .column-page-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-left table{background:#fff}.page-columns .column-page-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-right figcaption table{background:#fff}#quarto-content.page-columns #quarto-margin-sidebar,#quarto-content.page-columns #quarto-sidebar{z-index:1}@media(max-width: 991.98px){#quarto-content.page-columns #quarto-margin-sidebar.collapse,#quarto-content.page-columns #quarto-sidebar.collapse,#quarto-content.page-columns #quarto-margin-sidebar.collapsing,#quarto-content.page-columns #quarto-sidebar.collapsing{z-index:1055}}#quarto-content.page-columns main.column-page,#quarto-content.page-columns main.column-page-right,#quarto-content.page-columns main.column-page-left{z-index:0}.page-columns .column-screen-inset{grid-column:screen-start-inset/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:screen-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:screen-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:screen-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:screen-start/screen-end;padding:1em;background:#f8f5f0;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}.zindex-content{z-index:998;transform:translate3d(0, 0, 0)}.zindex-modal{z-index:1055;transform:translate3d(0, 0, 0)}.zindex-over-content{z-index:999;transform:translate3d(0, 0, 0)}img.img-fluid.column-screen,img.img-fluid.column-screen-inset-shaded,img.img-fluid.column-screen-inset,img.img-fluid.column-screen-inset-left,img.img-fluid.column-screen-inset-right,img.img-fluid.column-screen-left,img.img-fluid.column-screen-right{width:100%}@media(min-width: 992px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.column-sidebar{grid-column:page-start/body-start !important;z-index:998}.column-leftmargin{grid-column:screen-start-inset/body-start !important;z-index:998}.no-row-height{height:1em;overflow:visible}}@media(max-width: 991.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.no-row-height{height:1em;overflow:visible}.page-columns.page-full{overflow:visible}.page-columns.toc-left .margin-caption,.page-columns.toc-left div.aside,.page-columns.toc-left aside,.page-columns.toc-left .column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.page-columns.toc-left .no-row-height{height:initial;overflow:initial}}@media(max-width: 767.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.no-row-height{height:initial;overflow:initial}#quarto-margin-sidebar{display:none}#quarto-sidebar-toc-left{display:none}.hidden-sm{display:none}}.panel-grid{display:grid;grid-template-rows:repeat(1, 1fr);grid-template-columns:repeat(24, 1fr);gap:1em}.panel-grid .g-col-1{grid-column:auto/span 1}.panel-grid .g-col-2{grid-column:auto/span 2}.panel-grid .g-col-3{grid-column:auto/span 3}.panel-grid .g-col-4{grid-column:auto/span 4}.panel-grid .g-col-5{grid-column:auto/span 5}.panel-grid .g-col-6{grid-column:auto/span 6}.panel-grid .g-col-7{grid-column:auto/span 7}.panel-grid .g-col-8{grid-column:auto/span 8}.panel-grid .g-col-9{grid-column:auto/span 9}.panel-grid .g-col-10{grid-column:auto/span 10}.panel-grid .g-col-11{grid-column:auto/span 11}.panel-grid .g-col-12{grid-column:auto/span 12}.panel-grid .g-col-13{grid-column:auto/span 13}.panel-grid .g-col-14{grid-column:auto/span 14}.panel-grid .g-col-15{grid-column:auto/span 15}.panel-grid .g-col-16{grid-column:auto/span 16}.panel-grid .g-col-17{grid-column:auto/span 17}.panel-grid .g-col-18{grid-column:auto/span 18}.panel-grid .g-col-19{grid-column:auto/span 19}.panel-grid .g-col-20{grid-column:auto/span 20}.panel-grid .g-col-21{grid-column:auto/span 21}.panel-grid .g-col-22{grid-column:auto/span 22}.panel-grid .g-col-23{grid-column:auto/span 23}.panel-grid .g-col-24{grid-column:auto/span 24}.panel-grid .g-start-1{grid-column-start:1}.panel-grid .g-start-2{grid-column-start:2}.panel-grid .g-start-3{grid-column-start:3}.panel-grid .g-start-4{grid-column-start:4}.panel-grid .g-start-5{grid-column-start:5}.panel-grid .g-start-6{grid-column-start:6}.panel-grid .g-start-7{grid-column-start:7}.panel-grid .g-start-8{grid-column-start:8}.panel-grid .g-start-9{grid-column-start:9}.panel-grid .g-start-10{grid-column-start:10}.panel-grid .g-start-11{grid-column-start:11}.panel-grid .g-start-12{grid-column-start:12}.panel-grid .g-start-13{grid-column-start:13}.panel-grid .g-start-14{grid-column-start:14}.panel-grid .g-start-15{grid-column-start:15}.panel-grid .g-start-16{grid-column-start:16}.panel-grid .g-start-17{grid-column-start:17}.panel-grid .g-start-18{grid-column-start:18}.panel-grid .g-start-19{grid-column-start:19}.panel-grid .g-start-20{grid-column-start:20}.panel-grid .g-start-21{grid-column-start:21}.panel-grid .g-start-22{grid-column-start:22}.panel-grid .g-start-23{grid-column-start:23}@media(min-width: 576px){.panel-grid .g-col-sm-1{grid-column:auto/span 1}.panel-grid .g-col-sm-2{grid-column:auto/span 2}.panel-grid .g-col-sm-3{grid-column:auto/span 3}.panel-grid .g-col-sm-4{grid-column:auto/span 4}.panel-grid .g-col-sm-5{grid-column:auto/span 5}.panel-grid .g-col-sm-6{grid-column:auto/span 6}.panel-grid .g-col-sm-7{grid-column:auto/span 7}.panel-grid .g-col-sm-8{grid-column:auto/span 8}.panel-grid .g-col-sm-9{grid-column:auto/span 9}.panel-grid .g-col-sm-10{grid-column:auto/span 10}.panel-grid .g-col-sm-11{grid-column:auto/span 11}.panel-grid .g-col-sm-12{grid-column:auto/span 12}.panel-grid .g-col-sm-13{grid-column:auto/span 13}.panel-grid .g-col-sm-14{grid-column:auto/span 14}.panel-grid .g-col-sm-15{grid-column:auto/span 15}.panel-grid .g-col-sm-16{grid-column:auto/span 16}.panel-grid .g-col-sm-17{grid-column:auto/span 17}.panel-grid .g-col-sm-18{grid-column:auto/span 18}.panel-grid .g-col-sm-19{grid-column:auto/span 19}.panel-grid .g-col-sm-20{grid-column:auto/span 20}.panel-grid .g-col-sm-21{grid-column:auto/span 21}.panel-grid .g-col-sm-22{grid-column:auto/span 22}.panel-grid .g-col-sm-23{grid-column:auto/span 23}.panel-grid .g-col-sm-24{grid-column:auto/span 24}.panel-grid .g-start-sm-1{grid-column-start:1}.panel-grid .g-start-sm-2{grid-column-start:2}.panel-grid .g-start-sm-3{grid-column-start:3}.panel-grid .g-start-sm-4{grid-column-start:4}.panel-grid .g-start-sm-5{grid-column-start:5}.panel-grid .g-start-sm-6{grid-column-start:6}.panel-grid .g-start-sm-7{grid-column-start:7}.panel-grid .g-start-sm-8{grid-column-start:8}.panel-grid .g-start-sm-9{grid-column-start:9}.panel-grid .g-start-sm-10{grid-column-start:10}.panel-grid .g-start-sm-11{grid-column-start:11}.panel-grid .g-start-sm-12{grid-column-start:12}.panel-grid .g-start-sm-13{grid-column-start:13}.panel-grid .g-start-sm-14{grid-column-start:14}.panel-grid .g-start-sm-15{grid-column-start:15}.panel-grid .g-start-sm-16{grid-column-start:16}.panel-grid .g-start-sm-17{grid-column-start:17}.panel-grid .g-start-sm-18{grid-column-start:18}.panel-grid .g-start-sm-19{grid-column-start:19}.panel-grid .g-start-sm-20{grid-column-start:20}.panel-grid .g-start-sm-21{grid-column-start:21}.panel-grid .g-start-sm-22{grid-column-start:22}.panel-grid .g-start-sm-23{grid-column-start:23}}@media(min-width: 768px){.panel-grid .g-col-md-1{grid-column:auto/span 1}.panel-grid .g-col-md-2{grid-column:auto/span 2}.panel-grid .g-col-md-3{grid-column:auto/span 3}.panel-grid .g-col-md-4{grid-column:auto/span 4}.panel-grid .g-col-md-5{grid-column:auto/span 5}.panel-grid .g-col-md-6{grid-column:auto/span 6}.panel-grid .g-col-md-7{grid-column:auto/span 7}.panel-grid .g-col-md-8{grid-column:auto/span 8}.panel-grid .g-col-md-9{grid-column:auto/span 9}.panel-grid .g-col-md-10{grid-column:auto/span 10}.panel-grid .g-col-md-11{grid-column:auto/span 11}.panel-grid .g-col-md-12{grid-column:auto/span 12}.panel-grid .g-col-md-13{grid-column:auto/span 13}.panel-grid .g-col-md-14{grid-column:auto/span 14}.panel-grid .g-col-md-15{grid-column:auto/span 15}.panel-grid .g-col-md-16{grid-column:auto/span 16}.panel-grid .g-col-md-17{grid-column:auto/span 17}.panel-grid .g-col-md-18{grid-column:auto/span 18}.panel-grid .g-col-md-19{grid-column:auto/span 19}.panel-grid .g-col-md-20{grid-column:auto/span 20}.panel-grid .g-col-md-21{grid-column:auto/span 21}.panel-grid .g-col-md-22{grid-column:auto/span 22}.panel-grid .g-col-md-23{grid-column:auto/span 23}.panel-grid .g-col-md-24{grid-column:auto/span 24}.panel-grid .g-start-md-1{grid-column-start:1}.panel-grid .g-start-md-2{grid-column-start:2}.panel-grid .g-start-md-3{grid-column-start:3}.panel-grid .g-start-md-4{grid-column-start:4}.panel-grid .g-start-md-5{grid-column-start:5}.panel-grid .g-start-md-6{grid-column-start:6}.panel-grid .g-start-md-7{grid-column-start:7}.panel-grid .g-start-md-8{grid-column-start:8}.panel-grid .g-start-md-9{grid-column-start:9}.panel-grid .g-start-md-10{grid-column-start:10}.panel-grid .g-start-md-11{grid-column-start:11}.panel-grid .g-start-md-12{grid-column-start:12}.panel-grid .g-start-md-13{grid-column-start:13}.panel-grid .g-start-md-14{grid-column-start:14}.panel-grid .g-start-md-15{grid-column-start:15}.panel-grid .g-start-md-16{grid-column-start:16}.panel-grid .g-start-md-17{grid-column-start:17}.panel-grid .g-start-md-18{grid-column-start:18}.panel-grid .g-start-md-19{grid-column-start:19}.panel-grid .g-start-md-20{grid-column-start:20}.panel-grid .g-start-md-21{grid-column-start:21}.panel-grid .g-start-md-22{grid-column-start:22}.panel-grid .g-start-md-23{grid-column-start:23}}@media(min-width: 992px){.panel-grid .g-col-lg-1{grid-column:auto/span 1}.panel-grid .g-col-lg-2{grid-column:auto/span 2}.panel-grid .g-col-lg-3{grid-column:auto/span 3}.panel-grid .g-col-lg-4{grid-column:auto/span 4}.panel-grid .g-col-lg-5{grid-column:auto/span 5}.panel-grid .g-col-lg-6{grid-column:auto/span 6}.panel-grid .g-col-lg-7{grid-column:auto/span 7}.panel-grid .g-col-lg-8{grid-column:auto/span 8}.panel-grid .g-col-lg-9{grid-column:auto/span 9}.panel-grid .g-col-lg-10{grid-column:auto/span 10}.panel-grid .g-col-lg-11{grid-column:auto/span 11}.panel-grid .g-col-lg-12{grid-column:auto/span 12}.panel-grid .g-col-lg-13{grid-column:auto/span 13}.panel-grid .g-col-lg-14{grid-column:auto/span 14}.panel-grid .g-col-lg-15{grid-column:auto/span 15}.panel-grid .g-col-lg-16{grid-column:auto/span 16}.panel-grid .g-col-lg-17{grid-column:auto/span 17}.panel-grid .g-col-lg-18{grid-column:auto/span 18}.panel-grid .g-col-lg-19{grid-column:auto/span 19}.panel-grid .g-col-lg-20{grid-column:auto/span 20}.panel-grid .g-col-lg-21{grid-column:auto/span 21}.panel-grid .g-col-lg-22{grid-column:auto/span 22}.panel-grid .g-col-lg-23{grid-column:auto/span 23}.panel-grid .g-col-lg-24{grid-column:auto/span 24}.panel-grid .g-start-lg-1{grid-column-start:1}.panel-grid .g-start-lg-2{grid-column-start:2}.panel-grid .g-start-lg-3{grid-column-start:3}.panel-grid .g-start-lg-4{grid-column-start:4}.panel-grid .g-start-lg-5{grid-column-start:5}.panel-grid .g-start-lg-6{grid-column-start:6}.panel-grid .g-start-lg-7{grid-column-start:7}.panel-grid .g-start-lg-8{grid-column-start:8}.panel-grid .g-start-lg-9{grid-column-start:9}.panel-grid .g-start-lg-10{grid-column-start:10}.panel-grid .g-start-lg-11{grid-column-start:11}.panel-grid .g-start-lg-12{grid-column-start:12}.panel-grid .g-start-lg-13{grid-column-start:13}.panel-grid .g-start-lg-14{grid-column-start:14}.panel-grid .g-start-lg-15{grid-column-start:15}.panel-grid .g-start-lg-16{grid-column-start:16}.panel-grid .g-start-lg-17{grid-column-start:17}.panel-grid .g-start-lg-18{grid-column-start:18}.panel-grid .g-start-lg-19{grid-column-start:19}.panel-grid .g-start-lg-20{grid-column-start:20}.panel-grid .g-start-lg-21{grid-column-start:21}.panel-grid .g-start-lg-22{grid-column-start:22}.panel-grid .g-start-lg-23{grid-column-start:23}}@media(min-width: 1200px){.panel-grid .g-col-xl-1{grid-column:auto/span 1}.panel-grid .g-col-xl-2{grid-column:auto/span 2}.panel-grid .g-col-xl-3{grid-column:auto/span 3}.panel-grid .g-col-xl-4{grid-column:auto/span 4}.panel-grid .g-col-xl-5{grid-column:auto/span 5}.panel-grid .g-col-xl-6{grid-column:auto/span 6}.panel-grid .g-col-xl-7{grid-column:auto/span 7}.panel-grid .g-col-xl-8{grid-column:auto/span 8}.panel-grid .g-col-xl-9{grid-column:auto/span 9}.panel-grid .g-col-xl-10{grid-column:auto/span 10}.panel-grid .g-col-xl-11{grid-column:auto/span 11}.panel-grid .g-col-xl-12{grid-column:auto/span 12}.panel-grid .g-col-xl-13{grid-column:auto/span 13}.panel-grid .g-col-xl-14{grid-column:auto/span 14}.panel-grid .g-col-xl-15{grid-column:auto/span 15}.panel-grid .g-col-xl-16{grid-column:auto/span 16}.panel-grid .g-col-xl-17{grid-column:auto/span 17}.panel-grid .g-col-xl-18{grid-column:auto/span 18}.panel-grid .g-col-xl-19{grid-column:auto/span 19}.panel-grid .g-col-xl-20{grid-column:auto/span 20}.panel-grid .g-col-xl-21{grid-column:auto/span 21}.panel-grid .g-col-xl-22{grid-column:auto/span 22}.panel-grid .g-col-xl-23{grid-column:auto/span 23}.panel-grid .g-col-xl-24{grid-column:auto/span 24}.panel-grid .g-start-xl-1{grid-column-start:1}.panel-grid .g-start-xl-2{grid-column-start:2}.panel-grid .g-start-xl-3{grid-column-start:3}.panel-grid .g-start-xl-4{grid-column-start:4}.panel-grid .g-start-xl-5{grid-column-start:5}.panel-grid .g-start-xl-6{grid-column-start:6}.panel-grid .g-start-xl-7{grid-column-start:7}.panel-grid .g-start-xl-8{grid-column-start:8}.panel-grid .g-start-xl-9{grid-column-start:9}.panel-grid .g-start-xl-10{grid-column-start:10}.panel-grid .g-start-xl-11{grid-column-start:11}.panel-grid .g-start-xl-12{grid-column-start:12}.panel-grid .g-start-xl-13{grid-column-start:13}.panel-grid .g-start-xl-14{grid-column-start:14}.panel-grid .g-start-xl-15{grid-column-start:15}.panel-grid .g-start-xl-16{grid-column-start:16}.panel-grid .g-start-xl-17{grid-column-start:17}.panel-grid .g-start-xl-18{grid-column-start:18}.panel-grid .g-start-xl-19{grid-column-start:19}.panel-grid .g-start-xl-20{grid-column-start:20}.panel-grid .g-start-xl-21{grid-column-start:21}.panel-grid .g-start-xl-22{grid-column-start:22}.panel-grid .g-start-xl-23{grid-column-start:23}}@media(min-width: 1400px){.panel-grid .g-col-xxl-1{grid-column:auto/span 1}.panel-grid .g-col-xxl-2{grid-column:auto/span 2}.panel-grid .g-col-xxl-3{grid-column:auto/span 3}.panel-grid .g-col-xxl-4{grid-column:auto/span 4}.panel-grid .g-col-xxl-5{grid-column:auto/span 5}.panel-grid .g-col-xxl-6{grid-column:auto/span 6}.panel-grid .g-col-xxl-7{grid-column:auto/span 7}.panel-grid .g-col-xxl-8{grid-column:auto/span 8}.panel-grid .g-col-xxl-9{grid-column:auto/span 9}.panel-grid .g-col-xxl-10{grid-column:auto/span 10}.panel-grid .g-col-xxl-11{grid-column:auto/span 11}.panel-grid .g-col-xxl-12{grid-column:auto/span 12}.panel-grid .g-col-xxl-13{grid-column:auto/span 13}.panel-grid .g-col-xxl-14{grid-column:auto/span 14}.panel-grid .g-col-xxl-15{grid-column:auto/span 15}.panel-grid .g-col-xxl-16{grid-column:auto/span 16}.panel-grid .g-col-xxl-17{grid-column:auto/span 17}.panel-grid .g-col-xxl-18{grid-column:auto/span 18}.panel-grid .g-col-xxl-19{grid-column:auto/span 19}.panel-grid .g-col-xxl-20{grid-column:auto/span 20}.panel-grid .g-col-xxl-21{grid-column:auto/span 21}.panel-grid .g-col-xxl-22{grid-column:auto/span 22}.panel-grid .g-col-xxl-23{grid-column:auto/span 23}.panel-grid .g-col-xxl-24{grid-column:auto/span 24}.panel-grid .g-start-xxl-1{grid-column-start:1}.panel-grid .g-start-xxl-2{grid-column-start:2}.panel-grid .g-start-xxl-3{grid-column-start:3}.panel-grid .g-start-xxl-4{grid-column-start:4}.panel-grid .g-start-xxl-5{grid-column-start:5}.panel-grid .g-start-xxl-6{grid-column-start:6}.panel-grid .g-start-xxl-7{grid-column-start:7}.panel-grid .g-start-xxl-8{grid-column-start:8}.panel-grid .g-start-xxl-9{grid-column-start:9}.panel-grid .g-start-xxl-10{grid-column-start:10}.panel-grid .g-start-xxl-11{grid-column-start:11}.panel-grid .g-start-xxl-12{grid-column-start:12}.panel-grid .g-start-xxl-13{grid-column-start:13}.panel-grid .g-start-xxl-14{grid-column-start:14}.panel-grid .g-start-xxl-15{grid-column-start:15}.panel-grid .g-start-xxl-16{grid-column-start:16}.panel-grid .g-start-xxl-17{grid-column-start:17}.panel-grid .g-start-xxl-18{grid-column-start:18}.panel-grid .g-start-xxl-19{grid-column-start:19}.panel-grid .g-start-xxl-20{grid-column-start:20}.panel-grid .g-start-xxl-21{grid-column-start:21}.panel-grid .g-start-xxl-22{grid-column-start:22}.panel-grid .g-start-xxl-23{grid-column-start:23}}main{margin-top:1em;margin-bottom:1em}h1,.h1,h2,.h2{opacity:.9;margin-top:2rem;margin-bottom:1rem;font-weight:600}h1.title,.title.h1{margin-top:0}h2,.h2{border-bottom:1px solid #dee2e6;padding-bottom:.5rem}h3,.h3{font-weight:600}h3,.h3,h4,.h4{opacity:.9;margin-top:1.5rem}h5,.h5,h6,.h6{opacity:.9}.header-section-number{color:#404040}.nav-link.active .header-section-number{color:inherit}mark,.mark{padding:0em}.panel-caption,caption,.figure-caption{font-size:.9rem}.panel-caption,.figure-caption,figcaption{color:#404040}.table-caption,caption{color:#000}.quarto-layout-cell[data-ref-parent] caption{color:#404040}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#404040;font-size:.825rem}.panel-caption.margin-caption{text-align:inherit}.column-margin.column-container p{margin-bottom:0}.column-margin.column-container>*:not(.collapse){padding-top:.5em;padding-bottom:.5em;display:block}.column-margin.column-container>*.collapse:not(.show){display:none}@media(min-width: 768px){.column-margin.column-container .callout-margin-content:first-child{margin-top:4.5em}.column-margin.column-container .callout-margin-content-simple:first-child{margin-top:3.5em}}.margin-caption>*{padding-top:.5em;padding-bottom:.5em}@media(max-width: 767.98px){.quarto-layout-row{flex-direction:column}}.nav-tabs .nav-item{margin-top:1px;cursor:pointer}.tab-content{margin-top:0px;border-left:#dee2e6 1px solid;border-right:#dee2e6 1px solid;border-bottom:#dee2e6 1px solid;margin-left:0;padding:1em;margin-bottom:1em}@media(max-width: 767.98px){.layout-sidebar{margin-left:0;margin-right:0}}.panel-sidebar,.panel-sidebar .form-control,.panel-input,.panel-input .form-control,.selectize-dropdown{font-size:.9rem}.panel-sidebar .form-control,.panel-input .form-control{padding-top:.1rem}.tab-pane div.sourceCode{margin-top:0px}.tab-pane>p{padding-top:1em}.tab-content>.tab-pane:not(.active){display:none !important}div.sourceCode{background-color:#2e3440;border:1px solid #2e3440;border-radius:.25rem}pre.sourceCode{background-color:rgba(0,0,0,0)}pre.sourceCode{border:none;font-size:.875em;overflow:visible !important;padding:.4em}.callout pre.sourceCode{padding-left:0}div.sourceCode{overflow-y:hidden}.callout div.sourceCode{margin-left:initial}.blockquote{font-size:inherit;padding-left:1rem;padding-right:1.5rem;color:#404040}.blockquote h1:first-child,.blockquote .h1:first-child,.blockquote h2:first-child,.blockquote .h2:first-child,.blockquote h3:first-child,.blockquote .h3:first-child,.blockquote h4:first-child,.blockquote .h4:first-child,.blockquote h5:first-child,.blockquote .h5:first-child{margin-top:0}pre{background-color:initial;padding:initial;border:initial}p code:not(.sourceCode),li code:not(.sourceCode),td code:not(.sourceCode){background-color:#f5f5f5;padding:.2em}nav p code:not(.sourceCode),nav li code:not(.sourceCode),nav td code:not(.sourceCode){background-color:rgba(0,0,0,0);padding:0}td code:not(.sourceCode){white-space:pre-wrap}#quarto-embedded-source-code-modal>.modal-dialog{max-width:1000px;padding-left:1.75rem;padding-right:1.75rem}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body{padding:0}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body div.sourceCode{margin:0;padding:.2rem .2rem;border-radius:0px;border:none}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-header{padding:.7rem}.code-tools-button{font-size:1rem;padding:.15rem .15rem;margin-left:5px;color:#6c757d;background-color:rgba(0,0,0,0);transition:initial;cursor:pointer}.code-tools-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}.code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}.sidebar{will-change:top;transition:top 200ms linear;position:sticky;overflow-y:auto;padding-top:1.2em;max-height:100vh}.sidebar.toc-left,.sidebar.margin-sidebar{top:0px;padding-top:1em}.sidebar.toc-left>*,.sidebar.margin-sidebar>*{padding-top:.5em}.sidebar.quarto-banner-title-block-sidebar>*{padding-top:1.65em}figure .quarto-notebook-link{margin-top:.5em}.quarto-notebook-link{font-size:.75em;color:#6c757d;margin-bottom:1em;text-decoration:none;display:block}.quarto-notebook-link:hover{text-decoration:underline;color:#2b8cee}.quarto-notebook-link::before{display:inline-block;height:.75rem;width:.75rem;margin-bottom:0em;margin-right:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}.quarto-alternate-notebooks i.bi,.quarto-alternate-formats i.bi{margin-right:.4em}.quarto-notebook .cell-container{display:flex}.quarto-notebook .cell-container .cell{flex-grow:4}.quarto-notebook .cell-container .cell-decorator{padding-top:1.5em;padding-right:1em;text-align:right}.quarto-notebook h2,.quarto-notebook .h2{border-bottom:none}.sidebar .quarto-alternate-formats a,.sidebar .quarto-alternate-notebooks a{text-decoration:none}.sidebar .quarto-alternate-formats a:hover,.sidebar .quarto-alternate-notebooks a:hover{color:#2b8cee}.sidebar .quarto-alternate-notebooks h2,.sidebar .quarto-alternate-notebooks .h2,.sidebar .quarto-alternate-formats h2,.sidebar .quarto-alternate-formats .h2,.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-size:.875rem;font-weight:400;margin-bottom:.5rem;margin-top:.3rem;font-family:inherit;border-bottom:0;padding-bottom:0;padding-top:0px}.sidebar .quarto-alternate-notebooks h2,.sidebar .quarto-alternate-notebooks .h2,.sidebar .quarto-alternate-formats h2,.sidebar .quarto-alternate-formats .h2{margin-top:1rem}.sidebar nav[role=doc-toc]>ul a{border-left:1px solid #f8f5f0;padding-left:.6rem}.sidebar .quarto-alternate-notebooks h2>ul a,.sidebar .quarto-alternate-notebooks .h2>ul a,.sidebar .quarto-alternate-formats h2>ul a,.sidebar .quarto-alternate-formats .h2>ul a{border-left:none;padding-left:.6rem}.sidebar .quarto-alternate-notebooks ul a:empty,.sidebar .quarto-alternate-formats ul a:empty,.sidebar nav[role=doc-toc]>ul a:empty{display:none}.sidebar .quarto-alternate-notebooks ul,.sidebar .quarto-alternate-formats ul,.sidebar nav[role=doc-toc] ul{padding-left:0;list-style:none;font-size:.875rem;font-weight:300}.sidebar .quarto-alternate-notebooks ul li a,.sidebar .quarto-alternate-formats ul li a,.sidebar nav[role=doc-toc]>ul li a{line-height:1.1rem;padding-bottom:.2rem;padding-top:.2rem;color:inherit}.sidebar nav[role=doc-toc] ul>li>ul>li>a{padding-left:1.2em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>a{padding-left:2.4em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>a{padding-left:3.6em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:4.8em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:6em}.sidebar nav[role=doc-toc] ul>li>a.active,.sidebar nav[role=doc-toc] ul>li>ul>li>a.active{border-left:1px solid #2b8cee;color:#2b8cee !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:#2b8cee !important}kbd,.kbd{color:#000;background-color:#f8f9fa;border:1px solid;border-radius:5px;border-color:#dee2e6}div.hanging-indent{margin-left:1em;text-indent:-1em}.citation a,.footnote-ref{text-decoration:none}.footnotes ol{padding-left:1em}.tippy-content>*{margin-bottom:.7em}.tippy-content>*:last-child{margin-bottom:0}.table a{word-break:break-word}.table>thead{border-top-width:1px;border-top-color:#dee2e6;border-bottom:1px solid gray}.callout{margin-top:1.25rem;margin-bottom:1.25rem;border-radius:.25rem;overflow-wrap:break-word}.callout .callout-title-container{overflow-wrap:anywhere}.callout.callout-style-simple{padding:.4em .7em;border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout.callout-style-default{border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout .callout-body-container{flex-grow:1}.callout.callout-style-simple .callout-body{font-size:.9rem;font-weight:400}.callout.callout-style-default .callout-body{font-size:.9rem;font-weight:400}.callout.callout-titled .callout-body{margin-top:.2em}.callout:not(.no-icon).callout-titled.callout-style-simple .callout-body{padding-left:1.6em}.callout.callout-titled>.callout-header{padding-top:.2em;margin-bottom:-0.2em}.callout.callout-style-simple>div.callout-header{border-bottom:none;font-size:.9rem;font-weight:600;opacity:75%}.callout.callout-style-default>div.callout-header{border-bottom:none;font-weight:600;opacity:85%;font-size:.9rem;padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body{padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body>:first-child{margin-top:.5em}.callout>div.callout-header[data-bs-toggle=collapse]{cursor:pointer}.callout.callout-style-default .callout-header[aria-expanded=false],.callout.callout-style-default .callout-header[aria-expanded=true]{padding-top:0px;margin-bottom:0px;align-items:center}.callout.callout-titled .callout-body>:last-child:not(.sourceCode),.callout.callout-titled .callout-body>div>:last-child:not(.sourceCode){margin-bottom:.5rem}.callout:not(.callout-titled) .callout-body>:first-child,.callout:not(.callout-titled) .callout-body>div>:first-child{margin-top:.25rem}.callout:not(.callout-titled) .callout-body>:last-child,.callout:not(.callout-titled) .callout-body>div>:last-child{margin-bottom:.2rem}.callout.callout-style-simple .callout-icon::before,.callout.callout-style-simple .callout-toggle::before{height:1rem;width:1rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.callout.callout-style-default .callout-icon::before,.callout.callout-style-default .callout-toggle::before{height:.9rem;width:.9rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:.9rem .9rem}.callout.callout-style-default .callout-toggle::before{margin-top:5px}.callout .callout-btn-toggle .callout-toggle::before{transition:transform .2s linear}.callout .callout-header[aria-expanded=false] .callout-toggle::before{transform:rotate(-90deg)}.callout .callout-header[aria-expanded=true] .callout-toggle::before{transform:none}.callout.callout-style-simple:not(.no-icon) div.callout-icon-container{padding-top:.2em;padding-right:.55em}.callout.callout-style-default:not(.no-icon) div.callout-icon-container{padding-top:.1em;padding-right:.35em}.callout.callout-style-default:not(.no-icon) div.callout-title-container{margin-top:-1px}.callout.callout-style-default.callout-caution:not(.no-icon) div.callout-icon-container{padding-top:.3em;padding-right:.35em}.callout>.callout-body>.callout-icon-container>.no-icon,.callout>.callout-header>.callout-icon-container>.no-icon{display:none}div.callout.callout{border-left-color:#6c757d}div.callout.callout-style-default>.callout-header{background-color:#6c757d}div.callout-note.callout{border-left-color:#325d88}div.callout-note.callout-style-default>.callout-header{background-color:#ebeff3}div.callout-note:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-tip.callout{border-left-color:#93c54b}div.callout-tip.callout-style-default>.callout-header{background-color:#f4f9ed}div.callout-tip:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-warning.callout{border-left-color:#ffc107}div.callout-warning.callout-style-default>.callout-header{background-color:#fff9e6}div.callout-warning:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-caution.callout{border-left-color:#f47c3c}div.callout-caution.callout-style-default>.callout-header{background-color:#fef2ec}div.callout-caution:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-important.callout{border-left-color:#d9534f}div.callout-important.callout-style-default>.callout-header{background-color:#fbeeed}div.callout-important:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important .callout-toggle::before{background-image:url('data:image/svg+xml,')}.quarto-toggle-container{display:flex;align-items:center}.quarto-reader-toggle .bi::before,.quarto-color-scheme-toggle .bi::before{display:inline-block;height:1rem;width:1rem;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.sidebar-navigation{padding-left:20px}.navbar .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.quarto-sidebar-toggle{border-color:#dee2e6;border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem;border-style:solid;border-width:1px;overflow:hidden;border-top-width:0px;padding-top:0px !important}.quarto-sidebar-toggle-title{cursor:pointer;padding-bottom:2px;margin-left:.25em;text-align:center;font-weight:400;font-size:.775em}#quarto-content .quarto-sidebar-toggle{background:#fafafa}#quarto-content .quarto-sidebar-toggle-title{color:#000}.quarto-sidebar-toggle-icon{color:#dee2e6;margin-right:.5em;float:right;transition:transform .2s ease}.quarto-sidebar-toggle-icon::before{padding-top:5px}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-icon{transform:rotate(-180deg)}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-title{border-bottom:solid #dee2e6 1px}.quarto-sidebar-toggle-contents{background-color:#fff;padding-right:10px;padding-left:10px;margin-top:0px !important;transition:max-height .5s ease}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-contents{padding-top:1em;padding-bottom:10px}.quarto-sidebar-toggle:not(.expanded) .quarto-sidebar-toggle-contents{padding-top:0px !important;padding-bottom:0px}nav[role=doc-toc]{z-index:1020}#quarto-sidebar>*,nav[role=doc-toc]>*{transition:opacity .1s ease,border .1s ease}#quarto-sidebar.slow>*,nav[role=doc-toc].slow>*{transition:opacity .4s ease,border .4s ease}.quarto-color-scheme-toggle:not(.alternate).top-right .bi::before{background-image:url('data:image/svg+xml,')}.quarto-color-scheme-toggle.alternate.top-right .bi::before{background-image:url('data:image/svg+xml,')}#quarto-appendix.default{border-top:1px solid #dee2e6}#quarto-appendix.default{background-color:#fff;padding-top:1.5em;margin-top:2em;z-index:998}#quarto-appendix.default .quarto-appendix-heading{margin-top:0;line-height:1.4em;font-weight:600;opacity:.9;border-bottom:none;margin-bottom:0}#quarto-appendix.default .footnotes ol,#quarto-appendix.default .footnotes ol li>p:last-of-type,#quarto-appendix.default .quarto-appendix-contents>p:last-of-type{margin-bottom:0}#quarto-appendix.default .quarto-appendix-secondary-label{margin-bottom:.4em}#quarto-appendix.default .quarto-appendix-bibtex{font-size:.7em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-bibtex code.sourceCode{white-space:pre-wrap}#quarto-appendix.default .quarto-appendix-citeas{font-size:.9em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-heading{font-size:1em !important}#quarto-appendix.default *[role=doc-endnotes]>ol,#quarto-appendix.default .quarto-appendix-contents>*:not(h2):not(.h2){font-size:.9em}#quarto-appendix.default section{padding-bottom:1.5em}#quarto-appendix.default section *[role=doc-endnotes],#quarto-appendix.default section>*:not(a){opacity:.9;word-wrap:break-word}.btn.btn-quarto,div.cell-output-display .btn-quarto{color:#fefefe;background-color:#6c757d;border-color:#6c757d}.btn.btn-quarto:hover,div.cell-output-display .btn-quarto:hover{color:#fefefe;background-color:#828a91;border-color:#7b838a}.btn-check:focus+.btn.btn-quarto,.btn.btn-quarto:focus,.btn-check:focus+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:focus{color:#fefefe;background-color:#828a91;border-color:#7b838a;box-shadow:0 0 0 .25rem rgba(130,138,144,.5)}.btn-check:checked+.btn.btn-quarto,.btn-check:active+.btn.btn-quarto,.btn.btn-quarto:active,.btn.btn-quarto.active,.show>.btn.btn-quarto.dropdown-toggle,.btn-check:checked+div.cell-output-display .btn-quarto,.btn-check:active+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:active,div.cell-output-display .btn-quarto.active,.show>div.cell-output-display .btn-quarto.dropdown-toggle{color:#fff;background-color:#899197;border-color:#7b838a}.btn-check:checked+.btn.btn-quarto:focus,.btn-check:active+.btn.btn-quarto:focus,.btn.btn-quarto:active:focus,.btn.btn-quarto.active:focus,.show>.btn.btn-quarto.dropdown-toggle:focus,.btn-check:checked+div.cell-output-display .btn-quarto:focus,.btn-check:active+div.cell-output-display .btn-quarto:focus,div.cell-output-display .btn-quarto:active:focus,div.cell-output-display .btn-quarto.active:focus,.show>div.cell-output-display .btn-quarto.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,144,.5)}.btn.btn-quarto:disabled,.btn.btn-quarto.disabled,div.cell-output-display .btn-quarto:disabled,div.cell-output-display .btn-quarto.disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}nav.quarto-secondary-nav.color-navbar{background-color:#3980f5;color:#fff}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#fff}@media(max-width: 991.98px){body.nav-sidebar .quarto-title-banner{margin-bottom:0;padding-bottom:0}body.nav-sidebar #title-block-header{margin-block-end:0}}p.subtitle{margin-top:.25em;margin-bottom:.5em}code a:any-link{color:inherit;text-decoration-color:#6c757d}/*! light */div.observablehq table thead tr th{background-color:var(--bs-body-bg)}input,button,select,optgroup,textarea{background-color:var(--bs-body-bg)}.code-annotated .code-copy-button{margin-right:1.25em;margin-top:0;padding-bottom:0;padding-top:3px}.code-annotation-gutter-bg{background-color:#fff}.code-annotation-gutter{background-color:#2e3440}.code-annotation-gutter,.code-annotation-gutter-bg{height:100%;width:calc(20px + .5em);position:absolute;top:0;right:0}dl.code-annotation-container-grid dt{margin-right:1em;margin-top:.25rem}dl.code-annotation-container-grid dt{font-family:var(--bs-font-monospace);color:#1a1a1a;border:solid #1a1a1a 1px;border-radius:50%;height:22px;width:22px;line-height:22px;font-size:11px;text-align:center;vertical-align:middle;text-decoration:none}dl.code-annotation-container-grid dt[data-target-cell]{cursor:pointer}dl.code-annotation-container-grid dt[data-target-cell].code-annotation-active{color:#fff;border:solid #aaa 1px;background-color:#aaa}pre.code-annotation-code{padding-top:0;padding-bottom:0}pre.code-annotation-code code{z-index:3}#code-annotation-line-highlight-gutter{width:100%;border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}#code-annotation-line-highlight{margin-left:-4em;width:calc(100% + 4em);border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}code.sourceCode .code-annotation-anchor.code-annotation-active{background-color:var(--quarto-hl-normal-color, #aaaaaa);border:solid var(--quarto-hl-normal-color, #aaaaaa) 1px;color:#2e3440;font-weight:bolder}code.sourceCode .code-annotation-anchor{font-family:var(--bs-font-monospace);color:var(--quarto-hl-co-color);border:solid var(--quarto-hl-co-color) 1px;border-radius:50%;height:18px;width:18px;font-size:9px;margin-top:2px}code.sourceCode button.code-annotation-anchor{padding:2px}code.sourceCode a.code-annotation-anchor{line-height:18px;text-align:center;vertical-align:middle;cursor:default;text-decoration:none}@media print{.page-columns .column-screen-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:page-start-inset/page-end-inset;padding:1em;background:#f8f5f0;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}}.quarto-video{margin-bottom:1em}.table>thead{border-top-width:0}.table>:not(caption)>*:not(:last-child)>*{border-bottom-color:#b3b3b3;border-bottom-style:solid;border-bottom-width:1px}.table>:not(:first-child){border-top:1px solid gray;border-bottom:1px solid inherit}.table tbody{border-bottom-color:gray}a.external:after{display:inline-block;height:.75rem;width:.75rem;margin-bottom:.15em;margin-left:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}div.sourceCode code a.external:after{content:none}a.external:after:hover{cursor:pointer}.quarto-ext-icon{display:inline-block;font-size:.75em;padding-left:.3em}.code-with-filename .code-with-filename-file{margin-bottom:0;padding-bottom:2px;padding-top:2px;padding-left:.7em;border:var(--quarto-border-width) solid var(--quarto-border-color);border-radius:var(--quarto-border-radius);border-bottom:0;border-bottom-left-radius:0%;border-bottom-right-radius:0%}.code-with-filename div.sourceCode,.reveal .code-with-filename div.sourceCode{margin-top:0;border-top-left-radius:0%;border-top-right-radius:0%}.code-with-filename .code-with-filename-file pre{margin-bottom:0}.code-with-filename .code-with-filename-file,.code-with-filename .code-with-filename-file pre{background-color:rgba(219,219,219,.8)}.quarto-dark .code-with-filename .code-with-filename-file,.quarto-dark .code-with-filename .code-with-filename-file pre{background-color:#555}.code-with-filename .code-with-filename-file strong{font-weight:400}.quarto-title-banner{margin-bottom:1em;color:#fff;background:#3980f5}.quarto-title-banner .code-tools-button{color:#ccc}.quarto-title-banner .code-tools-button:hover{color:#fff}.quarto-title-banner .code-tools-button>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .quarto-title .title{font-weight:600}.quarto-title-banner .quarto-categories{margin-top:.75em}@media(min-width: 992px){.quarto-title-banner{padding-top:2.5em;padding-bottom:2.5em}}@media(max-width: 991.98px){.quarto-title-banner{padding-top:1em;padding-bottom:1em}}main.quarto-banner-title-block>section:first-child>h2,main.quarto-banner-title-block>section:first-child>.h2,main.quarto-banner-title-block>section:first-child>h3,main.quarto-banner-title-block>section:first-child>.h3,main.quarto-banner-title-block>section:first-child>h4,main.quarto-banner-title-block>section:first-child>.h4{margin-top:0}.quarto-title .quarto-categories{display:flex;flex-wrap:wrap;row-gap:.5em;column-gap:.4em;padding-bottom:.5em;margin-top:.75em}.quarto-title .quarto-categories .quarto-category{padding:.25em .75em;font-size:.65em;text-transform:uppercase;border:solid 1px;border-radius:.25rem;opacity:.6}.quarto-title .quarto-categories .quarto-category a{color:inherit}#title-block-header.quarto-title-block.default .quarto-title-meta{display:grid;grid-template-columns:repeat(2, 1fr)}#title-block-header.quarto-title-block.default .quarto-title .title{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-author-orcid img{margin-top:-5px}#title-block-header.quarto-title-block.default .quarto-description p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p,#title-block-header.quarto-title-block.default .quarto-title-authors p,#title-block-header.quarto-title-block.default .quarto-title-affiliations p{margin-bottom:.1em}#title-block-header.quarto-title-block.default .quarto-title-meta-heading{text-transform:uppercase;margin-top:1em;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-contents{font-size:.9em}#title-block-header.quarto-title-block.default .quarto-title-meta-contents a{color:#000}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p.affiliation:last-of-type{margin-bottom:.7em}#title-block-header.quarto-title-block.default p.affiliation{margin-bottom:.1em}#title-block-header.quarto-title-block.default .description,#title-block-header.quarto-title-block.default .abstract{margin-top:0}#title-block-header.quarto-title-block.default .description>p,#title-block-header.quarto-title-block.default .abstract>p{font-size:.9em}#title-block-header.quarto-title-block.default .description>p:last-of-type,#title-block-header.quarto-title-block.default .abstract>p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .description .abstract-title,#title-block-header.quarto-title-block.default .abstract .abstract-title{margin-top:1em;text-transform:uppercase;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-author{display:grid;grid-template-columns:1fr 1fr}.quarto-title-tools-only{display:flex;justify-content:right}.bg-primary{background-color:#3e3f3a !important}.bg-dark{background-color:#6c757d !important}.bg-light{background-color:#f8f5f0 !important}.sandstone,.tooltip,.dropdown-menu .dropdown-item,.pagination,.breadcrumb,.nav-pills .nav-link,.nav-tabs .nav-link,.btn,.navbar .nav-link{font-size:13px;line-height:22px;font-weight:500;text-transform:uppercase}.navbar-form input,.navbar-form .form-control{border:none}.btn:hover{border-color:rgba(0,0,0,0)}.btn-success,.btn-warning{color:#fff}.table .thead-dark th{background-color:#3e3f3a}.nav-tabs .nav-link{background-color:#f8f5f0;border-color:#dee2e6}.nav-tabs .nav-link,.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{color:#6c757d}.nav-tabs .nav-link.disabled,.nav-tabs .nav-link.disabled:hover,.nav-tabs .nav-link.disabled:focus{background-color:#f8f5f0;border-color:#dee2e6;color:#dee2e6}.nav-pills .nav-link{border:1px solid rgba(0,0,0,0);color:#6c757d}.nav-pills .nav-link.active,.nav-pills .nav-link:hover,.nav-pills .nav-link:focus{background-color:#f8f5f0;border-color:#dee2e6}.nav-pills .nav-link.disabled,.nav-pills .nav-link.disabled:hover{background-color:rgba(0,0,0,0);border-color:rgba(0,0,0,0);color:#dee2e6}.breadcrumb{border:1px solid #dee2e6}.pagination a:hover{text-decoration:none}.alert{color:#fff}.alert a,.alert .alert-link{color:#fff;text-decoration:underline}.alert-primary,.alert-primary>th,.alert-primary>td{background-color:#3980f5}.alert-secondary,.alert-secondary>th,.alert-secondary>td{background-color:#6c757d}.alert-success,.alert-success>th,.alert-success>td{background-color:#93c54b}.alert-info,.alert-info>th,.alert-info>td{background-color:#29abe0}.alert-danger,.alert-danger>th,.alert-danger>td{background-color:#d9534f}.alert-warning,.alert-warning>th,.alert-warning>td{background-color:#f47c3c}.alert-dark,.alert-dark>th,.alert-dark>td{background-color:#3e3f3a}.alert-light,.alert-light>th,.alert-light>td{background-color:#f8f5f0}.alert-light,.alert-light a:not(.btn),.alert-light .alert-link{color:#000}.badge.bg-light{color:#3e3f3a}.modal .btn-close,.toast .btn-close{background-image:url("data:image/svg+xml,")}/*# sourceMappingURL=9161419e6f82ea4435380a70856fa72b.css.map */ diff --git a/site_libs/bootstrap/bootstrap.min.js b/site_libs/bootstrap/bootstrap.min.js new file mode 100644 index 0000000..cc0a255 --- /dev/null +++ b/site_libs/bootstrap/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v5.1.3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t="transitionend",e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=(t,e,i)=>{Object.keys(i).forEach((n=>{const s=i[n],r=e[n],a=r&&o(r)?"element":null==(l=r)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}))},l=t=>!(!o(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",(()=>{p.forEach((t=>t()))})),p.push(e)):e()},_=t=>{"function"==typeof t&&t()},b=(e,i,n=!0)=>{if(!n)return void _(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),_(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},v=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,A={};let T=1;const O={mouseenter:"mouseover",mouseleave:"mouseout"},C=/^(mouseenter|mouseleave)/i,k=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function x(t){const e=L(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function D(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=L(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&j.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&j.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function I(t,e,i,n,s){const o=D(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),O[t]||t}const j={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void I(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach((i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach((o=>{if(o.includes(n)){const n=s[o];I(t,e,i,n.originalHandler,n.delegationSelector)}}))}(t,l,i,e.slice(1))}));const h=l[r]||{};Object.keys(h).forEach((i=>{const n=i.replace(E,"");if(!a||e.includes(n)){const e=h[i];I(t,l,r,e.originalHandler,e.delegationSelector)}}))},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=f(),s=P(e),o=e!==s,r=k.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach((t=>{Object.defineProperty(d,t,{get:()=>i[t]})})),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};class B{constructor(t){(t=r(t))&&(this._element=t,H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),j.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach((t=>{this[t]=null}))}_queueCallback(t,e,i=!0){b(t,e,i)}static getInstance(t){return H.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.1.3"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}}const R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;j.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),c(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class W extends B{static get NAME(){return"alert"}close(){if(j.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),j.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=W.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(W,"close"),g(W);const $='[data-bs-toggle="button"]';class z extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=z.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function q(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function F(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}j.on(document,"click.bs.button.data-api",$,(t=>{t.preventDefault();const e=t.target.closest($);z.getOrCreateInstance(e).toggle()})),g(z);const U={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${F(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${F(e)}`)},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter((t=>t.startsWith("bs"))).forEach((i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=q(t.dataset[i])})),e},getDataAttribute:(t,e)=>q(t.getAttribute(`data-bs-${F(e)}`)),offset(t){const e=t.getBoundingClientRect();return{top:e.top+window.pageYOffset,left:e.left+window.pageXOffset}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},V={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(", ");return this.find(e,t).filter((t=>!c(t)&&l(t)))}},K="carousel",X={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},Y={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Q="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z},et="slid.bs.carousel",it="active",nt=".active.carousel-item";class st extends B{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=V.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return X}static get NAME(){return K}next(){this._slide(Q)}nextWhenVisible(){!document.hidden&&l(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),V.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(s(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=V.findOne(nt,this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void j.one(this._element,et,(()=>this.to(t)));if(e===t)return this.pause(),void this.cycle();const i=t>e?Q:G;this._slide(i,this._items[t])}_getConfig(t){return t={...X,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(K,t,Y),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&j.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(j.on(this._element,"mouseenter.bs.carousel",(t=>this.pause(t))),j.on(this._element,"mouseleave.bs.carousel",(t=>this.cycle(t)))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>this._pointerEvent&&("pen"===t.pointerType||"touch"===t.pointerType),e=e=>{t(e)?this.touchStartX=e.clientX:this._pointerEvent||(this.touchStartX=e.touches[0].clientX)},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=e=>{t(e)&&(this.touchDeltaX=e.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((t=>this.cycle(t)),500+this._config.interval))};V.find(".carousel-item img",this._element).forEach((t=>{j.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()))})),this._pointerEvent?(j.on(this._element,"pointerdown.bs.carousel",(t=>e(t))),j.on(this._element,"pointerup.bs.carousel",(t=>n(t))),this._element.classList.add("pointer-event")):(j.on(this._element,"touchstart.bs.carousel",(t=>e(t))),j.on(this._element,"touchmove.bs.carousel",(t=>i(t))),j.on(this._element,"touchend.bs.carousel",(t=>n(t))))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?V.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const i=t===Q;return v(this._items,e,i,this._config.wrap)}_triggerSlideEvent(t,e){const i=this._getItemIndex(t),n=this._getItemIndex(V.findOne(nt,this._element));return j.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:i})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=V.findOne(".active",this._indicatorsElement);e.classList.remove(it),e.removeAttribute("aria-current");const i=V.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{j.trigger(this._element,et,{relatedTarget:o,direction:d,from:s,to:r})};if(this._element.classList.contains("slide")){o.classList.add(h),u(o),n.classList.add(c),o.classList.add(c);const t=()=>{o.classList.remove(c,h),o.classList.add(it),n.classList.remove(it,h,c),this._isSliding=!1,setTimeout(f,0)};this._queueCallback(t,n,!0)}else n.classList.remove(it),o.classList.add(it),this._isSliding=!1,f();a&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?m()?t===Z?G:Q:t===Z?Q:G:t}_orderToDirection(t){return[Q,G].includes(t)?m()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const i=st.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){st.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=n(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},s=this.getAttribute("data-bs-slide-to");s&&(i.interval=!1),st.carouselInterface(e,i),s&&st.getInstance(e).to(s),t.preventDefault()}}j.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",st.dataApiClickHandler),j.on(window,"load.bs.carousel.data-api",(()=>{const t=V.find('[data-bs-ride="carousel"]');for(let e=0,i=t.length;et===this._element));null!==s&&o.length&&(this._selector=s,this._triggerArray.push(e))}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return rt}static get NAME(){return ot}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t,e=[];if(this._config.parent){const t=V.find(ut,this._config.parent);e=V.find(".collapse.show, .collapse.collapsing",this._config.parent).filter((e=>!t.includes(e)))}const i=V.findOne(this._selector);if(e.length){const n=e.find((t=>i!==t));if(t=n?pt.getInstance(n):null,t&&t._isTransitioning)return}if(j.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e.forEach((e=>{i!==e&&pt.getOrCreateInstance(e,{toggle:!1}).hide(),t||H.set(e,"bs.collapse",null)}));const n=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),this._element.style[n]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const s=`scroll${n[0].toUpperCase()+n.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct,lt),this._element.style[n]="",j.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[n]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(j.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,u(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);const e=this._triggerArray.length;for(let t=0;t{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct),j.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_getConfig(t){return(t={...rt,...U.getDataAttributes(this._element),...t}).toggle=Boolean(t.toggle),t.parent=r(t.parent),a(ot,t,at),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=V.find(ut,this._config.parent);V.find(ft,this._config.parent).filter((e=>!t.includes(e))).forEach((t=>{const e=n(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}))}_addAriaAndCollapsedClass(t,e){t.length&&t.forEach((t=>{e?t.classList.remove(dt):t.classList.add(dt),t.setAttribute("aria-expanded",e)}))}static jQueryInterface(t){return this.each((function(){const e={};"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1);const i=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}j.on(document,"click.bs.collapse.data-api",ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this);V.find(e).forEach((t=>{pt.getOrCreateInstance(t,{toggle:!1}).toggle()}))})),g(pt);var mt="top",gt="bottom",_t="right",bt="left",vt="auto",yt=[mt,gt,_t,bt],wt="start",Et="end",At="clippingParents",Tt="viewport",Ot="popper",Ct="reference",kt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+Et])}),[]),Lt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+Et])}),[]),xt="beforeRead",Dt="read",St="afterRead",Nt="beforeMain",It="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",Bt=[xt,Dt,St,Nt,It,Pt,jt,Mt,Ht];function Rt(t){return t?(t.nodeName||"").toLowerCase():null}function Wt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function $t(t){return t instanceof Wt(t).Element||t instanceof Element}function zt(t){return t instanceof Wt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Wt(t).ShadowRoot||t instanceof ShadowRoot)}const Ft={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];zt(s)&&Rt(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});zt(n)&&Rt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Ut(t){return t.split("-")[0]}function Vt(t,e){var i=t.getBoundingClientRect();return{width:i.width/1,height:i.height/1,top:i.top/1,right:i.right/1,bottom:i.bottom/1,left:i.left/1,x:i.left/1,y:i.top/1}}function Kt(t){var e=Vt(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Xt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Yt(t){return Wt(t).getComputedStyle(t)}function Qt(t){return["table","td","th"].indexOf(Rt(t))>=0}function Gt(t){return(($t(t)?t.ownerDocument:t.document)||window.document).documentElement}function Zt(t){return"html"===Rt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||Gt(t)}function Jt(t){return zt(t)&&"fixed"!==Yt(t).position?t.offsetParent:null}function te(t){for(var e=Wt(t),i=Jt(t);i&&Qt(i)&&"static"===Yt(i).position;)i=Jt(i);return i&&("html"===Rt(i)||"body"===Rt(i)&&"static"===Yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&zt(t)&&"fixed"===Yt(t).position)return null;for(var i=Zt(t);zt(i)&&["html","body"].indexOf(Rt(i))<0;){var n=Yt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function ee(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var ie=Math.max,ne=Math.min,se=Math.round;function oe(t,e,i){return ie(t,ne(e,i))}function re(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ae(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const le={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=Ut(i.placement),l=ee(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return re("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ae(t,yt))}(s.padding,i),d=Kt(o),u="y"===l?mt:bt,f="y"===l?gt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=te(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,E=oe(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Xt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ce(t){return t.split("-")[1]}var he={top:"auto",right:"auto",bottom:"auto",left:"auto"};function de(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:se(se(e*n)/n)||0,y:se(se(i*n)/n)||0}}(r):"function"==typeof h?h(r):r,u=d.x,f=void 0===u?0:u,p=d.y,m=void 0===p?0:p,g=r.hasOwnProperty("x"),_=r.hasOwnProperty("y"),b=bt,v=mt,y=window;if(c){var w=te(i),E="clientHeight",A="clientWidth";w===Wt(i)&&"static"!==Yt(w=Gt(i)).position&&"absolute"===a&&(E="scrollHeight",A="scrollWidth"),w=w,s!==mt&&(s!==bt&&s!==_t||o!==Et)||(v=gt,m-=w[E]-n.height,m*=l?1:-1),s!==bt&&(s!==mt&&s!==gt||o!==Et)||(b=_t,f-=w[A]-n.width,f*=l?1:-1)}var T,O=Object.assign({position:a},c&&he);return l?Object.assign({},O,((T={})[v]=_?"0":"",T[b]=g?"0":"",T.transform=(y.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",T)):Object.assign({},O,((e={})[v]=_?m+"px":"",e[b]=g?f+"px":"",e.transform="",e))}const ue={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:Ut(e.placement),variation:ce(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,de(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,de(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var fe={passive:!0};const pe={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=Wt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,fe)})),a&&l.addEventListener("resize",i.update,fe),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,fe)})),a&&l.removeEventListener("resize",i.update,fe)}},data:{}};var me={left:"right",right:"left",bottom:"top",top:"bottom"};function ge(t){return t.replace(/left|right|bottom|top/g,(function(t){return me[t]}))}var _e={start:"end",end:"start"};function be(t){return t.replace(/start|end/g,(function(t){return _e[t]}))}function ve(t){var e=Wt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ye(t){return Vt(Gt(t)).left+ve(t).scrollLeft}function we(t){var e=Yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ee(t){return["html","body","#document"].indexOf(Rt(t))>=0?t.ownerDocument.body:zt(t)&&we(t)?t:Ee(Zt(t))}function Ae(t,e){var i;void 0===e&&(e=[]);var n=Ee(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Wt(n),r=s?[o].concat(o.visualViewport||[],we(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ae(Zt(r)))}function Te(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Oe(t,e){return e===Tt?Te(function(t){var e=Wt(t),i=Gt(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+ye(t),y:a}}(t)):zt(e)?function(t){var e=Vt(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Te(function(t){var e,i=Gt(t),n=ve(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ie(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ie(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ye(t),l=-n.scrollTop;return"rtl"===Yt(s||i).direction&&(a+=ie(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Gt(t)))}function Ce(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Ut(s):null,r=s?ce(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case mt:e={x:a,y:i.y-n.height};break;case gt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?ee(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Et:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ke(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?At:o,a=i.rootBoundary,l=void 0===a?Tt:a,c=i.elementContext,h=void 0===c?Ot:c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=re("number"!=typeof p?p:ae(p,yt)),g=h===Ot?Ct:Ot,_=t.rects.popper,b=t.elements[u?g:h],v=function(t,e,i){var n="clippingParents"===e?function(t){var e=Ae(Zt(t)),i=["absolute","fixed"].indexOf(Yt(t).position)>=0&&zt(t)?te(t):t;return $t(i)?e.filter((function(t){return $t(t)&&Xt(t,i)&&"body"!==Rt(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Oe(t,i);return e.top=ie(n.top,e.top),e.right=ne(n.right,e.right),e.bottom=ne(n.bottom,e.bottom),e.left=ie(n.left,e.left),e}),Oe(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}($t(b)?b:b.contextElement||Gt(t.elements.popper),r,l),y=Vt(t.elements.reference),w=Ce({reference:y,element:_,strategy:"absolute",placement:s}),E=Te(Object.assign({},_,w)),A=h===Ot?E:y,T={top:v.top-A.top+m.top,bottom:A.bottom-v.bottom+m.bottom,left:v.left-A.left+m.left,right:A.right-v.right+m.right},O=t.modifiersData.offset;if(h===Ot&&O){var C=O[s];Object.keys(T).forEach((function(t){var e=[_t,gt].indexOf(t)>=0?1:-1,i=[mt,gt].indexOf(t)>=0?"y":"x";T[t]+=C[i]*e}))}return T}function Le(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?Lt:l,h=ce(n),d=h?a?kt:kt.filter((function(t){return ce(t)===h})):yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ke(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Ut(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const xe={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=Ut(g),b=l||(_!==g&&p?function(t){if(Ut(t)===vt)return[];var e=ge(t);return[be(t),e,be(e)]}(g):[ge(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(Ut(i)===vt?Le(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=ke(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),N=x?L?_t:bt:L?gt:mt;y[D]>w[D]&&(N=ge(N));var I=ge(N),P=[];if(o&&P.push(S[k]<=0),a&&P.push(S[N]<=0,S[I]<=0),P.every((function(t){return t}))){T=C,A=!1;break}E.set(C,P)}if(A)for(var j=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function De(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Se(t){return[mt,_t,gt,bt].some((function(e){return t[e]>=0}))}const Ne={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=De(r,n),c=De(a,s,o),h=Se(l),d=Se(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Ie={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=Lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Ut(t),s=[bt,mt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[bt,_t].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Pe={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Ce({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},je={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Ut(e.placement),b=ce(e.placement),v=!b,y=ee(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?mt:bt,L="y"===y?gt:_t,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],N=E[y]-g[L],I=f?-T[x]/2:0,P=b===wt?A[x]:T[x],j=b===wt?-T[x]:-A[x],M=e.elements.arrow,H=f&&M?Kt(M):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},R=B[k],W=B[L],$=oe(0,A[x],H[x]),z=v?A[x]/2-I-$-R-O:P-$-R-O,q=v?-A[x]/2+I+$+W+O:j+$+W+O,F=e.elements.arrow&&te(e.elements.arrow),U=F?"y"===y?F.clientTop||0:F.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-U,X=E[y]+q-V;if(o){var Y=oe(f?ne(S,K):S,D,f?ie(N,X):N);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?mt:bt,G="x"===y?gt:_t,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=oe(f?ne(J,K):J,Z,f?ie(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function Me(t,e,i){void 0===i&&(i=!1);var n=zt(e);zt(e)&&function(t){var e=t.getBoundingClientRect();e.width,t.offsetWidth,e.height,t.offsetHeight}(e);var s,o,r=Gt(e),a=Vt(t),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(n||!n&&!i)&&(("body"!==Rt(e)||we(r))&&(l=(s=e)!==Wt(s)&&zt(s)?{scrollLeft:(o=s).scrollLeft,scrollTop:o.scrollTop}:ve(s)),zt(e)?((c=Vt(e)).x+=e.clientLeft,c.y+=e.clientTop):r&&(c.x=ye(r))),{x:a.left+l.scrollLeft-c.x,y:a.top+l.scrollTop-c.y,width:a.width,height:a.height}}function He(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Re(){for(var t=arguments.length,e=new Array(t),i=0;ij.on(t,"mouseover",d))),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Je),this._element.classList.add(Je),j.trigger(this._element,"shown.bs.dropdown",t)}hide(){if(c(this._element)||!this._isShown(this._menu))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){j.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._popper&&this._popper.destroy(),this._menu.classList.remove(Je),this._element.classList.remove(Je),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),j.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},a(Ue,t,this.constructor.DefaultType),"object"==typeof t.reference&&!o(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ue.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(t){if(void 0===Fe)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:o(this._config.reference)?e=r(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find((t=>"applyStyles"===t.name&&!1===t.enabled));this._popper=qe(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}_isShown(t=this._element){return t.classList.contains(Je)}_getMenuElement(){return V.next(this._element,ei)[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ri;if(t.classList.contains("dropstart"))return ai;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ni:ii:e?oi:si}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=V.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(l);i.length&&v(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=V.find(ti);for(let i=0,n=e.length;ie+t)),this._setElementAttributes(di,"paddingRight",(e=>e+t)),this._setElementAttributes(ui,"marginRight",(e=>e-t))}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t)[e];t.style[e]=`${i(Number.parseFloat(s))}px`}))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(di,"paddingRight"),this._resetElementAttributes(ui,"marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)}))}_applyManipulationCallback(t,e){o(t)?e(t):V.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const pi={className:"modal-backdrop",isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},mi={className:"string",isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"},gi="show",_i="mousedown.bs.backdrop";class bi{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&u(this._getElement()),this._getElement().classList.add(gi),this._emulateAnimation((()=>{_(t)}))):_(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove(gi),this._emulateAnimation((()=>{this.dispose(),_(t)}))):_(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...pi,..."object"==typeof t?t:{}}).rootElement=r(t.rootElement),a("backdrop",t,mi),t}_append(){this._isAppended||(this._config.rootElement.append(this._getElement()),j.on(this._getElement(),_i,(()=>{_(this._config.clickCallback)})),this._isAppended=!0)}dispose(){this._isAppended&&(j.off(this._element,_i),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){b(t,this._getElement(),this._config.isAnimated)}}const vi={trapElement:null,autofocus:!0},yi={trapElement:"element",autofocus:"boolean"},wi=".bs.focustrap",Ei="backward";class Ai{constructor(t){this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}activate(){const{trapElement:t,autofocus:e}=this._config;this._isActive||(e&&t.focus(),j.off(document,wi),j.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),j.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,j.off(document,wi))}_handleFocusin(t){const{target:e}=t,{trapElement:i}=this._config;if(e===document||e===i||i.contains(e))return;const n=V.focusableChildren(i);0===n.length?i.focus():this._lastTabNavDirection===Ei?n[n.length-1].focus():n[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ei:"forward")}_getConfig(t){return t={...vi,..."object"==typeof t?t:{}},a("focustrap",t,yi),t}}const Ti="modal",Oi="Escape",Ci={backdrop:!0,keyboard:!0,focus:!0},ki={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"},Li="hidden.bs.modal",xi="show.bs.modal",Di="resize.bs.modal",Si="click.dismiss.bs.modal",Ni="keydown.dismiss.bs.modal",Ii="mousedown.dismiss.bs.modal",Pi="modal-open",ji="show",Mi="modal-static";class Hi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=V.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new fi}static get Default(){return Ci}static get NAME(){return Ti}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||j.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add(Pi),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),j.on(this._dialog,Ii,(()=>{j.one(this._element,"mouseup.dismiss.bs.modal",(t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)}))})),this._showBackdrop((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;if(j.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const t=this._isAnimated();t&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),this._focustrap.deactivate(),this._element.classList.remove(ji),j.off(this._element,Si),j.off(this._dialog,Ii),this._queueCallback((()=>this._hideModal()),this._element,t)}dispose(){[window,this._dialog].forEach((t=>j.off(t,".bs.modal"))),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_getConfig(t){return t={...Ci,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Ti,t,ki),t}_showElement(t){const e=this._isAnimated(),i=V.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,i&&(i.scrollTop=0),e&&u(this._element),this._element.classList.add(ji),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,j.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,e)}_setEscapeEvent(){this._isShown?j.on(this._element,Ni,(t=>{this._config.keyboard&&t.key===Oi?(t.preventDefault(),this.hide()):this._config.keyboard||t.key!==Oi||this._triggerBackdropTransition()})):j.off(this._element,Ni)}_setResizeEvent(){this._isShown?j.on(window,Di,(()=>this._adjustDialog())):j.off(window,Di)}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Pi),this._resetAdjustments(),this._scrollBar.reset(),j.trigger(this._element,Li)}))}_showBackdrop(t){j.on(this._element,Si,(t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())})),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(j.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains(Mi)||(n||(i.overflowY="hidden"),t.add(Mi),this._queueCallback((()=>{t.remove(Mi),n||this._queueCallback((()=>{i.overflowY=""}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!m()||i&&!t&&m())&&(this._element.style.paddingLeft=`${e}px`),(i&&!t&&!m()||!i&&t&&m())&&(this._element.style.paddingRight=`${e}px`)}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}j.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),j.one(e,xi,(t=>{t.defaultPrevented||j.one(e,Li,(()=>{l(this)&&this.focus()}))}));const i=V.findOne(".modal.show");i&&Hi.getInstance(i).hide(),Hi.getOrCreateInstance(e).toggle(this)})),R(Hi),g(Hi);const Bi="offcanvas",Ri={backdrop:!0,keyboard:!0,scroll:!1},Wi={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"},$i="show",zi=".offcanvas.show",qi="hidden.bs.offcanvas";class Fi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get NAME(){return Bi}static get Default(){return Ri}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||j.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||(new fi).hide(),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add($i),this._queueCallback((()=>{this._config.scroll||this._focustrap.activate(),j.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(j.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.remove($i),this._backdrop.hide(),this._queueCallback((()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new fi).reset(),j.trigger(this._element,qi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_getConfig(t){return t={...Ri,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Bi,t,Wi),t}_initializeBackDrop(){return new bi({className:"offcanvas-backdrop",isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_addEventListeners(){j.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()}))}static jQueryInterface(t){return this.each((function(){const e=Fi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}j.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this))return;j.one(e,qi,(()=>{l(this)&&this.focus()}));const i=V.findOne(zi);i&&i!==e&&Fi.getInstance(i).hide(),Fi.getOrCreateInstance(e).toggle(this)})),j.on(window,"load.bs.offcanvas.data-api",(()=>V.find(zi).forEach((t=>Fi.getOrCreateInstance(t).show())))),R(Fi),g(Fi);const Ui=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Vi=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Ki=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Xi=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!Ui.has(i)||Boolean(Vi.test(t.nodeValue)||Ki.test(t.nodeValue));const n=e.filter((t=>t instanceof RegExp));for(let t=0,e=n.length;t{Xi(t,r)||i.removeAttribute(t.nodeName)}))}return n.body.innerHTML}const Qi="tooltip",Gi=new Set(["sanitize","allowList","sanitizeFn"]),Zi={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},Ji={AUTO:"auto",TOP:"top",RIGHT:m()?"left":"right",BOTTOM:"bottom",LEFT:m()?"right":"left"},tn={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},en={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},nn="fade",sn="show",on="show",rn="out",an=".tooltip-inner",ln=".modal",cn="hide.bs.modal",hn="hover",dn="focus";class un extends B{constructor(t,e){if(void 0===Fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return tn}static get NAME(){return Qi}static get Event(){return en}static get DefaultType(){return Zi}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains(sn))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),j.off(this._element.closest(ln),cn,this._hideModalHandler),this.tip&&this.tip.remove(),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=j.trigger(this._element,this.constructor.Event.SHOW),e=h(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;"tooltip"===this.constructor.NAME&&this.tip&&this.getTitle()!==this.tip.querySelector(an).innerHTML&&(this._disposePopper(),this.tip.remove(),this.tip=null);const n=this.getTipElement(),s=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME);n.setAttribute("id",s),this._element.setAttribute("aria-describedby",s),this._config.animation&&n.classList.add(nn);const o="function"==typeof this._config.placement?this._config.placement.call(this,n,this._element):this._config.placement,r=this._getAttachment(o);this._addAttachmentClass(r);const{container:a}=this._config;H.set(n,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(a.append(n),j.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=qe(this._element,n,this._getPopperConfig(r)),n.classList.add(sn);const l=this._resolvePossibleFunction(this._config.customClass);l&&n.classList.add(...l.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>{j.on(t,"mouseover",d)}));const c=this.tip.classList.contains(nn);this._queueCallback((()=>{const t=this._hoverState;this._hoverState=null,j.trigger(this._element,this.constructor.Event.SHOWN),t===rn&&this._leave(null,this)}),this.tip,c)}hide(){if(!this._popper)return;const t=this.getTipElement();if(j.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove(sn),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains(nn);this._queueCallback((()=>{this._isWithActiveTrigger()||(this._hoverState!==on&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),j.trigger(this._element,this.constructor.Event.HIDDEN),this._disposePopper())}),this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");t.innerHTML=this._config.template;const e=t.children[0];return this.setContent(e),e.classList.remove(nn,sn),this.tip=e,this.tip}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),an)}_sanitizeAndSetContent(t,e,i){const n=V.findOne(i,t);e||!n?this.setElementContent(n,e):n.remove()}setElementContent(t,e){if(null!==t)return o(e)?(e=r(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.append(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Yi(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){const t=this._element.getAttribute("data-bs-original-title")||this._config.title;return this._resolvePossibleFunction(t)}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){return e||this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(t)}`)}_getAttachment(t){return Ji[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach((t=>{if("click"===t)j.on(this._element,this.constructor.Event.CLICK,this._config.selector,(t=>this.toggle(t)));else if("manual"!==t){const e=t===hn?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i=t===hn?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;j.on(this._element,e,this._config.selector,(t=>this._enter(t))),j.on(this._element,i,this._config.selector,(t=>this._leave(t)))}})),this._hideModalHandler=()=>{this._element&&this.hide()},j.on(this._element.closest(ln),cn,this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?dn:hn]=!0),e.getTipElement().classList.contains(sn)||e._hoverState===on?e._hoverState=on:(clearTimeout(e._timeout),e._hoverState=on,e._config.delay&&e._config.delay.show?e._timeout=setTimeout((()=>{e._hoverState===on&&e.show()}),e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?dn:hn]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=rn,e._config.delay&&e._config.delay.hide?e._timeout=setTimeout((()=>{e._hoverState===rn&&e.hide()}),e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach((t=>{Gi.has(t)&&delete e[t]})),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),a(Qi,t,this.constructor.DefaultType),t.sanitize&&(t.template=Yi(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`,"g"),i=t.getAttribute("class").match(e);null!==i&&i.length>0&&i.map((t=>t.trim())).forEach((e=>t.classList.remove(e)))}_getBasicClassPrefix(){return"bs-tooltip"}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn={...un.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},pn={...un.DefaultType,content:"(string|element|function)"},mn={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class gn extends un{static get Default(){return fn}static get NAME(){return"popover"}static get Event(){return mn}static get DefaultType(){return pn}isWithContent(){return this.getTitle()||this._getContent()}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),".popover-header"),this._sanitizeAndSetContent(t,this._getContent(),".popover-body")}_getContent(){return this._resolvePossibleFunction(this._config.content)}_getBasicClassPrefix(){return"bs-popover"}static jQueryInterface(t){return this.each((function(){const e=gn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(gn);const _n="scrollspy",bn={offset:10,method:"auto",target:""},vn={offset:"number",method:"string",target:"(string|element)"},yn="active",wn=".nav-link, .list-group-item, .dropdown-item",En="position";class An extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,j.on(this._scrollElement,"scroll.bs.scrollspy",(()=>this._process())),this.refresh(),this._process()}static get Default(){return bn}static get NAME(){return _n}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":En,e="auto"===this._config.method?t:this._config.method,n=e===En?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),V.find(wn,this._config.target).map((t=>{const s=i(t),o=s?V.findOne(s):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[U[e](o).top+n,s]}return null})).filter((t=>t)).sort(((t,e)=>t[0]-e[0])).forEach((t=>{this._offsets.push(t[0]),this._targets.push(t[1])}))}dispose(){j.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){return(t={...bn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target=r(t.target)||document.documentElement,a(_n,t,vn),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`)),i=V.findOne(e.join(","),this._config.target);i.classList.add(yn),i.classList.contains("dropdown-item")?V.findOne(".dropdown-toggle",i.closest(".dropdown")).classList.add(yn):V.parents(i,".nav, .list-group").forEach((t=>{V.prev(t,".nav-link, .list-group-item").forEach((t=>t.classList.add(yn))),V.prev(t,".nav-item").forEach((t=>{V.children(t,".nav-link").forEach((t=>t.classList.add(yn)))}))})),j.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){V.find(wn,this._config.target).filter((t=>t.classList.contains(yn))).forEach((t=>t.classList.remove(yn)))}static jQueryInterface(t){return this.each((function(){const e=An.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(window,"load.bs.scrollspy.data-api",(()=>{V.find('[data-bs-spy="scroll"]').forEach((t=>new An(t)))})),g(An);const Tn="active",On="fade",Cn="show",kn=".active",Ln=":scope > li > .active";class xn extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains(Tn))return;let t;const e=n(this._element),i=this._element.closest(".nav, .list-group");if(i){const e="UL"===i.nodeName||"OL"===i.nodeName?Ln:kn;t=V.find(e,i),t=t[t.length-1]}const s=t?j.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(j.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==s&&s.defaultPrevented)return;this._activate(this._element,i);const o=()=>{j.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),j.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,i){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?V.children(e,kn):V.find(Ln,e))[0],s=i&&n&&n.classList.contains(On),o=()=>this._transitionComplete(t,n,i);n&&s?(n.classList.remove(Cn),this._queueCallback(o,t,!0)):o()}_transitionComplete(t,e,i){if(e){e.classList.remove(Tn);const t=V.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove(Tn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add(Tn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),u(t),t.classList.contains(On)&&t.classList.add(Cn);let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&V.find(".dropdown-toggle",e).forEach((t=>t.classList.add(Tn))),t.setAttribute("aria-expanded",!0)}i&&i()}static jQueryInterface(t){return this.each((function(){const e=xn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this)||xn.getOrCreateInstance(this).show()})),g(xn);const Dn="toast",Sn="hide",Nn="show",In="showing",Pn={animation:"boolean",autohide:"boolean",delay:"number"},jn={animation:!0,autohide:!0,delay:5e3};class Mn extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Pn}static get Default(){return jn}static get NAME(){return Dn}show(){j.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Sn),u(this._element),this._element.classList.add(Nn),this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.remove(In),j.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this._element.classList.contains(Nn)&&(j.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.add(Sn),this._element.classList.remove(In),this._element.classList.remove(Nn),j.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains(Nn)&&this._element.classList.remove(Nn),super.dispose()}_getConfig(t){return t={...jn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},a(Dn,t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){j.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),j.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Mn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(Mn),g(Mn),{Alert:W,Button:z,Carousel:st,Collapse:pt,Dropdown:hi,Modal:Hi,Offcanvas:Fi,Popover:gn,ScrollSpy:An,Tab:xn,Toast:Mn,Tooltip:un}})); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/site_libs/clipboard/clipboard.min.js b/site_libs/clipboard/clipboard.min.js new file mode 100644 index 0000000..1103f81 --- /dev/null +++ b/site_libs/clipboard/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1.anchorjs-link,.anchorjs-link:focus{opacity:1}",u.sheet.cssRules.length),u.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",u.sheet.cssRules.length),u.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',u.sheet.cssRules.length)),u=document.querySelectorAll("[id]"),t=[].map.call(u,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); +// @license-end \ No newline at end of file diff --git a/site_libs/quarto-html/popper.min.js b/site_libs/quarto-html/popper.min.js new file mode 100644 index 0000000..2269d66 --- /dev/null +++ b/site_libs/quarto-html/popper.min.js @@ -0,0 +1,6 @@ +/** + * @popperjs/core v2.11.4 - MIT License + */ + +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(e,t){void 0===t&&(t=!1);var n=e.getBoundingClientRect(),o=1,i=1;if(r(e)&&t){var a=e.offsetHeight,f=e.offsetWidth;f>0&&(o=s(n.width)/f||1),a>0&&(i=s(n.height)/a||1)}return{width:n.width/o,height:n.height/i,top:n.top/i,right:n.right/o,bottom:n.bottom/i,left:n.left/o,x:n.left/o,y:n.top/i}}function c(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function p(e){return e?(e.nodeName||"").toLowerCase():null}function u(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function l(e){return f(u(e)).left+c(e).scrollLeft}function d(e){return t(e).getComputedStyle(e)}function h(e){var t=d(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function m(e,n,o){void 0===o&&(o=!1);var i,a,d=r(n),m=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),v=u(n),g=f(e,m),y={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(d||!d&&!o)&&(("body"!==p(n)||h(v))&&(y=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:c(i)),r(n)?((b=f(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):v&&(b.x=l(v))),{x:g.left+y.scrollLeft-b.x,y:g.top+y.scrollTop-b.y,width:g.width,height:g.height}}function v(e){var t=f(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function g(e){return"html"===p(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||u(e)}function y(e){return["html","body","#document"].indexOf(p(e))>=0?e.ownerDocument.body:r(e)&&h(e)?e:y(g(e))}function b(e,n){var r;void 0===n&&(n=[]);var o=y(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],h(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(b(g(s)))}function x(e){return["table","td","th"].indexOf(p(e))>=0}function w(e){return r(e)&&"fixed"!==d(e).position?e.offsetParent:null}function O(e){for(var n=t(e),i=w(e);i&&x(i)&&"static"===d(i).position;)i=w(i);return i&&("html"===p(i)||"body"===p(i)&&"static"===d(i).position)?n:i||function(e){var t=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&r(e)&&"fixed"===d(e).position)return null;var n=g(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(p(n))<0;){var i=d(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var j="top",E="bottom",D="right",A="left",L="auto",P=[j,E,D,A],M="start",k="end",W="viewport",B="popper",H=P.reduce((function(e,t){return e.concat([t+"-"+M,t+"-"+k])}),[]),T=[].concat(P,[L]).reduce((function(e,t){return e.concat([t,t+"-"+M,t+"-"+k])}),[]),R=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function S(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function q(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function V(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function N(e,r){return r===W?V(function(e){var n=t(e),r=u(e),o=n.visualViewport,i=r.clientWidth,a=r.clientHeight,s=0,f=0;return o&&(i=o.width,a=o.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(s=o.offsetLeft,f=o.offsetTop)),{width:i,height:a,x:s+l(e),y:f}}(e)):n(r)?function(e){var t=f(e);return t.top=t.top+e.clientTop,t.left=t.left+e.clientLeft,t.bottom=t.top+e.clientHeight,t.right=t.left+e.clientWidth,t.width=e.clientWidth,t.height=e.clientHeight,t.x=t.left,t.y=t.top,t}(r):V(function(e){var t,n=u(e),r=c(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+l(e),p=-r.scrollTop;return"rtl"===d(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:p}}(u(e)))}function I(e,t,o){var s="clippingParents"===t?function(e){var t=b(g(e)),o=["absolute","fixed"].indexOf(d(e).position)>=0&&r(e)?O(e):e;return n(o)?t.filter((function(e){return n(e)&&q(e,o)&&"body"!==p(e)})):[]}(e):[].concat(t),f=[].concat(s,[o]),c=f[0],u=f.reduce((function(t,n){var r=N(e,n);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),N(e,c));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function _(e){return e.split("-")[1]}function F(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function U(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?_(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case j:t={x:s,y:n.y-r.height};break;case E:t={x:s,y:n.y+n.height};break;case D:t={x:n.x+n.width,y:f};break;case A:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?F(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case M:t[c]=t[c]-(n[p]/2-r[p]/2);break;case k:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function z(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function X(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function Y(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.boundary,s=void 0===a?"clippingParents":a,c=r.rootBoundary,p=void 0===c?W:c,l=r.elementContext,d=void 0===l?B:l,h=r.altBoundary,m=void 0!==h&&h,v=r.padding,g=void 0===v?0:v,y=z("number"!=typeof g?g:X(g,P)),b=d===B?"reference":B,x=e.rects.popper,w=e.elements[m?b:d],O=I(n(w)?w:w.contextElement||u(e.elements.popper),s,p),A=f(e.elements.reference),L=U({reference:A,element:x,strategy:"absolute",placement:i}),M=V(Object.assign({},x,L)),k=d===B?M:A,H={top:O.top-k.top+y.top,bottom:k.bottom-O.bottom+y.bottom,left:O.left-k.left+y.left,right:k.right-O.right+y.right},T=e.modifiersData.offset;if(d===B&&T){var R=T[i];Object.keys(H).forEach((function(e){var t=[D,E].indexOf(e)>=0?1:-1,n=[j,E].indexOf(e)>=0?"y":"x";H[e]+=R[n]*t}))}return H}var G={placement:"bottom",modifiers:[],strategy:"absolute"};function J(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[A,D].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},ie={left:"right",right:"left",bottom:"top",top:"bottom"};function ae(e){return e.replace(/left|right|bottom|top/g,(function(e){return ie[e]}))}var se={start:"end",end:"start"};function fe(e){return e.replace(/start|end/g,(function(e){return se[e]}))}function ce(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?T:f,p=_(r),u=p?s?H:H.filter((function(e){return _(e)===p})):P,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=Y(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var pe={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,g=C(v),y=f||(g===v||!h?[ae(v)]:function(e){if(C(e)===L)return[];var t=ae(e);return[fe(e),t,fe(t)]}(v)),b=[v].concat(y).reduce((function(e,n){return e.concat(C(n)===L?ce(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,P=!0,k=b[0],W=0;W=0,S=R?"width":"height",q=Y(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),V=R?T?D:A:T?E:j;x[S]>w[S]&&(V=ae(V));var N=ae(V),I=[];if(i&&I.push(q[H]<=0),s&&I.push(q[V]<=0,q[N]<=0),I.every((function(e){return e}))){k=B,P=!1;break}O.set(B,I)}if(P)for(var F=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return k=t,"break"},U=h?3:1;U>0;U--){if("break"===F(U))break}t.placement!==k&&(t.modifiersData[r]._skip=!0,t.placement=k,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function ue(e,t,n){return i(e,a(t,n))}var le={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,g=n.tetherOffset,y=void 0===g?0:g,b=Y(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=_(t.placement),L=!w,P=F(x),k="x"===P?"y":"x",W=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,q={x:0,y:0};if(W){if(s){var V,N="y"===P?j:A,I="y"===P?E:D,U="y"===P?"height":"width",z=W[P],X=z+b[N],G=z-b[I],J=m?-H[U]/2:0,K=w===M?B[U]:H[U],Q=w===M?-H[U]:-B[U],Z=t.elements.arrow,$=m&&Z?v(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=ue(0,B[U],$[U]),oe=L?B[U]/2-J-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=L?-B[U]/2+J+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&O(t.elements.arrow),se=ae?"y"===P?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(V=null==S?void 0:S[P])?V:0,ce=z+ie-fe,pe=ue(m?a(X,z+oe-fe-se):X,z,m?i(G,ce):G);W[P]=pe,q[P]=pe-z}if(c){var le,de="x"===P?j:A,he="x"===P?E:D,me=W[k],ve="y"===k?"height":"width",ge=me+b[de],ye=me-b[he],be=-1!==[j,A].indexOf(x),xe=null!=(le=null==S?void 0:S[k])?le:0,we=be?ge:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ye,je=m&&be?function(e,t,n){var r=ue(e,t,n);return r>n?n:r}(we,me,Oe):ue(m?we:ge,me,m?Oe:ye);W[k]=je,q[k]=je-me}t.modifiersData[r]=q}},requiresIfExists:["offset"]};var de={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=F(s),c=[A,D].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return z("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:X(e,P))}(o.padding,n),u=v(i),l="y"===f?j:A,d="y"===f?E:D,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],g=O(i),y=g?"y"===f?g.clientHeight||0:g.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],L=y/2-u[c]/2+b,M=ue(x,L,w),k=f;n.modifiersData[r]=((t={})[k]=M,t.centerOffset=M-L,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&q(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function he(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function me(e){return[j,D,E,A].some((function(t){return e[t]>=0}))}var ve={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=Y(t,{elementContext:"reference"}),s=Y(t,{altBoundary:!0}),f=he(a,r),c=he(s,o,i),p=me(f),u=me(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},ge=K({defaultModifiers:[Z,$,ne,re]}),ye=[Z,$,ne,re,oe,pe,le,de,ve],be=K({defaultModifiers:ye});e.applyStyles=re,e.arrow=de,e.computeStyles=ne,e.createPopper=be,e.createPopperLite=ge,e.defaultModifiers=ye,e.detectOverflow=Y,e.eventListeners=Z,e.flip=pe,e.hide=ve,e.offset=oe,e.popperGenerator=K,e.popperOffsets=$,e.preventOverflow=le,Object.defineProperty(e,"__esModule",{value:!0})})); + diff --git a/site_libs/quarto-html/quarto-syntax-highlighting-dark.css b/site_libs/quarto-html/quarto-syntax-highlighting-dark.css new file mode 100644 index 0000000..87a2c1f --- /dev/null +++ b/site_libs/quarto-html/quarto-syntax-highlighting-dark.css @@ -0,0 +1,180 @@ +/* quarto syntax highlight colors */ +:root { + --quarto-hl-al-color: #ff5555; + --quarto-hl-an-color: #75715e; + --quarto-hl-at-color: #f92672; + --quarto-hl-bn-color: #ae81ff; + --quarto-hl-bu-color: #66D9EF; + --quarto-hl-ch-color: #e6db74; + --quarto-hl-co-color: #75715e; + --quarto-hl-cv-color: #75715e; + --quarto-hl-cn-color: #ae81ff; + --quarto-hl-cf-color: #f92672; + --quarto-hl-dt-color: #66d9ef; + --quarto-hl-dv-color: #ae81ff; + --quarto-hl-do-color: #75715e; + --quarto-hl-er-color: #ff5555; + --quarto-hl-ex-color: #a6e22e; + --quarto-hl-fl-color: #ae81ff; + --quarto-hl-fu-color: #a6e22e; + --quarto-hl-im-color: #f92672; + --quarto-hl-in-color: #f1fa8c; + --quarto-hl-kw-color: #f92672; + --quarto-hl-op-color: #f8f8f2; + --quarto-hl-pp-color: #f92672; + --quarto-hl-re-color: #75715e; + --quarto-hl-sc-color: #ae81ff; + --quarto-hl-ss-color: #e6db74; + --quarto-hl-st-color: #e6db74; + --quarto-hl-va-color: #f8f8f2; + --quarto-hl-vs-color: #e6db74; + --quarto-hl-wa-color: #ff5555; +} + +/* other quarto variables */ +:root { + --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +code span.al { + font-weight: bold; + color: #ff5555; +} + +code span.an { + color: #75715e; +} + +code span.at { + color: #f92672; +} + +code span.bn { + color: #ae81ff; +} + +code span.bu { + color: #66D9EF; +} + +code span.ch { + color: #e6db74; +} + +code span.co { + color: #75715e; +} + +code span.cv { + color: #75715e; +} + +code span.cn { + color: #ae81ff; +} + +code span.cf { + color: #f92672; +} + +code span.dt { + font-style: italic; + color: #66d9ef; +} + +code span.dv { + color: #ae81ff; +} + +code span.do { + color: #75715e; +} + +code span.er { + color: #ff5555; + text-decoration: underline; +} + +code span.ex { + font-weight: bold; + color: #a6e22e; +} + +code span.fl { + color: #ae81ff; +} + +code span.fu { + color: #a6e22e; +} + +code span.im { + color: #f92672; +} + +code span.in { + color: #f1fa8c; +} + +code span.kw { + color: #f92672; +} + +pre > code.sourceCode > span { + color: #f8f8f2; +} + +code span { + color: #f8f8f2; +} + +code.sourceCode > span { + color: #f8f8f2; +} + +div.sourceCode, +div.sourceCode pre.sourceCode { + color: #f8f8f2; +} + +code span.op { + color: #f8f8f2; +} + +code span.pp { + color: #f92672; +} + +code span.re { + color: #75715e; +} + +code span.sc { + color: #ae81ff; +} + +code span.ss { + color: #e6db74; +} + +code span.st { + color: #e6db74; +} + +code span.va { + color: #f8f8f2; +} + +code span.vs { + color: #e6db74; +} + +code span.wa { + color: #ff5555; +} + +.prevent-inlining { + content: " code.sourceCode > span { + color: #f8f8f2; +} + +code span { + color: #f8f8f2; +} + +code.sourceCode > span { + color: #f8f8f2; +} + +div.sourceCode, +div.sourceCode pre.sourceCode { + color: #f8f8f2; +} + +code span.op { + color: #f8f8f2; +} + +code span.pp { + color: #f92672; +} + +code span.re { + color: #75715e; +} + +code span.sc { + color: #ae81ff; +} + +code span.ss { + color: #e6db74; +} + +code span.st { + color: #e6db74; +} + +code span.va { + color: #f8f8f2; +} + +code span.vs { + color: #e6db74; +} + +code span.wa { + color: #ff5555; +} + +.prevent-inlining { + content: " { + // Find any conflicting margin elements and add margins to the + // top to prevent overlap + const marginChildren = window.document.querySelectorAll( + ".column-margin.column-container > * " + ); + + let lastBottom = 0; + for (const marginChild of marginChildren) { + if (marginChild.offsetParent !== null) { + // clear the top margin so we recompute it + marginChild.style.marginTop = null; + const top = marginChild.getBoundingClientRect().top + window.scrollY; + console.log({ + childtop: marginChild.getBoundingClientRect().top, + scroll: window.scrollY, + top, + lastBottom, + }); + if (top < lastBottom) { + const margin = lastBottom - top; + marginChild.style.marginTop = `${margin}px`; + } + const styles = window.getComputedStyle(marginChild); + const marginTop = parseFloat(styles["marginTop"]); + + console.log({ + top, + height: marginChild.getBoundingClientRect().height, + marginTop, + total: top + marginChild.getBoundingClientRect().height + marginTop, + }); + lastBottom = top + marginChild.getBoundingClientRect().height + marginTop; + } + } +}; + +window.document.addEventListener("DOMContentLoaded", function (_event) { + // Recompute the position of margin elements anytime the body size changes + if (window.ResizeObserver) { + const resizeObserver = new window.ResizeObserver( + throttle(layoutMarginEls, 50) + ); + resizeObserver.observe(window.document.body); + } + + const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]'); + const sidebarEl = window.document.getElementById("quarto-sidebar"); + const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left"); + const marginSidebarEl = window.document.getElementById( + "quarto-margin-sidebar" + ); + // function to determine whether the element has a previous sibling that is active + const prevSiblingIsActiveLink = (el) => { + const sibling = el.previousElementSibling; + if (sibling && sibling.tagName === "A") { + return sibling.classList.contains("active"); + } else { + return false; + } + }; + + // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior) + function fireSlideEnter(e) { + const event = window.document.createEvent("Event"); + event.initEvent("slideenter", true, true); + window.document.dispatchEvent(event); + } + const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]'); + tabs.forEach((tab) => { + tab.addEventListener("shown.bs.tab", fireSlideEnter); + }); + + // fire slideEnter for tabby tab activations (for htmlwidget resize behavior) + document.addEventListener("tabby", fireSlideEnter, false); + + // Track scrolling and mark TOC links as active + // get table of contents and sidebar (bail if we don't have at least one) + const tocLinks = tocEl + ? [...tocEl.querySelectorAll("a[data-scroll-target]")] + : []; + const makeActive = (link) => tocLinks[link].classList.add("active"); + const removeActive = (link) => tocLinks[link].classList.remove("active"); + const removeAllActive = () => + [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link)); + + // activate the anchor for a section associated with this TOC entry + tocLinks.forEach((link) => { + link.addEventListener("click", () => { + if (link.href.indexOf("#") !== -1) { + const anchor = link.href.split("#")[1]; + const heading = window.document.querySelector( + `[data-anchor-id=${anchor}]` + ); + if (heading) { + // Add the class + heading.classList.add("reveal-anchorjs-link"); + + // function to show the anchor + const handleMouseout = () => { + heading.classList.remove("reveal-anchorjs-link"); + heading.removeEventListener("mouseout", handleMouseout); + }; + + // add a function to clear the anchor when the user mouses out of it + heading.addEventListener("mouseout", handleMouseout); + } + } + }); + }); + + const sections = tocLinks.map((link) => { + const target = link.getAttribute("data-scroll-target"); + if (target.startsWith("#")) { + return window.document.getElementById(decodeURI(`${target.slice(1)}`)); + } else { + return window.document.querySelector(decodeURI(`${target}`)); + } + }); + + const sectionMargin = 200; + let currentActive = 0; + // track whether we've initialized state the first time + let init = false; + + const updateActiveLink = () => { + // The index from bottom to top (e.g. reversed list) + let sectionIndex = -1; + if ( + window.innerHeight + window.pageYOffset >= + window.document.body.offsetHeight + ) { + sectionIndex = 0; + } else { + sectionIndex = [...sections].reverse().findIndex((section) => { + if (section) { + return window.pageYOffset >= section.offsetTop - sectionMargin; + } else { + return false; + } + }); + } + if (sectionIndex > -1) { + const current = sections.length - sectionIndex - 1; + if (current !== currentActive) { + removeAllActive(); + currentActive = current; + makeActive(current); + if (init) { + window.dispatchEvent(sectionChanged); + } + init = true; + } + } + }; + + const inHiddenRegion = (top, bottom, hiddenRegions) => { + for (const region of hiddenRegions) { + if (top <= region.bottom && bottom >= region.top) { + return true; + } + } + return false; + }; + + const categorySelector = "header.quarto-title-block .quarto-category"; + const activateCategories = (href) => { + // Find any categories + // Surround them with a link pointing back to: + // #category=Authoring + try { + const categoryEls = window.document.querySelectorAll(categorySelector); + for (const categoryEl of categoryEls) { + const categoryText = categoryEl.textContent; + if (categoryText) { + const link = `${href}#category=${encodeURIComponent(categoryText)}`; + const linkEl = window.document.createElement("a"); + linkEl.setAttribute("href", link); + for (const child of categoryEl.childNodes) { + linkEl.append(child); + } + categoryEl.appendChild(linkEl); + } + } + } catch { + // Ignore errors + } + }; + function hasTitleCategories() { + return window.document.querySelector(categorySelector) !== null; + } + + function offsetRelativeUrl(url) { + const offset = getMeta("quarto:offset"); + return offset ? offset + url : url; + } + + function offsetAbsoluteUrl(url) { + const offset = getMeta("quarto:offset"); + const baseUrl = new URL(offset, window.location); + + const projRelativeUrl = url.replace(baseUrl, ""); + if (projRelativeUrl.startsWith("/")) { + return projRelativeUrl; + } else { + return "/" + projRelativeUrl; + } + } + + // read a meta tag value + function getMeta(metaName) { + const metas = window.document.getElementsByTagName("meta"); + for (let i = 0; i < metas.length; i++) { + if (metas[i].getAttribute("name") === metaName) { + return metas[i].getAttribute("content"); + } + } + return ""; + } + + async function findAndActivateCategories() { + const currentPagePath = offsetAbsoluteUrl(window.location.href); + const response = await fetch(offsetRelativeUrl("listings.json")); + if (response.status == 200) { + return response.json().then(function (listingPaths) { + const listingHrefs = []; + for (const listingPath of listingPaths) { + const pathWithoutLeadingSlash = listingPath.listing.substring(1); + for (const item of listingPath.items) { + if ( + item === currentPagePath || + item === currentPagePath + "index.html" + ) { + // Resolve this path against the offset to be sure + // we already are using the correct path to the listing + // (this adjusts the listing urls to be rooted against + // whatever root the page is actually running against) + const relative = offsetRelativeUrl(pathWithoutLeadingSlash); + const baseUrl = window.location; + const resolvedPath = new URL(relative, baseUrl); + listingHrefs.push(resolvedPath.pathname); + break; + } + } + } + + // Look up the tree for a nearby linting and use that if we find one + const nearestListing = findNearestParentListing( + offsetAbsoluteUrl(window.location.pathname), + listingHrefs + ); + if (nearestListing) { + activateCategories(nearestListing); + } else { + // See if the referrer is a listing page for this item + const referredRelativePath = offsetAbsoluteUrl(document.referrer); + const referrerListing = listingHrefs.find((listingHref) => { + const isListingReferrer = + listingHref === referredRelativePath || + listingHref === referredRelativePath + "index.html"; + return isListingReferrer; + }); + + if (referrerListing) { + // Try to use the referrer if possible + activateCategories(referrerListing); + } else if (listingHrefs.length > 0) { + // Otherwise, just fall back to the first listing + activateCategories(listingHrefs[0]); + } + } + }); + } + } + if (hasTitleCategories()) { + findAndActivateCategories(); + } + + const findNearestParentListing = (href, listingHrefs) => { + if (!href || !listingHrefs) { + return undefined; + } + // Look up the tree for a nearby linting and use that if we find one + const relativeParts = href.substring(1).split("/"); + while (relativeParts.length > 0) { + const path = relativeParts.join("/"); + for (const listingHref of listingHrefs) { + if (listingHref.startsWith(path)) { + return listingHref; + } + } + relativeParts.pop(); + } + + return undefined; + }; + + const manageSidebarVisiblity = (el, placeholderDescriptor) => { + let isVisible = true; + let elRect; + + return (hiddenRegions) => { + if (el === null) { + return; + } + + // Find the last element of the TOC + const lastChildEl = el.lastElementChild; + + if (lastChildEl) { + // Converts the sidebar to a menu + const convertToMenu = () => { + for (const child of el.children) { + child.style.opacity = 0; + child.style.overflow = "hidden"; + } + + nexttick(() => { + const toggleContainer = window.document.createElement("div"); + toggleContainer.style.width = "100%"; + toggleContainer.classList.add("zindex-over-content"); + toggleContainer.classList.add("quarto-sidebar-toggle"); + toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom + toggleContainer.id = placeholderDescriptor.id; + toggleContainer.style.position = "fixed"; + + const toggleIcon = window.document.createElement("i"); + toggleIcon.classList.add("quarto-sidebar-toggle-icon"); + toggleIcon.classList.add("bi"); + toggleIcon.classList.add("bi-caret-down-fill"); + + const toggleTitle = window.document.createElement("div"); + const titleEl = window.document.body.querySelector( + placeholderDescriptor.titleSelector + ); + if (titleEl) { + toggleTitle.append( + titleEl.textContent || titleEl.innerText, + toggleIcon + ); + } + toggleTitle.classList.add("zindex-over-content"); + toggleTitle.classList.add("quarto-sidebar-toggle-title"); + toggleContainer.append(toggleTitle); + + const toggleContents = window.document.createElement("div"); + toggleContents.classList = el.classList; + toggleContents.classList.add("zindex-over-content"); + toggleContents.classList.add("quarto-sidebar-toggle-contents"); + for (const child of el.children) { + if (child.id === "toc-title") { + continue; + } + + const clone = child.cloneNode(true); + clone.style.opacity = 1; + clone.style.display = null; + toggleContents.append(clone); + } + toggleContents.style.height = "0px"; + const positionToggle = () => { + // position the element (top left of parent, same width as parent) + if (!elRect) { + elRect = el.getBoundingClientRect(); + } + toggleContainer.style.left = `${elRect.left}px`; + toggleContainer.style.top = `${elRect.top}px`; + toggleContainer.style.width = `${elRect.width}px`; + }; + positionToggle(); + + toggleContainer.append(toggleContents); + el.parentElement.prepend(toggleContainer); + + // Process clicks + let tocShowing = false; + // Allow the caller to control whether this is dismissed + // when it is clicked (e.g. sidebar navigation supports + // opening and closing the nav tree, so don't dismiss on click) + const clickEl = placeholderDescriptor.dismissOnClick + ? toggleContainer + : toggleTitle; + + const closeToggle = () => { + if (tocShowing) { + toggleContainer.classList.remove("expanded"); + toggleContents.style.height = "0px"; + tocShowing = false; + } + }; + + // Get rid of any expanded toggle if the user scrolls + window.document.addEventListener( + "scroll", + throttle(() => { + closeToggle(); + }, 50) + ); + + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + positionToggle(); + }, 50) + ); + + window.addEventListener("quarto-hrChanged", () => { + elRect = undefined; + }); + + // Process the click + clickEl.onclick = () => { + if (!tocShowing) { + toggleContainer.classList.add("expanded"); + toggleContents.style.height = null; + tocShowing = true; + } else { + closeToggle(); + } + }; + }); + }; + + // Converts a sidebar from a menu back to a sidebar + const convertToSidebar = () => { + for (const child of el.children) { + child.style.opacity = 1; + child.style.overflow = null; + } + + const placeholderEl = window.document.getElementById( + placeholderDescriptor.id + ); + if (placeholderEl) { + placeholderEl.remove(); + } + + el.classList.remove("rollup"); + }; + + if (isReaderMode()) { + convertToMenu(); + isVisible = false; + } else { + // Find the top and bottom o the element that is being managed + const elTop = el.offsetTop; + const elBottom = + elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight; + + if (!isVisible) { + // If the element is current not visible reveal if there are + // no conflicts with overlay regions + if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToSidebar(); + isVisible = true; + } + } else { + // If the element is visible, hide it if it conflicts with overlay regions + // and insert a placeholder toggle (or if we're in reader mode) + if (inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToMenu(); + isVisible = false; + } + } + } + } + }; + }; + + const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]'); + for (const tabEl of tabEls) { + const id = tabEl.getAttribute("data-bs-target"); + if (id) { + const columnEl = document.querySelector( + `${id} .column-margin, .tabset-margin-content` + ); + if (columnEl) + tabEl.addEventListener("shown.bs.tab", function (event) { + const el = event.srcElement; + if (el) { + const visibleCls = `${el.id}-margin-content`; + // walk up until we find a parent tabset + let panelTabsetEl = el.parentElement; + while (panelTabsetEl) { + if (panelTabsetEl.classList.contains("panel-tabset")) { + break; + } + panelTabsetEl = panelTabsetEl.parentElement; + } + + if (panelTabsetEl) { + const prevSib = panelTabsetEl.previousElementSibling; + if ( + prevSib && + prevSib.classList.contains("tabset-margin-container") + ) { + const childNodes = prevSib.querySelectorAll( + ".tabset-margin-content" + ); + for (const childEl of childNodes) { + if (childEl.classList.contains(visibleCls)) { + childEl.classList.remove("collapse"); + } else { + childEl.classList.add("collapse"); + } + } + } + } + } + + layoutMarginEls(); + }); + } + } + + // Manage the visibility of the toc and the sidebar + const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, { + id: "quarto-toc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, { + id: "quarto-sidebarnav-toggle", + titleSelector: ".title", + dismissOnClick: false, + }); + let tocLeftScrollVisibility; + if (leftTocEl) { + tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, { + id: "quarto-lefttoc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + } + + // Find the first element that uses formatting in special columns + const conflictingEls = window.document.body.querySelectorAll( + '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]' + ); + + // Filter all the possibly conflicting elements into ones + // the do conflict on the left or ride side + const arrConflictingEls = Array.from(conflictingEls); + const leftSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return false; + } + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + className.startsWith("column-") && + !className.endsWith("right") && + !className.endsWith("container") && + className !== "column-margin" + ); + }); + }); + const rightSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return true; + } + + const hasMarginCaption = Array.from(el.classList).find((className) => { + return className == "margin-caption"; + }); + if (hasMarginCaption) { + return true; + } + + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + !className.endsWith("container") && + className.startsWith("column-") && + !className.endsWith("left") + ); + }); + }); + + const kOverlapPaddingSize = 10; + function toRegions(els) { + return els.map((el) => { + const boundRect = el.getBoundingClientRect(); + const top = + boundRect.top + + document.documentElement.scrollTop - + kOverlapPaddingSize; + return { + top, + bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize, + }; + }); + } + + let hasObserved = false; + const visibleItemObserver = (els) => { + let visibleElements = [...els]; + const intersectionObserver = new IntersectionObserver( + (entries, _observer) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + if (visibleElements.indexOf(entry.target) === -1) { + visibleElements.push(entry.target); + } + } else { + visibleElements = visibleElements.filter((visibleEntry) => { + return visibleEntry !== entry; + }); + } + }); + + if (!hasObserved) { + hideOverlappedSidebars(); + } + hasObserved = true; + }, + {} + ); + els.forEach((el) => { + intersectionObserver.observe(el); + }); + + return { + getVisibleEntries: () => { + return visibleElements; + }, + }; + }; + + const rightElementObserver = visibleItemObserver(rightSideConflictEls); + const leftElementObserver = visibleItemObserver(leftSideConflictEls); + + const hideOverlappedSidebars = () => { + marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries())); + sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries())); + if (tocLeftScrollVisibility) { + tocLeftScrollVisibility( + toRegions(leftElementObserver.getVisibleEntries()) + ); + } + }; + + window.quartoToggleReader = () => { + // Applies a slow class (or removes it) + // to update the transition speed + const slowTransition = (slow) => { + const manageTransition = (id, slow) => { + const el = document.getElementById(id); + if (el) { + if (slow) { + el.classList.add("slow"); + } else { + el.classList.remove("slow"); + } + } + }; + + manageTransition("TOC", slow); + manageTransition("quarto-sidebar", slow); + }; + const readerMode = !isReaderMode(); + setReaderModeValue(readerMode); + + // If we're entering reader mode, slow the transition + if (readerMode) { + slowTransition(readerMode); + } + highlightReaderToggle(readerMode); + hideOverlappedSidebars(); + + // If we're exiting reader mode, restore the non-slow transition + if (!readerMode) { + slowTransition(!readerMode); + } + }; + + const highlightReaderToggle = (readerMode) => { + const els = document.querySelectorAll(".quarto-reader-toggle"); + if (els) { + els.forEach((el) => { + if (readerMode) { + el.classList.add("reader"); + } else { + el.classList.remove("reader"); + } + }); + } + }; + + const setReaderModeValue = (val) => { + if (window.location.protocol !== "file:") { + window.localStorage.setItem("quarto-reader-mode", val); + } else { + localReaderMode = val; + } + }; + + const isReaderMode = () => { + if (window.location.protocol !== "file:") { + return window.localStorage.getItem("quarto-reader-mode") === "true"; + } else { + return localReaderMode; + } + }; + let localReaderMode = null; + + const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded"); + const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1; + + // Walk the TOC and collapse/expand nodes + // Nodes are expanded if: + // - they are top level + // - they have children that are 'active' links + // - they are directly below an link that is 'active' + const walk = (el, depth) => { + // Tick depth when we enter a UL + if (el.tagName === "UL") { + depth = depth + 1; + } + + // It this is active link + let isActiveNode = false; + if (el.tagName === "A" && el.classList.contains("active")) { + isActiveNode = true; + } + + // See if there is an active child to this element + let hasActiveChild = false; + for (child of el.children) { + hasActiveChild = walk(child, depth) || hasActiveChild; + } + + // Process the collapse state if this is an UL + if (el.tagName === "UL") { + if (tocOpenDepth === -1 && depth > 1) { + el.classList.add("collapse"); + } else if ( + depth <= tocOpenDepth || + hasActiveChild || + prevSiblingIsActiveLink(el) + ) { + el.classList.remove("collapse"); + } else { + el.classList.add("collapse"); + } + + // untick depth when we leave a UL + depth = depth - 1; + } + return hasActiveChild || isActiveNode; + }; + + // walk the TOC and expand / collapse any items that should be shown + + if (tocEl) { + walk(tocEl, 0); + updateActiveLink(); + } + + // Throttle the scroll event and walk peridiocally + window.document.addEventListener( + "scroll", + throttle(() => { + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 5) + ); + window.addEventListener( + "resize", + throttle(() => { + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 10) + ); + hideOverlappedSidebars(); + highlightReaderToggle(isReaderMode()); +}); + +// grouped tabsets +window.addEventListener("pageshow", (_event) => { + function getTabSettings() { + const data = localStorage.getItem("quarto-persistent-tabsets-data"); + if (!data) { + localStorage.setItem("quarto-persistent-tabsets-data", "{}"); + return {}; + } + if (data) { + return JSON.parse(data); + } + } + + function setTabSettings(data) { + localStorage.setItem( + "quarto-persistent-tabsets-data", + JSON.stringify(data) + ); + } + + function setTabState(groupName, groupValue) { + const data = getTabSettings(); + data[groupName] = groupValue; + setTabSettings(data); + } + + function toggleTab(tab, active) { + const tabPanelId = tab.getAttribute("aria-controls"); + const tabPanel = document.getElementById(tabPanelId); + if (active) { + tab.classList.add("active"); + tabPanel.classList.add("active"); + } else { + tab.classList.remove("active"); + tabPanel.classList.remove("active"); + } + } + + function toggleAll(selectedGroup, selectorsToSync) { + for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) { + const active = selectedGroup === thisGroup; + for (const tab of tabs) { + toggleTab(tab, active); + } + } + } + + function findSelectorsToSyncByLanguage() { + const result = {}; + const tabs = Array.from( + document.querySelectorAll(`div[data-group] a[id^='tabset-']`) + ); + for (const item of tabs) { + const div = item.parentElement.parentElement.parentElement; + const group = div.getAttribute("data-group"); + if (!result[group]) { + result[group] = {}; + } + const selectorsToSync = result[group]; + const value = item.innerHTML; + if (!selectorsToSync[value]) { + selectorsToSync[value] = []; + } + selectorsToSync[value].push(item); + } + return result; + } + + function setupSelectorSync() { + const selectorsToSync = findSelectorsToSyncByLanguage(); + Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => { + Object.entries(tabSetsByValue).forEach(([value, items]) => { + items.forEach((item) => { + item.addEventListener("click", (_event) => { + setTabState(group, value); + toggleAll(value, selectorsToSync[group]); + }); + }); + }); + }); + return selectorsToSync; + } + + const selectorsToSync = setupSelectorSync(); + for (const [group, selectedName] of Object.entries(getTabSettings())) { + const selectors = selectorsToSync[group]; + // it's possible that stale state gives us empty selections, so we explicitly check here. + if (selectors) { + toggleAll(selectedName, selectors); + } + } +}); + +function throttle(func, wait) { + let waiting = false; + return function () { + if (!waiting) { + func.apply(this, arguments); + waiting = true; + setTimeout(function () { + waiting = false; + }, wait); + } + }; +} + +function nexttick(func) { + return setTimeout(func, 0); +} diff --git a/site_libs/quarto-html/tippy.css b/site_libs/quarto-html/tippy.css new file mode 100644 index 0000000..e6ae635 --- /dev/null +++ b/site_libs/quarto-html/tippy.css @@ -0,0 +1 @@ +.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} \ No newline at end of file diff --git a/site_libs/quarto-html/tippy.umd.min.js b/site_libs/quarto-html/tippy.umd.min.js new file mode 100644 index 0000000..ca292be --- /dev/null +++ b/site_libs/quarto-html/tippy.umd.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],t):(e=e||self).tippy=t(e.Popper)}(this,(function(e){"use strict";var t={passive:!0,capture:!0},n=function(){return document.body};function r(e,t,n){if(Array.isArray(e)){var r=e[t];return null==r?Array.isArray(n)?n[t]:n:r}return e}function o(e,t){var n={}.toString.call(e);return 0===n.indexOf("[object")&&n.indexOf(t+"]")>-1}function i(e,t){return"function"==typeof e?e.apply(void 0,t):e}function a(e,t){return 0===t?e:function(r){clearTimeout(n),n=setTimeout((function(){e(r)}),t)};var n}function s(e,t){var n=Object.assign({},e);return t.forEach((function(e){delete n[e]})),n}function u(e){return[].concat(e)}function c(e,t){-1===e.indexOf(t)&&e.push(t)}function p(e){return e.split("-")[0]}function f(e){return[].slice.call(e)}function l(e){return Object.keys(e).reduce((function(t,n){return void 0!==e[n]&&(t[n]=e[n]),t}),{})}function d(){return document.createElement("div")}function v(e){return["Element","Fragment"].some((function(t){return o(e,t)}))}function m(e){return o(e,"MouseEvent")}function g(e){return!(!e||!e._tippy||e._tippy.reference!==e)}function h(e){return v(e)?[e]:function(e){return o(e,"NodeList")}(e)?f(e):Array.isArray(e)?e:f(document.querySelectorAll(e))}function b(e,t){e.forEach((function(e){e&&(e.style.transitionDuration=t+"ms")}))}function y(e,t){e.forEach((function(e){e&&e.setAttribute("data-state",t)}))}function w(e){var t,n=u(e)[0];return null!=n&&null!=(t=n.ownerDocument)&&t.body?n.ownerDocument:document}function E(e,t,n){var r=t+"EventListener";["transitionend","webkitTransitionEnd"].forEach((function(t){e[r](t,n)}))}function O(e,t){for(var n=t;n;){var r;if(e.contains(n))return!0;n=null==n.getRootNode||null==(r=n.getRootNode())?void 0:r.host}return!1}var x={isTouch:!1},C=0;function T(){x.isTouch||(x.isTouch=!0,window.performance&&document.addEventListener("mousemove",A))}function A(){var e=performance.now();e-C<20&&(x.isTouch=!1,document.removeEventListener("mousemove",A)),C=e}function L(){var e=document.activeElement;if(g(e)){var t=e._tippy;e.blur&&!t.state.isVisible&&e.blur()}}var D=!!("undefined"!=typeof window&&"undefined"!=typeof document)&&!!window.msCrypto,R=Object.assign({appendTo:n,aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},{animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},{allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999}),k=Object.keys(R);function P(e){var t=(e.plugins||[]).reduce((function(t,n){var r,o=n.name,i=n.defaultValue;o&&(t[o]=void 0!==e[o]?e[o]:null!=(r=R[o])?r:i);return t}),{});return Object.assign({},e,t)}function j(e,t){var n=Object.assign({},t,{content:i(t.content,[e])},t.ignoreAttributes?{}:function(e,t){return(t?Object.keys(P(Object.assign({},R,{plugins:t}))):k).reduce((function(t,n){var r=(e.getAttribute("data-tippy-"+n)||"").trim();if(!r)return t;if("content"===n)t[n]=r;else try{t[n]=JSON.parse(r)}catch(e){t[n]=r}return t}),{})}(e,t.plugins));return n.aria=Object.assign({},R.aria,n.aria),n.aria={expanded:"auto"===n.aria.expanded?t.interactive:n.aria.expanded,content:"auto"===n.aria.content?t.interactive?null:"describedby":n.aria.content},n}function M(e,t){e.innerHTML=t}function V(e){var t=d();return!0===e?t.className="tippy-arrow":(t.className="tippy-svg-arrow",v(e)?t.appendChild(e):M(t,e)),t}function I(e,t){v(t.content)?(M(e,""),e.appendChild(t.content)):"function"!=typeof t.content&&(t.allowHTML?M(e,t.content):e.textContent=t.content)}function S(e){var t=e.firstElementChild,n=f(t.children);return{box:t,content:n.find((function(e){return e.classList.contains("tippy-content")})),arrow:n.find((function(e){return e.classList.contains("tippy-arrow")||e.classList.contains("tippy-svg-arrow")})),backdrop:n.find((function(e){return e.classList.contains("tippy-backdrop")}))}}function N(e){var t=d(),n=d();n.className="tippy-box",n.setAttribute("data-state","hidden"),n.setAttribute("tabindex","-1");var r=d();function o(n,r){var o=S(t),i=o.box,a=o.content,s=o.arrow;r.theme?i.setAttribute("data-theme",r.theme):i.removeAttribute("data-theme"),"string"==typeof r.animation?i.setAttribute("data-animation",r.animation):i.removeAttribute("data-animation"),r.inertia?i.setAttribute("data-inertia",""):i.removeAttribute("data-inertia"),i.style.maxWidth="number"==typeof r.maxWidth?r.maxWidth+"px":r.maxWidth,r.role?i.setAttribute("role",r.role):i.removeAttribute("role"),n.content===r.content&&n.allowHTML===r.allowHTML||I(a,e.props),r.arrow?s?n.arrow!==r.arrow&&(i.removeChild(s),i.appendChild(V(r.arrow))):i.appendChild(V(r.arrow)):s&&i.removeChild(s)}return r.className="tippy-content",r.setAttribute("data-state","hidden"),I(r,e.props),t.appendChild(n),n.appendChild(r),o(e.props,e.props),{popper:t,onUpdate:o}}N.$$tippy=!0;var B=1,H=[],U=[];function _(o,s){var v,g,h,C,T,A,L,k,M=j(o,Object.assign({},R,P(l(s)))),V=!1,I=!1,N=!1,_=!1,F=[],W=a(we,M.interactiveDebounce),X=B++,Y=(k=M.plugins).filter((function(e,t){return k.indexOf(e)===t})),$={id:X,reference:o,popper:d(),popperInstance:null,props:M,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},plugins:Y,clearDelayTimeouts:function(){clearTimeout(v),clearTimeout(g),cancelAnimationFrame(h)},setProps:function(e){if($.state.isDestroyed)return;ae("onBeforeUpdate",[$,e]),be();var t=$.props,n=j(o,Object.assign({},t,l(e),{ignoreAttributes:!0}));$.props=n,he(),t.interactiveDebounce!==n.interactiveDebounce&&(ce(),W=a(we,n.interactiveDebounce));t.triggerTarget&&!n.triggerTarget?u(t.triggerTarget).forEach((function(e){e.removeAttribute("aria-expanded")})):n.triggerTarget&&o.removeAttribute("aria-expanded");ue(),ie(),J&&J(t,n);$.popperInstance&&(Ce(),Ae().forEach((function(e){requestAnimationFrame(e._tippy.popperInstance.forceUpdate)})));ae("onAfterUpdate",[$,e])},setContent:function(e){$.setProps({content:e})},show:function(){var e=$.state.isVisible,t=$.state.isDestroyed,o=!$.state.isEnabled,a=x.isTouch&&!$.props.touch,s=r($.props.duration,0,R.duration);if(e||t||o||a)return;if(te().hasAttribute("disabled"))return;if(ae("onShow",[$],!1),!1===$.props.onShow($))return;$.state.isVisible=!0,ee()&&(z.style.visibility="visible");ie(),de(),$.state.isMounted||(z.style.transition="none");if(ee()){var u=re(),p=u.box,f=u.content;b([p,f],0)}A=function(){var e;if($.state.isVisible&&!_){if(_=!0,z.offsetHeight,z.style.transition=$.props.moveTransition,ee()&&$.props.animation){var t=re(),n=t.box,r=t.content;b([n,r],s),y([n,r],"visible")}se(),ue(),c(U,$),null==(e=$.popperInstance)||e.forceUpdate(),ae("onMount",[$]),$.props.animation&&ee()&&function(e,t){me(e,t)}(s,(function(){$.state.isShown=!0,ae("onShown",[$])}))}},function(){var e,t=$.props.appendTo,r=te();e=$.props.interactive&&t===n||"parent"===t?r.parentNode:i(t,[r]);e.contains(z)||e.appendChild(z);$.state.isMounted=!0,Ce()}()},hide:function(){var e=!$.state.isVisible,t=$.state.isDestroyed,n=!$.state.isEnabled,o=r($.props.duration,1,R.duration);if(e||t||n)return;if(ae("onHide",[$],!1),!1===$.props.onHide($))return;$.state.isVisible=!1,$.state.isShown=!1,_=!1,V=!1,ee()&&(z.style.visibility="hidden");if(ce(),ve(),ie(!0),ee()){var i=re(),a=i.box,s=i.content;$.props.animation&&(b([a,s],o),y([a,s],"hidden"))}se(),ue(),$.props.animation?ee()&&function(e,t){me(e,(function(){!$.state.isVisible&&z.parentNode&&z.parentNode.contains(z)&&t()}))}(o,$.unmount):$.unmount()},hideWithInteractivity:function(e){ne().addEventListener("mousemove",W),c(H,W),W(e)},enable:function(){$.state.isEnabled=!0},disable:function(){$.hide(),$.state.isEnabled=!1},unmount:function(){$.state.isVisible&&$.hide();if(!$.state.isMounted)return;Te(),Ae().forEach((function(e){e._tippy.unmount()})),z.parentNode&&z.parentNode.removeChild(z);U=U.filter((function(e){return e!==$})),$.state.isMounted=!1,ae("onHidden",[$])},destroy:function(){if($.state.isDestroyed)return;$.clearDelayTimeouts(),$.unmount(),be(),delete o._tippy,$.state.isDestroyed=!0,ae("onDestroy",[$])}};if(!M.render)return $;var q=M.render($),z=q.popper,J=q.onUpdate;z.setAttribute("data-tippy-root",""),z.id="tippy-"+$.id,$.popper=z,o._tippy=$,z._tippy=$;var G=Y.map((function(e){return e.fn($)})),K=o.hasAttribute("aria-expanded");return he(),ue(),ie(),ae("onCreate",[$]),M.showOnCreate&&Le(),z.addEventListener("mouseenter",(function(){$.props.interactive&&$.state.isVisible&&$.clearDelayTimeouts()})),z.addEventListener("mouseleave",(function(){$.props.interactive&&$.props.trigger.indexOf("mouseenter")>=0&&ne().addEventListener("mousemove",W)})),$;function Q(){var e=$.props.touch;return Array.isArray(e)?e:[e,0]}function Z(){return"hold"===Q()[0]}function ee(){var e;return!(null==(e=$.props.render)||!e.$$tippy)}function te(){return L||o}function ne(){var e=te().parentNode;return e?w(e):document}function re(){return S(z)}function oe(e){return $.state.isMounted&&!$.state.isVisible||x.isTouch||C&&"focus"===C.type?0:r($.props.delay,e?0:1,R.delay)}function ie(e){void 0===e&&(e=!1),z.style.pointerEvents=$.props.interactive&&!e?"":"none",z.style.zIndex=""+$.props.zIndex}function ae(e,t,n){var r;(void 0===n&&(n=!0),G.forEach((function(n){n[e]&&n[e].apply(n,t)})),n)&&(r=$.props)[e].apply(r,t)}function se(){var e=$.props.aria;if(e.content){var t="aria-"+e.content,n=z.id;u($.props.triggerTarget||o).forEach((function(e){var r=e.getAttribute(t);if($.state.isVisible)e.setAttribute(t,r?r+" "+n:n);else{var o=r&&r.replace(n,"").trim();o?e.setAttribute(t,o):e.removeAttribute(t)}}))}}function ue(){!K&&$.props.aria.expanded&&u($.props.triggerTarget||o).forEach((function(e){$.props.interactive?e.setAttribute("aria-expanded",$.state.isVisible&&e===te()?"true":"false"):e.removeAttribute("aria-expanded")}))}function ce(){ne().removeEventListener("mousemove",W),H=H.filter((function(e){return e!==W}))}function pe(e){if(!x.isTouch||!N&&"mousedown"!==e.type){var t=e.composedPath&&e.composedPath()[0]||e.target;if(!$.props.interactive||!O(z,t)){if(u($.props.triggerTarget||o).some((function(e){return O(e,t)}))){if(x.isTouch)return;if($.state.isVisible&&$.props.trigger.indexOf("click")>=0)return}else ae("onClickOutside",[$,e]);!0===$.props.hideOnClick&&($.clearDelayTimeouts(),$.hide(),I=!0,setTimeout((function(){I=!1})),$.state.isMounted||ve())}}}function fe(){N=!0}function le(){N=!1}function de(){var e=ne();e.addEventListener("mousedown",pe,!0),e.addEventListener("touchend",pe,t),e.addEventListener("touchstart",le,t),e.addEventListener("touchmove",fe,t)}function ve(){var e=ne();e.removeEventListener("mousedown",pe,!0),e.removeEventListener("touchend",pe,t),e.removeEventListener("touchstart",le,t),e.removeEventListener("touchmove",fe,t)}function me(e,t){var n=re().box;function r(e){e.target===n&&(E(n,"remove",r),t())}if(0===e)return t();E(n,"remove",T),E(n,"add",r),T=r}function ge(e,t,n){void 0===n&&(n=!1),u($.props.triggerTarget||o).forEach((function(r){r.addEventListener(e,t,n),F.push({node:r,eventType:e,handler:t,options:n})}))}function he(){var e;Z()&&(ge("touchstart",ye,{passive:!0}),ge("touchend",Ee,{passive:!0})),(e=$.props.trigger,e.split(/\s+/).filter(Boolean)).forEach((function(e){if("manual"!==e)switch(ge(e,ye),e){case"mouseenter":ge("mouseleave",Ee);break;case"focus":ge(D?"focusout":"blur",Oe);break;case"focusin":ge("focusout",Oe)}}))}function be(){F.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),F=[]}function ye(e){var t,n=!1;if($.state.isEnabled&&!xe(e)&&!I){var r="focus"===(null==(t=C)?void 0:t.type);C=e,L=e.currentTarget,ue(),!$.state.isVisible&&m(e)&&H.forEach((function(t){return t(e)})),"click"===e.type&&($.props.trigger.indexOf("mouseenter")<0||V)&&!1!==$.props.hideOnClick&&$.state.isVisible?n=!0:Le(e),"click"===e.type&&(V=!n),n&&!r&&De(e)}}function we(e){var t=e.target,n=te().contains(t)||z.contains(t);"mousemove"===e.type&&n||function(e,t){var n=t.clientX,r=t.clientY;return e.every((function(e){var t=e.popperRect,o=e.popperState,i=e.props.interactiveBorder,a=p(o.placement),s=o.modifiersData.offset;if(!s)return!0;var u="bottom"===a?s.top.y:0,c="top"===a?s.bottom.y:0,f="right"===a?s.left.x:0,l="left"===a?s.right.x:0,d=t.top-r+u>i,v=r-t.bottom-c>i,m=t.left-n+f>i,g=n-t.right-l>i;return d||v||m||g}))}(Ae().concat(z).map((function(e){var t,n=null==(t=e._tippy.popperInstance)?void 0:t.state;return n?{popperRect:e.getBoundingClientRect(),popperState:n,props:M}:null})).filter(Boolean),e)&&(ce(),De(e))}function Ee(e){xe(e)||$.props.trigger.indexOf("click")>=0&&V||($.props.interactive?$.hideWithInteractivity(e):De(e))}function Oe(e){$.props.trigger.indexOf("focusin")<0&&e.target!==te()||$.props.interactive&&e.relatedTarget&&z.contains(e.relatedTarget)||De(e)}function xe(e){return!!x.isTouch&&Z()!==e.type.indexOf("touch")>=0}function Ce(){Te();var t=$.props,n=t.popperOptions,r=t.placement,i=t.offset,a=t.getReferenceClientRect,s=t.moveTransition,u=ee()?S(z).arrow:null,c=a?{getBoundingClientRect:a,contextElement:a.contextElement||te()}:o,p=[{name:"offset",options:{offset:i}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!s}},{name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(e){var t=e.state;if(ee()){var n=re().box;["placement","reference-hidden","escaped"].forEach((function(e){"placement"===e?n.setAttribute("data-placement",t.placement):t.attributes.popper["data-popper-"+e]?n.setAttribute("data-"+e,""):n.removeAttribute("data-"+e)})),t.attributes.popper={}}}}];ee()&&u&&p.push({name:"arrow",options:{element:u,padding:3}}),p.push.apply(p,(null==n?void 0:n.modifiers)||[]),$.popperInstance=e.createPopper(c,z,Object.assign({},n,{placement:r,onFirstUpdate:A,modifiers:p}))}function Te(){$.popperInstance&&($.popperInstance.destroy(),$.popperInstance=null)}function Ae(){return f(z.querySelectorAll("[data-tippy-root]"))}function Le(e){$.clearDelayTimeouts(),e&&ae("onTrigger",[$,e]),de();var t=oe(!0),n=Q(),r=n[0],o=n[1];x.isTouch&&"hold"===r&&o&&(t=o),t?v=setTimeout((function(){$.show()}),t):$.show()}function De(e){if($.clearDelayTimeouts(),ae("onUntrigger",[$,e]),$.state.isVisible){if(!($.props.trigger.indexOf("mouseenter")>=0&&$.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(e.type)>=0&&V)){var t=oe(!1);t?g=setTimeout((function(){$.state.isVisible&&$.hide()}),t):h=requestAnimationFrame((function(){$.hide()}))}}else ve()}}function F(e,n){void 0===n&&(n={});var r=R.plugins.concat(n.plugins||[]);document.addEventListener("touchstart",T,t),window.addEventListener("blur",L);var o=Object.assign({},n,{plugins:r}),i=h(e).reduce((function(e,t){var n=t&&_(t,o);return n&&e.push(n),e}),[]);return v(e)?i[0]:i}F.defaultProps=R,F.setDefaultProps=function(e){Object.keys(e).forEach((function(t){R[t]=e[t]}))},F.currentInput=x;var W=Object.assign({},e.applyStyles,{effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow)}}),X={mouseover:"mouseenter",focusin:"focus",click:"click"};var Y={name:"animateFill",defaultValue:!1,fn:function(e){var t;if(null==(t=e.props.render)||!t.$$tippy)return{};var n=S(e.popper),r=n.box,o=n.content,i=e.props.animateFill?function(){var e=d();return e.className="tippy-backdrop",y([e],"hidden"),e}():null;return{onCreate:function(){i&&(r.insertBefore(i,r.firstElementChild),r.setAttribute("data-animatefill",""),r.style.overflow="hidden",e.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(i){var e=r.style.transitionDuration,t=Number(e.replace("ms",""));o.style.transitionDelay=Math.round(t/10)+"ms",i.style.transitionDuration=e,y([i],"visible")}},onShow:function(){i&&(i.style.transitionDuration="0ms")},onHide:function(){i&&y([i],"hidden")}}}};var $={clientX:0,clientY:0},q=[];function z(e){var t=e.clientX,n=e.clientY;$={clientX:t,clientY:n}}var J={name:"followCursor",defaultValue:!1,fn:function(e){var t=e.reference,n=w(e.props.triggerTarget||t),r=!1,o=!1,i=!0,a=e.props;function s(){return"initial"===e.props.followCursor&&e.state.isVisible}function u(){n.addEventListener("mousemove",f)}function c(){n.removeEventListener("mousemove",f)}function p(){r=!0,e.setProps({getReferenceClientRect:null}),r=!1}function f(n){var r=!n.target||t.contains(n.target),o=e.props.followCursor,i=n.clientX,a=n.clientY,s=t.getBoundingClientRect(),u=i-s.left,c=a-s.top;!r&&e.props.interactive||e.setProps({getReferenceClientRect:function(){var e=t.getBoundingClientRect(),n=i,r=a;"initial"===o&&(n=e.left+u,r=e.top+c);var s="horizontal"===o?e.top:r,p="vertical"===o?e.right:n,f="horizontal"===o?e.bottom:r,l="vertical"===o?e.left:n;return{width:p-l,height:f-s,top:s,right:p,bottom:f,left:l}}})}function l(){e.props.followCursor&&(q.push({instance:e,doc:n}),function(e){e.addEventListener("mousemove",z)}(n))}function d(){0===(q=q.filter((function(t){return t.instance!==e}))).filter((function(e){return e.doc===n})).length&&function(e){e.removeEventListener("mousemove",z)}(n)}return{onCreate:l,onDestroy:d,onBeforeUpdate:function(){a=e.props},onAfterUpdate:function(t,n){var i=n.followCursor;r||void 0!==i&&a.followCursor!==i&&(d(),i?(l(),!e.state.isMounted||o||s()||u()):(c(),p()))},onMount:function(){e.props.followCursor&&!o&&(i&&(f($),i=!1),s()||u())},onTrigger:function(e,t){m(t)&&($={clientX:t.clientX,clientY:t.clientY}),o="focus"===t.type},onHidden:function(){e.props.followCursor&&(p(),c(),i=!0)}}}};var G={name:"inlinePositioning",defaultValue:!1,fn:function(e){var t,n=e.reference;var r=-1,o=!1,i=[],a={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(o){var a=o.state;e.props.inlinePositioning&&(-1!==i.indexOf(a.placement)&&(i=[]),t!==a.placement&&-1===i.indexOf(a.placement)&&(i.push(a.placement),e.setProps({getReferenceClientRect:function(){return function(e){return function(e,t,n,r){if(n.length<2||null===e)return t;if(2===n.length&&r>=0&&n[0].left>n[1].right)return n[r]||t;switch(e){case"top":case"bottom":var o=n[0],i=n[n.length-1],a="top"===e,s=o.top,u=i.bottom,c=a?o.left:i.left,p=a?o.right:i.right;return{top:s,bottom:u,left:c,right:p,width:p-c,height:u-s};case"left":case"right":var f=Math.min.apply(Math,n.map((function(e){return e.left}))),l=Math.max.apply(Math,n.map((function(e){return e.right}))),d=n.filter((function(t){return"left"===e?t.left===f:t.right===l})),v=d[0].top,m=d[d.length-1].bottom;return{top:v,bottom:m,left:f,right:l,width:l-f,height:m-v};default:return t}}(p(e),n.getBoundingClientRect(),f(n.getClientRects()),r)}(a.placement)}})),t=a.placement)}};function s(){var t;o||(t=function(e,t){var n;return{popperOptions:Object.assign({},e.popperOptions,{modifiers:[].concat(((null==(n=e.popperOptions)?void 0:n.modifiers)||[]).filter((function(e){return e.name!==t.name})),[t])})}}(e.props,a),o=!0,e.setProps(t),o=!1)}return{onCreate:s,onAfterUpdate:s,onTrigger:function(t,n){if(m(n)){var o=f(e.reference.getClientRects()),i=o.find((function(e){return e.left-2<=n.clientX&&e.right+2>=n.clientX&&e.top-2<=n.clientY&&e.bottom+2>=n.clientY})),a=o.indexOf(i);r=a>-1?a:r}},onHidden:function(){r=-1}}}};var K={name:"sticky",defaultValue:!1,fn:function(e){var t=e.reference,n=e.popper;function r(t){return!0===e.props.sticky||e.props.sticky===t}var o=null,i=null;function a(){var s=r("reference")?(e.popperInstance?e.popperInstance.state.elements.reference:t).getBoundingClientRect():null,u=r("popper")?n.getBoundingClientRect():null;(s&&Q(o,s)||u&&Q(i,u))&&e.popperInstance&&e.popperInstance.update(),o=s,i=u,e.state.isMounted&&requestAnimationFrame(a)}return{onMount:function(){e.props.sticky&&a()}}}};function Q(e,t){return!e||!t||(e.top!==t.top||e.right!==t.right||e.bottom!==t.bottom||e.left!==t.left)}return F.setDefaultProps({plugins:[Y,J,G,K],render:N}),F.createSingleton=function(e,t){var n;void 0===t&&(t={});var r,o=e,i=[],a=[],c=t.overrides,p=[],f=!1;function l(){a=o.map((function(e){return u(e.props.triggerTarget||e.reference)})).reduce((function(e,t){return e.concat(t)}),[])}function v(){i=o.map((function(e){return e.reference}))}function m(e){o.forEach((function(t){e?t.enable():t.disable()}))}function g(e){return o.map((function(t){var n=t.setProps;return t.setProps=function(o){n(o),t.reference===r&&e.setProps(o)},function(){t.setProps=n}}))}function h(e,t){var n=a.indexOf(t);if(t!==r){r=t;var s=(c||[]).concat("content").reduce((function(e,t){return e[t]=o[n].props[t],e}),{});e.setProps(Object.assign({},s,{getReferenceClientRect:"function"==typeof s.getReferenceClientRect?s.getReferenceClientRect:function(){var e;return null==(e=i[n])?void 0:e.getBoundingClientRect()}}))}}m(!1),v(),l();var b={fn:function(){return{onDestroy:function(){m(!0)},onHidden:function(){r=null},onClickOutside:function(e){e.props.showOnCreate&&!f&&(f=!0,r=null)},onShow:function(e){e.props.showOnCreate&&!f&&(f=!0,h(e,i[0]))},onTrigger:function(e,t){h(e,t.currentTarget)}}}},y=F(d(),Object.assign({},s(t,["overrides"]),{plugins:[b].concat(t.plugins||[]),triggerTarget:a,popperOptions:Object.assign({},t.popperOptions,{modifiers:[].concat((null==(n=t.popperOptions)?void 0:n.modifiers)||[],[W])})})),w=y.show;y.show=function(e){if(w(),!r&&null==e)return h(y,i[0]);if(!r||null!=e){if("number"==typeof e)return i[e]&&h(y,i[e]);if(o.indexOf(e)>=0){var t=e.reference;return h(y,t)}return i.indexOf(e)>=0?h(y,e):void 0}},y.showNext=function(){var e=i[0];if(!r)return y.show(0);var t=i.indexOf(r);y.show(i[t+1]||e)},y.showPrevious=function(){var e=i[i.length-1];if(!r)return y.show(e);var t=i.indexOf(r),n=i[t-1]||e;y.show(n)};var E=y.setProps;return y.setProps=function(e){c=e.overrides||c,E(e)},y.setInstances=function(e){m(!0),p.forEach((function(e){return e()})),o=e,m(!1),v(),l(),p=g(y),y.setProps({triggerTarget:a})},p=g(y),y},F.delegate=function(e,n){var r=[],o=[],i=!1,a=n.target,c=s(n,["target"]),p=Object.assign({},c,{trigger:"manual",touch:!1}),f=Object.assign({touch:R.touch},c,{showOnCreate:!0}),l=F(e,p);function d(e){if(e.target&&!i){var t=e.target.closest(a);if(t){var r=t.getAttribute("data-tippy-trigger")||n.trigger||R.trigger;if(!t._tippy&&!("touchstart"===e.type&&"boolean"==typeof f.touch||"touchstart"!==e.type&&r.indexOf(X[e.type])<0)){var s=F(t,f);s&&(o=o.concat(s))}}}}function v(e,t,n,o){void 0===o&&(o=!1),e.addEventListener(t,n,o),r.push({node:e,eventType:t,handler:n,options:o})}return u(l).forEach((function(e){var n=e.destroy,a=e.enable,s=e.disable;e.destroy=function(e){void 0===e&&(e=!0),e&&o.forEach((function(e){e.destroy()})),o=[],r.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),r=[],n()},e.enable=function(){a(),o.forEach((function(e){return e.enable()})),i=!1},e.disable=function(){s(),o.forEach((function(e){return e.disable()})),i=!0},function(e){var n=e.reference;v(n,"touchstart",d,t),v(n,"mouseover",d),v(n,"focusin",d),v(n,"click",d)}(e)})),l},F.hideAll=function(e){var t=void 0===e?{}:e,n=t.exclude,r=t.duration;U.forEach((function(e){var t=!1;if(n&&(t=g(n)?e.reference===n:e.popper===n.popper),!t){var o=e.props.duration;e.setProps({duration:r}),e.hide(),e.state.isDestroyed||e.setProps({duration:o})}}))},F.roundArrow='',F})); + diff --git a/site_libs/quarto-listing/list.min.js b/site_libs/quarto-listing/list.min.js new file mode 100644 index 0000000..8131881 --- /dev/null +++ b/site_libs/quarto-listing/list.min.js @@ -0,0 +1,2 @@ +var List;List=function(){var t={"./src/add-async.js":function(t){t.exports=function(t){return function e(r,n,s){var i=r.splice(0,50);s=(s=s||[]).concat(t.add(i)),r.length>0?setTimeout((function(){e(r,n,s)}),1):(t.update(),n(s))}}},"./src/filter.js":function(t){t.exports=function(t){return t.handlers.filterStart=t.handlers.filterStart||[],t.handlers.filterComplete=t.handlers.filterComplete||[],function(e){if(t.trigger("filterStart"),t.i=1,t.reset.filter(),void 0===e)t.filtered=!1;else{t.filtered=!0;for(var r=t.items,n=0,s=r.length;nv.page,a=new g(t[s],void 0,n),v.items.push(a),r.push(a)}return v.update(),r}m(t.slice(0),e)}},this.show=function(t,e){return this.i=t,this.page=e,v.update(),v},this.remove=function(t,e,r){for(var n=0,s=0,i=v.items.length;s-1&&r.splice(n,1),v},this.trigger=function(t){for(var e=v.handlers[t].length;e--;)v.handlers[t][e](v);return v},this.reset={filter:function(){for(var t=v.items,e=t.length;e--;)t[e].filtered=!1;return v},search:function(){for(var t=v.items,e=t.length;e--;)t[e].found=!1;return v}},this.update=function(){var t=v.items,e=t.length;v.visibleItems=[],v.matchingItems=[],v.templater.clear();for(var r=0;r=v.i&&v.visibleItems.lengthe},innerWindow:function(t,e,r){return t>=e-r&&t<=e+r},dotted:function(t,e,r,n,s,i,a){return this.dottedLeft(t,e,r,n,s,i)||this.dottedRight(t,e,r,n,s,i,a)},dottedLeft:function(t,e,r,n,s,i){return e==r+1&&!this.innerWindow(e,s,i)&&!this.right(e,n)},dottedRight:function(t,e,r,n,s,i,a){return!t.items[a-1].values().dotted&&(e==n&&!this.innerWindow(e,s,i)&&!this.right(e,n))}};return function(e){var n=new i(t.listContainer.id,{listClass:e.paginationClass||"pagination",item:e.item||"
  • ",valueNames:["page","dotted"],searchClass:"pagination-search-that-is-not-supposed-to-exist",sortClass:"pagination-sort-that-is-not-supposed-to-exist"});s.bind(n.listContainer,"click",(function(e){var r=e.target||e.srcElement,n=t.utils.getAttribute(r,"data-page"),s=t.utils.getAttribute(r,"data-i");s&&t.show((s-1)*n+1,n)})),t.on("updated",(function(){r(n,e)})),r(n,e)}}},"./src/parse.js":function(t,e,r){t.exports=function(t){var e=r("./src/item.js")(t),n=function(r,n){for(var s=0,i=r.length;s0?setTimeout((function(){e(r,s)}),1):(t.update(),t.trigger("parseComplete"))};return t.handlers.parseComplete=t.handlers.parseComplete||[],function(){var e=function(t){for(var e=t.childNodes,r=[],n=0,s=e.length;n]/g.exec(t)){var e=document.createElement("tbody");return e.innerHTML=t,e.firstElementChild}if(-1!==t.indexOf("<")){var r=document.createElement("div");return r.innerHTML=t,r.firstElementChild}}},a=function(e,r,n){var s=void 0,i=function(e){for(var r=0,n=t.valueNames.length;r=1;)t.list.removeChild(t.list.firstChild)},function(){var r;if("function"!=typeof t.item){if(!(r="string"==typeof t.item?-1===t.item.indexOf("<")?document.getElementById(t.item):i(t.item):s()))throw new Error("The list needs to have at least one item on init otherwise you'll have to add a template.");r=n(r,t.valueNames),e=function(){return r.cloneNode(!0)}}else e=function(e){var r=t.item(e);return i(r)}}()};t.exports=function(t){return new e(t)}},"./src/utils/classes.js":function(t,e,r){var n=r("./src/utils/index-of.js"),s=/\s+/;Object.prototype.toString;function i(t){if(!t||!t.nodeType)throw new Error("A DOM element reference is required");this.el=t,this.list=t.classList}t.exports=function(t){return new i(t)},i.prototype.add=function(t){if(this.list)return this.list.add(t),this;var e=this.array();return~n(e,t)||e.push(t),this.el.className=e.join(" "),this},i.prototype.remove=function(t){if(this.list)return this.list.remove(t),this;var e=this.array(),r=n(e,t);return~r&&e.splice(r,1),this.el.className=e.join(" "),this},i.prototype.toggle=function(t,e){return this.list?(void 0!==e?e!==this.list.toggle(t,e)&&this.list.toggle(t):this.list.toggle(t),this):(void 0!==e?e?this.add(t):this.remove(t):this.has(t)?this.remove(t):this.add(t),this)},i.prototype.array=function(){var t=(this.el.getAttribute("class")||"").replace(/^\s+|\s+$/g,"").split(s);return""===t[0]&&t.shift(),t},i.prototype.has=i.prototype.contains=function(t){return this.list?this.list.contains(t):!!~n(this.array(),t)}},"./src/utils/events.js":function(t,e,r){var n=window.addEventListener?"addEventListener":"attachEvent",s=window.removeEventListener?"removeEventListener":"detachEvent",i="addEventListener"!==n?"on":"",a=r("./src/utils/to-array.js");e.bind=function(t,e,r,s){for(var o=0,l=(t=a(t)).length;o32)return!1;var a=n,o=function(){var t,r={};for(t=0;t=p;b--){var j=o[t.charAt(b-1)];if(C[b]=0===m?(C[b+1]<<1|1)&j:(C[b+1]<<1|1)&j|(v[b+1]|v[b])<<1|1|v[b+1],C[b]&d){var x=l(m,b-1);if(x<=u){if(u=x,!((c=b-1)>a))break;p=Math.max(1,2*a-c)}}}if(l(m+1,a)>u)break;v=C}return!(c<0)}},"./src/utils/get-attribute.js":function(t){t.exports=function(t,e){var r=t.getAttribute&&t.getAttribute(e)||null;if(!r)for(var n=t.attributes,s=n.length,i=0;i=48&&t<=57}function i(t,e){for(var i=(t+="").length,a=(e+="").length,o=0,l=0;o=i&&l=a?-1:l>=a&&o=i?1:i-a}i.caseInsensitive=i.i=function(t,e){return i((""+t).toLowerCase(),(""+e).toLowerCase())},Object.defineProperties(i,{alphabet:{get:function(){return e},set:function(t){r=[];var s=0;if(e=t)for(;s { + if (categoriesLoaded) { + activateCategory(category); + setCategoryHash(category); + } +}; + +window["quarto-listing-loaded"] = () => { + // Process any existing hash + const hash = getHash(); + + if (hash) { + // If there is a category, switch to that + if (hash.category) { + activateCategory(hash.category); + } + // Paginate a specific listing + const listingIds = Object.keys(window["quarto-listings"]); + for (const listingId of listingIds) { + const page = hash[getListingPageKey(listingId)]; + if (page) { + showPage(listingId, page); + } + } + } + + const listingIds = Object.keys(window["quarto-listings"]); + for (const listingId of listingIds) { + // The actual list + const list = window["quarto-listings"][listingId]; + + // Update the handlers for pagination events + refreshPaginationHandlers(listingId); + + // Render any visible items that need it + renderVisibleProgressiveImages(list); + + // Whenever the list is updated, we also need to + // attach handlers to the new pagination elements + // and refresh any newly visible items. + list.on("updated", function () { + renderVisibleProgressiveImages(list); + setTimeout(() => refreshPaginationHandlers(listingId)); + + // Show or hide the no matching message + toggleNoMatchingMessage(list); + }); + } +}; + +window.document.addEventListener("DOMContentLoaded", function (_event) { + // Attach click handlers to categories + const categoryEls = window.document.querySelectorAll( + ".quarto-listing-category .category" + ); + + for (const categoryEl of categoryEls) { + const category = categoryEl.getAttribute("data-category"); + categoryEl.onclick = () => { + activateCategory(category); + setCategoryHash(category); + }; + } + + // Attach a click handler to the category title + // (there should be only one, but since it is a class name, handle N) + const categoryTitleEls = window.document.querySelectorAll( + ".quarto-listing-category-title" + ); + for (const categoryTitleEl of categoryTitleEls) { + categoryTitleEl.onclick = () => { + activateCategory(""); + setCategoryHash(""); + }; + } + + categoriesLoaded = true; +}); + +function toggleNoMatchingMessage(list) { + const selector = `#${list.listContainer.id} .listing-no-matching`; + const noMatchingEl = window.document.querySelector(selector); + if (noMatchingEl) { + if (list.visibleItems.length === 0) { + noMatchingEl.classList.remove("d-none"); + } else { + if (!noMatchingEl.classList.contains("d-none")) { + noMatchingEl.classList.add("d-none"); + } + } + } +} + +function setCategoryHash(category) { + setHash({ category }); +} + +function setPageHash(listingId, page) { + const currentHash = getHash() || {}; + currentHash[getListingPageKey(listingId)] = page; + setHash(currentHash); +} + +function getListingPageKey(listingId) { + return `${listingId}-page`; +} + +function refreshPaginationHandlers(listingId) { + const listingEl = window.document.getElementById(listingId); + const paginationEls = listingEl.querySelectorAll( + ".pagination li.page-item:not(.disabled) .page.page-link" + ); + for (const paginationEl of paginationEls) { + paginationEl.onclick = (sender) => { + setPageHash(listingId, sender.target.getAttribute("data-i")); + showPage(listingId, sender.target.getAttribute("data-i")); + return false; + }; + } +} + +function renderVisibleProgressiveImages(list) { + // Run through the visible items and render any progressive images + for (const item of list.visibleItems) { + const itemEl = item.elm; + if (itemEl) { + const progressiveImgs = itemEl.querySelectorAll( + `img[${kProgressiveAttr}]` + ); + for (const progressiveImg of progressiveImgs) { + const srcValue = progressiveImg.getAttribute(kProgressiveAttr); + if (srcValue) { + progressiveImg.setAttribute("src", srcValue); + } + progressiveImg.removeAttribute(kProgressiveAttr); + } + } + } +} + +function getHash() { + // Hashes are of the form + // #name:value|name1:value1|name2:value2 + const currentUrl = new URL(window.location); + const hashRaw = currentUrl.hash ? currentUrl.hash.slice(1) : undefined; + return parseHash(hashRaw); +} + +const kAnd = "&"; +const kEquals = "="; + +function parseHash(hash) { + if (!hash) { + return undefined; + } + const hasValuesStrs = hash.split(kAnd); + const hashValues = hasValuesStrs + .map((hashValueStr) => { + const vals = hashValueStr.split(kEquals); + if (vals.length === 2) { + return { name: vals[0], value: vals[1] }; + } else { + return undefined; + } + }) + .filter((value) => { + return value !== undefined; + }); + + const hashObj = {}; + hashValues.forEach((hashValue) => { + hashObj[hashValue.name] = decodeURIComponent(hashValue.value); + }); + return hashObj; +} + +function makeHash(obj) { + return Object.keys(obj) + .map((key) => { + return `${key}${kEquals}${obj[key]}`; + }) + .join(kAnd); +} + +function setHash(obj) { + const hash = makeHash(obj); + window.history.pushState(null, null, `#${hash}`); +} + +function showPage(listingId, page) { + const list = window["quarto-listings"][listingId]; + if (list) { + list.show((page - 1) * list.page + 1, list.page); + } +} + +function activateCategory(category) { + // Deactivate existing categories + const activeEls = window.document.querySelectorAll( + ".quarto-listing-category .category.active" + ); + for (const activeEl of activeEls) { + activeEl.classList.remove("active"); + } + + // Activate this category + const categoryEl = window.document.querySelector( + `.quarto-listing-category .category[data-category='${category}'` + ); + if (categoryEl) { + categoryEl.classList.add("active"); + } + + // Filter the listings to this category + filterListingCategory(category); +} + +function filterListingCategory(category) { + const listingIds = Object.keys(window["quarto-listings"]); + for (const listingId of listingIds) { + const list = window["quarto-listings"][listingId]; + if (list) { + if (category === "") { + // resets the filter + list.filter(); + } else { + // filter to this category + list.filter(function (item) { + const itemValues = item.values(); + if (itemValues.categories !== null) { + const categories = itemValues.categories.split(","); + return categories.includes(category); + } else { + return false; + } + }); + } + } + } +} diff --git a/site_libs/quarto-nav/quarto-nav.js b/site_libs/quarto-nav/quarto-nav.js new file mode 100644 index 0000000..3b21201 --- /dev/null +++ b/site_libs/quarto-nav/quarto-nav.js @@ -0,0 +1,277 @@ +const headroomChanged = new CustomEvent("quarto-hrChanged", { + detail: {}, + bubbles: true, + cancelable: false, + composed: false, +}); + +window.document.addEventListener("DOMContentLoaded", function () { + let init = false; + + // Manage the back to top button, if one is present. + let lastScrollTop = window.pageYOffset || document.documentElement.scrollTop; + const scrollDownBuffer = 5; + const scrollUpBuffer = 35; + const btn = document.getElementById("quarto-back-to-top"); + const hideBackToTop = () => { + btn.style.display = "none"; + }; + const showBackToTop = () => { + btn.style.display = "inline-block"; + }; + if (btn) { + window.document.addEventListener( + "scroll", + function () { + const currentScrollTop = + window.pageYOffset || document.documentElement.scrollTop; + + // Shows and hides the button 'intelligently' as the user scrolls + if (currentScrollTop - scrollDownBuffer > lastScrollTop) { + hideBackToTop(); + lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop; + } else if (currentScrollTop < lastScrollTop - scrollUpBuffer) { + showBackToTop(); + lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop; + } + + // Show the button at the bottom, hides it at the top + if (currentScrollTop <= 0) { + hideBackToTop(); + } else if ( + window.innerHeight + currentScrollTop >= + document.body.offsetHeight + ) { + showBackToTop(); + } + }, + false + ); + } + + function throttle(func, wait) { + var timeout; + return function () { + const context = this; + const args = arguments; + const later = function () { + clearTimeout(timeout); + timeout = null; + func.apply(context, args); + }; + + if (!timeout) { + timeout = setTimeout(later, wait); + } + }; + } + + function headerOffset() { + // Set an offset if there is are fixed top navbar + const headerEl = window.document.querySelector("header.fixed-top"); + if (headerEl) { + return headerEl.clientHeight; + } else { + return 0; + } + } + + function footerOffset() { + const footerEl = window.document.querySelector("footer.footer"); + if (footerEl) { + return footerEl.clientHeight; + } else { + return 0; + } + } + + function updateDocumentOffsetWithoutAnimation() { + updateDocumentOffset(false); + } + + function updateDocumentOffset(animated) { + // set body offset + const topOffset = headerOffset(); + const bodyOffset = topOffset + footerOffset(); + const bodyEl = window.document.body; + bodyEl.setAttribute("data-bs-offset", topOffset); + bodyEl.style.paddingTop = topOffset + "px"; + + // deal with sidebar offsets + const sidebars = window.document.querySelectorAll( + ".sidebar, .headroom-target" + ); + sidebars.forEach((sidebar) => { + if (!animated) { + sidebar.classList.add("notransition"); + // Remove the no transition class after the animation has time to complete + setTimeout(function () { + sidebar.classList.remove("notransition"); + }, 201); + } + + if (window.Headroom && sidebar.classList.contains("sidebar-unpinned")) { + sidebar.style.top = "0"; + sidebar.style.maxHeight = "100vh"; + } else { + sidebar.style.top = topOffset + "px"; + sidebar.style.maxHeight = "calc(100vh - " + topOffset + "px)"; + } + }); + + // allow space for footer + const mainContainer = window.document.querySelector(".quarto-container"); + if (mainContainer) { + mainContainer.style.minHeight = "calc(100vh - " + bodyOffset + "px)"; + } + + // link offset + let linkStyle = window.document.querySelector("#quarto-target-style"); + if (!linkStyle) { + linkStyle = window.document.createElement("style"); + linkStyle.setAttribute("id", "quarto-target-style"); + window.document.head.appendChild(linkStyle); + } + while (linkStyle.firstChild) { + linkStyle.removeChild(linkStyle.firstChild); + } + if (topOffset > 0) { + linkStyle.appendChild( + window.document.createTextNode(` + section:target::before { + content: ""; + display: block; + height: ${topOffset}px; + margin: -${topOffset}px 0 0; + }`) + ); + } + if (init) { + window.dispatchEvent(headroomChanged); + } + init = true; + } + + // initialize headroom + var header = window.document.querySelector("#quarto-header"); + if (header && window.Headroom) { + const headroom = new window.Headroom(header, { + tolerance: 5, + onPin: function () { + const sidebars = window.document.querySelectorAll( + ".sidebar, .headroom-target" + ); + sidebars.forEach((sidebar) => { + sidebar.classList.remove("sidebar-unpinned"); + }); + updateDocumentOffset(); + }, + onUnpin: function () { + const sidebars = window.document.querySelectorAll( + ".sidebar, .headroom-target" + ); + sidebars.forEach((sidebar) => { + sidebar.classList.add("sidebar-unpinned"); + }); + updateDocumentOffset(); + }, + }); + headroom.init(); + + let frozen = false; + window.quartoToggleHeadroom = function () { + if (frozen) { + headroom.unfreeze(); + frozen = false; + } else { + headroom.freeze(); + frozen = true; + } + }; + } + + window.addEventListener( + "hashchange", + function (e) { + if ( + getComputedStyle(document.documentElement).scrollBehavior !== "smooth" + ) { + window.scrollTo(0, window.pageYOffset - headerOffset()); + } + }, + false + ); + + // Observe size changed for the header + const headerEl = window.document.querySelector("header.fixed-top"); + if (headerEl && window.ResizeObserver) { + const observer = new window.ResizeObserver( + updateDocumentOffsetWithoutAnimation + ); + observer.observe(headerEl, { + attributes: true, + childList: true, + characterData: true, + }); + } else { + window.addEventListener( + "resize", + throttle(updateDocumentOffsetWithoutAnimation, 50) + ); + } + setTimeout(updateDocumentOffsetWithoutAnimation, 250); + + // fixup index.html links if we aren't on the filesystem + if (window.location.protocol !== "file:") { + const links = window.document.querySelectorAll("a"); + for (let i = 0; i < links.length; i++) { + if (links[i].href) { + links[i].href = links[i].href.replace(/\/index\.html/, "/"); + } + } + + // Fixup any sharing links that require urls + // Append url to any sharing urls + const sharingLinks = window.document.querySelectorAll( + "a.sidebar-tools-main-item" + ); + for (let i = 0; i < sharingLinks.length; i++) { + const sharingLink = sharingLinks[i]; + const href = sharingLink.getAttribute("href"); + if (href) { + sharingLink.setAttribute( + "href", + href.replace("|url|", window.location.href) + ); + } + } + + // Scroll the active navigation item into view, if necessary + const navSidebar = window.document.querySelector("nav#quarto-sidebar"); + if (navSidebar) { + // Find the active item + const activeItem = navSidebar.querySelector("li.sidebar-item a.active"); + if (activeItem) { + // Wait for the scroll height and height to resolve by observing size changes on the + // nav element that is scrollable + const resizeObserver = new ResizeObserver((_entries) => { + // The bottom of the element + const elBottom = activeItem.offsetTop; + const viewBottom = navSidebar.scrollTop + navSidebar.clientHeight; + + // The element height and scroll height are the same, then we are still loading + if (viewBottom !== navSidebar.scrollHeight) { + // Determine if the item isn't visible and scroll to it + if (elBottom >= viewBottom) { + navSidebar.scrollTop = elBottom; + } + + // stop observing now since we've completed the scroll + resizeObserver.unobserve(navSidebar); + } + }); + resizeObserver.observe(navSidebar); + } + } + } +}); diff --git a/site_libs/quarto-search/autocomplete.umd.js b/site_libs/quarto-search/autocomplete.umd.js new file mode 100644 index 0000000..619c57c --- /dev/null +++ b/site_libs/quarto-search/autocomplete.umd.js @@ -0,0 +1,3 @@ +/*! @algolia/autocomplete-js 1.7.3 | MIT License | © Algolia, Inc. and contributors | https://github.com/algolia/autocomplete */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self)["@algolia/autocomplete-js"]={})}(this,(function(e){"use strict";function t(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function n(e){for(var n=1;n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function a(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null==n)return;var r,o,i=[],u=!0,a=!1;try{for(n=n.call(e);!(u=(r=n.next()).done)&&(i.push(r.value),!t||i.length!==t);u=!0);}catch(e){a=!0,o=e}finally{try{u||null==n.return||n.return()}finally{if(a)throw o}}return i}(e,t)||l(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function c(e){return function(e){if(Array.isArray(e))return s(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||l(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function l(e,t){if(e){if("string"==typeof e)return s(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?s(e,t):void 0}}function s(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=n?null===r?null:0:o}function S(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function I(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function E(e,t){var n=[];return Promise.resolve(e(t)).then((function(e){return Promise.all(e.filter((function(e){return Boolean(e)})).map((function(e){if(e.sourceId,n.includes(e.sourceId))throw new Error("[Autocomplete] The `sourceId` ".concat(JSON.stringify(e.sourceId)," is not unique."));n.push(e.sourceId);var t=function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var ae,ce,le,se=null,pe=(ae=-1,ce=-1,le=void 0,function(e){var t=++ae;return Promise.resolve(e).then((function(e){return le&&t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var ye=["props","refresh","store"],be=["inputElement","formElement","panelElement"],Oe=["inputElement"],_e=["inputElement","maxLength"],Pe=["item","source"];function je(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function we(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function Ee(e){var t=e.props,n=e.refresh,r=e.store,o=Ie(e,ye);return{getEnvironmentProps:function(e){var n=e.inputElement,o=e.formElement,i=e.panelElement;function u(e){!r.getState().isOpen&&r.pendingRequests.isEmpty()||e.target===n||!1===[o,i].some((function(t){return n=t,r=e.target,n===r||n.contains(r);var n,r}))&&(r.dispatch("blur",null),t.debug||r.pendingRequests.cancelAll())}return we({onTouchStart:u,onMouseDown:u,onTouchMove:function(e){!1!==r.getState().isOpen&&n===t.environment.document.activeElement&&e.target!==n&&n.blur()}},Ie(e,be))},getRootProps:function(e){return we({role:"combobox","aria-expanded":r.getState().isOpen,"aria-haspopup":"listbox","aria-owns":r.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label")},e)},getFormProps:function(e){return e.inputElement,we({action:"",noValidate:!0,role:"search",onSubmit:function(i){var u;i.preventDefault(),t.onSubmit(we({event:i,refresh:n,state:r.getState()},o)),r.dispatch("submit",null),null===(u=e.inputElement)||void 0===u||u.blur()},onReset:function(i){var u;i.preventDefault(),t.onReset(we({event:i,refresh:n,state:r.getState()},o)),r.dispatch("reset",null),null===(u=e.inputElement)||void 0===u||u.focus()}},Ie(e,Oe))},getLabelProps:function(e){return we({htmlFor:"".concat(t.id,"-input"),id:"".concat(t.id,"-label")},e)},getInputProps:function(e){var i;function u(e){(t.openOnFocus||Boolean(r.getState().query))&&fe(we({event:e,props:t,query:r.getState().completion||r.getState().query,refresh:n,store:r},o)),r.dispatch("focus",null)}var a=e||{};a.inputElement;var c=a.maxLength,l=void 0===c?512:c,s=Ie(a,_e),p=A(r.getState()),f=function(e){return Boolean(e&&e.match(C))}((null===(i=t.environment.navigator)||void 0===i?void 0:i.userAgent)||""),d=null!=p&&p.itemUrl&&!f?"go":"search";return we({"aria-autocomplete":"both","aria-activedescendant":r.getState().isOpen&&null!==r.getState().activeItemId?"".concat(t.id,"-item-").concat(r.getState().activeItemId):void 0,"aria-controls":r.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label"),value:r.getState().completion||r.getState().query,id:"".concat(t.id,"-input"),autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",enterKeyHint:d,spellCheck:"false",autoFocus:t.autoFocus,placeholder:t.placeholder,maxLength:l,type:"search",onChange:function(e){fe(we({event:e,props:t,query:e.currentTarget.value.slice(0,l),refresh:n,store:r},o))},onKeyDown:function(e){!function(e){var t=e.event,n=e.props,r=e.refresh,o=e.store,i=ge(e,de);if("ArrowUp"===t.key||"ArrowDown"===t.key){var u=function(){var e=n.environment.document.getElementById("".concat(n.id,"-item-").concat(o.getState().activeItemId));e&&(e.scrollIntoViewIfNeeded?e.scrollIntoViewIfNeeded(!1):e.scrollIntoView(!1))},a=function(){var e=A(o.getState());if(null!==o.getState().activeItemId&&e){var n=e.item,u=e.itemInputValue,a=e.itemUrl,c=e.source;c.onActive(ve({event:t,item:n,itemInputValue:u,itemUrl:a,refresh:r,source:c,state:o.getState()},i))}};t.preventDefault(),!1===o.getState().isOpen&&(n.openOnFocus||Boolean(o.getState().query))?fe(ve({event:t,props:n,query:o.getState().query,refresh:r,store:o},i)).then((function(){o.dispatch(t.key,{nextActiveItemId:n.defaultActiveItemId}),a(),setTimeout(u,0)})):(o.dispatch(t.key,{}),a(),u())}else if("Escape"===t.key)t.preventDefault(),o.dispatch(t.key,null),o.pendingRequests.cancelAll();else if("Tab"===t.key)o.dispatch("blur",null),o.pendingRequests.cancelAll();else if("Enter"===t.key){if(null===o.getState().activeItemId||o.getState().collections.every((function(e){return 0===e.items.length})))return void(n.debug||o.pendingRequests.cancelAll());t.preventDefault();var c=A(o.getState()),l=c.item,s=c.itemInputValue,p=c.itemUrl,f=c.source;if(t.metaKey||t.ctrlKey)void 0!==p&&(f.onSelect(ve({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i)),n.navigator.navigateNewTab({itemUrl:p,item:l,state:o.getState()}));else if(t.shiftKey)void 0!==p&&(f.onSelect(ve({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i)),n.navigator.navigateNewWindow({itemUrl:p,item:l,state:o.getState()}));else if(t.altKey);else{if(void 0!==p)return f.onSelect(ve({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i)),void n.navigator.navigate({itemUrl:p,item:l,state:o.getState()});fe(ve({event:t,nextState:{isOpen:!1},props:n,query:s,refresh:r,store:o},i)).then((function(){f.onSelect(ve({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i))}))}}}(we({event:e,props:t,refresh:n,store:r},o))},onFocus:u,onBlur:y,onClick:function(n){e.inputElement!==t.environment.document.activeElement||r.getState().isOpen||u(n)}},s)},getPanelProps:function(e){return we({onMouseDown:function(e){e.preventDefault()},onMouseLeave:function(){r.dispatch("mouseleave",null)}},e)},getListProps:function(e){return we({role:"listbox","aria-labelledby":"".concat(t.id,"-label"),id:"".concat(t.id,"-list")},e)},getItemProps:function(e){var i=e.item,u=e.source,a=Ie(e,Pe);return we({id:"".concat(t.id,"-item-").concat(i.__autocomplete_id),role:"option","aria-selected":r.getState().activeItemId===i.__autocomplete_id,onMouseMove:function(e){if(i.__autocomplete_id!==r.getState().activeItemId){r.dispatch("mousemove",i.__autocomplete_id);var t=A(r.getState());if(null!==r.getState().activeItemId&&t){var u=t.item,a=t.itemInputValue,c=t.itemUrl,l=t.source;l.onActive(we({event:e,item:u,itemInputValue:a,itemUrl:c,refresh:n,source:l,state:r.getState()},o))}}},onMouseDown:function(e){e.preventDefault()},onClick:function(e){var a=u.getItemInputValue({item:i,state:r.getState()}),c=u.getItemUrl({item:i,state:r.getState()});(c?Promise.resolve():fe(we({event:e,nextState:{isOpen:!1},props:t,query:a,refresh:n,store:r},o))).then((function(){u.onSelect(we({event:e,item:i,itemInputValue:a,itemUrl:c,refresh:n,source:u,state:r.getState()},o))}))}},a)}}}function Ae(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Ce(e){for(var t=1;t0},reshape:function(e){return e.sources}},e),{},{id:null!==(n=e.id)&&void 0!==n?n:v(),plugins:o,initialState:H({activeItemId:null,query:"",completion:null,collections:[],isOpen:!1,status:"idle",context:{}},e.initialState),onStateChange:function(t){var n;null===(n=e.onStateChange)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onStateChange)||void 0===n?void 0:n.call(e,t)}))},onSubmit:function(t){var n;null===(n=e.onSubmit)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onSubmit)||void 0===n?void 0:n.call(e,t)}))},onReset:function(t){var n;null===(n=e.onReset)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onReset)||void 0===n?void 0:n.call(e,t)}))},getSources:function(n){return Promise.all([].concat(F(o.map((function(e){return e.getSources}))),[e.getSources]).filter(Boolean).map((function(e){return E(e,n)}))).then((function(e){return d(e)})).then((function(e){return e.map((function(e){return H(H({},e),{},{onSelect:function(n){e.onSelect(n),t.forEach((function(e){var t;return null===(t=e.onSelect)||void 0===t?void 0:t.call(e,n)}))},onActive:function(n){e.onActive(n),t.forEach((function(e){var t;return null===(t=e.onActive)||void 0===t?void 0:t.call(e,n)}))}})}))}))},navigator:H({navigate:function(e){var t=e.itemUrl;r.location.assign(t)},navigateNewTab:function(e){var t=e.itemUrl,n=r.open(t,"_blank","noopener");null==n||n.focus()},navigateNewWindow:function(e){var t=e.itemUrl;r.open(t,"_blank","noopener")}},e.navigator)})}(e,t),r=R(Te,n,(function(e){var t=e.prevState,r=e.state;n.onStateChange(Be({prevState:t,state:r,refresh:u},o))})),o=function(e){var t=e.store;return{setActiveItemId:function(e){t.dispatch("setActiveItemId",e)},setQuery:function(e){t.dispatch("setQuery",e)},setCollections:function(e){var n=0,r=e.map((function(e){return L(L({},e),{},{items:d(e.items).map((function(e){return L(L({},e),{},{__autocomplete_id:n++})}))})}));t.dispatch("setCollections",r)},setIsOpen:function(e){t.dispatch("setIsOpen",e)},setStatus:function(e){t.dispatch("setStatus",e)},setContext:function(e){t.dispatch("setContext",e)}}}({store:r}),i=Ee(Be({props:n,refresh:u,store:r},o));function u(){return fe(Be({event:new Event("input"),nextState:{isOpen:r.getState().isOpen},props:n,query:r.getState().query,refresh:u,store:r},o))}return n.plugins.forEach((function(e){var n;return null===(n=e.subscribe)||void 0===n?void 0:n.call(e,Be(Be({},o),{},{refresh:u,onSelect:function(e){t.push({onSelect:e})},onActive:function(e){t.push({onActive:e})}}))})),function(e){var t,n,r=e.metadata,o=e.environment;if(null===(t=o.navigator)||void 0===t||null===(n=t.userAgent)||void 0===n?void 0:n.includes("Algolia Crawler")){var i=o.document.createElement("meta"),u=o.document.querySelector("head");i.name="algolia:metadata",setTimeout((function(){i.content=JSON.stringify(r),u.appendChild(i)}),0)}}({metadata:ke({plugins:n.plugins,options:e}),environment:n.environment}),Be(Be({refresh:u},i),o)}var Ue=function(e,t,n,r){var o;t[0]=0;for(var i=1;i=5&&((o||!e&&5===r)&&(u.push(r,0,o,n),r=6),e&&(u.push(r,e,0,n),r=6)),o=""},c=0;c"===t?(r=1,o=""):o=t+o[0]:i?t===i?i="":o+=t:'"'===t||"'"===t?i=t:">"===t?(a(),r=1):r&&("="===t?(r=5,n=o,o=""):"/"===t&&(r<5||">"===e[c][l+1])?(a(),3===r&&(u=u[0]),r=u,(u=u[0]).push(2,0,r),r=0):" "===t||"\t"===t||"\n"===t||"\r"===t?(a(),r=2):o+=t),3===r&&"!--"===o&&(r=4,u=u[0])}return a(),u}(e)),t),arguments,[])).length>1?t:t[0]}var We=function(e){var t=e.environment,n=t.document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("class","aa-ClearIcon"),n.setAttribute("viewBox","0 0 24 24"),n.setAttribute("width","18"),n.setAttribute("height","18"),n.setAttribute("fill","currentColor");var r=t.document.createElementNS("http://www.w3.org/2000/svg","path");return r.setAttribute("d","M5.293 6.707l5.293 5.293-5.293 5.293c-0.391 0.391-0.391 1.024 0 1.414s1.024 0.391 1.414 0l5.293-5.293 5.293 5.293c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414l-5.293-5.293 5.293-5.293c0.391-0.391 0.391-1.024 0-1.414s-1.024-0.391-1.414 0l-5.293 5.293-5.293-5.293c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414z"),n.appendChild(r),n};function Qe(e,t){if("string"==typeof t){var n=e.document.querySelector(t);return"The element ".concat(JSON.stringify(t)," is not in the document."),n}return t}function $e(){for(var e=arguments.length,t=new Array(e),n=0;n2&&(u.children=arguments.length>3?lt.call(arguments,2):n),"function"==typeof e&&null!=e.defaultProps)for(i in e.defaultProps)void 0===u[i]&&(u[i]=e.defaultProps[i]);return _t(e,u,r,o,null)}function _t(e,t,n,r,o){var i={type:e,props:t,key:n,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==o?++pt:o};return null==o&&null!=st.vnode&&st.vnode(i),i}function Pt(e){return e.children}function jt(e,t){this.props=e,this.context=t}function wt(e,t){if(null==t)return e.__?wt(e.__,e.__.__k.indexOf(e)+1):null;for(var n;t0?_t(d.type,d.props,d.key,null,d.__v):d)){if(d.__=n,d.__b=n.__b+1,null===(f=g[s])||f&&d.key==f.key&&d.type===f.type)g[s]=void 0;else for(p=0;p0&&void 0!==arguments[0]?arguments[0]:[];return{get:function(){return e},add:function(t){var n=e[e.length-1];(null==n?void 0:n.isHighlighted)===t.isHighlighted?e[e.length-1]={value:n.value+t.value,isHighlighted:n.isHighlighted}:e.push(t)}}}(n?[{value:n,isHighlighted:!1}]:[]);return t.forEach((function(e){var t=e.split(Ht);r.add({value:t[0],isHighlighted:!0}),""!==t[1]&&r.add({value:t[1],isHighlighted:!1})})),r.get()}function Wt(e){return function(e){if(Array.isArray(e))return Qt(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return Qt(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Qt(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function Qt(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n",""":'"',"'":"'"},Gt=new RegExp(/\w/i),Kt=/&(amp|quot|lt|gt|#39);/g,Jt=RegExp(Kt.source);function Yt(e,t){var n,r,o,i=e[t],u=(null===(n=e[t+1])||void 0===n?void 0:n.isHighlighted)||!0,a=(null===(r=e[t-1])||void 0===r?void 0:r.isHighlighted)||!0;return Gt.test((o=i.value)&&Jt.test(o)?o.replace(Kt,(function(e){return zt[e]})):o)||a!==u?i.isHighlighted:a}function Xt(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Zt(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function mn(e){return function(e){if(Array.isArray(e))return vn(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return vn(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return vn(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function vn(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0;if(!O.value.core.openOnFocus&&!t.query)return n;var r=Boolean(h.current||O.value.renderer.renderNoResults);return!n&&r||n},__autocomplete_metadata:{userAgents:Sn,options:e}}))})),j=p(n({collections:[],completion:null,context:{},isOpen:!1,query:"",activeItemId:null,status:"idle"},O.value.core.initialState)),w={getEnvironmentProps:O.value.renderer.getEnvironmentProps,getFormProps:O.value.renderer.getFormProps,getInputProps:O.value.renderer.getInputProps,getItemProps:O.value.renderer.getItemProps,getLabelProps:O.value.renderer.getLabelProps,getListProps:O.value.renderer.getListProps,getPanelProps:O.value.renderer.getPanelProps,getRootProps:O.value.renderer.getRootProps},S={setActiveItemId:P.value.setActiveItemId,setQuery:P.value.setQuery,setCollections:P.value.setCollections,setIsOpen:P.value.setIsOpen,setStatus:P.value.setStatus,setContext:P.value.setContext,refresh:P.value.refresh},I=d((function(){return Ve.bind(O.value.renderer.renderer.createElement)})),E=d((function(){return ct({autocomplete:P.value,autocompleteScopeApi:S,classNames:O.value.renderer.classNames,environment:O.value.core.environment,isDetached:_.value,placeholder:O.value.core.placeholder,propGetters:w,setIsModalOpen:k,state:j.current,translations:O.value.renderer.translations})}));function A(){tt(E.value.panel,{style:_.value?{}:wn({panelPlacement:O.value.renderer.panelPlacement,container:E.value.root,form:E.value.form,environment:O.value.core.environment})})}function C(e){j.current=e;var t={autocomplete:P.value,autocompleteScopeApi:S,classNames:O.value.renderer.classNames,components:O.value.renderer.components,container:O.value.renderer.container,html:I.value,dom:E.value,panelContainer:_.value?E.value.detachedContainer:O.value.renderer.panelContainer,propGetters:w,state:j.current,renderer:O.value.renderer.renderer},r=!g(e)&&!h.current&&O.value.renderer.renderNoResults||O.value.renderer.render;!function(e){var t=e.autocomplete,r=e.autocompleteScopeApi,o=e.dom,i=e.propGetters,u=e.state;nt(o.root,i.getRootProps(n({state:u,props:t.getRootProps({})},r))),nt(o.input,i.getInputProps(n({state:u,props:t.getInputProps({inputElement:o.input}),inputElement:o.input},r))),tt(o.label,{hidden:"stalled"===u.status}),tt(o.loadingIndicator,{hidden:"stalled"!==u.status}),tt(o.clearButton,{hidden:!u.query})}(t),function(e,t){var r=t.autocomplete,o=t.autocompleteScopeApi,u=t.classNames,a=t.html,c=t.dom,l=t.panelContainer,s=t.propGetters,p=t.state,f=t.components,d=t.renderer;if(p.isOpen){l.contains(c.panel)||"loading"===p.status||l.appendChild(c.panel),c.panel.classList.toggle("aa-Panel--stalled","stalled"===p.status);var m=p.collections.filter((function(e){var t=e.source,n=e.items;return t.templates.noResults||n.length>0})).map((function(e,t){var c=e.source,l=e.items;return d.createElement("section",{key:t,className:u.source,"data-autocomplete-source-id":c.sourceId},c.templates.header&&d.createElement("div",{className:u.sourceHeader},c.templates.header({components:f,createElement:d.createElement,Fragment:d.Fragment,items:l,source:c,state:p,html:a})),c.templates.noResults&&0===l.length?d.createElement("div",{className:u.sourceNoResults},c.templates.noResults({components:f,createElement:d.createElement,Fragment:d.Fragment,source:c,state:p,html:a})):d.createElement("ul",i({className:u.list},s.getListProps(n({state:p,props:r.getListProps({})},o))),l.map((function(e){var t=r.getItemProps({item:e,source:c});return d.createElement("li",i({key:t.id,className:u.item},s.getItemProps(n({state:p,props:t},o))),c.templates.item({components:f,createElement:d.createElement,Fragment:d.Fragment,item:e,state:p,html:a}))}))),c.templates.footer&&d.createElement("div",{className:u.sourceFooter},c.templates.footer({components:f,createElement:d.createElement,Fragment:d.Fragment,items:l,source:c,state:p,html:a})))})),v=d.createElement(d.Fragment,null,d.createElement("div",{className:u.panelLayout},m),d.createElement("div",{className:"aa-GradientBottom"})),h=m.reduce((function(e,t){return e[t.props["data-autocomplete-source-id"]]=t,e}),{});e(n(n({children:v,state:p,sections:m,elements:h},d),{},{components:f,html:a},o),c.panel)}else l.contains(c.panel)&&l.removeChild(c.panel)}(r,t)}function D(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};c();var t=O.value.renderer,n=t.components,r=u(t,In);y.current=Ge(r,O.value.core,{components:Ke(n,(function(e){return!e.value.hasOwnProperty("__autocomplete_componentName")})),initialState:j.current},e),m(),l(),P.value.refresh().then((function(){C(j.current)}))}function k(e){requestAnimationFrame((function(){var t=O.value.core.environment.document.body.contains(E.value.detachedOverlay);e!==t&&(e?(O.value.core.environment.document.body.appendChild(E.value.detachedOverlay),O.value.core.environment.document.body.classList.add("aa-Detached"),E.value.input.focus()):(O.value.core.environment.document.body.removeChild(E.value.detachedOverlay),O.value.core.environment.document.body.classList.remove("aa-Detached"),P.value.setQuery(""),P.value.refresh()))}))}return a((function(){var e=P.value.getEnvironmentProps({formElement:E.value.form,panelElement:E.value.panel,inputElement:E.value.input});return tt(O.value.core.environment,e),function(){tt(O.value.core.environment,Object.keys(e).reduce((function(e,t){return n(n({},e),{},o({},t,void 0))}),{}))}})),a((function(){var e=_.value?O.value.core.environment.document.body:O.value.renderer.panelContainer,t=_.value?E.value.detachedOverlay:E.value.panel;return _.value&&j.current.isOpen&&k(!0),C(j.current),function(){e.contains(t)&&e.removeChild(t)}})),a((function(){var e=O.value.renderer.container;return e.appendChild(E.value.root),function(){e.removeChild(E.value.root)}})),a((function(){var e=f((function(e){C(e.state)}),0);return b.current=function(t){var n=t.state,r=t.prevState;(_.value&&r.isOpen!==n.isOpen&&k(n.isOpen),_.value||!n.isOpen||r.isOpen||A(),n.query!==r.query)&&O.value.core.environment.document.querySelectorAll(".aa-Panel--scrollable").forEach((function(e){0!==e.scrollTop&&(e.scrollTop=0)}));e({state:n})},function(){b.current=void 0}})),a((function(){var e=f((function(){var e=_.value;_.value=O.value.core.environment.matchMedia(O.value.renderer.detachedMediaQuery).matches,e!==_.value?D({}):requestAnimationFrame(A)}),20);return O.value.core.environment.addEventListener("resize",e),function(){O.value.core.environment.removeEventListener("resize",e)}})),a((function(){if(!_.value)return function(){};function e(e){E.value.detachedContainer.classList.toggle("aa-DetachedContainer--modal",e)}function t(t){e(t.matches)}var n=O.value.core.environment.matchMedia(getComputedStyle(O.value.core.environment.document.documentElement).getPropertyValue("--aa-detached-modal-media-query"));e(n.matches);var r=Boolean(n.addEventListener);return r?n.addEventListener("change",t):n.addListener(t),function(){r?n.removeEventListener("change",t):n.removeListener(t)}})),a((function(){return requestAnimationFrame(A),function(){}})),n(n({},S),{},{update:D,destroy:function(){c()}})},e.getAlgoliaFacets=function(e){var t=En({transformResponse:function(e){return e.facetHits}}),r=e.queries.map((function(e){return n(n({},e),{},{type:"facet"})}));return t(n(n({},e),{},{queries:r}))},e.getAlgoliaResults=An,Object.defineProperty(e,"__esModule",{value:!0})})); + diff --git a/site_libs/quarto-search/fuse.min.js b/site_libs/quarto-search/fuse.min.js new file mode 100644 index 0000000..adc2835 --- /dev/null +++ b/site_libs/quarto-search/fuse.min.js @@ -0,0 +1,9 @@ +/** + * Fuse.js v6.6.2 - Lightweight fuzzy-search (http://fusejs.io) + * + * Copyright (c) 2022 Kiro Risk (http://kiro.me) + * All Rights Reserved. Apache Software License 2.0 + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +var e,t;e=this,t=function(){"use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function t(t){for(var n=1;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:3,n=new Map,r=Math.pow(10,t);return{get:function(t){var i=t.match(C).length;if(n.has(i))return n.get(i);var o=1/Math.pow(i,.5*e),c=parseFloat(Math.round(o*r)/r);return n.set(i,c),c},clear:function(){n.clear()}}}var $=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.getFn,i=void 0===n?I.getFn:n,o=t.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o;r(this,e),this.norm=E(c,3),this.getFn=i,this.isCreated=!1,this.setIndexRecords()}return o(e,[{key:"setSources",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.docs=e}},{key:"setIndexRecords",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.records=e}},{key:"setKeys",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.keys=t,this._keysMap={},t.forEach((function(t,n){e._keysMap[t.id]=n}))}},{key:"create",value:function(){var e=this;!this.isCreated&&this.docs.length&&(this.isCreated=!0,g(this.docs[0])?this.docs.forEach((function(t,n){e._addString(t,n)})):this.docs.forEach((function(t,n){e._addObject(t,n)})),this.norm.clear())}},{key:"add",value:function(e){var t=this.size();g(e)?this._addString(e,t):this._addObject(e,t)}},{key:"removeAt",value:function(e){this.records.splice(e,1);for(var t=e,n=this.size();t2&&void 0!==arguments[2]?arguments[2]:{},r=n.getFn,i=void 0===r?I.getFn:r,o=n.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o,a=new $({getFn:i,fieldNormWeight:c});return a.setKeys(e.map(_)),a.setSources(t),a.create(),a}function R(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.errors,r=void 0===n?0:n,i=t.currentLocation,o=void 0===i?0:i,c=t.expectedLocation,a=void 0===c?0:c,s=t.distance,u=void 0===s?I.distance:s,h=t.ignoreLocation,l=void 0===h?I.ignoreLocation:h,f=r/e.length;if(l)return f;var d=Math.abs(a-o);return u?f+d/u:d?1:f}function N(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:I.minMatchCharLength,n=[],r=-1,i=-1,o=0,c=e.length;o=t&&n.push([r,i]),r=-1)}return e[o-1]&&o-r>=t&&n.push([r,o-1]),n}var P=32;function W(e){for(var t={},n=0,r=e.length;n1&&void 0!==arguments[1]?arguments[1]:{},o=i.location,c=void 0===o?I.location:o,a=i.threshold,s=void 0===a?I.threshold:a,u=i.distance,h=void 0===u?I.distance:u,l=i.includeMatches,f=void 0===l?I.includeMatches:l,d=i.findAllMatches,v=void 0===d?I.findAllMatches:d,g=i.minMatchCharLength,y=void 0===g?I.minMatchCharLength:g,p=i.isCaseSensitive,m=void 0===p?I.isCaseSensitive:p,k=i.ignoreLocation,M=void 0===k?I.ignoreLocation:k;if(r(this,e),this.options={location:c,threshold:s,distance:h,includeMatches:f,findAllMatches:v,minMatchCharLength:y,isCaseSensitive:m,ignoreLocation:M},this.pattern=m?t:t.toLowerCase(),this.chunks=[],this.pattern.length){var b=function(e,t){n.chunks.push({pattern:e,alphabet:W(e),startIndex:t})},x=this.pattern.length;if(x>P){for(var w=0,L=x%P,S=x-L;w3&&void 0!==arguments[3]?arguments[3]:{},i=r.location,o=void 0===i?I.location:i,c=r.distance,a=void 0===c?I.distance:c,s=r.threshold,u=void 0===s?I.threshold:s,h=r.findAllMatches,l=void 0===h?I.findAllMatches:h,f=r.minMatchCharLength,d=void 0===f?I.minMatchCharLength:f,v=r.includeMatches,g=void 0===v?I.includeMatches:v,y=r.ignoreLocation,p=void 0===y?I.ignoreLocation:y;if(t.length>P)throw new Error(w(P));for(var m,k=t.length,M=e.length,b=Math.max(0,Math.min(o,M)),x=u,L=b,S=d>1||g,_=S?Array(M):[];(m=e.indexOf(t,L))>-1;){var O=R(t,{currentLocation:m,expectedLocation:b,distance:a,ignoreLocation:p});if(x=Math.min(O,x),L=m+k,S)for(var j=0;j=z;q-=1){var B=q-1,J=n[e.charAt(B)];if(S&&(_[B]=+!!J),K[q]=(K[q+1]<<1|1)&J,F&&(K[q]|=(A[q+1]|A[q])<<1|1|A[q+1]),K[q]&$&&(C=R(t,{errors:F,currentLocation:B,expectedLocation:b,distance:a,ignoreLocation:p}))<=x){if(x=C,(L=B)<=b)break;z=Math.max(1,2*b-L)}}if(R(t,{errors:F+1,currentLocation:b,expectedLocation:b,distance:a,ignoreLocation:p})>x)break;A=K}var U={isMatch:L>=0,score:Math.max(.001,C)};if(S){var V=N(_,d);V.length?g&&(U.indices=V):U.isMatch=!1}return U}(e,n,i,{location:c+o,distance:a,threshold:s,findAllMatches:u,minMatchCharLength:h,includeMatches:r,ignoreLocation:l}),p=y.isMatch,m=y.score,k=y.indices;p&&(g=!0),v+=m,p&&k&&(d=[].concat(f(d),f(k)))}));var y={isMatch:g,score:g?v/this.chunks.length:1};return g&&r&&(y.indices=d),y}}]),e}(),z=function(){function e(t){r(this,e),this.pattern=t}return o(e,[{key:"search",value:function(){}}],[{key:"isMultiMatch",value:function(e){return D(e,this.multiRegex)}},{key:"isSingleMatch",value:function(e){return D(e,this.singleRegex)}}]),e}();function D(e,t){var n=e.match(t);return n?n[1]:null}var K=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e===this.pattern;return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"exact"}},{key:"multiRegex",get:function(){return/^="(.*)"$/}},{key:"singleRegex",get:function(){return/^=(.*)$/}}]),n}(z),q=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=-1===e.indexOf(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"$/}},{key:"singleRegex",get:function(){return/^!(.*)$/}}]),n}(z),B=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"prefix-exact"}},{key:"multiRegex",get:function(){return/^\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^\^(.*)$/}}]),n}(z),J=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-prefix-exact"}},{key:"multiRegex",get:function(){return/^!\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^!\^(.*)$/}}]),n}(z),U=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[e.length-this.pattern.length,e.length-1]}}}],[{key:"type",get:function(){return"suffix-exact"}},{key:"multiRegex",get:function(){return/^"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^(.*)\$$/}}]),n}(z),V=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-suffix-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^!(.*)\$$/}}]),n}(z),G=function(e){a(n,e);var t=l(n);function n(e){var i,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},c=o.location,a=void 0===c?I.location:c,s=o.threshold,u=void 0===s?I.threshold:s,h=o.distance,l=void 0===h?I.distance:h,f=o.includeMatches,d=void 0===f?I.includeMatches:f,v=o.findAllMatches,g=void 0===v?I.findAllMatches:v,y=o.minMatchCharLength,p=void 0===y?I.minMatchCharLength:y,m=o.isCaseSensitive,k=void 0===m?I.isCaseSensitive:m,M=o.ignoreLocation,b=void 0===M?I.ignoreLocation:M;return r(this,n),(i=t.call(this,e))._bitapSearch=new T(e,{location:a,threshold:u,distance:l,includeMatches:d,findAllMatches:g,minMatchCharLength:p,isCaseSensitive:k,ignoreLocation:b}),i}return o(n,[{key:"search",value:function(e){return this._bitapSearch.searchIn(e)}}],[{key:"type",get:function(){return"fuzzy"}},{key:"multiRegex",get:function(){return/^"(.*)"$/}},{key:"singleRegex",get:function(){return/^(.*)$/}}]),n}(z),H=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){for(var t,n=0,r=[],i=this.pattern.length;(t=e.indexOf(this.pattern,n))>-1;)n=t+i,r.push([t,n-1]);var o=!!r.length;return{isMatch:o,score:o?0:1,indices:r}}}],[{key:"type",get:function(){return"include"}},{key:"multiRegex",get:function(){return/^'"(.*)"$/}},{key:"singleRegex",get:function(){return/^'(.*)$/}}]),n}(z),Q=[K,H,B,J,V,U,q,G],X=Q.length,Y=/ +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/;function Z(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.split("|").map((function(e){for(var n=e.trim().split(Y).filter((function(e){return e&&!!e.trim()})),r=[],i=0,o=n.length;i1&&void 0!==arguments[1]?arguments[1]:{},i=n.isCaseSensitive,o=void 0===i?I.isCaseSensitive:i,c=n.includeMatches,a=void 0===c?I.includeMatches:c,s=n.minMatchCharLength,u=void 0===s?I.minMatchCharLength:s,h=n.ignoreLocation,l=void 0===h?I.ignoreLocation:h,f=n.findAllMatches,d=void 0===f?I.findAllMatches:f,v=n.location,g=void 0===v?I.location:v,y=n.threshold,p=void 0===y?I.threshold:y,m=n.distance,k=void 0===m?I.distance:m;r(this,e),this.query=null,this.options={isCaseSensitive:o,includeMatches:a,minMatchCharLength:u,findAllMatches:d,ignoreLocation:l,location:g,threshold:p,distance:k},this.pattern=o?t:t.toLowerCase(),this.query=Z(this.pattern,this.options)}return o(e,[{key:"searchIn",value:function(e){var t=this.query;if(!t)return{isMatch:!1,score:1};var n=this.options,r=n.includeMatches;e=n.isCaseSensitive?e:e.toLowerCase();for(var i=0,o=[],c=0,a=0,s=t.length;a-1&&(n.refIndex=e.idx),t.matches.push(n)}}))}function ve(e,t){t.score=e.score}function ge(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.includeMatches,i=void 0===r?I.includeMatches:r,o=n.includeScore,c=void 0===o?I.includeScore:o,a=[];return i&&a.push(de),c&&a.push(ve),e.map((function(e){var n=e.idx,r={item:t[n],refIndex:n};return a.length&&a.forEach((function(t){t(e,r)})),r}))}var ye=function(){function e(n){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=arguments.length>2?arguments[2]:void 0;r(this,e),this.options=t(t({},I),i),this.options.useExtendedSearch,this._keyStore=new S(this.options.keys),this.setCollection(n,o)}return o(e,[{key:"setCollection",value:function(e,t){if(this._docs=e,t&&!(t instanceof $))throw new Error("Incorrect 'index' type");this._myIndex=t||F(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}},{key:"add",value:function(e){k(e)&&(this._docs.push(e),this._myIndex.add(e))}},{key:"remove",value:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!1},t=[],n=0,r=this._docs.length;n1&&void 0!==arguments[1]?arguments[1]:{},n=t.limit,r=void 0===n?-1:n,i=this.options,o=i.includeMatches,c=i.includeScore,a=i.shouldSort,s=i.sortFn,u=i.ignoreFieldNorm,h=g(e)?g(this._docs[0])?this._searchStringList(e):this._searchObjectList(e):this._searchLogical(e);return fe(h,{ignoreFieldNorm:u}),a&&h.sort(s),y(r)&&r>-1&&(h=h.slice(0,r)),ge(h,this._docs,{includeMatches:o,includeScore:c})}},{key:"_searchStringList",value:function(e){var t=re(e,this.options),n=this._myIndex.records,r=[];return n.forEach((function(e){var n=e.v,i=e.i,o=e.n;if(k(n)){var c=t.searchIn(n),a=c.isMatch,s=c.score,u=c.indices;a&&r.push({item:n,idx:i,matches:[{score:s,value:n,norm:o,indices:u}]})}})),r}},{key:"_searchLogical",value:function(e){var t=this,n=function(e,t){var n=(arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}).auto,r=void 0===n||n,i=function e(n){var i=Object.keys(n),o=ue(n);if(!o&&i.length>1&&!se(n))return e(le(n));if(he(n)){var c=o?n[ce]:i[0],a=o?n[ae]:n[c];if(!g(a))throw new Error(x(c));var s={keyId:j(c),pattern:a};return r&&(s.searcher=re(a,t)),s}var u={children:[],operator:i[0]};return i.forEach((function(t){var r=n[t];v(r)&&r.forEach((function(t){u.children.push(e(t))}))})),u};return se(e)||(e=le(e)),i(e)}(e,this.options),r=function e(n,r,i){if(!n.children){var o=n.keyId,c=n.searcher,a=t._findMatches({key:t._keyStore.get(o),value:t._myIndex.getValueForItemAtKeyId(r,o),searcher:c});return a&&a.length?[{idx:i,item:r,matches:a}]:[]}for(var s=[],u=0,h=n.children.length;u1&&void 0!==arguments[1]?arguments[1]:{},n=t.getFn,r=void 0===n?I.getFn:n,i=t.fieldNormWeight,o=void 0===i?I.fieldNormWeight:i,c=e.keys,a=e.records,s=new $({getFn:r,fieldNormWeight:o});return s.setKeys(c),s.setIndexRecords(a),s},ye.config=I,function(){ne.push.apply(ne,arguments)}(te),ye},"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Fuse=t(); \ No newline at end of file diff --git a/site_libs/quarto-search/quarto-search.js b/site_libs/quarto-search/quarto-search.js new file mode 100644 index 0000000..f5d852d --- /dev/null +++ b/site_libs/quarto-search/quarto-search.js @@ -0,0 +1,1140 @@ +const kQueryArg = "q"; +const kResultsArg = "show-results"; + +// If items don't provide a URL, then both the navigator and the onSelect +// function aren't called (and therefore, the default implementation is used) +// +// We're using this sentinel URL to signal to those handlers that this +// item is a more item (along with the type) and can be handled appropriately +const kItemTypeMoreHref = "0767FDFD-0422-4E5A-BC8A-3BE11E5BBA05"; + +window.document.addEventListener("DOMContentLoaded", function (_event) { + // Ensure that search is available on this page. If it isn't, + // should return early and not do anything + var searchEl = window.document.getElementById("quarto-search"); + if (!searchEl) return; + + const { autocomplete } = window["@algolia/autocomplete-js"]; + + let quartoSearchOptions = {}; + let language = {}; + const searchOptionEl = window.document.getElementById( + "quarto-search-options" + ); + if (searchOptionEl) { + const jsonStr = searchOptionEl.textContent; + quartoSearchOptions = JSON.parse(jsonStr); + language = quartoSearchOptions.language; + } + + // note the search mode + if (quartoSearchOptions.type === "overlay") { + searchEl.classList.add("type-overlay"); + } else { + searchEl.classList.add("type-textbox"); + } + + // Used to determine highlighting behavior for this page + // A `q` query param is expected when the user follows a search + // to this page + const currentUrl = new URL(window.location); + const query = currentUrl.searchParams.get(kQueryArg); + const showSearchResults = currentUrl.searchParams.get(kResultsArg); + const mainEl = window.document.querySelector("main"); + + // highlight matches on the page + if (query !== null && mainEl) { + // perform any highlighting + highlight(escapeRegExp(query), mainEl); + + // fix up the URL to remove the q query param + const replacementUrl = new URL(window.location); + replacementUrl.searchParams.delete(kQueryArg); + window.history.replaceState({}, "", replacementUrl); + } + + // function to clear highlighting on the page when the search query changes + // (e.g. if the user edits the query or clears it) + let highlighting = true; + const resetHighlighting = (searchTerm) => { + if (mainEl && highlighting && query !== null && searchTerm !== query) { + clearHighlight(query, mainEl); + highlighting = false; + } + }; + + // Clear search highlighting when the user scrolls sufficiently + const resetFn = () => { + resetHighlighting(""); + window.removeEventListener("quarto-hrChanged", resetFn); + window.removeEventListener("quarto-sectionChanged", resetFn); + }; + + // Register this event after the initial scrolling and settling of events + // on the page + window.addEventListener("quarto-hrChanged", resetFn); + window.addEventListener("quarto-sectionChanged", resetFn); + + // Responsively switch to overlay mode if the search is present on the navbar + // Note that switching the sidebar to overlay mode requires more coordinate (not just + // the media query since we generate different HTML for sidebar overlays than we do + // for sidebar input UI) + const detachedMediaQuery = + quartoSearchOptions.type === "overlay" ? "all" : "(max-width: 991px)"; + + // If configured, include the analytics client to send insights + const plugins = configurePlugins(quartoSearchOptions); + + let lastState = null; + const { setIsOpen, setQuery, setCollections } = autocomplete({ + container: searchEl, + detachedMediaQuery: detachedMediaQuery, + defaultActiveItemId: 0, + panelContainer: "#quarto-search-results", + panelPlacement: quartoSearchOptions["panel-placement"], + debug: false, + openOnFocus: true, + plugins, + classNames: { + form: "d-flex", + }, + translations: { + clearButtonTitle: language["search-clear-button-title"], + detachedCancelButtonText: language["search-detached-cancel-button-title"], + submitButtonTitle: language["search-submit-button-title"], + }, + initialState: { + query, + }, + getItemUrl({ item }) { + return item.href; + }, + onStateChange({ state }) { + // Perhaps reset highlighting + resetHighlighting(state.query); + + // If the panel just opened, ensure the panel is positioned properly + if (state.isOpen) { + if (lastState && !lastState.isOpen) { + setTimeout(() => { + positionPanel(quartoSearchOptions["panel-placement"]); + }, 150); + } + } + + // Perhaps show the copy link + showCopyLink(state.query, quartoSearchOptions); + + lastState = state; + }, + reshape({ sources, state }) { + return sources.map((source) => { + try { + const items = source.getItems(); + + // Validate the items + validateItems(items); + + // group the items by document + const groupedItems = new Map(); + items.forEach((item) => { + const hrefParts = item.href.split("#"); + const baseHref = hrefParts[0]; + const isDocumentItem = hrefParts.length === 1; + + const items = groupedItems.get(baseHref); + if (!items) { + groupedItems.set(baseHref, [item]); + } else { + // If the href for this item matches the document + // exactly, place this item first as it is the item that represents + // the document itself + if (isDocumentItem) { + items.unshift(item); + } else { + items.push(item); + } + groupedItems.set(baseHref, items); + } + }); + + const reshapedItems = []; + let count = 1; + for (const [_key, value] of groupedItems) { + const firstItem = value[0]; + reshapedItems.push({ + ...firstItem, + type: kItemTypeDoc, + }); + + const collapseMatches = quartoSearchOptions["collapse-after"]; + const collapseCount = + typeof collapseMatches === "number" ? collapseMatches : 1; + + if (value.length > 1) { + const target = `search-more-${count}`; + const isExpanded = + state.context.expanded && + state.context.expanded.includes(target); + + const remainingCount = value.length - collapseCount; + + for (let i = 1; i < value.length; i++) { + if (collapseMatches && i === collapseCount) { + reshapedItems.push({ + target, + title: isExpanded + ? language["search-hide-matches-text"] + : remainingCount === 1 + ? `${remainingCount} ${language["search-more-match-text"]}` + : `${remainingCount} ${language["search-more-matches-text"]}`, + type: kItemTypeMore, + href: kItemTypeMoreHref, + }); + } + + if (isExpanded || !collapseMatches || i < collapseCount) { + reshapedItems.push({ + ...value[i], + type: kItemTypeItem, + target, + }); + } + } + } + count += 1; + } + + return { + ...source, + getItems() { + return reshapedItems; + }, + }; + } catch (error) { + // Some form of error occurred + return { + ...source, + getItems() { + return [ + { + title: error.name || "An Error Occurred While Searching", + text: + error.message || + "An unknown error occurred while attempting to perform the requested search.", + type: kItemTypeError, + }, + ]; + }, + }; + } + }); + }, + navigator: { + navigate({ itemUrl }) { + if (itemUrl !== offsetURL(kItemTypeMoreHref)) { + window.location.assign(itemUrl); + } + }, + navigateNewTab({ itemUrl }) { + if (itemUrl !== offsetURL(kItemTypeMoreHref)) { + const windowReference = window.open(itemUrl, "_blank", "noopener"); + if (windowReference) { + windowReference.focus(); + } + } + }, + navigateNewWindow({ itemUrl }) { + if (itemUrl !== offsetURL(kItemTypeMoreHref)) { + window.open(itemUrl, "_blank", "noopener"); + } + }, + }, + getSources({ state, setContext, setActiveItemId, refresh }) { + return [ + { + sourceId: "documents", + getItemUrl({ item }) { + if (item.href) { + return offsetURL(item.href); + } else { + return undefined; + } + }, + onSelect({ + item, + state, + setContext, + setIsOpen, + setActiveItemId, + refresh, + }) { + if (item.type === kItemTypeMore) { + toggleExpanded(item, state, setContext, setActiveItemId, refresh); + + // Toggle more + setIsOpen(true); + } + }, + getItems({ query }) { + if (query === null || query === "") { + return []; + } + + const limit = quartoSearchOptions.limit; + if (quartoSearchOptions.algolia) { + return algoliaSearch(query, limit, quartoSearchOptions.algolia); + } else { + // Fuse search options + const fuseSearchOptions = { + isCaseSensitive: false, + shouldSort: true, + minMatchCharLength: 2, + limit: limit, + }; + + return readSearchData().then(function (fuse) { + return fuseSearch(query, fuse, fuseSearchOptions); + }); + } + }, + templates: { + noResults({ createElement }) { + const hasQuery = lastState.query; + + return createElement( + "div", + { + class: `quarto-search-no-results${ + hasQuery ? "" : " no-query" + }`, + }, + language["search-no-results-text"] + ); + }, + header({ items, createElement }) { + // count the documents + const count = items.filter((item) => { + return item.type === kItemTypeDoc; + }).length; + + if (count > 0) { + return createElement( + "div", + { class: "search-result-header" }, + `${count} ${language["search-matching-documents-text"]}` + ); + } else { + return createElement( + "div", + { class: "search-result-header-no-results" }, + `` + ); + } + }, + footer({ _items, createElement }) { + if ( + quartoSearchOptions.algolia && + quartoSearchOptions.algolia["show-logo"] + ) { + const libDir = quartoSearchOptions.algolia["libDir"]; + const logo = createElement("img", { + src: offsetURL( + `${libDir}/quarto-search/search-by-algolia.svg` + ), + class: "algolia-search-logo", + }); + return createElement( + "a", + { href: "http://www.algolia.com/" }, + logo + ); + } + }, + + item({ item, createElement }) { + return renderItem( + item, + createElement, + state, + setActiveItemId, + setContext, + refresh + ); + }, + }, + }, + ]; + }, + }); + + window.quartoOpenSearch = () => { + setIsOpen(false); + setIsOpen(true); + focusSearchInput(); + }; + + // Remove the labeleledby attribute since it is pointing + // to a non-existent label + if (quartoSearchOptions.type === "overlay") { + const inputEl = window.document.querySelector( + "#quarto-search .aa-Autocomplete" + ); + if (inputEl) { + inputEl.removeAttribute("aria-labelledby"); + } + } + + // If the main document scrolls dismiss the search results + // (otherwise, since they're floating in the document they can scroll with the document) + window.document.body.onscroll = () => { + setIsOpen(false); + }; + + if (showSearchResults) { + setIsOpen(true); + focusSearchInput(); + } +}); + +function configurePlugins(quartoSearchOptions) { + const autocompletePlugins = []; + const algoliaOptions = quartoSearchOptions.algolia; + if ( + algoliaOptions && + algoliaOptions["analytics-events"] && + algoliaOptions["search-only-api-key"] && + algoliaOptions["application-id"] + ) { + const apiKey = algoliaOptions["search-only-api-key"]; + const appId = algoliaOptions["application-id"]; + + // Aloglia insights may not be loaded because they require cookie consent + // Use deferred loading so events will start being recorded when/if consent + // is granted. + const algoliaInsightsDeferredPlugin = deferredLoadPlugin(() => { + if ( + window.aa && + window["@algolia/autocomplete-plugin-algolia-insights"] + ) { + window.aa("init", { + appId, + apiKey, + useCookie: true, + }); + + const { createAlgoliaInsightsPlugin } = + window["@algolia/autocomplete-plugin-algolia-insights"]; + // Register the insights client + const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ + insightsClient: window.aa, + onItemsChange({ insights, insightsEvents }) { + const events = insightsEvents.map((event) => { + const maxEvents = event.objectIDs.slice(0, 20); + return { + ...event, + objectIDs: maxEvents, + }; + }); + + insights.viewedObjectIDs(...events); + }, + }); + return algoliaInsightsPlugin; + } + }); + + // Add the plugin + autocompletePlugins.push(algoliaInsightsDeferredPlugin); + return autocompletePlugins; + } +} + +// For plugins that may not load immediately, create a wrapper +// plugin and forward events and plugin data once the plugin +// is initialized. This is useful for cases like cookie consent +// which may prevent the analytics insights event plugin from initializing +// immediately. +function deferredLoadPlugin(createPlugin) { + let plugin = undefined; + let subscribeObj = undefined; + const wrappedPlugin = () => { + if (!plugin && subscribeObj) { + plugin = createPlugin(); + if (plugin && plugin.subscribe) { + plugin.subscribe(subscribeObj); + } + } + return plugin; + }; + + return { + subscribe: (obj) => { + subscribeObj = obj; + }, + onStateChange: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.onStateChange) { + plugin.onStateChange(obj); + } + }, + onSubmit: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.onSubmit) { + plugin.onSubmit(obj); + } + }, + onReset: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.onReset) { + plugin.onReset(obj); + } + }, + getSources: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.getSources) { + return plugin.getSources(obj); + } else { + return Promise.resolve([]); + } + }, + data: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.data) { + plugin.data(obj); + } + }, + }; +} + +function validateItems(items) { + // Validate the first item + if (items.length > 0) { + const item = items[0]; + const missingFields = []; + if (item.href == undefined) { + missingFields.push("href"); + } + if (!item.title == undefined) { + missingFields.push("title"); + } + if (!item.text == undefined) { + missingFields.push("text"); + } + + if (missingFields.length === 1) { + throw { + name: `Error: Search index is missing the ${missingFields[0]} field.`, + message: `The items being returned for this search do not include all the required fields. Please ensure that your index items include the ${missingFields[0]} field or use index-fields in your _quarto.yml file to specify the field names.`, + }; + } else if (missingFields.length > 1) { + const missingFieldList = missingFields + .map((field) => { + return `${field}`; + }) + .join(", "); + + throw { + name: `Error: Search index is missing the following fields: ${missingFieldList}.`, + message: `The items being returned for this search do not include all the required fields. Please ensure that your index items includes the following fields: ${missingFieldList}, or use index-fields in your _quarto.yml file to specify the field names.`, + }; + } + } +} + +let lastQuery = null; +function showCopyLink(query, options) { + const language = options.language; + lastQuery = query; + // Insert share icon + const inputSuffixEl = window.document.body.querySelector( + ".aa-Form .aa-InputWrapperSuffix" + ); + + if (inputSuffixEl) { + let copyButtonEl = window.document.body.querySelector( + ".aa-Form .aa-InputWrapperSuffix .aa-CopyButton" + ); + + if (copyButtonEl === null) { + copyButtonEl = window.document.createElement("button"); + copyButtonEl.setAttribute("class", "aa-CopyButton"); + copyButtonEl.setAttribute("type", "button"); + copyButtonEl.setAttribute("title", language["search-copy-link-title"]); + copyButtonEl.onmousedown = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; + + const linkIcon = "bi-clipboard"; + const checkIcon = "bi-check2"; + + const shareIconEl = window.document.createElement("i"); + shareIconEl.setAttribute("class", `bi ${linkIcon}`); + copyButtonEl.appendChild(shareIconEl); + inputSuffixEl.prepend(copyButtonEl); + + const clipboard = new window.ClipboardJS(".aa-CopyButton", { + text: function (_trigger) { + const copyUrl = new URL(window.location); + copyUrl.searchParams.set(kQueryArg, lastQuery); + copyUrl.searchParams.set(kResultsArg, "1"); + return copyUrl.toString(); + }, + }); + clipboard.on("success", function (e) { + // Focus the input + + // button target + const button = e.trigger; + const icon = button.querySelector("i.bi"); + + // flash "checked" + icon.classList.add(checkIcon); + icon.classList.remove(linkIcon); + setTimeout(function () { + icon.classList.remove(checkIcon); + icon.classList.add(linkIcon); + }, 1000); + }); + } + + // If there is a query, show the link icon + if (copyButtonEl) { + if (lastQuery && options["copy-button"]) { + copyButtonEl.style.display = "flex"; + } else { + copyButtonEl.style.display = "none"; + } + } + } +} + +/* Search Index Handling */ +// create the index +var fuseIndex = undefined; +async function readSearchData() { + // Initialize the search index on demand + if (fuseIndex === undefined) { + // create fuse index + const options = { + keys: [ + { name: "title", weight: 20 }, + { name: "section", weight: 20 }, + { name: "text", weight: 10 }, + ], + ignoreLocation: true, + threshold: 0.1, + }; + const fuse = new window.Fuse([], options); + + // fetch the main search.json + const response = await fetch(offsetURL("search.json")); + if (response.status == 200) { + return response.json().then(function (searchDocs) { + searchDocs.forEach(function (searchDoc) { + fuse.add(searchDoc); + }); + fuseIndex = fuse; + return fuseIndex; + }); + } else { + return Promise.reject( + new Error( + "Unexpected status from search index request: " + response.status + ) + ); + } + } + return fuseIndex; +} + +function inputElement() { + return window.document.body.querySelector(".aa-Form .aa-Input"); +} + +function focusSearchInput() { + setTimeout(() => { + const inputEl = inputElement(); + if (inputEl) { + inputEl.focus(); + } + }, 50); +} + +/* Panels */ +const kItemTypeDoc = "document"; +const kItemTypeMore = "document-more"; +const kItemTypeItem = "document-item"; +const kItemTypeError = "error"; + +function renderItem( + item, + createElement, + state, + setActiveItemId, + setContext, + refresh +) { + switch (item.type) { + case kItemTypeDoc: + return createDocumentCard( + createElement, + "file-richtext", + item.title, + item.section, + item.text, + item.href + ); + case kItemTypeMore: + return createMoreCard( + createElement, + item, + state, + setActiveItemId, + setContext, + refresh + ); + case kItemTypeItem: + return createSectionCard( + createElement, + item.section, + item.text, + item.href + ); + case kItemTypeError: + return createErrorCard(createElement, item.title, item.text); + default: + return undefined; + } +} + +function createDocumentCard(createElement, icon, title, section, text, href) { + const iconEl = createElement("i", { + class: `bi bi-${icon} search-result-icon`, + }); + const titleEl = createElement("p", { class: "search-result-title" }, title); + const titleContainerEl = createElement( + "div", + { class: "search-result-title-container" }, + [iconEl, titleEl] + ); + + const textEls = []; + if (section) { + const sectionEl = createElement( + "p", + { class: "search-result-section" }, + section + ); + textEls.push(sectionEl); + } + const descEl = createElement("p", { + class: "search-result-text", + dangerouslySetInnerHTML: { + __html: text, + }, + }); + textEls.push(descEl); + + const textContainerEl = createElement( + "div", + { class: "search-result-text-container" }, + textEls + ); + + const containerEl = createElement( + "div", + { + class: "search-result-container", + }, + [titleContainerEl, textContainerEl] + ); + + const linkEl = createElement( + "a", + { + href: offsetURL(href), + class: "search-result-link", + }, + containerEl + ); + + const classes = ["search-result-doc", "search-item"]; + if (!section) { + classes.push("document-selectable"); + } + + return createElement( + "div", + { + class: classes.join(" "), + }, + linkEl + ); +} + +function createMoreCard( + createElement, + item, + state, + setActiveItemId, + setContext, + refresh +) { + const moreCardEl = createElement( + "div", + { + class: "search-result-more search-item", + onClick: (e) => { + // Handle expanding the sections by adding the expanded + // section to the list of expanded sections + toggleExpanded(item, state, setContext, setActiveItemId, refresh); + e.stopPropagation(); + }, + }, + item.title + ); + + return moreCardEl; +} + +function toggleExpanded(item, state, setContext, setActiveItemId, refresh) { + const expanded = state.context.expanded || []; + if (expanded.includes(item.target)) { + setContext({ + expanded: expanded.filter((target) => target !== item.target), + }); + } else { + setContext({ expanded: [...expanded, item.target] }); + } + + refresh(); + setActiveItemId(item.__autocomplete_id); +} + +function createSectionCard(createElement, section, text, href) { + const sectionEl = createSection(createElement, section, text, href); + return createElement( + "div", + { + class: "search-result-doc-section search-item", + }, + sectionEl + ); +} + +function createSection(createElement, title, text, href) { + const descEl = createElement("p", { + class: "search-result-text", + dangerouslySetInnerHTML: { + __html: text, + }, + }); + + const titleEl = createElement("p", { class: "search-result-section" }, title); + const linkEl = createElement( + "a", + { + href: offsetURL(href), + class: "search-result-link", + }, + [titleEl, descEl] + ); + return linkEl; +} + +function createErrorCard(createElement, title, text) { + const descEl = createElement("p", { + class: "search-error-text", + dangerouslySetInnerHTML: { + __html: text, + }, + }); + + const titleEl = createElement("p", { + class: "search-error-title", + dangerouslySetInnerHTML: { + __html: ` ${title}`, + }, + }); + const errorEl = createElement("div", { class: "search-error" }, [ + titleEl, + descEl, + ]); + return errorEl; +} + +function positionPanel(pos) { + const panelEl = window.document.querySelector( + "#quarto-search-results .aa-Panel" + ); + const inputEl = window.document.querySelector( + "#quarto-search .aa-Autocomplete" + ); + + if (panelEl && inputEl) { + panelEl.style.top = `${Math.round(panelEl.offsetTop)}px`; + if (pos === "start") { + panelEl.style.left = `${Math.round(inputEl.left)}px`; + } else { + panelEl.style.right = `${Math.round(inputEl.offsetRight)}px`; + } + } +} + +/* Highlighting */ +// highlighting functions +function highlightMatch(query, text) { + if (text) { + const start = text.toLowerCase().indexOf(query.toLowerCase()); + if (start !== -1) { + const startMark = ""; + const endMark = ""; + + const end = start + query.length; + text = + text.slice(0, start) + + startMark + + text.slice(start, end) + + endMark + + text.slice(end); + const startInfo = clipStart(text, start); + const endInfo = clipEnd( + text, + startInfo.position + startMark.length + endMark.length + ); + text = + startInfo.prefix + + text.slice(startInfo.position, endInfo.position) + + endInfo.suffix; + + return text; + } else { + return text; + } + } else { + return text; + } +} + +function clipStart(text, pos) { + const clipStart = pos - 50; + if (clipStart < 0) { + // This will just return the start of the string + return { + position: 0, + prefix: "", + }; + } else { + // We're clipping before the start of the string, walk backwards to the first space. + const spacePos = findSpace(text, pos, -1); + return { + position: spacePos.position, + prefix: "", + }; + } +} + +function clipEnd(text, pos) { + const clipEnd = pos + 200; + if (clipEnd > text.length) { + return { + position: text.length, + suffix: "", + }; + } else { + const spacePos = findSpace(text, clipEnd, 1); + return { + position: spacePos.position, + suffix: spacePos.clipped ? "…" : "", + }; + } +} + +function findSpace(text, start, step) { + let stepPos = start; + while (stepPos > -1 && stepPos < text.length) { + const char = text[stepPos]; + if (char === " " || char === "," || char === ":") { + return { + position: step === 1 ? stepPos : stepPos - step, + clipped: stepPos > 1 && stepPos < text.length, + }; + } + stepPos = stepPos + step; + } + + return { + position: stepPos - step, + clipped: false, + }; +} + +// removes highlighting as implemented by the mark tag +function clearHighlight(searchterm, el) { + const childNodes = el.childNodes; + for (let i = childNodes.length - 1; i >= 0; i--) { + const node = childNodes[i]; + if (node.nodeType === Node.ELEMENT_NODE) { + if ( + node.tagName === "MARK" && + node.innerText.toLowerCase() === searchterm.toLowerCase() + ) { + el.replaceChild(document.createTextNode(node.innerText), node); + } else { + clearHighlight(searchterm, node); + } + } + } +} + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string +} + +// highlight matches +function highlight(term, el) { + const termRegex = new RegExp(term, "ig"); + const childNodes = el.childNodes; + + // walk back to front avoid mutating elements in front of us + for (let i = childNodes.length - 1; i >= 0; i--) { + const node = childNodes[i]; + + if (node.nodeType === Node.TEXT_NODE) { + // Search text nodes for text to highlight + const text = node.nodeValue; + + let startIndex = 0; + let matchIndex = text.search(termRegex); + if (matchIndex > -1) { + const markFragment = document.createDocumentFragment(); + while (matchIndex > -1) { + const prefix = text.slice(startIndex, matchIndex); + markFragment.appendChild(document.createTextNode(prefix)); + + const mark = document.createElement("mark"); + mark.appendChild( + document.createTextNode( + text.slice(matchIndex, matchIndex + term.length) + ) + ); + markFragment.appendChild(mark); + + startIndex = matchIndex + term.length; + matchIndex = text.slice(startIndex).search(new RegExp(term, "ig")); + if (matchIndex > -1) { + matchIndex = startIndex + matchIndex; + } + } + if (startIndex < text.length) { + markFragment.appendChild( + document.createTextNode(text.slice(startIndex, text.length)) + ); + } + + el.replaceChild(markFragment, node); + } + } else if (node.nodeType === Node.ELEMENT_NODE) { + // recurse through elements + highlight(term, node); + } + } +} + +/* Link Handling */ +// get the offset from this page for a given site root relative url +function offsetURL(url) { + var offset = getMeta("quarto:offset"); + return offset ? offset + url : url; +} + +// read a meta tag value +function getMeta(metaName) { + var metas = window.document.getElementsByTagName("meta"); + for (let i = 0; i < metas.length; i++) { + if (metas[i].getAttribute("name") === metaName) { + return metas[i].getAttribute("content"); + } + } + return ""; +} + +function algoliaSearch(query, limit, algoliaOptions) { + const { getAlgoliaResults } = window["@algolia/autocomplete-preset-algolia"]; + + const applicationId = algoliaOptions["application-id"]; + const searchOnlyApiKey = algoliaOptions["search-only-api-key"]; + const indexName = algoliaOptions["index-name"]; + const indexFields = algoliaOptions["index-fields"]; + const searchClient = window.algoliasearch(applicationId, searchOnlyApiKey); + const searchParams = algoliaOptions["params"]; + const searchAnalytics = !!algoliaOptions["analytics-events"]; + + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: indexName, + query, + params: { + hitsPerPage: limit, + clickAnalytics: searchAnalytics, + ...searchParams, + }, + }, + ], + transformResponse: (response) => { + if (!indexFields) { + return response.hits.map((hit) => { + return hit.map((item) => { + return { + ...item, + text: highlightMatch(query, item.text), + }; + }); + }); + } else { + const remappedHits = response.hits.map((hit) => { + return hit.map((item) => { + const newItem = { ...item }; + ["href", "section", "title", "text"].forEach((keyName) => { + const mappedName = indexFields[keyName]; + if ( + mappedName && + item[mappedName] !== undefined && + mappedName !== keyName + ) { + newItem[keyName] = item[mappedName]; + delete newItem[mappedName]; + } + }); + newItem.text = highlightMatch(query, newItem.text); + return newItem; + }); + }); + return remappedHits; + } + }, + }); +} + +function fuseSearch(query, fuse, fuseOptions) { + return fuse.search(query, fuseOptions).map((result) => { + const addParam = (url, name, value) => { + const anchorParts = url.split("#"); + const baseUrl = anchorParts[0]; + const sep = baseUrl.search("\\?") > 0 ? "&" : "?"; + anchorParts[0] = baseUrl + sep + name + "=" + value; + return anchorParts.join("#"); + }; + + return { + title: result.item.title, + section: result.item.section, + href: addParam(result.item.href, kQueryArg, query), + text: highlightMatch(query, result.item.text), + }; + }); +} diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..a296d7c --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,175 @@ + + + + https://kornia.github.io/tutorials/nbs/extract_combine_patches.html + 2024-01-06T22:20:30.413Z + + + https://kornia.github.io/tutorials/nbs/data_augmentation_mosiac.html + 2024-01-06T22:20:29.581Z + + + https://kornia.github.io/tutorials/nbs/hello_world_tutorial.html + 2024-01-06T22:20:28.873Z + + + https://kornia.github.io/tutorials/nbs/filtering_operators.html + 2024-01-06T22:20:28.121Z + + + https://kornia.github.io/tutorials/nbs/data_patch_sequential.html + 2024-01-06T22:20:27.345Z + + + https://kornia.github.io/tutorials/nbs/color_conversions.html + 2024-01-06T22:20:26.605Z + + + https://kornia.github.io/tutorials/nbs/warp_perspective.html + 2024-01-06T22:20:25.849Z + + + https://kornia.github.io/tutorials/nbs/rotate_affine.html + 2024-01-06T22:20:25.149Z + + + https://kornia.github.io/tutorials/nbs/resize_antialias.html + 2024-01-06T22:20:24.397Z + + + https://kornia.github.io/tutorials/nbs/gaussian_blur.html + 2024-01-06T22:20:23.601Z + + + https://kornia.github.io/tutorials/nbs/unsharp_mask.html + 2024-01-06T22:20:22.845Z + + + https://kornia.github.io/tutorials/nbs/image_registration.html + 2024-01-06T22:20:21.893Z + + + https://kornia.github.io/tutorials/nbs/image_matching_adalam.html + 2024-01-06T22:20:20.897Z + + + https://kornia.github.io/tutorials/nbs/image_matching_disk.html + 2024-01-06T22:20:20.149Z + + + https://kornia.github.io/tutorials/nbs/fit_line.html + 2024-01-06T22:20:19.401Z + + + https://kornia.github.io/tutorials/nbs/aliased_and_not_aliased_patch_extraction.html + 2024-01-06T22:20:18.649Z + + + https://kornia.github.io/tutorials/nbs/geometry_generate_patch.html + 2024-01-06T22:20:17.925Z + + + https://kornia.github.io/tutorials/nbs/data_augmentation_segmentation.html + 2024-01-06T22:20:17.205Z + + + https://kornia.github.io/tutorials/nbs/data_augmentation_2d.html + 2024-01-06T22:20:16.393Z + + + https://kornia.github.io/tutorials/nbs/geometric_transforms.html + 2024-01-06T22:20:15.373Z + + + https://kornia.github.io/tutorials/nbs/data_augmentation_sequential.html + 2024-01-06T22:20:13.989Z + + + https://kornia.github.io/tutorials/index.html + 2024-01-06T22:20:12.429Z + + + https://kornia.github.io/tutorials/nbs/image_histogram.html + 2024-01-06T22:20:13.621Z + + + https://kornia.github.io/tutorials/nbs/image_prompter.html + 2024-01-06T22:20:14.753Z + + + https://kornia.github.io/tutorials/nbs/color_raw_to_rgb.html + 2024-01-06T22:20:15.725Z + + + https://kornia.github.io/tutorials/nbs/line_detection_and_matching_sold2.html + 2024-01-06T22:20:16.853Z + + + https://kornia.github.io/tutorials/nbs/color_yuv420_to_rgb.html + 2024-01-06T22:20:17.561Z + + + https://kornia.github.io/tutorials/nbs/image_points_transforms.html + 2024-01-06T22:20:18.277Z + + + https://kornia.github.io/tutorials/nbs/image_matching.html + 2024-01-06T22:20:19.069Z + + + https://kornia.github.io/tutorials/nbs/canny.html + 2024-01-06T22:20:19.769Z + + + https://kornia.github.io/tutorials/nbs/image_enhancement.html + 2024-01-06T22:20:20.517Z + + + https://kornia.github.io/tutorials/nbs/fit_plane.html + 2024-01-06T22:20:21.325Z + + + https://kornia.github.io/tutorials/nbs/descriptors_matching.html + 2024-01-06T22:20:22.477Z + + + https://kornia.github.io/tutorials/nbs/morphology_101.html + 2024-01-06T22:20:23.265Z + + + https://kornia.github.io/tutorials/nbs/homography.html + 2024-01-06T22:20:24.065Z + + + https://kornia.github.io/tutorials/nbs/image_matching_lightglue.html + 2024-01-06T22:20:24.809Z + + + https://kornia.github.io/tutorials/nbs/connected_components.html + 2024-01-06T22:20:25.501Z + + + https://kornia.github.io/tutorials/nbs/face_detection.html + 2024-01-06T22:20:26.221Z + + + https://kornia.github.io/tutorials/nbs/filtering_edges.html + 2024-01-06T22:20:26.969Z + + + https://kornia.github.io/tutorials/nbs/image_stitching.html + 2024-01-06T22:20:27.737Z + + + https://kornia.github.io/tutorials/nbs/total_variation_denoising.html + 2024-01-06T22:20:28.473Z + + + https://kornia.github.io/tutorials/nbs/data_augmentation_kornia_lightning.html + 2024-01-06T22:20:29.229Z + + + https://kornia.github.io/tutorials/nbs/zca_whitening.html + 2024-01-06T22:20:30.021Z + + diff --git a/tutorials/_static/img/kornia_logo_favicon.png b/tutorials/_static/img/kornia_logo_favicon.png new file mode 100644 index 0000000..0c52629 Binary files /dev/null and b/tutorials/_static/img/kornia_logo_favicon.png differ diff --git a/tutorials/_static/img/kornia_logo_mini.png b/tutorials/_static/img/kornia_logo_mini.png new file mode 100644 index 0000000..eadfc98 Binary files /dev/null and b/tutorials/_static/img/kornia_logo_mini.png differ diff --git a/tutorials/assets/aliased_and_not_aliased_patch_extraction.png b/tutorials/assets/aliased_and_not_aliased_patch_extraction.png new file mode 100644 index 0000000..34cd0d7 Binary files /dev/null and b/tutorials/assets/aliased_and_not_aliased_patch_extraction.png differ diff --git a/tutorials/assets/canny.png b/tutorials/assets/canny.png new file mode 100644 index 0000000..0c3c33a Binary files /dev/null and b/tutorials/assets/canny.png differ diff --git a/tutorials/assets/color_conversions.png b/tutorials/assets/color_conversions.png new file mode 100644 index 0000000..2f58754 Binary files /dev/null and b/tutorials/assets/color_conversions.png differ diff --git a/tutorials/assets/color_raw_to_rgb.png b/tutorials/assets/color_raw_to_rgb.png new file mode 100644 index 0000000..1537045 Binary files /dev/null and b/tutorials/assets/color_raw_to_rgb.png differ diff --git a/tutorials/assets/color_yuv420_to_rgb.png b/tutorials/assets/color_yuv420_to_rgb.png new file mode 100644 index 0000000..d497758 Binary files /dev/null and b/tutorials/assets/color_yuv420_to_rgb.png differ diff --git a/tutorials/assets/connected_components.png b/tutorials/assets/connected_components.png new file mode 100644 index 0000000..25dad7b Binary files /dev/null and b/tutorials/assets/connected_components.png differ diff --git a/tutorials/assets/data_augmentation_2d.png b/tutorials/assets/data_augmentation_2d.png new file mode 100644 index 0000000..089591a Binary files /dev/null and b/tutorials/assets/data_augmentation_2d.png differ diff --git a/tutorials/assets/data_augmentation_mosiac.png b/tutorials/assets/data_augmentation_mosiac.png new file mode 100644 index 0000000..ba37d4b Binary files /dev/null and b/tutorials/assets/data_augmentation_mosiac.png differ diff --git a/tutorials/assets/data_augmentation_segmentation.png b/tutorials/assets/data_augmentation_segmentation.png new file mode 100644 index 0000000..e54ecf7 Binary files /dev/null and b/tutorials/assets/data_augmentation_segmentation.png differ diff --git a/tutorials/assets/data_augmentation_sequential.png b/tutorials/assets/data_augmentation_sequential.png new file mode 100644 index 0000000..4cb91ed Binary files /dev/null and b/tutorials/assets/data_augmentation_sequential.png differ diff --git a/tutorials/assets/data_patch_sequential.png b/tutorials/assets/data_patch_sequential.png new file mode 100644 index 0000000..02a7366 Binary files /dev/null and b/tutorials/assets/data_patch_sequential.png differ diff --git a/tutorials/assets/descriptor_matching.png b/tutorials/assets/descriptor_matching.png new file mode 100644 index 0000000..e4797cd Binary files /dev/null and b/tutorials/assets/descriptor_matching.png differ diff --git a/tutorials/assets/extract_combine_patches.png b/tutorials/assets/extract_combine_patches.png new file mode 100644 index 0000000..2d36d00 Binary files /dev/null and b/tutorials/assets/extract_combine_patches.png differ diff --git a/tutorials/assets/face_detection.png b/tutorials/assets/face_detection.png new file mode 100644 index 0000000..d6720ac Binary files /dev/null and b/tutorials/assets/face_detection.png differ diff --git a/tutorials/assets/filtering_edges.png b/tutorials/assets/filtering_edges.png new file mode 100644 index 0000000..da29b0c Binary files /dev/null and b/tutorials/assets/filtering_edges.png differ diff --git a/tutorials/assets/filtering_operators.png b/tutorials/assets/filtering_operators.png new file mode 100644 index 0000000..c2e3e12 Binary files /dev/null and b/tutorials/assets/filtering_operators.png differ diff --git a/tutorials/assets/fit_line.png b/tutorials/assets/fit_line.png new file mode 100644 index 0000000..c54f892 Binary files /dev/null and b/tutorials/assets/fit_line.png differ diff --git a/tutorials/assets/fit_plane.png b/tutorials/assets/fit_plane.png new file mode 100644 index 0000000..cd45b85 Binary files /dev/null and b/tutorials/assets/fit_plane.png differ diff --git a/tutorials/assets/gaussian_blur.png b/tutorials/assets/gaussian_blur.png new file mode 100644 index 0000000..4d00d89 Binary files /dev/null and b/tutorials/assets/gaussian_blur.png differ diff --git a/tutorials/assets/geometric_transforms.png b/tutorials/assets/geometric_transforms.png new file mode 100644 index 0000000..705e69c Binary files /dev/null and b/tutorials/assets/geometric_transforms.png differ diff --git a/tutorials/assets/geometry_generate_patch.png b/tutorials/assets/geometry_generate_patch.png new file mode 100644 index 0000000..10a1fbc Binary files /dev/null and b/tutorials/assets/geometry_generate_patch.png differ diff --git a/tutorials/assets/hello_world_tutorial.png b/tutorials/assets/hello_world_tutorial.png new file mode 100644 index 0000000..100ceae Binary files /dev/null and b/tutorials/assets/hello_world_tutorial.png differ diff --git a/tutorials/assets/homography.png b/tutorials/assets/homography.png new file mode 100644 index 0000000..ab19766 Binary files /dev/null and b/tutorials/assets/homography.png differ diff --git a/tutorials/assets/image_enhancement.png b/tutorials/assets/image_enhancement.png new file mode 100644 index 0000000..f64bdf2 Binary files /dev/null and b/tutorials/assets/image_enhancement.png differ diff --git a/tutorials/assets/image_histogram.png b/tutorials/assets/image_histogram.png new file mode 100644 index 0000000..15dca7a Binary files /dev/null and b/tutorials/assets/image_histogram.png differ diff --git a/tutorials/assets/image_matching.png b/tutorials/assets/image_matching.png new file mode 100644 index 0000000..b3e45d2 Binary files /dev/null and b/tutorials/assets/image_matching.png differ diff --git a/tutorials/assets/image_matching_LightGlue.png b/tutorials/assets/image_matching_LightGlue.png new file mode 100644 index 0000000..acaec63 Binary files /dev/null and b/tutorials/assets/image_matching_LightGlue.png differ diff --git a/tutorials/assets/image_matching_adalam.png b/tutorials/assets/image_matching_adalam.png new file mode 100644 index 0000000..b4d55f8 Binary files /dev/null and b/tutorials/assets/image_matching_adalam.png differ diff --git a/tutorials/assets/image_matching_disk.png b/tutorials/assets/image_matching_disk.png new file mode 100644 index 0000000..07751f8 Binary files /dev/null and b/tutorials/assets/image_matching_disk.png differ diff --git a/tutorials/assets/image_points_transforms.png b/tutorials/assets/image_points_transforms.png new file mode 100644 index 0000000..62aa5b4 Binary files /dev/null and b/tutorials/assets/image_points_transforms.png differ diff --git a/tutorials/assets/image_prompter.png b/tutorials/assets/image_prompter.png new file mode 100644 index 0000000..a02c1fa Binary files /dev/null and b/tutorials/assets/image_prompter.png differ diff --git a/tutorials/assets/image_registration.png b/tutorials/assets/image_registration.png new file mode 100644 index 0000000..7cca48d Binary files /dev/null and b/tutorials/assets/image_registration.png differ diff --git a/tutorials/assets/image_stitching.png b/tutorials/assets/image_stitching.png new file mode 100644 index 0000000..ae8ec87 Binary files /dev/null and b/tutorials/assets/image_stitching.png differ diff --git a/tutorials/assets/kornia.png b/tutorials/assets/kornia.png new file mode 100644 index 0000000..8f9f9aa Binary files /dev/null and b/tutorials/assets/kornia.png differ diff --git a/tutorials/assets/line_detection_and_matching_sold2.png b/tutorials/assets/line_detection_and_matching_sold2.png new file mode 100644 index 0000000..29c2c72 Binary files /dev/null and b/tutorials/assets/line_detection_and_matching_sold2.png differ diff --git a/tutorials/assets/morphology_101.png b/tutorials/assets/morphology_101.png new file mode 100644 index 0000000..5e68b47 Binary files /dev/null and b/tutorials/assets/morphology_101.png differ diff --git a/tutorials/assets/resize_antialias.png b/tutorials/assets/resize_antialias.png new file mode 100644 index 0000000..446571d Binary files /dev/null and b/tutorials/assets/resize_antialias.png differ diff --git a/tutorials/assets/rotate_affine.png b/tutorials/assets/rotate_affine.png new file mode 100644 index 0000000..90ae298 Binary files /dev/null and b/tutorials/assets/rotate_affine.png differ diff --git a/tutorials/assets/total_variation_denoising.png b/tutorials/assets/total_variation_denoising.png new file mode 100644 index 0000000..eb4f9ac Binary files /dev/null and b/tutorials/assets/total_variation_denoising.png differ diff --git a/tutorials/assets/unsharp_mask.png b/tutorials/assets/unsharp_mask.png new file mode 100644 index 0000000..a14cb8c Binary files /dev/null and b/tutorials/assets/unsharp_mask.png differ diff --git a/tutorials/assets/warp_perspective.png b/tutorials/assets/warp_perspective.png new file mode 100644 index 0000000..323a892 Binary files /dev/null and b/tutorials/assets/warp_perspective.png differ diff --git a/tutorials/assets/zca_whitening.png b/tutorials/assets/zca_whitening.png new file mode 100644 index 0000000..f14a08e Binary files /dev/null and b/tutorials/assets/zca_whitening.png differ