Issue categories
project = client.get_project("<PROJECT_ID>")
# Create
category = project.create_issue_category(
name="Quality",
description="Quality-related issues",
)
# List all
categories = project.get_issue_categories()
# Update
category = category.update(name="Renamed", description="New description")
# Delete
category.delete()
Issues
from labelbox import IssueStatus, ImageIssuePosition, VideoIssuePosition, VideoFrameRange
# Create -- minimal
issue = project.create_issue(
content="General quality issue",
data_row_id="<DATA_ROW_ID>",
)
# Create -- with label, category, and position
issue = project.create_issue(
content="Bounding box is misaligned",
data_row_id="<DATA_ROW_ID>",
label_id="<LABEL_ID>",
category_id=category.id,
position=ImageIssuePosition(x=100, y=200),
)
# Also accepts DataRow/Label/IssueCategory objects directly
issue = project.create_issue(
content="Using objects instead of IDs",
data_row_id=data_row, # DataRow object
label_id=label, # Label object
category_id=category, # IssueCategory object
)
# Fetch with filters (returns PaginatedCollection)
for issue in project.get_issues(status=IssueStatus.OPEN):
print(issue.friendly_id, issue.content)
# Other available filters
project.get_issues(
data_row_id="<DATA_ROW_ID>",
category_id="<CATEGORY_ID>",
created_by_ids=["<USER_ID>"],
content="search text",
)
# Fetch single issue
issue = project.get_issue("<ISSUE_ID>")
# Update (only provided fields are changed)
issue = issue.update(content="Updated text")
issue = issue.update(category_id="<NEW_CATEGORY_ID>")
# Resolve / Reopen
issue = issue.resolve()
issue = issue.reopen()
# Lazy-loaded related objects (each makes an API call)
data_row = issue.data_row() # Optional[DataRow]
label = issue.label() # Optional[Label]
category = issue.category() # Optional[IssueCategory]
# Delete single
issue.delete()
# Bulk delete
project.delete_issues(["<ISSUE_ID_1>", "<ISSUE_ID_2>"])
Comments
# Create
comment = issue.create_comment(content="I agree, this needs fixing")
# List all comments on an issue
comments = issue.comments()
# Update
comment = comment.update(content="Revised comment")
# Delete
comment.delete()
Video position variants
# Single frame
VideoIssuePosition(frames=[
VideoFrameRange(start=5, end=5, x=100, y=200)
])
# Contiguous range (stationary pin)
VideoIssuePosition(frames=[
VideoFrameRange(start=5, end=11, x=450, y=300)
])
# Contiguous range (moving pin)
VideoIssuePosition(frames=[
VideoFrameRange(start=5, end=11, x=450, y=300, end_x=500, end_y=350)
])
# Multiple separated ranges
VideoIssuePosition(frames=[
VideoFrameRange(start=5, end=11, x=450, y=300),
VideoFrameRange(start=20, end=25, x=100, y=100),
])
Addendum: Position formats for different data types
# PDF (%)
PdfIssuePosition(x=0.5, y=0.75, page=2)
# Text (Char range)
TextIssuePosition(text_block_id="b1", start_char_index=10, end_char_index=25)
# Video (Multiple frames/paths)
VideoIssuePosition(frames=[
VideoFrameRange(start=5, end=11, x=450, y=300, end_x=500, end_y=350),
VideoFrameRange(start=20, end=25, x=100, y=100)
])