17 KiB
17 KiB
Metadata & Annotations
This reference covers creating and managing annotations in OMERO, including tags, key-value pairs, file attachments, and comments.
Annotation Types
OMERO supports several annotation types:
- TagAnnotation: Text labels for categorization
- MapAnnotation: Key-value pairs for structured metadata
- FileAnnotation: File attachments (PDFs, CSVs, analysis results, etc.)
- CommentAnnotation: Free-text comments
- LongAnnotation: Integer values
- DoubleAnnotation: Floating-point values
- BooleanAnnotation: Boolean values
- TimestampAnnotation: Date/time stamps
- TermAnnotation: Ontology terms
Tag Annotations
Create and Link Tag
import omero.gateway
# Create new tag
tag_ann = omero.gateway.TagAnnotationWrapper(conn)
tag_ann.setValue("Experiment 2024")
tag_ann.setDescription("Optional description of this tag")
tag_ann.save()
# Link tag to an object
project = conn.getObject("Project", project_id)
project.linkAnnotation(tag_ann)
Create Tag with Namespace
# Create tag with custom namespace
tag_ann = omero.gateway.TagAnnotationWrapper(conn)
tag_ann.setValue("Quality Control")
tag_ann.setNs("mylab.qc.tags")
tag_ann.save()
# Link to image
image = conn.getObject("Image", image_id)
image.linkAnnotation(tag_ann)
Reuse Existing Tag
# Find existing tag
tag_id = 123
tag_ann = conn.getObject("TagAnnotation", tag_id)
# Link to multiple images
for image in conn.getObjects("Image", [img1, img2, img3]):
image.linkAnnotation(tag_ann)
Create Tag Set (Tag with Children)
# Create tag set (parent tag)
tag_set = omero.gateway.TagAnnotationWrapper(conn)
tag_set.setValue("Cell Types")
tag_set.save()
# Create child tags
tags = ["HeLa", "U2OS", "HEK293"]
for tag_value in tags:
tag = omero.gateway.TagAnnotationWrapper(conn)
tag.setValue(tag_value)
tag.save()
# Link child to parent
tag_set.linkAnnotation(tag)
Map Annotations (Key-Value Pairs)
Create Map Annotation
import omero.gateway
import omero.constants.metadata
# Prepare key-value data
key_value_data = [
["Drug Name", "Monastrol"],
["Concentration", "5 mg/ml"],
["Treatment Time", "24 hours"],
["Temperature", "37C"]
]
# Create map annotation
map_ann = omero.gateway.MapAnnotationWrapper(conn)
# Use standard client namespace
namespace = omero.constants.metadata.NSCLIENTMAPANNOTATION
map_ann.setNs(namespace)
# Set data
map_ann.setValue(key_value_data)
map_ann.save()
# Link to dataset
dataset = conn.getObject("Dataset", dataset_id)
dataset.linkAnnotation(map_ann)
Custom Namespace for Map Annotations
# Use custom namespace for organization-specific metadata
key_value_data = [
["Microscope", "Zeiss LSM 880"],
["Objective", "63x Oil"],
["Laser Power", "10%"]
]
map_ann = omero.gateway.MapAnnotationWrapper(conn)
map_ann.setNs("mylab.microscopy.settings")
map_ann.setValue(key_value_data)
map_ann.save()
image = conn.getObject("Image", image_id)
image.linkAnnotation(map_ann)
Read Map Annotation
# Get map annotation
image = conn.getObject("Image", image_id)
for ann in image.listAnnotations():
if isinstance(ann, omero.gateway.MapAnnotationWrapper):
print(f"Map Annotation (ID: {ann.getId()}):")
print(f"Namespace: {ann.getNs()}")
# Get key-value pairs
for key, value in ann.getValue():
print(f" {key}: {value}")
File Annotations
Upload and Attach File
import os
# Prepare file
file_path = "analysis_results.csv"
# Create file annotation
namespace = "mylab.analysis.results"
file_ann = conn.createFileAnnfromLocalFile(
file_path,
mimetype="text/csv",
ns=namespace,
desc="Cell segmentation results"
)
# Link to dataset
dataset = conn.getObject("Dataset", dataset_id)
dataset.linkAnnotation(file_ann)
Supported MIME Types
Common MIME types:
- Text:
"text/plain","text/csv","text/tab-separated-values" - Documents:
"application/pdf","application/vnd.ms-excel" - Images:
"image/png","image/jpeg" - Data:
"application/json","application/xml" - Archives:
"application/zip","application/gzip"
Upload Multiple Files
files = ["figure1.pdf", "figure2.pdf", "table1.csv"]
namespace = "publication.supplementary"
dataset = conn.getObject("Dataset", dataset_id)
for file_path in files:
file_ann = conn.createFileAnnfromLocalFile(
file_path,
mimetype="application/octet-stream",
ns=namespace,
desc=f"Supplementary file: {os.path.basename(file_path)}"
)
dataset.linkAnnotation(file_ann)
Download File Annotation
import os
# Get object with file annotation
image = conn.getObject("Image", image_id)
# Download directory
download_path = "./downloads"
os.makedirs(download_path, exist_ok=True)
# Filter by namespace
namespace = "mylab.analysis.results"
for ann in image.listAnnotations(ns=namespace):
if isinstance(ann, omero.gateway.FileAnnotationWrapper):
file_name = ann.getFile().getName()
file_path = os.path.join(download_path, file_name)
print(f"Downloading: {file_name}")
# Download file in chunks
with open(file_path, 'wb') as f:
for chunk in ann.getFileInChunks():
f.write(chunk)
print(f"Saved to: {file_path}")
Get File Annotation Metadata
for ann in dataset.listAnnotations():
if isinstance(ann, omero.gateway.FileAnnotationWrapper):
orig_file = ann.getFile()
print(f"File Annotation ID: {ann.getId()}")
print(f" File Name: {orig_file.getName()}")
print(f" File Size: {orig_file.getSize()} bytes")
print(f" MIME Type: {orig_file.getMimetype()}")
print(f" Namespace: {ann.getNs()}")
print(f" Description: {ann.getDescription()}")
Comment Annotations
Add Comment
# Create comment
comment = omero.gateway.CommentAnnotationWrapper(conn)
comment.setValue("This image shows excellent staining quality")
comment.save()
# Link to image
image = conn.getObject("Image", image_id)
image.linkAnnotation(comment)
Add Comment with Namespace
comment = omero.gateway.CommentAnnotationWrapper(conn)
comment.setValue("Approved for publication")
comment.setNs("mylab.publication.status")
comment.save()
dataset = conn.getObject("Dataset", dataset_id)
dataset.linkAnnotation(comment)
Numeric Annotations
Long Annotation (Integer)
# Create long annotation
long_ann = omero.gateway.LongAnnotationWrapper(conn)
long_ann.setValue(42)
long_ann.setNs("mylab.cell.count")
long_ann.save()
image = conn.getObject("Image", image_id)
image.linkAnnotation(long_ann)
Double Annotation (Float)
# Create double annotation
double_ann = omero.gateway.DoubleAnnotationWrapper(conn)
double_ann.setValue(3.14159)
double_ann.setNs("mylab.fluorescence.intensity")
double_ann.save()
image = conn.getObject("Image", image_id)
image.linkAnnotation(double_ann)
Listing Annotations
List All Annotations on Object
import omero.model
# Get object
project = conn.getObject("Project", project_id)
# List all annotations
for ann in project.listAnnotations():
print(f"Annotation ID: {ann.getId()}")
print(f" Type: {ann.OMERO_TYPE}")
print(f" Added by: {ann.link.getDetails().getOwner().getOmeName()}")
# Type-specific handling
if ann.OMERO_TYPE == omero.model.TagAnnotationI:
print(f" Tag value: {ann.getTextValue()}")
elif isinstance(ann, omero.gateway.MapAnnotationWrapper):
print(f" Map data: {ann.getValue()}")
elif isinstance(ann, omero.gateway.FileAnnotationWrapper):
print(f" File: {ann.getFile().getName()}")
elif isinstance(ann, omero.gateway.CommentAnnotationWrapper):
print(f" Comment: {ann.getValue()}")
print()
Filter Annotations by Namespace
# Get annotations with specific namespace
namespace = "mylab.qc.tags"
for ann in image.listAnnotations(ns=namespace):
print(f"Found annotation: {ann.getId()}")
if isinstance(ann, omero.gateway.MapAnnotationWrapper):
for key, value in ann.getValue():
print(f" {key}: {value}")
Get First Annotation with Namespace
# Get single annotation by namespace
namespace = "mylab.analysis.results"
ann = dataset.getAnnotation(namespace)
if ann:
print(f"Found annotation with namespace: {ann.getNs()}")
else:
print("No annotation found with that namespace")
Query Annotations Across Multiple Objects
# Get all tag annotations linked to image IDs
image_ids = [1, 2, 3, 4, 5]
for link in conn.getAnnotationLinks('Image', parent_ids=image_ids):
ann = link.getChild()
if isinstance(ann._obj, omero.model.TagAnnotationI):
print(f"Image {link.getParent().getId()}: Tag '{ann.getTextValue()}'")
Counting Annotations
# Count annotations on project
project_id = 123
count = conn.countAnnotations('Project', [project_id])
print(f"Project has {count[project_id]} annotations")
# Count annotations on multiple images
image_ids = [1, 2, 3]
counts = conn.countAnnotations('Image', image_ids)
for image_id, count in counts.items():
print(f"Image {image_id}: {count} annotations")
Annotation Links
Create Annotation Link Manually
# Get annotation and image
tag = conn.getObject("TagAnnotation", tag_id)
image = conn.getObject("Image", image_id)
# Create link
link = omero.model.ImageAnnotationLinkI()
link.setParent(omero.model.ImageI(image.getId(), False))
link.setChild(omero.model.TagAnnotationI(tag.getId(), False))
# Save link
conn.getUpdateService().saveAndReturnObject(link)
Update Annotation Links
# Get existing links
annotation_ids = [1, 2, 3]
new_tag_id = 5
for link in conn.getAnnotationLinks('Image', ann_ids=annotation_ids):
print(f"Image ID: {link.getParent().id}")
# Change linked annotation
link._obj.child = omero.model.TagAnnotationI(new_tag_id, False)
link.save()
Removing Annotations
Delete Annotations
# Get image
image = conn.getObject("Image", image_id)
# Collect annotation IDs to delete
to_delete = []
namespace = "mylab.temp.annotations"
for ann in image.listAnnotations(ns=namespace):
to_delete.append(ann.getId())
# Delete annotations
if to_delete:
conn.deleteObjects('Annotation', to_delete, wait=True)
print(f"Deleted {len(to_delete)} annotations")
Unlink Annotations (Keep Annotation, Remove Link)
# Get image
image = conn.getObject("Image", image_id)
# Collect link IDs to delete
to_delete = []
for ann in image.listAnnotations():
if isinstance(ann, omero.gateway.TagAnnotationWrapper):
to_delete.append(ann.link.getId())
# Delete links (annotations remain in database)
if to_delete:
conn.deleteObjects("ImageAnnotationLink", to_delete, wait=True)
print(f"Unlinked {len(to_delete)} annotations")
Delete Specific Annotation Types
import omero.gateway
# Delete only map annotations
image = conn.getObject("Image", image_id)
to_delete = []
for ann in image.listAnnotations():
if isinstance(ann, omero.gateway.MapAnnotationWrapper):
to_delete.append(ann.getId())
conn.deleteObjects('Annotation', to_delete, wait=True)
Annotation Ownership
Set Annotation Owner (Admin Only)
import omero.model
# Create tag with specific owner
tag_ann = omero.gateway.TagAnnotationWrapper(conn)
tag_ann.setValue("Admin Tag")
# Set owner (requires admin privileges)
user_id = 5
tag_ann._obj.details.owner = omero.model.ExperimenterI(user_id, False)
tag_ann.save()
Create Annotation as Another User (Admin Only)
# Admin connection
admin_conn = BlitzGateway(admin_user, admin_pass, host=host, port=4064)
admin_conn.connect()
# Get target user
user_id = 10
user = admin_conn.getObject("Experimenter", user_id).getName()
# Create connection as user
user_conn = admin_conn.suConn(user)
# Create annotation as that user
map_ann = omero.gateway.MapAnnotationWrapper(user_conn)
map_ann.setNs("mylab.metadata")
map_ann.setValue([["key", "value"]])
map_ann.save()
# Link to project
project = admin_conn.getObject("Project", project_id)
project.linkAnnotation(map_ann)
# Close connections
user_conn.close()
admin_conn.close()
Bulk Annotation Operations
Tag Multiple Images
# Create or get tag
tag = omero.gateway.TagAnnotationWrapper(conn)
tag.setValue("Validated")
tag.save()
# Get images to tag
dataset = conn.getObject("Dataset", dataset_id)
# Tag all images in dataset
for image in dataset.listChildren():
image.linkAnnotation(tag)
print(f"Tagged image: {image.getName()}")
Batch Add Map Annotations
# Prepare metadata for multiple images
image_metadata = {
101: [["Quality", "Good"], ["Reviewed", "Yes"]],
102: [["Quality", "Excellent"], ["Reviewed", "Yes"]],
103: [["Quality", "Poor"], ["Reviewed", "No"]]
}
# Add annotations
for image_id, kv_data in image_metadata.items():
image = conn.getObject("Image", image_id)
if image:
map_ann = omero.gateway.MapAnnotationWrapper(conn)
map_ann.setNs("mylab.qc")
map_ann.setValue(kv_data)
map_ann.save()
image.linkAnnotation(map_ann)
print(f"Annotated image {image_id}")
Namespaces
Standard OMERO Namespaces
import omero.constants.metadata as omero_ns
# Client map annotation namespace
omero_ns.NSCLIENTMAPANNOTATION
# "openmicroscopy.org/omero/client/mapAnnotation"
# Bulk annotations namespace
omero_ns.NSBULKANNOTATIONS
# "openmicroscopy.org/omero/bulk_annotations"
Custom Namespaces
Best practices for custom namespaces:
- Use reverse domain notation:
"org.mylab.category.subcategory" - Be specific:
"com.company.project.analysis.v1" - Include version if schema may change:
"mylab.metadata.v2"
# Define namespaces
NS_QC = "org.mylab.quality_control"
NS_ANALYSIS = "org.mylab.image_analysis.v1"
NS_PUBLICATION = "org.mylab.publication.2024"
# Use in annotations
map_ann.setNs(NS_ANALYSIS)
Load All Annotations by Type
Load All File Annotations
# Define namespaces to include/exclude
ns_to_include = ["mylab.analysis.results"]
ns_to_exclude = []
# Get metadata service
metadataService = conn.getMetadataService()
# Load all file annotations with namespace
annotations = metadataService.loadSpecifiedAnnotations(
'omero.model.FileAnnotation',
ns_to_include,
ns_to_exclude,
None
)
for ann in annotations:
print(f"File Annotation ID: {ann.getId().getValue()}")
print(f" File: {ann.getFile().getName().getValue()}")
print(f" Size: {ann.getFile().getSize().getValue()} bytes")
Complete Example
from omero.gateway import BlitzGateway
import omero.gateway
import omero.constants.metadata
HOST = 'omero.example.com'
PORT = 4064
USERNAME = 'user'
PASSWORD = 'pass'
with BlitzGateway(USERNAME, PASSWORD, host=HOST, port=PORT) as conn:
# Get dataset
dataset = conn.getObject("Dataset", dataset_id)
# Add tag
tag = omero.gateway.TagAnnotationWrapper(conn)
tag.setValue("Analysis Complete")
tag.save()
dataset.linkAnnotation(tag)
# Add map annotation with metadata
metadata = [
["Analysis Date", "2024-10-20"],
["Software", "CellProfiler 4.2"],
["Pipeline", "cell_segmentation_v3"]
]
map_ann = omero.gateway.MapAnnotationWrapper(conn)
map_ann.setNs(omero.constants.metadata.NSCLIENTMAPANNOTATION)
map_ann.setValue(metadata)
map_ann.save()
dataset.linkAnnotation(map_ann)
# Add file annotation
file_ann = conn.createFileAnnfromLocalFile(
"analysis_summary.pdf",
mimetype="application/pdf",
ns="mylab.reports",
desc="Analysis summary report"
)
dataset.linkAnnotation(file_ann)
# Add comment
comment = omero.gateway.CommentAnnotationWrapper(conn)
comment.setValue("Dataset ready for review")
comment.save()
dataset.linkAnnotation(comment)
print(f"Added 4 annotations to dataset {dataset.getName()}")
Best Practices
- Use Namespaces: Always use namespaces to organize annotations
- Descriptive Tags: Use clear, consistent tag names
- Structured Metadata: Prefer map annotations over comments for structured data
- File Organization: Use descriptive filenames and MIME types
- Link Reuse: Reuse existing tags instead of creating duplicates
- Batch Operations: Process multiple objects in loops for efficiency
- Error Handling: Check for successful saves before linking
- Cleanup: Remove temporary annotations when no longer needed
- Documentation: Document custom namespace meanings
- Permissions: Consider annotation ownership for collaborative workflows