1
2
3 from django.http import HttpResponseRedirect, HttpResponse
4 from django.core.urlresolvers import reverse
5 from django.shortcuts import render_to_response
6 from omeroweb.webgateway import views as webgateway_views
7 from omeroweb.connector import Server
8
9 from omeroweb.webclient.decorators import login_required, render_response
10 from omeroweb.connector import Connector
11
12 from cStringIO import StringIO
13
14 import settings
15 import logging
16 import traceback
17 import omero
18 from omero.rtypes import rint, rstring
19 import omero.gateway
20 import random
21
22
23 logger = logging.getLogger(__name__)
24
25
26 try:
27 from PIL import Image
28 except:
29 try:
30 import Image
31 except:
32 logger.error('No PIL installed, line plots and split channel will fail!')
33
34
35 @login_required()
36 -def dataset(request, datasetId, conn=None, **kwargs):
37 """ 'Hello World' example from tutorial on http://trac.openmicroscopy.org.uk/ome/wiki/OmeroWeb """
38 ds = conn.getObject("Dataset", datasetId)
39 return render_to_response('webtest/dataset.html', {'dataset': ds})
40
41
42 @login_required()
43 -def index(request, conn=None, **kwargs):
66
70 """
71 Viewer for overlaying separate channels from the same image or different images
72 and adjusting horizontal and vertical alignment of each
73 """
74 image = conn.getObject("Image", imageId)
75 default_z = image.getSizeZ()/2
76
77
78 red = None
79 green = None
80 blue = None
81 notAssigned = []
82 channels = []
83 for i, c in enumerate(image.getChannels()):
84 channels.append( {'name':c.getName()} )
85 if c.getColor().getRGB() == (255, 0, 0) and red == None:
86 red = i
87 elif c.getColor().getRGB() == (0, 255, 0) and green == None:
88 green = i
89 elif c.getColor().getRGB() == (0, 0, 255) and blue == None:
90 blue = i
91 else:
92 notAssigned.append(i)
93
94 for i in notAssigned:
95 if red == None: red = i
96 elif green == None: green = i
97 elif blue == None: blue = i
98
99
100
101 ns = "omero.web.channel_overlay.offsets"
102 comment = image.getAnnotation(ns)
103 if comment == None:
104 for ann in image.listAnnotations():
105 if isinstance(ann, omero.gateway.CommentAnnotationWrapper):
106 if ann.getValue().startswith("0|z:"):
107 comment = ann
108 break
109 if comment != None:
110 offsets = comment.getValue()
111 for o in offsets.split(","):
112 index,zxy = o.split("|",1)
113 if int(index) < len(channels):
114 keyVals = zxy.split("_")
115 for kv in keyVals:
116 key, val = kv.split(":")
117 if key == "z": val = int(val) + default_z
118 channels[int(index)][key] = int(val)
119
120 return render_to_response('webtest/demo_viewers/channel_overlay_viewer.html', {
121 'image': image, 'channels':channels, 'default_z':default_z, 'red': red, 'green': green, 'blue': blue})
122
126 """
127 Overlays separate channels (red, green, blue) from the same image or different images
128 manipulating each indepdently (translate, scale, rotate etc? )
129 """
130
131
132
133 planes = {}
134 p = request.REQUEST.get('planes', None)
135 if p is None:
136 return HttpResponse("Request needs plane info to render jpeg. E.g. ?planes=0|2305:7:0:0$x:-50_y:10,1|2305:7:1:0,2|2305:7:2:0&red=2&blue=0&green=1")
137 for plane in p.split(','):
138 infoMap = {}
139 plane_info = plane.split('|')
140 key = plane_info[0].strip()
141 info = plane_info[1].strip()
142 shift = None
143 if info.find('$')>=0:
144 info,shift = info.split('$')
145 imageId,z,c,t = [int(i) for i in info.split(':')]
146 infoMap['imageId'] = imageId
147 infoMap['z'] = z
148 infoMap['c'] = c
149 infoMap['t'] = t
150 if shift != None:
151 for kv in shift.split("_"):
152 k, v = kv.split(":")
153 infoMap[k] = v
154 planes[key] = infoMap
155
156
157
158 red = request.REQUEST.get('red', None)
159 green = request.REQUEST.get('green', None)
160 blue = request.REQUEST.get('blue', None)
161
162
163
164 redImg = None
165
166 def translate(image, deltaX, deltaY):
167
168 xsize, ysize = image.size
169 mode = image.mode
170 bg = Image.new(mode, image.size)
171 x = abs(min(deltaX, 0))
172 pasteX = max(0, deltaX)
173 y = abs(min(deltaY, 0))
174 pasteY = max(0, deltaY)
175
176 part = image.crop((x, y, xsize-deltaX, ysize-deltaY))
177 bg.paste(part, (pasteX, pasteY))
178 return bg
179
180 def getPlane(planeInfo):
181 """ Returns the rendered plane split into a single channel (ready for merging) """
182 img = conn.getObject("Image", planeInfo['imageId'])
183 img.setActiveChannels((planeInfo['c']+1,))
184 img.setGreyscaleRenderingModel()
185 rgb = img.renderImage(planeInfo['z'], planeInfo['t'])
186
187
188 rgb.save(StringIO(), 'jpeg', quality=90)
189
190 r,g,b = rgb.split()
191
192 x,y = 0,0
193 if 'x' in planeInfo:
194 x = int(planeInfo['x'])
195 if 'y' in planeInfo:
196 y = int(planeInfo['y'])
197
198 if x or y:
199 r = translate(r, x, y)
200 return r
201
202 redChannel = None
203 greenChannel = None
204 blueChannel = None
205 if red != None and red in planes:
206 redChannel = getPlane(planes[red])
207 if green != None and green in planes:
208 greenChannel = getPlane(planes[green])
209 if blue != None and blue in planes:
210 blueChannel = getPlane(planes[blue])
211
212 if redChannel != None:
213 size = redChannel.size
214 elif greenChannel != None:
215 size = greenChannel.size
216 elif blueChannel != None:
217 size = blueChannel.size
218
219 black = Image.new('L', size)
220 redChannel = redChannel and redChannel or black
221 greenChannel = greenChannel and greenChannel or black
222 blueChannel = blueChannel and blueChannel or black
223
224 merge = Image.merge("RGB", (redChannel, greenChannel, blueChannel))
225
226 rv = StringIO()
227 compression = 0.9
228 merge.save(rv, 'jpeg', quality=int(compression*100))
229 jpeg_data = rv.getvalue()
230
231 rsp = HttpResponse(jpeg_data, mimetype='image/jpeg')
232 return rsp
233
234
235 @login_required()
236 -def add_annotations (request, conn=None, **kwargs):
237 """
238 Creates a L{omero.gateway.CommentAnnotationWrapper} and adds it to the images according
239 to variables in the http request.
240
241 @param request: The django L{django.core.handlers.wsgi.WSGIRequest}
242 - imageIds: A comma-delimited list of image IDs
243 - comment: The text to add as a comment to the images
244 - ns: Namespace for the annotation
245 - replace: If "true", try to replace existing annotation with same ns
246
247 @return: A simple html page with a success message
248 """
249 idList = request.REQUEST.get('imageIds', None)
250 if idList:
251 imageIds = [long(i) for i in idList.split(",")]
252 else: imageIds = []
253
254 comment = request.REQUEST.get('comment', None)
255 ns = request.REQUEST.get('ns', None)
256 replace = request.REQUEST.get('replace', False) in ('true', 'True')
257
258 updateService = conn.getUpdateService()
259 ann = omero.model.CommentAnnotationI()
260 ann.setTextValue(rstring( str(comment) ))
261 if ns != None:
262 ann.setNs(rstring( str(ns) ))
263 ann = updateService.saveAndReturnObject(ann)
264 annId = ann.getId().getValue()
265
266 images = []
267 for iId in imageIds:
268 image = conn.getObject("Image", iId)
269 if image == None: continue
270 if replace and ns != None:
271 oldComment = image.getAnnotation(ns)
272 if oldComment != None:
273 oldComment.setTextValue(rstring( str(comment) ))
274 updateService.saveObject(oldComment)
275 continue
276 l = omero.model.ImageAnnotationLinkI()
277 parent = omero.model.ImageI(iId, False)
278 l.setParent(parent)
279 l.setChild(ann)
280 updateService.saveObject(l)
281 images.append(image)
282
283 return render_to_response('webtest/util/add_annotations.html', {'images':images, 'comment':comment})
284
350
351 channels = None
352 images = []
353 for iId in imageIds:
354 image = conn.getObject("Image", iId)
355 if image == None: continue
356 default_z = image.getSizeZ()/2
357
358 images.append({"id":iId, "z":default_z, "name": image.getName() })
359 if channels is None:
360 channels = getChannelData(image)
361 if height == 0:
362 height = image.getSizeY()
363 if width == 0:
364 width = image.getSizeX()
365
366 if channels is None:
367 return HttpResponse("Couldn't load channels for this image")
368 size = {"height": height, "width": width}
369 c_strs = []
370 if channels:
371 indexes = range(1, len(channels)+1)
372 c_string = ",".join(["-%s" % str(c) for c in indexes])
373 mergedFlags = []
374 for i, c, in enumerate(channels):
375 if c["render_all"]:
376 levels = "%s:%s" % (c["start"], c["end"])
377 else: levels = ""
378 if c["active"]:
379 onFlag = str(i+1) + "|"
380 onFlag += levels
381 if split_grey: onFlag += "$FFFFFF"
382 c_strs.append( c_string.replace("-%s" % str(i+1), onFlag) )
383 if c["merged"]:
384 mergedFlags.append("%s|%s" % (i+1, levels))
385 else: mergedFlags.append("-%s" % (i+1))
386
387 c_strs.append( ",".join(mergedFlags) )
388
389 template = kwargs.get('template', 'webtest/demo_viewers/split_view_figure.html')
390 return render_to_response(template, {'images':images, 'c_strs': c_strs,'imageIds':idList,
391 'channels': channels, 'split_grey':split_grey, 'merged_names': merged_names, 'proj': proj, 'size': size, 'query_string':query_string})
392
393
394 @login_required()
395 -def dataset_split_view (request, datasetId, conn=None, **kwargs):
396 """
397 Generates a web page that displays a dataset in two panels, with the option to choose different
398 rendering settings (channels on/off) for each panel. It uses the render_image url for each
399 image, generating the full sized image which is scaled down to view.
400
401 The page also includes a form for editing the channel settings and display size of images.
402 This form resubmits to this page and displays the page again with updated parameters.
403
404 @param request: The django L{http request <django.core.handlers.wsgi.WSGIRequest>}
405 @param datasetId: The ID of the dataset.
406 @type datasetId: Number.
407
408 @return: The http response - html page displaying split view figure.
409 """
410 dataset = conn.getObject("Dataset", datasetId)
411
412 try:
413 size = request.REQUEST.get('size', 100)
414 size = int(size)
415 except:
416 size = 100
417
418
419 def getChannelData(image):
420 channels = []
421 i = 0;
422 chs = image.getChannels()
423 if chs is None:
424 return []
425 for i, c in enumerate(chs):
426 if c is None:
427 continue
428 name = c.getLogicalChannel().getName()
429
430 if request.REQUEST.get('cStart%s' % i, None):
431 active_left = (None != request.REQUEST.get('cActiveLeft%s' % i, None) )
432 active_right = (None != request.REQUEST.get('cActiveRight%s' % i, None) )
433 else:
434 active_left = True
435 active_right = True
436 colour = c.getColor()
437 if colour is None:
438 continue
439 colour = colour.getHtml();
440 start = request.REQUEST.get('cStart%s' % i, c.getWindowStart())
441 end = request.REQUEST.get('cEnd%s' % i, c.getWindowEnd())
442 render_all = (None != request.REQUEST.get('cRenderAll%s' % i, None) )
443 channels.append({"name": name, "index": i, "active_left": active_left, "active_right": active_right,
444 "colour": colour, "start": start, "end": end, "render_all": render_all})
445 return channels
446
447 images = []
448 channels = None
449
450 for image in dataset.listChildren():
451 if channels == None or len(channels) == 0:
452 channels = getChannelData(image)
453 default_z = image.getSizeZ()/2
454
455 images.append({"id":image.getId(), "z":default_z, "name": image.getName() })
456
457 if channels is None:
458 return HttpResponse("<p class='center_message'>No Images in Dataset<p>")
459
460 indexes = range(1, len(channels)+1)
461 c_string = ",".join(["-%s" % str(c) for c in indexes])
462
463 leftFlags = []
464 rightFlags = []
465 for i, c, in enumerate(channels):
466 if c["render_all"]:
467 levels = "%s:%s" % (c["start"], c["end"])
468 else: levels = ""
469 if c["active_left"]:
470 leftFlags.append("%s|%s" % (i+1, levels))
471 else: leftFlags.append("-%s" % (i+1))
472 if c["active_right"]:
473 rightFlags.append("%s|%s" % (i+1, levels))
474 else: rightFlags.append("-%s" % (i+1))
475
476 c_left = ",".join(leftFlags)
477 c_right = ",".join(rightFlags)
478
479 template = kwargs.get('template', 'webtest/webclient_plugins/dataset_split_view.html')
480
481 return render_to_response(template, {'dataset': dataset, 'images': images,
482 'channels':channels, 'size': size, 'c_left': c_left, 'c_right': c_right})
483
484
485 @login_required()
486 -def image_dimensions (request, imageId, conn=None, **kwargs):
487 """
488 Prepare data to display various dimensions of a multi-dim image as axes of a grid of image planes.
489 E.g. x-axis = Time, y-axis = Channel.
490 """
491 image = conn.getObject("Image", imageId)
492 if image is None:
493 return render_to_response('webtest/demo_viewers/image_dimensions.html', {})
494
495 mode = request.REQUEST.get('mode', None) and 'g' or 'c'
496 dims = {'Z':image.getSizeZ(), 'C': image.getSizeC(), 'T': image.getSizeT()}
497
498 default_yDim = 'Z'
499
500 xDim = request.REQUEST.get('xDim', 'C')
501 if xDim not in dims.keys():
502 xDim = 'C'
503
504 yDim = request.REQUEST.get('yDim', default_yDim)
505 if yDim not in dims.keys():
506 yDim = 'Z'
507
508 xFrames = int(request.REQUEST.get('xFrames', 5))
509 xSize = dims[xDim]
510 yFrames = int(request.REQUEST.get('yFrames', 10))
511 ySize = dims[yDim]
512
513 xFrames = min(xFrames, xSize)
514 yFrames = min(yFrames, ySize)
515
516 xRange = range(xFrames)
517 yRange = range(yFrames)
518
519
520 grid = []
521 for y in yRange:
522 grid.append([])
523 for x in xRange:
524 iid, theZ, theC, theT = image.id, 0,None,0
525 if xDim == 'Z':
526 theZ = x
527 if xDim == 'C':
528 theC = x
529 if xDim == 'T':
530 theT = x
531 if yDim == 'Z':
532 theZ = y
533 if yDim == 'C':
534 theC = y
535 if yDim == 'T':
536 theT = y
537
538 grid[y].append( (iid, theZ, theC is not None and theC+1 or None, theT) )
539
540
541 size = {"height": 125, "width": 125}
542
543 return render_to_response('webtest/demo_viewers/image_dimensions.html', {'image':image, 'grid': grid,
544 "size": size, "mode":mode, 'xDim':xDim, 'xRange':xRange, 'yRange':yRange, 'yDim':yDim,
545 'xFrames':xFrames, 'yFrames':yFrames})
546
547
548 @login_required()
549 -def image_rois (request, imageId, conn=None, **kwargs):
550 """ Simply shows a page of ROI thumbnails for the specified image """
551 roiService = conn.getRoiService()
552 result = roiService.findByImage(long(imageId), None, conn.SERVICE_OPTS)
553 roiIds = [r.getId().getValue() for r in result.rois]
554 return render_to_response('webtest/demo_viewers/image_rois.html', {'roiIds':roiIds})
555
558 """ Simply return the named template. Similar functionality to django.views.generic.simple.direct_to_template """
559 template_name = 'webtest/webgateway/%s.html' % base_template
560 return render_to_response(template_name, {})
561
562 @login_required()
563 @render_response()
564 -def webclient_templates (request, base_template, **kwargs):
565 """ Simply return the named template. Similar functionality to django.views.generic.simple.direct_to_template """
566 template_name = 'webtest/webgateway/%s.html' % base_template
567 return {'template': template_name}
568
569
570 @login_required()
571 -def image_viewer (request, iid=None, conn=None, **kwargs):
572 """ This view is responsible for showing pixel data as images. Delegates to webgateway, using share connection if appropriate """
573
574 if iid is None:
575 iid = request.REQUEST.get('image')
576
577 template = 'webtest/webclient_plugins/center_plugin.fullviewer.html'
578
579 return webgateway_views.full_viewer(request, iid, _conn=conn, template=template, **kwargs)
580
581 @login_required()
582 -def stack_preview (request, imageId, conn=None, **kwargs):
583 """ Shows a subset of Z-planes for an image """
584 image = conn.getObject("Image", imageId)
585 image_name = image.getName()
586 sizeZ = image.getSizeZ()
587 z_indexes = [0, int(sizeZ*0.25), int(sizeZ*0.5), int(sizeZ*0.75), sizeZ-1]
588 return render_to_response('webtest/stack_preview.html', {'imageId':imageId, 'image_name':image_name, 'z_indexes':z_indexes})
589
633