1
2
3 """
4 components/tools/OmeroPy/src/omero/util/imageUitl.py
5
6 -----------------------------------------------------------------------------
7 Copyright (C) 2006-2009 University of Dundee. All rights reserved.
8
9
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License along
20 with this program; if not, write to the Free Software Foundation, Inc.,
21 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
23 ------------------------------------------------------------------------------
24
25 A collection of utility methods based on the Python Imaging Library (PIL)
26 used for making figures.
27
28 @author William Moore
29 <a href="mailto:will@lifesci.dundee.ac.uk">will@lifesci.dundee.ac.uk</a>
30 @author Jean-Marie Burel
31 <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
32 @author Donald MacDonald
33 <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
34 @version 3.0
35 <small>
36 (<b>Internal version:</b> $Revision: $Date: $)
37 </small>
38 @since 3.0-Beta4.1
39
40 """
41
42
43 try:
44 from PIL import Image, ImageDraw, ImageFont
45 except ImportError:
46 import Image, ImageDraw, ImageFont
47
48 import os.path
49 import omero.gateway
50 import StringIO
51 from omero.rtypes import *
52
53 GATEWAYPATH = omero.gateway.THISPATH
54
56 """
57 Returns a PIL ImageFont Sans-serif true-type font of the specified size
58 or a pre-compiled font of fixed size if the ttf font is not found
59
60 @param fontsize: The size of the font you want
61 @return: A PIL Font
62 """
63
64 fontPath = os.path.join(GATEWAYPATH, "pilfonts", "FreeSans.ttf")
65 try:
66 font = ImageFont.truetype ( fontPath, fontsize )
67 except:
68 font = ImageFont.load('%s/pilfonts/B%0.2d.pil' % (GATEWAYPATH, 24) )
69 return font
70
71
73 """
74 Pastes the image onto the canvas at the specified coordinates
75 Image and canvas are instances of PIL 'Image'
76
77 @param image: The PIL image to be pasted. Image
78 @param canvas: The PIL image on which to paste. Image
79 @param x: X coordinate (left) to paste
80 @param y: Y coordinate (top) to paste
81 """
82
83 xRight = image.size[0] + x
84 yBottom = image.size[1] + y
85
86 pasteBox = (x, y, xRight, yBottom)
87 canvas.paste(image, pasteBox)
88
89
91 """
92 Returns a thumbnail (as string) from the pixelsId, the longest side is 'length'
93
94 @param thumbnailStore: The Omero thumbnail store
95 @param pixelsId: The ID of the pixels. long
96 @param length: Length of longest side. int
97 @return: The thumbnail as a String, or None if not found (invalid image)
98 """
99 if not thumbnailStore.setPixelsId(pixelsId):
100 thumbnailStore.needDefaults()
101 thumbnailStore.setPixelsId(pixelsId)
102 try:
103 return thumbnailStore.getThumbnailByLongestSide(rint(length))
104 except:
105 return None
106
108 """
109 Returns map of thumbnails whose keys are the pixels id and the values are the image, the longest side is 'length'
110
111 @param thumbnailStore: The Omero thumbnail store
112 @param pixelIds: The collection of pixels ID.
113 @param length: Length of longest side. int
114 @return: See above
115 """
116 try:
117 return thumbnailStore.getThumbnailByLongestSideSet(rint(length), pixelIds)
118 except:
119 return None
120
121 -def paintThumbnailGrid(thumbnailStore, length, spacing, pixelIds, colCount, bg=(255,255,255),
122 leftLabel=None, textColour=(0,0,0), fontsize=None, topLabel=None):
123 """
124 Retrieves thumbnails for each pixelId, and places them in a grid, with White background.
125 Option to add a vertical label to the left of the canvas
126 Creates a PIL 'Image' which is returned
127
128 @param thumbnailStore: The omero thumbnail store.
129 @param length: Length of longest thumbnail side. int
130 @param spacing: The spacing between thumbnails and around the edges. int
131 @param pixelIds: List of pixel IDs. [long]
132 @param colCount: The number of columns. int
133 @param bg: Background colour as (r,g,b). Default is white (255,255,255)
134 @param leftLabel: Optional string to display vertically to the left.
135 @param textColour: The colour of the text as (r,g,b). Default is black (0,0,0)
136 @param fontsize: Size of the font. Defualt is calculated based on thumbnail length. int
137 @return: The PIL Image canvas.
138 """
139 mode = "RGB"
140
141 imgCount = len(pixelIds)
142
143 rowCount = (imgCount/colCount)
144 while (colCount * rowCount) < imgCount:
145 rowCount += 1
146
147 leftSpace = topSpace = spacing
148 minWidth = 0
149
150 textHeight = 0
151 if leftLabel or topLabel:
152
153 if leftLabel is not None and rowCount == 0: rowCount = 1
154 if fontsize == None:
155 fontsize = length/10 + 5
156 font = getFont(fontsize)
157 if leftLabel:
158 textWidth, textHeight = font.getsize(leftLabel)
159 leftSpace = spacing + textHeight + spacing
160 if topLabel:
161 textWidth, textHeight = font.getsize(topLabel)
162 topSpace = spacing + textHeight + spacing
163 minWidth = leftSpace + textWidth + spacing
164
165
166 colsNeeded = min(colCount, imgCount)
167 canvasWidth = max(minWidth, (leftSpace + colsNeeded * (length+spacing)))
168 canvasHeight = topSpace + rowCount * (length+spacing) + spacing
169 mode = "RGB"
170 size = (canvasWidth, canvasHeight)
171 canvas = Image.new(mode, size, bg)
172
173
174 if leftLabel:
175 labelCanvasWidth = canvasHeight
176 labelCanvasHeight = textHeight + spacing
177 labelSize = (labelCanvasWidth, labelCanvasHeight)
178 textCanvas = Image.new(mode, labelSize, bg)
179 draw = ImageDraw.Draw(textCanvas)
180 textWidth = font.getsize(leftLabel)[0]
181 textX = (labelCanvasWidth - textWidth) / 2
182 draw.text((textX, spacing), leftLabel, font=font, fill=textColour)
183 verticalCanvas = textCanvas.rotate(90)
184 pasteImage(verticalCanvas, canvas, 0, 0)
185 del draw
186
187 if topLabel is not None:
188 labelCanvasWidth = canvasWidth
189 labelCanvasHeight = textHeight + spacing
190 labelSize = (labelCanvasWidth, labelCanvasHeight)
191 textCanvas = Image.new(mode, labelSize, bg)
192 draw = ImageDraw.Draw(textCanvas)
193 draw.text((spacing, spacing), topLabel, font=font, fill=textColour)
194 pasteImage(textCanvas, canvas, leftSpace, 0)
195 del draw
196
197
198 r = 0
199 c = 0
200 thumbnailMap = getThumbnailSet(thumbnailStore, length, pixelIds)
201 for pixelsId in pixelIds:
202 if pixelsId in thumbnailMap:
203 thumbnail = thumbnailMap[pixelsId]
204 if thumbnail:
205 thumbImage = Image.open(StringIO.StringIO(thumbnail))
206
207 x = c*(length+spacing) + leftSpace
208 y = r*(length+spacing) + topSpace
209 pasteImage(thumbImage, canvas, x, y)
210
211
212 c = c + 1
213 if c == colCount:
214 c = 0
215 r = r + 1
216
217 return canvas
218
219
221 """
222 Checks that the value is between 0 and 255. Returns integer value
223 If the value is not valid, return 255 (better to see something than nothing! )
224
225 @param value: The value to check.
226 @return: An integer between 0 and 255
227 """
228 try:
229 v = int(value)
230 if 0 <= v <= 255:
231 return v
232 except:
233 return 255
234
235
237 """
238 Returns a tuple of (r,g,b,a) from an integer colour
239 r, g, b, a are 0-255.
240
241 @param RGB: A colour as integer. Int
242 @return: A tuple of (r,g,b,a)
243 """
244 r = checkRGBRange((RGB >> 16) & 0xFF)
245 g = checkRGBRange((RGB >> 8) & 0xFF)
246 b = checkRGBRange((RGB >> 0) & 0xFF)
247 a = checkRGBRange((RGB >> 24) & 0xFF)
248 if a == 0:
249 a = 255
250 return (r,g,b,a)
251
252
254 """
255 Returns a tuple of (r,g,b) from an integer colour
256 r, g, b are 0-255.
257
258 @param RGB: A colour as integer. Int
259 @return: A tuple of (r,g,b)
260 """
261 r,g,b,a = RGBIntToRGBA(RGB)
262 return (r,g,b)
263
264
266 """
267 Returns the factor by which the Image has to be shrunk so that it's dimensions are less that maxW and maxH
268 E.g. If the image must be half-sized, this method returns 2.0 (float)
269
270 @param imageSize: Size of the image as tuple (width, height)
271 @param maxW: The max width after zooming
272 @param maxH: The max height after zooming
273 @return: The factor by which to shrink the image to be within max width and height
274 """
275 imageW, imageH = imageSize
276 zoomW = float(imageW) / float(maxW)
277 zoomH = float(imageH) / float(maxH)
278 return max(zoomW, zoomH)
279
280
282 """
283 Resize the image so that it is as big as possible, within the dimensions maxW, maxH
284
285 @param image: The PIL Image to zoom
286 @param maxW: The max width of the zoomed image
287 @param maxH: The max height of the zoomed image
288 @return: The zoomed image. PIL Image.
289 """
290 imageW, imageH = image.size
291 if imageW == maxW and imageH == maxH:
292 return image
293
294 zoomW = float(imageW) / float(maxW)
295 zoomH = float(imageH) / float(maxH)
296 zoom = max(zoomW, zoomH)
297 if zoomW >= zoomH:
298 maxH = int(imageH//zoom)
299 else:
300 maxW = int(imageW//zoom)
301 return image.resize((maxW, maxH))
302