# 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 ```python 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 ```python # 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 ```python # 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) ```python # 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 ```python 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 ```python # 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 ```python # 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python # 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 ```python 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) ```python # 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) ```python # 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 ```python 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 ```python # 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 ```python # 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 ```python # 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 ```python # 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 ```python # 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 ```python # 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 ```python # 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) ```python # 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 ```python 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) ```python 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) ```python # 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 ```python # 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 ```python # 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 ```python 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"` ```python # 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 ```python # 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 ```python 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 1. **Use Namespaces**: Always use namespaces to organize annotations 2. **Descriptive Tags**: Use clear, consistent tag names 3. **Structured Metadata**: Prefer map annotations over comments for structured data 4. **File Organization**: Use descriptive filenames and MIME types 5. **Link Reuse**: Reuse existing tags instead of creating duplicates 6. **Batch Operations**: Process multiple objects in loops for efficiency 7. **Error Handling**: Check for successful saves before linking 8. **Cleanup**: Remove temporary annotations when no longer needed 9. **Documentation**: Document custom namespace meanings 10. **Permissions**: Consider annotation ownership for collaborative workflows