diff --git a/images_cache/cache_interface.py b/images_cache/cache_interface.py index 42b0fc9..1305fdd 100644 --- a/images_cache/cache_interface.py +++ b/images_cache/cache_interface.py @@ -1,3 +1,5 @@ +from ome_seadragon import settings + from abc import ABCMeta, abstractmethod @@ -10,13 +12,15 @@ def _get_tile_key(self, image_id, level, column, row, tile_size, image_format, image_quality=None): if image_quality: image_format = '%s%s' % (image_format, image_quality) - return 'TILE::IMG_%s|L_%s|C_%s-R_%s|S_%spx|F_%s' % (image_id, level, column, row, - tile_size, image_format.upper()) + return 'TILE::IMG_%s|L_%s|C_%s-R_%s|S_%spx|F_%s|E_%s' % (image_id, level, column, row, + tile_size, image_format.upper(), + settings.TILES_RENDERING_ENGINE) @abstractmethod - def _get_thumbnail_key(self, image_id, image_width, image_height, image_format): - return 'THUMB::IMG_%s|W_%spx-H_%spx|F_%s' % (image_id, image_width, image_height, - image_format.upper()) + def _get_thumbnail_key(self, image_id, thumbnail_size, image_format): + return 'THUMB::IMG_%s|S_%spx|F_%s|E_%s' % (image_id, thumbnail_size, + image_format.upper(), + settings.THUMBNAILS_RENDERING_ENGINE) @abstractmethod def tile_to_cache(self, image_id, image_obj, level, column, row, tile_size, image_format, @@ -29,9 +33,9 @@ def tile_from_cache(self, image_id, level, column, row, tile_size, image_format, pass @abstractmethod - def thumbnail_to_cache(self, image_id, image_obj, image_width, image_height, image_format): + def thumbnail_to_cache(self, image_id, image_obj, thumbnail_size, image_format): pass @abstractmethod - def thumbnail_from_cache(self, image_id, image_width, image_height, image_format): + def thumbnail_from_cache(self, image_id, thumbnail_size, image_format): pass diff --git a/images_cache/fake_cache.py b/images_cache/fake_cache.py index 574a8fa..ba9ccfc 100644 --- a/images_cache/fake_cache.py +++ b/images_cache/fake_cache.py @@ -10,7 +10,7 @@ def _get_tile_key(self, image_id, level, column, row, tile_size, image_format, image_quality=None): pass - def _get_thumbnail_key(self, image_id, image_width, image_height, image_format): + def _get_thumbnail_key(self, image_id, thumbnail_size, image_format): pass def tile_to_cache(self, image_id, image_obj, level, column, row, tile_size, image_format, @@ -21,8 +21,8 @@ def tile_from_cache(self, image_id, level, column, row, tile_size, image_format, image_quality=None): return None - def thumbnail_to_cache(self, image_id, image_obj, image_width, image_height, image_format): + def thumbnail_to_cache(self, image_id, image_obj, thumbnail_size, image_format): pass - def thumbnail_from_cache(self, image_id, image_width, image_height, image_format): + def thumbnail_from_cache(self, image_id, thumbnail_size, image_format): return None diff --git a/images_cache/redis_img_cache.py b/images_cache/redis_img_cache.py index 98e697c..dc936f3 100644 --- a/images_cache/redis_img_cache.py +++ b/images_cache/redis_img_cache.py @@ -19,9 +19,8 @@ def _get_tile_key(self, image_id, level, column, row, tile_size, image_format, tile_size, image_format, image_quality) - def _get_thumbnail_key(self, image_id, image_width, image_height, image_format): - return super(RedisCache, self)._get_thumbnail_key(image_id, image_width, - image_height, image_format) + def _get_thumbnail_key(self, image_id, thumbnail_size, image_format): + return super(RedisCache, self)._get_thumbnail_key(image_id, thumbnail_size, image_format) def tile_to_cache(self, image_id, image_obj, level, column, row, tile_size, image_format, image_quality=None): @@ -49,16 +48,15 @@ def tile_from_cache(self, image_id, level, column, row, tile_size, image_format, self.logger.info('No tile') return None - def thumbnail_to_cache(self, image_id, image_obj, image_width, image_height, image_format): + def thumbnail_to_cache(self, image_id, image_obj, thumbnail_size, image_format): out_buffer = StringIO() image_obj.save(out_buffer, image_format) - th_key = self._get_thumbnail_key(image_id, image_width, - image_height, image_format) + th_key = self._get_thumbnail_key(image_id, thumbnail_size, image_format) self.client.set(th_key, out_buffer.getvalue()) self._set_expire_time(th_key) - def thumbnail_from_cache(self, image_id, image_width, image_height, image_format): - th_key = self._get_thumbnail_key(image_id, image_width, image_height, image_format) + def thumbnail_from_cache(self, image_id, thumbnail_size, image_format): + th_key = self._get_thumbnail_key(image_id, thumbnail_size, image_format) self.logger.info('Thumbnail from cache: %s' % th_key) img_str = self.client.get(th_key) if img_str: diff --git a/ome_data/projects_datasets.py b/ome_data/projects_datasets.py index 33bf918..24bee62 100644 --- a/ome_data/projects_datasets.py +++ b/ome_data/projects_datasets.py @@ -13,7 +13,19 @@ def _get_image_resolution(image_object): return image_object.getSizeX() * image_object.getSizeY() -def _project_to_json(project_object, datasets_map=None): +def get_fileset_highest_resolution(image_object, connection): + fs = connection.getObject('Fileset', image_object.getFileset().getId()) + big_image = None + for tmp_img in fs.copyImages(): + if big_image is None: + big_image = tmp_img + else: + if (tmp_img.getSizeX() * tmp_img.getSizeY()) > (big_image.getSizeX() * big_image.getSizeY()): + big_image = tmp_img + return big_image + + +def _project_to_json(project_object, connection, datasets_map=None): prj_json = { 'id': project_object.getId(), 'name': project_object.getName(), @@ -22,11 +34,11 @@ def _project_to_json(project_object, datasets_map=None): } if datasets_map is not None: for dset, imgs in datasets_map: - prj_json['datasets'].append(_dataset_to_json(dset, imgs)) + prj_json['datasets'].append(_dataset_to_json(dset, connection, imgs)) return prj_json -def _dataset_to_json(dataset_object, image_objects=None): +def _dataset_to_json(dataset_object, connection, image_objects=None): dset_obj = { 'id': dataset_object.getId(), 'type': dataset_object.OMERO_CLASS, @@ -37,11 +49,11 @@ def _dataset_to_json(dataset_object, image_objects=None): } if image_objects is not None: for img_obj in image_objects: - dset_obj['images'].append(_image_to_json(img_obj)) + dset_obj['images'].append(_image_to_json(img_obj, connection)) return dset_obj -def _image_to_json(image_object, full_info=False, roi_objects=None): +def _image_to_json(image_object, connection, full_info=False, roi_objects=None): img_obj = { 'id': image_object.getId(), 'type': 'image', @@ -53,7 +65,8 @@ def _image_to_json(image_object, full_info=False, roi_objects=None): 'creationTime': _date_to_timestamp(image_object.getDate()), 'importTime': _date_to_timestamp(image_object.creationEventDate()), 'lastUpdate': _date_to_timestamp(image_object.updateEventDate()), - 'rois': [] + 'rois': [], + 'high_resolution_image': get_fileset_highest_resolution(image_object, connection).getId() } if full_info: img_obj.update({ @@ -101,7 +114,8 @@ def get_projects(connection, fetch_datasets=False): datasets = list(proj.listChildren()) else: datasets = [] - projects_json.append(_project_to_json(proj, ((d, []) for d in datasets))) + projects_json.append(_project_to_json(proj, connection=connection, + datasets_map=((d, []) for d in datasets))) return projects_json @@ -119,7 +133,8 @@ def get_project(connection, project_id, fetch_datasets=False, fetch_images=False else: images = [] datasets_map.append((ds, images)) - return _project_to_json(project, datasets_map) + return _project_to_json(project, datasets_map=datasets_map, + connection=connection) return None @@ -131,7 +146,7 @@ def get_dataset(connection, dataset_id, fetch_images=False, expand_img_series=Fa images = _get_images_for_dataset(dataset, not expand_img_series) else: images = [] - return _dataset_to_json(dataset, images) + return _dataset_to_json(dataset, connection=connection, image_objects=images) return None @@ -145,5 +160,5 @@ def get_image(connection, image_id, fetch_rois=False): rois = list(results.rois) else: rois = [] - return _image_to_json(img, True, rois) + return _image_to_json(img, connection, True, rois) return None diff --git a/ome_data/tags_data.py b/ome_data/tags_data.py index 671c709..f7b7b70 100644 --- a/ome_data/tags_data.py +++ b/ome_data/tags_data.py @@ -53,7 +53,7 @@ def _get_images_by_tag(tag_id, connection): imgs_generator = connection.getObjectsByAnnotations('Image', [tag_id]) images = list() for img in imgs_generator: - images.append(_image_to_json(img)) + images.append(_image_to_json(img, connection)) return images diff --git a/settings.py b/settings.py index b407ebe..88ee458 100644 --- a/settings.py +++ b/settings.py @@ -21,6 +21,10 @@ def time_dict_to_seconds(value): # /bin/omero config set omero.web.ome_seadragon.repository $(/bin/omero config get omero.data.dir) 'omero.web.ome_seadragon.repository': ['IMGS_REPOSITORY', None, identity, None], 'omero.web.ome_seadragon.images_folder': ['IMGS_FOLDER', 'ManagedRepository', identity, None], + # default rendering engine + 'omero.web.ome_seadragon.tiles.rendering_engine': ['TILES_RENDERING_ENGINE', 'openslide', identity, None], + 'omero.web.ome_seadragon.thumbnails.rendering_engine': ['THUMBNAILS_RENDERING_ENGINE', 'omero', + identity, None], # deepzoom properties 'omero.web.ome_seadragon.deepzoom.overlap': ['DEEPZOOM_OVERLAP', 1, identity, None], 'omero.web.ome_seadragon.deepzoom.format': ['DEEPZOOM_FORMAT', 'jpeg', identity, None], diff --git a/slides_manager/__init__.py b/slides_manager/__init__.py index 690175f..406a623 100644 --- a/slides_manager/__init__.py +++ b/slides_manager/__init__.py @@ -1,121 +1,38 @@ -import logging -import os -import openslide -from openslide import OpenSlide, OpenSlideError -from openslide.deepzoom import DeepZoomGenerator -from cStringIO import StringIO -from PIL import Image - from ome_seadragon import settings -from ome_seadragon.images_cache import CacheDriverFactory - - -logger = logging.getLogger(__name__) - -def _get_image_path(image_id, conn): - img = conn.getObject("Image", image_id) - if img is None: - return None - else: - return os.path.join(settings.IMGS_REPOSITORY, settings.IMGS_FOLDER, - img.getImportedImageFilePaths()['server_paths'][0]) +class UnknownRenderingEngine(Exception): + pass -def _get_deepzoom_slide_config(): - return { - 'tile_size': settings.DEEPZOOM_TILE_SIZE, - 'overlap': settings.DEEPZOOM_OVERLAP, - 'limit_bounds': settings.DEEPZOOM_LIMIT_BOUNDS - } +class RenderingEngineFactory(object): -def _get_slide(image_path): - oslide_ref = OpenSlide(image_path) - slide = DeepZoomGenerator(oslide_ref, **_get_deepzoom_slide_config()) - try: - mpp_x = oslide_ref.properties[openslide.PROPERTY_NAME_MPP_X] - mpp_y = oslide_ref.properties[openslide.PROPERTY_NAME_MPP_Y] - slide.mpp = (float(mpp_x) + float(mpp_y)) / 2 - except (KeyError, ValueError): - slide.mpp = 0 - return slide + def __init__(self): + self.tiles_rendering_engine = settings.TILES_RENDERING_ENGINE + self.thumbnails_rendering_engine = settings.THUMBNAILS_RENDERING_ENGINE + def _get_openslide_engine(self, image_id, connection): + from openslide_engine import OpenSlideEngine -def get_deepzoom_metadata(image_id, conn): - image_path = _get_image_path(image_id, conn) - if image_path: - slide = _get_slide(image_path) - return slide, settings.DEEPZOOM_FORMAT - else: - return None, settings.DEEPZOOM_FORMAT + return OpenSlideEngine(image_id, connection) + def _get_omero_engine(self, image_id, connection): + from ome_engine import OmeEngine -def get_image_mpp(image_id, conn): - image_path = _get_image_path(image_id, conn) - if image_path: - slide = OpenSlide(image_path) - try: - mpp_x = slide.properties[openslide.PROPERTY_NAME_MPP_X] - mpp_y = slide.properties[openslide.PROPERTY_NAME_MPP_Y] - return (float(mpp_x) + float(mpp_y)) / 2 - except (KeyError, ValueError): - return 0 + return OmeEngine(image_id, connection) - -def get_thumbnail(image_id, width, height, conn): - cache_factory = CacheDriverFactory() - cache = cache_factory.get_cache() - # get thumbnail from cache - thumbnail = cache.thumbnail_from_cache(image_id, width, height, - settings.DEEPZOOM_FORMAT) - # if thumbnail is not in cache build it... - if thumbnail is None: - logger.info('No item loaded from cache, building it') - image_path = _get_image_path(image_id, conn) - if image_path: - slide = OpenSlide(image_path) - thumbnail = slide.get_thumbnail((width, height)) - # ... and store it to cache - cache.thumbnail_to_cache(image_id, thumbnail, width, height, - settings.DEEPZOOM_FORMAT) + def get_tiles_rendering_engine(self, image_id, connection): + if self.tiles_rendering_engine == 'openslide': + return self._get_openslide_engine(image_id, connection) + if self.tiles_rendering_engine == 'omero': + return self._get_omero_engine(image_id, connection) else: - thumbnail = None - else: - logger.info('Thumbnail loaded from cache') - return thumbnail, settings.DEEPZOOM_FORMAT - + raise UnknownRenderingEngine('%s is not a valid rendering engine' % self.tiles_rendering_engine) -def get_tile(image_id, level, column, row, conn): - cache_factory = CacheDriverFactory() - cache = cache_factory.get_cache() - # configure cache callback - cache_callback_params = { - 'image_id': image_id, - 'level': level, - 'column': column, - 'row': row, - 'tile_size': settings.DEEPZOOM_TILE_SIZE, - 'image_format': settings.DEEPZOOM_FORMAT - } - if settings.DEEPZOOM_FORMAT.lower() == 'jpeg': - cache_callback_params['image_quality'] = settings.DEEPZOOM_JPEG_QUALITY - image = cache.tile_from_cache(**cache_callback_params) - # if tile is not in cache build it... - if image is None: - image_path = _get_image_path(image_id, conn) - if image_path: - slide = _get_slide(image_path) - tile = slide.get_tile(level, (column, row)) - img_buffer = StringIO() - tile_conf = { - 'format': settings.DEEPZOOM_FORMAT - } - if settings.DEEPZOOM_FORMAT == 'jpeg': - tile_conf['quality'] = settings.DEEPZOOM_JPEG_QUALITY - tile.save(img_buffer, **tile_conf) - image = Image.open(img_buffer) - # ... and store it to cache - cache_callback_params['image_obj'] = image - cache.tile_to_cache(**cache_callback_params) - return image, settings.DEEPZOOM_FORMAT + def get_thumbnails_rendering_engine(self, image_id, connection): + if self.thumbnails_rendering_engine == 'openslide': + return self._get_openslide_engine(image_id, connection) + if self.thumbnails_rendering_engine == 'omero': + return self._get_omero_engine(image_id, connection) + else: + raise UnknownRenderingEngine('%s is not a valid rendering engine' % self.thumbnails_rendering_engine) diff --git a/slides_manager/ome_engine.py b/slides_manager/ome_engine.py new file mode 100644 index 0000000..292881f --- /dev/null +++ b/slides_manager/ome_engine.py @@ -0,0 +1,186 @@ +import math +from lxml import etree +from PIL import Image +from cStringIO import StringIO + +from ome_seadragon.ome_data.projects_datasets import get_fileset_highest_resolution +from rendering_engine_interface import RenderingEngineInterface +from ome_seadragon import settings +from ome_seadragon.images_cache import CacheDriverFactory + + +class OmeEngine(RenderingEngineInterface): + + def __init__(self, image_id, connection): + super(OmeEngine, self).__init__(image_id, connection) + + # if get_biggest_in_filest is True, return the image with the highest resolution in the fileset + # of the image with ID image_id, if False simply return image with ID image_id + def _get_image_object(self, get_biggest_in_fileset=True): + img = self.connection.getObject('Image', self.image_id) + if img is None: + return None + if get_biggest_in_fileset: + return get_fileset_highest_resolution(img, self.connection) + else: + return img + + def _get_dzi_max_level(self, img=None): + if img is None: + img = self._get_image_object() + x = img.getSizeX() + y = img.getSizeY() + return int(math.ceil(math.log(max(x, y), 2))) + + def _get_dzi_scale_factor(self, level, img=None): + max_level = self._get_dzi_max_level(img) + if level > max_level: + raise ValueError('Level %d is higher than max DZI level for the image' % level) + return math.pow(0.5, max_level - level) + + def _get_ome_scale_map(self, img=None, swap_keys=False): + if img is None: + img = self._get_image_object() + tmp_map = img.getZoomLevelScaling() + ome_scale_map = dict((len(tmp_map) - k - 1, v) for k, v in tmp_map.iteritems()) + if not swap_keys: + return ome_scale_map + else: + return dict((v, k) for k, v in ome_scale_map.iteritems()) + + def _get_ome_scale_factor(self, level, img=None): + return self._get_ome_scale_map(img)[level] + + def _get_best_downscale_level(self, level, img_obj=None): + ome_scale_map = self._get_ome_scale_map(img_obj, swap_keys=True) + dzi_scale_factor = self._get_dzi_scale_factor(level, img_obj) + for ome_scale_factor, ome_level in sorted(ome_scale_map.iteritems(), reverse=True): + if ome_scale_factor < dzi_scale_factor: + return ome_level + 1 + return ome_level + + def _get_scale_factor(self, original_x, original_y, ome_scale_factor, dzi_scale_factor): + ome_w = original_x * ome_scale_factor + ome_h = original_y * ome_scale_factor + ome_diag = math.sqrt(math.pow(ome_w, 2) + math.pow(ome_h, 2)) + dzi_w = original_x * dzi_scale_factor + dzi_h = original_y * dzi_scale_factor + dzi_diag = math.sqrt(math.pow(dzi_w, 2) + math.pow(dzi_h, 2)) + return ome_diag / dzi_diag + + def _get_ome_tile(self, ome_img, ome_level, dzi_level, column, row): + scale_factor = self._get_scale_factor(ome_img.getSizeX(), ome_img.getSizeY(), + self._get_ome_scale_factor(ome_level, ome_img), + self._get_dzi_scale_factor(dzi_level, ome_img)) + ome_tile_size = (settings.DEEPZOOM_TILE_SIZE + 2 * settings.DEEPZOOM_OVERLAP) * scale_factor + ome_x = row * (settings.DEEPZOOM_TILE_SIZE * scale_factor) + if ome_x != 0: + ome_x -= settings.DEEPZOOM_OVERLAP * scale_factor + ome_y = column * (settings.DEEPZOOM_TILE_SIZE * scale_factor) + if ome_y != 0: + ome_y -= settings.DEEPZOOM_OVERLAP * scale_factor + if ome_x == 0 or (ome_x + ome_tile_size >= ome_img.getSizeX()): + ome_tile_size_x = ome_tile_size - (settings.DEEPZOOM_OVERLAP * scale_factor) + else: + ome_tile_size_x = ome_tile_size + if ome_y == 0 or (ome_y + ome_tile_size >= ome_img.getSizeY()): + ome_tile_size_y = ome_tile_size - (settings.DEEPZOOM_OVERLAP * scale_factor) + else: + ome_tile_size_y = ome_tile_size + # right now, we don't handle Z and T levels + self.logger.debug('Getting tile with settings: X %s Y %s W %s H %s L %s', ome_x, ome_y, + ome_tile_size_x, ome_tile_size_y, ome_level) + try: + tile_buffer = StringIO(ome_img.renderJpegRegion(0, 0, ome_x, ome_y, ome_tile_size_x, + ome_tile_size_y, level=ome_level, + compression=settings.DEEPZOOM_JPEG_QUALITY/100.0)) + ome_tile = Image.open(tile_buffer) + tile_w, tile_h = ome_tile.size + if scale_factor != 1: + self.logger.debug('Scale factor is %s resize tile', scale_factor) + tile = ome_tile.resize((int(round(tile_w / scale_factor)), + int(round(tile_h / scale_factor))), + Image.ANTIALIAS) + else: + tile = ome_tile + return tile + except TypeError: + # return a white tile + return Image.new('RGB', (settings.DEEPZOOM_TILE_SIZE, settings.DEEPZOOM_TILE_SIZE), 'white') + + def _get_image_mpp(self): + img = self._get_image_object() + if img: + try: + mpp = (img.getPixelSizeX() + img.getPixelSizeY()) / 2.0 + except TypeError: + mpp = 0 + else: + mpp = 0 + return mpp + + def get_openseadragon_config(self): + return { + 'mpp': self._get_image_mpp() + } + + def get_dzi_description(self): + img = self._get_image_object() + if img: + dzi_root = etree.Element( + 'Image', + attrib={ + 'Format': str(settings.DEEPZOOM_FORMAT), + 'Overlap': str(settings.DEEPZOOM_OVERLAP), + 'TileSize': str(settings.DEEPZOOM_TILE_SIZE) + }, + nsmap={None: 'http://schemas.microsoft.com/deepzoom/2008'} + ) + etree.SubElement(dzi_root, 'Size', + attrib={'Height': str(img.getSizeY()), 'Width': str(img.getSizeX())}) + return etree.tostring(dzi_root) + else: + return None + + def get_thumbnail(self, size): + cache = CacheDriverFactory().get_cache() + thumbnail = cache.thumbnail_from_cache(self.image_id, size, + settings.DEEPZOOM_FORMAT) + if thumbnail is None: + self.logger.info('No thumbnail loaded from cache, building it') + # we want the thumbnail of the image, not the one of the highest resolution image in fileset + ome_img = self._get_image_object(get_biggest_in_fileset=False) + if ome_img: + if ome_img.getSizeX() >= ome_img.getSizeY(): + th_size = (size, ) + else: + th_w = size * (float(ome_img.getSizeX()) / ome_img.getSizeY()) + th_size = (th_w, size) + thumbnail_buffer = StringIO(ome_img.getThumbnail(size=th_size)) + thumbnail = Image.open(thumbnail_buffer) + cache.thumbnail_to_cache(self.image_id, thumbnail, size, + settings.DEEPZOOM_FORMAT) + else: + self.logger.info('Thumbnail loaded from cache') + return thumbnail, settings.DEEPZOOM_FORMAT + + def get_tile(self, level, column, row): + cache = CacheDriverFactory().get_cache() + cache_params = { + 'image_id': self.image_id, + 'level': level, + 'column': column, + 'row': row, + 'tile_size': settings.DEEPZOOM_TILE_SIZE, + 'image_format': settings.DEEPZOOM_FORMAT + } + if cache_params['image_format'].lower() == 'jpeg': + cache_params['image_quality'] = settings.DEEPZOOM_JPEG_QUALITY + tile = cache.tile_from_cache(**cache_params) + if tile is None: + ome_img = self._get_image_object() + ome_level = self._get_best_downscale_level(level, ome_img) + tile = self._get_ome_tile(ome_img, ome_level, level, row=column, column=row) + cache_params['image_obj'] = tile + cache.tile_to_cache(**cache_params) + return tile, settings.DEEPZOOM_FORMAT diff --git a/slides_manager/openslide_engine.py b/slides_manager/openslide_engine.py new file mode 100644 index 0000000..55bc4ff --- /dev/null +++ b/slides_manager/openslide_engine.py @@ -0,0 +1,111 @@ +import openslide +from openslide import OpenSlide +from openslide.deepzoom import DeepZoomGenerator +from cStringIO import StringIO +from PIL import Image + +from rendering_engine_interface import RenderingEngineInterface +from ome_seadragon import settings +from ome_seadragon.images_cache import CacheDriverFactory + + +class OpenSlideEngine(RenderingEngineInterface): + + def __init__(self, image_id, connection): + super(OpenSlideEngine, self).__init__(image_id, connection) + + def _get_openslide_wrapper(self): + img_path = self._get_image_path() + if img_path: + return OpenSlide(img_path) + else: + return None + + def _get_deepzoom_config(self): + return { + 'tile_size': settings.DEEPZOOM_TILE_SIZE, + 'overlap': settings.DEEPZOOM_OVERLAP, + 'limit_bounds': settings.DEEPZOOM_LIMIT_BOUNDS + } + + def _get_deepzoom_wrapper(self): + os_wrapper = self._get_openslide_wrapper() + if os_wrapper: + return DeepZoomGenerator(os_wrapper, **self._get_deepzoom_config()) + else: + return None + + def _get_image_mpp(self): + slide = self._get_openslide_wrapper() + if slide: + try: + mpp_x = slide.properties[openslide.PROPERTY_NAME_MPP_X] + mpp_y = slide.properties[openslide.PROPERTY_NAME_MPP_Y] + return (float(mpp_x) + float(mpp_y)) / 2 + except (KeyError, ValueError): + return 0 + else: + return 0 + + def get_openseadragon_config(self): + return { + 'mpp': self._get_image_mpp() + } + + def get_dzi_description(self): + dzi_slide = self._get_deepzoom_wrapper() + if dzi_slide: + return dzi_slide.get_dzi(settings.DEEPZOOM_FORMAT) + else: + return None + + def get_thumbnail(self, size): + cache = CacheDriverFactory().get_cache() + # get thumbnail from cache + thumb = cache.thumbnail_from_cache(self.image_id, size, + settings.DEEPZOOM_FORMAT) + # if thumbnail is not in cache build it .... + if thumb is None: + self.logger.info('No thumbnail loaded from cache, building it') + slide = self._get_openslide_wrapper() + if slide: + thumb = slide.get_thumbnail((size, size)) + # ... and store it into the cache + cache.thumbnail_to_cache(self.image_id, thumb, size, + settings.DEEPZOOM_FORMAT) + else: + self.logger.info('Thumbnail loaded from cache') + return thumb, settings.DEEPZOOM_FORMAT + + def get_tile(self, level, column, row): + cache = CacheDriverFactory().get_cache() + cache_params = { + 'image_id': self.image_id, + 'level': level, + 'column': column, + 'row': row, + 'tile_size': settings.DEEPZOOM_TILE_SIZE, + 'image_format': settings.DEEPZOOM_FORMAT + } + if cache_params['image_format'].lower() == 'jpeg': + cache_params['image_quality'] = settings.DEEPZOOM_JPEG_QUALITY + # get tile from cache + tile = cache.tile_from_cache(**cache_params) + # if tile is not in cache build id ... + if tile is None: + img_path = self._get_image_path() + if img_path: + slide = self._get_deepzoom_wrapper() + dzi_tile = slide.get_tile(level, (column, row)) + tile_buffer = StringIO() + tile_conf = { + 'format': settings.DEEPZOOM_FORMAT + } + if tile_conf['format'].lower() == 'jpeg': + tile_conf['quality'] = settings.DEEPZOOM_JPEG_QUALITY + dzi_tile.save(tile_buffer, **tile_conf) + tile = Image.open(tile_buffer) + # ... and store it into the cache + cache_params['image_obj'] = tile + cache.tile_to_cache(**cache_params) + return tile, settings.DEEPZOOM_FORMAT diff --git a/slides_manager/rendering_engine_interface.py b/slides_manager/rendering_engine_interface.py new file mode 100644 index 0000000..bf2a323 --- /dev/null +++ b/slides_manager/rendering_engine_interface.py @@ -0,0 +1,45 @@ +from abc import ABCMeta, abstractmethod +import os +import logging + +from ome_seadragon import settings + + +class RenderingEngineInterface(object): + + __metaclass__ = ABCMeta + + def __init__(self, image_id, connection): + self.connection = connection + self.image_id = image_id + self.logger = logging.getLogger(__name__) + + def _get_image_object(self): + return self.connection.getObject('Image', self.image_id) + + def _get_image_path(self): + img = self._get_image_object() + if img is None: + return None + else: + return os.path.join( + settings.IMGS_REPOSITORY, + settings.IMGS_FOLDER, + img.getImportedImageFilePaths()['server_paths'][0] + ) + + @abstractmethod + def get_openseadragon_config(self): + pass + + @abstractmethod + def get_dzi_description(self): + pass + + @abstractmethod + def get_thumbnail(self, size): + pass + + @abstractmethod + def get_tile(self, level, column, row): + pass diff --git a/views.py b/views.py index 54a4a8f..a3304ad 100644 --- a/views.py +++ b/views.py @@ -1,6 +1,6 @@ from ome_seadragon.ome_data import tags_data, projects_datasets from ome_seadragon import settings -from ome_seadragon import slides_manager +from ome_seadragon.slides_manager import RenderingEngineFactory import logging from distutils.util import strtobool @@ -159,19 +159,18 @@ def find_annotations(request, conn=None, **kwargs): @login_required() def get_image_dzi(request, image_id, conn=None, **kwargs): - slide_obj, slides_format = slides_manager.get_deepzoom_metadata(image_id, conn) - if slide_obj: - return HttpResponse(slide_obj.get_dzi(slides_format), content_type='application/xml') + rendering_engine = RenderingEngineFactory().get_tiles_rendering_engine(image_id, conn) + dzi_metadata = rendering_engine.get_dzi_description() + if dzi_metadata: + return HttpResponse(dzi_metadata, content_type='application/xml') else: - return HttpResponseNotFound("No image with ID " + image_id) + return HttpResponseNotFound('No image with ID ' + image_id) @login_required() def get_image_thumbnail(request, image_id, conn=None, **kwargs): - thumbnail, image_format = slides_manager.get_thumbnail(image_id, - int(request.GET.get('width')), - int(request.GET.get('height')), - conn) + rendering_engine = RenderingEngineFactory().get_thumbnails_rendering_engine(image_id, conn) + thumbnail, image_format = rendering_engine.get_thumbnail(int(request.GET.get('size'))) if thumbnail: response = HttpResponse(content_type="image/%s" % image_format) thumbnail.save(response, image_format) @@ -184,17 +183,18 @@ def get_image_thumbnail(request, image_id, conn=None, **kwargs): def get_tile(request, image_id, level, column, row, tile_format, conn=None, **kwargs): if tile_format != settings.DEEPZOOM_FORMAT: return HttpResponseServerError("Format %s not supported by the server" % tile_format) - image, image_format = slides_manager.get_tile(image_id, int(level), - int(column), int(row), conn) - if image: + rendering_engine = RenderingEngineFactory().get_tiles_rendering_engine(image_id, conn) + tile, image_format = rendering_engine.get_tile(int(level), int(column), int(row)) + if tile: response = HttpResponse(content_type='image/%s' % image_format) - image.save(response, image_format) + tile.save(response, image_format) return response else: - return HttpResponseNotFound("No tile can be found") + return HttpResponseNotFound('No tile can be found') @login_required() def get_image_mpp(request, image_id, conn=None, **kwargs): - image_mpp = slides_manager.get_image_mpp(image_id, conn) + rendering_engine = RenderingEngineFactory().get_tiles_rendering_engine(image_id, conn) + image_mpp = rendering_engine.get_openseadragon_config()['mpp'] return HttpResponse(json.dumps({'image_mpp': image_mpp}), content_type='application_json')