" + p
page_y = self.add_para_with_thumb(p, page_y, style=style_n)
else:
page_y = self.add_para_with_thumb(legend, page_y,
style=style_n)
page_y = self.add_para_with_thumb(
"Figure contains the following images:", page_y, style=style_h3)
# Go through sorted panels, adding paragraph for each unique image
for p in panels_json:
iid = p['imageId']
# list unique scalebar lengths
if 'scalebar' in p and p['scalebar'].get('show'):
sb_length = p['scalebar'].get('length')
symbol = u"\u00B5m"
sb_units = p['scalebar'].get('units')
if sb_units and sb_units in unit_symbols:
symbol = unit_symbols[sb_units]['symbol']
scalebars.append("%s %s" % (sb_length, symbol))
if iid in img_ids:
continue # ignore images we've already handled
img_ids.add(iid)
thumb_src = self.get_thumbnail(iid)
# thumb = "" % (thumbSrc, thumbSize, thumbSize)
lines = []
lines.append(p['name'])
img_url = "%s?show=image-%s" % (base_url, iid)
lines.append(
"%s" % (img_url, img_url))
# addPara([" ".join(line)])
line = " ".join(lines)
page_y = self.add_para_with_thumb(
line, page_y, style=style_n, thumb_src=thumb_src)
if len(scalebars) > 0:
scalebars = list(set(scalebars))
page_y = self.add_para_with_thumb("Scalebars:", page_y,
style=style_h3)
page_y = self.add_para_with_thumb(
"Scalebar Lengths: %s" % ", ".join(scalebars),
page_y, style=style_n)
def panel_is_on_page(self, panel, page):
""" Return true if panel overlaps with this page """
px = panel['x']
px2 = px + panel['width']
py = panel['y']
py2 = py + panel['height']
cx = page['x']
cx2 = cx + self.page_width
cy = page['y']
cy2 = cy + self.page_height
# overlap needs overlap on x-axis...
return px < cx2 and cx < px2 and py < cy2 and cy < py2
def add_panels_to_page(self, panels_json, image_ids, page):
""" Add panels that are within the bounds of this page """
for i, panel in enumerate(panels_json):
if not self.panel_is_on_page(panel, page):
continue
image_id = panel['imageId']
# draw_panel() creates PIL image then applies it to the page.
# For TIFF export, draw_panel() also adds shapes to the
# PIL image before pasting onto the page...
image, pil_img = self.draw_panel(panel, page, i)
if image is None:
continue
if image.canAnnotate():
image_ids.add(image_id)
# ... but for PDF we have to add shapes to the whole PDF page
self.add_rois(panel, page) # This does nothing for TIFF export
# Finally, add scale bar and labels to the page
self.draw_scalebar(panel, pil_img.size[0], page)
self.draw_labels(panel, page)
def get_figure_file_ext(self):
return "pdf"
def create_figure(self):
"""
Creates a PDF figure. This is overwritten by ExportTiff subclass.
"""
if not reportlab_installed:
raise ImportError(
"Need to install https://bitbucket.org/rptlab/reportlab")
name = self.get_figure_file_name()
self.figure_canvas = canvas.Canvas(
name, pagesize=(self.page_width, self.page_height))
def add_page_color(self):
""" Simply draw colored rectangle over whole current page."""
page_color = self.figure_json.get('page_color')
if page_color and page_color.lower() != 'ffffff':
rgb = ShapeToPdfExport.get_rgb('#' + page_color)
r = float(rgb[0]) / 255
g = float(rgb[1]) / 255
b = float(rgb[2]) / 255
self.figure_canvas.setStrokeColorRGB(r, g, b)
self.figure_canvas.setFillColorRGB(r, g, b)
self.figure_canvas.setLineWidth(4)
self.figure_canvas.rect(0, 0, self.page_width,
self.page_height, fill=1)
def save_page(self, page=None):
""" Called on completion of each page. Saves page of PDF """
self.figure_canvas.showPage()
def save_figure(self):
""" Completes PDF figure (or info-page PDF for TIFF export) """
self.figure_canvas.save()
def draw_text(self, text, x, y, fontsize, rgb, align="center"):
""" Adds text to PDF. Overwritten for TIFF below """
if markdown_imported:
# convert markdown to html
text = markdown.markdown(text)
y = self.page_height - y
c = self.figure_canvas
# Needs to be wide enough to avoid wrapping
para_width = self.page_width
red, green, blue = rgb
red = float(red) / 255
green = float(green) / 255
blue = float(blue) / 255
alignment = TA_LEFT
if (align == "center"):
alignment = TA_CENTER
x = x - (para_width / 2)
elif (align == "right"):
alignment = TA_RIGHT
x = x - para_width
elif (align == "left"):
pass
elif align == 'left-vertical':
# Switch axes
c.rotate(90)
px = x
x = y
y = -px
# Align center
alignment = TA_CENTER
x = x - (para_width / 2)
elif align == 'right-vertical':
# Switch axes
c.rotate(-90)
px = x
x = -y
y = px
# Align center
alignment = TA_CENTER
x = x - (para_width / 2)
# set fully opaque background color to avoid transparent text
c.setFillColorRGB(0, 0, 0, 1)
style_n = getSampleStyleSheet()['Normal']
style = ParagraphStyle(
'label',
parent=style_n,
alignment=alignment,
textColor=(red, green, blue),
fontSize=fontsize)
para = Paragraph(text, style)
w, h = para.wrap(para_width, y) # find required space
para.drawOn(c, x, y - h + int(fontsize * 0.25))
# Rotate back again
if align == 'left-vertical':
c.rotate(-90)
elif align == 'right-vertical':
c.rotate(90)
def draw_scalebar_line(self, x, y, x2, y2, width, rgb):
""" Adds line to PDF. Overwritten for TIFF below """
red, green, blue = rgb
red = float(red) / 255
green = float(green) / 255
blue = float(blue) / 255
y = self.page_height - y
y2 = self.page_height - y2
c = self.figure_canvas
c.setLineWidth(width)
c.setStrokeColorRGB(red, green, blue, 1)
c.line(x, y, x2, y2)
def paste_image(self, pil_img, img_name, panel, page, dpi):
""" Adds the PIL image to the PDF figure. Overwritten for TIFFs """
# Apply flip transformations before drawing the image
h_flip = panel.get('horizontal_flip', False)
v_flip = panel.get('vertical_flip', False)
if h_flip:
pil_img = pil_img.transpose(Image.FLIP_LEFT_RIGHT)
if v_flip:
pil_img = pil_img.transpose(Image.FLIP_TOP_BOTTOM)
x = panel['x']
y = panel['y']
width = panel['width']
height = panel['height']
# Handle page offsets
x = x - page['x']
y = y - page['y']
if dpi is not None:
# E.g. target is 300 dpi and width & height is '72 dpi'
# so we need image to be width * dpi/72 pixels
target_w = (width * dpi) / 72
curr_w, curr_h = pil_img.size
dpi_scale = float(target_w) / curr_w
target_h = dpi_scale * curr_h
target_w = int(round(target_w))
target_h = int(round(target_h))
if target_w > curr_w:
if self.export_images:
# Save image BEFORE resampling
rs_name = os.path.join(
self.zip_folder_name, RESAMPLED_DIR, img_name)
pil_img.save(rs_name)
pil_img = pil_img.resize((target_w, target_h), Image.BICUBIC)
# in the folder to zip
if self.zip_folder_name is not None:
img_name = os.path.join(self.zip_folder_name, FINAL_DIR, img_name)
# Save Image to file, then bring into PDF
pil_img.save(img_name)
# Since coordinate system is 'bottom-up', convert from 'top-down'
y = self.page_height - height - y
# set fill color alpha to fully opaque, since this impacts drawImage
self.figure_canvas.setFillColorRGB(0, 0, 0, alpha=1)
self.figure_canvas.drawImage(img_name, x, y, width, height)
class TiffExport(FigureExport):
"""
Subclass to handle export of Figure as TIFFs, 1 per page.
We only need to overwrite methods that actually put content on
the TIFF instead of PDF.
"""
def __init__(self, conn, script_params, export_images=None):
super(TiffExport, self).__init__(conn, script_params, export_images)
from omero.gateway import THISPATH
self.GATEWAYPATH = THISPATH
self.ns = "omero.web.figure.tiff"
self.mimetype = "image/tiff"
def add_rois(self, panel, page):
""" TIFF export doesn't add ROIs to page (does it to panel)"""
pass
def get_font(self, fontsize, bold=False, italics=False):
""" Try to load font from known location in OMERO """
font_name = "FreeSans.ttf"
if bold and italics:
font_name = "FreeSansBoldOblique.ttf"
elif bold:
font_name = "FreeSansBold.ttf"
elif italics:
font_name = "FreeSansOblique.ttf"
path_to_font = os.path.join(self.GATEWAYPATH, "pilfonts", font_name)
try:
font = ImageFont.truetype(path_to_font, fontsize)
except Exception:
font = ImageFont.load(
'%s/pilfonts/B%0.2d.pil' % (self.GATEWAYPATH, 24))
return font
def get_figure_file_ext(self):
return "tiff"
def create_figure(self):
"""
Creates a new PIL image ready to receive panels, labels etc.
This is created for each page in the figure.
"""
tiff_width = int(scale_to_export_dpi(self.page_width))
tiff_height = int(scale_to_export_dpi(self.page_height))
rgb = (255, 255, 255)
page_color = self.figure_json.get('page_color')
if page_color is not None:
rgb = ShapeToPdfExport.get_rgb('#' + page_color)
self.tiff_figure = Image.new("RGBA", (tiff_width, tiff_height), rgb)
def add_page_color(self):
""" Don't need to do anything for TIFF. Image is already colored."""
pass
def paste_image(self, pil_img, img_name, panel, page, dpi=None):
""" Add the PIL image to the current figure page """
# Apply flip transformations before drawing the image
h_flip = panel.get('horizontal_flip', False)
v_flip = panel.get('vertical_flip', False)
if h_flip:
pil_img = pil_img.transpose(Image.FLIP_LEFT_RIGHT)
if v_flip:
pil_img = pil_img.transpose(Image.FLIP_TOP_BOTTOM)
x = panel['x']
y = panel['y']
width = panel['width']
height = panel['height']
# Handle page offsets
x = x - page['x']
y = y - page['y']
x = scale_to_export_dpi(x)
y = scale_to_export_dpi(y)
width = scale_to_export_dpi(width)
height = scale_to_export_dpi(height)
x = int(round(x))
y = int(round(y))
width = int(round(width))
height = int(round(height))
# Save image BEFORE resampling
if self.export_images:
rs_name = os.path.join(self.zip_folder_name, RESAMPLED_DIR,
img_name)
pil_img.save(rs_name)
# Resize to our target size to match DPI of figure
pil_img = pil_img.resize((width, height), Image.BICUBIC)
if self.export_images:
img_name = os.path.join(self.zip_folder_name, FINAL_DIR, img_name)
pil_img.save(img_name)
# Now at full figure resolution - Good time to add shapes...
crop = self.get_crop_region(panel)
exporter = ShapeToPilExport(pil_img, panel, crop)
width, height = pil_img.size
# Add border if needed - Rectangle around the whole panel
if 'border' in panel and panel['border'].get('showBorder'):
sw = panel['border'].get('strokeWidth')
border_width = int(round(scale_to_export_dpi(sw)))
border_color = panel['border'].get('color')
padding = border_width * 2
canvas = Image.new("RGB", (width + padding, height + padding),
exporter.get_rgb(border_color))
canvas.paste(pil_img, (border_width, border_width))
pil_img = canvas
box = (x - border_width,
y - border_width,
x + width + border_width,
y + height + border_width)
else:
box = (x, y, x + width, y + height)
self.tiff_figure.paste(pil_img, box)
def draw_scalebar_line(self, x, y, x2, y2, width, rgb):
""" Draw line on the current figure page """
draw = ImageDraw.Draw(self.tiff_figure)
x = scale_to_export_dpi(x)
y = scale_to_export_dpi(y)
x2 = scale_to_export_dpi(x2)
y2 = scale_to_export_dpi(y2)
width = scale_to_export_dpi(width)
for value in range(-width // 2, width // 2):
draw.line([(x, y + value), (x2, y2 + value)], fill=rgb)
def draw_temp_label(self, text, fontsize, rgb):
"""Returns a new PIL image with text. Handles html."""
tokens = self.parse_html(text)
widths = []
heights = []
for t in tokens:
font = self.get_font(fontsize, t['bold'], t['italics'])
box = font.getbbox(t['text'])
txt_w = box[2] - box[0]
txt_h = box[3] - box[1]
widths.append(txt_w)
heights.append(txt_h)
label_w = sum(widths)
label_h = max(heights)
temp_label = Image.new('RGBA', (label_w, label_h), (255, 255, 255, 0))
textdraw = ImageDraw.Draw(temp_label)
w = 0
for t in tokens:
font = self.get_font(fontsize, t['bold'], t['italics'])
box = font.getbbox(t['text'])
txt_w = box[2] - box[0]
txt_h = box[3] - box[1]
textdraw.text((w, -box[1]), t['text'], font=font, fill=rgb)
w += txt_w
return temp_label
def parse_html(self, html):
"""
Parse html to give list of tokens with bold or italics
Returns list of [{'text': txt, 'bold': true, 'italics': false}]
"""
in_bold = False
in_italics = False
# Remove any
tags html = html.replace('
', '') html = html.replace('
', '') tokens = [] token = "" i = 0 while i < len(html): # look for start / end of b or i elements start_bold = html[i:].startswith("") end_bold = html[i:].startswith("") start_ital = html[i:].startswith("") end_ital = html[i:].startswith("") if start_bold: i += len("") elif end_bold: i += len("") elif start_ital: i += len("") elif end_ital: i += len("") # if style has changed: if start_bold or end_bold or start_ital or end_ital: # save token with previous style tokens.append({'text': token, 'bold': in_bold, 'italics': in_italics}) token = "" if start_bold or end_bold: in_bold = start_bold elif start_ital or end_ital: in_italics = start_ital else: token = token + html[i] i += 1 tokens.append({'text': token, 'bold': in_bold, 'italics': in_italics}) return tokens def draw_text(self, text, x, y, fontsize, rgb, align="center"): """ Add text to the current figure page """ x = scale_to_export_dpi(x) y = scale_to_export_dpi(y) fontsize = scale_to_export_dpi(fontsize) if markdown_imported: # convert markdown to html text = markdown.markdown(text) temp_label = self.draw_temp_label(text, fontsize, rgb) if align == "left-vertical": temp_label = temp_label.rotate(90, expand=True) y = y - (temp_label.size[1] / 2) elif align == "right-vertical": temp_label = temp_label.rotate(-90, expand=True) y = y - (temp_label.size[1] / 2) x = x - temp_label.size[0] elif align == "center": x = x - (temp_label.size[0] / 2) elif align == "right": x = x - temp_label.size[0] x = int(round(x)) y = int(round(y)) # Use label as mask, so transparent part is not pasted self.tiff_figure.paste(temp_label, (x, y), mask=temp_label) def save_page(self, page=None): """ Save the current PIL image page as a TIFF and start a new PIL image for the next page """ self.figure_file_name = self.get_figure_file_name() self.tiff_figure.save(self.figure_file_name) # Create a new blank tiffFigure for subsequent pages self.create_figure() def add_info_page(self, panels_json): """ Since we need a PDF for the info page, we create one first, then call superclass add_info_page """ # We allow TIFF figure export without reportlab (no Info page) if not reportlab_installed: return full_name = "info_page.pdf" if self.zip_folder_name is not None: full_name = os.path.join(self.zip_folder_name, full_name) self.figure_canvas = canvas.Canvas( full_name, pagesize=(self.page_width, self.page_height)) # Superclass method will call add_para_with_thumb(), # to add lines to self.infoLines super(TiffExport, self).add_info_page(panels_json) def save_figure(self): """ Completes PDF figure (or info-page PDF for TIFF export) """ # We allow TIFF figure export without reportlab (no Info page) if not reportlab_installed: return self.figure_canvas.save() class OmeroExport(TiffExport): def __init__(self, conn, script_params): super(OmeroExport, self).__init__(conn, script_params) self.new_image = None def save_page(self, page=None): """ Save the current PIL image page as a new OMERO image and start a new PIL image for the next page """ self.figure_file_name = self.get_figure_file_name(page + 1) # Try to get a Dataset dataset = None for panel in self.figure_json['panels']: parent = self.conn.getObject('Image', panel['imageId']).getParent() if parent is not None and parent.OMERO_CLASS == 'Dataset': if parent.canLink(): dataset = parent break # Need to specify group for new image group_id = self.conn.getEventContext().groupId if dataset is not None: group_id = dataset.getDetails().group.id.val dataset = dataset._obj # get the omero.model.DatasetI self.conn.SERVICE_OPTS.setOmeroGroup(group_id) description = "Created from OMERO.figure: " url = self.script_params.get("Figure_URI") legend = self.figure_json.get('legend') if url is not None: description += url if legend is not None: description = "%s\n\n%s" % (description, legend) img_ids = set() lines = [] for p in self.figure_json['panels']: iid = p['imageId'] if iid in img_ids: continue # ignore images we've already handled img_ids.add(iid) lines.append('- Image:%s %s' % (iid, p['name'])) description += "Contains images:\n%s" % "\n".join(lines) np_array = numpy.asarray(self.tiff_figure) red = np_array[::, ::, 0] green = np_array[::, ::, 1] blue = np_array[::, ::, 2] plane_gen = iter([red, green, blue]) self.new_image = self.conn.createImageFromNumpySeq( plane_gen, self.figure_file_name, sizeC=3, description=description, dataset=dataset) # Reset group context self.conn.SERVICE_OPTS.setOmeroGroup(-1) # Create a new blank tiffFigure for subsequent pages self.create_figure() def create_file_annotation(self, image_ids): """Return result of script.""" # We don't need to create file annotation, but we can return # the new image, which will be returned from the script return self.new_image def export_figure(conn, script_params): # make sure we can find all images conn.SERVICE_OPTS.setOmeroGroup(-1) export_option = script_params['Export_Option'] if export_option == 'PDF': fig_export = FigureExport(conn, script_params) elif export_option == 'PDF_IMAGES': fig_export = FigureExport(conn, script_params, export_images=True) elif export_option == 'TIFF': fig_export = TiffExport(conn, script_params) elif export_option == 'TIFF_IMAGES': fig_export = TiffExport(conn, script_params, export_images=True) elif export_option == 'OMERO': fig_export = OmeroExport(conn, script_params) return fig_export.build_figure() def run_script(): """ The main entry point of the script, as called by the client via the scripting service, passing the required parameters. """ export_options = [rstring('PDF'), rstring('PDF_IMAGES'), rstring('TIFF'), rstring('TIFF_IMAGES'), rstring('OMERO')] client = scripts.client( 'Figure_To_Pdf.py', """Used by web.figure to generate pdf figures from json data""", scripts.String("Figure_JSON", optional=False, description="All figure info as json stringified"), scripts.String("Export_Option", values=export_options, default="PDF"), scripts.String("Webclient_URI", optional=False, grouping="4", description="webclient URL for adding links to images"), scripts.String("Figure_Name", grouping="4", description="Name of the Pdf Figure"), scripts.String("Figure_URI", description="URL to the Figure") ) try: script_params = {} conn = BlitzGateway(client_obj=client) # process the list of args above. for key in client.getInputKeys(): if client.getInput(key): script_params[key] = client.getInput(key, unwrap=True) # call the main script - returns a file annotation wrapper file_annotation = export_figure(conn, script_params) # return this file_annotation to the client. client.setOutput("Message", rstring("Figure created")) if file_annotation is not None: client.setOutput( "New_Figure", robject(file_annotation._obj)) finally: client.closeSession() if __name__ == "__main__": run_script()