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

Fix issue #8807 - Save Prediction Results to .csv #8967

Open
wants to merge 29 commits into
base: main
Choose a base branch
from

Conversation

stavros-p
Copy link

@stavros-p stavros-p commented Mar 15, 2024

  • The following attributes will be saved in a .csv file
    • Image File Path
    • ID Class
    • Confidence
    • x1: x coordinate of top left point of bb
    • y1: y coordinate of top left point of bb
    • x2: x coordinate of bottom right point of bb
    • y2: y coordinate of bottom right point of bb
    • width: Width of bb
    • height: Height of bb

I have read the CLA Document and I hereby sign the CLA

🛠️ PR Summary

Made with ❤️ by Ultralytics Actions

🌟 Summary

Enhanced detection results storage by adding CSV export functionality.

📊 Key Changes

  • Added import of pandas as pd for handling CSV file operations.
  • Introduced a new method save_detect_csv(csv_file) in results.py to save detection predictions into a CSV file.
    • This method supports both bounding box and oriented bounding box formats.
    • It extracts and saves details like image path, class ID, confidence scores, and coordinates (x1, y1, x2, y2) along with center (x_center, y_center), width, and height of detected objects.

🎯 Purpose & Impact

  • Purpose: To provide users with an easy way to export detection predictions into a widely used and accessible format (CSV), facilitating analysis and integration with other data processing tools.
  • Impact:
    • Enhances the utility of the software by enabling better data management and sharing capabilities.
    • Users can effortlessly integrate detection results into their workflows, which could range from further analysis in data science projects to integration with external systems requiring input in a tabular format.

🚀 This addition opens up new possibilities for users to work with and analyze their detection results, making the overall tool more versatile and user-friendly.

stavros-p and others added 2 commits March 15, 2024 12:54
- The following attributes will be saved in a .csv file
	- Image File Path
	- ID Class
	- Confidence
	- x1: x coordinate of top left point of bb
	- y1: y coordinate of top left point of bb
	- x2: x coordinate of bottom right point of bb
	- y2: y coordinate of bottom right point of bb
	- width: Width of bb
	- height: Height of bb
Copy link

codecov bot commented Mar 15, 2024

Codecov Report

Attention: Patch coverage is 95.12195% with 2 lines in your changes are missing coverage. Please review.

Project coverage is 76.13%. Comparing base (d608565) to head (f333873).

Files Patch % Lines
ultralytics/engine/results.py 96.29% 1 Missing ⚠️
ultralytics/utils/ops.py 92.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8967      +/-   ##
==========================================
+ Coverage   75.99%   76.13%   +0.13%     
==========================================
  Files         121      121              
  Lines       15332    15373      +41     
==========================================
+ Hits        11652    11704      +52     
+ Misses       3680     3669      -11     
Flag Coverage Δ
Benchmarks 35.96% <14.63%> (-0.06%) ⬇️
GPU 37.86% <14.63%> (-0.08%) ⬇️
Tests 71.44% <95.12%> (+0.14%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@stavros-p
Copy link
Author

stavros-p commented Mar 15, 2024

Introduction

This code snippet demonstrates how to perform object detection on a list of image files using the YOLO model from the Ultralytics library.

Usage

Assuming you have a list with image file paths, you can utilize the provided code snippet as follows:

from ultralytics import YOLO
import os 

# Specify the path to the dataset containing images to be predicted
dataset_path = "folder_path\with\images\to\be\predicted"

# Generate a list of absolute image paths within the dataset directory
images_path_list = []
for dirpath, _, filenames in os.walk(dataset_path):
    for f in filenames:
        images_path_list.append(os.path.abspath(os.path.join(dirpath, f)))

# Load the YOLO model (adjust the model path as needed)
model = YOLO('yolov8n.pt')

# Perform object detection on the provided images
results = model(images_path_list)

# Save detection results as CSV files
for idx, result in enumerate(results):
    result.save_detect_csv(f"output/predictions_{idx}.csv")

@glenn-jocher
Copy link
Member

@stavros-p thanks for the PR!

@Burhan-Q and myself have been thinking of introducing improved offical support for Pandas -> JSON, TXT, CSV etc. outputs, but this would need to incorporate multiple output formats and multiple tasks (i.e. detect, segment, pose, classify, obb) in order to be meaningful.

@stavros-p
Copy link
Author

@glenn-jocher thanks for the comment!

Yes, initially I thought the same.

For detection results this is quite straightforward.

What do you think for the other (segment, pose, classify, obb, pose)? How and what you would like to save on JSON, TXT, CSV?

I would be more than happy to contribute on that :)

@glenn-jocher
Copy link
Member

Hey @stavros-p! 😊

Great enthusiasm! For other tasks like segment, pose, classify, obb, and pose, the approach would slightly vary based on the output details each task provides. For example, segmentation might include saving mask details, pose could save keypoints, classify saves class probabilities, and so forth.

A generic format could look like this for each task:

  • segment: adding mask pixel values or a reference to saved mask images.
  • pose: keypoint coordinates and confidence scores.
  • classify: class IDs and their respective probabilities.
  • obb and regular detect: oriented bounding box coordinates or standard bounding box coordinates along with class IDs and scores.

The key is to maintain a structured and consistent format that's easy to parse. If you'd like, I can share more specific code examples for each. Your contributions are highly appreciated! Let's make YOLOv8 even better together. 🚀

@Burhan-Q
Copy link
Member

@stavros-p the doc that Glenn pointed me to before is here and covers the I/O tools for pandas. I have some ideas on what we could do for the other tasks, would you be okay with me adding commits to your PR? I think the idea is that once we get the information into a DataFrame we don't have to worry about the file type any one person wants to work with, because they'll be able to use the pandas methods to save it out to the file format they want.

@Burhan-Q Burhan-Q added the enhancement New feature or request label Mar 15, 2024
@stavros-p
Copy link
Author

Hey @Burhan-Q . I am aware of this API :) Yes please free to add commits in this PR. I'd be more than happy to collab with you on that.

@Burhan-Q
Copy link
Member

@stavros-p excellent! I will push some changes later today (it'll be a good bit different looking) and we can discuss the what and whys of what changes. I'm of course open to input/feedback if there's anything you feel should be different, so please don't hesitate to share your thoughts.

@Burhan-Q
Copy link
Member

@stavros-p please take a look at the changes I've added when you have the opportunity. This will be compatible with all task types and uses a pandas.MultiIndex so that it's possible to generate a single CSV file with all images + detections (looping over the results) OR single CSV files for each image (see the docstring examples). Let me know your thoughts and/or if you run into any issues with these changes/additions.

We will also need to update the documentation to reflect these updates/changes once we've ironed out all of the aspects we want to keep. Additionally, I want to update tojson() to use the contents of self.results_dict as well. This was part of the discussion I had with @glenn-jocher but I have to double check that changes won't interfere with any other processes or tests.

@Burhan-Q Burhan-Q linked an issue Mar 18, 2024 that may be closed by this pull request
2 tasks
"top5": r.probs.top5 if probs else None,
"top5conf": r.probs.top5conf if probs else None,
}
for n in range(max(len(lbl_idx or []), 1))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This portion of the code presents a potential ambiguity:

Suggested change
for n in range(max(len(lbl_idx or []), 1))
if lbl_idx is None:
length = 1
else:
length = max(len(lbl_idx), 1)
for n in range(length):

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair. I think I will abstain from making the call as to which version to use and let @glenn-jocher make the final call for this portion of the code (once we wrap up everything else).

@Burhan-Q
Copy link
Member

@stavros-p I appreciate your input so far. 🙌 Overall what are your thoughts on how this operates? Do you feel that we should move on to updating the docs and adding tests, or did you have anything else you wished to discuss, review, or recommend?

@stavros-p
Copy link
Author

stavros-p commented Mar 21, 2024

@stavros-p I appreciate your input so far. 🙌 Overall what are your thoughts on how this operates? Do you feel that we should move on to updating the docs and adding tests, or did you have anything else you wished to discuss, review, or recommend?

Hey @Burhan-Q ! It's my pleasure. I made a commit few hours ago. I've tested the function for all tasks and for multiple cases and it seems to work as expected :) Let me know if you have something else to add there. If not, we could proceed on updating the docs

@Burhan-Q
Copy link
Member

@stavros-p I did see that, looks like a solid middle ground choice! 😆 Glad to hear it worked for you as well 🙌

I think we're good to proceed to docs and tests. Want to divide and conquer? I can do either tests or docs, but I have less experience with writing tests. If you have no preference I'd like to get more experience writing tests and then you can tackle the docs. Let me know if that works for you, I'm open to the inverse as well 👍

@Burhan-Q
Copy link
Member

@stavros-p I went ahead and did both docs and tests. This was to make it ready to go as some of the changes may be used along with other features that are in the works. Please feel free to share any input you have. I definitely would like to expand the docs some, but for now I'm going with this MVP

@Burhan-Q Burhan-Q added the TODO High priority items label Mar 22, 2024
@Burhan-Q
Copy link
Member

Burhan-Q commented Mar 25, 2024

  • TODO align all keys with existing attribute names

@Burhan-Q
Copy link
Member

Preview asdict() output format

{'bus.jpg': {'detections': {0: {'class': 5,
                                'conf': 0.8733859062194824,
                                'id': None,
                                'kp-conf': None,
                                'kp-xy': None,
                                'kp-xyn': None,
                                'mask-xy': None,
                                'mask-xyn': None,
                                'name': 'bus',
                                'top1': None,
                                'top1conf': None,
                                'top5': None,
                                'top5conf': None,
                                'xywh': [413.92755126953125,
                                         494.0536193847656,
                                         782.09912109375,
                                         525.5584106445312],
                                'xywhn': [0.5110216736793518,
                                          0.4574570655822754,
                                          0.9655544757843018,
                                          0.48662814497947693],
                                'xywhr': None,
                                'xyxy': [22.87796401977539,
                                         231.27442932128906,
                                         804.9771118164062,
                                         756.8328247070312],
                                'xyxyn': [0.028244400396943092,
                                          0.21414299309253693,
                                          0.9937989115715027,
                                          0.700771152973175],
                                'xyxyxyxy': None,
                                'xyxyxyxyn': None},
                            1: {'class': 0,
                                'conf': 0.8657410144805908,
                                'id': None,
                                'kp-conf': None,
                                'kp-xy': None,
                                'kp-xyn': None,
                                'mask-xy': None,
                                'mask-xyn': None,
                                'name': 'person',
                                'top1': None,
                                'top1conf': None,
                                'top5': None,
                                'top5conf': None,
                                'xywh': [146.9514923095703,
                                         650.6341552734375,
                                         196.79891967773438,
                                         504.1564636230469],
                                'xywhn': [0.18142159283161163,
                                          0.6024390459060669,
                                          0.24296163022518158,
                                          0.46681153774261475],
                                'xywhr': None,
                                'xyxy': [48.55202865600586,
                                         398.5559387207031,
                                         245.3509521484375,
                                         902.71240234375],
                                'xyxyn': [0.059940777719020844,
                                          0.3690332770347595,
                                          0.3029024004936218,
                                          0.8358448147773743],
                                'xyxyxyxy': None,
                                'xyxyxyxyn': None},
                            2: {'class': 0,
                                'conf': 0.8528025150299072,
                                'id': None,
                                'kp-conf': None,
                                'kp-xy': None,
                                'kp-xyn': None,
                                'mask-xy': None,
                                'mask-xyn': None,
                                'name': 'person',
                                'top1': None,
                                'top1conf': None,
                                'top5': None,
                                'top5conf': None,
                                'xywh': [739.5939331054688,
                                         634.6122436523438,
                                         140.2515869140625,
                                         484.8529968261719],
                                'xywhn': [0.9130789041519165,
                                          0.5876039266586304,
                                          0.17315010726451874,
                                          0.44893795251846313],
                                'xywhr': None,
                                'xyxy': [669.4681396484375,
                                         392.1857604980469,
                                         809.7197265625,
                                         877.0387573242188],
                                'xyxyn': [0.8265038728713989,
                                          0.3631349503993988,
                                          0.9996539950370789,
                                          0.8120729327201843],
                                'xyxyxyxy': None,
                                'xyxyxyxyn': None},
                            3: {'class': 0,
                                'conf': 0.8252988457679749,
                                'id': None,
                                'kp-conf': None,
                                'kp-xy': None,
                                'kp-xyn': None,
                                'mask-xy': None,
                                'mask-xyn': None,
                                'name': 'person',
                                'top1': None,
                                'top1conf': None,
                                'top5': None,
                                'top5conf': None,
                                'xywh': [283.250732421875,
                                         631.670166015625,
                                         123.45741271972656,
                                         451.74078369140625],
                                'xywhn': [0.3496922552585602,
                                          0.5848797559738159,
                                          0.15241655707359314,
                                          0.4182785153388977],
                                'xywhr': None,
                                'xyxy': [221.5220184326172,
                                         405.79974365234375,
                                         344.97943115234375,
                                         857.54052734375],
                                'xyxyn': [0.2734839618206024,
                                          0.37574049830436707,
                                          0.42590051889419556,
                                          0.7940189838409424],
                                'xyxyxyxy': None,
                                'xyxyxyxyn': None},
                            4: {'class': 0,
                                'conf': 0.2610270082950592,
                                'id': None,
                                'kp-conf': None,
                                'kp-xy': None,
                                'kp-xyn': None,
                                'mask-xy': None,
                                'mask-xyn': None,
                                'name': 'person',
                                'top1': None,
                                'top1conf': None,
                                'top5': None,
                                'top5conf': None,
                                'xywh': [31.504772186279297,
                                         711.985107421875,
                                         63.009544372558594,
                                         322.91729736328125],
                                'xywhn': [0.03889477998018265,
                                          0.0777895599603653,
                                          0.2989974915981293],
                                'xywhr': None,
                                'xyxy': [0.0,
                                         550.5264892578125,
                                         63.009544372558594,
                                         873.4437866210938],
                                'xyxyn': [0.0,
                                          0.5097467303276062,
                                          0.0777895599603653,
                                          0.8087442517280579],
                                'xyxyxyxy': None,
                                'xyxyxyxyn': None},
                            5: {'class': 11,
                                'conf': 0.25512558221817017,
                                'id': None,
                                'kp-conf': None,
                                'kp-xy': None,
                                'kp-xyn': None,
                                'mask-xy': None,
                                'mask-xyn': None,
                                'name': 'stop sign',
                                'top1': None,
                                'top1conf': None,
                                'top5': None,
                                'top5conf': None,
                                'xywh': [16.309720993041992,
                                         289.6671447753906,
                                         32.50263595581055,
                                         70.41302490234375],
                                'xywhn': [0.020135458558797836,
                                          0.2682103216648102,
                                          0.04012671113014221,
                                          0.06519724428653717],
                                'xywhr': None,
                                'xyxy': [0.058402419090270996,
                                         254.46063232421875,
                                         32.561038970947266,
                                         324.8736572265625],
                                'xyxyn': [7.210174953797832e-05,
                                          0.235611692070961,
                                          0.04019881412386894,
                                          0.30080893635749817],
                                'xyxyxyxy': None,
                                'xyxyxyxyn': None}}}}

CSV output

image detections name class conf id xyxy xyxyn xywh xywhn mask-xy mask-xyn kp-xy kp-xyn kp-conf xywhr xyxyxyxy xyxyxyxyn top1 top1conf top5 top5conf
bus.jpg 0 bus 5 0.8733859062   [22.87796401977539, 231.27442932128906, 804.9771118164062, 756.8328247070312] [0.028244400396943092, 0.21414299309253693, 0.9937989115715027, 0.700771152973175] [413.92755126953125, 494.0536193847656, 782.09912109375, 525.5584106445312] [0.5110216736793518, 0.4574570655822754, 0.9655544757843018, 0.48662814497947693]                        
bus.jpg 1 person 0 0.8657410145   [48.55202865600586, 398.5559387207031, 245.3509521484375, 902.71240234375] [0.059940777719020844, 0.3690332770347595, 0.3029024004936218, 0.8358448147773743] [146.9514923095703, 650.6341552734375, 196.79891967773438, 504.1564636230469] [0.18142159283161163, 0.6024390459060669, 0.24296163022518158, 0.46681153774261475]                        
bus.jpg 2 person 0 0.852802515   [669.4681396484375, 392.1857604980469, 809.7197265625, 877.0387573242188] [0.8265038728713989, 0.3631349503993988, 0.9996539950370789, 0.8120729327201843] [739.5939331054688, 634.6122436523438, 140.2515869140625, 484.8529968261719] [0.9130789041519165, 0.5876039266586304, 0.17315010726451874, 0.44893795251846313]                        
bus.jpg 3 person 0 0.8252988458   [221.5220184326172, 405.79974365234375, 344.97943115234375, 857.54052734375] [0.2734839618206024, 0.37574049830436707, 0.42590051889419556, 0.7940189838409424] [283.250732421875, 631.670166015625, 123.45741271972656, 451.74078369140625] [0.3496922552585602, 0.5848797559738159, 0.15241655707359314, 0.4182785153388977]                        
bus.jpg 4 person 0 0.2610270083   [0.0, 550.5264892578125, 63.009544372558594, 873.4437866210938] [0.0, 0.5097467303276062, 0.0777895599603653, 0.8087442517280579] [31.504772186279297, 711.985107421875, 63.009544372558594, 322.91729736328125] [0.03889477998018265, 0.659245491027832, 0.0777895599603653, 0.2989974915981293]                        
bus.jpg 5 stop sign 11 0.2551255822   [0.058402419090270996, 254.46063232421875, 32.561038970947266, 324.8736572265625] [7.210174953797832e-05, 0.235611692070961, 0.04019881412386894, 0.30080893635749817] [16.309720993041992, 289.6671447753906, 32.50263595581055, 70.41302490234375] [0.020135458558797836, 0.2682103216648102, 0.04012671113014221, 0.06519724428653717]                        

NOTE: DataFrame format would be similar, except the image filename would not be repeated in the first column and empty columns could be dropped (if desired) using the method .dropna(axis=1) on the DataFrame

@stavros-p
Copy link
Author

Hey @Burhan-Q ,

Sorry for my late response 🙏 I had to be off for some days. I saw the great work you did there both for tests and docs. Please let me know what do you think about the current status of the PR.

@Burhan-Q
Copy link
Member

@stavros-p not to worry. I think the PR is in pretty good shape, but I'm seeing other changes that might supersede the changes made here. I think for the time being, we leave this in place and see if it's accepted. Otherwise I'll keep an eye open for other opportunities to include this functionality.

@glenn-jocher glenn-jocher removed the TODO High priority items label Apr 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: Needs Review
Development

Successfully merging this pull request may close these issues.

Request for YOLO v8 Detected class in a excel/csv
4 participants