Timmyafolami commited on
Commit
15c7eff
·
verified ·
1 Parent(s): 512c546

Upload 11 files

Browse files
Files changed (11) hide show
  1. .env +5 -0
  2. Dockerfile +26 -0
  3. db_bucket.py +50 -0
  4. db_conn_testing.ipynb +0 -0
  5. db_user_info.py +38 -0
  6. detection.py +135 -0
  7. docker-compose.yml +16 -0
  8. entrypoint.sh +8 -0
  9. main.py +99 -0
  10. new_yolov8_best.pt +3 -0
  11. requirements.txt +177 -0
.env ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Database_Password="rB97dJHBQEawiwM2"
2
+ SUPABASE_URL="https://xfmceiwvmuxyfgsyogct.supabase.co"
3
+ SUPABASE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhmbWNlaXd2bXV4eWZnc3lvZ2N0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MjE2MzM2NTksImV4cCI6MjAzNzIwOTY1OX0.O8i1TEUeA8xE5ZJpBbn9yw8owjcXQ3mrLvrRrcu_XeQ"
4
+ USER_EMAIL_1="[email protected]"
5
+ USER_PASSWORD_1="Timmy8469@@"
Dockerfile ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.10-slim
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies
8
+ RUN apt-get update && apt-get install -y \
9
+ gcc \
10
+ libgl1-mesa-glx \
11
+ libglib2.0-0
12
+
13
+ # Copy only requirements to leverage Docker cache
14
+ COPY requirements.txt .
15
+
16
+ # Install any needed packages specified in requirements.txt
17
+ RUN pip install --no-cache-dir -r requirements.txt
18
+
19
+ # Copy the rest of the application code
20
+ COPY . /app
21
+
22
+ # Make port 8000 available to the world outside this container
23
+ EXPOSE 8000
24
+
25
+ # Run the entrypoint script
26
+ CMD ["sh", "./entrypoint.sh"]
db_bucket.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from dotenv import load_dotenv
3
+ import os
4
+ load_dotenv()
5
+
6
+ from supabase import create_client, Client
7
+
8
+ url: str = os.environ.get("SUPABASE_URL")
9
+ key: str = os.environ.get("SUPABASE_KEY")
10
+ supabase: Client = create_client(url, key)
11
+
12
+
13
+ # authentication set up
14
+ user_email:str = os.environ.get("USER_EMAIL_1")
15
+ user_password: str = os.environ.get("USER_PASSWORD_1")
16
+
17
+ # Sign in the user
18
+ auth_response = supabase.auth.sign_in_with_password({"email": user_email, "password": user_password})
19
+
20
+ # checking if a file exists
21
+ def file_exists(file_path):
22
+ return os.path.isfile(file_path)
23
+
24
+ # adding detected weed shp files to the bucket
25
+ def upload_file_to_bucket(file_path: str, storage_path: str = "shapefiles_storage/", bucket_name: str = "shapefiles-bucket") -> None:
26
+ # firstlt checking if the path exists
27
+ if not file_exists(file_path):
28
+ print(f"Error: {file_path} does not exist")
29
+ return None
30
+ # extracting the file ame
31
+ file_name = os.path.basename(file_path)
32
+
33
+ # adding today's date and time to the file name
34
+ file_name = datetime.now().strftime("%Y-%m-%d_%H-%M-%S_") + file_name
35
+
36
+ with open(file_path, 'rb') as file:
37
+ response = supabase.storage.from_(bucket_name).upload(storage_path+file_name, file)
38
+
39
+ if response.status_code != 200:
40
+ return f"Error: {response.json()['message']}"
41
+ else:
42
+ return "File uploaded successfully:", response.json()
43
+
44
+ if __name__ == "__main__":
45
+ # Define the relative path to the file from the current script location
46
+ file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'db setup.zip'))
47
+
48
+ # upload the file to the bucket
49
+ response = upload_file_to_bucket(file_path=file_path)
50
+ print(response)
db_conn_testing.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
db_user_info.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ import os
3
+ load_dotenv()
4
+
5
+ from supabase import create_client, Client
6
+
7
+ url: str = os.environ.get("SUPABASE_URL")
8
+ key: str = os.environ.get("SUPABASE_KEY")
9
+ supabase: Client = create_client(url, key)
10
+
11
+
12
+ # authentication set up
13
+ user_email:str = os.environ.get("USER_EMAIL_1")
14
+ user_password: str = os.environ.get("USER_PASSWORD_1")
15
+
16
+ # Sign in the user
17
+ auth_response = supabase.auth.sign_in_with_password({"email": user_email, "password": user_password})
18
+
19
+
20
+ # creating a function to insert user info
21
+ def insert_user_info(user_info: dict) -> None:
22
+ try:
23
+ response = supabase.table("user-info").insert(user_info).execute()
24
+ return f"'response': {response}"
25
+ except Exception as e:
26
+ print(e)
27
+
28
+
29
+ if __name__ == "__main__":
30
+
31
+ new_info = {
32
+ "Name": "Timothy Afolami",
33
+ "Address": "Lagos, Nigeria",
34
+ "Phone Number": "08100450227",
35
+ "Email" : "[email protected]"
36
+ }
37
+ response = insert_user_info(user_info=new_info)
38
+ print(response)
detection.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import zipfile
4
+ import shapefile
5
+ import numpy as np
6
+ from shapely.geometry import Polygon
7
+ from io import BytesIO
8
+ from PIL import Image
9
+ import rasterio
10
+ from rasterio.windows import Window
11
+ from ultralytics import YOLO
12
+ from db_bucket import upload_file_to_bucket # Import the bucket upload function
13
+
14
+ # Paths and configurations
15
+ path_to_store_bounding_boxes = 'detect/'
16
+ path_to_save_shapefile = 'weed_detections'
17
+ slice_size = 3000
18
+
19
+ # Load YOLO model (update the path to your model)
20
+ model = YOLO('new_yolov8_best.pt')
21
+
22
+ class_names = ["citrus area", "trees", "weeds", "weeds and trees"]
23
+
24
+ # Function to initialize directories
25
+ def initialize_directories():
26
+ os.makedirs(path_to_store_bounding_boxes, exist_ok=True)
27
+ os.makedirs("slices", exist_ok=True)
28
+
29
+ # Function to slice the GeoTIFF
30
+ async def slice_geotiff(file_path, slice_size=3000):
31
+ slices = []
32
+ with rasterio.open(file_path) as dataset:
33
+ img_width = dataset.width
34
+ img_height = dataset.height
35
+ transform = dataset.transform
36
+
37
+ for i in range(0, img_height, slice_size):
38
+ for j in range(0, img_width, slice_size):
39
+ window = Window(j, i, slice_size, slice_size)
40
+ transform_window = rasterio.windows.transform(window, transform)
41
+ slice_data = dataset.read(window=window)
42
+ slice_img = Image.fromarray(slice_data.transpose(1, 2, 0)) # Convert to HWC format
43
+ slice_filename = f"slices/slice_{i}_{j}.png"
44
+ slice_img.save(slice_filename)
45
+ slices.append((slice_filename, transform_window))
46
+ return slices
47
+
48
+ # Function to create a shapefile with image dimensions and bounding boxes
49
+ def create_shapefile_with_latlon(bboxes, shapefile_path="weed_detections.shp"):
50
+ w = shapefile.Writer(shapefile_path)
51
+ w.field('id', 'C')
52
+
53
+ for idx, (x1, y1, x2, y2, transform) in enumerate(bboxes):
54
+ top_left = rasterio.transform.xy(transform, y1, x1, offset='center')
55
+ top_right = rasterio.transform.xy(transform, y1, x2, offset='center')
56
+ bottom_left = rasterio.transform.xy(transform, y2, x1, offset='center')
57
+ bottom_right = rasterio.transform.xy(transform, y2, x2, offset='center')
58
+
59
+ poly = Polygon([top_left, top_right, bottom_right, bottom_left, top_left])
60
+ w.poly([poly.exterior.coords])
61
+ w.record(f'weed_{idx}')
62
+
63
+ w.close()
64
+
65
+ # Function to detect weeds in image slices
66
+ async def detect_weeds_in_slices(slices):
67
+ weed_bboxes = []
68
+ img_width, img_height = slice_size, slice_size # Assuming fixed slice size
69
+
70
+ for slice_filename, transform in slices:
71
+ img_array = np.array(Image.open(slice_filename))
72
+ results = model.predict(slice_filename, imgsz=640, conf=0.2, iou=0.4)
73
+ results = results[0]
74
+
75
+ for i, box in enumerate(results.boxes):
76
+ tensor = box.xyxy[0]
77
+ x1 = int(tensor[0].item())
78
+ y1 = int(tensor[1].item())
79
+ x2 = int(tensor[2].item())
80
+ y2 = int(tensor[3].item())
81
+ conf = box.conf[0].item()
82
+ label = box.cls[0].item()
83
+
84
+ if class_names[int(label)] == "weeds":
85
+ cv2.rectangle(img_array, (x1, y1), (x2, y2), (255, 0, 255), 3)
86
+ weed_bboxes.append((x1, y1, x2, y2, transform))
87
+
88
+ # Save the image with bounding boxes
89
+ detected_image_path = os.path.join(path_to_store_bounding_boxes, os.path.basename(slice_filename))
90
+ cv2.imwrite(detected_image_path, cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR))
91
+
92
+ create_shapefile_with_latlon(weed_bboxes)
93
+
94
+ async def create_zip():
95
+ # Create a zip file
96
+ zip_file_path = "weed_detections.zip"
97
+ with zipfile.ZipFile(zip_file_path, 'w') as zip_file:
98
+ for ext in ['shp', 'shx', 'dbf']:
99
+ file_name = f"{path_to_save_shapefile}.{ext}"
100
+ if os.path.exists(file_name):
101
+ zip_file.write(file_name, os.path.basename(file_name))
102
+
103
+ return zip_file_path
104
+
105
+ # Function to clean up created files and directories
106
+ def cleanup():
107
+ # Remove the zip file
108
+ if os.path.exists("weed_detections.zip"):
109
+ os.remove("weed_detections.zip")
110
+
111
+ # Remove the geotiff file
112
+ if os.path.exists("uploaded_geotiff.tif"):
113
+ os.remove("uploaded_geotiff.tif")
114
+
115
+ # Remove shapefile components
116
+ for ext in ['shp', 'shx', 'dbf']:
117
+ file_name = f"{path_to_save_shapefile}.{ext}"
118
+ if os.path.exists(file_name):
119
+ os.remove(file_name)
120
+
121
+ # Remove slices
122
+ if os.path.exists("slices"):
123
+ for file in os.listdir("slices"):
124
+ file_path = os.path.join("slices", file)
125
+ if os.path.isfile(file_path):
126
+ os.remove(file_path)
127
+ os.rmdir("slices")
128
+
129
+ # Remove detected bounding boxes
130
+ if os.path.exists("detect"):
131
+ for file in os.listdir("detect"):
132
+ file_path = os.path.join("detect", file)
133
+ if os.path.isfile(file_path):
134
+ os.remove(file_path)
135
+ os.rmdir("detect")
docker-compose.yml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.9'
2
+
3
+ services:
4
+ app:
5
+ build:
6
+ context: .
7
+ dockerfile: Dockerfile
8
+ ports:
9
+ - "8000:8000"
10
+ volumes:
11
+ - .:/app
12
+ environment:
13
+ - SUPABASE_URL=${SUPABASE_URL}
14
+ - SUPABASE_KEY=${SUPABASE_KEY}
15
+ - USER_EMAIL_1=${USER_EMAIL_1}
16
+ - USER_PASSWORD_1=${USER_PASSWORD_1}
entrypoint.sh ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+
3
+ # Exit immediately if a command exits with a non-zero status
4
+ set -e
5
+
6
+ # Step 4: Run FastAPI App
7
+ echo "Starting FastAPI app..."
8
+ uvicorn main:app --host 0.0.0.0 --port 8000
main.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect, UploadFile, File, Request
2
+ from fastapi.responses import JSONResponse, HTMLResponse, StreamingResponse
3
+ from fastapi.staticfiles import StaticFiles
4
+ from detection import initialize_directories, slice_geotiff, detect_weeds_in_slices, cleanup, create_zip # Import detection functions
5
+ from db_user_info import insert_user_info
6
+ from db_bucket import upload_file_to_bucket
7
+ from typing import List
8
+ from io import BytesIO
9
+
10
+ app = FastAPI()
11
+
12
+ # Mount static files
13
+ app.mount("/static", StaticFiles(directory="static"), name="static")
14
+
15
+ # WebSocket manager
16
+ class ConnectionManager:
17
+ def __init__(self):
18
+ self.active_connections: List[WebSocket] = []
19
+
20
+ async def connect(self, websocket: WebSocket):
21
+ await websocket.accept()
22
+ self.active_connections.append(websocket)
23
+
24
+ def disconnect(self, websocket: WebSocket):
25
+ self.active_connections.remove(websocket)
26
+
27
+ async def send_message(self, message: str):
28
+ for connection in self.active_connections:
29
+ await connection.send_text(message)
30
+
31
+ manager = ConnectionManager()
32
+
33
+ @app.get("/", response_class=HTMLResponse)
34
+ async def read_index():
35
+ with open("static/index.html") as f:
36
+ return HTMLResponse(content=f.read())
37
+
38
+ @app.get("/app", response_class=HTMLResponse)
39
+ async def read_app():
40
+ with open("static/app.html") as f:
41
+ return HTMLResponse(content=f.read())
42
+
43
+ @app.post("/register")
44
+ async def register_user(request: Request):
45
+ user_info = await request.json()
46
+ try:
47
+ insert_user_info(user_info)
48
+ return JSONResponse(content={"detail": "User registered successfully"}, status_code=200)
49
+ except Exception as e:
50
+ return JSONResponse(content={"detail": str(e)}, status_code=400)
51
+
52
+ @app.post("/upload_geotiff/")
53
+ async def upload_geotiff(file: UploadFile = File(...)):
54
+ # Initialize directories at the start
55
+ initialize_directories()
56
+
57
+ file_location = f"uploaded_geotiff.tif"
58
+ with open(file_location, "wb") as f:
59
+ f.write(file.file.read())
60
+
61
+ await manager.send_message("GeoTIFF file uploaded successfully. Slicing started.")
62
+ slices = await slice_geotiff(file_location, slice_size=3000)
63
+ await manager.send_message("Slicing complete. Starting weed detection.")
64
+ weed_bboxes = await detect_weeds_in_slices(slices)
65
+ await manager.send_message("Weed detection complete. Generating shapefile.")
66
+
67
+ # Create zip file
68
+ zip_file_path = await create_zip()
69
+ await manager.send_message("Shapefiles Generated. Zipping shapefile.")
70
+
71
+ # Upload the zip file to the bucket
72
+ response = upload_file_to_bucket(zip_file_path)
73
+ print(response)
74
+ await manager.send_message("Zip file uploaded to bucket storage.")
75
+
76
+ # Read zip file into buffer for download
77
+ zip_buffer = BytesIO()
78
+ with open(zip_file_path, 'rb') as f:
79
+ zip_buffer.write(f.read())
80
+ zip_buffer.seek(0)
81
+
82
+ # Cleanup files and directories
83
+ cleanup()
84
+
85
+ return StreamingResponse(zip_buffer, media_type="application/zip", headers={"Content-Disposition": "attachment; filename=weed_detections.zip"})
86
+
87
+ @app.websocket("/ws")
88
+ async def websocket_endpoint(websocket: WebSocket):
89
+ await manager.connect(websocket)
90
+ try:
91
+ while True:
92
+ data = await websocket.receive_text()
93
+ await manager.send_message(data)
94
+ except WebSocketDisconnect:
95
+ manager.disconnect(websocket)
96
+
97
+
98
+ if __name__ == "__main__":
99
+ uvicorn.run(app, host="0.0.0.0", port=8000)
new_yolov8_best.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2664fca042b1ead70a9dc27597052b1c137719ab8ae3a870c940bf16cdfc4c0e
3
+ size 22508569
requirements.txt ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ affine==2.4.0
2
+ altair==5.3.0
3
+ annotated-types==0.7.0
4
+ anyio==4.4.0
5
+ argon2-cffi==23.1.0
6
+ argon2-cffi-bindings==21.2.0
7
+ arrow==1.3.0
8
+ asttokens==2.4.1
9
+ async-lru==2.0.4
10
+ attrs==23.2.0
11
+ Babel==2.15.0
12
+ beautifulsoup4==4.12.3
13
+ bleach==6.1.0
14
+ blinker==1.8.2
15
+ cachetools==5.3.3
16
+ certifi==2024.2.2
17
+ cffi==1.16.0
18
+ charset-normalizer==3.3.2
19
+ click==8.1.7
20
+ click-plugins==1.1.1
21
+ cligj==0.7.2
22
+ colorama==0.4.6
23
+ comm==0.2.2
24
+ contourpy==1.2.1
25
+ cycler==0.12.1
26
+ debugpy==1.8.1
27
+ decorator==5.1.1
28
+ defusedxml==0.7.1
29
+ deprecation==2.1.0
30
+ dnspython==2.6.1
31
+ email_validator==2.1.1
32
+ exceptiongroup==1.2.1
33
+ executing==2.0.1
34
+ fastapi==0.111.0
35
+ fastapi-cli==0.0.4
36
+ fastjsonschema==2.19.1
37
+ filelock==3.14.0
38
+ fiona==1.9.6
39
+ fonttools==4.52.3
40
+ fqdn==1.5.1
41
+ fsspec==2024.5.0
42
+ geopandas==0.14.4
43
+ gitdb==4.0.11
44
+ GitPython==3.1.43
45
+ gotrue==2.6.0
46
+ h11==0.14.0
47
+ h2==4.1.0
48
+ hpack==4.0.0
49
+ httpcore==1.0.5
50
+ httptools==0.6.1
51
+ httpx==0.27.0
52
+ hyperframe==6.0.1
53
+ idna==3.7
54
+ intel-openmp==2021.4.0
55
+ ipykernel==6.29.4
56
+ ipython==8.24.0
57
+ isoduration==20.11.0
58
+ jedi==0.19.1
59
+ Jinja2==3.1.4
60
+ json5==0.9.25
61
+ jsonpointer==2.4
62
+ jsonschema==4.22.0
63
+ jsonschema-specifications==2023.12.1
64
+ jupyter_client==8.6.2
65
+ jupyter_core==5.7.2
66
+ jupyter-events==0.10.0
67
+ jupyter-lsp===2.2.5
68
+ jupyter_server==2.14.0
69
+ jupyter_server_terminals==0.5.3
70
+ jupyterlab==4.2.1
71
+ jupyterlab_pygments==0.3.0
72
+ jupyterlab_server==2.27.2
73
+ kiwisolver==1.4.5
74
+ markdown-it-py==3.0.0
75
+ MarkupSafe==2.1.5
76
+ matplotlib==3.9.0
77
+ matplotlib-inline==0.1.7
78
+ mdurl==0.1.2
79
+ mistune==3.0.2
80
+ mkl==2021.4.0
81
+ mpmath==1.3.0
82
+ nbclient==0.10.0
83
+ nbconvert==7.16.4
84
+ nbformat==5.10.4
85
+ nest-asyncio==1.6.0
86
+ networkx==3.3
87
+ notebook==7.2.0
88
+ notebook_shim===0.2.4
89
+ numpy==1.26.4
90
+ opencv-python==4.10.0.82
91
+ opencv-python-headless==4.10.0.82
92
+ orjson==3.10.3
93
+ overrides==7.7.0
94
+ packaging==24.0
95
+ pandas==2.2.2
96
+ pandocfilters==1.5.1
97
+ parso==0.8.4
98
+ pillow==10.3.0
99
+ pip==24.0
100
+ platformdirs==4.2.2
101
+ postgrest==0.16.9
102
+ prometheus_client==0.20.0
103
+ prompt_toolkit==3.0.44
104
+ protobuf==4.25.3
105
+ psutil==5.9.8
106
+ pure-eval==0.2.2
107
+ py-cpuinfo==9.0.0
108
+ pyarrow==16.1.0
109
+ pycparser==2.22
110
+ pydantic==2.7.3
111
+ pydantic_core==2.18.4
112
+ pydeck==0.9.1
113
+ Pygments==2.18.0
114
+ pyparsing==3.1.2
115
+ pyproj==3.6.1
116
+ pyshp==2.3.1
117
+ python-dateutil==2.9.0.post0
118
+ python-dotenv==1.0.1
119
+ python-json-logger==2.0.7
120
+ python-multipart==0.0.9
121
+ rasterio==1.3.10
122
+ realtime==1.0.6
123
+ referencing==0.35.1
124
+ requests==2.32.2
125
+ rfc3339-validator==0.1.4
126
+ rfc3986-validator==0.1.1
127
+ rich==13.7.1
128
+ rpds-py==0.18.1
129
+ scipy==1.13.1
130
+ seaborn==0.13.2
131
+ Send2Trash==1.8.3
132
+ setuptools==69.5.1
133
+ shapely==2.0.4
134
+ shellingham==1.5.4
135
+ six==1.16.0
136
+ smmap==5.0.1
137
+ sniffio==1.3.1
138
+ snuggs==1.4.7
139
+ soupsieve==2.5
140
+ stack-data==0.6.3
141
+ starlette==0.37.2
142
+ storage3==0.7.7
143
+ streamlit==1.35.0
144
+ StrEnum==0.4.15
145
+ supabase==2.5.3
146
+ supafunc==0.4.7
147
+ sympy==1.12
148
+ tbb==2021.12.0
149
+ tenacity==8.3.0
150
+ terminado==0.18.1
151
+ thop==0.1.1.post2209072238
152
+ tinycss2==1.3.0
153
+ toml==0.10.2
154
+ tomli==2.0.1
155
+ toolz==0.12.1
156
+ torch==2.3.0
157
+ torchvision==0.18.0
158
+ tornado==6.4
159
+ tqdm==4.66.4
160
+ traitlets==5.14.3
161
+ typer==0.12.3
162
+ types-python-dateutil==2.9.0.20240316
163
+ typing_extensions==4.12.2
164
+ tzdata==2024.1
165
+ ujson==5.10.0
166
+ ultralytics==8.2.23
167
+ uri-template==1.3.0
168
+ urllib3==2.2.1
169
+ uvicorn==0.30.1
170
+ watchdog==4.0.1
171
+ watchfiles==0.22.0
172
+ wcwidth==0.2.13
173
+ webcolors==1.13
174
+ webencodings==0.5.1
175
+ websocket-client==1.8.0
176
+ websockets==12.0
177
+ wheel==0.43.0