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

TEST 874 #875

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions .github/workflows/Conda-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Conda package

on:
push:
branches: [ main ]
branches: [main]
pull_request:
branches: [ main ]
branches: [main]

jobs:
ci:
Expand All @@ -19,7 +19,7 @@ jobs:
- "3.10"

os:
- "ubuntu-latest"
- "ubuntu-22.04"

runs-on: "${{ matrix.os }}"

Expand All @@ -35,7 +35,7 @@ jobs:
- name: Install opencv dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgl1 and libglx-mesa0
sudo apt-get install -y libgl1 and libglx-mesa0

- name: Install Conda environment with Micromamba
uses: mamba-org/setup-micromamba@v1
Expand Down
24 changes: 19 additions & 5 deletions src/deepforest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,8 @@ def predict_tile(self,
thickness=1,
crop_model=None,
crop_transform=None,
crop_augment=False):
crop_augment=False,
verbose=True):
"""For images too large to input into the model, predict_tile cuts the
image into overlapping windows, predicts trees on each window and
reassambles into a single array.
Expand All @@ -498,6 +499,7 @@ def predict_tile(self,
cropModel: a deepforest.model.CropModel object to predict on crops
crop_transform: a torchvision.transforms object to apply to crops
crop_augment: a boolean to apply augmentations to crops
verbose: a boolean to print the number of predictions in overlapping windows
(deprecated) return_plot: return a plot of the image with predictions overlaid
(deprecated) color: color of the bounding box as a tuple of BGR color, e.g. orange annotations is (0, 165, 255)
(deprecated) thickness: thickness of the rectangle border line in px
Expand All @@ -523,13 +525,21 @@ def predict_tile(self,
self.model.eval()
self.model.nms_thresh = self.config["nms_thresh"]

# if more than one GPU present, use only a the first available gpu
# if more than one GPU present, use only the first available gpu
if torch.cuda.device_count() > 1:
# Get available gpus and regenerate trainer
warnings.warn(
"More than one GPU detected. Using only the first GPU for predict_tile.")
self.config["devices"] = 1
self.create_trainer()

# Create a new trainer with appropriate progress bar setting
# Remove any existing progress bar callbacks
callbacks = [
cb for cb in self.trainer.callbacks
if not isinstance(cb, pl.callbacks.ProgressBar)
]

# Create new trainer with updated settings
self.create_trainer(enable_progress_bar=verbose, callbacks=callbacks)

if (raster_path is None) and (image is None):
raise ValueError(
Expand Down Expand Up @@ -560,6 +570,9 @@ def predict_tile(self,
patch_overlap=patch_overlap,
patch_size=patch_size)

if not verbose:
self.create_trainer(enable_progress_bar=False)

batched_results = self.trainer.predict(self, self.predict_dataloader(ds))

# Flatten list from batched prediction
Expand All @@ -573,7 +586,8 @@ def predict_tile(self,
ds.windows,
sigma=sigma,
thresh=thresh,
iou_threshold=iou_threshold)
iou_threshold=iou_threshold,
verbose=verbose)
results["label"] = results.label.apply(
lambda x: self.numeric_to_label_dict[x])
if raster_path:
Expand Down
12 changes: 7 additions & 5 deletions src/deepforest/predict.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def _predict_image_(model,
return df


def mosiac(boxes, windows, sigma=0.5, thresh=0.001, iou_threshold=0.1):
def mosiac(boxes, windows, sigma=0.5, thresh=0.001, iou_threshold=0.1, verbose=True):
# transform the coordinates to original system
for index, _ in enumerate(boxes):
xmin, ymin, xmax, ymax = windows[index].getRect()
Expand All @@ -71,9 +71,10 @@ def mosiac(boxes, windows, sigma=0.5, thresh=0.001, iou_threshold=0.1):
boxes[index].ymax += ymin

predicted_boxes = pd.concat(boxes)
print(
f"{predicted_boxes.shape[0]} predictions in overlapping windows, applying non-max supression"
)
if verbose:
print(
f"{predicted_boxes.shape[0]} predictions in overlapping windows, applying non-max supression"
)
# move prediciton to tensor
boxes = torch.tensor(predicted_boxes[["xmin", "ymin", "xmax", "ymax"]].values,
dtype=torch.float32)
Expand All @@ -98,7 +99,8 @@ def mosiac(boxes, windows, sigma=0.5, thresh=0.001, iou_threshold=0.1):
mosaic_df = pd.DataFrame(image_detections,
columns=["xmin", "ymin", "xmax", "ymax", "label", "score"])

print(f"{mosaic_df.shape[0]} predictions kept after non-max suppression")
if verbose:
print(f"{mosaic_df.shape[0]} predictions kept after non-max suppression")

return mosaic_df

Expand Down
44 changes: 44 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from deepforest import main, get_data, dataset, model
from pytorch_lightning import Trainer
from pytorch_lightning.callbacks import Callback
from pytorch_lightning.callbacks import TQDMProgressBar
from pytorch_lightning.loggers import TensorBoardLogger

from PIL import Image
Expand Down Expand Up @@ -323,6 +324,49 @@ def test_predict_tile_no_mosaic(m, raster_path):
assert prediction[0][1].shape == (300, 300, 3)



def test_predict_tile_verbose(m):
# Ensure TQDMProgressBar is not in the callbacks list
if hasattr(m.trainer, 'callbacks'):
m.trainer.callbacks = [cb for cb in m.trainer.callbacks if not isinstance(cb, TQDMProgressBar)]

m.config["train"]["fast_dev_run"] = False
m.create_trainer()
prediction = m.predict_tile(raster_path=get_data("test_tiled.tif"), patch_size=300, patch_overlap=0, in_memory=True)
assert isinstance(prediction, pd.DataFrame)
assert not prediction.empty


# def test_predict_tile_verbose(m, raster_path, capsys):
# """Test that verbose output can be controlled in predict_tile"""
# # Test with verbose=False
# m.config["train"]["fast_dev_run"] = False
# m.create_trainer()
# prediction = m.predict_tile(
# raster_path=raster_path,
# patch_size=300,
# patch_overlap=0,
# mosaic=True,
# verbose=False
# )

# # Check no output was printed
# captured = capsys.readouterr()
# assert not captured.out.strip()

# # Test with verbose=True
# prediction = m.predict_tile(
# raster_path=raster_path,
# patch_size=300,
# patch_overlap=0,
# mosaic=True,
# verbose=True
# )

# # Check output was printed
# captured = capsys.readouterr()
# assert captured.out.strip()

def test_evaluate(m, tmpdir):
csv_file = get_data("OSBS_029.csv")
root_dir = os.path.dirname(csv_file)
Expand Down