segment-animals 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ from typing import Literal
2
+ from PIL import Image
3
+ import torch
4
+ from .detect import DetectionModel, DetectionModelNames
5
+ from .segment import SegmentationModel, SegmentationModelNames
6
+
7
+
8
+ def get_default_device() -> Literal["cpu", "cuda", "mps"]:
9
+ """
10
+ Get the default device for model inference.
11
+ This function checks for CUDA availability first, then MPS (for Apple Silicon),
12
+ and defaults to CPU if neither is available.
13
+ """
14
+ if torch.cuda.is_available():
15
+ return "cuda"
16
+ elif "mps" in dir(torch.backends) and torch.backends.mps.is_available():
17
+ return "mps"
18
+ else:
19
+ return "cpu"
20
+
21
+
22
+
23
+ class AutoAnimalSegmenter:
24
+ """
25
+ AutoAnimalSegmenter is a class that combines detection and segmentation models
26
+ to automatically detect and segment animals in images.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ detection_model_name: DetectionModelNames = "MDV5A",
32
+ detection_threshold: float = 0.15,
33
+ segmentation_model_name: SegmentationModelNames = "vit_h",
34
+ segmentation_device: Literal["cpu", "cuda", "mps"] = get_default_device(),
35
+ ):
36
+ self.detector = DetectionModel(
37
+ model_name=detection_model_name, threshold=detection_threshold
38
+ )
39
+ self.segmentor = SegmentationModel(
40
+ model_name=segmentation_model_name, device=segmentation_device
41
+ )
42
+
43
+ def process_image(self, image: Image.Image) -> tuple:
44
+
45
+ detections = self.detector.detect(image)
46
+ masks = self.segmentor.segment(image, detections)
47
+ return detections, masks
48
+
49
+
50
+ def main() -> None:
51
+ print(
52
+ "No main function implemented yet. Use AutoAnimalSegmenter to process images."
53
+ )
@@ -0,0 +1,57 @@
1
+ import torch
2
+ from megadetector.detection import run_detector
3
+ from .models import AnimalDetection
4
+ from typing import List, Literal
5
+ from logging import getLogger
6
+
7
+ logger = getLogger(__name__)
8
+
9
+ DetectionModelNames = Literal["MDV5A", "MDV5B"]
10
+
11
+
12
+ class DetectionModel:
13
+ """
14
+ Model for detecting animals in images.
15
+ """
16
+
17
+ def __init__(
18
+ self,
19
+ model_name: str = "MDV5A",
20
+ device: Literal["cpu", "cuda", "mps"] = "cpu",
21
+ threshold: float = 0.15,
22
+ ):
23
+ """
24
+ Initialize the detection model with a specified model name.
25
+ """
26
+ self.device = torch.device(device)
27
+ logger.info(f"Loading detection model '{model_name}' on device: {self.device}")
28
+
29
+ self.model = run_detector.load_detector(model_name)
30
+ logger.info(f"Model loaded: {self.model}")
31
+
32
+ self.threshold = threshold
33
+
34
+ def detect(self, image) -> List[AnimalDetection]:
35
+ """
36
+ Detect animals in the provided image.
37
+
38
+ :param image: The input image to process.
39
+ :return: A list of detections above a confidence threshold.
40
+ """
41
+ result = self.model.generate_detections_one_image(
42
+ image, image_id="", detection_threshold=self.threshold
43
+ )
44
+
45
+ return [
46
+ AnimalDetection(
47
+ bbox=(
48
+ d["bbox"][0] * image.width,
49
+ d["bbox"][1] * image.height,
50
+ d["bbox"][2] * image.width,
51
+ d["bbox"][3] * image.height,
52
+ ),
53
+ confidence=d["conf"],
54
+ )
55
+ for d in result["detections"]
56
+ if d["conf"] >= self.threshold and d["category"] == "1"
57
+ ]
@@ -0,0 +1,262 @@
1
+ """
2
+ Model caching and download utilities for segment_animals.
3
+
4
+ Handles downloading and caching of model weights with hash verification.
5
+ """
6
+
7
+ import os
8
+ import hashlib
9
+ import requests
10
+ from pathlib import Path
11
+ from typing import Optional, Dict, List
12
+
13
+
14
+ # Model configurations with their download URLs, expected SHA256 hashes, and namespaces
15
+ MODEL_CONFIGS = {
16
+ "vit_h": {
17
+ "url": "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth",
18
+ "filename": "sam_vit_h_4b8939.pth",
19
+ "sha256": "a7bf3b02f3ebf1267aba913ff637d9a2d5c33d3173bb679e46d9f338c26f262e",
20
+ "namespace": "sam",
21
+ },
22
+ "vit_l": {
23
+ "url": "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_l_0b3195.pth",
24
+ "filename": "sam_vit_l_0b3195.pth",
25
+ "sha256": "3adcc4315b642a4d2101128f611684e8734c41232a17c648ed1693702a49a622",
26
+ "namespace": "sam",
27
+ },
28
+ "vit_b": {
29
+ "url": "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth",
30
+ "filename": "sam_vit_b_01ec64.pth",
31
+ "sha256": "ec2df62732614e57411cdcf32a23ffdf28910380d03139ee0f4fcbe91eb8c912",
32
+ "namespace": "sam",
33
+ },
34
+ }
35
+
36
+
37
+ def get_cache_dir(model_namespace: str = "sam") -> Path:
38
+ """
39
+ Get the cache directory for a specific model namespace within segment_animals.
40
+
41
+ Args:
42
+ model_namespace: The namespace for the models (e.g., 'sam', 'yolo', etc.)
43
+
44
+ Returns:
45
+ Path to the cache directory for the specified namespace
46
+ """
47
+ if os.name == "nt": # Windows
48
+ cache_base = os.environ.get(
49
+ "LOCALAPPDATA", os.path.expanduser("~\\AppData\\Local")
50
+ )
51
+ else: # macOS and Linux
52
+ cache_base = os.environ.get("XDG_CACHE_HOME", os.path.expanduser("~/.cache"))
53
+
54
+ cache_dir = Path(cache_base) / "segment_animals" / "models" / model_namespace
55
+ cache_dir.mkdir(parents=True, exist_ok=True)
56
+ return cache_dir
57
+
58
+
59
+ def calculate_file_hash(file_path: Path) -> str:
60
+ """
61
+ Calculate SHA256 hash of a file.
62
+
63
+ Args:
64
+ file_path: Path to the file
65
+
66
+ Returns:
67
+ Hexadecimal SHA256 hash string
68
+ """
69
+ sha256_hash = hashlib.sha256()
70
+ with open(file_path, "rb") as f:
71
+ # Read file in chunks to handle large files efficiently
72
+ for chunk in iter(lambda: f.read(4096), b""):
73
+ sha256_hash.update(chunk)
74
+ return sha256_hash.hexdigest()
75
+
76
+
77
+ def verify_file_hash(file_path: Path, expected_hash: str) -> bool:
78
+ """
79
+ Verify that a file matches the expected SHA256 hash.
80
+
81
+ Args:
82
+ file_path: Path to the file to verify
83
+ expected_hash: Expected SHA256 hash
84
+
85
+ Returns:
86
+ True if hash matches, False otherwise
87
+ """
88
+ if not file_path.exists():
89
+ return False
90
+
91
+ actual_hash = calculate_file_hash(file_path)
92
+ return actual_hash.lower() == expected_hash.lower()
93
+
94
+
95
+ def download_file_with_progress(url: str, destination: Path) -> None:
96
+ """
97
+ Download a file with progress indication.
98
+
99
+ Args:
100
+ url: URL to download from
101
+ destination: Path where to save the file
102
+ """
103
+ print(f"Downloading from {url}...")
104
+
105
+ response = requests.get(url, stream=True)
106
+ response.raise_for_status()
107
+
108
+ total_size = int(response.headers.get("content-length", 0))
109
+ downloaded_size = 0
110
+
111
+ with open(destination, "wb") as f:
112
+ for chunk in response.iter_content(chunk_size=8192):
113
+ if chunk:
114
+ f.write(chunk)
115
+ downloaded_size += len(chunk)
116
+
117
+ if total_size > 0:
118
+ progress = (downloaded_size / total_size) * 100
119
+ print(f"\rProgress: {progress:.1f}%", end="", flush=True)
120
+
121
+ if total_size > 0:
122
+ print() # New line after progress
123
+ print(f"Download completed: {destination}")
124
+
125
+
126
+ def get_model_path(model_name: str, auto_download: bool = True) -> Path:
127
+ """
128
+ Get the path to a cached model, downloading it if necessary.
129
+
130
+ Args:
131
+ model_name: Name of the model (e.g., 'sam_vit_h_4b8939')
132
+ auto_download: Whether to automatically download the model if not found
133
+
134
+ Returns:
135
+ Path to the model file
136
+
137
+ Raises:
138
+ ValueError: If model_name is not recognized
139
+ FileNotFoundError: If model is not cached and auto_download is False
140
+ RuntimeError: If download fails or hash verification fails
141
+ """
142
+ if model_name not in MODEL_CONFIGS:
143
+ raise ValueError(
144
+ f"Unknown model: {model_name}. Available models: {list(MODEL_CONFIGS.keys())}"
145
+ )
146
+
147
+ config = MODEL_CONFIGS[model_name]
148
+ model_namespace = config.get(
149
+ "namespace", "sam"
150
+ ) # Default to 'sam' for backward compatibility
151
+ cache_dir = get_cache_dir(model_namespace)
152
+ model_path = cache_dir / config["filename"]
153
+
154
+ # Check if model exists and has correct hash
155
+ if model_path.exists():
156
+ print(f"Found cached model: {model_path}")
157
+ if verify_file_hash(model_path, config["sha256"]):
158
+ print("Hash verification passed")
159
+ return model_path
160
+ else:
161
+ print("Hash verification failed, re-downloading...")
162
+ model_path.unlink() # Remove corrupted file
163
+
164
+ if not auto_download:
165
+ raise FileNotFoundError(
166
+ f"Model {model_name} not found in cache and auto_download is disabled"
167
+ )
168
+
169
+ # Download the model
170
+ try:
171
+ print(f"Downloading {model_name} model...")
172
+ download_file_with_progress(config["url"], model_path)
173
+
174
+ # Verify hash after download
175
+ if not verify_file_hash(model_path, config["sha256"]):
176
+ model_path.unlink() # Remove corrupted download
177
+ raise RuntimeError(
178
+ f"Downloaded file hash verification failed for {model_name}"
179
+ )
180
+
181
+ print("Hash verification passed")
182
+ return model_path
183
+
184
+ except Exception as e:
185
+ if model_path.exists():
186
+ model_path.unlink() # Clean up partial download
187
+ raise RuntimeError(f"Failed to download {model_name}: {e}")
188
+
189
+
190
+ def register_model(
191
+ model_name: str, url: str, filename: str, sha256: str, namespace: str = "sam"
192
+ ) -> None:
193
+ """
194
+ Register a new model in the model registry.
195
+
196
+ Args:
197
+ model_name: Unique identifier for the model
198
+ url: Download URL for the model
199
+ filename: Local filename to save the model as
200
+ sha256: Expected SHA256 hash of the model file
201
+ namespace: Model namespace (default: 'sam')
202
+ """
203
+ MODEL_CONFIGS[model_name] = {
204
+ "url": url,
205
+ "filename": filename,
206
+ "sha256": sha256,
207
+ "namespace": namespace,
208
+ }
209
+
210
+
211
+ def list_models(namespace: Optional[str] = None) -> List[str]:
212
+ """
213
+ List available models, optionally filtered by namespace.
214
+
215
+ Args:
216
+ namespace: Optional namespace to filter by
217
+
218
+ Returns:
219
+ List of model names
220
+ """
221
+ if namespace is None:
222
+ return list(MODEL_CONFIGS.keys())
223
+
224
+ return [
225
+ model_name
226
+ for model_name, config in MODEL_CONFIGS.items()
227
+ if config.get("namespace", "sam") == namespace
228
+ ]
229
+
230
+
231
+ def list_namespaces() -> List[str]:
232
+ """
233
+ List all available model namespaces.
234
+
235
+ Returns:
236
+ List of unique namespaces
237
+ """
238
+ namespaces = set()
239
+ for config in MODEL_CONFIGS.values():
240
+ namespaces.add(config.get("namespace", "sam"))
241
+ return sorted(list(namespaces))
242
+
243
+
244
+ def get_model_info(model_name: str) -> Dict[str, str]:
245
+ """
246
+ Get information about a specific model.
247
+
248
+ Args:
249
+ model_name: Name of the model
250
+
251
+ Returns:
252
+ Dictionary with model information
253
+
254
+ Raises:
255
+ ValueError: If model_name is not recognized
256
+ """
257
+ if model_name not in MODEL_CONFIGS:
258
+ raise ValueError(
259
+ f"Unknown model: {model_name}. Available models: {list(MODEL_CONFIGS.keys())}"
260
+ )
261
+
262
+ return MODEL_CONFIGS[model_name].copy()
@@ -0,0 +1,25 @@
1
+ from typing import Annotated, Tuple
2
+ from pydantic import BaseModel
3
+
4
+
5
+ class AnimalDetection(BaseModel):
6
+ """
7
+ Model representing an animal detection.
8
+ """
9
+
10
+ bbox: Annotated[
11
+ Tuple[float, float, float, float],
12
+ "Bounding box coordinates (x_min, y_min, x_max, y_max)",
13
+ ]
14
+ confidence: Annotated[float, "Confidence score of the detection (0.0 to 1.0)"]
15
+
16
+
17
+ class AnimalSegment(BaseModel):
18
+ """
19
+ Model representing an animal segmentation.
20
+ """
21
+
22
+ mask: Annotated[
23
+ list[list[float]],
24
+ "Segmentation mask as a binary array",
25
+ ]
@@ -0,0 +1,71 @@
1
+ from typing import List, Literal
2
+ from segment_anything import SamPredictor, sam_model_registry
3
+ from PIL import Image
4
+ import numpy as np
5
+ import torch
6
+ from segment_animals.models import AnimalDetection
7
+ from segment_animals.model_cache import get_model_path
8
+ from segment_anything.utils.transforms import ResizeLongestSide
9
+
10
+ SegmentationModelNames = Literal["vit_h", "vit_l", "vit_b"]
11
+
12
+
13
+ class SegmentationModel:
14
+ """
15
+ Model for segmenting animals in images using SAM (Segment Anything Model).
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ model_name: SegmentationModelNames = "vit_h",
21
+ device: Literal["cpu", "cuda", "mps"] = "cpu",
22
+ ):
23
+ # Get the model path, downloading it if necessary
24
+ model_path = get_model_path(model_name, auto_download=True)
25
+
26
+ self.sam = sam_model_registry[model_name](checkpoint=str(model_path))
27
+ self.sam.to(device)
28
+ self.predictor = SamPredictor(self.sam)
29
+ self.resize_transform = ResizeLongestSide(self.sam.image_encoder.img_size)
30
+
31
+ def segment(self, image: Image.Image, detections: List[AnimalDetection]):
32
+ """
33
+ Segment animals in the provided image.
34
+
35
+ :param image: The input image to process.
36
+ :param detections: The detections to guide the segmentation.
37
+ :return: A list of segmentation masks.
38
+ """
39
+ np_image = np.array(image.convert("RGB"))
40
+ self.predictor.set_image(np_image)
41
+
42
+ if not detections:
43
+ return torch.empty((0, *np_image.shape[:2]), dtype=torch.bool)
44
+
45
+ transformed_boxes = self.resize_transform.apply_boxes_torch(
46
+ torch.tensor(
47
+ np.atleast_2d(
48
+ np.array(
49
+ [
50
+ [
51
+ d.bbox[0],
52
+ d.bbox[1],
53
+ d.bbox[0] + d.bbox[2],
54
+ d.bbox[1] + d.bbox[3],
55
+ ]
56
+ for d in detections
57
+ ]
58
+ )
59
+ )
60
+ ),
61
+ np_image.shape[:2],
62
+ ).to(self.sam.device)
63
+
64
+ masks, _, _ = self.predictor.predict_torch(
65
+ point_coords=None,
66
+ point_labels=None,
67
+ boxes=transformed_boxes,
68
+ multimask_output=False,
69
+ )
70
+
71
+ return masks.cpu()
@@ -0,0 +1,33 @@
1
+ from PIL import Image
2
+ from requests import get
3
+ from io import BytesIO
4
+ from pathlib import Path
5
+
6
+ ImageSource = str | Path | Image.Image
7
+
8
+
9
+ def load_image(source: ImageSource) -> Image.Image:
10
+ """
11
+ Load an image from a file path or URL.
12
+
13
+ :param source: The file path or URL of the image.
14
+ :return: The loaded image as a PIL Image object.
15
+ """
16
+ if isinstance(source, Image.Image):
17
+ # If the source is already a PIL Image, return a copy of it
18
+ return source.copy()
19
+
20
+ elif isinstance(source, (Path, str)) and Path(source).is_file():
21
+ # If the source is a file path, open the image from the file
22
+ return Image.open(source)
23
+
24
+ elif isinstance(source, str):
25
+ # If the source is a URL, fetch the image from the URL
26
+ response = get(source)
27
+ response.raise_for_status()
28
+ return Image.open(BytesIO(response.content))
29
+
30
+ else:
31
+ raise ValueError(
32
+ "Invalid image source. Provide a file path, URL, or PIL Image object."
33
+ )
segment_animals/viz.py ADDED
@@ -0,0 +1,100 @@
1
+ from matplotlib import pyplot as plt
2
+ from matplotlib.patches import Rectangle
3
+ import numpy as np
4
+ from PIL import Image
5
+ import random
6
+
7
+
8
+ def thumbnail(image, size=512):
9
+ """
10
+ Create a thumbnail of the image with the specified size.
11
+ The thumbnail will maintain the aspect ratio of the original image.
12
+ """
13
+ image = image.copy()
14
+ image.thumbnail((size, size))
15
+ return image
16
+
17
+
18
+ def random_bright_rgb_0_1():
19
+ """
20
+ Generate a random bright RGB color with values between 0 and 1.
21
+ The color will be bright enough to be visible against most backgrounds.
22
+ """
23
+ return tuple(random.randint(128, 255) / 255 for _ in range(3))
24
+
25
+
26
+ def plot_detections_and_masks(image, detections, masks):
27
+ """
28
+ Plot the detections and masks on the image.
29
+ """
30
+ plt.figure(figsize=(10, 10))
31
+ plt.imshow(image)
32
+
33
+ for i, (detection, mask) in enumerate(zip(detections, masks)):
34
+ random_color = random_bright_rgb_0_1()
35
+
36
+ plt.gca().add_patch(
37
+ Rectangle(
38
+ (detection.bbox[0], detection.bbox[1]),
39
+ detection.bbox[2],
40
+ detection.bbox[3],
41
+ fill=False,
42
+ color=random_color,
43
+ )
44
+ )
45
+ plt.text(
46
+ detection.bbox[0] + 8,
47
+ detection.bbox[1] - 20,
48
+ f"Animal {i + 1}: {detection.confidence:.2f}",
49
+ color="white",
50
+ fontsize=12,
51
+ bbox=dict(
52
+ facecolor=random_color,
53
+ alpha=0.5,
54
+ edgecolor="none",
55
+ boxstyle="round,pad=0.3",
56
+ ),
57
+ )
58
+
59
+ # Create an overlay for the mask in the same color
60
+ mask_2d = mask.squeeze() # Remove extra dimension
61
+ overlay = np.zeros_like(image, dtype=np.float32)
62
+ overlay[mask_2d] = random_color
63
+ plt.imshow(overlay, alpha=0.5)
64
+
65
+ plt.axis("off")
66
+ plt.show()
67
+ return plt.gcf() # Return the figure object for further manipulation if needed
68
+
69
+
70
+ def mask_to_bbox(mask):
71
+ """Convert a mask to a bounding box."""
72
+
73
+ mask_2d = mask.squeeze().numpy().astype("uint8")
74
+ mask_pil = Image.fromarray(mask_2d * 255, mode="L")
75
+ bbox = mask_pil.getbbox()
76
+ return bbox
77
+
78
+
79
+ def extract_masks(image, masks, whole_image=False):
80
+ """Extracts the original image filtered by each detected animal mask, with background transparent."""
81
+
82
+ extracted_images = []
83
+ image_np = np.array(image.convert("RGBA"))
84
+ for mask in masks:
85
+ mask_2d = mask.squeeze().numpy().astype("uint8")
86
+ # Create an alpha channel based on the mask
87
+ alpha = (mask_2d * 255).astype("uint8")
88
+ # Stack alpha channel to image
89
+ rgba = image_np.copy()
90
+ rgba[..., 3] = alpha
91
+ filtered_img = Image.fromarray(rgba, mode="RGBA")
92
+ extracted_images.append(filtered_img)
93
+
94
+ if not whole_image:
95
+ # Crop the image to the bounding box of the mask
96
+ bbox = mask_to_bbox(mask)
97
+ filtered_img = filtered_img.crop(bbox)
98
+ extracted_images[-1] = filtered_img
99
+
100
+ return extracted_images
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: segment-animals
3
+ Version: 1.0.0
4
+ Summary: Segment (Extract) Animals from Images - Removing Background
5
+ Author-email: Ben Evans <ben@bluechimp.io>
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: megadetector>=5.0.29
8
+ Requires-Dist: pillow>=11.2.1
9
+ Requires-Dist: requests>=2.32.4
10
+ Requires-Dist: segment-anything
11
+ Description-Content-Type: text/markdown
12
+
13
+ # Segment Animals
14
+
15
+ Segment Animals is a Python package for segmenting (extracting) animals from images using deep learning models. It provides a pipeline that combines object detection and segmentation to identify and extract animals from images, making it useful for wildlife research, conservation efforts, and any application where you wish to remove the background from images containing animals.
16
+
17
+ Segment Animals builds upon the [Segment Anything](https://github.com/facebookresearch/segment-anything) and [MegaDetector](https://github.com/agentmorris/MegaDetector/blob/main/getting-started.md) models.
18
+
19
+ # Installation
20
+
21
+ You can install Segment Animals using pip:
22
+
23
+ ```bash
24
+ pip install segment-animals
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ Here's a quick example of how to use Segment Animals, for a more detailed guide refer to the [notebook](./notebook.ipynb).
30
+
31
+ ### Importing the library and processing an image
32
+
33
+ ```python
34
+ from segment_animals import AutoAnimalSegmenter
35
+ from segment_animals.util import load_image
36
+
37
+ model = AutoAnimalSegmenter()
38
+
39
+ image = load_image("path/to/your/image.jpg")
40
+
41
+ detections, masks = model.process_image(image)
42
+ print(f"Found {len(detections)} animals.")
43
+ ```
44
+
45
+ ### Visualizing detections and masks
46
+
47
+ ```python
48
+ from segment_animals.viz import plot_detections_and_masks
49
+
50
+ plot_detections_and_masks(image, detections, masks)
51
+ ```
52
+
53
+ You should then see a visualisation along the lines of this ([original image from Wikipedia](https://commons.wikimedia.org/wiki/File:Camouflaged_Predator.jpg))...
54
+
55
+ ![Example Segmentation](./example_viz.png)
56
+
57
+ ### Extracting and saving masks
58
+
59
+ ```python
60
+ from segment_animals.viz import extract_masks
61
+
62
+ # Setting whole_image to False will return individual masks cropped to the extent
63
+ # of the predicted masks.
64
+ for i, mask_extract in enumerate(extract_masks(image, masks, whole_image=False)):
65
+ # mask_extract is a PIL Image object so you can save it or manipulate it further
66
+ mask_extract.save(f"animal_mask_{i}.png")
67
+ ```
68
+
69
+ Resulting in something like this:
70
+
71
+ ![Example Mask](./example_extract.png)
72
+
73
+ # Working with Segment Animals?
74
+
75
+ It'd be great to hear how you're using Segment Animals! Drop me a line at Benjamin.Evans at ioz.ac.uk or open an issue on the [GitHub repository](https://github.com/bencevans/segment-animals/issues).
@@ -0,0 +1,11 @@
1
+ segment_animals/__init__.py,sha256=R6bXAh2SQ40zkEKCkkaE27Sai7qOqVgjoz4ggRU8RDw,1686
2
+ segment_animals/detect.py,sha256=pR6xeoYAo6MccWKtxO1N5Nzy232Bb49bZdDwNb2nlP4,1691
3
+ segment_animals/model_cache.py,sha256=QjypUGXD2ncTI7RwrsXlUVta43HDJRXydlewzHQW_XA,7847
4
+ segment_animals/models.py,sha256=N98DcIiYXRad_Vb0hRRRh3rQbIgwdV3zQVC_44MTa3Q,581
5
+ segment_animals/segment.py,sha256=UqdC6SW_4UR6KqK-OBgGDKrmSdOuHFJgRTzVXmSg3CE,2384
6
+ segment_animals/util.py,sha256=v8RC-crFNOUsNFIGkXmOvbFH390xhfApiCLUGx9ToA4,1014
7
+ segment_animals/viz.py,sha256=fzPYARppg9o74b0iZYQ5IaNClb1Jwmxygj66gNEgKzo,3075
8
+ segment_animals-1.0.0.dist-info/METADATA,sha256=TpAHXBxjqLyXZ6zavnLazfkT5PLEuCxj4v2QBXdHA4E,2631
9
+ segment_animals-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ segment_animals-1.0.0.dist-info/entry_points.txt,sha256=EL5kd7XoXzMszTmmwZKaLmXlEmezkeTWh6kgWRaUArY,57
11
+ segment_animals-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ segment-animals = segment_animals:main