Converting masks back to annotations¶
Overview:
Most segmentation algorithms produce outputs in an image format. Visualizing these outputs in HistomicsUI requires conversion from mask images to an annotation document containing (x,y) coordinates in the whole-slide image coordinate frame. This notebook demonstrates this conversion process in two steps:
Converting a mask image into contours (coordinates in the mask frame)
Placing contours data into a format following the annotation document schema that can be pushed to DSA for visualization in HistomicsUI.
This notebook is based on work described in Amgad et al, 2019:
Mohamed Amgad, Habiba Elfandy, Hagar Hussein, …, Jonathan Beezley, Deepak R Chittajallu, David Manthey, David A Gutman, Lee A D Cooper, Structured crowdsourcing enables convolutional segmentation of histology images, Bioinformatics, 2019, btz083
Where to look?
|_ histomicstk/
|_annotations_and_masks/
| |_masks_to_annotations_handler.py
|_tests/
|_test_masks_to_annotations_handler.py
[1]:
import os
CWD = os.getcwd()
import girder_client
from pandas import read_csv
from imageio import imread
from histomicstk.annotations_and_masks.masks_to_annotations_handler import (
get_contours_from_mask,
get_single_annotation_document_from_contours,
get_annotation_documents_from_contours)
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['figure.figsize'] = 7, 7
1. Connect girder client and set parameters¶
[2]:
# APIURL = 'http://demo.kitware.com/histomicstk/api/v1/'
# SAMPLE_SLIDE_ID = '5bbdee92e629140048d01b5d'
APIURL = 'http://candygram.neurology.emory.edu:8080/api/v1/'
SAMPLE_SLIDE_ID = '5d586d76bd4404c6b1f286ae'
# Connect to girder client
gc = girder_client.GirderClient(apiUrl=APIURL)
gc.authenticate(interactive=True)
# gc.authenticate(apiKey='kri19nTIGOkWH01TbzRqfohaaDWb6kPecRqGmemb')
[2]:
{'_accessLevel': 2,
'_id': '59bc677892ca9a0017c2e855',
'_modelType': 'user',
'admin': True,
'created': '2017-09-15T23:51:20.203000+00:00',
'email': 'mtageld@emory.edu',
'emailVerified': False,
'firstName': 'Mohamed',
'groupInvites': [],
'groups': ['59f7713a92ca9a0017a29765',
'5c607488e62914004d0ff4a6',
'5e44a2e0ddda5f8398785304',
'5e76b3f3ddda5f83982beb9a'],
'lastName': 'Tageldin',
'login': 'kheffah',
'otp': False,
'public': True,
'size': 0,
'status': 'enabled'}
Let’s inspect the ground truth codes file¶
This contains the ground truth codes and information dataframe. This is a dataframe that is indexed by the annotation group name and has the following columns:
group
: group name of annotation (string), eg. “mostly_tumor”GT_code
: int, desired ground truth code (in the mask) Pixels of this value belong to corresponding group (class)color
: str, rgb format. eg. rgb(255,0,0).
NOTE:
Zero pixels have special meaning and do not encode specific ground truth class. Instead, they simply mean ‘Outside ROI’ and should be ignored during model training or evaluation.
[3]:
# read GTCodes dataframe
GTCODE_PATH = os.path.join(
CWD, '..', '..', 'tests', 'test_files', 'sample_GTcodes.csv')
GTCodes_df = read_csv(GTCODE_PATH)
GTCodes_df.index = GTCodes_df.loc[:, 'group']
[4]:
GTCodes_df.head()
[4]:
group | overlay_order | GT_code | is_roi | is_background_class | color | comments | |
---|---|---|---|---|---|---|---|
group | |||||||
roi | roi | 0 | 254 | 1 | 0 | rgb(200,0,150) | NaN |
evaluation_roi | evaluation_roi | 0 | 253 | 1 | 0 | rgb(255,0,0) | NaN |
mostly_tumor | mostly_tumor | 1 | 1 | 0 | 0 | rgb(255,0,0) | core class |
mostly_stroma | mostly_stroma | 2 | 2 | 0 | 1 | rgb(255,125,0) | core class |
mostly_lymphocytic_infiltrate | mostly_lymphocytic_infiltrate | 1 | 3 | 0 | 0 | rgb(0,0,255) | core class |
Read and visualize mask¶
[5]:
# read mask
X_OFFSET = 59206
Y_OFFSET = 33505
MASKNAME = 'TCGA-A2-A0YE-01Z-00-DX1.8A2E3094-5755-42BC-969D-7F0A2ECA0F39' + \
'_left-%d_top-%d_mag-BASE.png' % (X_OFFSET, Y_OFFSET)
MASKPATH = os.path.join(CWD, '..', '..', 'tests', 'test_files', 'annotations_and_masks', MASKNAME)
MASK = imread(MASKPATH)
[6]:
plt.figure(figsize=(7,7))
plt.imshow(MASK)
plt.title(MASKNAME[:23])
plt.show()
2. Get contours from mask¶
This function get_contours_from_mask()
generates contours from a mask image. There are many parameters that can be set but most have defaults set for the most common use cases. The only required parameters you must provide are MASK
and GTCodes_df
, but you may want to consider setting the following parameters based on your specific needs: get_roi_contour
, roi_group
, discard_nonenclosed_background
, background_group
, that control behaviour regarding region of interest
(ROI) boundary and background pixel class (e.g. stroma).
[7]:
print(get_contours_from_mask.__doc__)
Parse ground truth mask and gets countours for annotations.
Parameters
-----------
MASK : nd array
ground truth mask (m,n) where pixel values encode group membership.
GTCodes_df : pandas Dataframe
the ground truth codes and information dataframe.
This is a dataframe that is indexed by the annotation group name and
has the following columns.
group: str
group name of annotation, eg. mostly_tumor.
GT_code: int
desired ground truth code (in the mask). Pixels of this value
belong to corresponding group (class).
color: str
rgb format. eg. rgb(255,0,0).
groups_to_get : None
if None (default) then all groups (ground truth labels) will be
extracted. Otherwise pass a list fo strings like ['mostly_tumor',].
MIN_SIZE : int
minimum bounding box size of contour
MAX_SIZE : None
if not None, int. Maximum bounding box size of contour. Sometimes
very large contours cause segmentation faults that originate from
opencv and are not caught by python, causing the python process
to unexpectedly hault. If you would like to set a maximum size to
defend against this, a suggested maximum would be 15000.
get_roi_contour : bool
whether to get contour for boundary of region of interest (ROI). This
is most relevant when dealing with multiple ROIs per slide and with
rotated rectangular or polygonal ROIs.
roi_group : str
name of roi group in the GT_Codes dataframe (eg roi)
discard_nonenclosed_background : bool
If a background group contour is NOT fully enclosed, discard it.
This is a purely aesthetic method, makes sure that the background group
contours (eg stroma) are discarded by default to avoid cluttering the
field when posted to DSA for viewing online. The only exception is
if they are enclosed within something else (eg tumor), in which case
they are kept since they represent holes. This is related to
https://github.com/DigitalSlideArchive/HistomicsTK/issues/675
WARNING - This is a bit slower since the contours will have to be
converted to shapely polygons. It is not noticeable for hundreds of
contours, but you will notice the speed difference if you are parsing
thousands of contours. Default, for this reason, is False.
background_group : str
name of background group in the GT_codes dataframe (eg mostly_stroma)
verbose : bool
Print progress to screen?
monitorPrefix : str
text to prepend to printed statements
Returns
--------
pandas DataFrame
contours extracted from input mask. The following columns are output.
group : str
annotation group (ground truth label).
color : str
annotation color if it were to be posted to DSA.
is_roi : bool
whether this annotation is a region of interest boundary
ymin : int
minimun y coordinate
ymax : int
maximum y coordinate
xmin : int
minimum x coordinate
xmax : int
maximum x coordinate
has_holes : bool
whether this contour has holes
touches_edge-top : bool
whether this contour touches top mask edge
touches_edge-bottom : bool
whether this contour touches bottom mask edge
touches_edge-left : bool
whether this contour touches left mask edge
touches_edge-right : bool
whether this contour touches right mask edge
coords_x : str
vertix x coordinates comma-separated values
coords_y
vertix y coordinated comma-separated values
Extract contours¶
[8]:
# Let's extract all contours from a mask, including ROI boundary. We will
# be discarding any stromal contours that are not fully enclosed within a
# non-stromal contour since we already know that stroma is the background
# group. This is so things look uncluttered when posted to DSA.
groups_to_get = None
contours_df = get_contours_from_mask(
MASK=MASK, GTCodes_df=GTCodes_df, groups_to_get=groups_to_get,
get_roi_contour=True, roi_group='roi',
discard_nonenclosed_background=True,
background_group='mostly_stroma',
MIN_SIZE=30, MAX_SIZE=None, verbose=True,
monitorPrefix=MASKNAME[:12] + ': getting contours')
TCGA-A2-A0YE: getting contours: non-roi: roi: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: evaluation_roi: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: mostly_tumor: getting contours
TCGA-A2-A0YE: getting contours: non-roi: mostly_tumor: adding contours
TCGA-A2-A0YE: getting contours: non-roi: mostly_stroma: getting contours
TCGA-A2-A0YE: getting contours: non-roi: mostly_stroma: adding contours
TCGA-A2-A0YE: getting contours: non-roi: nest 1 of 11: TOO SIMPLE (1 coordinates) -- IGNORED
TCGA-A2-A0YE: getting contours: non-roi: nest 2 of 11: TOO SIMPLE (2 coordinates) -- IGNORED
TCGA-A2-A0YE: getting contours: non-roi: nest 3 of 11: TOO SIMPLE (1 coordinates) -- IGNORED
TCGA-A2-A0YE: getting contours: non-roi: nest 4 of 11: TOO SIMPLE (1 coordinates) -- IGNORED
TCGA-A2-A0YE: getting contours: non-roi: nest 5 of 11: TOO SMALL (10 x 18 pixels) -- IGNORED
TCGA-A2-A0YE: getting contours: non-roi: nest 6 of 11: TOO SIMPLE (1 coordinates) -- IGNORED
TCGA-A2-A0YE: getting contours: non-roi: nest 8 of 11: TOO SIMPLE (1 coordinates) -- IGNORED
TCGA-A2-A0YE: getting contours: non-roi: nest 9 of 11: TOO SIMPLE (1 coordinates) -- IGNORED
TCGA-A2-A0YE: getting contours: non-roi: mostly_lymphocytic_infiltrate: getting contours
TCGA-A2-A0YE: getting contours: non-roi: mostly_lymphocytic_infiltrate: adding contours
TCGA-A2-A0YE: getting contours: non-roi: nest 5 of 14: TOO SMALL (23 x 74 pixels) -- IGNORED
TCGA-A2-A0YE: getting contours: non-roi: necrosis_or_debris: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: glandular_secretions: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: mostly_blood: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: exclude: getting contours
TCGA-A2-A0YE: getting contours: non-roi: exclude: adding contours
TCGA-A2-A0YE: getting contours: non-roi: metaplasia_NOS: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: mostly_fat: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: mostly_plasma_cells: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: other_immune_infiltrate: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: mostly_mucoid_material: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: normal_acinus_or_duct: getting contours
TCGA-A2-A0YE: getting contours: non-roi: normal_acinus_or_duct: adding contours
TCGA-A2-A0YE: getting contours: non-roi: lymphatics: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: undetermined: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: nerve: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: skin_adnexia: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: blood_vessel: getting contours
TCGA-A2-A0YE: getting contours: non-roi: blood_vessel: adding contours
TCGA-A2-A0YE: getting contours: non-roi: angioinvasion: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: mostly_dcis: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: non-roi: other: NO OBJECTS!!
TCGA-A2-A0YE: getting contours: discarding backgrnd: discarded 3 contours
TCGA-A2-A0YE: getting contours: roi: roi: getting contours
TCGA-A2-A0YE: getting contours: roi: roi: adding contours
Let’s inspect the contours dataframe¶
The columns that really matter here are group
, color
, coords_x
, and coords_y
.
[9]:
contours_df.head()
[9]:
group | color | ymin | ymax | xmin | xmax | has_holes | touches_edge-top | touches_edge-left | touches_edge-bottom | touches_edge-right | coords_x | coords_y | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | roi | rgb(200,0,150) | 0.0 | 4593.0 | 0.0 | 4541.0 | 0.0 | 1.0 | 1.0 | 1.0 | 1.0 | 2835,2834,2833,2832,2831,2830,2829,2827,2826,2... | 0,1,1,2,2,3,3,5,5,6,6,8,8,9,9,10,10,12,12,13,1... |
1 | mostly_tumor | rgb(255,0,0) | 4269.0 | 4560.0 | 1639.0 | 2039.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1673,1672,1668,1667,1662,1661,1659,1658,1658,1... | 4269,4270,4270,4271,4271,4272,4272,4273,4274,4... |
2 | mostly_tumor | rgb(255,0,0) | 3764.0 | 4282.0 | 1607.0 | 2187.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1770,1769,1768,1767,1765,1764,1762,1761,1760,1... | 3764,3765,3765,3766,3766,3767,3767,3768,3768,3... |
3 | mostly_tumor | rgb(255,0,0) | 3712.0 | 4051.0 | 1201.0 | 1411.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1214,1213,1211,1210,1208,1207,1206,1205,1203,1... | 3712,3713,3713,3714,3714,3715,3715,3716,3716,3... |
4 | mostly_tumor | rgb(255,0,0) | 3356.0 | 3748.0 | 3108.0 | 3540.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 3342,3341,3337,3336,3332,3331,3328,3327,3326,3... | 3356,3357,3357,3358,3358,3359,3359,3360,3360,3... |
3. Get annotation documents from contours¶
This method get_annotation_documents_from_contours()
generates formatted annotation documents from contours that can be posted to the DSA server.
[10]:
print(get_annotation_documents_from_contours.__doc__)
Given dataframe of contours, get list of annotation documents.
This method parses a dataframe of contours to a list of dictionaries, each
of which represents and large_image style annotation. This is a wrapper
that extends the functionality of the method
get_single_annotation_document_from_contours(), whose docstring should
be referenced for implementation details and further explanation.
Parameters
-----------
contours_df : pandas DataFrame
WARNING - This is modified inside the function, so pass a copy.
This dataframe includes data on contours extracted from input mask
using get_contours_from_mask(). If you have contours using some other
method, just make sure the dataframe follows the same schema as the
output from get_contours_from_mask(). You may find a sample dataframe
in thie repo at ./tests/test_files/annotations_and_masks/sample_contours_df.tsv
The following columns are relevant for this method.
group : str
annotation group (ground truth label).
color : str
annotation color if it were to be posted to DSA.
coords_x : str
vertix x coordinates comma-separated values
coords_y
vertix y coordinated comma-separated values
separate_docs_by_group : bool
if set to True, you get one or more annotation documents (dicts)
for each group (eg tumor) independently.
annots_per_doc : int
maximum number of annotation elements (polygons) per dict. The smaller
this number, the more numerous the annotation documents, but the more
seamless it is to post this data to the DSA server or to view using the
HistomicsTK interface since you will be loading smaller chunks of data
at a time.
annprops : dict
properties of annotation elements. Contains the following keys
F, X_OFFSET, Y_OFFSET, opacity, lineWidth. Refer to
get_single_annotation_document_from_contours() for details.
docnamePrefix : str
test to prepend to annotation document name
verbose : bool
Print progress to screen?
monitorPrefix : str
text to prepend to printed statements
Returns
--------
list of dicts
DSA-style annotation document.
As mentioned in the docs, this function wraps get_single_annotation_document_from_contours()
[11]:
print(get_single_annotation_document_from_contours.__doc__)
Given dataframe of contours, get annotation document.
This uses the large_image annotation schema to create an annotation
document that maybe posted to DSA for viewing using something like:
resp = gc.post("/annotation?itemId=" + slide_id, json=annotation_doc)
The annotation schema can be found at:
github.com/girder/large_image/blob/master/docs/annotations.md .
Parameters
-----------
contours_df_slice : pandas DataFrame
The following columns are of relevance and must be contained.
group : str
annotation group (ground truth label).
color : str
annotation color if it were to be posted to DSA.
coords_x : str
vertix x coordinates comma-separated values
coords_y
vertix y coordinated comma-separated values
docname : str
annotation document name
F : float
how much smaller is the mask where the contours come from is relative
to the slide scan magnification. For example, if the mask is at 10x
whereas the slide scan magnification is 20x, then F would be 2.0.
X_OFFSET : int
x offset to add to contours at BASE (SCAN) magnification
Y_OFFSET : int
y offset to add to contours at BASE (SCAN) magnification
opacity : float
opacity of annotation elements (in the range [0, 1])
lineWidth : float
width of boarders of annotation elements
verbose : bool
Print progress to screen?
monitorPrefix : str
text to prepend to printed statements
Returns
--------
dict
DSA-style annotation document ready to be post for viewing.
Let’s get a list of annotation documents (each is a dictionary). For the purpose of this tutorial, we separate the documents by group (i.e. each document is composed of polygons from the same style/group). You could decide to allow heterogeneous groups in the same annotation document by setting separate_docs_by_group
to False
. We place 10 polygons in each document for this demo for illustration purposes. Realistically you would want each document to contain several hundred depending on
their complexity. Placing too many polygons in each document can lead to performance issues when rendering in HistomicsUI.
Get annotation documents¶
[12]:
# get list of annotation documents
annprops = {
'X_OFFSET': X_OFFSET,
'Y_OFFSET': Y_OFFSET,
'opacity': 0.2,
'lineWidth': 4.0,
}
annotation_docs = get_annotation_documents_from_contours(
contours_df.copy(), separate_docs_by_group=True, annots_per_doc=10,
docnamePrefix='demo', annprops=annprops,
verbose=True, monitorPrefix=MASKNAME[:12] + ': annotation docs')
TCGA-A2-A0YE: annotation docs: mostly_lymphocytic_infiltrate: doc 1 of 1: contour 1 of 13
TCGA-A2-A0YE: annotation docs: mostly_lymphocytic_infiltrate: doc 1 of 1: contour 2 of 13
TCGA-A2-A0YE: annotation docs: mostly_lymphocytic_infiltrate: doc 1 of 1: contour 3 of 13
TCGA-A2-A0YE: annotation docs: mostly_lymphocytic_infiltrate: doc 1 of 1: contour 4 of 13
TCGA-A2-A0YE: annotation docs: mostly_lymphocytic_infiltrate: doc 1 of 1: contour 5 of 13
TCGA-A2-A0YE: annotation docs: mostly_lymphocytic_infiltrate: doc 1 of 1: contour 6 of 13
TCGA-A2-A0YE: annotation docs: mostly_lymphocytic_infiltrate: doc 1 of 1: contour 7 of 13
TCGA-A2-A0YE: annotation docs: mostly_lymphocytic_infiltrate: doc 1 of 1: contour 8 of 13
TCGA-A2-A0YE: annotation docs: mostly_lymphocytic_infiltrate: doc 1 of 1: contour 9 of 13
TCGA-A2-A0YE: annotation docs: mostly_lymphocytic_infiltrate: doc 1 of 1: contour 10 of 13
TCGA-A2-A0YE: annotation docs: mostly_lymphocytic_infiltrate: doc 1 of 1: contour 11 of 13
TCGA-A2-A0YE: annotation docs: mostly_lymphocytic_infiltrate: doc 1 of 1: contour 12 of 13
TCGA-A2-A0YE: annotation docs: mostly_lymphocytic_infiltrate: doc 1 of 1: contour 13 of 13
TCGA-A2-A0YE: annotation docs: exclude: doc 1 of 1: contour 1 of 1
TCGA-A2-A0YE: annotation docs: blood_vessel: doc 1 of 1: contour 1 of 3
TCGA-A2-A0YE: annotation docs: blood_vessel: doc 1 of 1: contour 2 of 3
TCGA-A2-A0YE: annotation docs: blood_vessel: doc 1 of 1: contour 3 of 3
TCGA-A2-A0YE: annotation docs: roi: doc 1 of 1: contour 1 of 1
TCGA-A2-A0YE: annotation docs: normal_acinus_or_duct: doc 1 of 1: contour 1 of 2
TCGA-A2-A0YE: annotation docs: normal_acinus_or_duct: doc 1 of 1: contour 2 of 2
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 1 of 2: contour 1 of 10
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 1 of 2: contour 2 of 10
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 1 of 2: contour 3 of 10
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 1 of 2: contour 4 of 10
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 1 of 2: contour 5 of 10
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 1 of 2: contour 6 of 10
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 1 of 2: contour 7 of 10
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 1 of 2: contour 8 of 10
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 1 of 2: contour 9 of 10
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 1 of 2: contour 10 of 10
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 2 of 2: contour 1 of 15
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 2 of 2: contour 2 of 15
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 2 of 2: contour 3 of 15
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 2 of 2: contour 4 of 15
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 2 of 2: contour 5 of 15
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 2 of 2: contour 6 of 15
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 2 of 2: contour 7 of 15
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 2 of 2: contour 8 of 15
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 2 of 2: contour 9 of 15
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 2 of 2: contour 10 of 15
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 2 of 2: contour 11 of 15
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 2 of 2: contour 12 of 15
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 2 of 2: contour 13 of 15
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 2 of 2: contour 14 of 15
TCGA-A2-A0YE: annotation docs: mostly_tumor: doc 2 of 2: contour 15 of 15
Let’s examine one of the documents.¶
Limit display to the first two elements (polygons) and cap the vertices for clarity.
[13]:
ann_doc = annotation_docs[0].copy()
ann_doc['elements'] = ann_doc['elements'][:2]
for i in range(2):
ann_doc['elements'][i]['points'] = ann_doc['elements'][i]['points'][:5]
[14]:
ann_doc
[14]:
{'name': 'demo_mostly_lymphocytic_infiltrate-0',
'description': '',
'elements': [{'group': 'mostly_lymphocytic_infiltrate',
'type': 'polyline',
'lineColor': 'rgb(0,0,255)',
'lineWidth': 4.0,
'closed': True,
'points': [[61974.0, 37427.0, 0.0],
[61975.0, 37428.0, 0.0],
[61975.0, 37429.0, 0.0],
[61976.0, 37430.0, 0.0],
[61976.0, 37431.0, 0.0]],
'label': {'value': 'mostly_lymphocytic_infiltrate'},
'fillColor': 'rgba(0,0,255,0.2)'},
{'group': 'mostly_lymphocytic_infiltrate',
'type': 'polyline',
'lineColor': 'rgb(0,0,255)',
'lineWidth': 4.0,
'closed': True,
'points': [[60531.0, 37045.0, 0.0],
[60528.0, 37048.0, 0.0],
[60527.0, 37048.0, 0.0],
[60522.0, 37053.0, 0.0],
[60522.0, 37054.0, 0.0]],
'label': {'value': 'mostly_lymphocytic_infiltrate'},
'fillColor': 'rgba(0,0,255,0.2)'}]}
Post the annotation to the correct item/slide in DSA¶
[15]:
# deleting existing annotations in target slide (if any)
existing_annotations = gc.get('/annotation/item/' + SAMPLE_SLIDE_ID)
for ann in existing_annotations:
gc.delete('/annotation/%s' % ann['_id'])
# post the annotation documents you created
for annotation_doc in annotation_docs:
resp = gc.post(
'/annotation?itemId=' + SAMPLE_SLIDE_ID, json=annotation_doc)
Now you can go to HistomicsUI and confirm that the posted annotations make sense and correspond to tissue boundaries and expected labels.