Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature : Yolo v8 Seg Integration #31

Merged
merged 59 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
63d1006
Refactor files
kshitijrajsharma Oct 28, 2024
86a122e
Fix workflow
kshitijrajsharma Oct 28, 2024
177e98d
Import yolo format fix
kshitijrajsharma Oct 28, 2024
5395489
Add yolov8s v2 model
kshitijrajsharma Oct 28, 2024
2d767b3
reformat
kshitijrajsharma Oct 28, 2024
4b79d48
import yolo v8 y1
kshitijrajsharma Oct 28, 2024
f860988
add format files empty
kshitijrajsharma Oct 28, 2024
f215612
Added relative import
kshitijrajsharma Oct 28, 2024
ba009c4
added yolo format import fuctions
kshitijrajsharma Oct 28, 2024
5e426ed
Added preprocessing script for the yolo v1 and v2
kshitijrajsharma Oct 28, 2024
d0f310b
Add test yolo v2 with training code
kshitijrajsharma Oct 28, 2024
0afa16d
Add specific folder lookup
kshitijrajsharma Oct 28, 2024
fb787b9
Change yolo weights !
kshitijrajsharma Oct 28, 2024
c564ac6
Correct naming convention
kshitijrajsharma Oct 28, 2024
96ab8c8
Change checkpoint to default checkpoint
kshitijrajsharma Oct 28, 2024
bddc47b
Add omdeena v2 seg model
kshitijrajsharma Oct 30, 2024
059625d
Upgrade pytorch
kshitijrajsharma Oct 30, 2024
afac979
add patch version
kshitijrajsharma Oct 30, 2024
b1d5150
Add torch version upgradae
kshitijrajsharma Oct 30, 2024
556b2a5
Update build.yml
kshitijrajsharma Oct 30, 2024
9214b65
Update build.yml
kshitijrajsharma Oct 30, 2024
a3d051f
Update build.yml
kshitijrajsharma Oct 30, 2024
fd89269
Update torch audio version
kshitijrajsharma Oct 30, 2024
57bbf69
Fix torch version
kshitijrajsharma Oct 30, 2024
9e37da6
Resolve dependencies with ultralytics
kshitijrajsharma Oct 30, 2024
d02e97d
check for fix prediction
kshitijrajsharma Oct 30, 2024
b984359
Fix version
kshitijrajsharma Oct 30, 2024
493f701
use model predict for the yolo prediction
kshitijrajsharma Oct 30, 2024
f8cd6ac
Added masks on prediction
kshitijrajsharma Oct 31, 2024
0ae032a
Upgrade python to 3.10
kshitijrajsharma Oct 31, 2024
b4e09f1
python version 3.10.15
kshitijrajsharma Oct 31, 2024
6312326
Added dataset yaml
kshitijrajsharma Oct 31, 2024
392e1be
Added yolo dir on filepath
kshitijrajsharma Oct 31, 2024
f4f19db
fix file path
kshitijrajsharma Oct 31, 2024
96eb492
Add obsolute value
kshitijrajsharma Oct 31, 2024
bb2c18b
restored data path
kshitijrajsharma Oct 31, 2024
e794f0d
fix yaml path
kshitijrajsharma Oct 31, 2024
9f6ad27
Added output path in the yolo file
kshitijrajsharma Oct 31, 2024
0b51a80
Add use cuda if available for the training of yolo
kshitijrajsharma Nov 2, 2024
a9bf7a6
Fix bug on preprocessing for yolo
kshitijrajsharma Nov 2, 2024
dfbe200
Fix Yolo training path for the v2
kshitijrajsharma Nov 2, 2024
9f68269
Update build.yml
kshitijrajsharma Nov 2, 2024
6f43ce7
return checkpoint location after training and use cuda if available f…
kshitijrajsharma Nov 4, 2024
0936a78
Add accuracy metrics as iou default
kshitijrajsharma Nov 5, 2024
4060acf
Added test comment
kshitijrajsharma Nov 7, 2024
0a5f7c0
Enable run feedback option on ramp training submodule
kshitijrajsharma Nov 11, 2024
4a1f2a6
add name to training accuracy graph
kshitijrajsharma Nov 11, 2024
4d46b4a
Added log save dir
kshitijrajsharma Nov 12, 2024
721db6d
Add verbose on the training
kshitijrajsharma Nov 12, 2024
a17d939
feat(yololib): adds lib required for packaging yolo code
kshitijrajsharma Nov 15, 2024
0f803ea
bump: version 1.3.0 → 2.0.0
kshitijrajsharma Nov 15, 2024
04856ad
fix(bundlelib): bundles lib itself with new pandas version
kshitijrajsharma Nov 15, 2024
ef933ac
fix(libversion): added version of pandas and other integration
kshitijrajsharma Nov 15, 2024
e3b17ae
fix(version-fix): fixes version of geopandas
kshitijrajsharma Nov 15, 2024
e61bfa9
fix(dependencies): update geopandas version to 1.0.0
kshitijrajsharma Nov 18, 2024
ca04026
fix(dependencies): downgrade geopandas version to 0.14.4
kshitijrajsharma Nov 18, 2024
8b44b1b
refactor(preprocessing): clean up imports and improve code formatting
kshitijrajsharma Nov 18, 2024
1358338
Fix reading issue from latest fiona version input file path
kshitijrajsharma Nov 21, 2024
da44aa2
Fix chart issue on seaborn
kshitijrajsharma Nov 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
- name: Set up Python 3.9
uses: actions/setup-python@v1
with:
python-version: 3.8
python-version: 3.9.20

- name: Remove solaris
run: sudo rm -rf ./docker ./weights
Expand Down Expand Up @@ -46,19 +46,20 @@ jobs:
- name: Install tensorflow
run: pip install tensorflow==2.9.2

- name : Install pytorch and ultralytics
run: pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 torchaudio==0.12.1 --extra-index-url https://download.pytorch.org/whl/cu113 ultralytics==8.1.6
- name: Install pytorch and ultralytics
run: pip install torch==2.5.1 torchvision==0.20.1 torchaudio==2.5.1 --extra-index-url https://download.pytorch.org/whl/cu113 ultralytics==8.3.26

- name: Install fair utilities
run: pip install -e .

- name: Run test ramp
run: |
pip uninstall -y gdal
pip install numpy
pip install numpy==1.26.4
pip install GDAL==$(gdal-config --version) --global-option=build_ext --global-option="-I/usr/include/gdal"
python test_app.py
python test_ramp.py

- name : Run test yolo
run : |
python test_yolo.py
- name: Run test yolo
run: |
python test_yolo_v1.py
python test_yolo_v2.py
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
## v3.0.0 (2024-11-15)

### Feat

- **yololib**: adds lib required for packaging yolo code

### Fix

- **bundlelib**: bundles lib itself with new pandas version

## v2.0.0 (2024-11-15)

### Feat

- **yololib**: adds lib required for packaging yolo code
- replace FastSAM with YOLO including training
- add FastSAM inference

### Fix

- **postprocessing/utils**: resolve OAM-x-y-z.mask.tif
- **predict**: support both .png and .tif in inference

## v1.3.0 (2024-10-04)

### Feat
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Initially lib was developed during Open AI Challenge with [Omdeena](https://omde

Installing all libraries could be pain so we suggest you to use docker , If you like to do it bare , You can follow `.github/build.yml`

<!-- comment -->

Clone repo

Expand Down
7 changes: 4 additions & 3 deletions hot_fair_utilities/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .georeferencing import georeference
from .inference import predict, evaluate
from .inference import evaluate, predict
from .postprocessing import polygonize, vectorize
from .preprocessing import preprocess
from .training import train
from .preprocessing import preprocess, yolo_v8_v1

# from .training import ramp, yolo_v8_v1
from .utils import bbox2tiles, tms2img
6 changes: 3 additions & 3 deletions hot_fair_utilities/georeferencing.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from .utils import get_bounding_box


def georeference(input_path: str, output_path: str, is_mask=False) -> None:
def georeference(input_path: str, output_path: str, is_mask=False,epsg=3857) -> None:
"""Perform georeferencing and remove the fourth band from images (if any).

CRS of the georeferenced images will be EPSG:3857 ('WGS 84 / Pseudo-Mercator').
Expand All @@ -38,7 +38,7 @@ def georeference(input_path: str, output_path: str, is_mask=False) -> None:
out_file = f"{output_path}/{filename}.tif"

# Get bounding box in EPSG:3857
x_min, y_min, x_max, y_max = get_bounding_box(filename)
x_min, y_min, x_max, y_max = get_bounding_box(filename,epsg=epsg)

# Use one band for masks and the first three bands for images
bands = [1] if is_mask else [1, 2, 3]
Expand All @@ -51,7 +51,7 @@ def georeference(input_path: str, output_path: str, is_mask=False) -> None:
format="GTiff",
bandList=bands,
outputBounds=[x_min, y_max, x_max, y_min],
outputSRS="EPSG:3857",
outputSRS=f"EPSG:{epsg}",
)
# Close dataset
_ = None
17 changes: 13 additions & 4 deletions hot_fair_utilities/inference/evaluate.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
# Patched from ramp-code.scripts.calculate_accuracy.iou created for ramp project by [email protected]

# Standard library imports
from pathlib import Path

# Third party imports
import geopandas as gpd

from ramp.utils.eval_utils import get_iou_accuracy_metrics
try:
# Third party imports
from ramp.utils.eval_utils import get_iou_accuracy_metrics
except ImportError:
print("Ramp eval metrics are not available, Possibly ramp is not installed")


def evaluate(test_path, truth_path, filter_area_m2=None, iou_threshold=0.5, verbose=False):
def evaluate(
test_path, truth_path, filter_area_m2=None, iou_threshold=0.5, verbose=False
):
"""
Calculate precision/recall/F1-score based on intersection-over-union accuracy evaluation protocol defined by RAMP.

Expand All @@ -29,9 +38,9 @@ def evaluate(test_path, truth_path, filter_area_m2=None, iou_threshold=0.5, verb
truth_df, test_df = gpd.read_file(str(truth_path)), gpd.read_file(str(test_path))
metrics = get_iou_accuracy_metrics(test_df, truth_df, filter_area_m2, iou_threshold)

n_detections = metrics['n_detections']
n_detections = metrics["n_detections"]
n_truth = metrics["n_truth"]
n_truepos = metrics['true_pos']
n_truepos = metrics["true_pos"]
n_falsepos = n_detections - n_truepos
n_falseneg = n_truth - n_truepos
agg_precision = n_truepos / n_detections
Expand Down
34 changes: 24 additions & 10 deletions hot_fair_utilities/inference/predict.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@

from ..georeferencing import georeference
from ..utils import remove_files
from .utils import open_images, save_mask, initialize_model
from .utils import initialize_model, open_images, save_mask

BATCH_SIZE = 8
IMAGE_SIZE = 256
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"


def predict(
checkpoint_path: str, input_path: str, prediction_path: str, confidence: float = 0.5, remove_images=True
checkpoint_path: str,
input_path: str,
prediction_path: str,
confidence: float = 0.5,
remove_images=True,
) -> None:
"""Predict building footprints for aerial images given a model checkpoint.

Expand All @@ -46,7 +50,7 @@ def predict(
"""
start = time.time()
print(f"Using : {checkpoint_path}")
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = initialize_model(checkpoint_path, device=device)
print(f"It took {round(time.time()-start)} sec to load model")
start = time.time()
Expand Down Expand Up @@ -74,14 +78,24 @@ def predict(
)
elif isinstance(model, YOLO):
for idx in range(0, len(image_paths), BATCH_SIZE):
batch = image_paths[idx:idx + BATCH_SIZE]
for i, r in enumerate(model(batch, stream=True, conf=confidence, verbose=False)):
if r.masks is None:
preds = np.zeros((IMAGE_SIZE, IMAGE_SIZE,), dtype=np.float32)
batch = image_paths[idx : idx + BATCH_SIZE]
for i, r in enumerate(
model.predict(batch, conf=confidence, imgsz=IMAGE_SIZE, verbose=False)
):
if hasattr(r, "masks") and r.masks is not None:
preds = (
r.masks.data.max(dim=0)[0].detach().cpu().numpy()
) # Combine masks and convert to numpy

else:
preds = r.masks.data.max(dim=0)[0] # dim=0 means to take only footprint
preds = torch.where(preds > confidence, torch.tensor(1), torch.tensor(0))
preds = preds.detach().cpu().numpy()
preds = np.zeros(
(
IMAGE_SIZE,
IMAGE_SIZE,
),
dtype=np.float32,
) # Default if no masks

save_mask(preds, str(f"{prediction_path}/{Path(batch[i]).stem}.png"))
else:
raise RuntimeError("Loaded model is not supported")
Expand Down
4 changes: 3 additions & 1 deletion hot_fair_utilities/postprocessing/building_footprint.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Standard library imports
import collections

# Third party imports
from geopandas import GeoSeries
from shapely.geometry import MultiPolygon, Polygon
from shapely.ops import unary_union
Expand Down Expand Up @@ -75,4 +77,4 @@ def extract(self, tile, mask):
self.features.append(feature)

def save(self, out):
GeoSeries(self.features).set_crs(CRS).to_file(out)
GeoSeries(self.features).set_crs(CRS).to_file(out, driver="GeoJSON")
5 changes: 3 additions & 2 deletions hot_fair_utilities/postprocessing/merge_polygons.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from shapely.geometry import MultiPolygon, Polygon
from shapely.validation import make_valid
from tqdm import tqdm
import os

from .utils import UndirectedGraph, make_index, project, union

Expand All @@ -19,7 +20,7 @@ def merge_polygons(polygons_path, new_polygons_path, distance_threshold):
new_polygons_path: Path to GeoJSON file where the merged polygons will be saved
distance_threshold: Minimum distance to define adjacent polygons, in meters
"""
gdf = read_file(polygons_path)
gdf = read_file(os.path.relpath(polygons_path))
shapes = list(gdf["geometry"])

graph = UndirectedGraph()
Expand Down Expand Up @@ -71,4 +72,4 @@ def unbuffered(shape):
features.append(feature)

gs = GeoSeries(features).set_crs(SOURCE_CRS)
gs.simplify(TOLERANCE).to_file(new_polygons_path)
gs.simplify(TOLERANCE).to_file(new_polygons_path, driver="GeoJSON")
11 changes: 6 additions & 5 deletions hot_fair_utilities/postprocessing/vectorize.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
AREA_THRESHOLD = 5


def vectorize(input_path: str, output_path: str , tolerance: float = 0.5, area_threshold: float = 5) -> None:
def vectorize(
input_path: str, output_path: str, tolerance: float = 0.5, area_threshold: float = 5
) -> None:
"""Polygonize raster tiles from the input path.

Note that as input, we are expecting GeoTIF images with EPSG:3857 as
Expand Down Expand Up @@ -52,15 +54,14 @@ def vectorize(input_path: str, output_path: str , tolerance: float = 0.5, area_t
polygons = [
Polygon(poly.exterior.coords)
for poly in polygons
if poly.area != max_area
and poly.area / median_area > area_threshold
if poly.area != max_area and poly.area / median_area > area_threshold
]

gs = gpd.GeoSeries(polygons, crs=kwargs["crs"]).simplify(tolerance)
gs = remove_overlapping_polygons(gs)
if gs.empty:
raise ValueError("No Features Found")
gs.to_crs("EPSG:4326").to_file(output_path)
gs.to_crs("EPSG:4326").to_file(output_path, driver="GeoJSON")


def remove_overlapping_polygons(gs: gpd.GeoSeries) -> gpd.GeoSeries:
Expand All @@ -79,4 +80,4 @@ def remove_overlapping_polygons(gs: gpd.GeoSeries) -> gpd.GeoSeries:
else:
to_remove.add(j)

return gs.drop(list(to_remove))
return gs.drop(list(to_remove))
1 change: 1 addition & 0 deletions hot_fair_utilities/preprocessing/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .preprocess import preprocess
from .yolo_v8_v1 import yolo_format
25 changes: 18 additions & 7 deletions hot_fair_utilities/preprocessing/clip_labels.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from glob import glob
from pathlib import Path

# Third party imports
# Third-party imports
import geopandas
from osgeo import gdal
Expand All @@ -13,7 +14,12 @@


def clip_labels(
input_path: str, output_path: str, rasterize=False, rasterize_options=None
input_path: str,
output_path: str,
rasterize=False,
rasterize_options=None,
all_geojson_file=None,
epsg=3857,
) -> None:
"""Clip and rasterize the GeoJSON labels for each aerial image.

Expand Down Expand Up @@ -71,24 +77,29 @@ def clip_labels(
glob(f"{input_path}/*.png"), desc=f"Clipping labels for {Path(input_path).stem}"
):
filename = Path(path).stem
geojson_file_all_labels = f"{output_path}/labels_epsg3857.geojson"
if all_geojson_file:
geojson_file_all_labels = all_geojson_file
else:
geojson_file_all_labels = f"{output_path}/labels_epsg3857.geojson"
clipped_geojson_file = f"{output_geojson_path}/{filename}.geojson"

# Bounding box as a tuple
x_min, y_min, x_max, y_max = get_bounding_box(filename)
x_min, y_min, x_max, y_max = get_bounding_box(filename, epsg=epsg)
# Bounding box as a polygon
bounding_box_polygon = box(x_min, y_min, x_max, y_max)

# Read all labels into a GeoDataFrame, clip it and
# write to GeoJSON
gdf_all_labels = geopandas.read_file(geojson_file_all_labels)
gdf_all_labels = geopandas.read_file(os.path.relpath(geojson_file_all_labels))
gdf_clipped = gdf_all_labels.clip(bounding_box_polygon)
if len(gdf_clipped) > 0:
gdf_clipped.to_file(clipped_geojson_file)
gdf_clipped.to_file(clipped_geojson_file, driver="GeoJSON")
else:
schema = {"geometry": "Polygon", "properties": {"id": "int"}}
crs = "EPSG:3857"
gdf_clipped.to_file(clipped_geojson_file, schema=schema, crs=crs)
crs = f"EPSG:{epsg}"
gdf_clipped.to_file(
clipped_geojson_file, schema=schema, crs=crs, driver="GeoJSON"
)

# Rasterizing
if rasterize:
Expand Down
7 changes: 4 additions & 3 deletions hot_fair_utilities/preprocessing/fix_labels.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Third party imports
import geopandas
from shapely.validation import explain_validity, make_valid

import os

def remove_self_intersection(row):
"""Fix self-intersections in the polygons.
Expand Down Expand Up @@ -29,8 +29,9 @@ def fix_labels(input_path: str, output_path: str) -> None:
input_path: Path to the GeoJSON file where the input data are stored.
output_path: Path to the GeoJSON file where the output data will go.
"""
gdf = geopandas.read_file(input_path)
gdf = geopandas.read_file(os.path.relpath(input_path))
# print(gdf)
if gdf.empty:
raise ValueError("Error: gdf is empty, No Labels found : Check your labels")
gdf["geometry"] = gdf.apply(remove_self_intersection, axis=1)
gdf.to_file(output_path)
gdf.to_file(output_path, driver="GeoJSON")
4 changes: 2 additions & 2 deletions hot_fair_utilities/preprocessing/multimasks_from_polygons.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Standard library imports
from pathlib import Path

import os
# Third party imports
import geopandas as gpd
import rasterio as rio
Expand Down Expand Up @@ -88,7 +88,7 @@ def multimasks_from_polygons(
# workaround for bug in solaris
mask_shape, mask_transform = get_rasterio_shape_and_transform(chip_path)

gdf = gpd.read_file(json_path)
gdf = gpd.read_file(os.path.relpath(json_path))

# remove empty and null geometries
gdf = gdf[~gdf["geometry"].isna()]
Expand Down
Loading
Loading