Skip to content

Commit

Permalink
Merge branch 'wildfusion' of https://github.com/WildlifeDatasets/wild…
Browse files Browse the repository at this point in the history
…life-tools into wildfusion
  • Loading branch information
VojtechCermak committed Nov 12, 2024
2 parents 7d21388 + af0dee2 commit e9b1db3
Show file tree
Hide file tree
Showing 23 changed files with 113 additions and 134 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
python -m pip install --upgrade pip
pip install black[jupyter]==22.3.0
- name: Analysing the code with black
run: black --check .
run: black --check --diff --line-length 120 wildlife_tools
isort:
runs-on: ubuntu-latest
steps:
Expand All @@ -40,7 +40,8 @@ jobs:
python -m pip install --upgrade pip
pip install isort==5.10.1
- name: Analysing the code with isort
run: isort --check .
run: isort --check --diff --line-length 120 wildlife_tools

# flake8:
# runs-on: ubuntu-latest
# steps:
Expand All @@ -55,4 +56,4 @@ jobs:
# python -m pip install --upgrade pip
# pip install flake8==4.0.1 flake8-docstrings==1.6.0
# - name: Analysing the code with flake8
# run: flake8 .
# run: flake8 wildlife_tools
9 changes: 8 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,11 @@ packages = [
]

[tool.flake8]
max-line-length = 120
max-line-length = 120

[tool.black]
line-length = 120

[tool.isort]
profile = "black"
line_length = 120
2 changes: 1 addition & 1 deletion wildlife_tools/data/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .dataset import FeatureDatabase, FeatureDataset, WildlifeDataset, ImageDataset
from .dataset import FeatureDatabase, FeatureDataset, ImageDataset, WildlifeDataset
26 changes: 10 additions & 16 deletions wildlife_tools/data/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import os
import pickle
from typing import Callable

import cv2
import numpy as np
import pandas as pd
import pycocotools.mask as mask_coco
from PIL import Image
import torch


class ImageDataset:
Expand Down Expand Up @@ -44,9 +44,7 @@ def __init__(
self.col_path = col_path
self.col_label = col_label
self.load_label = load_label
self.labels, self.labels_map = pd.factorize(
self.metadata[self.col_label].values
)
self.labels, self.labels_map = pd.factorize(self.metadata[self.col_label].values)

@property
def labels_string(self):
Expand Down Expand Up @@ -82,7 +80,6 @@ def __getitem__(self, idx):
return img



class WildlifeDataset(ImageDataset):
"""
PyTorch-style dataset for a datasets from wildlife-datasets library.
Expand Down Expand Up @@ -122,9 +119,7 @@ def __init__(
self.col_path = col_path
self.col_label = col_label
self.load_label = load_label
self.labels, self.labels_map = pd.factorize(
self.metadata[self.col_label].values
)
self.labels, self.labels_map = pd.factorize(self.metadata[self.col_label].values)

def __getitem__(self, idx):
data = self.metadata.iloc[idx]
Expand All @@ -146,9 +141,11 @@ def __getitem__(self, idx):
w, h = img.size
rles = mask_coco.frPyObjects([segmentation], h, w)
segmentation = mask_coco.merge(rles)
if isinstance(segmentation, dict) and (isinstance(segmentation['counts'], list) or isinstance(segmentation['counts'], np.ndarray)):
if isinstance(segmentation, dict) and (
isinstance(segmentation["counts"], list) or isinstance(segmentation["counts"], np.ndarray)
):
# Convert uncompressed RLE to compressed RLE
h, w = segmentation['size']
h, w = segmentation["size"]
segmentation = mask_coco.frPyObjects(segmentation, h, w)

if self.img_load in ["bbox"]:
Expand Down Expand Up @@ -182,7 +179,7 @@ def __getitem__(self, idx):

# Mask background using segmentation mask and crop to bounding box.
elif self.img_load == "bbox_mask":
if (not np.any(pd.isnull(segmentation))):
if not np.any(pd.isnull(segmentation)):
mask = mask_coco.decode(segmentation).astype("bool")
img = Image.fromarray(img * mask[..., np.newaxis])
y_nonzero, x_nonzero, _ = np.nonzero(img)
Expand All @@ -197,7 +194,7 @@ def __getitem__(self, idx):

# Hide object using segmentation mask and crop to bounding box.
elif self.img_load == "bbox_hide":
if (not np.any(pd.isnull(segmentation))):
if not np.any(pd.isnull(segmentation)):
mask = mask_coco.decode(segmentation).astype("bool")
img = Image.fromarray(img * ~mask[..., np.newaxis])
y_nonzero, x_nonzero, _ = np.nonzero(img)
Expand Down Expand Up @@ -234,7 +231,6 @@ def __getitem__(self, idx):
return img



class FeatureDataset:
"""
PyTorch-style dataset for a extracted features. Couples features with metadata.
Expand Down Expand Up @@ -267,9 +263,7 @@ def __init__(
self.features = features
self.metadata = metadata.reset_index(drop=True)
self.col_label = col_label
self.labels, self.labels_map = pd.factorize(
self.metadata[self.col_label].values
)
self.labels, self.labels_map = pd.factorize(self.metadata[self.col_label].values)

@property
def labels_string(self):
Expand Down
3 changes: 2 additions & 1 deletion wildlife_tools/data/split.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import numpy as np

class SplitChunk():

class SplitChunk:
"""Returns split as given nth chunk of metadata from equally sized metadata chunks."""

def __init__(self, chunk=1, chunk_total=1):
Expand Down
2 changes: 1 addition & 1 deletion wildlife_tools/features/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .deep import ClipFeatures, DeepFeatures
from .local import AlikedExtractor, DiskExtractor, SiftExtractor, SuperPointExtractor
from .memory import DataToMemory
from .local import SiftExtractor, SuperPointExtractor, AlikedExtractor, DiskExtractor
11 changes: 6 additions & 5 deletions wildlife_tools/features/deep.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import torch
from tqdm import tqdm
from transformers import CLIPModel, CLIPProcessor
from wildlife_tools.data import ImageDataset, FeatureDataset

from wildlife_tools.data import FeatureDataset, ImageDataset


class DeepFeatures:
Expand All @@ -16,14 +17,14 @@ def __init__(
num_workers: int = 1,
device: str = "cpu",
):
'''
"""
Args:
model: Pytorch model used for the feature extraction.
batch_size: Batch size used for the feature extraction.
num_workers: Number of workers used for data loading.
device: Select between cuda and cpu devices.
'''
"""

self.batch_size = batch_size
self.num_workers = num_workers
Expand Down Expand Up @@ -81,15 +82,15 @@ def __init__(
num_workers=1,
device="cpu",
):
'''
"""
Args:
model: transformer.CLIPModel. Uses VIT-L backbone by default.
processor: transformer.CLIPProcessor. Uses VIT-L processor by default.
batch_size: Batch size used for the feature extraction.
num_workers: Number of workers used for data loading.
device: Select between cuda and cpu devices.
'''
"""
if model is None:
model = CLIPModel.from_pretrained("openai/clip-vit-large-patch14").vision_model

Expand Down
10 changes: 3 additions & 7 deletions wildlife_tools/features/gluefactory_fix.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
except ImportError:
pycolmap = None

from gluefactory.models.utils.misc import pad_to_length
from gluefactory.models.extractors.sift import filter_dog_point, run_opencv_sift
from gluefactory.models.utils.misc import pad_to_length


def extract_single_image_fix(self, image: torch.Tensor):
Expand All @@ -33,9 +33,7 @@ def extract_single_image_fix(self, image: torch.Tensor):
scores = np.abs(scores) * scales
elif self.conf.backend == "opencv":
# TODO: Check if opencv keypoints are already in corner convention
keypoints, scores, scales, angles, descriptors = run_opencv_sift(
self.sift, (image_np * 255.0).astype(np.uint8)
)
keypoints, scores, scales, angles, descriptors = run_opencv_sift(self.sift, (image_np * 255.0).astype(np.uint8))

if (descriptors is None) or (descriptors.size == 0):
descriptors = np.empty(shape=(0, 128), dtype=np.float32)
Expand Down Expand Up @@ -89,7 +87,5 @@ def extract_single_image_fix(self, image: torch.Tensor):
pred["oris"] = pad_to_length(pred["oris"], num_points, -1, mode="zeros")
pred["descriptors"] = pad_to_length(pred["descriptors"], num_points, -2, mode="zeros")
if pred["keypoint_scores"] is not None:
pred["keypoint_scores"] = pad_to_length(
pred["keypoint_scores"], num_points, -1, mode="zeros"
)
pred["keypoint_scores"] = pad_to_length(pred["keypoint_scores"], num_points, -1, mode="zeros")
return pred
12 changes: 6 additions & 6 deletions wildlife_tools/features/local.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import types

import torch
from tqdm import tqdm
from gluefactory.models import get_model
from omegaconf import OmegaConf
from tqdm import tqdm

from wildlife_tools.data import FeatureDataset, ImageDataset

# Fix https://github.com/cvg/glue-factory/pull/50
import types
from .gluefactory_fix import extract_single_image_fix
from .gluefactory_fix import extract_single_image_fix # https://github.com/cvg/glue-factory/pull/50


class GlueFactoryExtractor:
Expand Down Expand Up @@ -42,14 +43,13 @@ def __init__(
self.device = device
self.num_workers = num_workers


def __call__(self, dataset: ImageDataset) -> FeatureDataset:
"""
Extract clip features from input dataset and return them as a new FeatureDataset.
Gluefactory extractors requires with 3 channel RBG tensors scaled to [0, 1].
Args:
dataset: Extract features from this dataset.
dataset: Extract features from this dataset.
Returns:
feature_dataset: A FeatureDataset containing the extracted features
"""
Expand Down
2 changes: 1 addition & 1 deletion wildlife_tools/features/memory.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from tqdm import tqdm

from wildlife_tools.data import ImageDataset, FeatureDataset
from wildlife_tools.data import FeatureDataset, ImageDataset


class DataToMemory:
Expand Down
10 changes: 4 additions & 6 deletions wildlife_tools/inference/classifier.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from __future__ import annotations

import numpy as np
import pandas as pd
import torch
Expand All @@ -21,7 +22,6 @@ def __init__(self, database_labels: np.array, k: int = 1, return_scores=False):
self.database_labels = database_labels
self.return_scores = return_scores


def __call__(self, similarity):
"""
Predicts the label for each query based on the k nearest matches in the database.
Expand Down Expand Up @@ -76,7 +76,6 @@ def __call__(self, similarity):
return preds



class TopkClassifier:
"""
Predict top k query labels given nearest matches in the database.
Expand All @@ -93,7 +92,6 @@ def __init__(self, database_labels: np.array, k: int = 10, return_all: bool = Fa
self.database_labels = database_labels
self.return_all = return_all


def __call__(self, similarity):
"""
Predicts the top k labels for each query based on the similarity matrix.
Expand Down Expand Up @@ -134,9 +132,9 @@ def __call__(self, similarity):
data.append(list(zip(*data_row)))

preds, scores, idx = list(zip(*data))
preds = np.array(preds)[:, :self.k]
scores = np.array(scores)[:, :self.k]
idx = np.array(idx)[:, :self.k]
preds = np.array(preds)[:, : self.k]
scores = np.array(scores)[:, : self.k]
idx = np.array(idx)[:, : self.k]

if self.return_all:
return preds, scores, idx
Expand Down
4 changes: 2 additions & 2 deletions wildlife_tools/similarity/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .cosine import CosineSimilarity
from .wildfusion import WildFusion, SimilarityPipeline
from .pairwise.lightglue import MatchLightGlue
from .pairwise.loftr import MatchLOFTR
from .pairwise.lightglue import MatchLightGlue
from .wildfusion import SimilarityPipeline, WildFusion
7 changes: 3 additions & 4 deletions wildlife_tools/similarity/calibration.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from sklearn.linear_model import LogisticRegression
from sklearn.isotonic import IsotonicRegression
from scipy.interpolate import PchipInterpolator
from sklearn.isotonic import IsotonicRegression
from sklearn.linear_model import LogisticRegression


class LogisticCalibration:
Expand Down
2 changes: 1 addition & 1 deletion wildlife_tools/similarity/cosine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import torch
import torch.nn.functional as F

from wildlife_tools.data import FeatureDataset
from ..data import FeatureDataset


def cosine_similarity(a, b):
Expand Down
17 changes: 11 additions & 6 deletions wildlife_tools/similarity/pairwise/base.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from .collectors import CollectCounts
import itertools
import torch
import numpy as np
import matplotlib.pyplot as plt

import cv2
import matplotlib.pyplot as plt
import numpy as np
import torch
from tqdm import tqdm

from ...data import FeatureDataset
from .collectors import CollectCounts


def visualise_matches(img0, keypoints0, img1, keypoints1):
Expand Down Expand Up @@ -109,7 +111,7 @@ def __init__(
batch_size: Number of pairs processed in one batch.
num_workers: Number of workers used for data loading.
tqdm_silent: If True, progress bar is disabled.
collector: Collector object used for storing results.
collector: Collector object used for storing results.
By default, CollectCounts(thresholds=[0.5]) is used.
"""

Expand All @@ -122,7 +124,10 @@ def __init__(
self.tqdm_kwargs = {"mininterval": 1, "ncols": 100, "disable": tqdm_silent}

def __call__(
self, dataset0: FeatureDataset, dataset1: FeatureDataset, pairs: np.array | None = None
self,
dataset0: FeatureDataset,
dataset1: FeatureDataset,
pairs: np.array | None = None,
):
"""
Match pairs of features from two feature datasets.
Expand Down
Loading

0 comments on commit e9b1db3

Please sign in to comment.