Пример #1
0
 def makeTagsetCanvas(tagString, tagsetPixIds, showSubsetLabels):
     log(" Tagset: %s  (contains %d images)"
         % (tagString, len(tagsetPixIds)))
     if not showSubsetLabels:
         tagString = None
     subCanvas = imgUtil.paintThumbnailGrid(
         thumbnailStore, length,
         spacing, tagsetPixIds, colCount, topLabel=tagString)
     tagSubPanes.append(subCanvas)
Пример #2
0
 def makeTagsetCanvas(tagString, tagsetPixIds, showSubsetLabels):
     log(" Tagset: %s  (contains %d images)" %
         (tagString, len(tagsetPixIds)))
     if not showSubsetLabels:
         tagString = None
     subCanvas = imgUtil.paintThumbnailGrid(thumbnailStore,
                                            length,
                                            spacing,
                                            tagsetPixIds,
                                            colCount,
                                            topLabel=tagString)
     tagSubPanes.append(subCanvas)
Пример #3
0
def paintDatasetCanvas(conn,
                       images,
                       title,
                       tagIds=None,
                       showUntagged=False,
                       colCount=10,
                       length=100):
    """
        Paints and returns a canvas of thumbnails from images, laid out in a
        set number of columns.
        Title and date-range of the images is printed above the thumbnails,
        to the left and right, respectively.

        @param conn:        Blitz connection
        @param imageIds:    Image IDs
        @param title:       title to display at top of figure. String
        @param tagIds:      Optional to sort thumbnails by tag. [long]
        @param colCount:    Max number of columns to lay out thumbnails
        @param length:      Length of longest side of thumbnails
    """

    mode = "RGB"
    figCanvas = None
    spacing = length / 40 + 2

    thumbnailStore = conn.createThumbnailStore()
    # returns  omero.api.ThumbnailStorePrx
    metadataService = conn.getMetadataService()

    if len(images) == 0:
        return None
    timestampMin = images[0].getDate()  # datetime
    timestampMax = timestampMin

    dsImageIds = []
    imagePixelMap = {}
    imageNames = {}

    # sort the images by name
    images.sort(key=lambda x: (x.getName().lower()))

    for image in images:
        imageId = image.getId()
        pixelId = image.getPrimaryPixels().getId()
        name = image.getName()
        dsImageIds.append(imageId)  # make a list of image-IDs
        imagePixelMap[imageId] = pixelId  # and a map of image-ID: pixel-ID
        imageNames[imageId] = name
        timestampMin = min(timestampMin, image.getDate())
        timestampMax = max(timestampMax, image.getDate())

    # set-up fonts
    fontsize = length / 7 + 5
    font = imgUtil.getFont(fontsize)
    textHeight = font.getsize("Textq")[1]
    topSpacer = spacing + textHeight
    leftSpacer = spacing + textHeight

    tagPanes = []
    maxWidth = 0
    totalHeight = topSpacer

    # if we have a list of tags, then sort images by tag
    if tagIds:
        # Cast to int since List can be any type
        tagIds = [int(tagId) for tagId in tagIds]
        log(" Sorting images by tags: %s" % tagIds)
        tagNames = {}
        taggedImages = {}  # a map of tagId: list-of-image-Ids
        imgTags = {}  # a map of imgId: list-of-tagIds
        for tagId in tagIds:
            taggedImages[tagId] = []

        # look for images that have a tag
        types = ["ome.model.annotations.TagAnnotation"]
        annotations = metadataService.loadAnnotations("Image", dsImageIds,
                                                      types, None, None)
        # filter images by annotation...
        for imageId, tags in annotations.items():
            imgTagIds = []
            for tag in tags:
                tagId = tag.getId().getValue()
                # make a dict of tag-names
                tagNames[tagId] = tag.getTextValue().getValue().decode('utf8')
                print "     Tag:", tagId, tagId in tagIds
                imgTagIds.append(tagId)
            imgTags[imageId] = imgTagIds

        # get a sorted list of {'iid': iid, 'tagKey': tagKey,
        # 'tagIds':orderedTags}
        sortedThumbs = sortImagesByTag(tagIds, imgTags)

        if not showUntagged:
            sortedThumbs = [t for t in sortedThumbs if len(t['tagIds']) > 0]

        # Need to group sets of thumbnails by FIRST tag.
        toptagSets = []
        groupedPixelIds = []
        showSubsetLabels = False
        currentTagStr = None
        for i, img in enumerate(sortedThumbs):
            tagIds = img['tagIds']
            if len(tagIds) == 0:
                tagString = "Not Tagged"
            else:
                tagString = tagNames[tagIds[0]]
            if tagString == currentTagStr or currentTagStr is None:
                # only show subset labels (later) if there are more than 1
                # subset
                if (len(tagIds) > 1):
                    showSubsetLabels = True
                groupedPixelIds.append({
                    'pid': imagePixelMap[img['iid']],
                    'tagIds': tagIds
                })
            else:
                toptagSets.append({
                    'tagText': currentTagStr,
                    'pixelIds': groupedPixelIds,
                    'showSubsetLabels': showSubsetLabels
                })
                showSubsetLabels = len(tagIds) > 1
                groupedPixelIds = [{
                    'pid': imagePixelMap[img['iid']],
                    'tagIds': tagIds
                }]
            currentTagStr = tagString
        toptagSets.append({
            'tagText': currentTagStr,
            'pixelIds': groupedPixelIds,
            'showSubsetLabels': showSubsetLabels
        })

        # Find the indent we need
        maxTagNameWidth = max(
            [font.getsize(ts['tagText'])[0] for ts in toptagSets])
        if showUntagged:
            maxTagNameWidth = max(maxTagNameWidth,
                                  font.getsize("Not Tagged")[0])

        print "toptagSets", toptagSets

        tagSubPanes = []

        # make a canvas for each tag combination
        def makeTagsetCanvas(tagString, tagsetPixIds, showSubsetLabels):
            log(" Tagset: %s  (contains %d images)" %
                (tagString, len(tagsetPixIds)))
            if not showSubsetLabels:
                tagString = None
            subCanvas = imgUtil.paintThumbnailGrid(thumbnailStore,
                                                   length,
                                                   spacing,
                                                   tagsetPixIds,
                                                   colCount,
                                                   topLabel=tagString)
            tagSubPanes.append(subCanvas)

        for toptagSet in toptagSets:
            tagText = toptagSet['tagText']
            showSubsetLabels = toptagSet['showSubsetLabels']
            imageData = toptagSet['pixelIds']
            # loop through all thumbs under TAG, grouping into subsets.
            tagsetPixIds = []
            currentTagStr = None
            for i, img in enumerate(imageData):
                tag_ids = img['tagIds']
                pid = img['pid']
                tagString = ", ".join([tagNames[tid] for tid in tag_ids])
                if tagString == "":
                    tagString = "Not Tagged"
                # Keep grouping thumbs under similar tag set (if not on the
                # last loop)
                if tagString == currentTagStr or currentTagStr is None:
                    tagsetPixIds.append(pid)
                else:
                    # Process thumbs added so far
                    makeTagsetCanvas(currentTagStr, tagsetPixIds,
                                     showSubsetLabels)
                    # reset for next tagset
                    tagsetPixIds = [pid]
                currentTagStr = tagString

            makeTagsetCanvas(currentTagStr, tagsetPixIds, showSubsetLabels)

            maxWidth = max([c.size[0] for c in tagSubPanes])
            totalHeight = sum([c.size[1] for c in tagSubPanes])

            # paste them into a single canvas for each Tag

            leftSpacer = spacing + maxTagNameWidth + 2 * spacing
            # Draw vertical line to right
            size = (leftSpacer + maxWidth, totalHeight)
            tagCanvas = Image.new(mode, size, WHITE)
            pX = leftSpacer
            pY = 0
            for pane in tagSubPanes:
                imgUtil.pasteImage(pane, tagCanvas, pX, pY)
                pY += pane.size[1]
            if tagText is not None:
                draw = ImageDraw.Draw(tagCanvas)
                tt_w, tt_h = font.getsize(tagText)
                h_offset = (totalHeight - tt_h) / 2
                draw.text((spacing, h_offset),
                          tagText,
                          font=font,
                          fill=(50, 50, 50))
            # draw vertical line
            draw.line(
                (leftSpacer - spacing, 0, leftSpacer - spacing, totalHeight),
                fill=(0, 0, 0))
            tagPanes.append(tagCanvas)
            tagSubPanes = []
    else:
        leftSpacer = spacing
        pixelIds = []
        for imageId in dsImageIds:
            log("  Name: %s  ID: %d" % (imageNames[imageId], imageId))
            pixelIds.append(imagePixelMap[imageId])
        figCanvas = imgUtil.paintThumbnailGrid(thumbnailStore, length, spacing,
                                               pixelIds, colCount)
        tagPanes.append(figCanvas)

    # paste them into a single canvas
    tagsetSpacer = length / 3
    maxWidth = max([c.size[0] for c in tagPanes])
    totalHeight = totalHeight + sum(
        [c.size[1] + tagsetSpacer for c in tagPanes]) - tagsetSpacer
    size = (maxWidth, totalHeight)
    fullCanvas = Image.new(mode, size, WHITE)
    pX = 0
    pY = topSpacer
    for pane in tagPanes:
        imgUtil.pasteImage(pane, fullCanvas, pX, pY)
        pY += pane.size[1] + tagsetSpacer

    # create dates for the image timestamps. If dates are not the same, show
    # first - last.
    # firstdate = timestampMin
    # lastdate = timestampMax
    # figureDate = str(firstdate)
    # if firstdate != lastdate:
    #     figureDate = "%s - %s" % (firstdate, lastdate)

    draw = ImageDraw.Draw(fullCanvas)
    # dateWidth = draw.textsize(figureDate, font=font)[0]
    # titleWidth = draw.textsize(title, font=font)[0]
    dateY = spacing
    # dateX = fullCanvas.size[0] - spacing - dateWidth
    draw.text((leftSpacer, dateY), title, font=font, fill=(0, 0, 0))  # title
    # Don't show dates: see
    # https://github.com/openmicroscopy/openmicroscopy/pull/1002
    # if (leftSpacer+titleWidth) < dateX:
    # if there's enough space...
    #     draw.text((dateX, dateY), figureDate, font=font, fill=(0,0,0))
    # add date

    return fullCanvas
Пример #4
0
def paintDatasetCanvas(conn, images, title, tagIds=None, showUntagged=False,
                       colCount=10, length=100):
    """
        Paints and returns a canvas of thumbnails from images, laid out in a
        set number of columns.
        Title and date-range of the images is printed above the thumbnails,
        to the left and right, respectively.

        @param conn:        Blitz connection
        @param imageIds:    Image IDs
        @param title:       title to display at top of figure. String
        @param tagIds:      Optional to sort thumbnails by tag. [long]
        @param colCount:    Max number of columns to lay out thumbnails
        @param length:      Length of longest side of thumbnails
    """

    mode = "RGB"
    figCanvas = None
    spacing = length/40 + 2

    thumbnailStore = conn.createThumbnailStore()
    # returns  omero.api.ThumbnailStorePrx
    metadataService = conn.getMetadataService()

    if len(images) == 0:
        return None
    timestampMin = images[0].getDate()   # datetime
    timestampMax = timestampMin

    dsImageIds = []
    imagePixelMap = {}
    imageNames = {}

    # sort the images by name
    images.sort(key=lambda x: (x.getName().lower()))

    for image in images:
        imageId = image.getId()
        pixelId = image.getPrimaryPixels().getId()
        name = image.getName()
        dsImageIds.append(imageId)        # make a list of image-IDs
        imagePixelMap[imageId] = pixelId    # and a map of image-ID: pixel-ID
        imageNames[imageId] = name
        timestampMin = min(timestampMin, image.getDate())
        timestampMax = max(timestampMax, image.getDate())

    # set-up fonts
    fontsize = length/7 + 5
    font = imgUtil.getFont(fontsize)
    textHeight = font.getsize("Textq")[1]
    topSpacer = spacing + textHeight
    leftSpacer = spacing + textHeight

    tagPanes = []
    maxWidth = 0
    totalHeight = topSpacer

    # if we have a list of tags, then sort images by tag
    if tagIds:
        # Cast to int since List can be any type
        tagIds = [int(tagId) for tagId in tagIds]
        log(" Sorting images by tags: %s" % tagIds)
        tagNames = {}
        taggedImages = {}    # a map of tagId: list-of-image-Ids
        imgTags = {}        # a map of imgId: list-of-tagIds
        for tagId in tagIds:
            taggedImages[tagId] = []

        # look for images that have a tag
        types = ["ome.model.annotations.TagAnnotation"]
        annotations = metadataService.loadAnnotations(
            "Image", dsImageIds, types, None, None)
        # filter images by annotation...
        for imageId, tags in annotations.items():
            imgTagIds = []
            for tag in tags:
                tagId = tag.getId().getValue()
                # make a dict of tag-names
                tagNames[tagId] = tag.getTextValue().getValue()
                print "     Tag:", tagId, tagId in tagIds
                imgTagIds.append(tagId)
            imgTags[imageId] = imgTagIds

        # get a sorted list of {'iid': iid, 'tagKey': tagKey,
        # 'tagIds':orderedTags}
        sortedThumbs = sortImagesByTag(tagIds, imgTags)

        if not showUntagged:
            sortedThumbs = [t for t in sortedThumbs if len(t['tagIds']) > 0]

        # Need to group sets of thumbnails by FIRST tag.
        toptagSets = []
        groupedPixelIds = []
        showSubsetLabels = False
        currentTagStr = None
        for i, img in enumerate(sortedThumbs):
            tagIds = img['tagIds']
            if len(tagIds) == 0:
                tagString = "Not Tagged"
            else:
                tagString = tagNames[tagIds[0]]
            if tagString == currentTagStr or currentTagStr is None:
                # only show subset labels (later) if there are more than 1
                # subset
                if (len(tagIds) > 1):
                    showSubsetLabels = True
                groupedPixelIds.append({
                    'pid': imagePixelMap[img['iid']],
                    'tagIds': tagIds})
            else:
                toptagSets.append({
                    'tagText': currentTagStr,
                    'pixelIds': groupedPixelIds,
                    'showSubsetLabels': showSubsetLabels})
                showSubsetLabels = len(tagIds) > 1
                groupedPixelIds = [{
                    'pid': imagePixelMap[img['iid']],
                    'tagIds': tagIds}]
            currentTagStr = tagString
        toptagSets.append({
            'tagText': currentTagStr,
            'pixelIds': groupedPixelIds,
            'showSubsetLabels': showSubsetLabels})

        # Find the indent we need
        maxTagNameWidth = max([font.getsize(ts['tagText'])[0]
                               for ts in toptagSets])
        if showUntagged:
            maxTagNameWidth = max(maxTagNameWidth,
                                  font.getsize("Not Tagged")[0])

        print "toptagSets", toptagSets

        tagSubPanes = []

        # make a canvas for each tag combination
        def makeTagsetCanvas(tagString, tagsetPixIds, showSubsetLabels):
            log(" Tagset: %s  (contains %d images)"
                % (tagString, len(tagsetPixIds)))
            if not showSubsetLabels:
                tagString = None
            subCanvas = imgUtil.paintThumbnailGrid(
                thumbnailStore, length,
                spacing, tagsetPixIds, colCount, topLabel=tagString)
            tagSubPanes.append(subCanvas)

        for toptagSet in toptagSets:
            tagText = toptagSet['tagText']
            showSubsetLabels = toptagSet['showSubsetLabels']
            imageData = toptagSet['pixelIds']
            # loop through all thumbs under TAG, grouping into subsets.
            tagsetPixIds = []
            currentTagStr = None
            for i, img in enumerate(imageData):
                tag_ids = img['tagIds']
                pid = img['pid']
                tagString = ", ".join([tagNames[tid] for tid in tag_ids])
                if tagString == "":
                    tagString = "Not Tagged"
                # Keep grouping thumbs under similar tag set (if not on the
                # last loop)
                if tagString == currentTagStr or currentTagStr is None:
                    tagsetPixIds.append(pid)
                else:
                    # Process thumbs added so far
                    makeTagsetCanvas(currentTagStr, tagsetPixIds,
                                     showSubsetLabels)
                    # reset for next tagset
                    tagsetPixIds = [pid]
                currentTagStr = tagString

            makeTagsetCanvas(currentTagStr, tagsetPixIds, showSubsetLabels)

            maxWidth = max([c.size[0] for c in tagSubPanes])
            totalHeight = sum([c.size[1] for c in tagSubPanes])

            # paste them into a single canvas for each Tag

            leftSpacer = spacing + maxTagNameWidth + 2*spacing
            # Draw vertical line to right
            size = (leftSpacer + maxWidth, totalHeight)
            tagCanvas = Image.new(mode, size, WHITE)
            pX = leftSpacer
            pY = 0
            for pane in tagSubPanes:
                imgUtil.pasteImage(pane, tagCanvas, pX, pY)
                pY += pane.size[1]
            if tagText is not None:
                draw = ImageDraw.Draw(tagCanvas)
                tt_w, tt_h = font.getsize(tagText)
                h_offset = (totalHeight - tt_h)/2
                draw.text((spacing, h_offset), tagText, font=font,
                          fill=(50, 50, 50))
            # draw vertical line
            draw.line((leftSpacer-spacing, 0, leftSpacer - spacing,
                       totalHeight), fill=(0, 0, 0))
            tagPanes.append(tagCanvas)
            tagSubPanes = []
    else:
        leftSpacer = spacing
        pixelIds = []
        for imageId in dsImageIds:
            log("  Name: %s  ID: %d" % (imageNames[imageId], imageId))
            pixelIds.append(imagePixelMap[imageId])
        figCanvas = imgUtil.paintThumbnailGrid(
            thumbnailStore, length, spacing, pixelIds, colCount)
        tagPanes.append(figCanvas)

    # paste them into a single canvas
    tagsetSpacer = length / 3
    maxWidth = max([c.size[0] for c in tagPanes])
    totalHeight = totalHeight + sum([c.size[1]+tagsetSpacer
                                     for c in tagPanes]) - tagsetSpacer
    size = (maxWidth, totalHeight)
    fullCanvas = Image.new(mode, size, WHITE)
    pX = 0
    pY = topSpacer
    for pane in tagPanes:
        imgUtil.pasteImage(pane, fullCanvas, pX, pY)
        pY += pane.size[1] + tagsetSpacer

    # create dates for the image timestamps. If dates are not the same, show
    # first - last.
    # firstdate = timestampMin
    # lastdate = timestampMax
    # figureDate = str(firstdate)
    # if firstdate != lastdate:
    #     figureDate = "%s - %s" % (firstdate, lastdate)

    draw = ImageDraw.Draw(fullCanvas)
    # dateWidth = draw.textsize(figureDate, font=font)[0]
    # titleWidth = draw.textsize(title, font=font)[0]
    dateY = spacing
    # dateX = fullCanvas.size[0] - spacing - dateWidth
    draw.text((leftSpacer, dateY), title, font=font, fill=(0, 0, 0))  # title
    # Don't show dates: see
    # https://github.com/openmicroscopy/openmicroscopy/pull/1002
    # if (leftSpacer+titleWidth) < dateX:
    # if there's enough space...
    #     draw.text((dateX, dateY), figureDate, font=font, fill=(0,0,0))
    # add date

    return fullCanvas
Пример #5
0
def paintDatasetCanvas(conn,
                       images,
                       title,
                       tagIds=None,
                       showUntagged=False,
                       colCount=10,
                       length=100):
    """
        Paints and returns a canvas of thumbnails from images, laid out in a set number of columns. 
        Title and date-range of the images is printed above the thumbnails,
        to the left and right, respectively. 
        
        @param conn:        Blitz connection
        @param imageIds:    Image IDs
        @param title:       title to display at top of figure. String
        @param tagIds:      Optional to sort thumbnails by tag. [long]
        @param colCount:    Max number of columns to lay out thumbnails 
        @param length:      Length of longest side of thumbnails
    """

    mode = "RGB"
    figCanvas = None
    spacing = length / 40 + 2

    thumbnailStore = conn.createThumbnailStore(
    )  # returns  omero.api.ThumbnailStorePrx
    metadataService = conn.getMetadataService()

    if len(images) == 0:
        return None
    timestampMin = images[0].getDate()  # datetime
    timestampMax = timestampMin

    dsImageIds = []
    imagePixelMap = {}
    imageNames = {}

    # sort the images by name
    images.sort(key=lambda x: (x.getName().lower()))

    for image in images:
        imageId = image.getId()
        pixelId = image.getPrimaryPixels().getId()
        name = image.getName()
        dsImageIds.append(imageId)  # make a list of image-IDs
        imagePixelMap[imageId] = pixelId  # and a map of image-ID: pixel-ID
        imageNames[imageId] = name
        timestampMin = min(timestampMin, image.getDate())
        timestampMax = max(timestampMax, image.getDate())

    # set-up fonts
    fontsize = length / 7 + 5
    font = imgUtil.getFont(fontsize)
    textHeight = font.getsize("Textq")[1]
    topSpacer = spacing + textHeight
    leftSpacer = spacing + textHeight

    tagPanes = []
    maxWidth = 0
    totalHeight = topSpacer

    # if we have a list of tags, then sort images by tag
    if tagIds:
        log(" Sorting images by tags")
        tagNames = {}
        taggedImages = {}  # a map of tagId: list-of-image-Ids
        for tagId in tagIds:
            taggedImages[tagId] = []

        # look for images that have a tag
        types = ["ome.model.annotations.TagAnnotation"]
        annotations = metadataService.loadAnnotations("Image", dsImageIds,
                                                      types, None, None)
        #filter images by annotation...
        for imageId, tags in annotations.items():
            for tag in tags:
                tagId = tag.getId().getValue()
                if tagId in tagIds:  # if image has multiple tags, it will be display more than once
                    taggedImages[tagId].append(
                        imageId)  # add the image id to the appropriate list
                    if imageId in dsImageIds:
                        dsImageIds.remove(
                            imageId)  # remember which we've picked already
                    if tagId not in tagNames.keys():
                        tagNames[tagId] = tag.getTextValue().getValue(
                        )  # make a dict of tag-names

        # if we want to show remaining images in dataset (not picked by tag)...
        if showUntagged:
            tagIds.append("noTag")
            taggedImages["noTag"] = [untaggedId for untaggedId in dsImageIds]
            tagNames["noTag"] = "Untagged"

        # print results and convert image-id to pixel-id
        # make a canvas for each tag
        for tagId in tagIds:
            if tagId not in tagNames.keys():  # no images with this tag
                continue
            leftLabel = tagNames[tagId]
            log(" Tag: %s  (contains %d images)" %
                (leftLabel, len(taggedImages[tagId])))
            pixelIds = []
            for imageId in taggedImages[tagId]:
                log("  Name: %s  ID: %d" % (imageNames[imageId], imageId))
                pixelIds.append(imagePixelMap[imageId])
            print 'pixelIds', pixelIds
            tagCanvas = imgUtil.paintThumbnailGrid(thumbnailStore,
                                                   length,
                                                   spacing,
                                                   pixelIds,
                                                   colCount,
                                                   leftLabel=leftLabel)
            tagPanes.append(tagCanvas)
            maxWidth = max(maxWidth, tagCanvas.size[0])
            totalHeight += tagCanvas.size[1]

    else:
        leftSpacer = spacing
        pixelIds = []
        for imageId in dsImageIds:
            log("  Name: %s  ID: %d" % (imageNames[imageId], imageId))
            pixelIds.append(imagePixelMap[imageId])
        figCanvas = imgUtil.paintThumbnailGrid(thumbnailStore, length, spacing,
                                               pixelIds, colCount)
        tagPanes.append(figCanvas)
        maxWidth = max(maxWidth, figCanvas.size[0])
        totalHeight += figCanvas.size[1]

    # paste them into a single canvas
    size = (maxWidth, totalHeight)
    fullCanvas = Image.new(mode, size, WHITE)
    pX = 0
    pY = topSpacer
    for pane in tagPanes:
        imgUtil.pasteImage(pane, fullCanvas, pX, pY)
        pY += pane.size[1]

    # create dates for the image timestamps. If dates are not the same, show first - last.
    firstdate = timestampMin
    lastdate = timestampMax
    figureDate = str(firstdate)
    if firstdate != lastdate:
        figureDate = "%s - %s" % (firstdate, lastdate)

    draw = ImageDraw.Draw(fullCanvas)
    dateWidth = draw.textsize(figureDate, font=font)[0]
    titleWidth = draw.textsize(title, font=font)[0]
    dateY = spacing
    dateX = fullCanvas.size[0] - spacing - dateWidth
    draw.text((leftSpacer, dateY), title, font=font, fill=(0, 0, 0))  # title
    if (leftSpacer + titleWidth) < dateX:  # if there's enough space...
        draw.text((dateX, dateY), figureDate, font=font,
                  fill=(0, 0, 0))  # add date

    return fullCanvas
def paintDatasetCanvas(conn, images, title, tagIds=None, showUntagged = False, colCount = 10, length = 100):
    """
        Paints and returns a canvas of thumbnails from images, laid out in a set number of columns. 
        Title and date-range of the images is printed above the thumbnails,
        to the left and right, respectively. 
        
        @param conn:        Blitz connection
        @param imageIds:    Image IDs
        @param title:       title to display at top of figure. String
        @param tagIds:      Optional to sort thumbnails by tag. [long]
        @param colCount:    Max number of columns to lay out thumbnails 
        @param length:      Length of longest side of thumbnails
    """
    
    mode = "RGB"
    figCanvas = None
    spacing = length/40 + 2
    
    thumbnailStore = conn.createThumbnailStore()        # returns  omero.api.ThumbnailStorePrx
    metadataService = conn.getMetadataService()
    
    if len(images) == 0:
        return None
    timestampMin = images[0].getDate()   # datetime
    timestampMax = timestampMin
    
    dsImageIds = []
    imagePixelMap = {}
    imageNames = {}
    
    # sort the images by name
    images.sort(key=lambda x:(x.getName().lower()))
    
    for image in images:
        imageId = image.getId()
        pixelId = image.getPrimaryPixels().getId()
        name = image.getName()
        dsImageIds.append(imageId)        # make a list of image-IDs
        imagePixelMap[imageId] = pixelId    # and a map of image-ID: pixel-ID
        imageNames[imageId] = name
        timestampMin = min(timestampMin, image.getDate())
        timestampMax = max(timestampMax, image.getDate())
    
    # set-up fonts
    fontsize = length/7 + 5
    font = imgUtil.getFont(fontsize)
    textHeight = font.getsize("Textq")[1]
    topSpacer = spacing + textHeight
    leftSpacer = spacing + textHeight
    
    tagPanes = []
    maxWidth = 0
    totalHeight = topSpacer
    
    # if we have a list of tags, then sort images by tag 
    if tagIds:
        log(" Sorting images by tags")
        tagNames = {}
        taggedImages = {}    # a map of tagId: list-of-image-Ids
        for tagId in tagIds:
            taggedImages[tagId] = []
        
        # look for images that have a tag
        types = ["ome.model.annotations.TagAnnotation"]
        annotations = metadataService.loadAnnotations("Image", dsImageIds, types, None, None)
        #filter images by annotation...
        for imageId, tags in annotations.items():
            for tag in tags:
                tagId = tag.getId().getValue()
                if tagId in tagIds:        # if image has multiple tags, it will be display more than once
                    taggedImages[tagId].append(imageId)        # add the image id to the appropriate list
                    if imageId in dsImageIds:
                        dsImageIds.remove(imageId)                # remember which we've picked already
                    if tagId not in tagNames.keys():
                        tagNames[tagId] = tag.getTextValue().getValue()        # make a dict of tag-names
        
        # if we want to show remaining images in dataset (not picked by tag)...
        if showUntagged:
            tagIds.append("noTag")
            taggedImages["noTag"] = [untaggedId for untaggedId in dsImageIds]
            tagNames["noTag"] = "Untagged"
        
        # print results and convert image-id to pixel-id
        # make a canvas for each tag
        for tagId in tagIds:
            if tagId not in tagNames.keys():    # no images with this tag
                continue
            leftLabel = tagNames[tagId]
            log(" Tag: %s  (contains %d images)" % (leftLabel, len(taggedImages[tagId])))
            pixelIds = []
            for imageId in taggedImages[tagId]:
                log("  Name: %s  ID: %d" % (imageNames[imageId], imageId))
                pixelIds.append(imagePixelMap[imageId])
            print 'pixelIds', pixelIds
            tagCanvas = imgUtil.paintThumbnailGrid(thumbnailStore, length, spacing, pixelIds, colCount, leftLabel=leftLabel)
            tagPanes.append(tagCanvas)
            maxWidth = max(maxWidth, tagCanvas.size[0])
            totalHeight += tagCanvas.size[1]
    
    else:
        leftSpacer = spacing
        pixelIds = []
        for imageId in dsImageIds:
            log("  Name: %s  ID: %d" % (imageNames[imageId], imageId))
            pixelIds.append(imagePixelMap[imageId])
        figCanvas = imgUtil.paintThumbnailGrid(thumbnailStore, length, spacing, pixelIds, colCount)
        tagPanes.append(figCanvas)
        maxWidth = max(maxWidth, figCanvas.size[0])
        totalHeight += figCanvas.size[1]
    
    # paste them into a single canvas
    size = (maxWidth, totalHeight)
    fullCanvas = Image.new(mode, size, WHITE)
    pX = 0
    pY = topSpacer
    for pane in tagPanes:
        imgUtil.pasteImage(pane, fullCanvas, pX, pY)
        pY += pane.size[1]
        
    # create dates for the image timestamps. If dates are not the same, show first - last. 
    firstdate = timestampMin
    lastdate = timestampMax
    figureDate = str(firstdate)
    if firstdate != lastdate:
        figureDate = "%s - %s" % (firstdate, lastdate)

    draw = ImageDraw.Draw(fullCanvas)
    dateWidth = draw.textsize(figureDate, font=font) [0]
    titleWidth = draw.textsize(title, font=font) [0]
    dateY = spacing
    dateX = fullCanvas.size[0] - spacing - dateWidth
    draw.text((leftSpacer, dateY), title, font=font, fill=(0,0,0))        # title
    if (leftSpacer+titleWidth) < dateX:            # if there's enough space...
        draw.text((dateX, dateY), figureDate, font=font, fill=(0,0,0))    # add date 
    
    return fullCanvas