Skip to content

Commit

Permalink
Merge pull request #346 from ImageMarkup/study-add-images
Browse files Browse the repository at this point in the history
Create API endpoints to add additional images and multiple users to a study
  • Loading branch information
brianhelba authored Feb 27, 2017
2 parents d7f1c69 + 1fc34bd commit 0e7eb4b
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 104 deletions.
7 changes: 4 additions & 3 deletions server/api/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@
import datetime

from girder.api import access
from girder.api.rest import Resource, RestException, loadmodel, \
setRawResponse, setResponseHeader
from girder.api.rest import RestException, loadmodel, setRawResponse, \
setResponseHeader
from girder.api.describe import Description, describeRoute
from girder.constants import AccessType

from .base import IsicResource
from ..models.segmentation_helpers import ScikitSegmentationHelper


class AnnotationResource(Resource):
class AnnotationResource(IsicResource):
def __init__(self):
super(AnnotationResource, self).__init__()
self.resourceName = 'annotation'
Expand Down
51 changes: 51 additions & 0 deletions server/api/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

###############################################################################
# Copyright Kitware Inc.
#
# Licensed under the Apache License, Version 2.0 ( the "License" );
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###############################################################################

import json

import cherrypy
import six

from girder.api.rest import Resource


class IsicResource(Resource):
def _decodeParams(self, params):
"""
Decode POSTed or PUTed JSON parameters, from either
"application/x-www-form-urlencoded" or "application/json" bodies.
:param params: The "params" parameter from a Resource route handler.
:type params: dict
:return: The decoded parameters.
:rtype: dict
"""
if cherrypy.request.headers['Content-Type'].split(';')[0] == \
'application/json':
decodedParams = self.getBodyJson()
else:
decodedParams = {}
for field, value in six.viewitems(params):
try:
decodedValue = json.loads(value)
except ValueError:
# Assume this was just a simple string; invalid JSON should
# be caught later by type checking validation
decodedValue = value
decodedParams[field] = decodedValue
return decodedParams
31 changes: 8 additions & 23 deletions server/api/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,16 @@
# limitations under the License.
###############################################################################

import json

import cherrypy
import mimetypes

from girder.api import access
from girder.api.rest import Resource, loadmodel, RestException
from girder.api.rest import loadmodel
from girder.api.describe import Description, describeRoute
from girder.constants import AccessType, SortDir
from girder.models.model_base import AccessException, ValidationException

from .base import IsicResource

CSV_FORMATS = [
'text/csv',
'application/vnd.ms-excel'
Expand All @@ -41,7 +40,7 @@
]


class DatasetResource(Resource):
class DatasetResource(IsicResource):
def __init__(self):
super(DatasetResource, self).__init__()
self.resourceName = 'dataset'
Expand Down Expand Up @@ -123,9 +122,7 @@ def ingestDataset(self, params):
File = self.model('file')
User = self.model('user', 'isic_archive')

if cherrypy.request.headers['Content-Type'].split(';')[0] == \
'application/json':
params = self.getBodyJson()
params = self._decodeParams(params)
self.requireParams(('zipFileId', 'name', 'owner'), params)

user = self.getCurrentUser()
Expand Down Expand Up @@ -232,19 +229,9 @@ def submitReviewImages(self, dataset, params):
user = self.getCurrentUser()
User.requireReviewDataset(user)

isJson = cherrypy.request.headers['Content-Type'].split(';')[0] == \
'application/json'
if isJson:
params = self.getBodyJson()
params = self._decodeParams(params)
self.requireParams(['accepted', 'flagged'], params)

if not isJson:
for field in ['accepted', 'flagged']:
try:
params[field] = json.loads(params[field])
except ValueError:
raise RestException(
'Invalid JSON passed in %s parameter.' % field)
# TODO: validate that parameters are lists of strings

acceptedImages = [
Image.load(imageId, user=user, level=AccessType.READ, exc=True)
Expand Down Expand Up @@ -272,9 +259,7 @@ def registerMetadata(self, dataset, params):
File = self.model('file')
User = self.model('user', 'isic_archive')

if cherrypy.request.headers['Content-Type'].split(';')[0] == \
'application/json':
params = self.getBodyJson()
params = self._decodeParams(params)
self.requireParams('metadataFileId', params)

user = self.getCurrentUser()
Expand Down
22 changes: 5 additions & 17 deletions server/api/featureset.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,16 @@
# limitations under the License.
###############################################################################

import cherrypy
import json

from girder.api import access
from girder.api.rest import Resource, loadmodel, RestException
from girder.api.rest import loadmodel
from girder.api.describe import Description, describeRoute
from girder.constants import SortDir
from girder.models.model_base import ValidationException

from .base import IsicResource


class FeaturesetResource(Resource):
class FeaturesetResource(IsicResource):
def __init__(self):
super(FeaturesetResource, self).__init__()
self.resourceName = 'featureset'
Expand Down Expand Up @@ -114,22 +113,11 @@ def createFeatureset(self, params):
# For now, study admins will be the ones that can create featuresets
User.requireAdminStudy(creatorUser)

isJson = cherrypy.request.headers['Content-Type'].split(';')[0] == \
'application/json'
if isJson:
params = self.getBodyJson()
params = self._decodeParams(params)
self.requireParams(
['name', 'version', 'globalFeatures', 'localFeatures'],
params)

if not isJson:
for field in ['globalFeatures', 'localFeatures']:
try:
params[field] = json.loads(params[field])
except ValueError:
raise RestException(
'Invalid JSON passed in %s parameter.' % field)

featuresetName = params['name'].strip()
if not featuresetName:
raise ValidationException('Name must not be empty.', 'name')
Expand Down
15 changes: 8 additions & 7 deletions server/api/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,17 @@
import geojson

from girder.api import access
from girder.api.rest import Resource, RestException, loadmodel, \
setRawResponse, setResponseHeader
from girder.api.rest import RestException, loadmodel, setRawResponse, \
setResponseHeader
from girder.api.describe import Description, describeRoute
from girder.constants import AccessType
from girder.models.model_base import GirderException

from .base import IsicResource
from ..utility import querylang


class ImageResource(Resource):
class ImageResource(IsicResource):
def __init__(self,):
super(ImageResource, self).__init__()
self.resourceName = 'image'
Expand Down Expand Up @@ -224,19 +225,19 @@ def getSuperpixels(self, image, params):
@loadmodel(model='image', plugin='isic_archive', level=AccessType.READ)
def doSegmentation(self, image, params):
Segmentation = self.model('segmentation', 'isic_archive')
bodyJson = self.getBodyJson()
self.requireParams(('seed', 'tolerance'), bodyJson)
params = self._decodeParams(params)
self.requireParams(('seed', 'tolerance'), params)

# validate parameters
seedCoord = bodyJson['seed']
seedCoord = params['seed']
if not (
isinstance(seedCoord, list) and
len(seedCoord) == 2 and
all(isinstance(value, int) for value in seedCoord)
):
raise RestException('Submitted "seed" must be a coordinate pair.')

tolerance = bodyJson['tolerance']
tolerance = params['tolerance']
if not isinstance(tolerance, int):
raise RestException('Submitted "tolerance" must be an integer.')

Expand Down
29 changes: 15 additions & 14 deletions server/api/segmentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,17 @@
import six

from girder.api import access
from girder.api.rest import Resource, RestException, loadmodel, \
setRawResponse, setResponseHeader
from girder.api.rest import RestException, loadmodel, setRawResponse, \
setResponseHeader
from girder.api.describe import Description, describeRoute
from girder.constants import AccessType, SortDir

from .base import IsicResource
from ..models.segmentation_helpers import OpenCVSegmentationHelper, \
ScikitSegmentationHelper


class SegmentationResource(Resource):
class SegmentationResource(IsicResource):
def __init__(self,):
super(SegmentationResource, self).__init__()
self.resourceName = 'segmentation'
Expand Down Expand Up @@ -112,16 +113,16 @@ def createSegmentation(self, params):
Segmentation = self.model('segmentation', 'isic_archive')
User = self.model('user', 'isic_archive')

bodyJson = self.getBodyJson()
self.requireParams(['imageId'], bodyJson)
params = self._decodeParams(params)
self.requireParams(['imageId'], params)

user = self.getCurrentUser()
User.requireSegmentationSkill(user)

image = Image.load(
bodyJson['imageId'], level=AccessType.READ, user=user, exc=True)
params['imageId'], level=AccessType.READ, user=user, exc=True)

failed = self.boolParam('failed', bodyJson)
failed = self.boolParam('failed', params)

if failed:
segmentation = Segmentation.createSegmentation(
Expand All @@ -132,8 +133,8 @@ def createSegmentation(self, params):
'flagged': 'could not segment'
}
)
elif 'lesionBoundary' in bodyJson:
lesionBoundary = bodyJson['lesionBoundary']
elif 'lesionBoundary' in params:
lesionBoundary = params['lesionBoundary']

meta = lesionBoundary['properties']
meta['startTime'] = datetime.datetime.utcfromtimestamp(
Expand Down Expand Up @@ -178,9 +179,9 @@ def createSegmentation(self, params):
mask=mask,
meta=meta
)
elif 'mask' in bodyJson:
elif 'mask' in params:
maskStream = six.BytesIO()
maskStream.write(base64.b64decode(bodyJson['mask']))
maskStream.write(base64.b64decode(params['mask']))
mask = ScikitSegmentationHelper.loadImage(maskStream)

segmentation = Segmentation.createSegmentation(
Expand Down Expand Up @@ -325,10 +326,10 @@ def doReview(self, segmentation, params):
Segmentation = self.model('segmentation', 'isic_archive')
User = self.model('user', 'isic_archive')

bodyJson = self.getBodyJson()
self.requireParams(['approved'], bodyJson)
params = self._decodeParams(params)
self.requireParams(['approved'], params)

approved = self.boolParam('approved', bodyJson)
approved = self.boolParam('approved', params)

user = self.getCurrentUser()
User.requireSegmentationSkill(user)
Expand Down
Loading

0 comments on commit 0e7eb4b

Please sign in to comment.