Initial commit
This commit is contained in:
631
skills/omero-integration/references/advanced.md
Normal file
631
skills/omero-integration/references/advanced.md
Normal file
@@ -0,0 +1,631 @@
|
||||
# Advanced Features
|
||||
|
||||
This reference covers advanced OMERO operations including permissions, deletion, filesets, and administrative tasks.
|
||||
|
||||
## Deleting Objects
|
||||
|
||||
### Delete with Wait
|
||||
|
||||
```python
|
||||
# Delete objects and wait for completion
|
||||
project_ids = [1, 2, 3]
|
||||
conn.deleteObjects("Project", project_ids, wait=True)
|
||||
print("Deletion complete")
|
||||
|
||||
# Delete without waiting (asynchronous)
|
||||
conn.deleteObjects("Dataset", [dataset_id], wait=False)
|
||||
```
|
||||
|
||||
### Delete with Callback Monitoring
|
||||
|
||||
```python
|
||||
from omero.callbacks import CmdCallbackI
|
||||
|
||||
# Start delete operation
|
||||
handle = conn.deleteObjects("Project", [project_id])
|
||||
|
||||
# Create callback to monitor progress
|
||||
cb = CmdCallbackI(conn.c, handle)
|
||||
print("Deleting, please wait...")
|
||||
|
||||
# Poll for completion
|
||||
while not cb.block(500): # Check every 500ms
|
||||
print(".", end="", flush=True)
|
||||
|
||||
print("\nDeletion finished")
|
||||
|
||||
# Check for errors
|
||||
response = cb.getResponse()
|
||||
if isinstance(response, omero.cmd.ERR):
|
||||
print("Error occurred:")
|
||||
print(response)
|
||||
else:
|
||||
print("Deletion successful")
|
||||
|
||||
# Clean up
|
||||
cb.close(True) # Also closes handle
|
||||
```
|
||||
|
||||
### Delete Different Object Types
|
||||
|
||||
```python
|
||||
# Delete images
|
||||
image_ids = [101, 102, 103]
|
||||
conn.deleteObjects("Image", image_ids, wait=True)
|
||||
|
||||
# Delete datasets
|
||||
dataset_ids = [10, 11]
|
||||
conn.deleteObjects("Dataset", dataset_ids, wait=True)
|
||||
|
||||
# Delete ROIs
|
||||
roi_ids = [201, 202]
|
||||
conn.deleteObjects("Roi", roi_ids, wait=True)
|
||||
|
||||
# Delete annotations
|
||||
annotation_ids = [301, 302]
|
||||
conn.deleteObjects("Annotation", annotation_ids, wait=True)
|
||||
```
|
||||
|
||||
### Delete with Cascade
|
||||
|
||||
```python
|
||||
# Deleting a project will cascade to contained datasets
|
||||
# This behavior depends on server configuration
|
||||
project_id = 123
|
||||
conn.deleteObjects("Project", [project_id], wait=True)
|
||||
|
||||
# Datasets and images may be deleted or orphaned
|
||||
# depending on delete specifications
|
||||
```
|
||||
|
||||
## Filesets
|
||||
|
||||
Filesets represent collections of original imported files. They were introduced in OMERO 5.0.
|
||||
|
||||
### Check if Image Has Fileset
|
||||
|
||||
```python
|
||||
image = conn.getObject("Image", image_id)
|
||||
|
||||
fileset = image.getFileset()
|
||||
if fileset:
|
||||
print(f"Image is part of fileset {fileset.getId()}")
|
||||
else:
|
||||
print("Image has no fileset (pre-OMERO 5.0)")
|
||||
```
|
||||
|
||||
### Access Fileset Information
|
||||
|
||||
```python
|
||||
image = conn.getObject("Image", image_id)
|
||||
fileset = image.getFileset()
|
||||
|
||||
if fileset:
|
||||
fs_id = fileset.getId()
|
||||
print(f"Fileset ID: {fs_id}")
|
||||
|
||||
# List all images in this fileset
|
||||
print("Images in fileset:")
|
||||
for fs_image in fileset.copyImages():
|
||||
print(f" {fs_image.getId()}: {fs_image.getName()}")
|
||||
|
||||
# List original imported files
|
||||
print("\nOriginal files:")
|
||||
for orig_file in fileset.listFiles():
|
||||
print(f" {orig_file.getPath()}/{orig_file.getName()}")
|
||||
print(f" Size: {orig_file.getSize()} bytes")
|
||||
```
|
||||
|
||||
### Get Fileset Directly
|
||||
|
||||
```python
|
||||
# Get fileset object
|
||||
fileset = conn.getObject("Fileset", fileset_id)
|
||||
|
||||
if fileset:
|
||||
# Access images
|
||||
for image in fileset.copyImages():
|
||||
print(f"Image: {image.getName()}")
|
||||
|
||||
# Access files
|
||||
for orig_file in fileset.listFiles():
|
||||
print(f"File: {orig_file.getName()}")
|
||||
```
|
||||
|
||||
### Download Original Files
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
fileset = image.getFileset()
|
||||
|
||||
if fileset:
|
||||
download_dir = "./original_files"
|
||||
os.makedirs(download_dir, exist_ok=True)
|
||||
|
||||
for orig_file in fileset.listFiles():
|
||||
file_name = orig_file.getName()
|
||||
file_path = os.path.join(download_dir, file_name)
|
||||
|
||||
print(f"Downloading: {file_name}")
|
||||
|
||||
# Get file as RawFileStore
|
||||
raw_file_store = conn.createRawFileStore()
|
||||
raw_file_store.setFileId(orig_file.getId())
|
||||
|
||||
# Download in chunks
|
||||
with open(file_path, 'wb') as f:
|
||||
offset = 0
|
||||
chunk_size = 1024 * 1024 # 1MB chunks
|
||||
size = orig_file.getSize()
|
||||
|
||||
while offset < size:
|
||||
chunk = raw_file_store.read(offset, chunk_size)
|
||||
f.write(chunk)
|
||||
offset += len(chunk)
|
||||
|
||||
raw_file_store.close()
|
||||
print(f"Saved to: {file_path}")
|
||||
```
|
||||
|
||||
## Group Permissions
|
||||
|
||||
OMERO uses group-based permissions to control data access.
|
||||
|
||||
### Permission Levels
|
||||
|
||||
- **PRIVATE** (`rw----`): Only owner can read/write
|
||||
- **READ-ONLY** (`rwr---`): Group members can read, only owner can write
|
||||
- **READ-ANNOTATE** (`rwra--`): Group members can read and annotate
|
||||
- **READ-WRITE** (`rwrw--`): Group members can read and write
|
||||
|
||||
### Check Current Group Permissions
|
||||
|
||||
```python
|
||||
# Get current group
|
||||
group = conn.getGroupFromContext()
|
||||
|
||||
# Get permissions
|
||||
permissions = group.getDetails().getPermissions()
|
||||
perm_string = str(permissions)
|
||||
|
||||
# Map to readable names
|
||||
permission_names = {
|
||||
'rw----': 'PRIVATE',
|
||||
'rwr---': 'READ-ONLY',
|
||||
'rwra--': 'READ-ANNOTATE',
|
||||
'rwrw--': 'READ-WRITE'
|
||||
}
|
||||
|
||||
perm_name = permission_names.get(perm_string, 'UNKNOWN')
|
||||
print(f"Group: {group.getName()}")
|
||||
print(f"Permissions: {perm_name} ({perm_string})")
|
||||
```
|
||||
|
||||
### List User's Groups
|
||||
|
||||
```python
|
||||
# Get all groups for current user
|
||||
print("User's groups:")
|
||||
for group in conn.getGroupsMemberOf():
|
||||
print(f" {group.getName()} (ID: {group.getId()})")
|
||||
|
||||
# Get group permissions
|
||||
perms = group.getDetails().getPermissions()
|
||||
print(f" Permissions: {perms}")
|
||||
```
|
||||
|
||||
### Get Group Members
|
||||
|
||||
```python
|
||||
# Get group
|
||||
group = conn.getObject("ExperimenterGroup", group_id)
|
||||
|
||||
# List members
|
||||
print(f"Members of {group.getName()}:")
|
||||
for member in group.getMembers():
|
||||
print(f" {member.getFullName()} ({member.getOmeName()})")
|
||||
```
|
||||
|
||||
## Cross-Group Queries
|
||||
|
||||
### Query Across All Groups
|
||||
|
||||
```python
|
||||
# Set context to query all accessible groups
|
||||
conn.SERVICE_OPTS.setOmeroGroup('-1')
|
||||
|
||||
# Now queries span all groups
|
||||
image = conn.getObject("Image", image_id)
|
||||
if image:
|
||||
group = image.getDetails().getGroup()
|
||||
print(f"Image found in group: {group.getName()}")
|
||||
|
||||
# List projects across all groups
|
||||
for project in conn.getObjects("Project"):
|
||||
group = project.getDetails().getGroup()
|
||||
print(f"Project: {project.getName()} (Group: {group.getName()})")
|
||||
```
|
||||
|
||||
### Switch to Specific Group
|
||||
|
||||
```python
|
||||
# Get image's group
|
||||
image = conn.getObject("Image", image_id)
|
||||
group_id = image.getDetails().getGroup().getId()
|
||||
|
||||
# Switch to that group's context
|
||||
conn.SERVICE_OPTS.setOmeroGroup(group_id)
|
||||
|
||||
# Subsequent operations use this group
|
||||
projects = conn.listProjects() # Only from this group
|
||||
```
|
||||
|
||||
### Reset to Default Group
|
||||
|
||||
```python
|
||||
# Get default group
|
||||
default_group_id = conn.getEventContext().groupId
|
||||
|
||||
# Switch back to default
|
||||
conn.SERVICE_OPTS.setOmeroGroup(default_group_id)
|
||||
```
|
||||
|
||||
## Administrative Operations
|
||||
|
||||
### Check Admin Status
|
||||
|
||||
```python
|
||||
# Check if current user is admin
|
||||
if conn.isAdmin():
|
||||
print("User has admin privileges")
|
||||
|
||||
# Check if full admin
|
||||
if conn.isFullAdmin():
|
||||
print("User is full administrator")
|
||||
else:
|
||||
# Check specific privileges
|
||||
privileges = conn.getCurrentAdminPrivileges()
|
||||
print(f"Admin privileges: {privileges}")
|
||||
```
|
||||
|
||||
### List Administrators
|
||||
|
||||
```python
|
||||
# Get all administrators
|
||||
print("Administrators:")
|
||||
for admin in conn.getAdministrators():
|
||||
print(f" ID: {admin.getId()}")
|
||||
print(f" Username: {admin.getOmeName()}")
|
||||
print(f" Full Name: {admin.getFullName()}")
|
||||
```
|
||||
|
||||
### Set Object Owner (Admin Only)
|
||||
|
||||
```python
|
||||
import omero.model
|
||||
|
||||
# Create annotation with specific owner (requires admin)
|
||||
tag_ann = omero.gateway.TagAnnotationWrapper(conn)
|
||||
tag_ann.setValue("Admin-created tag")
|
||||
|
||||
# Set owner
|
||||
user_id = 5
|
||||
tag_ann._obj.details.owner = omero.model.ExperimenterI(user_id, False)
|
||||
tag_ann.save()
|
||||
|
||||
print(f"Created annotation owned by user {user_id}")
|
||||
```
|
||||
|
||||
### Substitute User Connection (Admin Only)
|
||||
|
||||
```python
|
||||
# Connect as admin
|
||||
admin_conn = BlitzGateway(admin_user, admin_pass, host=host, port=4064)
|
||||
admin_conn.connect()
|
||||
|
||||
# Get target user
|
||||
target_user_id = 10
|
||||
user = admin_conn.getObject("Experimenter", target_user_id)
|
||||
username = user.getOmeName()
|
||||
|
||||
# Create connection as that user
|
||||
user_conn = admin_conn.suConn(username)
|
||||
|
||||
print(f"Connected as {username}")
|
||||
|
||||
# Perform operations as that user
|
||||
for project in user_conn.listProjects():
|
||||
print(f" {project.getName()}")
|
||||
|
||||
# Close connections
|
||||
user_conn.close()
|
||||
admin_conn.close()
|
||||
```
|
||||
|
||||
### List All Users
|
||||
|
||||
```python
|
||||
# Get all users (admin operation)
|
||||
print("All users:")
|
||||
for user in conn.getObjects("Experimenter"):
|
||||
print(f" ID: {user.getId()}")
|
||||
print(f" Username: {user.getOmeName()}")
|
||||
print(f" Full Name: {user.getFullName()}")
|
||||
print(f" Email: {user.getEmail()}")
|
||||
print()
|
||||
```
|
||||
|
||||
## Service Access
|
||||
|
||||
OMERO provides various services for specific operations.
|
||||
|
||||
### Update Service
|
||||
|
||||
```python
|
||||
# Get update service
|
||||
updateService = conn.getUpdateService()
|
||||
|
||||
# Save and return object
|
||||
roi = omero.model.RoiI()
|
||||
roi.setImage(image._obj)
|
||||
saved_roi = updateService.saveAndReturnObject(roi)
|
||||
|
||||
# Save multiple objects
|
||||
objects = [obj1, obj2, obj3]
|
||||
saved_objects = updateService.saveAndReturnArray(objects)
|
||||
```
|
||||
|
||||
### ROI Service
|
||||
|
||||
```python
|
||||
# Get ROI service
|
||||
roi_service = conn.getRoiService()
|
||||
|
||||
# Find ROIs for image
|
||||
result = roi_service.findByImage(image_id, None)
|
||||
|
||||
# Get shape statistics
|
||||
shape_ids = [shape.id.val for roi in result.rois
|
||||
for shape in roi.copyShapes()]
|
||||
stats = roi_service.getShapeStatsRestricted(shape_ids, 0, 0, [0])
|
||||
```
|
||||
|
||||
### Metadata Service
|
||||
|
||||
```python
|
||||
# Get metadata service
|
||||
metadataService = conn.getMetadataService()
|
||||
|
||||
# Load annotations by type and namespace
|
||||
ns_to_include = ["mylab.analysis"]
|
||||
ns_to_exclude = []
|
||||
|
||||
annotations = metadataService.loadSpecifiedAnnotations(
|
||||
'omero.model.FileAnnotation',
|
||||
ns_to_include,
|
||||
ns_to_exclude,
|
||||
None
|
||||
)
|
||||
|
||||
for ann in annotations:
|
||||
print(f"Annotation: {ann.getId().getValue()}")
|
||||
```
|
||||
|
||||
### Query Service
|
||||
|
||||
```python
|
||||
# Get query service
|
||||
queryService = conn.getQueryService()
|
||||
|
||||
# Build query (more complex queries)
|
||||
params = omero.sys.ParametersI()
|
||||
params.addLong("image_id", image_id)
|
||||
|
||||
query = "select i from Image i where i.id = :image_id"
|
||||
image = queryService.findByQuery(query, params)
|
||||
```
|
||||
|
||||
### Thumbnail Service
|
||||
|
||||
```python
|
||||
# Get thumbnail service
|
||||
thumbnailService = conn.createThumbnailStore()
|
||||
|
||||
# Set current image
|
||||
thumbnailService.setPixelsId(image.getPrimaryPixels().getId())
|
||||
|
||||
# Get thumbnail
|
||||
thumbnail = thumbnailService.getThumbnail(96, 96)
|
||||
|
||||
# Close service
|
||||
thumbnailService.close()
|
||||
```
|
||||
|
||||
### Raw File Store
|
||||
|
||||
```python
|
||||
# Get raw file store
|
||||
rawFileStore = conn.createRawFileStore()
|
||||
|
||||
# Set file ID
|
||||
rawFileStore.setFileId(orig_file_id)
|
||||
|
||||
# Read file
|
||||
data = rawFileStore.read(0, rawFileStore.size())
|
||||
|
||||
# Close
|
||||
rawFileStore.close()
|
||||
```
|
||||
|
||||
## Object Ownership and Details
|
||||
|
||||
### Get Object Details
|
||||
|
||||
```python
|
||||
image = conn.getObject("Image", image_id)
|
||||
|
||||
# Get details
|
||||
details = image.getDetails()
|
||||
|
||||
# Owner information
|
||||
owner = details.getOwner()
|
||||
print(f"Owner ID: {owner.getId()}")
|
||||
print(f"Username: {owner.getOmeName()}")
|
||||
print(f"Full Name: {owner.getFullName()}")
|
||||
|
||||
# Group information
|
||||
group = details.getGroup()
|
||||
print(f"Group: {group.getName()} (ID: {group.getId()})")
|
||||
|
||||
# Creation information
|
||||
creation_event = details.getCreationEvent()
|
||||
print(f"Created: {creation_event.getTime()}")
|
||||
|
||||
# Update information
|
||||
update_event = details.getUpdateEvent()
|
||||
print(f"Updated: {update_event.getTime()}")
|
||||
```
|
||||
|
||||
### Get Permissions
|
||||
|
||||
```python
|
||||
# Get object permissions
|
||||
details = image.getDetails()
|
||||
permissions = details.getPermissions()
|
||||
|
||||
# Check specific permissions
|
||||
can_edit = permissions.canEdit()
|
||||
can_annotate = permissions.canAnnotate()
|
||||
can_link = permissions.canLink()
|
||||
can_delete = permissions.canDelete()
|
||||
|
||||
print(f"Can edit: {can_edit}")
|
||||
print(f"Can annotate: {can_annotate}")
|
||||
print(f"Can link: {can_link}")
|
||||
print(f"Can delete: {can_delete}")
|
||||
```
|
||||
|
||||
## Event Context
|
||||
|
||||
### Get Current Event Context
|
||||
|
||||
```python
|
||||
# Get event context (current session info)
|
||||
ctx = conn.getEventContext()
|
||||
|
||||
print(f"User ID: {ctx.userId}")
|
||||
print(f"Username: {ctx.userName}")
|
||||
print(f"Group ID: {ctx.groupId}")
|
||||
print(f"Group Name: {ctx.groupName}")
|
||||
print(f"Session ID: {ctx.sessionId}")
|
||||
print(f"Is Admin: {ctx.isAdmin}")
|
||||
```
|
||||
|
||||
## Complete Admin Example
|
||||
|
||||
```python
|
||||
from omero.gateway import BlitzGateway
|
||||
|
||||
# Connect as admin
|
||||
ADMIN_USER = 'root'
|
||||
ADMIN_PASS = 'password'
|
||||
HOST = 'omero.example.com'
|
||||
PORT = 4064
|
||||
|
||||
with BlitzGateway(ADMIN_USER, ADMIN_PASS, host=HOST, port=PORT) as admin_conn:
|
||||
print("=== Administrator Operations ===\n")
|
||||
|
||||
# List all users
|
||||
print("All Users:")
|
||||
for user in admin_conn.getObjects("Experimenter"):
|
||||
print(f" {user.getOmeName()}: {user.getFullName()}")
|
||||
|
||||
# List all groups
|
||||
print("\nAll Groups:")
|
||||
for group in admin_conn.getObjects("ExperimenterGroup"):
|
||||
perms = group.getDetails().getPermissions()
|
||||
print(f" {group.getName()}: {perms}")
|
||||
|
||||
# List members
|
||||
for member in group.getMembers():
|
||||
print(f" - {member.getOmeName()}")
|
||||
|
||||
# Query across all groups
|
||||
print("\nAll Projects (all groups):")
|
||||
admin_conn.SERVICE_OPTS.setOmeroGroup('-1')
|
||||
|
||||
for project in admin_conn.getObjects("Project"):
|
||||
owner = project.getDetails().getOwner()
|
||||
group = project.getDetails().getGroup()
|
||||
print(f" {project.getName()}")
|
||||
print(f" Owner: {owner.getOmeName()}")
|
||||
print(f" Group: {group.getName()}")
|
||||
|
||||
# Connect as another user
|
||||
target_user_id = 5
|
||||
user = admin_conn.getObject("Experimenter", target_user_id)
|
||||
|
||||
if user:
|
||||
print(f"\n=== Operating as {user.getOmeName()} ===\n")
|
||||
|
||||
user_conn = admin_conn.suConn(user.getOmeName())
|
||||
|
||||
# List that user's projects
|
||||
for project in user_conn.listProjects():
|
||||
print(f" {project.getName()}")
|
||||
|
||||
user_conn.close()
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Permissions**: Always check permissions before operations
|
||||
2. **Group Context**: Set appropriate group context for queries
|
||||
3. **Admin Operations**: Use admin privileges sparingly and carefully
|
||||
4. **Delete Confirmation**: Always confirm before deleting objects
|
||||
5. **Callback Monitoring**: Monitor long delete operations with callbacks
|
||||
6. **Fileset Awareness**: Check for filesets when working with images
|
||||
7. **Service Cleanup**: Close services when done (thumbnailStore, rawFileStore)
|
||||
8. **Cross-Group Queries**: Use `-1` group ID for cross-group access
|
||||
9. **Error Handling**: Always handle permission and access errors
|
||||
10. **Documentation**: Document administrative operations clearly
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Permission Denied
|
||||
|
||||
```python
|
||||
try:
|
||||
conn.deleteObjects("Project", [project_id], wait=True)
|
||||
except Exception as e:
|
||||
if "SecurityViolation" in str(e):
|
||||
print("Permission denied: You don't own this object")
|
||||
else:
|
||||
raise
|
||||
```
|
||||
|
||||
### Object Not Found
|
||||
|
||||
```python
|
||||
# Check if object exists before accessing
|
||||
obj = conn.getObject("Image", image_id)
|
||||
if obj is None:
|
||||
print(f"Image {image_id} not found or not accessible")
|
||||
else:
|
||||
# Process object
|
||||
pass
|
||||
```
|
||||
|
||||
### Group Context Issues
|
||||
|
||||
```python
|
||||
# If object not found, try cross-group query
|
||||
conn.SERVICE_OPTS.setOmeroGroup('-1')
|
||||
obj = conn.getObject("Image", image_id)
|
||||
|
||||
if obj:
|
||||
# Switch to object's group for further operations
|
||||
group_id = obj.getDetails().getGroup().getId()
|
||||
conn.SERVICE_OPTS.setOmeroGroup(group_id)
|
||||
```
|
||||
369
skills/omero-integration/references/connection.md
Normal file
369
skills/omero-integration/references/connection.md
Normal file
@@ -0,0 +1,369 @@
|
||||
# Connection & Session Management
|
||||
|
||||
This reference covers establishing and managing connections to OMERO servers using BlitzGateway.
|
||||
|
||||
## Basic Connection
|
||||
|
||||
### Standard Connection Pattern
|
||||
|
||||
```python
|
||||
from omero.gateway import BlitzGateway
|
||||
|
||||
# Create connection
|
||||
conn = BlitzGateway(username, password, host=host, port=4064)
|
||||
|
||||
# Connect to server
|
||||
if conn.connect():
|
||||
print("Connected successfully")
|
||||
# Perform operations
|
||||
conn.close()
|
||||
else:
|
||||
print("Failed to connect")
|
||||
```
|
||||
|
||||
### Connection Parameters
|
||||
|
||||
- **username** (str): OMERO user account name
|
||||
- **password** (str): User password
|
||||
- **host** (str): OMERO server hostname or IP address
|
||||
- **port** (int): Server port (default: 4064)
|
||||
- **secure** (bool): Force encrypted connection (default: False)
|
||||
|
||||
### Secure Connection
|
||||
|
||||
To ensure all data transfers are encrypted:
|
||||
|
||||
```python
|
||||
conn = BlitzGateway(username, password, host=host, port=4064, secure=True)
|
||||
conn.connect()
|
||||
```
|
||||
|
||||
## Context Manager Pattern (Recommended)
|
||||
|
||||
Use context managers for automatic connection management and cleanup:
|
||||
|
||||
```python
|
||||
from omero.gateway import BlitzGateway
|
||||
|
||||
with BlitzGateway(username, password, host=host, port=4064) as conn:
|
||||
# Connection automatically established
|
||||
for project in conn.getObjects('Project'):
|
||||
print(project.getName())
|
||||
# Connection automatically closed on exit
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Automatic `connect()` call
|
||||
- Automatic `close()` call on exit
|
||||
- Exception-safe resource cleanup
|
||||
- Cleaner code
|
||||
|
||||
## Session Management
|
||||
|
||||
### Connection from Existing Client
|
||||
|
||||
Create BlitzGateway from an existing `omero.client` session:
|
||||
|
||||
```python
|
||||
import omero.clients
|
||||
from omero.gateway import BlitzGateway
|
||||
|
||||
# Create client and session
|
||||
client = omero.client(host, port)
|
||||
session = client.createSession(username, password)
|
||||
|
||||
# Create BlitzGateway from existing client
|
||||
conn = BlitzGateway(client_obj=client)
|
||||
|
||||
# Use connection
|
||||
# ...
|
||||
|
||||
# Close when done
|
||||
conn.close()
|
||||
```
|
||||
|
||||
### Retrieve Session Information
|
||||
|
||||
```python
|
||||
# Get current user information
|
||||
user = conn.getUser()
|
||||
print(f"User ID: {user.getId()}")
|
||||
print(f"Username: {user.getName()}")
|
||||
print(f"Full Name: {user.getFullName()}")
|
||||
print(f"Is Admin: {conn.isAdmin()}")
|
||||
|
||||
# Get current group
|
||||
group = conn.getGroupFromContext()
|
||||
print(f"Current Group: {group.getName()}")
|
||||
print(f"Group ID: {group.getId()}")
|
||||
```
|
||||
|
||||
### Check Admin Privileges
|
||||
|
||||
```python
|
||||
if conn.isAdmin():
|
||||
print("User has admin privileges")
|
||||
|
||||
if conn.isFullAdmin():
|
||||
print("User is full administrator")
|
||||
else:
|
||||
# Check specific admin privileges
|
||||
privileges = conn.getCurrentAdminPrivileges()
|
||||
print(f"Admin privileges: {privileges}")
|
||||
```
|
||||
|
||||
## Group Context Management
|
||||
|
||||
OMERO uses groups to manage data access permissions. Users can belong to multiple groups.
|
||||
|
||||
### Get Current Group Context
|
||||
|
||||
```python
|
||||
# Get the current group context
|
||||
group = conn.getGroupFromContext()
|
||||
print(f"Current group: {group.getName()}")
|
||||
print(f"Group ID: {group.getId()}")
|
||||
```
|
||||
|
||||
### Query Across All Groups
|
||||
|
||||
Use group ID `-1` to query across all accessible groups:
|
||||
|
||||
```python
|
||||
# Set context to query all groups
|
||||
conn.SERVICE_OPTS.setOmeroGroup('-1')
|
||||
|
||||
# Now queries span all accessible groups
|
||||
image = conn.getObject("Image", image_id)
|
||||
projects = conn.listProjects()
|
||||
```
|
||||
|
||||
### Switch to Specific Group
|
||||
|
||||
Switch context to work within a specific group:
|
||||
|
||||
```python
|
||||
# Get group ID from an object
|
||||
image = conn.getObject("Image", image_id)
|
||||
group_id = image.getDetails().getGroup().getId()
|
||||
|
||||
# Switch to that group's context
|
||||
conn.SERVICE_OPTS.setOmeroGroup(group_id)
|
||||
|
||||
# Subsequent operations use this group context
|
||||
projects = conn.listProjects()
|
||||
```
|
||||
|
||||
### List Available Groups
|
||||
|
||||
```python
|
||||
# Get all groups for current user
|
||||
for group in conn.getGroupsMemberOf():
|
||||
print(f"Group: {group.getName()} (ID: {group.getId()})")
|
||||
```
|
||||
|
||||
## Advanced Connection Features
|
||||
|
||||
### Substitute User Connection (Admin Only)
|
||||
|
||||
Administrators can create connections acting as other users:
|
||||
|
||||
```python
|
||||
# Connect as admin
|
||||
admin_conn = BlitzGateway(admin_user, admin_pass, host=host, port=4064)
|
||||
admin_conn.connect()
|
||||
|
||||
# Get target user
|
||||
target_user = admin_conn.getObject("Experimenter", user_id).getName()
|
||||
|
||||
# Create connection as that user
|
||||
user_conn = admin_conn.suConn(target_user)
|
||||
|
||||
# Operations performed as target user
|
||||
for project in user_conn.listProjects():
|
||||
print(project.getName())
|
||||
|
||||
# Close substitute connection
|
||||
user_conn.close()
|
||||
admin_conn.close()
|
||||
```
|
||||
|
||||
### List Administrators
|
||||
|
||||
```python
|
||||
# Get all administrators
|
||||
for admin in conn.getAdministrators():
|
||||
print(f"ID: {admin.getId()}, Name: {admin.getFullName()}, "
|
||||
f"Username: {admin.getOmeName()}")
|
||||
```
|
||||
|
||||
## Connection Lifecycle
|
||||
|
||||
### Closing Connections
|
||||
|
||||
Always close connections to free server resources:
|
||||
|
||||
```python
|
||||
try:
|
||||
conn = BlitzGateway(username, password, host=host, port=4064)
|
||||
conn.connect()
|
||||
|
||||
# Perform operations
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
```
|
||||
|
||||
### Check Connection Status
|
||||
|
||||
```python
|
||||
if conn.isConnected():
|
||||
print("Connection is active")
|
||||
else:
|
||||
print("Connection is closed")
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Robust Connection Pattern
|
||||
|
||||
```python
|
||||
from omero.gateway import BlitzGateway
|
||||
import traceback
|
||||
|
||||
def connect_to_omero(username, password, host, port=4064):
|
||||
"""
|
||||
Establish connection to OMERO server with error handling.
|
||||
|
||||
Returns:
|
||||
BlitzGateway connection object or None if failed
|
||||
"""
|
||||
try:
|
||||
conn = BlitzGateway(username, password, host=host, port=port, secure=True)
|
||||
if conn.connect():
|
||||
print(f"Connected to {host}:{port} as {username}")
|
||||
return conn
|
||||
else:
|
||||
print("Failed to establish connection")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Connection error: {e}")
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
# Usage
|
||||
conn = connect_to_omero(username, password, host)
|
||||
if conn:
|
||||
try:
|
||||
# Perform operations
|
||||
pass
|
||||
finally:
|
||||
conn.close()
|
||||
```
|
||||
|
||||
## Common Connection Patterns
|
||||
|
||||
### Pattern 1: Simple Script
|
||||
|
||||
```python
|
||||
from omero.gateway import BlitzGateway
|
||||
|
||||
# Connection parameters
|
||||
HOST = 'omero.example.com'
|
||||
PORT = 4064
|
||||
USERNAME = 'user'
|
||||
PASSWORD = 'pass'
|
||||
|
||||
# Connect
|
||||
with BlitzGateway(USERNAME, PASSWORD, host=HOST, port=PORT) as conn:
|
||||
print(f"Connected as {conn.getUser().getName()}")
|
||||
# Perform operations
|
||||
```
|
||||
|
||||
### Pattern 2: Configuration-Based Connection
|
||||
|
||||
```python
|
||||
import yaml
|
||||
from omero.gateway import BlitzGateway
|
||||
|
||||
# Load configuration
|
||||
with open('omero_config.yaml', 'r') as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
# Connect using config
|
||||
with BlitzGateway(
|
||||
config['username'],
|
||||
config['password'],
|
||||
host=config['host'],
|
||||
port=config.get('port', 4064),
|
||||
secure=config.get('secure', True)
|
||||
) as conn:
|
||||
# Perform operations
|
||||
pass
|
||||
```
|
||||
|
||||
### Pattern 3: Environment Variables
|
||||
|
||||
```python
|
||||
import os
|
||||
from omero.gateway import BlitzGateway
|
||||
|
||||
# Get credentials from environment
|
||||
USERNAME = os.environ.get('OMERO_USER')
|
||||
PASSWORD = os.environ.get('OMERO_PASSWORD')
|
||||
HOST = os.environ.get('OMERO_HOST', 'localhost')
|
||||
PORT = int(os.environ.get('OMERO_PORT', 4064))
|
||||
|
||||
# Connect
|
||||
with BlitzGateway(USERNAME, PASSWORD, host=HOST, port=PORT) as conn:
|
||||
# Perform operations
|
||||
pass
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Context Managers**: Always prefer context managers for automatic cleanup
|
||||
2. **Secure Connections**: Use `secure=True` for production environments
|
||||
3. **Error Handling**: Wrap connection code in try-except blocks
|
||||
4. **Close Connections**: Always close connections when done
|
||||
5. **Group Context**: Set appropriate group context before queries
|
||||
6. **Credential Security**: Never hardcode credentials; use environment variables or config files
|
||||
7. **Connection Pooling**: For web applications, implement connection pooling
|
||||
8. **Timeouts**: Consider implementing connection timeouts for long-running operations
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Refused
|
||||
|
||||
```
|
||||
Unable to contact ORB
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
- Verify host and port are correct
|
||||
- Check firewall settings
|
||||
- Ensure OMERO server is running
|
||||
- Verify network connectivity
|
||||
|
||||
### Authentication Failed
|
||||
|
||||
```
|
||||
Cannot connect to server
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
- Verify username and password
|
||||
- Check user account is active
|
||||
- Verify group membership
|
||||
- Check server logs for details
|
||||
|
||||
### Session Timeout
|
||||
|
||||
**Solutions:**
|
||||
- Increase session timeout on server
|
||||
- Implement session keepalive
|
||||
- Reconnect on timeout
|
||||
- Use connection pools for long-running applications
|
||||
544
skills/omero-integration/references/data_access.md
Normal file
544
skills/omero-integration/references/data_access.md
Normal file
@@ -0,0 +1,544 @@
|
||||
# Data Access & Retrieval
|
||||
|
||||
This reference covers navigating OMERO's hierarchical data structure and retrieving objects.
|
||||
|
||||
## OMERO Data Hierarchy
|
||||
|
||||
### Standard Hierarchy
|
||||
|
||||
```
|
||||
Project
|
||||
└─ Dataset
|
||||
└─ Image
|
||||
```
|
||||
|
||||
### Screening Hierarchy
|
||||
|
||||
```
|
||||
Screen
|
||||
└─ Plate
|
||||
└─ Well
|
||||
└─ WellSample
|
||||
└─ Image
|
||||
```
|
||||
|
||||
## Listing Objects
|
||||
|
||||
### List Projects
|
||||
|
||||
```python
|
||||
# List all projects for current user
|
||||
for project in conn.listProjects():
|
||||
print(f"Project: {project.getName()} (ID: {project.getId()})")
|
||||
```
|
||||
|
||||
### List Projects with Filtering
|
||||
|
||||
```python
|
||||
# Get current user and group
|
||||
my_exp_id = conn.getUser().getId()
|
||||
default_group_id = conn.getEventContext().groupId
|
||||
|
||||
# List projects with filters
|
||||
for project in conn.getObjects("Project", opts={
|
||||
'owner': my_exp_id, # Filter by owner
|
||||
'group': default_group_id, # Filter by group
|
||||
'order_by': 'lower(obj.name)', # Sort alphabetically
|
||||
'limit': 10, # Limit results
|
||||
'offset': 0 # Pagination offset
|
||||
}):
|
||||
print(f"Project: {project.getName()}")
|
||||
```
|
||||
|
||||
### List Datasets
|
||||
|
||||
```python
|
||||
# List all datasets
|
||||
for dataset in conn.getObjects("Dataset"):
|
||||
print(f"Dataset: {dataset.getName()} (ID: {dataset.getId()})")
|
||||
|
||||
# List orphaned datasets (not in any project)
|
||||
for dataset in conn.getObjects("Dataset", opts={'orphaned': True}):
|
||||
print(f"Orphaned Dataset: {dataset.getName()}")
|
||||
```
|
||||
|
||||
### List Images
|
||||
|
||||
```python
|
||||
# List all images
|
||||
for image in conn.getObjects("Image"):
|
||||
print(f"Image: {image.getName()} (ID: {image.getId()})")
|
||||
|
||||
# List images in specific dataset
|
||||
dataset_id = 123
|
||||
for image in conn.getObjects("Image", opts={'dataset': dataset_id}):
|
||||
print(f"Image: {image.getName()}")
|
||||
|
||||
# List orphaned images
|
||||
for image in conn.getObjects("Image", opts={'orphaned': True}):
|
||||
print(f"Orphaned Image: {image.getName()}")
|
||||
```
|
||||
|
||||
## Retrieving Objects by ID
|
||||
|
||||
### Get Single Object
|
||||
|
||||
```python
|
||||
# Get project by ID
|
||||
project = conn.getObject("Project", project_id)
|
||||
if project:
|
||||
print(f"Project: {project.getName()}")
|
||||
else:
|
||||
print("Project not found")
|
||||
|
||||
# Get dataset by ID
|
||||
dataset = conn.getObject("Dataset", dataset_id)
|
||||
|
||||
# Get image by ID
|
||||
image = conn.getObject("Image", image_id)
|
||||
```
|
||||
|
||||
### Get Multiple Objects by ID
|
||||
|
||||
```python
|
||||
# Get multiple projects at once
|
||||
project_ids = [1, 2, 3, 4, 5]
|
||||
projects = conn.getObjects("Project", project_ids)
|
||||
|
||||
for project in projects:
|
||||
print(f"Project: {project.getName()}")
|
||||
```
|
||||
|
||||
### Supported Object Types
|
||||
|
||||
The `getObject()` and `getObjects()` methods support:
|
||||
- `"Project"`
|
||||
- `"Dataset"`
|
||||
- `"Image"`
|
||||
- `"Screen"`
|
||||
- `"Plate"`
|
||||
- `"Well"`
|
||||
- `"Roi"`
|
||||
- `"Annotation"` (and specific types: `"TagAnnotation"`, `"FileAnnotation"`, etc.)
|
||||
- `"Experimenter"`
|
||||
- `"ExperimenterGroup"`
|
||||
- `"Fileset"`
|
||||
|
||||
## Query by Attributes
|
||||
|
||||
### Query Objects by Name
|
||||
|
||||
```python
|
||||
# Find images with specific name
|
||||
images = conn.getObjects("Image", attributes={"name": "sample_001.tif"})
|
||||
|
||||
for image in images:
|
||||
print(f"Found image: {image.getName()} (ID: {image.getId()})")
|
||||
|
||||
# Find datasets with specific name
|
||||
datasets = conn.getObjects("Dataset", attributes={"name": "Control Group"})
|
||||
```
|
||||
|
||||
### Query Annotations by Value
|
||||
|
||||
```python
|
||||
# Find tags with specific text value
|
||||
tags = conn.getObjects("TagAnnotation",
|
||||
attributes={"textValue": "experiment_tag"})
|
||||
|
||||
for tag in tags:
|
||||
print(f"Tag: {tag.getValue()}")
|
||||
|
||||
# Find map annotations
|
||||
map_anns = conn.getObjects("MapAnnotation",
|
||||
attributes={"ns": "custom.namespace"})
|
||||
```
|
||||
|
||||
## Navigating Hierarchies
|
||||
|
||||
### Navigate Down (Parent to Children)
|
||||
|
||||
```python
|
||||
# Project → Datasets → Images
|
||||
project = conn.getObject("Project", project_id)
|
||||
|
||||
for dataset in project.listChildren():
|
||||
print(f"Dataset: {dataset.getName()}")
|
||||
|
||||
for image in dataset.listChildren():
|
||||
print(f" Image: {image.getName()}")
|
||||
```
|
||||
|
||||
### Navigate Up (Child to Parent)
|
||||
|
||||
```python
|
||||
# Image → Dataset → Project
|
||||
image = conn.getObject("Image", image_id)
|
||||
|
||||
# Get parent dataset
|
||||
dataset = image.getParent()
|
||||
if dataset:
|
||||
print(f"Dataset: {dataset.getName()}")
|
||||
|
||||
# Get parent project
|
||||
project = dataset.getParent()
|
||||
if project:
|
||||
print(f"Project: {project.getName()}")
|
||||
```
|
||||
|
||||
### Complete Hierarchy Traversal
|
||||
|
||||
```python
|
||||
# Traverse complete project hierarchy
|
||||
for project in conn.getObjects("Project", opts={'order_by': 'lower(obj.name)'}):
|
||||
print(f"Project: {project.getName()} (ID: {project.getId()})")
|
||||
|
||||
for dataset in project.listChildren():
|
||||
image_count = dataset.countChildren()
|
||||
print(f" Dataset: {dataset.getName()} ({image_count} images)")
|
||||
|
||||
for image in dataset.listChildren():
|
||||
print(f" Image: {image.getName()}")
|
||||
print(f" Size: {image.getSizeX()} x {image.getSizeY()}")
|
||||
print(f" Channels: {image.getSizeC()}")
|
||||
```
|
||||
|
||||
## Screening Data Access
|
||||
|
||||
### List Screens and Plates
|
||||
|
||||
```python
|
||||
# List all screens
|
||||
for screen in conn.getObjects("Screen"):
|
||||
print(f"Screen: {screen.getName()} (ID: {screen.getId()})")
|
||||
|
||||
# List plates in screen
|
||||
for plate in screen.listChildren():
|
||||
print(f" Plate: {plate.getName()} (ID: {plate.getId()})")
|
||||
```
|
||||
|
||||
### Access Plate Wells
|
||||
|
||||
```python
|
||||
# Get plate
|
||||
plate = conn.getObject("Plate", plate_id)
|
||||
|
||||
# Plate metadata
|
||||
print(f"Plate: {plate.getName()}")
|
||||
print(f"Grid size: {plate.getGridSize()}") # e.g., (8, 12) for 96-well
|
||||
print(f"Number of fields: {plate.getNumberOfFields()}")
|
||||
|
||||
# Iterate through wells
|
||||
for well in plate.listChildren():
|
||||
print(f"Well at row {well.row}, column {well.column}")
|
||||
|
||||
# Count images in well (fields)
|
||||
field_count = well.countWellSample()
|
||||
print(f" Number of fields: {field_count}")
|
||||
|
||||
# Access images in well
|
||||
for index in range(field_count):
|
||||
image = well.getImage(index)
|
||||
print(f" Field {index}: {image.getName()}")
|
||||
```
|
||||
|
||||
### Direct Well Access
|
||||
|
||||
```python
|
||||
# Get specific well by row and column
|
||||
well = plate.getWell(row=0, column=0) # Top-left well
|
||||
|
||||
# Get image from well
|
||||
if well.countWellSample() > 0:
|
||||
image = well.getImage(0) # First field
|
||||
print(f"Image: {image.getName()}")
|
||||
```
|
||||
|
||||
### Well Sample Access
|
||||
|
||||
```python
|
||||
# Access well samples directly
|
||||
for well in plate.listChildren():
|
||||
for ws in well.listChildren(): # ws = WellSample
|
||||
image = ws.getImage()
|
||||
print(f"WellSample {ws.getId()}: {image.getName()}")
|
||||
```
|
||||
|
||||
## Image Properties
|
||||
|
||||
### Basic Dimensions
|
||||
|
||||
```python
|
||||
image = conn.getObject("Image", image_id)
|
||||
|
||||
# Pixel dimensions
|
||||
print(f"X: {image.getSizeX()}")
|
||||
print(f"Y: {image.getSizeY()}")
|
||||
print(f"Z: {image.getSizeZ()} (Z-sections)")
|
||||
print(f"C: {image.getSizeC()} (Channels)")
|
||||
print(f"T: {image.getSizeT()} (Time points)")
|
||||
|
||||
# Image type
|
||||
print(f"Type: {image.getPixelsType()}") # e.g., 'uint16', 'uint8'
|
||||
```
|
||||
|
||||
### Physical Dimensions
|
||||
|
||||
```python
|
||||
# Get pixel sizes with units (OMERO 5.1.0+)
|
||||
size_x_obj = image.getPixelSizeX(units=True)
|
||||
size_y_obj = image.getPixelSizeY(units=True)
|
||||
size_z_obj = image.getPixelSizeZ(units=True)
|
||||
|
||||
print(f"Pixel Size X: {size_x_obj.getValue()} {size_x_obj.getSymbol()}")
|
||||
print(f"Pixel Size Y: {size_y_obj.getValue()} {size_y_obj.getSymbol()}")
|
||||
print(f"Pixel Size Z: {size_z_obj.getValue()} {size_z_obj.getSymbol()}")
|
||||
|
||||
# Get as floats (micrometers)
|
||||
size_x = image.getPixelSizeX() # Returns float in µm
|
||||
size_y = image.getPixelSizeY()
|
||||
size_z = image.getPixelSizeZ()
|
||||
```
|
||||
|
||||
### Channel Information
|
||||
|
||||
```python
|
||||
# Iterate through channels
|
||||
for channel in image.getChannels():
|
||||
print(f"Channel {channel.getLabel()}:")
|
||||
print(f" Color: {channel.getColor().getRGB()}")
|
||||
print(f" Lookup Table: {channel.getLut()}")
|
||||
print(f" Wavelength: {channel.getEmissionWave()}")
|
||||
```
|
||||
|
||||
### Image Metadata
|
||||
|
||||
```python
|
||||
# Acquisition date
|
||||
acquired = image.getAcquisitionDate()
|
||||
if acquired:
|
||||
print(f"Acquired: {acquired}")
|
||||
|
||||
# Description
|
||||
description = image.getDescription()
|
||||
if description:
|
||||
print(f"Description: {description}")
|
||||
|
||||
# Owner and group
|
||||
details = image.getDetails()
|
||||
print(f"Owner: {details.getOwner().getFullName()}")
|
||||
print(f"Username: {details.getOwner().getOmeName()}")
|
||||
print(f"Group: {details.getGroup().getName()}")
|
||||
print(f"Created: {details.getCreationEvent().getTime()}")
|
||||
```
|
||||
|
||||
## Object Ownership and Permissions
|
||||
|
||||
### Get Owner Information
|
||||
|
||||
```python
|
||||
# Get object owner
|
||||
obj = conn.getObject("Dataset", dataset_id)
|
||||
owner = obj.getDetails().getOwner()
|
||||
|
||||
print(f"Owner ID: {owner.getId()}")
|
||||
print(f"Username: {owner.getOmeName()}")
|
||||
print(f"Full Name: {owner.getFullName()}")
|
||||
print(f"Email: {owner.getEmail()}")
|
||||
```
|
||||
|
||||
### Get Group Information
|
||||
|
||||
```python
|
||||
# Get object's group
|
||||
obj = conn.getObject("Image", image_id)
|
||||
group = obj.getDetails().getGroup()
|
||||
|
||||
print(f"Group: {group.getName()} (ID: {group.getId()})")
|
||||
```
|
||||
|
||||
### Filter by Owner
|
||||
|
||||
```python
|
||||
# Get objects for specific user
|
||||
user_id = 5
|
||||
datasets = conn.getObjects("Dataset", opts={'owner': user_id})
|
||||
|
||||
for dataset in datasets:
|
||||
print(f"Dataset: {dataset.getName()}")
|
||||
```
|
||||
|
||||
## Advanced Queries
|
||||
|
||||
### Pagination
|
||||
|
||||
```python
|
||||
# Paginate through large result sets
|
||||
page_size = 50
|
||||
offset = 0
|
||||
|
||||
while True:
|
||||
images = list(conn.getObjects("Image", opts={
|
||||
'limit': page_size,
|
||||
'offset': offset,
|
||||
'order_by': 'obj.id'
|
||||
}))
|
||||
|
||||
if not images:
|
||||
break
|
||||
|
||||
for image in images:
|
||||
print(f"Image: {image.getName()}")
|
||||
|
||||
offset += page_size
|
||||
```
|
||||
|
||||
### Sorting Results
|
||||
|
||||
```python
|
||||
# Sort by name (case-insensitive)
|
||||
projects = conn.getObjects("Project", opts={
|
||||
'order_by': 'lower(obj.name)'
|
||||
})
|
||||
|
||||
# Sort by ID (ascending)
|
||||
datasets = conn.getObjects("Dataset", opts={
|
||||
'order_by': 'obj.id'
|
||||
})
|
||||
|
||||
# Sort by name (descending)
|
||||
images = conn.getObjects("Image", opts={
|
||||
'order_by': 'lower(obj.name) desc'
|
||||
})
|
||||
```
|
||||
|
||||
### Combining Filters
|
||||
|
||||
```python
|
||||
# Complex query with multiple filters
|
||||
my_exp_id = conn.getUser().getId()
|
||||
default_group_id = conn.getEventContext().groupId
|
||||
|
||||
images = conn.getObjects("Image", opts={
|
||||
'owner': my_exp_id,
|
||||
'group': default_group_id,
|
||||
'dataset': dataset_id,
|
||||
'order_by': 'lower(obj.name)',
|
||||
'limit': 100,
|
||||
'offset': 0
|
||||
})
|
||||
```
|
||||
|
||||
## Counting Objects
|
||||
|
||||
### Count Children
|
||||
|
||||
```python
|
||||
# Count images in dataset
|
||||
dataset = conn.getObject("Dataset", dataset_id)
|
||||
image_count = dataset.countChildren()
|
||||
print(f"Dataset contains {image_count} images")
|
||||
|
||||
# Count datasets in project
|
||||
project = conn.getObject("Project", project_id)
|
||||
dataset_count = project.countChildren()
|
||||
print(f"Project contains {dataset_count} datasets")
|
||||
```
|
||||
|
||||
### Count Annotations
|
||||
|
||||
```python
|
||||
# Count annotations on object
|
||||
image = conn.getObject("Image", image_id)
|
||||
annotation_count = image.countAnnotations()
|
||||
print(f"Image has {annotation_count} annotations")
|
||||
```
|
||||
|
||||
## Orphaned Objects
|
||||
|
||||
### Find Orphaned Datasets
|
||||
|
||||
```python
|
||||
# Datasets not linked to any project
|
||||
orphaned_datasets = conn.getObjects("Dataset", opts={'orphaned': True})
|
||||
|
||||
print("Orphaned Datasets:")
|
||||
for dataset in orphaned_datasets:
|
||||
print(f" {dataset.getName()} (ID: {dataset.getId()})")
|
||||
print(f" Owner: {dataset.getDetails().getOwner().getOmeName()}")
|
||||
print(f" Images: {dataset.countChildren()}")
|
||||
```
|
||||
|
||||
### Find Orphaned Images
|
||||
|
||||
```python
|
||||
# Images not in any dataset
|
||||
orphaned_images = conn.getObjects("Image", opts={'orphaned': True})
|
||||
|
||||
print("Orphaned Images:")
|
||||
for image in orphaned_images:
|
||||
print(f" {image.getName()} (ID: {image.getId()})")
|
||||
```
|
||||
|
||||
### Find Orphaned Plates
|
||||
|
||||
```python
|
||||
# Plates not in any screen
|
||||
orphaned_plates = conn.getObjects("Plate", opts={'orphaned': True})
|
||||
|
||||
for plate in orphaned_plates:
|
||||
print(f"Orphaned Plate: {plate.getName()}")
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
```python
|
||||
from omero.gateway import BlitzGateway
|
||||
|
||||
# Connection details
|
||||
HOST = 'omero.example.com'
|
||||
PORT = 4064
|
||||
USERNAME = 'user'
|
||||
PASSWORD = 'pass'
|
||||
|
||||
# Connect and query data
|
||||
with BlitzGateway(USERNAME, PASSWORD, host=HOST, port=PORT) as conn:
|
||||
# Get user context
|
||||
user = conn.getUser()
|
||||
group = conn.getGroupFromContext()
|
||||
|
||||
print(f"Connected as {user.getName()} in group {group.getName()}")
|
||||
print()
|
||||
|
||||
# List projects with datasets and images
|
||||
for project in conn.getObjects("Project", opts={'limit': 5}):
|
||||
print(f"Project: {project.getName()} (ID: {project.getId()})")
|
||||
|
||||
for dataset in project.listChildren():
|
||||
image_count = dataset.countChildren()
|
||||
print(f" Dataset: {dataset.getName()} ({image_count} images)")
|
||||
|
||||
# Show first 3 images
|
||||
for idx, image in enumerate(dataset.listChildren()):
|
||||
if idx >= 3:
|
||||
print(f" ... and {image_count - 3} more")
|
||||
break
|
||||
print(f" Image: {image.getName()}")
|
||||
print(f" Size: {image.getSizeX()}x{image.getSizeY()}")
|
||||
print(f" Channels: {image.getSizeC()}, Z: {image.getSizeZ()}")
|
||||
|
||||
print()
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Context Managers**: Always use `with` statements for automatic connection cleanup
|
||||
2. **Limit Results**: Use `limit` and `offset` for large datasets
|
||||
3. **Filter Early**: Apply filters to reduce data transfer
|
||||
4. **Check for None**: Always check if `getObject()` returns None before using
|
||||
5. **Efficient Traversal**: Use `listChildren()` instead of querying separately
|
||||
6. **Count Before Loading**: Use `countChildren()` to decide whether to load data
|
||||
7. **Group Context**: Set appropriate group context before cross-group queries
|
||||
8. **Pagination**: Implement pagination for large result sets
|
||||
9. **Object Reuse**: Cache frequently accessed objects to reduce queries
|
||||
10. **Error Handling**: Wrap queries in try-except blocks for robustness
|
||||
665
skills/omero-integration/references/image_processing.md
Normal file
665
skills/omero-integration/references/image_processing.md
Normal file
@@ -0,0 +1,665 @@
|
||||
# Image Processing & Rendering
|
||||
|
||||
This reference covers accessing raw pixel data, image rendering, and creating new images in OMERO.
|
||||
|
||||
## Accessing Raw Pixel Data
|
||||
|
||||
### Get Single Plane
|
||||
|
||||
```python
|
||||
# Get image
|
||||
image = conn.getObject("Image", image_id)
|
||||
|
||||
# Get dimensions
|
||||
size_z = image.getSizeZ()
|
||||
size_c = image.getSizeC()
|
||||
size_t = image.getSizeT()
|
||||
|
||||
# Get pixels object
|
||||
pixels = image.getPrimaryPixels()
|
||||
|
||||
# Get single plane (returns NumPy array)
|
||||
z, c, t = 0, 0, 0 # First Z-section, channel, and timepoint
|
||||
plane = pixels.getPlane(z, c, t)
|
||||
|
||||
print(f"Shape: {plane.shape}")
|
||||
print(f"Data type: {plane.dtype.name}")
|
||||
print(f"Min: {plane.min()}, Max: {plane.max()}")
|
||||
```
|
||||
|
||||
### Get Multiple Planes
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
|
||||
# Get Z-stack for specific channel and timepoint
|
||||
pixels = image.getPrimaryPixels()
|
||||
c, t = 0, 0 # First channel and timepoint
|
||||
|
||||
# Build list of (z, c, t) coordinates
|
||||
zct_list = [(z, c, t) for z in range(size_z)]
|
||||
|
||||
# Get all planes at once
|
||||
planes = pixels.getPlanes(zct_list)
|
||||
|
||||
# Stack into 3D array
|
||||
z_stack = np.array([p for p in planes])
|
||||
print(f"Z-stack shape: {z_stack.shape}")
|
||||
```
|
||||
|
||||
### Get Hypercube (Subset of 5D Data)
|
||||
|
||||
```python
|
||||
# Get subset of 5D data (Z, C, T)
|
||||
zct_list = []
|
||||
for z in range(size_z // 2, size_z): # Second half of Z
|
||||
for c in range(size_c): # All channels
|
||||
for t in range(size_t): # All timepoints
|
||||
zct_list.append((z, c, t))
|
||||
|
||||
# Get planes
|
||||
planes = pixels.getPlanes(zct_list)
|
||||
|
||||
# Process each plane
|
||||
for i, plane in enumerate(planes):
|
||||
z, c, t = zct_list[i]
|
||||
print(f"Plane Z={z}, C={c}, T={t}: Min={plane.min()}, Max={plane.max()}")
|
||||
```
|
||||
|
||||
### Get Tile (Region of Interest)
|
||||
|
||||
```python
|
||||
# Define tile coordinates
|
||||
x, y = 50, 50 # Top-left corner
|
||||
width, height = 100, 100 # Tile size
|
||||
tile = (x, y, width, height)
|
||||
|
||||
# Get tile for specific Z, C, T
|
||||
z, c, t = 0, 0, 0
|
||||
zct_list = [(z, c, t, tile)]
|
||||
|
||||
tiles = pixels.getTiles(zct_list)
|
||||
tile_data = tiles[0]
|
||||
|
||||
print(f"Tile shape: {tile_data.shape}") # Should be (height, width)
|
||||
```
|
||||
|
||||
### Get Multiple Tiles
|
||||
|
||||
```python
|
||||
# Get tiles from Z-stack
|
||||
c, t = 0, 0
|
||||
tile = (50, 50, 100, 100) # x, y, width, height
|
||||
|
||||
# Build list with tiles
|
||||
zct_list = [(z, c, t, tile) for z in range(size_z)]
|
||||
|
||||
tiles = pixels.getTiles(zct_list)
|
||||
|
||||
for i, tile_data in enumerate(tiles):
|
||||
print(f"Tile Z={i}: {tile_data.shape}, Min={tile_data.min()}")
|
||||
```
|
||||
|
||||
## Image Histograms
|
||||
|
||||
### Get Histogram
|
||||
|
||||
```python
|
||||
# Get histogram for first channel
|
||||
channel_index = 0
|
||||
num_bins = 256
|
||||
z, t = 0, 0
|
||||
|
||||
histogram = image.getHistogram([channel_index], num_bins, False, z, t)
|
||||
print(f"Histogram bins: {len(histogram)}")
|
||||
print(f"First 10 bins: {histogram[:10]}")
|
||||
```
|
||||
|
||||
### Multi-Channel Histogram
|
||||
|
||||
```python
|
||||
# Get histograms for all channels
|
||||
channels = list(range(image.getSizeC()))
|
||||
histograms = image.getHistogram(channels, 256, False, 0, 0)
|
||||
|
||||
for c, hist in enumerate(histograms):
|
||||
print(f"Channel {c}: Total pixels = {sum(hist)}")
|
||||
```
|
||||
|
||||
## Image Rendering
|
||||
|
||||
### Render Image with Current Settings
|
||||
|
||||
```python
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
|
||||
# Get image
|
||||
image = conn.getObject("Image", image_id)
|
||||
|
||||
# Render at specific Z and T
|
||||
z = image.getSizeZ() // 2 # Middle Z-section
|
||||
t = 0
|
||||
|
||||
rendered_image = image.renderImage(z, t)
|
||||
# rendered_image is a PIL Image object
|
||||
rendered_image.save("rendered_image.jpg")
|
||||
```
|
||||
|
||||
### Get Thumbnail
|
||||
|
||||
```python
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
|
||||
# Get thumbnail (uses current rendering settings)
|
||||
thumbnail_data = image.getThumbnail()
|
||||
|
||||
# Convert to PIL Image
|
||||
thumbnail = Image.open(BytesIO(thumbnail_data))
|
||||
thumbnail.save("thumbnail.jpg")
|
||||
|
||||
# Get specific thumbnail size
|
||||
thumbnail_data = image.getThumbnail(size=(96, 96))
|
||||
thumbnail = Image.open(BytesIO(thumbnail_data))
|
||||
```
|
||||
|
||||
## Rendering Settings
|
||||
|
||||
### View Current Settings
|
||||
|
||||
```python
|
||||
# Display rendering settings
|
||||
print("Current Rendering Settings:")
|
||||
print(f"Grayscale mode: {image.isGreyscaleRenderingModel()}")
|
||||
print(f"Default Z: {image.getDefaultZ()}")
|
||||
print(f"Default T: {image.getDefaultT()}")
|
||||
print()
|
||||
|
||||
# Channel settings
|
||||
print("Channel Settings:")
|
||||
for idx, channel in enumerate(image.getChannels()):
|
||||
print(f"Channel {idx + 1}:")
|
||||
print(f" Label: {channel.getLabel()}")
|
||||
print(f" Color: {channel.getColor().getHtml()}")
|
||||
print(f" Active: {channel.isActive()}")
|
||||
print(f" Window: {channel.getWindowStart()} - {channel.getWindowEnd()}")
|
||||
print(f" Min/Max: {channel.getWindowMin()} - {channel.getWindowMax()}")
|
||||
```
|
||||
|
||||
### Set Rendering Model
|
||||
|
||||
```python
|
||||
# Switch to grayscale rendering
|
||||
image.setGreyscaleRenderingModel()
|
||||
|
||||
# Switch to color rendering
|
||||
image.setColorRenderingModel()
|
||||
```
|
||||
|
||||
### Set Active Channels
|
||||
|
||||
```python
|
||||
# Activate specific channels (1-indexed)
|
||||
image.setActiveChannels([1, 3]) # Channels 1 and 3 only
|
||||
|
||||
# Activate all channels
|
||||
all_channels = list(range(1, image.getSizeC() + 1))
|
||||
image.setActiveChannels(all_channels)
|
||||
|
||||
# Activate single channel
|
||||
image.setActiveChannels([2])
|
||||
```
|
||||
|
||||
### Set Channel Colors
|
||||
|
||||
```python
|
||||
# Set channel colors (hex format)
|
||||
channels = [1, 2, 3]
|
||||
colors = ['FF0000', '00FF00', '0000FF'] # Red, Green, Blue
|
||||
|
||||
image.setActiveChannels(channels, colors=colors)
|
||||
|
||||
# Use None to keep existing color
|
||||
colors = ['FF0000', None, '0000FF'] # Keep channel 2's color
|
||||
image.setActiveChannels(channels, colors=colors)
|
||||
```
|
||||
|
||||
### Set Channel Window (Intensity Range)
|
||||
|
||||
```python
|
||||
# Set intensity windows for channels
|
||||
channels = [1, 2]
|
||||
windows = [
|
||||
[100.0, 500.0], # Channel 1: 100-500
|
||||
[50.0, 300.0] # Channel 2: 50-300
|
||||
]
|
||||
|
||||
image.setActiveChannels(channels, windows=windows)
|
||||
|
||||
# Use None to keep existing window
|
||||
windows = [[100.0, 500.0], [None, None]]
|
||||
image.setActiveChannels(channels, windows=windows)
|
||||
```
|
||||
|
||||
### Set Default Z and T
|
||||
|
||||
```python
|
||||
# Set default Z-section and timepoint
|
||||
image.setDefaultZ(5)
|
||||
image.setDefaultT(0)
|
||||
|
||||
# Render using defaults
|
||||
rendered_image = image.renderImage(z=None, t=None)
|
||||
rendered_image.save("default_rendering.jpg")
|
||||
```
|
||||
|
||||
## Render Individual Channels
|
||||
|
||||
### Render Each Channel Separately
|
||||
|
||||
```python
|
||||
# Set grayscale mode
|
||||
image.setGreyscaleRenderingModel()
|
||||
|
||||
z = image.getSizeZ() // 2
|
||||
t = 0
|
||||
|
||||
# Render each channel
|
||||
for c in range(1, image.getSizeC() + 1):
|
||||
image.setActiveChannels([c])
|
||||
rendered = image.renderImage(z, t)
|
||||
rendered.save(f"channel_{c}.jpg")
|
||||
```
|
||||
|
||||
### Render Multi-Channel Composites
|
||||
|
||||
```python
|
||||
# Color composite of first 3 channels
|
||||
image.setColorRenderingModel()
|
||||
channels = [1, 2, 3]
|
||||
colors = ['FF0000', '00FF00', '0000FF'] # RGB
|
||||
|
||||
image.setActiveChannels(channels, colors=colors)
|
||||
rendered = image.renderImage(z, t)
|
||||
rendered.save("rgb_composite.jpg")
|
||||
```
|
||||
|
||||
## Image Projections
|
||||
|
||||
### Maximum Intensity Projection
|
||||
|
||||
```python
|
||||
# Set projection type
|
||||
image.setProjection('intmax')
|
||||
|
||||
# Render (projects across all Z)
|
||||
z, t = 0, 0 # Z is ignored for projections
|
||||
rendered = image.renderImage(z, t)
|
||||
rendered.save("max_projection.jpg")
|
||||
|
||||
# Reset to normal rendering
|
||||
image.setProjection('normal')
|
||||
```
|
||||
|
||||
### Mean Intensity Projection
|
||||
|
||||
```python
|
||||
image.setProjection('intmean')
|
||||
rendered = image.renderImage(z, t)
|
||||
rendered.save("mean_projection.jpg")
|
||||
image.setProjection('normal')
|
||||
```
|
||||
|
||||
### Available Projection Types
|
||||
|
||||
- `'normal'`: No projection (default)
|
||||
- `'intmax'`: Maximum intensity projection
|
||||
- `'intmean'`: Mean intensity projection
|
||||
- `'intmin'`: Minimum intensity projection (if supported)
|
||||
|
||||
## Save and Reset Rendering Settings
|
||||
|
||||
### Save Current Settings as Default
|
||||
|
||||
```python
|
||||
# Modify rendering settings
|
||||
image.setActiveChannels([1, 2])
|
||||
image.setDefaultZ(5)
|
||||
|
||||
# Save as new default
|
||||
image.saveDefaults()
|
||||
```
|
||||
|
||||
### Reset to Import Settings
|
||||
|
||||
```python
|
||||
# Reset to original import settings
|
||||
image.resetDefaults(save=True)
|
||||
```
|
||||
|
||||
## Create Images from NumPy Arrays
|
||||
|
||||
### Create Simple Image
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
|
||||
# Create sample data
|
||||
size_x, size_y = 512, 512
|
||||
size_z, size_c, size_t = 10, 2, 1
|
||||
|
||||
# Generate planes
|
||||
def plane_generator():
|
||||
"""Generator that yields planes"""
|
||||
for z in range(size_z):
|
||||
for c in range(size_c):
|
||||
for t in range(size_t):
|
||||
# Create synthetic data
|
||||
plane = np.random.randint(0, 255, (size_y, size_x), dtype=np.uint8)
|
||||
yield plane
|
||||
|
||||
# Create image
|
||||
image = conn.createImageFromNumpySeq(
|
||||
plane_generator(),
|
||||
"Test Image",
|
||||
size_z, size_c, size_t,
|
||||
description="Image created from NumPy arrays",
|
||||
dataset=None
|
||||
)
|
||||
|
||||
print(f"Created image ID: {image.getId()}")
|
||||
```
|
||||
|
||||
### Create Image from Hard-Coded Arrays
|
||||
|
||||
```python
|
||||
from numpy import array, int8
|
||||
|
||||
# Define dimensions
|
||||
size_x, size_y = 5, 4
|
||||
size_z, size_c, size_t = 1, 2, 1
|
||||
|
||||
# Create planes
|
||||
plane1 = array(
|
||||
[[0, 1, 2, 3, 4],
|
||||
[5, 6, 7, 8, 9],
|
||||
[0, 1, 2, 3, 4],
|
||||
[5, 6, 7, 8, 9]],
|
||||
dtype=int8
|
||||
)
|
||||
|
||||
plane2 = array(
|
||||
[[5, 6, 7, 8, 9],
|
||||
[0, 1, 2, 3, 4],
|
||||
[5, 6, 7, 8, 9],
|
||||
[0, 1, 2, 3, 4]],
|
||||
dtype=int8
|
||||
)
|
||||
|
||||
planes = [plane1, plane2]
|
||||
|
||||
def plane_gen():
|
||||
for p in planes:
|
||||
yield p
|
||||
|
||||
# Create image
|
||||
desc = "Image created from hard-coded arrays"
|
||||
image = conn.createImageFromNumpySeq(
|
||||
plane_gen(),
|
||||
"numpy_image",
|
||||
size_z, size_c, size_t,
|
||||
description=desc,
|
||||
dataset=None
|
||||
)
|
||||
|
||||
print(f"Created image: {image.getName()} (ID: {image.getId()})")
|
||||
```
|
||||
|
||||
### Create Image in Dataset
|
||||
|
||||
```python
|
||||
# Get target dataset
|
||||
dataset = conn.getObject("Dataset", dataset_id)
|
||||
|
||||
# Create image
|
||||
image = conn.createImageFromNumpySeq(
|
||||
plane_generator(),
|
||||
"New Analysis Result",
|
||||
size_z, size_c, size_t,
|
||||
description="Result from analysis pipeline",
|
||||
dataset=dataset # Add to dataset
|
||||
)
|
||||
```
|
||||
|
||||
### Create Derived Image
|
||||
|
||||
```python
|
||||
# Get source image
|
||||
source = conn.getObject("Image", source_image_id)
|
||||
size_z = source.getSizeZ()
|
||||
size_c = source.getSizeC()
|
||||
size_t = source.getSizeT()
|
||||
dataset = source.getParent()
|
||||
|
||||
pixels = source.getPrimaryPixels()
|
||||
new_size_c = 1 # Average channels
|
||||
|
||||
def plane_gen():
|
||||
"""Average channels together"""
|
||||
for z in range(size_z):
|
||||
for c in range(new_size_c):
|
||||
for t in range(size_t):
|
||||
# Get multiple channels
|
||||
channel0 = pixels.getPlane(z, 0, t)
|
||||
channel1 = pixels.getPlane(z, 1, t)
|
||||
|
||||
# Combine
|
||||
new_plane = (channel0.astype(float) + channel1.astype(float)) / 2
|
||||
new_plane = new_plane.astype(channel0.dtype)
|
||||
|
||||
yield new_plane
|
||||
|
||||
# Create new image
|
||||
desc = "Averaged channels from source image"
|
||||
derived = conn.createImageFromNumpySeq(
|
||||
plane_gen(),
|
||||
f"{source.getName()}_averaged",
|
||||
size_z, new_size_c, size_t,
|
||||
description=desc,
|
||||
dataset=dataset
|
||||
)
|
||||
|
||||
print(f"Created derived image: {derived.getId()}")
|
||||
```
|
||||
|
||||
## Set Physical Dimensions
|
||||
|
||||
### Set Pixel Sizes with Units
|
||||
|
||||
```python
|
||||
from omero.model.enums import UnitsLength
|
||||
import omero.model
|
||||
|
||||
# Get image
|
||||
image = conn.getObject("Image", image_id)
|
||||
|
||||
# Create unit objects
|
||||
pixel_size_x = omero.model.LengthI(0.325, UnitsLength.MICROMETER)
|
||||
pixel_size_y = omero.model.LengthI(0.325, UnitsLength.MICROMETER)
|
||||
pixel_size_z = omero.model.LengthI(1.0, UnitsLength.MICROMETER)
|
||||
|
||||
# Get pixels object
|
||||
pixels = image.getPrimaryPixels()._obj
|
||||
|
||||
# Set physical sizes
|
||||
pixels.setPhysicalSizeX(pixel_size_x)
|
||||
pixels.setPhysicalSizeY(pixel_size_y)
|
||||
pixels.setPhysicalSizeZ(pixel_size_z)
|
||||
|
||||
# Save changes
|
||||
conn.getUpdateService().saveObject(pixels)
|
||||
|
||||
print("Updated pixel dimensions")
|
||||
```
|
||||
|
||||
### Available Length Units
|
||||
|
||||
From `omero.model.enums.UnitsLength`:
|
||||
- `ANGSTROM`
|
||||
- `NANOMETER`
|
||||
- `MICROMETER`
|
||||
- `MILLIMETER`
|
||||
- `CENTIMETER`
|
||||
- `METER`
|
||||
- `PIXEL`
|
||||
|
||||
### Set Pixel Size on New Image
|
||||
|
||||
```python
|
||||
from omero.model.enums import UnitsLength
|
||||
import omero.model
|
||||
|
||||
# Create image
|
||||
image = conn.createImageFromNumpySeq(
|
||||
plane_generator(),
|
||||
"New Image with Dimensions",
|
||||
size_z, size_c, size_t
|
||||
)
|
||||
|
||||
# Set pixel sizes
|
||||
pixel_size = omero.model.LengthI(0.5, UnitsLength.MICROMETER)
|
||||
pixels = image.getPrimaryPixels()._obj
|
||||
pixels.setPhysicalSizeX(pixel_size)
|
||||
pixels.setPhysicalSizeY(pixel_size)
|
||||
|
||||
z_size = omero.model.LengthI(2.0, UnitsLength.MICROMETER)
|
||||
pixels.setPhysicalSizeZ(z_size)
|
||||
|
||||
conn.getUpdateService().saveObject(pixels)
|
||||
```
|
||||
|
||||
## Complete Example: Image Processing Pipeline
|
||||
|
||||
```python
|
||||
from omero.gateway import BlitzGateway
|
||||
import numpy as np
|
||||
|
||||
HOST = 'omero.example.com'
|
||||
PORT = 4064
|
||||
USERNAME = 'user'
|
||||
PASSWORD = 'pass'
|
||||
|
||||
with BlitzGateway(USERNAME, PASSWORD, host=HOST, port=PORT) as conn:
|
||||
# Get source image
|
||||
source = conn.getObject("Image", source_image_id)
|
||||
print(f"Processing: {source.getName()}")
|
||||
|
||||
# Get dimensions
|
||||
size_x = source.getSizeX()
|
||||
size_y = source.getSizeY()
|
||||
size_z = source.getSizeZ()
|
||||
size_c = source.getSizeC()
|
||||
size_t = source.getSizeT()
|
||||
|
||||
pixels = source.getPrimaryPixels()
|
||||
|
||||
# Process: Maximum intensity projection over Z
|
||||
def plane_gen():
|
||||
for c in range(size_c):
|
||||
for t in range(size_t):
|
||||
# Get all Z planes for this C, T
|
||||
z_stack = []
|
||||
for z in range(size_z):
|
||||
plane = pixels.getPlane(z, c, t)
|
||||
z_stack.append(plane)
|
||||
|
||||
# Maximum projection
|
||||
max_proj = np.max(z_stack, axis=0)
|
||||
yield max_proj
|
||||
|
||||
# Create result image (single Z-section)
|
||||
result = conn.createImageFromNumpySeq(
|
||||
plane_gen(),
|
||||
f"{source.getName()}_MIP",
|
||||
1, size_c, size_t, # Z=1 for projection
|
||||
description="Maximum intensity projection",
|
||||
dataset=source.getParent()
|
||||
)
|
||||
|
||||
print(f"Created MIP image: {result.getId()}")
|
||||
|
||||
# Copy pixel sizes (X and Y only, no Z for projection)
|
||||
from omero.model.enums import UnitsLength
|
||||
import omero.model
|
||||
|
||||
source_pixels = source.getPrimaryPixels()._obj
|
||||
result_pixels = result.getPrimaryPixels()._obj
|
||||
|
||||
result_pixels.setPhysicalSizeX(source_pixels.getPhysicalSizeX())
|
||||
result_pixels.setPhysicalSizeY(source_pixels.getPhysicalSizeY())
|
||||
|
||||
conn.getUpdateService().saveObject(result_pixels)
|
||||
|
||||
print("Processing complete")
|
||||
```
|
||||
|
||||
## Working with Different Data Types
|
||||
|
||||
### Handle Various Pixel Types
|
||||
|
||||
```python
|
||||
# Get pixel type
|
||||
pixel_type = image.getPixelsType()
|
||||
print(f"Pixel type: {pixel_type}")
|
||||
|
||||
# Common types: uint8, uint16, uint32, int8, int16, int32, float, double
|
||||
|
||||
# Get plane with correct dtype
|
||||
plane = pixels.getPlane(z, c, t)
|
||||
print(f"NumPy dtype: {plane.dtype}")
|
||||
|
||||
# Convert if needed for processing
|
||||
if plane.dtype == np.uint16:
|
||||
# Convert to float for processing
|
||||
plane_float = plane.astype(np.float32)
|
||||
# Process...
|
||||
# Convert back
|
||||
result = plane_float.astype(np.uint16)
|
||||
```
|
||||
|
||||
### Handle Large Images
|
||||
|
||||
```python
|
||||
# Process large images in tiles to save memory
|
||||
tile_size = 512
|
||||
size_x = image.getSizeX()
|
||||
size_y = image.getSizeY()
|
||||
|
||||
for y in range(0, size_y, tile_size):
|
||||
for x in range(0, size_x, tile_size):
|
||||
# Get tile dimensions
|
||||
w = min(tile_size, size_x - x)
|
||||
h = min(tile_size, size_y - y)
|
||||
tile = (x, y, w, h)
|
||||
|
||||
# Get tile data
|
||||
zct_list = [(z, c, t, tile)]
|
||||
tile_data = pixels.getTiles(zct_list)[0]
|
||||
|
||||
# Process tile
|
||||
# ...
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Generators**: For creating images, use generators to avoid loading all data in memory
|
||||
2. **Specify Data Types**: Match NumPy dtypes to OMERO pixel types
|
||||
3. **Set Physical Dimensions**: Always set pixel sizes for new images
|
||||
4. **Tile Large Images**: Process large images in tiles to manage memory
|
||||
5. **Close Connections**: Always close connections when done
|
||||
6. **Rendering Efficiency**: Cache rendering settings when rendering multiple images
|
||||
7. **Channel Indexing**: Remember channels are 1-indexed for rendering, 0-indexed for pixel access
|
||||
8. **Save Settings**: Save rendering settings if they should be permanent
|
||||
9. **Compression**: Use compression parameter in renderImage() for faster transfers
|
||||
10. **Error Handling**: Check for None returns and handle exceptions
|
||||
688
skills/omero-integration/references/metadata.md
Normal file
688
skills/omero-integration/references/metadata.md
Normal file
@@ -0,0 +1,688 @@
|
||||
# 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
|
||||
648
skills/omero-integration/references/rois.md
Normal file
648
skills/omero-integration/references/rois.md
Normal file
@@ -0,0 +1,648 @@
|
||||
# Regions of Interest (ROIs)
|
||||
|
||||
This reference covers creating, retrieving, and analyzing ROIs in OMERO.
|
||||
|
||||
## ROI Overview
|
||||
|
||||
ROIs (Regions of Interest) in OMERO are containers for geometric shapes that mark specific regions on images. Each ROI can contain multiple shapes, and shapes can be specific to Z-sections and timepoints.
|
||||
|
||||
### Supported Shape Types
|
||||
|
||||
- **Rectangle**: Rectangular regions
|
||||
- **Ellipse**: Circular and elliptical regions
|
||||
- **Line**: Line segments
|
||||
- **Point**: Single points
|
||||
- **Polygon**: Multi-point polygons
|
||||
- **Mask**: Pixel-based masks
|
||||
- **Polyline**: Multi-segment lines
|
||||
|
||||
## Creating ROIs
|
||||
|
||||
### Helper Functions
|
||||
|
||||
```python
|
||||
from omero.rtypes import rdouble, rint, rstring
|
||||
import omero.model
|
||||
|
||||
def create_roi(conn, image, shapes):
|
||||
"""
|
||||
Create an ROI and link it to shapes.
|
||||
|
||||
Args:
|
||||
conn: BlitzGateway connection
|
||||
image: Image object
|
||||
shapes: List of shape objects
|
||||
|
||||
Returns:
|
||||
Saved ROI object
|
||||
"""
|
||||
roi = omero.model.RoiI()
|
||||
roi.setImage(image._obj)
|
||||
|
||||
for shape in shapes:
|
||||
roi.addShape(shape)
|
||||
|
||||
updateService = conn.getUpdateService()
|
||||
return updateService.saveAndReturnObject(roi)
|
||||
|
||||
def rgba_to_int(red, green, blue, alpha=255):
|
||||
"""
|
||||
Convert RGBA values (0-255) to integer encoding for OMERO.
|
||||
|
||||
Args:
|
||||
red, green, blue, alpha: Color values (0-255)
|
||||
|
||||
Returns:
|
||||
Integer color value
|
||||
"""
|
||||
return int.from_bytes([red, green, blue, alpha],
|
||||
byteorder='big', signed=True)
|
||||
```
|
||||
|
||||
### Rectangle ROI
|
||||
|
||||
```python
|
||||
from omero.rtypes import rdouble, rint, rstring
|
||||
import omero.model
|
||||
|
||||
# Get image
|
||||
image = conn.getObject("Image", image_id)
|
||||
|
||||
# Define position and size
|
||||
x, y = 50, 100
|
||||
width, height = 200, 150
|
||||
z, t = 0, 0 # Z-section and timepoint
|
||||
|
||||
# Create rectangle
|
||||
rect = omero.model.RectangleI()
|
||||
rect.x = rdouble(x)
|
||||
rect.y = rdouble(y)
|
||||
rect.width = rdouble(width)
|
||||
rect.height = rdouble(height)
|
||||
rect.theZ = rint(z)
|
||||
rect.theT = rint(t)
|
||||
|
||||
# Set label and colors
|
||||
rect.textValue = rstring("Cell Region")
|
||||
rect.fillColor = rint(rgba_to_int(255, 0, 0, 50)) # Red, semi-transparent
|
||||
rect.strokeColor = rint(rgba_to_int(255, 255, 0, 255)) # Yellow border
|
||||
|
||||
# Create ROI
|
||||
roi = create_roi(conn, image, [rect])
|
||||
print(f"Created ROI ID: {roi.getId().getValue()}")
|
||||
```
|
||||
|
||||
### Ellipse ROI
|
||||
|
||||
```python
|
||||
# Center position and radii
|
||||
center_x, center_y = 250, 250
|
||||
radius_x, radius_y = 100, 75
|
||||
z, t = 0, 0
|
||||
|
||||
# Create ellipse
|
||||
ellipse = omero.model.EllipseI()
|
||||
ellipse.x = rdouble(center_x)
|
||||
ellipse.y = rdouble(center_y)
|
||||
ellipse.radiusX = rdouble(radius_x)
|
||||
ellipse.radiusY = rdouble(radius_y)
|
||||
ellipse.theZ = rint(z)
|
||||
ellipse.theT = rint(t)
|
||||
ellipse.textValue = rstring("Nucleus")
|
||||
ellipse.fillColor = rint(rgba_to_int(0, 255, 0, 50))
|
||||
|
||||
# Create ROI
|
||||
roi = create_roi(conn, image, [ellipse])
|
||||
```
|
||||
|
||||
### Line ROI
|
||||
|
||||
```python
|
||||
# Line endpoints
|
||||
x1, y1 = 100, 100
|
||||
x2, y2 = 300, 200
|
||||
z, t = 0, 0
|
||||
|
||||
# Create line
|
||||
line = omero.model.LineI()
|
||||
line.x1 = rdouble(x1)
|
||||
line.y1 = rdouble(y1)
|
||||
line.x2 = rdouble(x2)
|
||||
line.y2 = rdouble(y2)
|
||||
line.theZ = rint(z)
|
||||
line.theT = rint(t)
|
||||
line.textValue = rstring("Measurement Line")
|
||||
line.strokeColor = rint(rgba_to_int(0, 0, 255, 255))
|
||||
|
||||
# Create ROI
|
||||
roi = create_roi(conn, image, [line])
|
||||
```
|
||||
|
||||
### Point ROI
|
||||
|
||||
```python
|
||||
# Point position
|
||||
x, y = 150, 150
|
||||
z, t = 0, 0
|
||||
|
||||
# Create point
|
||||
point = omero.model.PointI()
|
||||
point.x = rdouble(x)
|
||||
point.y = rdouble(y)
|
||||
point.theZ = rint(z)
|
||||
point.theT = rint(t)
|
||||
point.textValue = rstring("Feature Point")
|
||||
|
||||
# Create ROI
|
||||
roi = create_roi(conn, image, [point])
|
||||
```
|
||||
|
||||
### Polygon ROI
|
||||
|
||||
```python
|
||||
from omero.model.enums import UnitsLength
|
||||
|
||||
# Define vertices as string "x1,y1 x2,y2 x3,y3 ..."
|
||||
vertices = "10,20 50,150 200,200 250,75"
|
||||
z, t = 0, 0
|
||||
|
||||
# Create polygon
|
||||
polygon = omero.model.PolygonI()
|
||||
polygon.points = rstring(vertices)
|
||||
polygon.theZ = rint(z)
|
||||
polygon.theT = rint(t)
|
||||
polygon.textValue = rstring("Cell Outline")
|
||||
|
||||
# Set colors and stroke width
|
||||
polygon.fillColor = rint(rgba_to_int(255, 0, 255, 50))
|
||||
polygon.strokeColor = rint(rgba_to_int(255, 255, 0, 255))
|
||||
polygon.strokeWidth = omero.model.LengthI(2, UnitsLength.PIXEL)
|
||||
|
||||
# Create ROI
|
||||
roi = create_roi(conn, image, [polygon])
|
||||
```
|
||||
|
||||
### Mask ROI
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import struct
|
||||
import math
|
||||
|
||||
def create_mask_bytes(mask_array, bytes_per_pixel=1):
|
||||
"""
|
||||
Convert binary mask array to bit-packed bytes for OMERO.
|
||||
|
||||
Args:
|
||||
mask_array: Binary numpy array (0s and 1s)
|
||||
bytes_per_pixel: 1 or 2
|
||||
|
||||
Returns:
|
||||
Byte array for OMERO mask
|
||||
"""
|
||||
if bytes_per_pixel == 2:
|
||||
divider = 16.0
|
||||
format_string = "H"
|
||||
byte_factor = 0.5
|
||||
elif bytes_per_pixel == 1:
|
||||
divider = 8.0
|
||||
format_string = "B"
|
||||
byte_factor = 1
|
||||
else:
|
||||
raise ValueError("bytes_per_pixel must be 1 or 2")
|
||||
|
||||
mask_bytes = mask_array.astype(np.uint8).tobytes()
|
||||
steps = math.ceil(len(mask_bytes) / divider)
|
||||
packed_mask = []
|
||||
|
||||
for i in range(int(steps)):
|
||||
binary = mask_bytes[i * int(divider):
|
||||
i * int(divider) + int(divider)]
|
||||
format_str = str(int(byte_factor * len(binary))) + format_string
|
||||
binary = struct.unpack(format_str, binary)
|
||||
s = "".join(str(bit) for bit in binary)
|
||||
packed_mask.append(int(s, 2))
|
||||
|
||||
return bytearray(packed_mask)
|
||||
|
||||
# Create binary mask (1s and 0s)
|
||||
mask_w, mask_h = 100, 100
|
||||
mask_array = np.fromfunction(
|
||||
lambda x, y: ((x - 50)**2 + (y - 50)**2) < 40**2, # Circle
|
||||
(mask_w, mask_h)
|
||||
)
|
||||
|
||||
# Pack mask
|
||||
mask_packed = create_mask_bytes(mask_array, bytes_per_pixel=1)
|
||||
|
||||
# Mask position
|
||||
mask_x, mask_y = 50, 50
|
||||
z, t, c = 0, 0, 0
|
||||
|
||||
# Create mask
|
||||
mask = omero.model.MaskI()
|
||||
mask.setX(rdouble(mask_x))
|
||||
mask.setY(rdouble(mask_y))
|
||||
mask.setWidth(rdouble(mask_w))
|
||||
mask.setHeight(rdouble(mask_h))
|
||||
mask.setTheZ(rint(z))
|
||||
mask.setTheT(rint(t))
|
||||
mask.setTheC(rint(c))
|
||||
mask.setBytes(mask_packed)
|
||||
mask.textValue = rstring("Segmentation Mask")
|
||||
|
||||
# Set color
|
||||
from omero.gateway import ColorHolder
|
||||
mask_color = ColorHolder()
|
||||
mask_color.setRed(255)
|
||||
mask_color.setGreen(0)
|
||||
mask_color.setBlue(0)
|
||||
mask_color.setAlpha(100)
|
||||
mask.setFillColor(rint(mask_color.getInt()))
|
||||
|
||||
# Create ROI
|
||||
roi = create_roi(conn, image, [mask])
|
||||
```
|
||||
|
||||
## Multiple Shapes in One ROI
|
||||
|
||||
```python
|
||||
# Create multiple shapes for the same ROI
|
||||
shapes = []
|
||||
|
||||
# Rectangle
|
||||
rect = omero.model.RectangleI()
|
||||
rect.x = rdouble(100)
|
||||
rect.y = rdouble(100)
|
||||
rect.width = rdouble(50)
|
||||
rect.height = rdouble(50)
|
||||
rect.theZ = rint(0)
|
||||
rect.theT = rint(0)
|
||||
shapes.append(rect)
|
||||
|
||||
# Ellipse
|
||||
ellipse = omero.model.EllipseI()
|
||||
ellipse.x = rdouble(125)
|
||||
ellipse.y = rdouble(125)
|
||||
ellipse.radiusX = rdouble(20)
|
||||
ellipse.radiusY = rdouble(20)
|
||||
ellipse.theZ = rint(0)
|
||||
ellipse.theT = rint(0)
|
||||
shapes.append(ellipse)
|
||||
|
||||
# Create single ROI with both shapes
|
||||
roi = create_roi(conn, image, shapes)
|
||||
```
|
||||
|
||||
## Retrieving ROIs
|
||||
|
||||
### Get All ROIs for Image
|
||||
|
||||
```python
|
||||
# Get ROI service
|
||||
roi_service = conn.getRoiService()
|
||||
|
||||
# Find all ROIs for image
|
||||
result = roi_service.findByImage(image_id, None)
|
||||
|
||||
print(f"Found {len(result.rois)} ROIs")
|
||||
|
||||
for roi in result.rois:
|
||||
print(f"ROI ID: {roi.getId().getValue()}")
|
||||
print(f" Number of shapes: {len(roi.copyShapes())}")
|
||||
```
|
||||
|
||||
### Parse ROI Shapes
|
||||
|
||||
```python
|
||||
import omero.model
|
||||
|
||||
result = roi_service.findByImage(image_id, None)
|
||||
|
||||
for roi in result.rois:
|
||||
roi_id = roi.getId().getValue()
|
||||
print(f"ROI ID: {roi_id}")
|
||||
|
||||
for shape in roi.copyShapes():
|
||||
shape_id = shape.getId().getValue()
|
||||
z = shape.getTheZ().getValue() if shape.getTheZ() else None
|
||||
t = shape.getTheT().getValue() if shape.getTheT() else None
|
||||
|
||||
# Get label
|
||||
label = ""
|
||||
if shape.getTextValue():
|
||||
label = shape.getTextValue().getValue()
|
||||
|
||||
print(f" Shape ID: {shape_id}, Z: {z}, T: {t}, Label: {label}")
|
||||
|
||||
# Type-specific parsing
|
||||
if isinstance(shape, omero.model.RectangleI):
|
||||
x = shape.getX().getValue()
|
||||
y = shape.getY().getValue()
|
||||
width = shape.getWidth().getValue()
|
||||
height = shape.getHeight().getValue()
|
||||
print(f" Rectangle: ({x}, {y}) {width}x{height}")
|
||||
|
||||
elif isinstance(shape, omero.model.EllipseI):
|
||||
x = shape.getX().getValue()
|
||||
y = shape.getY().getValue()
|
||||
rx = shape.getRadiusX().getValue()
|
||||
ry = shape.getRadiusY().getValue()
|
||||
print(f" Ellipse: center ({x}, {y}), radii ({rx}, {ry})")
|
||||
|
||||
elif isinstance(shape, omero.model.PointI):
|
||||
x = shape.getX().getValue()
|
||||
y = shape.getY().getValue()
|
||||
print(f" Point: ({x}, {y})")
|
||||
|
||||
elif isinstance(shape, omero.model.LineI):
|
||||
x1 = shape.getX1().getValue()
|
||||
y1 = shape.getY1().getValue()
|
||||
x2 = shape.getX2().getValue()
|
||||
y2 = shape.getY2().getValue()
|
||||
print(f" Line: ({x1}, {y1}) to ({x2}, {y2})")
|
||||
|
||||
elif isinstance(shape, omero.model.PolygonI):
|
||||
points = shape.getPoints().getValue()
|
||||
print(f" Polygon: {points}")
|
||||
|
||||
elif isinstance(shape, omero.model.MaskI):
|
||||
x = shape.getX().getValue()
|
||||
y = shape.getY().getValue()
|
||||
width = shape.getWidth().getValue()
|
||||
height = shape.getHeight().getValue()
|
||||
print(f" Mask: ({x}, {y}) {width}x{height}")
|
||||
```
|
||||
|
||||
## Analyzing ROI Intensities
|
||||
|
||||
### Get Statistics for ROI Shapes
|
||||
|
||||
```python
|
||||
# Get all shapes from ROIs
|
||||
roi_service = conn.getRoiService()
|
||||
result = roi_service.findByImage(image_id, None)
|
||||
|
||||
shape_ids = []
|
||||
for roi in result.rois:
|
||||
for shape in roi.copyShapes():
|
||||
shape_ids.append(shape.id.val)
|
||||
|
||||
# Define position
|
||||
z, t = 0, 0
|
||||
channel_index = 0
|
||||
|
||||
# Get statistics
|
||||
stats = roi_service.getShapeStatsRestricted(
|
||||
shape_ids, z, t, [channel_index]
|
||||
)
|
||||
|
||||
# Display statistics
|
||||
for i, stat in enumerate(stats):
|
||||
shape_id = shape_ids[i]
|
||||
print(f"Shape {shape_id} statistics:")
|
||||
print(f" Points Count: {stat.pointsCount[channel_index]}")
|
||||
print(f" Min: {stat.min[channel_index]}")
|
||||
print(f" Mean: {stat.mean[channel_index]}")
|
||||
print(f" Max: {stat.max[channel_index]}")
|
||||
print(f" Sum: {stat.sum[channel_index]}")
|
||||
print(f" Std Dev: {stat.stdDev[channel_index]}")
|
||||
```
|
||||
|
||||
### Extract Pixel Values Within ROI
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
|
||||
# Get image and ROI
|
||||
image = conn.getObject("Image", image_id)
|
||||
result = roi_service.findByImage(image_id, None)
|
||||
|
||||
# Get first rectangle shape
|
||||
roi = result.rois[0]
|
||||
rect = roi.copyShapes()[0]
|
||||
|
||||
# Get rectangle bounds
|
||||
x = int(rect.getX().getValue())
|
||||
y = int(rect.getY().getValue())
|
||||
width = int(rect.getWidth().getValue())
|
||||
height = int(rect.getHeight().getValue())
|
||||
z = rect.getTheZ().getValue()
|
||||
t = rect.getTheT().getValue()
|
||||
|
||||
# Get pixel data
|
||||
pixels = image.getPrimaryPixels()
|
||||
|
||||
# Extract region for each channel
|
||||
for c in range(image.getSizeC()):
|
||||
# Get plane
|
||||
plane = pixels.getPlane(z, c, t)
|
||||
|
||||
# Extract ROI region
|
||||
roi_region = plane[y:y+height, x:x+width]
|
||||
|
||||
print(f"Channel {c}:")
|
||||
print(f" Mean intensity: {np.mean(roi_region)}")
|
||||
print(f" Max intensity: {np.max(roi_region)}")
|
||||
```
|
||||
|
||||
## Modifying ROIs
|
||||
|
||||
### Update Shape Properties
|
||||
|
||||
```python
|
||||
# Get ROI and shape
|
||||
result = roi_service.findByImage(image_id, None)
|
||||
roi = result.rois[0]
|
||||
shape = roi.copyShapes()[0]
|
||||
|
||||
# Modify shape (example: change rectangle size)
|
||||
if isinstance(shape, omero.model.RectangleI):
|
||||
shape.setWidth(rdouble(150))
|
||||
shape.setHeight(rdouble(100))
|
||||
shape.setTextValue(rstring("Updated Rectangle"))
|
||||
|
||||
# Save changes
|
||||
updateService = conn.getUpdateService()
|
||||
updated_roi = updateService.saveAndReturnObject(roi._obj)
|
||||
```
|
||||
|
||||
### Remove Shape from ROI
|
||||
|
||||
```python
|
||||
result = roi_service.findByImage(image_id, None)
|
||||
|
||||
for roi in result.rois:
|
||||
for shape in roi.copyShapes():
|
||||
# Check condition (e.g., remove by label)
|
||||
if (shape.getTextValue() and
|
||||
shape.getTextValue().getValue() == "test-Ellipse"):
|
||||
|
||||
print(f"Removing shape {shape.getId().getValue()}")
|
||||
roi.removeShape(shape)
|
||||
|
||||
# Save modified ROI
|
||||
updateService = conn.getUpdateService()
|
||||
roi = updateService.saveAndReturnObject(roi)
|
||||
```
|
||||
|
||||
## Deleting ROIs
|
||||
|
||||
### Delete Single ROI
|
||||
|
||||
```python
|
||||
# Delete ROI by ID
|
||||
roi_id = 123
|
||||
conn.deleteObjects("Roi", [roi_id], wait=True)
|
||||
print(f"Deleted ROI {roi_id}")
|
||||
```
|
||||
|
||||
### Delete All ROIs for Image
|
||||
|
||||
```python
|
||||
# Get all ROI IDs for image
|
||||
result = roi_service.findByImage(image_id, None)
|
||||
roi_ids = [roi.getId().getValue() for roi in result.rois]
|
||||
|
||||
# Delete all
|
||||
if roi_ids:
|
||||
conn.deleteObjects("Roi", roi_ids, wait=True)
|
||||
print(f"Deleted {len(roi_ids)} ROIs")
|
||||
```
|
||||
|
||||
## Batch ROI Creation
|
||||
|
||||
### Create ROIs for Multiple Images
|
||||
|
||||
```python
|
||||
# Get images
|
||||
dataset = conn.getObject("Dataset", dataset_id)
|
||||
|
||||
for image in dataset.listChildren():
|
||||
# Create rectangle at center of each image
|
||||
x = image.getSizeX() // 2 - 50
|
||||
y = image.getSizeY() // 2 - 50
|
||||
|
||||
rect = omero.model.RectangleI()
|
||||
rect.x = rdouble(x)
|
||||
rect.y = rdouble(y)
|
||||
rect.width = rdouble(100)
|
||||
rect.height = rdouble(100)
|
||||
rect.theZ = rint(0)
|
||||
rect.theT = rint(0)
|
||||
rect.textValue = rstring("Auto ROI")
|
||||
|
||||
roi = create_roi(conn, image, [rect])
|
||||
print(f"Created ROI for image {image.getName()}")
|
||||
```
|
||||
|
||||
### Create ROIs Across Z-Stack
|
||||
|
||||
```python
|
||||
image = conn.getObject("Image", image_id)
|
||||
size_z = image.getSizeZ()
|
||||
|
||||
# Create rectangle on each Z-section
|
||||
shapes = []
|
||||
for z in range(size_z):
|
||||
rect = omero.model.RectangleI()
|
||||
rect.x = rdouble(100)
|
||||
rect.y = rdouble(100)
|
||||
rect.width = rdouble(50)
|
||||
rect.height = rdouble(50)
|
||||
rect.theZ = rint(z)
|
||||
rect.theT = rint(0)
|
||||
shapes.append(rect)
|
||||
|
||||
# Single ROI with shapes across Z
|
||||
roi = create_roi(conn, image, shapes)
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
```python
|
||||
from omero.gateway import BlitzGateway
|
||||
from omero.rtypes import rdouble, rint, rstring
|
||||
import omero.model
|
||||
|
||||
HOST = 'omero.example.com'
|
||||
PORT = 4064
|
||||
USERNAME = 'user'
|
||||
PASSWORD = 'pass'
|
||||
|
||||
def rgba_to_int(r, g, b, a=255):
|
||||
return int.from_bytes([r, g, b, a], byteorder='big', signed=True)
|
||||
|
||||
with BlitzGateway(USERNAME, PASSWORD, host=HOST, port=PORT) as conn:
|
||||
# Get image
|
||||
image = conn.getObject("Image", image_id)
|
||||
print(f"Processing: {image.getName()}")
|
||||
|
||||
# Create multiple ROIs
|
||||
updateService = conn.getUpdateService()
|
||||
|
||||
# ROI 1: Rectangle
|
||||
roi1 = omero.model.RoiI()
|
||||
roi1.setImage(image._obj)
|
||||
|
||||
rect = omero.model.RectangleI()
|
||||
rect.x = rdouble(50)
|
||||
rect.y = rdouble(50)
|
||||
rect.width = rdouble(100)
|
||||
rect.height = rdouble(100)
|
||||
rect.theZ = rint(0)
|
||||
rect.theT = rint(0)
|
||||
rect.textValue = rstring("Cell 1")
|
||||
rect.strokeColor = rint(rgba_to_int(255, 0, 0, 255))
|
||||
|
||||
roi1.addShape(rect)
|
||||
roi1 = updateService.saveAndReturnObject(roi1)
|
||||
print(f"Created ROI 1: {roi1.getId().getValue()}")
|
||||
|
||||
# ROI 2: Ellipse
|
||||
roi2 = omero.model.RoiI()
|
||||
roi2.setImage(image._obj)
|
||||
|
||||
ellipse = omero.model.EllipseI()
|
||||
ellipse.x = rdouble(200)
|
||||
ellipse.y = rdouble(150)
|
||||
ellipse.radiusX = rdouble(40)
|
||||
ellipse.radiusY = rdouble(30)
|
||||
ellipse.theZ = rint(0)
|
||||
ellipse.theT = rint(0)
|
||||
ellipse.textValue = rstring("Cell 2")
|
||||
ellipse.strokeColor = rint(rgba_to_int(0, 255, 0, 255))
|
||||
|
||||
roi2.addShape(ellipse)
|
||||
roi2 = updateService.saveAndReturnObject(roi2)
|
||||
print(f"Created ROI 2: {roi2.getId().getValue()}")
|
||||
|
||||
# Retrieve and analyze
|
||||
roi_service = conn.getRoiService()
|
||||
result = roi_service.findByImage(image_id, None)
|
||||
|
||||
shape_ids = []
|
||||
for roi in result.rois:
|
||||
for shape in roi.copyShapes():
|
||||
shape_ids.append(shape.id.val)
|
||||
|
||||
# Get statistics
|
||||
stats = roi_service.getShapeStatsRestricted(shape_ids, 0, 0, [0])
|
||||
|
||||
for i, stat in enumerate(stats):
|
||||
print(f"Shape {shape_ids[i]}:")
|
||||
print(f" Mean intensity: {stat.mean[0]:.2f}")
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Organize Shapes**: Group related shapes in single ROIs
|
||||
2. **Label Shapes**: Use textValue for identification
|
||||
3. **Set Z and T**: Always specify Z-section and timepoint
|
||||
4. **Color Coding**: Use consistent colors for shape types
|
||||
5. **Validate Coordinates**: Ensure shapes are within image bounds
|
||||
6. **Batch Creation**: Create multiple ROIs in single transaction when possible
|
||||
7. **Delete Unused**: Remove temporary or test ROIs
|
||||
8. **Export Data**: Store ROI statistics in tables for later analysis
|
||||
9. **Version Control**: Document ROI creation methods in annotations
|
||||
10. **Performance**: Use shape statistics service instead of manual pixel extraction
|
||||
637
skills/omero-integration/references/scripts.md
Normal file
637
skills/omero-integration/references/scripts.md
Normal file
@@ -0,0 +1,637 @@
|
||||
# Scripts & Batch Operations
|
||||
|
||||
This reference covers creating OMERO.scripts for server-side processing and batch operations.
|
||||
|
||||
## OMERO.scripts Overview
|
||||
|
||||
OMERO.scripts are Python scripts that run on the OMERO server and can be called from OMERO clients (web, insight, CLI). They function as plugins that extend OMERO functionality.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Server-Side Execution**: Scripts run on the server, avoiding data transfer
|
||||
- **Client Integration**: Callable from any OMERO client with auto-generated UI
|
||||
- **Parameter Handling**: Define input parameters with validation
|
||||
- **Result Reporting**: Return images, files, or messages to clients
|
||||
- **Batch Processing**: Process multiple images or datasets efficiently
|
||||
|
||||
## Basic Script Structure
|
||||
|
||||
### Minimal Script Template
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import omero
|
||||
from omero.gateway import BlitzGateway
|
||||
import omero.scripts as scripts
|
||||
from omero.rtypes import rlong, rstring, robject
|
||||
|
||||
def run_script():
|
||||
"""
|
||||
Main script function.
|
||||
"""
|
||||
# Script definition
|
||||
client = scripts.client(
|
||||
'Script_Name.py',
|
||||
"""
|
||||
Description of what this script does.
|
||||
""",
|
||||
|
||||
# Input parameters
|
||||
scripts.String("Data_Type", optional=False, grouping="1",
|
||||
description="Choose source of images",
|
||||
values=[rstring('Dataset'), rstring('Image')],
|
||||
default=rstring('Dataset')),
|
||||
|
||||
scripts.Long("IDs", optional=False, grouping="2",
|
||||
description="Dataset or Image ID(s)").ofType(rlong(0)),
|
||||
|
||||
# Outputs
|
||||
namespaces=[omero.constants.namespaces.NSDYNAMIC],
|
||||
version="1.0"
|
||||
)
|
||||
|
||||
try:
|
||||
# Get connection
|
||||
conn = BlitzGateway(client_obj=client)
|
||||
|
||||
# Get script parameters
|
||||
script_params = client.getInputs(unwrap=True)
|
||||
data_type = script_params["Data_Type"]
|
||||
ids = script_params["IDs"]
|
||||
|
||||
# Process data
|
||||
message = process_data(conn, data_type, ids)
|
||||
|
||||
# Return results
|
||||
client.setOutput("Message", rstring(message))
|
||||
|
||||
finally:
|
||||
client.closeSession()
|
||||
|
||||
def process_data(conn, data_type, ids):
|
||||
"""
|
||||
Process images based on parameters.
|
||||
"""
|
||||
# Implementation here
|
||||
return "Processing complete"
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_script()
|
||||
```
|
||||
|
||||
## Script Parameters
|
||||
|
||||
### Parameter Types
|
||||
|
||||
```python
|
||||
# String parameter
|
||||
scripts.String("Name", optional=False,
|
||||
description="Enter a name")
|
||||
|
||||
# String with choices
|
||||
scripts.String("Mode", optional=False,
|
||||
values=[rstring('Fast'), rstring('Accurate')],
|
||||
default=rstring('Fast'))
|
||||
|
||||
# Integer parameter
|
||||
scripts.Long("ImageID", optional=False,
|
||||
description="Image to process").ofType(rlong(0))
|
||||
|
||||
# List of integers
|
||||
scripts.List("ImageIDs", optional=False,
|
||||
description="Multiple images").ofType(rlong(0))
|
||||
|
||||
# Float parameter
|
||||
scripts.Float("Threshold", optional=True,
|
||||
description="Threshold value",
|
||||
min=0.0, max=1.0, default=0.5)
|
||||
|
||||
# Boolean parameter
|
||||
scripts.Bool("SaveResults", optional=True,
|
||||
description="Save results to OMERO",
|
||||
default=True)
|
||||
```
|
||||
|
||||
### Parameter Grouping
|
||||
|
||||
```python
|
||||
# Group related parameters
|
||||
scripts.String("Data_Type", grouping="1",
|
||||
description="Source type",
|
||||
values=[rstring('Dataset'), rstring('Image')])
|
||||
|
||||
scripts.Long("Dataset_ID", grouping="1.1",
|
||||
description="Dataset ID").ofType(rlong(0))
|
||||
|
||||
scripts.List("Image_IDs", grouping="1.2",
|
||||
description="Image IDs").ofType(rlong(0))
|
||||
```
|
||||
|
||||
## Accessing Input Data
|
||||
|
||||
### Get Script Parameters
|
||||
|
||||
```python
|
||||
# Inside run_script()
|
||||
client = scripts.client(...)
|
||||
|
||||
# Get parameters as Python objects
|
||||
script_params = client.getInputs(unwrap=True)
|
||||
|
||||
# Access individual parameters
|
||||
data_type = script_params.get("Data_Type", "Image")
|
||||
image_ids = script_params.get("Image_IDs", [])
|
||||
threshold = script_params.get("Threshold", 0.5)
|
||||
save_results = script_params.get("SaveResults", True)
|
||||
```
|
||||
|
||||
### Get Images from Parameters
|
||||
|
||||
```python
|
||||
def get_images_from_params(conn, script_params):
|
||||
"""
|
||||
Get image objects based on script parameters.
|
||||
"""
|
||||
images = []
|
||||
|
||||
data_type = script_params["Data_Type"]
|
||||
|
||||
if data_type == "Dataset":
|
||||
dataset_id = script_params["Dataset_ID"]
|
||||
dataset = conn.getObject("Dataset", dataset_id)
|
||||
if dataset:
|
||||
images = list(dataset.listChildren())
|
||||
|
||||
elif data_type == "Image":
|
||||
image_ids = script_params["Image_IDs"]
|
||||
for image_id in image_ids:
|
||||
image = conn.getObject("Image", image_id)
|
||||
if image:
|
||||
images.append(image)
|
||||
|
||||
return images
|
||||
```
|
||||
|
||||
## Processing Images
|
||||
|
||||
### Batch Image Processing
|
||||
|
||||
```python
|
||||
def process_images(conn, images, threshold):
|
||||
"""
|
||||
Process multiple images.
|
||||
"""
|
||||
results = []
|
||||
|
||||
for image in images:
|
||||
print(f"Processing: {image.getName()}")
|
||||
|
||||
# Get pixel data
|
||||
pixels = image.getPrimaryPixels()
|
||||
size_z = image.getSizeZ()
|
||||
size_c = image.getSizeC()
|
||||
size_t = image.getSizeT()
|
||||
|
||||
# Process each plane
|
||||
for z in range(size_z):
|
||||
for c in range(size_c):
|
||||
for t in range(size_t):
|
||||
plane = pixels.getPlane(z, c, t)
|
||||
|
||||
# Apply threshold
|
||||
binary = (plane > threshold).astype(np.uint8)
|
||||
|
||||
# Count features
|
||||
feature_count = count_features(binary)
|
||||
|
||||
results.append({
|
||||
'image_id': image.getId(),
|
||||
'image_name': image.getName(),
|
||||
'z': z, 'c': c, 't': t,
|
||||
'feature_count': feature_count
|
||||
})
|
||||
|
||||
return results
|
||||
```
|
||||
|
||||
## Generating Outputs
|
||||
|
||||
### Return Messages
|
||||
|
||||
```python
|
||||
# Simple message
|
||||
message = "Processed 10 images successfully"
|
||||
client.setOutput("Message", rstring(message))
|
||||
|
||||
# Detailed message
|
||||
message = "Results:\n"
|
||||
for result in results:
|
||||
message += f"Image {result['image_id']}: {result['count']} cells\n"
|
||||
client.setOutput("Message", rstring(message))
|
||||
```
|
||||
|
||||
### Return Images
|
||||
|
||||
```python
|
||||
# Return newly created image
|
||||
new_image = conn.createImageFromNumpySeq(...)
|
||||
client.setOutput("New_Image", robject(new_image._obj))
|
||||
```
|
||||
|
||||
### Return Files
|
||||
|
||||
```python
|
||||
# Create and return file annotation
|
||||
file_ann = conn.createFileAnnfromLocalFile(
|
||||
output_file_path,
|
||||
mimetype="text/csv",
|
||||
ns="analysis.results"
|
||||
)
|
||||
|
||||
client.setOutput("Result_File", robject(file_ann._obj))
|
||||
```
|
||||
|
||||
### Return Tables
|
||||
|
||||
```python
|
||||
# Create OMERO table and return
|
||||
resources = conn.c.sf.sharedResources()
|
||||
table = create_results_table(resources, results)
|
||||
orig_file = table.getOriginalFile()
|
||||
table.close()
|
||||
|
||||
# Create file annotation
|
||||
file_ann = omero.model.FileAnnotationI()
|
||||
file_ann.setFile(orig_file)
|
||||
file_ann = conn.getUpdateService().saveAndReturnObject(file_ann)
|
||||
|
||||
client.setOutput("Results_Table", robject(file_ann._obj))
|
||||
```
|
||||
|
||||
## Complete Example Scripts
|
||||
|
||||
### Example 1: Maximum Intensity Projection
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import omero
|
||||
from omero.gateway import BlitzGateway
|
||||
import omero.scripts as scripts
|
||||
from omero.rtypes import rlong, rstring, robject
|
||||
import numpy as np
|
||||
|
||||
def run_script():
|
||||
client = scripts.client(
|
||||
'Maximum_Intensity_Projection.py',
|
||||
"""
|
||||
Creates maximum intensity projection from Z-stack images.
|
||||
""",
|
||||
|
||||
scripts.String("Data_Type", optional=False, grouping="1",
|
||||
description="Process images from",
|
||||
values=[rstring('Dataset'), rstring('Image')],
|
||||
default=rstring('Image')),
|
||||
|
||||
scripts.List("IDs", optional=False, grouping="2",
|
||||
description="Dataset or Image ID(s)").ofType(rlong(0)),
|
||||
|
||||
scripts.Bool("Link_to_Source", optional=True, grouping="3",
|
||||
description="Link results to source dataset",
|
||||
default=True),
|
||||
|
||||
version="1.0"
|
||||
)
|
||||
|
||||
try:
|
||||
conn = BlitzGateway(client_obj=client)
|
||||
script_params = client.getInputs(unwrap=True)
|
||||
|
||||
# Get images
|
||||
images = get_images(conn, script_params)
|
||||
created_images = []
|
||||
|
||||
for image in images:
|
||||
print(f"Processing: {image.getName()}")
|
||||
|
||||
# Create MIP
|
||||
mip_image = create_mip(conn, image)
|
||||
if mip_image:
|
||||
created_images.append(mip_image)
|
||||
|
||||
# Report results
|
||||
if created_images:
|
||||
message = f"Created {len(created_images)} MIP images"
|
||||
# Return first image for display
|
||||
client.setOutput("Message", rstring(message))
|
||||
client.setOutput("Result", robject(created_images[0]._obj))
|
||||
else:
|
||||
client.setOutput("Message", rstring("No images created"))
|
||||
|
||||
finally:
|
||||
client.closeSession()
|
||||
|
||||
def get_images(conn, script_params):
|
||||
"""Get images from script parameters."""
|
||||
images = []
|
||||
data_type = script_params["Data_Type"]
|
||||
ids = script_params["IDs"]
|
||||
|
||||
if data_type == "Dataset":
|
||||
for dataset_id in ids:
|
||||
dataset = conn.getObject("Dataset", dataset_id)
|
||||
if dataset:
|
||||
images.extend(list(dataset.listChildren()))
|
||||
else:
|
||||
for image_id in ids:
|
||||
image = conn.getObject("Image", image_id)
|
||||
if image:
|
||||
images.append(image)
|
||||
|
||||
return images
|
||||
|
||||
def create_mip(conn, source_image):
|
||||
"""Create maximum intensity projection."""
|
||||
pixels = source_image.getPrimaryPixels()
|
||||
size_z = source_image.getSizeZ()
|
||||
size_c = source_image.getSizeC()
|
||||
size_t = source_image.getSizeT()
|
||||
|
||||
if size_z == 1:
|
||||
print(" Skipping (single Z-section)")
|
||||
return None
|
||||
|
||||
def plane_gen():
|
||||
for c in range(size_c):
|
||||
for t in range(size_t):
|
||||
# Get Z-stack
|
||||
z_stack = []
|
||||
for z in range(size_z):
|
||||
plane = pixels.getPlane(z, c, t)
|
||||
z_stack.append(plane)
|
||||
|
||||
# Maximum projection
|
||||
max_proj = np.max(z_stack, axis=0)
|
||||
yield max_proj
|
||||
|
||||
# Create new image
|
||||
new_image = conn.createImageFromNumpySeq(
|
||||
plane_gen(),
|
||||
f"{source_image.getName()}_MIP",
|
||||
1, size_c, size_t,
|
||||
description="Maximum intensity projection",
|
||||
dataset=source_image.getParent()
|
||||
)
|
||||
|
||||
return new_image
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_script()
|
||||
```
|
||||
|
||||
### Example 2: Batch ROI Analysis
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import omero
|
||||
from omero.gateway import BlitzGateway
|
||||
import omero.scripts as scripts
|
||||
from omero.rtypes import rlong, rstring, robject
|
||||
import omero.grid
|
||||
|
||||
def run_script():
|
||||
client = scripts.client(
|
||||
'Batch_ROI_Analysis.py',
|
||||
"""
|
||||
Analyzes ROIs across multiple images and creates results table.
|
||||
""",
|
||||
|
||||
scripts.Long("Dataset_ID", optional=False,
|
||||
description="Dataset with images and ROIs").ofType(rlong(0)),
|
||||
|
||||
scripts.Long("Channel_Index", optional=True,
|
||||
description="Channel to analyze (0-indexed)",
|
||||
default=0, min=0),
|
||||
|
||||
version="1.0"
|
||||
)
|
||||
|
||||
try:
|
||||
conn = BlitzGateway(client_obj=client)
|
||||
script_params = client.getInputs(unwrap=True)
|
||||
|
||||
dataset_id = script_params["Dataset_ID"]
|
||||
channel_index = script_params["Channel_Index"]
|
||||
|
||||
# Get dataset
|
||||
dataset = conn.getObject("Dataset", dataset_id)
|
||||
if not dataset:
|
||||
client.setOutput("Message", rstring("Dataset not found"))
|
||||
return
|
||||
|
||||
# Analyze ROIs
|
||||
results = analyze_rois(conn, dataset, channel_index)
|
||||
|
||||
# Create table
|
||||
table_file = create_results_table(conn, dataset, results)
|
||||
|
||||
# Report
|
||||
message = f"Analyzed {len(results)} ROIs from {dataset.getName()}"
|
||||
client.setOutput("Message", rstring(message))
|
||||
client.setOutput("Results_Table", robject(table_file._obj))
|
||||
|
||||
finally:
|
||||
client.closeSession()
|
||||
|
||||
def analyze_rois(conn, dataset, channel_index):
|
||||
"""Analyze all ROIs in dataset images."""
|
||||
roi_service = conn.getRoiService()
|
||||
results = []
|
||||
|
||||
for image in dataset.listChildren():
|
||||
result = roi_service.findByImage(image.getId(), None)
|
||||
|
||||
if not result.rois:
|
||||
continue
|
||||
|
||||
# Get shape IDs
|
||||
shape_ids = []
|
||||
for roi in result.rois:
|
||||
for shape in roi.copyShapes():
|
||||
shape_ids.append(shape.id.val)
|
||||
|
||||
# Get statistics
|
||||
stats = roi_service.getShapeStatsRestricted(
|
||||
shape_ids, 0, 0, [channel_index]
|
||||
)
|
||||
|
||||
# Store results
|
||||
for i, stat in enumerate(stats):
|
||||
results.append({
|
||||
'image_id': image.getId(),
|
||||
'image_name': image.getName(),
|
||||
'shape_id': shape_ids[i],
|
||||
'mean': stat.mean[channel_index],
|
||||
'min': stat.min[channel_index],
|
||||
'max': stat.max[channel_index],
|
||||
'sum': stat.sum[channel_index],
|
||||
'area': stat.pointsCount[channel_index]
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
def create_results_table(conn, dataset, results):
|
||||
"""Create OMERO table from results."""
|
||||
# Prepare data
|
||||
image_ids = [r['image_id'] for r in results]
|
||||
shape_ids = [r['shape_id'] for r in results]
|
||||
means = [r['mean'] for r in results]
|
||||
mins = [r['min'] for r in results]
|
||||
maxs = [r['max'] for r in results]
|
||||
sums = [r['sum'] for r in results]
|
||||
areas = [r['area'] for r in results]
|
||||
|
||||
# Create table
|
||||
resources = conn.c.sf.sharedResources()
|
||||
repository_id = resources.repositories().descriptions[0].getId().getValue()
|
||||
table = resources.newTable(repository_id, f"ROI_Analysis_{dataset.getId()}")
|
||||
|
||||
# Define columns
|
||||
columns = [
|
||||
omero.grid.ImageColumn('Image', 'Source image', []),
|
||||
omero.grid.LongColumn('ShapeID', 'ROI shape ID', []),
|
||||
omero.grid.DoubleColumn('Mean', 'Mean intensity', []),
|
||||
omero.grid.DoubleColumn('Min', 'Min intensity', []),
|
||||
omero.grid.DoubleColumn('Max', 'Max intensity', []),
|
||||
omero.grid.DoubleColumn('Sum', 'Integrated density', []),
|
||||
omero.grid.LongColumn('Area', 'Area in pixels', [])
|
||||
]
|
||||
table.initialize(columns)
|
||||
|
||||
# Add data
|
||||
data = [
|
||||
omero.grid.ImageColumn('Image', 'Source image', image_ids),
|
||||
omero.grid.LongColumn('ShapeID', 'ROI shape ID', shape_ids),
|
||||
omero.grid.DoubleColumn('Mean', 'Mean intensity', means),
|
||||
omero.grid.DoubleColumn('Min', 'Min intensity', mins),
|
||||
omero.grid.DoubleColumn('Max', 'Max intensity', maxs),
|
||||
omero.grid.DoubleColumn('Sum', 'Integrated density', sums),
|
||||
omero.grid.LongColumn('Area', 'Area in pixels', areas)
|
||||
]
|
||||
table.addData(data)
|
||||
|
||||
orig_file = table.getOriginalFile()
|
||||
table.close()
|
||||
|
||||
# Link to dataset
|
||||
file_ann = omero.model.FileAnnotationI()
|
||||
file_ann.setFile(orig_file)
|
||||
file_ann = conn.getUpdateService().saveAndReturnObject(file_ann)
|
||||
|
||||
link = omero.model.DatasetAnnotationLinkI()
|
||||
link.setParent(dataset._obj)
|
||||
link.setChild(file_ann)
|
||||
conn.getUpdateService().saveAndReturnObject(link)
|
||||
|
||||
return file_ann
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_script()
|
||||
```
|
||||
|
||||
## Script Deployment
|
||||
|
||||
### Installation Location
|
||||
|
||||
Scripts should be placed in the OMERO server scripts directory:
|
||||
```
|
||||
OMERO_DIR/lib/scripts/
|
||||
```
|
||||
|
||||
### Recommended Structure
|
||||
|
||||
```
|
||||
lib/scripts/
|
||||
├── analysis/
|
||||
│ ├── Cell_Counter.py
|
||||
│ └── ROI_Analyzer.py
|
||||
├── export/
|
||||
│ ├── Export_Images.py
|
||||
│ └── Export_ROIs.py
|
||||
└── util/
|
||||
└── Helper_Functions.py
|
||||
```
|
||||
|
||||
### Testing Scripts
|
||||
|
||||
```bash
|
||||
# Test script syntax
|
||||
python Script_Name.py
|
||||
|
||||
# Upload to OMERO
|
||||
omero script upload Script_Name.py
|
||||
|
||||
# List scripts
|
||||
omero script list
|
||||
|
||||
# Run script from CLI
|
||||
omero script launch Script_ID Dataset_ID=123
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Error Handling**: Always use try-finally to close session
|
||||
2. **Progress Updates**: Print status messages for long operations
|
||||
3. **Parameter Validation**: Check parameters before processing
|
||||
4. **Memory Management**: Process large datasets in batches
|
||||
5. **Documentation**: Include clear description and parameter docs
|
||||
6. **Versioning**: Include version number in script
|
||||
7. **Namespaces**: Use appropriate namespaces for outputs
|
||||
8. **Return Objects**: Return created objects for client display
|
||||
9. **Logging**: Use print() for server logs
|
||||
10. **Testing**: Test with various input combinations
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Progress Reporting
|
||||
|
||||
```python
|
||||
total = len(images)
|
||||
for idx, image in enumerate(images):
|
||||
print(f"Processing {idx + 1}/{total}: {image.getName()}")
|
||||
# Process image
|
||||
```
|
||||
|
||||
### Error Collection
|
||||
|
||||
```python
|
||||
errors = []
|
||||
for image in images:
|
||||
try:
|
||||
process_image(image)
|
||||
except Exception as e:
|
||||
errors.append(f"{image.getName()}: {str(e)}")
|
||||
|
||||
if errors:
|
||||
message = "Completed with errors:\n" + "\n".join(errors)
|
||||
else:
|
||||
message = "All images processed successfully"
|
||||
```
|
||||
|
||||
### Resource Cleanup
|
||||
|
||||
```python
|
||||
try:
|
||||
# Script processing
|
||||
pass
|
||||
finally:
|
||||
# Clean up temporary files
|
||||
if os.path.exists(temp_file):
|
||||
os.remove(temp_file)
|
||||
client.closeSession()
|
||||
```
|
||||
532
skills/omero-integration/references/tables.md
Normal file
532
skills/omero-integration/references/tables.md
Normal file
@@ -0,0 +1,532 @@
|
||||
# OMERO Tables
|
||||
|
||||
This reference covers creating and managing structured tabular data in OMERO using OMERO.tables.
|
||||
|
||||
## OMERO.tables Overview
|
||||
|
||||
OMERO.tables provides a way to store structured tabular data associated with OMERO objects. Tables are stored as HDF5 files and can be queried efficiently. Common use cases include:
|
||||
|
||||
- Storing quantitative measurements from images
|
||||
- Recording analysis results
|
||||
- Tracking experimental metadata
|
||||
- Linking measurements to specific images or ROIs
|
||||
|
||||
## Column Types
|
||||
|
||||
OMERO.tables supports various column types:
|
||||
|
||||
- **LongColumn**: Integer values (64-bit)
|
||||
- **DoubleColumn**: Floating-point values
|
||||
- **StringColumn**: Text data (fixed max length)
|
||||
- **BoolColumn**: Boolean values
|
||||
- **LongArrayColumn**: Arrays of integers
|
||||
- **DoubleArrayColumn**: Arrays of floats
|
||||
- **FileColumn**: References to OMERO files
|
||||
- **ImageColumn**: References to OMERO images
|
||||
- **RoiColumn**: References to OMERO ROIs
|
||||
- **WellColumn**: References to OMERO wells
|
||||
|
||||
## Creating Tables
|
||||
|
||||
### Basic Table Creation
|
||||
|
||||
```python
|
||||
from random import random
|
||||
import omero.grid
|
||||
|
||||
# Create unique table name
|
||||
table_name = f"MyAnalysisTable_{random()}"
|
||||
|
||||
# Define columns (empty data for initialization)
|
||||
col1 = omero.grid.LongColumn('ImageID', 'Image identifier', [])
|
||||
col2 = omero.grid.DoubleColumn('MeanIntensity', 'Mean pixel intensity', [])
|
||||
col3 = omero.grid.StringColumn('Category', 'Classification', 64, [])
|
||||
|
||||
columns = [col1, col2, col3]
|
||||
|
||||
# Get resources and create table
|
||||
resources = conn.c.sf.sharedResources()
|
||||
repository_id = resources.repositories().descriptions[0].getId().getValue()
|
||||
table = resources.newTable(repository_id, table_name)
|
||||
|
||||
# Initialize table with column definitions
|
||||
table.initialize(columns)
|
||||
```
|
||||
|
||||
### Add Data to Table
|
||||
|
||||
```python
|
||||
# Prepare data
|
||||
image_ids = [1, 2, 3, 4, 5]
|
||||
intensities = [123.4, 145.2, 98.7, 156.3, 132.8]
|
||||
categories = ["Good", "Good", "Poor", "Excellent", "Good"]
|
||||
|
||||
# Create data columns
|
||||
data_col1 = omero.grid.LongColumn('ImageID', 'Image identifier', image_ids)
|
||||
data_col2 = omero.grid.DoubleColumn('MeanIntensity', 'Mean pixel intensity', intensities)
|
||||
data_col3 = omero.grid.StringColumn('Category', 'Classification', 64, categories)
|
||||
|
||||
data = [data_col1, data_col2, data_col3]
|
||||
|
||||
# Add data to table
|
||||
table.addData(data)
|
||||
|
||||
# Get file reference
|
||||
orig_file = table.getOriginalFile()
|
||||
table.close() # Always close table when done
|
||||
```
|
||||
|
||||
### Link Table to Dataset
|
||||
|
||||
```python
|
||||
# Create file annotation from table
|
||||
orig_file_id = orig_file.id.val
|
||||
file_ann = omero.model.FileAnnotationI()
|
||||
file_ann.setFile(omero.model.OriginalFileI(orig_file_id, False))
|
||||
file_ann = conn.getUpdateService().saveAndReturnObject(file_ann)
|
||||
|
||||
# Link to dataset
|
||||
link = omero.model.DatasetAnnotationLinkI()
|
||||
link.setParent(omero.model.DatasetI(dataset_id, False))
|
||||
link.setChild(omero.model.FileAnnotationI(file_ann.getId().getValue(), False))
|
||||
conn.getUpdateService().saveAndReturnObject(link)
|
||||
|
||||
print(f"Linked table to dataset {dataset_id}")
|
||||
```
|
||||
|
||||
## Column Types in Detail
|
||||
|
||||
### Long Column (Integers)
|
||||
|
||||
```python
|
||||
# Column for integer values
|
||||
image_ids = [101, 102, 103, 104, 105]
|
||||
col = omero.grid.LongColumn('ImageID', 'Image identifier', image_ids)
|
||||
```
|
||||
|
||||
### Double Column (Floats)
|
||||
|
||||
```python
|
||||
# Column for floating-point values
|
||||
measurements = [12.34, 56.78, 90.12, 34.56, 78.90]
|
||||
col = omero.grid.DoubleColumn('Measurement', 'Value in microns', measurements)
|
||||
```
|
||||
|
||||
### String Column (Text)
|
||||
|
||||
```python
|
||||
# Column for text (max length required)
|
||||
labels = ["Control", "Treatment A", "Treatment B", "Control", "Treatment A"]
|
||||
col = omero.grid.StringColumn('Condition', 'Experimental condition', 64, labels)
|
||||
```
|
||||
|
||||
### Boolean Column
|
||||
|
||||
```python
|
||||
# Column for boolean values
|
||||
flags = [True, False, True, True, False]
|
||||
col = omero.grid.BoolColumn('QualityPass', 'Passes quality control', flags)
|
||||
```
|
||||
|
||||
### Image Column (References to Images)
|
||||
|
||||
```python
|
||||
# Column linking to OMERO images
|
||||
image_ids = [101, 102, 103, 104, 105]
|
||||
col = omero.grid.ImageColumn('Image', 'Source image', image_ids)
|
||||
```
|
||||
|
||||
### ROI Column (References to ROIs)
|
||||
|
||||
```python
|
||||
# Column linking to OMERO ROIs
|
||||
roi_ids = [201, 202, 203, 204, 205]
|
||||
col = omero.grid.RoiColumn('ROI', 'Associated ROI', roi_ids)
|
||||
```
|
||||
|
||||
### Array Columns
|
||||
|
||||
```python
|
||||
# Column for arrays of doubles
|
||||
histogram_data = [
|
||||
[10, 20, 30, 40],
|
||||
[15, 25, 35, 45],
|
||||
[12, 22, 32, 42]
|
||||
]
|
||||
col = omero.grid.DoubleArrayColumn('Histogram', 'Intensity histogram', histogram_data)
|
||||
|
||||
# Column for arrays of longs
|
||||
bin_counts = [[5, 10, 15], [8, 12, 16], [6, 11, 14]]
|
||||
col = omero.grid.LongArrayColumn('Bins', 'Histogram bins', bin_counts)
|
||||
```
|
||||
|
||||
## Reading Table Data
|
||||
|
||||
### Open Existing Table
|
||||
|
||||
```python
|
||||
# Get table file by name
|
||||
orig_table_file = conn.getObject("OriginalFile",
|
||||
attributes={'name': table_name})
|
||||
|
||||
# Open table
|
||||
resources = conn.c.sf.sharedResources()
|
||||
table = resources.openTable(orig_table_file._obj)
|
||||
|
||||
print(f"Opened table: {table.getOriginalFile().getName().getValue()}")
|
||||
print(f"Number of rows: {table.getNumberOfRows()}")
|
||||
```
|
||||
|
||||
### Read All Data
|
||||
|
||||
```python
|
||||
# Get column headers
|
||||
print("Columns:")
|
||||
for col in table.getHeaders():
|
||||
print(f" {col.name}: {col.description}")
|
||||
|
||||
# Read all data
|
||||
row_count = table.getNumberOfRows()
|
||||
data = table.readCoordinates(range(row_count))
|
||||
|
||||
# Display data
|
||||
for col in data.columns:
|
||||
print(f"\nColumn: {col.name}")
|
||||
for value in col.values:
|
||||
print(f" {value}")
|
||||
|
||||
table.close()
|
||||
```
|
||||
|
||||
### Read Specific Rows
|
||||
|
||||
```python
|
||||
# Read rows 10-20
|
||||
start = 10
|
||||
stop = 20
|
||||
data = table.read(list(range(table.getHeaders().__len__())), start, stop)
|
||||
|
||||
for col in data.columns:
|
||||
print(f"Column: {col.name}")
|
||||
for value in col.values:
|
||||
print(f" {value}")
|
||||
```
|
||||
|
||||
### Read Specific Columns
|
||||
|
||||
```python
|
||||
# Read only columns 0 and 2
|
||||
column_indices = [0, 2]
|
||||
start = 0
|
||||
stop = table.getNumberOfRows()
|
||||
|
||||
data = table.read(column_indices, start, stop)
|
||||
|
||||
for col in data.columns:
|
||||
print(f"Column: {col.name}")
|
||||
print(f"Values: {col.values}")
|
||||
```
|
||||
|
||||
## Querying Tables
|
||||
|
||||
### Query with Conditions
|
||||
|
||||
```python
|
||||
# Query rows where MeanIntensity > 100
|
||||
row_count = table.getNumberOfRows()
|
||||
|
||||
query_rows = table.getWhereList(
|
||||
"(MeanIntensity > 100)",
|
||||
variables={},
|
||||
start=0,
|
||||
stop=row_count,
|
||||
step=0
|
||||
)
|
||||
|
||||
print(f"Found {len(query_rows)} matching rows")
|
||||
|
||||
# Read matching rows
|
||||
data = table.readCoordinates(query_rows)
|
||||
|
||||
for col in data.columns:
|
||||
print(f"\n{col.name}:")
|
||||
for value in col.values:
|
||||
print(f" {value}")
|
||||
```
|
||||
|
||||
### Complex Queries
|
||||
|
||||
```python
|
||||
# Multiple conditions with AND
|
||||
query_rows = table.getWhereList(
|
||||
"(MeanIntensity > 100) & (MeanIntensity < 150)",
|
||||
variables={},
|
||||
start=0,
|
||||
stop=row_count,
|
||||
step=0
|
||||
)
|
||||
|
||||
# Multiple conditions with OR
|
||||
query_rows = table.getWhereList(
|
||||
"(Category == 'Good') | (Category == 'Excellent')",
|
||||
variables={},
|
||||
start=0,
|
||||
stop=row_count,
|
||||
step=0
|
||||
)
|
||||
|
||||
# String matching
|
||||
query_rows = table.getWhereList(
|
||||
"(Category == 'Good')",
|
||||
variables={},
|
||||
start=0,
|
||||
stop=row_count,
|
||||
step=0
|
||||
)
|
||||
```
|
||||
|
||||
## Complete Example: Image Analysis Results
|
||||
|
||||
```python
|
||||
from omero.gateway import BlitzGateway
|
||||
import omero.grid
|
||||
import omero.model
|
||||
import numpy as np
|
||||
|
||||
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)
|
||||
print(f"Analyzing dataset: {dataset.getName()}")
|
||||
|
||||
# Collect measurements from images
|
||||
image_ids = []
|
||||
mean_intensities = []
|
||||
max_intensities = []
|
||||
cell_counts = []
|
||||
|
||||
for image in dataset.listChildren():
|
||||
image_ids.append(image.getId())
|
||||
|
||||
# Get pixel data
|
||||
pixels = image.getPrimaryPixels()
|
||||
plane = pixels.getPlane(0, 0, 0) # Z=0, C=0, T=0
|
||||
|
||||
# Calculate statistics
|
||||
mean_intensities.append(float(np.mean(plane)))
|
||||
max_intensities.append(float(np.max(plane)))
|
||||
|
||||
# Simulate cell count (would be from actual analysis)
|
||||
cell_counts.append(np.random.randint(50, 200))
|
||||
|
||||
# Create table
|
||||
table_name = f"Analysis_Results_{dataset.getId()}"
|
||||
|
||||
# Define columns
|
||||
col1 = omero.grid.ImageColumn('Image', 'Source image', [])
|
||||
col2 = omero.grid.DoubleColumn('MeanIntensity', 'Mean pixel value', [])
|
||||
col3 = omero.grid.DoubleColumn('MaxIntensity', 'Maximum pixel value', [])
|
||||
col4 = omero.grid.LongColumn('CellCount', 'Number of cells detected', [])
|
||||
|
||||
# Initialize table
|
||||
resources = conn.c.sf.sharedResources()
|
||||
repository_id = resources.repositories().descriptions[0].getId().getValue()
|
||||
table = resources.newTable(repository_id, table_name)
|
||||
table.initialize([col1, col2, col3, col4])
|
||||
|
||||
# Add data
|
||||
data_col1 = omero.grid.ImageColumn('Image', 'Source image', image_ids)
|
||||
data_col2 = omero.grid.DoubleColumn('MeanIntensity', 'Mean pixel value',
|
||||
mean_intensities)
|
||||
data_col3 = omero.grid.DoubleColumn('MaxIntensity', 'Maximum pixel value',
|
||||
max_intensities)
|
||||
data_col4 = omero.grid.LongColumn('CellCount', 'Number of cells detected',
|
||||
cell_counts)
|
||||
|
||||
table.addData([data_col1, data_col2, data_col3, data_col4])
|
||||
|
||||
# Get file and close table
|
||||
orig_file = table.getOriginalFile()
|
||||
table.close()
|
||||
|
||||
# Link to dataset
|
||||
orig_file_id = orig_file.id.val
|
||||
file_ann = omero.model.FileAnnotationI()
|
||||
file_ann.setFile(omero.model.OriginalFileI(orig_file_id, False))
|
||||
file_ann = conn.getUpdateService().saveAndReturnObject(file_ann)
|
||||
|
||||
link = omero.model.DatasetAnnotationLinkI()
|
||||
link.setParent(omero.model.DatasetI(dataset_id, False))
|
||||
link.setChild(omero.model.FileAnnotationI(file_ann.getId().getValue(), False))
|
||||
conn.getUpdateService().saveAndReturnObject(link)
|
||||
|
||||
print(f"Created and linked table with {len(image_ids)} rows")
|
||||
|
||||
# Query results
|
||||
table = resources.openTable(orig_file)
|
||||
|
||||
high_cell_count_rows = table.getWhereList(
|
||||
"(CellCount > 100)",
|
||||
variables={},
|
||||
start=0,
|
||||
stop=table.getNumberOfRows(),
|
||||
step=0
|
||||
)
|
||||
|
||||
print(f"Images with >100 cells: {len(high_cell_count_rows)}")
|
||||
|
||||
# Read those rows
|
||||
data = table.readCoordinates(high_cell_count_rows)
|
||||
for i in range(len(high_cell_count_rows)):
|
||||
img_id = data.columns[0].values[i]
|
||||
count = data.columns[3].values[i]
|
||||
print(f" Image {img_id}: {count} cells")
|
||||
|
||||
table.close()
|
||||
```
|
||||
|
||||
## Retrieve Tables from Objects
|
||||
|
||||
### Find Tables Attached to Dataset
|
||||
|
||||
```python
|
||||
# Get dataset
|
||||
dataset = conn.getObject("Dataset", dataset_id)
|
||||
|
||||
# List file annotations
|
||||
for ann in dataset.listAnnotations():
|
||||
if isinstance(ann, omero.gateway.FileAnnotationWrapper):
|
||||
file_obj = ann.getFile()
|
||||
file_name = file_obj.getName()
|
||||
|
||||
# Check if it's a table (might have specific naming pattern)
|
||||
if "Table" in file_name or file_name.endswith(".h5"):
|
||||
print(f"Found table: {file_name} (ID: {file_obj.getId()})")
|
||||
|
||||
# Open and inspect
|
||||
resources = conn.c.sf.sharedResources()
|
||||
table = resources.openTable(file_obj._obj)
|
||||
|
||||
print(f" Rows: {table.getNumberOfRows()}")
|
||||
print(f" Columns:")
|
||||
for col in table.getHeaders():
|
||||
print(f" {col.name}")
|
||||
|
||||
table.close()
|
||||
```
|
||||
|
||||
## Updating Tables
|
||||
|
||||
### Append Rows
|
||||
|
||||
```python
|
||||
# Open existing table
|
||||
resources = conn.c.sf.sharedResources()
|
||||
table = resources.openTable(orig_file._obj)
|
||||
|
||||
# Prepare new data
|
||||
new_image_ids = [106, 107]
|
||||
new_intensities = [88.9, 92.3]
|
||||
new_categories = ["Good", "Excellent"]
|
||||
|
||||
# Create data columns
|
||||
data_col1 = omero.grid.LongColumn('ImageID', '', new_image_ids)
|
||||
data_col2 = omero.grid.DoubleColumn('MeanIntensity', '', new_intensities)
|
||||
data_col3 = omero.grid.StringColumn('Category', '', 64, new_categories)
|
||||
|
||||
# Append data
|
||||
table.addData([data_col1, data_col2, data_col3])
|
||||
|
||||
print(f"New row count: {table.getNumberOfRows()}")
|
||||
table.close()
|
||||
```
|
||||
|
||||
## Deleting Tables
|
||||
|
||||
### Delete Table File
|
||||
|
||||
```python
|
||||
# Get file object
|
||||
orig_file = conn.getObject("OriginalFile", file_id)
|
||||
|
||||
# Delete file (also deletes table)
|
||||
conn.deleteObjects("OriginalFile", [file_id], wait=True)
|
||||
print(f"Deleted table file {file_id}")
|
||||
```
|
||||
|
||||
### Unlink Table from Object
|
||||
|
||||
```python
|
||||
# Find annotation links
|
||||
dataset = conn.getObject("Dataset", dataset_id)
|
||||
|
||||
for ann in dataset.listAnnotations():
|
||||
if isinstance(ann, omero.gateway.FileAnnotationWrapper):
|
||||
if "Table" in ann.getFile().getName():
|
||||
# Delete link (keeps table, removes association)
|
||||
conn.deleteObjects("DatasetAnnotationLink",
|
||||
[ann.link.getId()],
|
||||
wait=True)
|
||||
print(f"Unlinked table from dataset")
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Descriptive Names**: Use meaningful table and column names
|
||||
2. **Close Tables**: Always close tables after use
|
||||
3. **String Length**: Set appropriate max length for string columns
|
||||
4. **Link to Objects**: Attach tables to relevant datasets or projects
|
||||
5. **Use References**: Use ImageColumn, RoiColumn for object references
|
||||
6. **Query Efficiently**: Use getWhereList() instead of reading all data
|
||||
7. **Document**: Add descriptions to columns
|
||||
8. **Version Control**: Include version info in table name or metadata
|
||||
9. **Batch Operations**: Add data in batches for better performance
|
||||
10. **Error Handling**: Check for None returns and handle exceptions
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### ROI Measurements Table
|
||||
|
||||
```python
|
||||
# Table structure for ROI measurements
|
||||
columns = [
|
||||
omero.grid.ImageColumn('Image', 'Source image', []),
|
||||
omero.grid.RoiColumn('ROI', 'Measured ROI', []),
|
||||
omero.grid.LongColumn('ChannelIndex', 'Channel number', []),
|
||||
omero.grid.DoubleColumn('Area', 'ROI area in pixels', []),
|
||||
omero.grid.DoubleColumn('MeanIntensity', 'Mean intensity', []),
|
||||
omero.grid.DoubleColumn('IntegratedDensity', 'Sum of intensities', []),
|
||||
omero.grid.StringColumn('CellType', 'Cell classification', 32, [])
|
||||
]
|
||||
```
|
||||
|
||||
### Time Series Data Table
|
||||
|
||||
```python
|
||||
# Table structure for time series measurements
|
||||
columns = [
|
||||
omero.grid.ImageColumn('Image', 'Time series image', []),
|
||||
omero.grid.LongColumn('Timepoint', 'Time index', []),
|
||||
omero.grid.DoubleColumn('Timestamp', 'Time in seconds', []),
|
||||
omero.grid.DoubleColumn('Value', 'Measured value', []),
|
||||
omero.grid.StringColumn('Measurement', 'Type of measurement', 64, [])
|
||||
]
|
||||
```
|
||||
|
||||
### Screening Results Table
|
||||
|
||||
```python
|
||||
# Table structure for screening plate analysis
|
||||
columns = [
|
||||
omero.grid.WellColumn('Well', 'Plate well', []),
|
||||
omero.grid.LongColumn('FieldIndex', 'Field number', []),
|
||||
omero.grid.DoubleColumn('CellCount', 'Number of cells', []),
|
||||
omero.grid.DoubleColumn('Viability', 'Percent viable', []),
|
||||
omero.grid.StringColumn('Phenotype', 'Observed phenotype', 128, []),
|
||||
omero.grid.BoolColumn('Hit', 'Hit in screen', [])
|
||||
]
|
||||
```
|
||||
Reference in New Issue
Block a user