" + 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 """ 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 """ 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) ShapeToPilExport(pil_img, panel, crop) width, height = pil_img.size 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()