Skip to content

Commit

Permalink
Get all plottable data
Browse files Browse the repository at this point in the history
  • Loading branch information
manthey committed Jul 10, 2024
1 parent d8d094c commit b20bd99
Show file tree
Hide file tree
Showing 3 changed files with 327 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Show a loading spinner on the image display in geojs in girder ([#1559](../../pull/1559))
- Better handle images that are composed of a folder and an item ([#1561](../../pull/1561))
- Allow specifying which sources are checked with canReadList ([#1562](../../pull/1562))
- Added endpoints to get plottable data related to annotations ([#1524](../../pull/1524))

### Bug Fixes
- Fix a compositing error in transformed multi source images ([#1560](../../pull/1560))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from girder.utility import JsonEncoder
from girder.utility.progress import setResponseTimeLimit

from .. import constants
from .. import constants, utils
from ..models.annotation import Annotation, AnnotationSchema
from ..models.annotationelement import Annotationelement

Expand Down Expand Up @@ -65,6 +65,8 @@ def __init__(self):
self.route('GET', ('item', ':id'), self.getItemAnnotations)
self.route('POST', ('item', ':id'), self.createItemAnnotations)
self.route('DELETE', ('item', ':id'), self.deleteItemAnnotations)
self.route('POST', ('item', ':id', 'plot', 'list'), self.getItemPlottableElements)
self.route('POST', ('item', ':id', 'plot', 'data'), self.getItemPlottableData)
self.route('GET', ('folder', ':id'), self.returnFolderAnnotations)
self.route('GET', ('folder', ':id', 'present'), self.existFolderAnnotations)
self.route('GET', ('folder', ':id', 'create'), self.canCreateFolderAnnotations)
Expand Down Expand Up @@ -617,6 +619,45 @@ def deleteItemAnnotations(self, item):
count += 1
return count

@autoDescribeRoute(
Description('Get a list of plottable data related to an item and its annotations.')
.modelParam('id', model=Item, level=AccessType.READ)
.jsonParam('annotations', 'A JSON list of annotation IDs that should '
'be included. An entry of __all__ will include all '
'annotations.', paramType='formData', requireArray=True,
required=False)
.errorResponse('ID was invalid.')
.errorResponse('Read access was denied for the item.', 403),
)
@access.public(cookie=True, scope=TokenScope.DATA_READ)
def getItemPlottableElements(self, item, annotations):
user = self.getCurrentUser()
data = utils.PlottableItemData(user, item, annotations=annotations)
return data.columns

Check warning on line 636 in girder_annotation/girder_large_image_annotation/rest/annotation.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/rest/annotation.py#L634-L636

Added lines #L634 - L636 were not covered by tests

@autoDescribeRoute(
Description('Get plottable data related to an item and its annotations.')
.modelParam('id', model=Item, level=AccessType.READ)
.param('adjacentItems', 'Whether to include adjacent item data.',
required=False, default=True, dataType='boolean')
.param('keys', 'A comma separated list of data keys to retrieve (not json).',
required=True)
.param('requiredKeys', 'A comma separated list of data keys that must '
'be non null in all response rows (not json).', required=False)
.jsonParam('annotations', 'A JSON list of annotation IDs that should '
'be included. An entry of \\__all__ will include all '
'annotations.', paramType='formData', requireArray=True,
required=False)
.errorResponse('ID was invalid.')
.errorResponse('Read access was denied for the item.', 403),
)
@access.public(cookie=True, scope=TokenScope.DATA_READ)
def getItemPlottableData(self, item, keys, adjacentItems, annotations, requiredKeys):
user = self.getCurrentUser()
data = utils.PlottableItemData(

Check warning on line 657 in girder_annotation/girder_large_image_annotation/rest/annotation.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/rest/annotation.py#L656-L657

Added lines #L656 - L657 were not covered by tests
user, item, annotations=annotations, adjacentItems=adjacentItems)
return data.data(keys, requiredKeys)

Check warning on line 659 in girder_annotation/girder_large_image_annotation/rest/annotation.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/rest/annotation.py#L659

Added line #L659 was not covered by tests

def getFolderAnnotations(self, id, recurse, user, limit=False, offset=False, sort=False,
sortDir=False, count=False):

Expand Down
284 changes: 284 additions & 0 deletions girder_annotation/girder_large_image_annotation/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import json
import math
import re

from bson.objectid import ObjectId

from girder import logger
from girder.constants import AccessType
from girder.models.folder import Folder


class AnnotationGeoJSON:
Expand Down Expand Up @@ -334,3 +341,280 @@ def isGeoJSON(annotation):
'Feature', 'FeatureCollection', 'GeometryCollection', 'Point',
'LineString', 'Polygon', 'MultiPoint', 'MultiLineString',
'MultiPolygon'}


class PlottableItemData:
maxItems = 1000
maxAnnotationElements = 10000
maxDistinct = 20
allowedTypes = (str, bool, int, float)

def __init__(self, user, item, annotations=None, adjacentItems=False):
"""
Get plottable data associated with an item.
:param user: authenticating user.
:param item: the item record.
:param annotations: None, a list of annotation ids, or __all__. If
adjacent items are included, the most recent annotation with the
same name will also be included.
:param adjacentItems: if True, include data other items in the same
folder.
"""
self.user = user
self._columns = None
self._datacolumns = None
self._data = None
self._findItems(item, adjacentItems)
self._findAnnotations(annotations)

Check warning on line 369 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L364-L369

Added lines #L364 - L369 were not covered by tests

def _findItems(self, item, adjacentItems=False):
self._columns = None
self.item = item
self.folder = Folder().load(id=item['folderId'], user=self.user, level=AccessType.READ)
self.items = [item]

Check warning on line 375 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L372-L375

Added lines #L372 - L375 were not covered by tests
if adjacentItems:
for entry in Folder().childItems(self.folder):
if len(self.items) >= self.maxItems:
break

Check warning on line 379 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L379

Added line #L379 was not covered by tests
if entry['_id'] != item['_id']:
# skip if item doesn't have appropriate metadata or
# annotations. If skipping, add to list to check if
# dataframe
# TODO:
self.items.append(entry)

Check warning on line 385 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L385

Added line #L385 was not covered by tests
# TODO: find csv/xlsx/dataframe items in the folder, exclude them from
# the item list but include them in general

def _findAnnotations(self, annotations):
from ..models.annotation import Annotation

Check warning on line 390 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L390

Added line #L390 was not covered by tests

self._columns = None

Check warning on line 392 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L392

Added line #L392 was not covered by tests
if isinstance(annotations, str):
annotations = annotations.split(',')
self.annotations = None

Check warning on line 395 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L394-L395

Added lines #L394 - L395 were not covered by tests
if annotations and len(annotations):
self.annotations = []
query = {'_active': {'$ne': False}, 'itemId': self.item['_id']}

Check warning on line 398 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L397-L398

Added lines #L397 - L398 were not covered by tests
if annotations[0] != '__all__':
query['_id'] = {'$in': [ObjectId(annotId) for annotId in annotations]}
self.annotations.append(list(Annotation().find(

Check warning on line 401 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L401

Added line #L401 was not covered by tests
query, limit=0, sort=[('_version', -1)])))
if not len(self.annotations[0]):
self.annotations = None

Check warning on line 404 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L404

Added line #L404 was not covered by tests
# Find adjacent annotations
if annotations and len(self.items) > 1:
names = {}

Check warning on line 407 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L407

Added line #L407 was not covered by tests
for idx, annot in enumerate(self.annotations[0]):
if annot['annotation']['name'] not in names:
names[annot['annotation']['name']] = idx

Check warning on line 410 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L410

Added line #L410 was not covered by tests
for adjitem in self.items[1:]:
query = {'_active': {'$ne': False}, 'itemId': adjitem['_id']}
annotList = [None] * len(self.annotations[0])

Check warning on line 413 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L412-L413

Added lines #L412 - L413 were not covered by tests
for annot in Annotation().find(query, limit=0, sort=[('_version', -1)]):
if annot['annotation']['name'] in names and annotList[
names[annot['annotation']['name']]] is None:
annotList[names[annot['annotation']['name']]] = annot
self.annotations.append(annotList)

Check warning on line 418 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L417-L418

Added lines #L417 - L418 were not covered by tests

def _addColumn(self, columns, fullkey, title, root, key, source):
if fullkey not in columns:
columns[fullkey] = {

Check warning on line 422 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L422

Added line #L422 was not covered by tests
'key': fullkey,
'type': 'number',
'where': [[root, key, source]], 'title': title,
'count': 0, 'distinct': set(), 'min': None,
'max': None}
return (root, source, 0)

Check warning on line 428 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L428

Added line #L428 was not covered by tests
elif [root, key, source] not in columns[fullkey]['where']:
columns[fullkey]['where'].append([root, key, source])
where = -1

Check warning on line 431 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L430-L431

Added lines #L430 - L431 were not covered by tests
for colwhere in columns[fullkey]['where']:
if colwhere[0] == root and colwhere[2] == source:
where += 1

Check warning on line 434 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L434

Added line #L434 was not covered by tests
if tuple(colwhere) == (root, key, source):
return (root, source, where)
return (root, source, where)

Check warning on line 437 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L436-L437

Added lines #L436 - L437 were not covered by tests

def _columnKey(self, source, root, key):
if not hasattr(self, '_columnKeyCache'):
self._columnKeyCache = {}
hashkey = (source, root, key)

Check warning on line 442 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L441-L442

Added lines #L441 - L442 were not covered by tests
if hashkey in self._columnKeyCache:
return self._columnKeyCache[hashkey]
fullkey = f'{root}.{key}.{source}'.lower()
title = f'{root} {key}'
keymap = {

Check warning on line 447 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L444-L447

Added lines #L444 - L447 were not covered by tests
r'(?i)(item|image)_(id|name)$': {'key': '_0_item.name', 'title': 'Item Name'},
r'(?i)(low|min)(_|)x': {'key': '_bbox.x0', 'title': 'Bounding Box Low X'},
r'(?i)(low|min)(_|)y': {'key': '_bbox.y0', 'title': 'Bounding Box Low Y'},
r'(?i)(high|max)(_|)x': {'key': '_bbox.x1', 'title': 'Bounding Box High X'},
r'(?i)(high|max)(_|)y': {'key': '_bbox.y1', 'title': 'Bounding Box High Y'},
}
for k, v in keymap.items():
if re.match(k, key):
fullkey = v['key']
title = v['title']
break
self._columnKeyCache[hashkey] = fullkey, title
return fullkey, title

Check warning on line 460 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L456-L460

Added lines #L456 - L460 were not covered by tests

def _scanColumnByKey(self, result, key, entry, where=0):
if result['type'] == 'number':
try:

Check warning on line 464 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L464

Added line #L464 was not covered by tests
[float(record[key]) for record in entry
if isinstance(record.get(key), self.allowedTypes)]
except Exception:
result['type'] = 'string'

Check warning on line 468 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L467-L468

Added lines #L467 - L468 were not covered by tests
result['distinct'] = {str(v) for v in result['distinct']}
for ridx, record in enumerate(entry):
v = record.get(key)

Check warning on line 471 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L471

Added line #L471 was not covered by tests
if not isinstance(v, self.allowedTypes):
continue
result['count'] += 1
v = float(v) if result['type'] == 'number' else str(v)

Check warning on line 475 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L473-L475

Added lines #L473 - L475 were not covered by tests
if len(result['distinct']) <= self.maxDistinct:
result['distinct'].add(v)

Check warning on line 477 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L477

Added line #L477 was not covered by tests
if result['type'] == 'number':
if result['min'] is None:
result['min'] = result['max'] = v
result['min'] = min(result['min'], v)
result['max'] = max(result['max'], v)

Check warning on line 482 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L480-L482

Added lines #L480 - L482 were not covered by tests
if self._datacolumns and result['key'] in self._datacolumns:
self._datacolumns[result['key']][(where, ridx)] = v

Check warning on line 484 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L484

Added line #L484 was not covered by tests

def _scanColumn(self, meta, source, columns, auxmeta=None):
for root, entry in meta.items():
if not isinstance(entry, list) or not len(entry) or not isinstance(entry[0], dict):
continue

Check warning on line 489 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L489

Added line #L489 was not covered by tests
for key in entry[0]:
if not isinstance(entry[0][key], self.allowedTypes):
continue
fullkey, title = self._columnKey(source, root, key)
where = self._addColumn(columns, fullkey, title, root, key, source)
result = columns[fullkey]
self._scanColumnByKey(result, key, entry, where)

Check warning on line 496 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L492-L496

Added lines #L492 - L496 were not covered by tests
if auxmeta:
for aux in auxmeta:
if (isinstance(aux.get(root), list) and
len(aux[root]) and
isinstance(aux[root][0], dict) and
key in aux[root][0]):
self._scanColumnByKey(result, key, aux[root], where)

Check warning on line 503 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L503

Added line #L503 was not covered by tests

@property
def columns(self):
"""
Get a sorted list of plottable columns with some metadata for each.
Each data entry contains
:fullkey: a unique string. This is a good first-order sort
:root: the root data array
:key: the specific data tag
:source: the source of the data (folder, item, annotation,
annotationelement, file)
:type: string or number
:title: a human readable title
:[distinct]: a list of distinct values if there are less than some
maximum number of distinct values. This might not include i
values from adjacent items
:[min]: for number data types, the lowest value present
:[max]: for number data types, the highest value present
:returns: a sorted list of data entries.
"""
if self._columns is not None:
return self._columns
columns = {}
self._addColumn(

Check warning on line 530 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L528-L530

Added lines #L528 - L530 were not covered by tests
columns, '_0_item.name', 'Item Name', 'Item', 'name', 'base')
self._addColumn(

Check warning on line 532 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L532

Added line #L532 was not covered by tests
columns, '_2_item.id', 'Item ID', 'Item', '_id', 'base')
self._scanColumn(self.folder.get('meta', {}), 'folder', columns)

Check warning on line 534 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L534

Added line #L534 was not covered by tests
self._scanColumn(self.item.get('meta', {}), 'item', columns,
[item.get('meta', {}) for item in self.items[1:]])
for anidx, annot in enumerate(self.annotations[0] if self.annotations is not None else []):
self._scanColumn(
annot.get('attributes', {}), 'annotation', columns,
[itemannot[anidx].get('attributes', {})
for itemannot in self.annotations[1:]
if itemannot[anidx] is not None])
if not anidx:
self._addColumn(

Check warning on line 544 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L544

Added line #L544 was not covered by tests
columns, '_1_annotation.name', 'Annotation Name',
'Annotation', 'name', 'base')
self._addColumn(

Check warning on line 547 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L547

Added line #L547 was not covered by tests
columns, '_3_annotation.id', 'Annotation ID',
'Annotation', '_id', 'base')
self._addColumn(

Check warning on line 550 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L550

Added line #L550 was not covered by tests
columns, '_bbox.x0', 'Bounding Box Low X', 'bbox', 'lowx',
'annotationelement')
self._addColumn(

Check warning on line 553 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L553

Added line #L553 was not covered by tests
columns, '_bbox.y0', 'Bounding Box Low Y', 'bbox', 'lowy',
'annotationelement')
self._addColumn(

Check warning on line 556 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L556

Added line #L556 was not covered by tests
columns, '_bbox.x1', 'Bounding Box High X', 'bbox',
'highx', 'annotationelement')
self._addColumn(

Check warning on line 559 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L559

Added line #L559 was not covered by tests
columns, '_bbox.y1', 'Bounding Box High Y', 'bbox',
'highy', 'annotationelement')
# TODO: add annotation elements
# TODO: bbox could be from min/max query
# TODO: Add csv
for result in columns.values():
if len(result['distinct']) <= self.maxDistinct:
result['distinct'] = sorted(result['distinct'])
result['distinctcount'] = len(result['distinct'])

Check warning on line 568 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L567-L568

Added lines #L567 - L568 were not covered by tests
else:
result.pop('distinct', None)

Check warning on line 570 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L570

Added line #L570 was not covered by tests
if result['type'] != 'number' or result['min'] is None:
result.pop('min', None)
result.pop('max', None)

Check warning on line 573 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L572-L573

Added lines #L572 - L573 were not covered by tests
self._columns = sorted(columns.values(), key=lambda x: x['key'])
return self._columns

Check warning on line 575 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L575

Added line #L575 was not covered by tests

def data(self, columns, requiredColumns=None):
"""
Get plottable data.
:param columns: the columns to return. Either a list of column names
or a comma-delimited string.
:param requiredColumns: only return data rows where all of these
columns are non-None. Either a list of column names of a
comma-delimited string.
"""
if not isinstance(columns, list):
columns = columns.split(',')

Check warning on line 588 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L588

Added line #L588 was not covered by tests
if not isinstance(requiredColumns, list):
requiredColumns = requiredColumns.split(',') if requiredColumns is not None else []

Check warning on line 590 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L590

Added line #L590 was not covered by tests
# TODO: Always augment columns with item id, annotation id?
self._datacolumns = {c: {} for c in columns}
rows = set()

Check warning on line 593 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L593

Added line #L593 was not covered by tests
# collects data as a side effect
collist = self.columns

Check warning on line 595 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L595

Added line #L595 was not covered by tests
for coldata in self._datacolumns.values():
rows |= set(coldata.keys())
rows = sorted(rows)

Check warning on line 598 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L597-L598

Added lines #L597 - L598 were not covered by tests
colsout = [col.copy() for col in collist if col['key'] in columns]
for cidx, col in enumerate(colsout):
col['index'] = cidx
logger.info(f'Gathering {len(self._datacolumns)} x {len(rows)} data')

Check warning on line 602 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L601-L602

Added lines #L601 - L602 were not covered by tests
data = [[None] * len(self._datacolumns) for _ in range(len(rows))]
for cidx, col in enumerate(colsout):
colkey = col['key']

Check warning on line 605 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L605

Added line #L605 was not covered by tests
if colkey in self._datacolumns:
datacol = self._datacolumns[colkey]

Check warning on line 607 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L607

Added line #L607 was not covered by tests
for ridx, rowid in enumerate(rows):
data[ridx][cidx] = datacol.get(rowid, None)

Check warning on line 609 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L609

Added line #L609 was not covered by tests
for cidx, col in enumerate(colsout):
colkey = col['key']
numrows = len(data)

Check warning on line 612 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L611-L612

Added lines #L611 - L612 were not covered by tests
if colkey in requiredColumns:
data = [row for row in data if row[cidx] is not None]
if len(data) < numrows:
logger.info(f'Reduced row count from {numrows} to {len(data)} '

Check warning on line 616 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L616

Added line #L616 was not covered by tests
f'because of None values in column {colkey}')
return {

Check warning on line 618 in girder_annotation/girder_large_image_annotation/utils/__init__.py

View check run for this annotation

Codecov / codecov/patch

girder_annotation/girder_large_image_annotation/utils/__init__.py#L618

Added line #L618 was not covered by tests
'columns': colsout,
'data': data}

0 comments on commit b20bd99

Please sign in to comment.