Package omeroweb :: Package webclient :: Module views
[hide private]
[frames] | no frames]

Source Code for Module omeroweb.webclient.views

   1  #!/usr/bin/env python 
   2  # -*- coding: utf-8 -*- 
   3  #  
   4  #  
   5  #  
   6  # Copyright (c) 2008-2011 University of Dundee. 
   7  #  
   8  # This program is free software: you can redistribute it and/or modify 
   9  # it under the terms of the GNU Affero General Public License as 
  10  # published by the Free Software Foundation, either version 3 of the 
  11  # License, or (at your option) any later version. 
  12  #  
  13  # This program is distributed in the hope that it will be useful, 
  14  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  16  # GNU Affero General Public License for more details. 
  17  #  
  18  # You should have received a copy of the GNU Affero General Public License 
  19  # along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  20  #  
  21  # Author: Aleksandra Tarkowska <A(dot)Tarkowska(at)dundee(dot)ac(dot)uk>, 2008. 
  22  #  
  23  # Version: 1.0 
  24  # 
  25   
  26  ''' A view functions is simply a Python function that takes a Web request and  
  27  returns a Web response. This response can be the HTML contents of a Web page,  
  28  or a redirect, or the 404 and 500 error, or an XML document, or an image...  
  29  or anything.''' 
  30   
  31  import sys 
  32  import copy 
  33  import re 
  34  import os 
  35  import calendar 
  36  import cStringIO 
  37  import datetime 
  38  import httplib 
  39  import Ice 
  40  import locale 
  41  import logging 
  42  import traceback 
  43   
  44  import shutil 
  45  import zipfile 
  46   
  47  from time import time 
  48  from thread import start_new_thread 
  49   
  50  from omero_version import omero_version 
  51  import omero, omero.scripts  
  52  from omero.rtypes import * 
  53   
  54  from django.conf import settings 
  55  from django.contrib.sessions.backends.cache import SessionStore 
  56  from django.core import template_loader 
  57  from django.core.cache import cache 
  58  from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseServerError, HttpResponseForbidden 
  59  from django.shortcuts import render_to_response 
  60  from django.template import RequestContext as Context 
  61  from django.utils import simplejson 
  62  from django.utils.http import urlencode 
  63  from django.views.defaults import page_not_found, server_error 
  64  from django.views import debug 
  65  from django.core.urlresolvers import reverse 
  66  from django.utils.translation import ugettext_lazy as _ 
  67  from django.utils.encoding import smart_str 
  68  from django.core.servers.basehttp import FileWrapper 
  69   
  70  from webclient.webclient_gateway import OmeroWebGateway 
  71   
  72  from webclient_http import HttpJavascriptRedirect, HttpJavascriptResponse, HttpLoginRedirect 
  73   
  74  from webclient_utils import _formatReport, _purgeCallback 
  75  from forms import ShareForm, BasketShareForm, \ 
  76                      ContainerForm, ContainerNameForm, ContainerDescriptionForm, \ 
  77                      CommentAnnotationForm, TagsAnnotationForm, \ 
  78                      UsersForm, ActiveGroupForm, \ 
  79                      MetadataFilterForm, MetadataDetectorForm, MetadataChannelForm, \ 
  80                      MetadataEnvironmentForm, MetadataObjectiveForm, MetadataObjectiveSettingsForm, MetadataStageLabelForm, \ 
  81                      MetadataLightSourceForm, MetadataDichroicForm, MetadataMicroscopeForm, \ 
  82                      FilesAnnotationForm, WellIndexForm 
  83   
  84  from controller import BaseController 
  85  from controller.index import BaseIndex 
  86  from controller.basket import BaseBasket 
  87  from controller.container import BaseContainer 
  88  from controller.help import BaseHelp 
  89  from controller.history import BaseCalendar 
  90  from controller.impexp import BaseImpexp 
  91  from controller.search import BaseSearch 
  92  from controller.share import BaseShare 
  93   
  94  from omeroweb.connector import Server 
  95   
  96  from omeroweb.webadmin.forms import LoginForm 
  97  from omeroweb.webadmin.webadmin_utils import toBoolean, upgradeCheck 
  98   
  99  from omeroweb.webgateway import views as webgateway_views 
 100   
 101  from omeroweb.feedback.views import handlerInternalError 
 102   
 103  from omeroweb.webclient.decorators import login_required 
 104  from omeroweb.webclient.decorators import render_response 
 105  from omeroweb.connector import Connector 
 106  from omeroweb.decorators import ConnCleaningHttpResponse 
 107   
 108  logger = logging.getLogger(__name__) 
 109   
 110  logger.info("INIT '%s'" % os.getpid()) 
111 112 # helper method 113 -def getIntOrDefault(request, name, default):
114 try: 115 index = int(request.REQUEST.get(name, default)) 116 except ValueError: 117 index = 0 118 return index
119
120 ################################################################################ 121 # views controll 122 123 -def login(request):
124 """ 125 Webclient Login - Also can be used by other Apps to log in to OMERO. 126 Uses the 'server' id from request to lookup the server-id (index), host and port from settings. E.g. "localhost", 4064. 127 Stores these details, along with username, password etc in the request.session. 128 Resets other data parameters in the request.session. 129 Tries to get connection to OMERO and if this works, then we are redirected to the 'index' page or url specified in REQUEST. 130 If we can't connect, the login page is returned with appropriate error messages. 131 """ 132 133 request.session.modified = True 134 135 conn = None 136 error = None 137 138 server_id = request.REQUEST.get('server') 139 form = LoginForm(data=request.REQUEST.copy()) 140 useragent = 'OMERO.web' 141 if form.is_valid(): 142 username = form.cleaned_data['username'] 143 password = form.cleaned_data['password'] 144 server_id = form.cleaned_data['server'] 145 is_secure = toBoolean(form.cleaned_data['ssl']) 146 147 connector = Connector(server_id, is_secure) 148 149 # TODO: version check should be done on the low level, see #5983 150 if server_id is not None and username is not None and password is not None \ 151 and connector.check_version(useragent): 152 conn = connector.create_connection(useragent, username, password) 153 if conn is not None: 154 # Check if user is in "user" group 155 userGroupId = conn.getAdminService().getSecurityRoles().userGroupId 156 if userGroupId in conn.getEventContext().memberOfGroups: 157 request.session['connector'] = connector 158 upgradeCheck() 159 160 # if 'active_group' remains in session from previous login, check it's valid for this user 161 if request.session.get('active_group'): 162 if request.session.get('active_group') not in conn.getEventContext().memberOfGroups: 163 del request.session['active_group'] 164 if request.session.get('user_id'): # always want to revert to logged-in user 165 del request.session['user_id'] 166 # do we ned to display server version ? 167 # server_version = conn.getServerVersion() 168 if request.REQUEST.get('noredirect'): 169 return HttpResponse('OK') 170 url = request.REQUEST.get("url") 171 if url is not None and len(url) != 0: 172 return HttpResponseRedirect(url) 173 else: 174 return HttpResponseRedirect(reverse("webindex")) 175 elif username == "guest": 176 error = "Guest account is for internal OMERO use only. Not for login." 177 else: 178 error = "This user is not active." 179 180 181 if request.method == 'POST' and server_id is not None: 182 connector = Connector(server_id, True) 183 if not connector.is_server_up(useragent): 184 error = "Server is not responding, please contact administrator." 185 elif not connector.check_version(useragent): 186 error = "Client version does not match server, please contact administrator." 187 else: 188 error = "Connection not available, please check your user name and password." 189 url = request.REQUEST.get("url") 190 191 template = "webclient/login.html" 192 if request.method != 'POST': 193 if server_id is not None: 194 initial = {'server': unicode(server_id)} 195 form = LoginForm(initial=initial) 196 else: 197 form = LoginForm() 198 199 context = {"version": omero_version, 'error':error, 'form':form} 200 if url is not None and len(url) != 0: 201 context['url'] = urlencode({'url':url}) 202 203 t = template_loader.get_template(template) 204 c = Context(request, context) 205 rsp = t.render(c) 206 return HttpResponse(rsp)
207
208 @login_required(ignore_login_fail=True) 209 -def keepalive_ping(request, conn=None, **kwargs):
210 """ Keeps the OMERO session alive by pinging the server """ 211 212 # login_required handles ping, timeout etc, so we don't need to do anything else 213 return HttpResponse("OK")
214
215 @login_required() 216 @render_response() 217 -def feed(request, conn=None, **kwargs):
218 """ 219 Viewing this page doesn't perform any action. All we do here is assemble various data for display. 220 Last imports, tag cloud etc are retrived via separate AJAX calls. 221 """ 222 template = "webclient/index/index.html" 223 224 controller = BaseIndex(conn) 225 226 context = {'controller':controller} 227 context['template'] = template 228 return context
229
230 231 @login_required() 232 @render_response() 233 -def index_last_imports(request, conn=None, **kwargs):
234 """ 235 Gets the most recent imports - Used in an AJAX call by home page. 236 """ 237 238 controller = BaseIndex(conn) 239 controller.loadLastAcquisitions() 240 241 context = {'controller':controller} 242 context['template'] = "webclient/index/index_last_imports.html" 243 return context
244
245 @login_required() 246 @render_response() 247 -def index_most_recent(request, conn=None, **kwargs):
248 """ Gets the most recent 'shares' and 'share' comments. Used by the homepage via AJAX call """ 249 250 controller = BaseIndex(conn) 251 controller.loadMostRecent() 252 253 context = {'controller':controller} 254 context['template'] = "webclient/index/index_most_recent.html" 255 return context
256
257 @login_required() 258 @render_response() 259 -def index_tag_cloud(request, conn=None, **kwargs):
260 """ Gets the most used Tags. Used by the homepage via AJAX call """ 261 262 controller = BaseIndex(conn) 263 controller.loadTagCloud() 264 265 context = {'controller':controller } 266 context['template'] = "webclient/index/index_tag_cloud.html" 267 return context
268
269 @login_required() 270 -def change_active_group(request, conn=None, url=None, **kwargs):
271 """ 272 Simply changes the request.session['active_group'] which is then used by the 273 @login_required decorator to configure conn for any group-based queries. 274 Finally this redirects to the 'url'. 275 """ 276 switch_active_group(request) 277 url = url or reverse("webindex") 278 return HttpResponseRedirect(url)
279
280 -def switch_active_group(request, active_group=None):
281 """ 282 Simply changes the request.session['active_group'] which is then used by the 283 @login_required decorator to configure conn for any group-based queries. 284 """ 285 if active_group is None: 286 active_group = request.REQUEST.get('active_group') 287 active_group = int(active_group) 288 if 'active_group' not in request.session or active_group != request.session['active_group']: 289 request.session.modified = True 290 request.session['active_group'] = active_group 291 request.session['imageInBasket'] = set() # empty basket 292 request.session['basket_counter'] = 0
293
294 @login_required(login_redirect='webindex') 295 -def logout(request, conn=None, **kwargs):
296 """ Logout of the session and redirects to the homepage (will redirect to login first) """ 297 298 if request.session.get('active_group') is not None: 299 try: 300 conn.setDefaultGroup(request.session.get('active_group')) 301 except: 302 logger.error('Exception during logout.', exc_info=True) 303 try: 304 try: 305 conn.seppuku() 306 except: 307 logger.error('Exception during logout.', exc_info=True) 308 finally: 309 request.session.flush() 310 return HttpResponseRedirect(reverse("webindex"))
311
312 313 ########################################################################### 314 @login_required() 315 @render_response() 316 -def load_template(request, menu, conn=None, url=None, **kwargs):
317 """ 318 This view handles most of the top-level pages, as specified by 'menu' E.g. userdata, usertags, history, search etc. 319 Query string 'path' that specifies an object to display in the data tree is parsed. 320 We also prepare the list of users in the current group, for the switch-user form. Change-group form is also prepared. 321 """ 322 request.session.modified = True 323 324 if menu == 'userdata': 325 template = "webclient/data/containers.html" 326 elif menu == 'usertags': 327 template = "webclient/data/container_tags.html" 328 else: 329 template = "webclient/%s/%s.html" % (menu,menu) 330 331 #tree support 332 init = {'initially_open':None, 'initially_select': []} 333 first_sel = None 334 initially_open_owner = None 335 # E.g. backwards compatible support for path=project=51|dataset=502|image=607 (select the image) 336 path = request.REQUEST.get('path', '') 337 i = path.split("|")[-1] 338 if i.split("=")[0] in ('project', 'dataset', 'image', 'screen', 'plate', 'tag'): 339 init['initially_select'].append(str(i).replace("=",'-')) # Backwards compatible with image=607 etc 340 # Now we support show=image-607|image-123 (multi-objects selected) 341 show = request.REQUEST.get('show', '') 342 for i in show.split("|"): 343 if i.split("-")[0] in ('project', 'dataset', 'image', 'screen', 'plate', 'tag', 'acquisition', 'run', 'well'): 344 i = i.replace('run', 'acquisition') # alternatives for 'acquisition' 345 init['initially_select'].append(str(i)) 346 if len(init['initially_select']) > 0: 347 # tree hierarchy open to first selected object 348 init['initially_open'] = [ init['initially_select'][0] ] 349 first_obj, first_id = init['initially_open'][0].split("-",1) 350 # if we're showing a tag, make sure we're on the tags page... 351 if first_obj == "tag" and menu != "usertags": 352 return HttpResponseRedirect(reverse(viewname="load_template", args=['usertags']) + "?show=" + init['initially_select'][0]) 353 try: 354 conn.SERVICE_OPTS.setOmeroGroup('-1') # set context to 'cross-group' 355 if first_obj == "tag": 356 first_sel = conn.getObject("TagAnnotation", long(first_id)) 357 else: 358 first_sel = conn.getObject(first_obj, long(first_id)) 359 initially_open_owner = first_sel.details.owner.id.val 360 # Wells aren't in the tree, so we need parent... 361 if first_obj == "well": 362 parentNode = first_sel.getWellSample().getPlateAcquisition() 363 ptype = "acquisition" 364 if parentNode is None: # No Acquisition for this well... 365 parentNode = first_sel.getParent() #...use Plate instead 366 ptype = "plate" 367 first_sel = parentNode 368 init['initially_open'] = ["%s-%s" % (ptype, parentNode.getId())] 369 init['initially_select'] = init['initially_open'][:] 370 except: 371 pass # invalid id 372 if first_obj not in ("project", "screen"): 373 # need to see if first item has parents 374 if first_sel is not None: 375 for p in first_sel.getAncestry(): 376 if first_obj == "tag": # parents of tags must be tags (no OMERO_CLASS) 377 init['initially_open'].insert(0, "tag-%s" % p.getId()) 378 else: 379 init['initially_open'].insert(0, "%s-%s" % (p.OMERO_CLASS.lower(), p.getId())) 380 initially_open_owner = p.details.owner.id.val 381 if init['initially_open'][0].split("-")[0] == 'image': 382 init['initially_open'].insert(0, "orphaned-0") 383 # need to be sure that tree will be correct omero.group 384 if first_sel is not None: 385 switch_active_group(request, first_sel.details.group.id.val) 386 387 # search support 388 if menu == "search" and request.REQUEST.get('search_query'): 389 init['query'] = str(request.REQUEST.get('search_query')).replace(" ", "%20") 390 391 # get url without request string - used to refresh page after switch user/group etc 392 url = reverse(viewname="load_template", args=[menu]) 393 394 manager = BaseContainer(conn) 395 396 # validate experimenter is in the active group 397 active_group = request.session.get('active_group') or conn.getEventContext().groupId 398 # prepare members of group... 399 s = conn.groupSummary(active_group) 400 leaders = s["leaders"] 401 members = s["colleagues"] 402 userIds = [u.id for u in leaders] 403 userIds.extend( [u.id for u in members] ) 404 users = [] 405 if len(leaders) > 0: 406 users.append( ("Owners", leaders) ) 407 if len(members) > 0: 408 users.append( ("Members", members) ) 409 users = tuple(users) 410 411 # check any change in experimenter... 412 user_id = request.REQUEST.get('experimenter') 413 if initially_open_owner is not None: 414 if (request.session.get('user_id', None) != -1): # if we're not already showing 'All Members'... 415 user_id = initially_open_owner 416 try: 417 user_id = long(user_id) 418 except: 419 user_id = None 420 if user_id is not None: 421 form_users = UsersForm(initial={'users': users, 'empty_label':None, 'menu':menu}, data=request.REQUEST.copy()) 422 if not form_users.is_valid(): 423 if user_id != -1: # All users in group is allowed 424 user_id = None 425 if user_id is None: 426 # ... or check that current user is valid in active group 427 user_id = request.session.get('user_id', None) 428 if user_id is None or int(user_id) not in userIds: 429 if user_id != -1: # All users in group is allowed 430 user_id = conn.getEventContext().userId 431 432 request.session['user_id'] = user_id 433 434 if conn.isAdmin(): # Admin can see all groups 435 myGroups = [g for g in conn.getObjects("ExperimenterGroup") if g.getName() not in ("user", "guest")] 436 else: 437 myGroups = list(conn.getGroupsMemberOf()) 438 myGroups.sort(key=lambda x: x.getName().lower()) 439 new_container_form = ContainerForm() 440 441 context = {'init':init, 'myGroups':myGroups, 'new_container_form':new_container_form} 442 context['groups'] = myGroups 443 context['active_group'] = conn.getObject("ExperimenterGroup", long(active_group)) 444 for g in context['groups']: 445 g.groupSummary() # load leaders / members 446 context['active_user'] = conn.getObject("Experimenter", long(user_id)) 447 448 context['isLeader'] = conn.isLeader() 449 context['current_url'] = url 450 context['template'] = template 451 return context
452
453 454 @login_required(setGroupContext=True) 455 @render_response() 456 -def load_data(request, o1_type=None, o1_id=None, o2_type=None, o2_id=None, o3_type=None, o3_id=None, conn=None, **kwargs):
457 """ 458 This loads data for the tree, via AJAX calls. 459 The template is specified by query string. E.g. icon, table, tree. 460 By default this loads Projects and Datasets. 461 E.g. /load_data?view=tree provides data for the tree as <li>. 462 """ 463 464 # get page 465 page = getIntOrDefault(request, 'page', 1) 466 467 # get view 468 view = str(request.REQUEST.get('view', None)) 469 470 # get index of the plate 471 index = getIntOrDefault(request, 'index', 0) 472 473 # prepare data. E.g. kw = {} or {'dataset': 301L} or {'project': 151L, 'dataset': 301L} 474 kw = dict() 475 if o1_type is not None: 476 if o1_id is not None and o1_id > 0: 477 kw[str(o1_type)] = long(o1_id) 478 else: 479 kw[str(o1_type)] = bool(o1_id) 480 if o2_type is not None and o2_id > 0: 481 kw[str(o2_type)] = long(o2_id) 482 if o3_type is not None and o3_id > 0: 483 kw[str(o3_type)] = long(o3_id) 484 485 try: 486 manager= BaseContainer(conn, **kw) 487 except AttributeError, x: 488 return handlerInternalError(request, x) 489 490 # prepare forms 491 filter_user_id = request.session.get('user_id') 492 form_well_index = None 493 494 context = {'manager':manager, 'form_well_index':form_well_index, 'index':index} 495 496 # load data & template 497 template = None 498 if kw.has_key('orphaned'): 499 manager.listOrphanedImages(filter_user_id, page) 500 if view =='icon': 501 template = "webclient/data/containers_icon.html" 502 else: 503 template = "webclient/data/container_subtree.html" 504 elif len(kw.keys()) > 0 : 505 if kw.has_key('dataset'): 506 load_pixels = (view == 'icon') # we need the sizeX and sizeY for these 507 filter_user_id = None # Show images belonging to all users 508 manager.listImagesInDataset(kw.get('dataset'), filter_user_id, page, load_pixels=load_pixels) 509 if view =='icon': 510 template = "webclient/data/containers_icon.html" 511 else: 512 template = "webclient/data/container_subtree.html" 513 elif kw.has_key('plate') or kw.has_key('acquisition'): 514 if view == 'tree': # Only used when pasting Plate into Screen - load Acquisition in tree 515 template = "webclient/data/container_subtree.html" 516 else: 517 fields = manager.getNumberOfFields() 518 if fields is not None: 519 form_well_index = WellIndexForm(initial={'index':index, 'range':fields}) 520 if index == 0: 521 index = fields[0] 522 show = request.REQUEST.get('show', None) 523 if show is not None: 524 select_wells = [w.split("-")[1] for w in show.split("|") if w.startswith("well-")] 525 context['select_wells'] = ",".join(select_wells) 526 context['baseurl'] = reverse('webgateway').rstrip('/') 527 context['form_well_index'] = form_well_index 528 template = "webclient/data/plate.html" 529 else: 530 manager.listContainerHierarchy(filter_user_id) 531 if view =='tree': 532 template = "webclient/data/containers_tree.html" 533 elif view =='icon': 534 template = "webclient/data/containers_icon.html" 535 else: 536 template = "webclient/data/containers.html" 537 538 context['template_view'] = view 539 context['isLeader'] = conn.isLeader() 540 context['template'] = template 541 return context
542
543 544 @login_required(setGroupContext=True) 545 @render_response() 546 -def load_chgrp_target(request, group_id, target_type, conn=None, **kwargs):
547 """ Loads a tree for user to pick target Project, Dataset or Screen """ 548 549 # filter by group (not switching group) 550 conn.SERVICE_OPTS.setOmeroGroup(int(group_id)) 551 552 manager= BaseContainer(conn) 553 manager.listContainerHierarchy() 554 template = 'webclient/data/chgrp_target_tree.html' 555 556 show_projects = target_type in ('project', 'dataset') 557 context = {'manager': manager, 'target_type': target_type, 'show_projects':show_projects, 'template': template} 558 return context
559
560 @login_required(setGroupContext=True) 561 @render_response() 562 -def load_searching(request, form=None, conn=None, **kwargs):
563 """ 564 Handles AJAX calls to search 565 """ 566 567 manager = BaseSearch(conn) 568 # form = 'form' if we are searching. Get query from request... 569 if form is not None: 570 query_search = request.REQUEST.get('query').replace("+", " ") 571 template = "webclient/search/search_details.html" 572 573 onlyTypes = list() 574 if request.REQUEST.get('projects') is not None and request.REQUEST.get('projects') == 'on': 575 onlyTypes.append('projects') 576 if request.REQUEST.get('datasets') is not None and request.REQUEST.get('datasets') == 'on': 577 onlyTypes.append('datasets') 578 if request.REQUEST.get('images') is not None and request.REQUEST.get('images') == 'on': 579 onlyTypes.append('images') 580 if request.REQUEST.get('plates') is not None and request.REQUEST.get('plates') == 'on': 581 onlyTypes.append('plates') 582 if request.REQUEST.get('screens') is not None and request.REQUEST.get('screens') == 'on': 583 onlyTypes.append('screens') 584 585 startdate = request.REQUEST.get('startdateinput', None) 586 startdate = startdate is not None and smart_str(startdate) or None 587 enddate = request.REQUEST.get('enddateinput', None) 588 enddate = enddate is not None and smart_str(enddate) or None 589 date = None 590 if startdate is not None: 591 if enddate is None: 592 enddate = startdate 593 date = "%s_%s" % (startdate, enddate) 594 595 # by default, if user has not specified any types: 596 if len(onlyTypes) == 0: 597 onlyTypes = ['images'] 598 599 # search is carried out and results are stored in manager.containers.images etc. 600 manager.search(query_search, onlyTypes, date) 601 else: 602 # simply display the search home page. 603 template = "webclient/search/search.html" 604 605 # batch query for searching wells in plates 606 batch_query = request.REQUEST.get('batch_query') 607 if batch_query is not None: 608 delimiter = request.REQUEST.get('delimiter') 609 delimiter = delimiter.decode("string_escape") 610 batch_query = batch_query.split("\n") 611 batch_query = [query.split(delimiter) for query in batch_query] 612 template = "webclient/search/search_details.html" 613 manager.batch_search(batch_query) 614 615 context = {'manager':manager} 616 context['template'] = template 617 return context
618
619 620 @login_required(setGroupContext=True) 621 @render_response() 622 -def load_data_by_tag(request, o_type=None, o_id=None, conn=None, **kwargs):
623 """ 624 Loads data for the tag tree and center panel. 625 Either get the P/D/I etc under tags, or the images etc under a tagged Dataset or Project. 626 @param o_type 'tag' or 'project', 'dataset'. 627 """ 628 629 if request.REQUEST.get("o_type") is not None and len(request.REQUEST.get("o_type")) > 0: 630 o_type = request.REQUEST.get("o_type") 631 try: 632 o_id = long(request.REQUEST.get("o_id")) 633 except: 634 pass 635 636 # check view 637 view = request.REQUEST.get("view") 638 639 # the index of a field within a well 640 index = getIntOrDefault(request, 'index', 0) 641 642 643 # prepare forms 644 filter_user_id = request.session.get('user_id') 645 646 # prepare data 647 kw = dict() 648 if o_type is not None and o_id > 0: 649 kw[str(o_type)] = long(o_id) 650 651 try: 652 manager= BaseContainer(conn, **kw) 653 except AttributeError, x: 654 return handlerInternalError(request, x) 655 656 if o_id is not None: 657 if o_type == "tag": 658 manager.loadDataByTag() 659 if view == "tree": 660 template = "webclient/data/container_tags_containers.html" 661 elif view == "icon": 662 template = "webclient/data/containers_icon.html" 663 664 elif o_type == "dataset": 665 manager.listImagesInDataset(o_id, filter_user_id) 666 template = "webclient/data/container_tags_subtree.html" 667 else: 668 manager.loadTags(filter_user_id) 669 template = "webclient/data/container_tags_tree.html" 670 # load data 671 form_well_index = None 672 673 674 context = {'manager':manager} 675 context['template_view'] = view 676 context['isLeader'] = conn.isLeader() 677 context['template'] = template 678 return context
679
680 681 @login_required() 682 @render_response() 683 -def open_astex_viewer(request, obj_type, obj_id, conn=None, **kwargs):
684 """ 685 Opens the Open Astex Viewer applet, to display volume masks in a couple of formats: 686 - mrc.map files that are attached to images. obj_type = 'file' 687 - Convert OMERO image to mrc on the fly. obj_type = 'image_8bit' or 'image' 688 In this case, we may use 'scipy' to scale the image volume. 689 """ 690 691 # can only populate these for 'image' 692 image = None 693 data_storage_mode = "" 694 pixelRange = None # (min, max) values of the raw data 695 contourSliderInit, contourSliderIncr = None, None 696 sizeOptions = None # only give user choice if we need to scale down (and we CAN scale with scipy) 697 # If we convert to 8bit map, subtract dataOffset, multiply by mapPixelFactor add mapOffset. (used for js contour controls) 698 if obj_type == 'file': 699 ann = conn.getObject("Annotation", obj_id) 700 if ann is None: 701 return handlerInternalError(request, "Can't find file Annotation ID %s as data source for Open Astex Viewer." % obj_id) 702 # determine mapType by name 703 imageName = ann.getFileName() 704 if imageName.endswith(".bit"): 705 data_url = reverse("open_astex_bit", args=[obj_id]) 706 else: 707 data_url = reverse("open_astex_map", args=[obj_id]) 708 709 elif obj_type in ('image', 'image_8bit'): 710 image = conn.getObject("Image", obj_id) # just check the image exists 711 if image is None: 712 return handlerInternalError(request, "Can't find image ID %s as data source for Open Astex Viewer." % obj_id) 713 imageName = image.getName() 714 c = image.getChannels()[0] 715 # By default, scale to 120 ^3. Also give option to load 'bigger' map or full sized 716 DEFAULTMAPSIZE = 120 717 BIGGERMAPSIZE = 160 718 targetSize = DEFAULTMAPSIZE * DEFAULTMAPSIZE * DEFAULTMAPSIZE 719 biggerSize = BIGGERMAPSIZE * BIGGERMAPSIZE * BIGGERMAPSIZE 720 imgSize = image.getSizeX() * image.getSizeY() * image.getSizeZ() 721 if imgSize > targetSize: 722 try: 723 import scipy.ndimage 724 sizeOptions = {} 725 factor = float(targetSize)/ imgSize 726 f = pow(factor,1.0/3) 727 sizeOptions["small"] = {'x':image.getSizeX() * f, 'y':image.getSizeY() * f, 'z':image.getSizeZ() * f, 'size':DEFAULTMAPSIZE} 728 if imgSize > biggerSize: 729 factor2 = float(biggerSize)/ imgSize 730 f2 = pow(factor2,1.0/3) 731 sizeOptions["medium"] = {'x':image.getSizeX() * f2, 'y':image.getSizeY() * f2, 'z':image.getSizeZ() * f2, 'size':BIGGERMAPSIZE} 732 else: 733 sizeOptions["full"] = {'x':image.getSizeX(), 'y':image.getSizeY(), 'z':image.getSizeZ()} 734 except ImportError: 735 DEFAULTMAPSIZE = 0 # don't try to resize the map (see image_as_map) 736 pass 737 pixelRange = (c.getWindowMin(), c.getWindowMax()) 738 contourSliderInit = (pixelRange[0] + pixelRange[1])/2 # best guess as starting position for contour slider 739 740 def calcPrecision(range): 741 dec=0 742 if (range == 0): dec = 0 743 elif (range < 0.0000001): dec = 10 744 elif (range < 0.000001): dec = 9 745 elif (range < 0.00001): dec = 8 746 elif (range < 0.0001): dec = 7 747 elif (range < 0.001): dec = 6 748 elif (range < 0.01): dec = 5 749 elif (range < 0.1): dec = 4 750 elif (range < 1.0): dec = 3 751 elif (range < 10.0): dec = 2 752 elif (range < 100.0): dec = 1 753 return dec
754 dec = calcPrecision(pixelRange[1]-pixelRange[0]) 755 contourSliderIncr = "%.*f" % (dec,abs((pixelRange[1]-pixelRange[0])/128.0)) 756 757 if obj_type == 'image_8bit': 758 data_storage_mode = 1 759 data_url = reverse("webclient_image_as_map_8bit", args=[obj_id, DEFAULTMAPSIZE]) 760 else: 761 if image.getPrimaryPixels().getPixelsType.value == 'float': 762 data_storage_mode = 2 763 else: 764 data_storage_mode = 1 # E.g. uint16 image will get served as 8bit map 765 data_url = reverse("webclient_image_as_map", args=[obj_id, DEFAULTMAPSIZE]) 766 767 context = {'data_url': data_url, "image": image, 768 "sizeOptions":sizeOptions, "contourSliderInit":contourSliderInit, "contourSliderIncr":contourSliderIncr, 769 "data_storage_mode": data_storage_mode,'pixelRange':pixelRange} 770 context['template'] = 'webclient/annotations/open_astex_viewer.html' 771 return context 772
773 774 @login_required() 775 @render_response() 776 -def load_metadata_details(request, c_type, c_id, conn=None, share_id=None, **kwargs):
777 """ 778 This page is the right-hand panel 'general metadata', first tab only. 779 Shown for Projects, Datasets, Images, Screens, Plates, Wells, Tags etc. 780 The data and annotations are loaded by the manager. Display of appropriate data is handled by the template. 781 """ 782 783 # the index of a field within a well 784 index = getIntOrDefault(request, 'index', 0) 785 786 # we only expect a single object, but forms can take multiple objects 787 images = c_type == "image" and list(conn.getObjects("Image", [c_id])) or list() 788 datasets = c_type == "dataset" and list(conn.getObjects("Dataset", [c_id])) or list() 789 projects = c_type == "project" and list(conn.getObjects("Project", [c_id])) or list() 790 screens = c_type == "screen" and list(conn.getObjects("Screen", [c_id])) or list() 791 plates = c_type == "plate" and list(conn.getObjects("Plate", [c_id])) or list() 792 acquisitions = c_type == "acquisition" and list(conn.getObjects("PlateAcquisition", [c_id])) or list() 793 shares = (c_type == "share" or c_type == "discussion") and [conn.getShare(c_id)] or list() 794 wells = list() 795 if c_type == "well": 796 for w in conn.getObjects("Well", [c_id]): 797 w.index=index 798 wells.append(w) 799 800 # we simply set up the annotation form, passing the objects to be annotated. 801 selected = {'images':c_type == "image" and [c_id] or [], 802 'datasets':c_type == "dataset" and [c_id] or [], 803 'projects':c_type == "project" and [c_id] or [], 804 'screens':c_type == "screen" and [c_id] or [], 805 'plates':c_type == "plate" and [c_id] or [], 806 'acquisitions':c_type == "acquisition" and [c_id] or [], 807 'wells':c_type == "well" and [c_id] or [], 808 'shares':(c_type == "share" or c_type == "discussion") and [c_id] or []} 809 810 initial={'selected':selected, 'images':images, 'datasets':datasets, 'projects':projects, 'screens':screens, 'plates':plates, 'acquisitions':acquisitions, 'wells':wells, 'shares': shares} 811 812 form_comment = None 813 figScripts = None 814 if c_type in ("share", "discussion"): 815 template = "webclient/annotations/annotations_share.html" 816 manager = BaseShare(conn, c_id) 817 manager.getAllUsers(c_id) 818 manager.getComments(c_id) 819 form_comment = CommentAnnotationForm(initial=initial) 820 else: 821 try: 822 manager = BaseContainer(conn, index=index, **{str(c_type): long(c_id)}) 823 except AttributeError, x: 824 return handlerInternalError(request, x) 825 if share_id is None: 826 template = "webclient/annotations/metadata_general.html" 827 manager.annotationList() 828 figScripts = manager.listFigureScripts() 829 form_comment = CommentAnnotationForm(initial=initial) 830 else: 831 template = "webclient/annotations/annotations_share.html" 832 833 if c_type in ("tag"): 834 context = {'manager':manager} 835 else: 836 context = {'manager':manager, 'form_comment':form_comment, 'index':index, 837 'share_id':share_id} 838 context['figScripts'] = figScripts 839 context['template'] = template 840 context['webclient_path'] = request.build_absolute_uri(reverse('webindex')) 841 return context
842
843 844 @login_required() 845 @render_response() 846 -def load_metadata_preview(request, c_type, c_id, conn=None, share_id=None, **kwargs):
847 """ 848 This is the image 'Preview' tab for the right-hand panel. 849 Currently this doesn't do much except launch the view-port plugin using the image Id (and share Id if necessary) 850 """ 851 852 # the index of a field within a well 853 index = getIntOrDefault(request, 'index', 0) 854 855 manager = BaseContainer(conn, index=index, **{str(c_type): long(c_id)}) 856 857 if c_type == "well": 858 manager.image = manager.well.getImage(index) 859 860 context = {'manager':manager, 'share_id':share_id} 861 context['template'] = "webclient/annotations/metadata_preview.html" 862 return context
863
864 865 @login_required() 866 @render_response() 867 -def load_metadata_hierarchy(request, c_type, c_id, conn=None, **kwargs):
868 """ 869 This loads the ancestors of the specified object and displays them in a static tree. 870 Used by an AJAX call from the metadata_general panel. 871 """ 872 873 # the index of a field within a well 874 index = getIntOrDefault(request, 'index', 0) 875 876 manager = BaseContainer(conn, index=index, **{str(c_type): long(c_id)}) 877 878 context = {'manager':manager} 879 context['template'] = "webclient/annotations/metadata_hierarchy.html" 880 return context
881
882 883 @login_required() 884 @render_response() 885 -def load_metadata_acquisition(request, c_type, c_id, conn=None, share_id=None, **kwargs):
886 """ 887 The acquisition tab of the right-hand panel. Only loaded for images. 888 TODO: urls regex should make sure that c_type is only 'image' OR 'well' 889 """ 890 891 # the index of a field within a well 892 index = getIntOrDefault(request, 'index', 0) 893 894 try: 895 if c_type in ("share", "discussion"): 896 template = "webclient/annotations/annotations_share.html" 897 manager = BaseShare(conn, c_id) 898 manager.getAllUsers(c_id) 899 manager.getComments(c_id) 900 else: 901 template = "webclient/annotations/metadata_acquisition.html" 902 manager = BaseContainer(conn, index=index, **{str(c_type): long(c_id)}) 903 except AttributeError, x: 904 return handlerInternalError(request, x) 905 906 form_environment = None 907 form_objective = None 908 form_microscope = None 909 form_instrument_objectives = list() 910 form_stageLabel = None 911 form_filters = list() 912 form_dichroics = list() 913 form_detectors = list() 914 form_channels = list() 915 form_lasers = list() 916 917 # various enums we need for the forms (don't load unless needed) 918 mediums = None 919 immersions = None 920 corrections = None 921 922 if c_type == 'well' or c_type == 'image': 923 if c_type == "well": 924 manager.image = manager.well.getImage(index) 925 if share_id is None: 926 manager.originalMetadata() 927 manager.channelMetadata() 928 for theC, ch in enumerate(manager.channel_metadata): 929 logicalChannel = ch.getLogicalChannel() 930 if logicalChannel is not None: 931 channel = dict() 932 channel['form'] = MetadataChannelForm(initial={'logicalChannel': logicalChannel, 933 'illuminations': list(conn.getEnumerationEntries("IlluminationI")), 934 'contrastMethods': list(conn.getEnumerationEntries("ContrastMethodI")), 935 'modes': list(conn.getEnumerationEntries("AcquisitionModeI"))}) 936 if share_id is None: #9853 Much metadata is not available to 'shares' 937 lightPath = logicalChannel.getLightPath() 938 if lightPath is not None: 939 channel['form_dichroic'] = None 940 channel['form_excitation_filters'] = list() 941 channel['form_emission_filters'] = list() 942 lightPathDichroic = lightPath.getDichroic() 943 if lightPathDichroic is not None: 944 channel['form_dichroic'] = MetadataDichroicForm(initial={'dichroic': lightPathDichroic}) 945 filterTypes = list(conn.getEnumerationEntries("FilterTypeI")) 946 for f in lightPath.getEmissionFilters(): 947 channel['form_emission_filters'].append(MetadataFilterForm(initial={'filter': f,'types':filterTypes})) 948 for f in lightPath.getExcitationFilters(): 949 channel['form_excitation_filters'].append(MetadataFilterForm(initial={'filter': f,'types':filterTypes})) 950 if logicalChannel.getDetectorSettings()._obj is not None and logicalChannel.getDetectorSettings().getDetector(): 951 channel['form_detector_settings'] = MetadataDetectorForm(initial={'detectorSettings':logicalChannel.getDetectorSettings(), 952 'detector': logicalChannel.getDetectorSettings().getDetector(), 953 'types':list(conn.getEnumerationEntries("DetectorTypeI")), 954 'binnings':list(conn.getEnumerationEntries("Binning"))}) 955 956 lightSourceSettings = logicalChannel.getLightSourceSettings() 957 if lightSourceSettings is not None and lightSourceSettings._obj is not None: 958 if lightSourceSettings.getLightSource() is not None: 959 channel['form_light_source'] = MetadataLightSourceForm(initial={'lightSource': lightSourceSettings.getLightSource(), 960 'lstypes': list(conn.getEnumerationEntries("LaserType")), 961 'mediums': list(conn.getEnumerationEntries("LaserMediumI")), 962 'pulses': list(conn.getEnumerationEntries("PulseI"))}) 963 # TODO: We don't display filter sets here yet since they are not populated on Import by BioFormats. 964 channel['label'] = ch.getLabel() 965 color = ch.getColor() 966 channel['color'] = color is not None and color.getHtml() or None 967 planeInfo = manager.image and manager.image.getPrimaryPixels().copyPlaneInfo(theC=theC, theZ=0) 968 channel['plane_info'] = list(planeInfo) 969 form_channels.append(channel) 970 971 try: 972 image = manager.well.getWellSample().image() 973 except: 974 image = manager.image 975 976 if share_id is None: #9853 977 if image.getObjectiveSettings() is not None: 978 # load the enums if needed and create our Objective Form 979 if mediums is None: mediums = list(conn.getEnumerationEntries("MediumI")) 980 if immersions is None: immersions = list(conn.getEnumerationEntries("ImmersionI")) 981 if corrections is None: corrections = list(conn.getEnumerationEntries("CorrectionI")) 982 form_objective = MetadataObjectiveSettingsForm(initial={'objectiveSettings': image.getObjectiveSettings(), 983 'objective': image.getObjectiveSettings().getObjective(), 984 'mediums': mediums, 'immersions': immersions, 'corrections': corrections }) 985 if image.getImagingEnvironment() is not None: 986 form_environment = MetadataEnvironmentForm(initial={'image': image}) 987 if image.getStageLabel() is not None: 988 form_stageLabel = MetadataStageLabelForm(initial={'image': image }) 989 990 instrument = image.getInstrument() 991 if instrument is not None: 992 if instrument.getMicroscope() is not None: 993 form_microscope = MetadataMicroscopeForm(initial={'microscopeTypes':list(conn.getEnumerationEntries("MicroscopeTypeI")), 'microscope': instrument.getMicroscope()}) 994 995 objectives = instrument.getObjectives() 996 for o in objectives: 997 # load the enums if needed and create our Objective Form 998 if mediums is None: mediums = list(conn.getEnumerationEntries("MediumI")) 999 if immersions is None: immersions = list(conn.getEnumerationEntries("ImmersionI")) 1000 if corrections is None: corrections = list(conn.getEnumerationEntries("CorrectionI")) 1001 obj_form = MetadataObjectiveForm(initial={'objective': o, 1002 'mediums': mediums, 'immersions': immersions, 'corrections': corrections }) 1003 form_instrument_objectives.append(obj_form) 1004 filters = list(instrument.getFilters()) 1005 if len(filters) > 0: 1006 for f in filters: 1007 form_filter = MetadataFilterForm(initial={'filter': f, 'types':list(conn.getEnumerationEntries("FilterTypeI"))}) 1008 form_filters.append(form_filter) 1009 1010 dichroics = list(instrument.getDichroics()) 1011 for d in dichroics: 1012 form_dichroic = MetadataDichroicForm(initial={'dichroic': d}) 1013 form_dichroics.append(form_dichroic) 1014 1015 detectors = list(instrument.getDetectors()) 1016 if len(detectors) > 0: 1017 for d in detectors: 1018 form_detector = MetadataDetectorForm(initial={'detectorSettings':None, 'detector': d, 'types':list(conn.getEnumerationEntries("DetectorTypeI"))}) 1019 form_detectors.append(form_detector) 1020 1021 lasers = list(instrument.getLightSources()) 1022 if len(lasers) > 0: 1023 for l in lasers: 1024 form_laser = MetadataLightSourceForm(initial={'lightSource': l, 1025 'lstypes':list(conn.getEnumerationEntries("LaserType")), 1026 'mediums': list(conn.getEnumerationEntries("LaserMediumI")), 1027 'pulses': list(conn.getEnumerationEntries("PulseI"))}) 1028 form_lasers.append(form_laser) 1029 1030 # TODO: remove this 'if' since we should only have c_type = 'image'? 1031 if c_type in ("share", "discussion", "tag"): 1032 context = {'manager':manager} 1033 else: 1034 context = {'manager':manager, 1035 'form_channels':form_channels, 'form_environment':form_environment, 'form_objective':form_objective, 1036 'form_microscope':form_microscope, 'form_instrument_objectives': form_instrument_objectives, 'form_filters':form_filters, 1037 'form_dichroics':form_dichroics, 'form_detectors':form_detectors, 'form_lasers':form_lasers, 'form_stageLabel':form_stageLabel} 1038 context['template'] = template 1039 return context 1040
1041 1042 ########################################################################### 1043 # ACTIONS 1044 1045 # Annotation in the right-hand panel is handled the same way for single objects (metadata_general.html) 1046 # AND for batch annotation (batch_annotate.html) by 4 forms: 1047 # Comment (this is loaded in the initial page) 1048 # Tags (the empty form is in the initial page but fields are loaded via AJAX) 1049 # Local File (this is loaded in the initial page) 1050 # Existing File (the empty form is in the initial page but field is loaded via AJAX) 1051 # 1052 # In each case, the form itself contains hidden fields to specify the object(s) being annotated 1053 # All forms inherit from a single form that has these fields. 1054 1055 -def getObjects(request, conn=None):
1056 """ 1057 Prepare objects for use in the annotation forms. 1058 These objects are required by the form superclass to populate hidden fields, so we know what we're annotating on submission 1059 """ 1060 images = len(request.REQUEST.getlist('image')) > 0 and list(conn.getObjects("Image", request.REQUEST.getlist('image'))) or list() 1061 datasets = len(request.REQUEST.getlist('dataset')) > 0 and list(conn.getObjects("Dataset", request.REQUEST.getlist('dataset'))) or list() 1062 projects = len(request.REQUEST.getlist('project')) > 0 and list(conn.getObjects("Project", request.REQUEST.getlist('project'))) or list() 1063 screens = len(request.REQUEST.getlist('screen')) > 0 and list(conn.getObjects("Screen", request.REQUEST.getlist('screen'))) or list() 1064 plates = len(request.REQUEST.getlist('plate')) > 0 and list(conn.getObjects("Plate", request.REQUEST.getlist('plate'))) or list() 1065 acquisitions = len(request.REQUEST.getlist('acquisition')) > 0 and \ 1066 list(conn.getObjects("PlateAcquisition", request.REQUEST.getlist('acquisition'))) or list() 1067 shares = len(request.REQUEST.getlist('share')) > 0 and [conn.getShare(request.REQUEST.getlist('share')[0])] or list() 1068 wells = list() 1069 if len(request.REQUEST.getlist('well')) > 0: 1070 index = getIntOrDefault(request, 'index', 0) 1071 for w in conn.getObjects("Well", request.REQUEST.getlist('well')): 1072 w.index=index 1073 wells.append(w) 1074 return {'image':images, 'dataset':datasets, 'project':projects, 'screen':screens, 1075 'plate':plates, 'acquisition':acquisitions, 'well':wells, 'share':shares}
1076
1077 -def getIds(request):
1078 """ Used by forms to indicate the currently selected objects prepared above """ 1079 selected = {'images':request.REQUEST.getlist('image'), 'datasets':request.REQUEST.getlist('dataset'), \ 1080 'projects':request.REQUEST.getlist('project'), 'screens':request.REQUEST.getlist('screen'), \ 1081 'plates':request.REQUEST.getlist('plate'), 'acquisitions':request.REQUEST.getlist('acquisition'), \ 1082 'wells':request.REQUEST.getlist('well'), 'shares':request.REQUEST.getlist('share')} 1083 return selected
1084
1085 1086 @login_required(setGroupContext=True) 1087 @render_response() 1088 -def batch_annotate(request, conn=None, **kwargs):
1089 """ 1090 This page gives a form for batch annotation. 1091 Local File form and Comment form are loaded. Other forms are loaded via AJAX 1092 """ 1093 1094 objs = getObjects(request, conn) 1095 selected = getIds(request) 1096 initial = {'selected':selected, 'images':objs['image'], 'datasets': objs['dataset'], 'projects':objs['project'], 1097 'screens':objs['screen'], 'plates':objs['plate'], 'acquisitions':objs['acquisition'], 'wells':objs['well']} 1098 form_comment = CommentAnnotationForm(initial=initial) 1099 index = getIntOrDefault(request, 'index', 0) 1100 1101 manager = BaseContainer(conn) 1102 batchAnns = manager.loadBatchAnnotations(objs) 1103 figScripts = manager.listFigureScripts(objs) 1104 1105 obj_ids = [] 1106 obj_labels = [] 1107 for key in objs: 1108 obj_ids += ["%s=%s"%(key,o.id) for o in objs[key]] 1109 for o in objs[key]: 1110 obj_labels.append( {'type':key.title(), 'id':o.id, 'name':o.getName()} ) 1111 obj_string = "&".join(obj_ids) 1112 link_string = "|".join(obj_ids).replace("=", "-") 1113 1114 context = {'form_comment':form_comment, 'obj_string':obj_string, 'link_string': link_string, 1115 'obj_labels': obj_labels, 'batchAnns': batchAnns, 'batch_ann':True, 'index': index, 1116 'figScripts':figScripts} 1117 context['template'] = "webclient/annotations/batch_annotate.html" 1118 context['webclient_path'] = request.build_absolute_uri(reverse('webindex')) 1119 return context
1120
1121 1122 @login_required(setGroupContext=True) 1123 @render_response() 1124 -def annotate_file(request, conn=None, **kwargs):
1125 """ 1126 On 'POST', This handles attaching an existing file-annotation(s) and/or upload of a new file to one or more objects 1127 Otherwise it generates the form for choosing file-annotations & local files. 1128 """ 1129 index = getIntOrDefault(request, 'index', 0) 1130 oids = getObjects(request, conn) 1131 selected = getIds(request) 1132 initial = {'selected':selected, 'images':oids['image'], 'datasets': oids['dataset'], 'projects':oids['project'], 1133 'screens':oids['screen'], 'plates':oids['plate'], 'acquisitions':oids['acquisition'], 'wells':oids['well']} 1134 1135 obj_count = sum( [len(selected[types]) for types in selected] ) 1136 1137 # Get appropriate manager, either to list available Tags to add to single object, or list ALL Tags (multiple objects) 1138 manager = None 1139 if obj_count == 1: 1140 for t in selected: 1141 if len(selected[t]) > 0: 1142 o_type = t[:-1] # "images" -> "image" 1143 o_id = selected[t][0] 1144 break 1145 if o_type in ("dataset", "project", "image", "screen", "plate", "acquisition", "well","comment", "file", "tag", "tagset"): 1146 if o_type == 'tagset': o_type = 'tag' # TODO: this should be handled by the BaseContainer 1147 kw = {'index':index} 1148 if o_type is not None and o_id > 0: 1149 kw[str(o_type)] = long(o_id) 1150 try: 1151 manager = BaseContainer(conn, **kw) 1152 except AttributeError, x: 1153 return handlerInternalError(request, x) 1154 1155 if manager is not None: 1156 files = manager.getFilesByObject() 1157 else: 1158 manager = BaseContainer(conn) 1159 for dtype, objs in oids.items(): 1160 if len(objs) > 0: 1161 # NB: we only support a single data-type now. E.g. 'image' OR 'dataset' etc. 1162 files = manager.getFilesByObject(parent_type=dtype, parent_ids=[o.getId() for o in objs]) 1163 break 1164 1165 initial['files'] = files 1166 1167 if request.method == 'POST': 1168 # handle form submission 1169 form_file = FilesAnnotationForm(initial=initial, data=request.REQUEST.copy()) 1170 if form_file.is_valid(): 1171 # Link existing files... 1172 files = form_file.cleaned_data['files'] 1173 added_files = [] 1174 if files is not None and len(files)>0: 1175 added_files = manager.createAnnotationsLinks('file', files, oids, well_index=index) 1176 # upload new file 1177 fileupload = 'annotation_file' in request.FILES and request.FILES['annotation_file'] or None 1178 if fileupload is not None and fileupload != "": 1179 newFileId = manager.createFileAnnotations(fileupload, oids, well_index=index) 1180 added_files.append(newFileId) 1181 if len(added_files) == 0: 1182 return HttpResponse("<div>No Files chosen</div>") 1183 template = "webclient/annotations/fileanns.html" 1184 context = {} 1185 # Now we lookup the object-annotations (same as for def batch_annotate above) 1186 batchAnns = manager.loadBatchAnnotations(oids, ann_ids=added_files, addedByMe=(obj_count==1)) 1187 if obj_count > 1: 1188 context["batchAnns"] = batchAnns 1189 context['batch_ann'] = True 1190 else: 1191 # We only need a subset of the info in batchAnns 1192 fileanns = [] 1193 for a in batchAnns['File']: 1194 for l in a['links']: 1195 fileanns.append(l.getAnnotation()) 1196 context['fileanns'] = fileanns 1197 context['can_remove'] = True 1198 else: 1199 return HttpResponse(form_file.errors) 1200 1201 else: 1202 form_file = FilesAnnotationForm(initial=initial) 1203 context = {'form_file': form_file, 'index': index} 1204 template = "webclient/annotations/files_form.html" 1205 context['template'] = template 1206 return context
1207
1208 @login_required(setGroupContext=True) 1209 @render_response() 1210 -def annotate_comment(request, conn=None, **kwargs):
1211 """ Handle adding Comments to one or more objects 1212 Unbound instance of Comment form not available. 1213 If the form has been submitted, a bound instance of the form 1214 is created using request.POST""" 1215 1216 if request.method != 'POST': 1217 raise Http404("Unbound instance of form not available.") 1218 1219 index = getIntOrDefault(request, 'index', 0) 1220 oids = getObjects(request, conn) 1221 selected = getIds(request) 1222 initial = {'selected':selected, 'images':oids['image'], 'datasets': oids['dataset'], 'projects':oids['project'], 1223 'screens':oids['screen'], 'plates':oids['plate'], 'acquisitions':oids['acquisition'], 'wells':oids['well'], 1224 'shares':oids['share']} 1225 1226 # Handle form submission... 1227 form_multi = CommentAnnotationForm(initial=initial, data=request.REQUEST.copy()) 1228 if form_multi.is_valid(): 1229 # In each case below, we pass the {'object_type': [ids]} map 1230 content = form_multi.cleaned_data['comment'] 1231 if content is not None and content != "": 1232 if oids['share'] is not None and len(oids['share']) > 0: 1233 sid = oids['share'][0].id 1234 manager = BaseShare(conn, sid) 1235 host = request.build_absolute_uri(reverse("load_template", args=["public"])) 1236 textAnn = manager.addComment(host, conn.server_id, content) 1237 else: 1238 manager = BaseContainer(conn) 1239 textAnn = manager.createCommentAnnotations(content, oids, well_index=index) 1240 context = {'tann': textAnn, 'template':"webclient/annotations/comment.html"} 1241 return context 1242 else: 1243 return HttpResponse(str(form_multi.errors)) # TODO: handle invalid form error
1244
1245 @login_required(setGroupContext=True) 1246 @render_response() 1247 -def annotate_tags(request, conn=None, **kwargs):
1248 """ This handles creation AND submission of Tags form, adding new AND/OR existing tags to one or more objects """ 1249 1250 index = getIntOrDefault(request, 'index', 0) 1251 oids = getObjects(request, conn) 1252 selected = getIds(request) 1253 obj_count = sum( [len(selected[types]) for types in selected] ) 1254 1255 # Get appropriate manager, either to list available Tags to add to single object, or list ALL Tags (multiple objects) 1256 manager = None 1257 if obj_count == 1: 1258 for t in selected: 1259 if len(selected[t]) > 0: 1260 o_type = t[:-1] # "images" -> "image" 1261 o_id = selected[t][0] 1262 break 1263 if o_type in ("dataset", "project", "image", "screen", "plate", "acquisition", "well","comment", "file", "tag", "tagset"): 1264 if o_type == 'tagset': o_type = 'tag' # TODO: this should be handled by the BaseContainer 1265 kw = {'index':index} 1266 if o_type is not None and o_id > 0: 1267 kw[str(o_type)] = long(o_id) 1268 try: 1269 manager = BaseContainer(conn, **kw) 1270 except AttributeError, x: 1271 return handlerInternalError(request, x) 1272 elif o_type in ("share", "sharecomment"): 1273 manager = BaseShare(conn, o_id) 1274 1275 if manager is not None: 1276 tags = manager.getTagsByObject() 1277 else: 1278 manager = BaseContainer(conn) 1279 for dtype, objs in oids.items(): 1280 if len(objs) > 0: 1281 # NB: we only support a single data-type now. E.g. 'image' OR 'dataset' etc. 1282 tags = manager.getTagsByObject(parent_type=dtype, parent_ids=[o.getId() for o in objs]) 1283 break 1284 1285 initial = {'selected':selected, 'images':oids['image'], 'datasets': oids['dataset'], 'projects':oids['project'], 1286 'screens':oids['screen'], 'plates':oids['plate'], 'acquisitions':oids['acquisition'], 'wells':oids['well']} 1287 initial['tags'] = tags 1288 1289 if request.method == 'POST': 1290 # handle form submission 1291 form_tags = TagsAnnotationForm(initial=initial, data=request.REQUEST.copy()) 1292 # Create new tags or Link existing tags... 1293 if form_tags.is_valid(): 1294 tag = form_tags.cleaned_data['tag'] 1295 description = form_tags.cleaned_data['description'] 1296 tags = form_tags.cleaned_data['tags'] 1297 added_tags = []; 1298 if tags is not None and len(tags)>0: 1299 added_tags = manager.createAnnotationsLinks('tag', tags, oids, well_index=index) 1300 if tag is not None and tag != "": 1301 new_tag_id = manager.createTagAnnotations(tag, description, oids, well_index=index) 1302 added_tags.append(new_tag_id) 1303 if len(added_tags) == 0: 1304 return HttpResponse("<div>No Tags Added</div>") 1305 template = "webclient/annotations/tags.html" 1306 context = {} 1307 # Now we lookup the object-annotations (same as for def batch_annotate above) 1308 batchAnns = manager.loadBatchAnnotations(oids, ann_ids=added_tags, addedByMe=(obj_count==1)) 1309 if obj_count > 1: 1310 context["batchAnns"] = batchAnns 1311 context['batch_ann'] = True 1312 else: 1313 # We only need a subset of the info in batchAnns 1314 taganns = [] 1315 for a in batchAnns['Tag']: 1316 for l in a['links']: 1317 taganns.append(l.getAnnotation()) 1318 context['tags'] = taganns 1319 context['can_remove'] = True 1320 else: 1321 return HttpResponse(str(form_tags.errors)) # TODO: handle invalid form error 1322 1323 else: 1324 form_tags = TagsAnnotationForm(initial=initial) 1325 context = {'form_tags': form_tags, 'index': index} 1326 template = "webclient/annotations/tags_form.html" 1327 context['template'] = template 1328 return context
1329
1330 1331 @login_required() 1332 @render_response() 1333 -def edit_channel_names(request, imageId, conn=None, **kwargs):
1334 """ 1335 Edit and save channel names 1336 """ 1337 image = conn.getObject("Image", imageId) 1338 sizeC = image.getSizeC() 1339 channelNames = {} 1340 nameDict = {} 1341 for i in range(sizeC): 1342 cname = request.REQUEST.get("channel%d" % i, None) 1343 if cname is not None: 1344 channelNames["channel%d" % i] = smart_str(cname) 1345 nameDict[i+1] = smart_str(cname) 1346 # If the 'Apply to Dataset' button was used to submit... 1347 if request.REQUEST.get('confirm_apply', None) is not None: 1348 parentId = request.REQUEST.get('parentId', None) # plate-123 OR dataset-234 1349 if parentId is not None: 1350 ptype = parentId.split("-")[0].title() 1351 pid = long(parentId.split("-")[1]) 1352 counts = conn.setChannelNames(ptype, [pid], nameDict, channelCount=sizeC) 1353 else: 1354 counts = conn.setChannelNames("Image", [image.getId()], nameDict) 1355 rv = {"channelNames": channelNames} 1356 if counts: 1357 rv['imageCount'] = counts['imageCount'] 1358 rv['updateCount'] = counts['updateCount'] 1359 return rv 1360 else: 1361 return {"error": "No parent found to apply Channel Names"}
1362
1363 1364 @login_required(setGroupContext=True) 1365 @render_response() 1366 -def manage_action_containers(request, action, o_type=None, o_id=None, conn=None, **kwargs):
1367 """ 1368 Handles many different actions on various objects. 1369 1370 @param action: "addnewcontainer", (creates a new Project, Dataset, Screen) 1371 "editname", "savename", "editdescription", "savedescription", (used as GET and POST for in-line editing) 1372 "paste", "move", "remove", "removefromshare", (tree P/D/I moving etc) 1373 "delete", "deletemany" (delete objects) 1374 @param o_type: "dataset", "project", "image", "screen", "plate", "acquisition", "well","comment", "file", "tag", "tagset","share", "sharecomment" 1375 """ 1376 template = None 1377 1378 # the index of a field within a well 1379 index = getIntOrDefault(request, 'index', 0) 1380 1381 manager = None 1382 if o_type in ("dataset", "project", "image", "screen", "plate", "acquisition", "well","comment", "file", "tag", "tagset"): 1383 if o_type == 'tagset': o_type = 'tag' # TODO: this should be handled by the BaseContainer 1384 kw = {'index':index} 1385 if o_type is not None and o_id > 0: 1386 kw[str(o_type)] = long(o_id) 1387 try: 1388 manager = BaseContainer(conn, **kw) 1389 except AttributeError, x: 1390 return handlerInternalError(request, x) 1391 elif o_type in ("share", "sharecomment"): 1392 manager = BaseShare(conn, o_id) 1393 else: 1394 manager = BaseContainer(conn) 1395 1396 form = None 1397 if action == 'addnewcontainer': 1398 # Used within the jsTree to add a new Project, Dataset etc under a specified parent OR top-level 1399 if not request.method == 'POST': 1400 return HttpResponseRedirect(reverse("manage_action_containers", args=["edit", o_type, o_id])) 1401 if o_type is not None and hasattr(manager, o_type) and o_id > 0: 1402 # E.g. Parent o_type is 'project'... 1403 form = ContainerForm(data=request.REQUEST.copy()) 1404 if form.is_valid(): 1405 logger.debug("Create new in %s: %s" % (o_type, str(form.cleaned_data))) 1406 name = form.cleaned_data['name'] 1407 description = form.cleaned_data['description'] 1408 oid = manager.createDataset(name, description) 1409 rdict = {'bad':'false', 'id': oid} 1410 json = simplejson.dumps(rdict, ensure_ascii=False) 1411 return HttpResponse( json, mimetype='application/javascript') 1412 else: 1413 d = dict() 1414 for e in form.errors.iteritems(): 1415 d.update({e[0]:unicode(e[1])}) 1416 rdict = {'bad':'true','errs': d } 1417 json = simplejson.dumps(rdict, ensure_ascii=False) 1418 return HttpResponse( json, mimetype='application/javascript') 1419 elif request.REQUEST.get('folder_type') in ("project", "screen", "dataset"): 1420 # No parent specified. We can create orphaned 'project', 'dataset' etc. 1421 form = ContainerForm(data=request.REQUEST.copy()) 1422 if form.is_valid(): 1423 logger.debug("Create new: %s" % (str(form.cleaned_data))) 1424 name = form.cleaned_data['name'] 1425 description = form.cleaned_data['description'] 1426 folder_type = request.REQUEST.get('folder_type') 1427 if folder_type == "dataset": 1428 oid = manager.createDataset(name,description, img_ids=request.REQUEST.get('img_ids', None)) 1429 else: 1430 oid = getattr(manager, "create"+folder_type.capitalize())(name, description) 1431 rdict = {'bad':'false', 'id': oid} 1432 json = simplejson.dumps(rdict, ensure_ascii=False) 1433 return HttpResponse( json, mimetype='application/javascript') 1434 else: 1435 d = dict() 1436 for e in form.errors.iteritems(): 1437 d.update({e[0]:unicode(e[1])}) 1438 rdict = {'bad':'true','errs': d } 1439 json = simplejson.dumps(rdict, ensure_ascii=False) 1440 return HttpResponse( json, mimetype='application/javascript') 1441 else: 1442 return HttpResponseServerError("Object does not exist") 1443 elif action == 'edit': 1444 # form for editing an Object. E.g. Project etc. TODO: not used now? 1445 if o_type == "share" and o_id > 0: 1446 template = "webclient/public/share_form.html" 1447 manager.getMembers(o_id) 1448 manager.getComments(o_id) 1449 experimenters = list(conn.getExperimenters()) 1450 experimenters.sort(key=lambda x: x.getOmeName().lower()) 1451 initial={'message': manager.share.message, 'expiration': "", \ 1452 'shareMembers': manager.membersInShare, 'enable': manager.share.active, \ 1453 'experimenters': experimenters} 1454 if manager.share.getExpireDate() is not None: 1455 initial['expiration'] = manager.share.getExpireDate().strftime("%Y-%m-%d") 1456 form = ShareForm(initial=initial) #'guests': share.guestsInShare, 1457 context = {'share':manager, 'form':form} 1458 elif hasattr(manager, o_type) and o_id > 0: 1459 obj = getattr(manager, o_type) 1460 template = "webclient/data/container_form.html" 1461 form = ContainerForm(initial={'name': obj.name, 'description':obj.description}) 1462 context = {'manager':manager, 'form':form} 1463 elif action == 'save': 1464 # Handles submission of the 'edit' form above. TODO: not used now? 1465 if not request.method == 'POST': 1466 return HttpResponseRedirect(reverse("manage_action_containers", args=["edit", o_type, o_id])) 1467 if o_type == "share": 1468 experimenters = list(conn.getExperimenters()) 1469 experimenters.sort(key=lambda x: x.getOmeName().lower()) 1470 form = ShareForm(initial={'experimenters':experimenters}, data=request.REQUEST.copy()) 1471 if form.is_valid(): 1472 logger.debug("Update share: %s" % (str(form.cleaned_data))) 1473 message = form.cleaned_data['message'] 1474 expiration = form.cleaned_data['expiration'] 1475 members = form.cleaned_data['members'] 1476 #guests = request.REQUEST['guests'] 1477 enable = toBoolean(form.cleaned_data['enable']) 1478 host = request.build_absolute_uri(reverse("load_template", args=["public"])) 1479 manager.updateShareOrDiscussion(host, conn.server_id, message, members, enable, expiration) 1480 return HttpResponse("DONE") 1481 else: 1482 template = "webclient/public/share_form.html" 1483 context = {'share':manager, 'form':form} 1484 else: 1485 return HttpResponseServerError("Object does not exist") 1486 elif action == 'editname': 1487 # start editing 'name' in-line 1488 if hasattr(manager, o_type) and o_id > 0: 1489 obj = getattr(manager, o_type) 1490 if (o_type == "well"): 1491 obj = obj.getWellSample(index).image() 1492 template = "webclient/ajax_form/container_form_ajax.html" 1493 if o_type == "tag": 1494 txtValue = obj.textValue 1495 else: 1496 txtValue = obj.getName() 1497 form = ContainerNameForm(initial={'name': txtValue}) 1498 context = {'manager':manager, 'form':form} 1499 else: 1500 return HttpResponseServerError("Object does not exist") 1501 elif action == 'savename': 1502 # Save name edit in-line 1503 if not request.method == 'POST': 1504 return HttpResponseRedirect(reverse("manage_action_containers", args=["edit", o_type, o_id])) 1505 if hasattr(manager, o_type) and o_id > 0: 1506 form = ContainerNameForm(data=request.REQUEST.copy()) 1507 if form.is_valid(): 1508 logger.debug("Update name form:" + str(form.cleaned_data)) 1509 name = form.cleaned_data['name'] 1510 rdict = {'bad':'false', 'o_type': o_type} 1511 if (o_type == "well"): 1512 manager.image = manager.well.getWellSample(index).image() 1513 o_type = "image" 1514 manager.updateName(o_type, name) 1515 json = simplejson.dumps(rdict, ensure_ascii=False) 1516 return HttpResponse( json, mimetype='application/javascript') 1517 else: 1518 d = dict() 1519 for e in form.errors.iteritems(): 1520 d.update({e[0]:unicode(e[1])}) 1521 rdict = {'bad':'true','errs': d } 1522 json = simplejson.dumps(rdict, ensure_ascii=False) 1523 return HttpResponse( json, mimetype='application/javascript') 1524 else: 1525 return HttpResponseServerError("Object does not exist") 1526 elif action == 'editdescription': 1527 # start editing description in-line 1528 if hasattr(manager, o_type) and o_id > 0: 1529 obj = getattr(manager, o_type) 1530 if (o_type == "well"): 1531 obj = obj.getWellSample(index).image() 1532 template = "webclient/ajax_form/container_form_ajax.html" 1533 form = ContainerDescriptionForm(initial={'description': obj.description}) 1534 context = {'manager':manager, 'form':form} 1535 else: 1536 return HttpResponseServerError("Object does not exist") 1537 elif action == 'savedescription': 1538 # Save editing of description in-line 1539 if not request.method == 'POST': 1540 return HttpResponseServerError("Action '%s' on the '%s' id:%s cannot be complited" % (action, o_type, o_id)) 1541 if hasattr(manager, o_type) and o_id > 0: 1542 form = ContainerDescriptionForm(data=request.REQUEST.copy()) 1543 if form.is_valid(): 1544 logger.debug("Update name form:" + str(form.cleaned_data)) 1545 description = form.cleaned_data['description'] 1546 if (o_type == "well"): 1547 manager.image = manager.well.getWellSample(index).image() 1548 o_type = "image" 1549 manager.updateDescription(o_type, description) 1550 rdict = {'bad':'false' } 1551 json = simplejson.dumps(rdict, ensure_ascii=False) 1552 return HttpResponse( json, mimetype='application/javascript') 1553 else: 1554 d = dict() 1555 for e in form.errors.iteritems(): 1556 d.update({e[0]:unicode(e[1])}) 1557 rdict = {'bad':'true','errs': d } 1558 json = simplejson.dumps(rdict, ensure_ascii=False) 1559 return HttpResponse( json, mimetype='application/javascript') 1560 else: 1561 return HttpResponseServerError("Object does not exist") 1562 elif action == 'paste': 1563 # Handles 'paste' action from the jsTree. Destination in POST 1564 destination = request.REQUEST['destination'].split('-') 1565 rv = manager.paste(destination) 1566 if rv: 1567 rdict = {'bad':'true','errs': rv } 1568 json = simplejson.dumps(rdict, ensure_ascii=False) 1569 return HttpResponse( json, mimetype='application/javascript') 1570 else: 1571 rdict = {'bad':'false' } 1572 json = simplejson.dumps(rdict, ensure_ascii=False) 1573 return HttpResponse( json, mimetype='application/javascript') 1574 elif action == 'move': 1575 # Handles drag-and-drop moving of objects in jsTree. 1576 # Also handles 'remove' of Datasets (moves to 'Experimenter' parent) 1577 parent = request.REQUEST['parent'].split('-') 1578 #source = request.REQUEST['source'].split('-') 1579 destination = request.REQUEST['destination'].split('-') 1580 rv = None 1581 try: 1582 if parent[1] == destination[1]: 1583 rv = "Error: Cannot move to the same place." 1584 except Exception, x: 1585 rdict = {'bad':'true','errs': str(x) } 1586 else: 1587 if rv is None: 1588 rv = manager.move(parent,destination) 1589 if rv: 1590 rdict = {'bad':'true','errs': rv } 1591 else: 1592 rdict = {'bad':'false' } 1593 json = simplejson.dumps(rdict, ensure_ascii=False) 1594 return HttpResponse( json, mimetype='application/javascript') 1595 elif action == 'remove': 1596 # Handles 'remove' of Images from jsTree, removal of comment, tag from Object etc. 1597 parents = request.REQUEST['parent'] # E.g. image-123 or image-1|image-2 1598 try: 1599 manager.remove(parents.split('|'), index) 1600 except Exception, x: 1601 logger.error(traceback.format_exc()) 1602 rdict = {'bad':'true','errs': str(x) } 1603 json = simplejson.dumps(rdict, ensure_ascii=False) 1604 return HttpResponse( json, mimetype='application/javascript') 1605 1606 rdict = {'bad':'false' } 1607 json = simplejson.dumps(rdict, ensure_ascii=False) 1608 return HttpResponse( json, mimetype='application/javascript') 1609 elif action == 'removefromshare': 1610 image_id = request.REQUEST.get('source') 1611 try: 1612 manager.removeImage(image_id) 1613 except Exception, x: 1614 logger.error(traceback.format_exc()) 1615 rdict = {'bad':'true','errs': str(x) } 1616 json = simplejson.dumps(rdict, ensure_ascii=False) 1617 return HttpResponse( json, mimetype='application/javascript') 1618 rdict = {'bad':'false' } 1619 json = simplejson.dumps(rdict, ensure_ascii=False) 1620 return HttpResponse( json, mimetype='application/javascript') 1621 elif action == 'delete': 1622 # Handles delete of a file attached to object. 1623 child = toBoolean(request.REQUEST.get('child')) 1624 anns = toBoolean(request.REQUEST.get('anns')) 1625 try: 1626 handle = manager.deleteItem(child, anns) 1627 request.session['callback'][str(handle)] = {'job_type': 'delete', 'delmany':False,'did':o_id, 'dtype':o_type, 'status':'in progress', 1628 'derror':0, 'dreport':_formatReport(handle), 'start_time': datetime.datetime.now()} 1629 request.session.modified = True 1630 except Exception, x: 1631 logger.error('Failed to delete: %r' % {'did':o_id, 'dtype':o_type}, exc_info=True) 1632 rdict = {'bad':'true','errs': str(x) } 1633 else: 1634 rdict = {'bad':'false' } 1635 json = simplejson.dumps(rdict, ensure_ascii=False) 1636 return HttpResponse( json, mimetype='application/javascript') 1637 elif action == 'deletemany': 1638 # Handles multi-delete from jsTree. 1639 object_ids = {'Image':request.REQUEST.getlist('image'), 'Dataset':request.REQUEST.getlist('dataset'), 'Project':request.REQUEST.getlist('project'), 'Screen':request.REQUEST.getlist('screen'), 'Plate':request.REQUEST.getlist('plate'), 'Well':request.REQUEST.getlist('well'), 'PlateAcquisition':request.REQUEST.getlist('acquisition')} 1640 child = toBoolean(request.REQUEST.get('child')) 1641 anns = toBoolean(request.REQUEST.get('anns')) 1642 logger.debug("Delete many: child? %s anns? %s object_ids %s" % (child, anns, object_ids)) 1643 try: 1644 for key,ids in object_ids.iteritems(): 1645 if ids is not None and len(ids) > 0: 1646 handle = manager.deleteObjects(key, ids, child, anns) 1647 dMap = {'job_type': 'delete', 'start_time': datetime.datetime.now(),'status':'in progress', 'derrors':0, 1648 'dreport':_formatReport(handle), 'dtype':key} 1649 if len(ids) > 1: 1650 dMap['delmany'] = len(ids) 1651 dMap['did'] = ids 1652 else: 1653 dMap['delmany'] = False 1654 dMap['did'] = ids[0] 1655 request.session['callback'][str(handle)] = dMap 1656 request.session.modified = True 1657 except Exception, x: 1658 logger.error('Failed to delete: %r' % {'did':ids, 'dtype':key}, exc_info=True) 1659 rdict = {'bad':'true','errs': str(x) } 1660 else: 1661 rdict = {'bad':'false' } 1662 json = simplejson.dumps(rdict, ensure_ascii=False) 1663 return HttpResponse( json, mimetype='application/javascript') 1664 context['template'] = template 1665 return context
1666
1667 @login_required(doConnectionCleanup=False) 1668 -def get_original_file(request, fileId, conn=None, **kwargs):
1669 """ Returns the specified original file as an http response. Used for displaying text or png/jpeg etc files in browser """ 1670 1671 # May be viewing results of a script run in a different group. 1672 conn.SERVICE_OPTS.setOmeroGroup(-1) 1673 1674 orig_file = conn.getObject("OriginalFile", fileId) 1675 if orig_file is None: 1676 return handlerInternalError(request, "Original File does not exists (id:%s)." % (fileId)) 1677 1678 rsp = ConnCleaningHttpResponse(orig_file.getFileInChunks()) 1679 rsp.conn = conn 1680 mimetype = orig_file.mimetype 1681 if mimetype == "text/x-python": 1682 mimetype = "text/plain" # allows display in browser 1683 rsp['Content-Type'] = mimetype 1684 rsp['Content-Length'] = orig_file.getSize() 1685 #rsp['Content-Disposition'] = 'attachment; filename=%s' % (orig_file.name.replace(" ","_")) 1686 return rsp
1687
1688 1689 @login_required() 1690 -def image_as_map(request, imageId, conn=None, **kwargs):
1691 """ Converts OMERO image into mrc.map file (using tiltpicker utils) and returns the file """ 1692 1693 from omero_ext.tiltpicker.pyami import mrc 1694 from numpy import dstack, zeros, int8 1695 1696 image = conn.getObject("Image", imageId) 1697 if image is None: 1698 message = "Image ID %s not found in image_as_map" % imageId 1699 logger.error(message) 1700 return handlerInternalError(request, message) 1701 1702 imageName = image.getName() 1703 downloadName = imageName.endswith(".map") and imageName or "%s.map" % imageName 1704 pixels = image.getPrimaryPixels() 1705 1706 # get a list of numpy planes and make stack 1707 zctList = [(z,0,0) for z in range(image.getSizeZ())] 1708 npList = list(pixels.getPlanes(zctList)) 1709 npStack = dstack(npList) 1710 logger.info("Numpy stack for image_as_map: dtype: %s, range %s-%s" % (npStack.dtype.name, npStack.min(), npStack.max()) ) 1711 1712 # OAV only supports 'float' and 'int8'. Convert anything else to int8 1713 if pixels.getPixelsType().value != 'float' or ('8bit' in kwargs and kwargs['8bit']): 1714 #scale from -127 -> 128 and conver to 8 bit integer 1715 npStack = npStack - npStack.min() # start at 0 1716 npStack = (npStack * 255.0 / npStack.max()) - 127 # range - 127 -> 128 1717 a = zeros(npStack.shape, dtype=int8) 1718 npStack = npStack.round(out=a) 1719 1720 if "maxSize" in kwargs and int(kwargs["maxSize"]) > 0: 1721 sz = int(kwargs["maxSize"]) 1722 targetSize = sz * sz * sz 1723 # if available, use scipy.ndimage to resize 1724 if npStack.size > targetSize: 1725 try: 1726 import scipy.ndimage 1727 from numpy import round 1728 factor = float(targetSize)/ npStack.size 1729 factor = pow(factor,1.0/3) 1730 logger.info("Resizing numpy stack %s by factor of %s" % (npStack.shape, factor)) 1731 npStack = round(scipy.ndimage.interpolation.zoom(npStack, factor), 1) 1732 except ImportError: 1733 logger.info("Failed to import scipy.ndimage for interpolation of 'image_as_map'. Full size: %s" % str(npStack.shape)) 1734 pass 1735 1736 header = {} 1737 # Sometimes causes scaling issues in OAV. 1738 #header["xlen"] = pixels.physicalSizeX * image.getSizeX() 1739 #header["ylen"] = pixels.physicalSizeY * image.getSizeY() 1740 #header["zlen"] = pixels.physicalSizeZ * image.getSizeZ() 1741 #if header["xlen"] == 0 or header["ylen"] == 0 or header["zlen"] == 0: 1742 #header = {} 1743 1744 # write mrc.map to temp file 1745 import tempfile 1746 temp = tempfile.NamedTemporaryFile(suffix='.map') 1747 try: 1748 mrc.write(npStack, temp.name, header) 1749 logger.debug("download file: %r" % {'name':temp.name, 'size':temp.tell()}) 1750 originalFile_data = FileWrapper(temp) 1751 rsp = HttpResponse(originalFile_data) 1752 rsp['Content-Type'] = 'application/force-download' 1753 #rsp['Content-Length'] = temp.tell() 1754 rsp['Content-Length'] =os.path.getsize(temp.name) 1755 rsp['Content-Disposition'] = 'attachment; filename=%s' % downloadName 1756 temp.seek(0) 1757 except Exception, x: 1758 temp.close() 1759 logger.error(traceback.format_exc()) 1760 return handlerInternalError(request, "Cannot generate map (id:%s)." % (imageId)) 1761 return rsp
1762
1763 1764 @login_required(doConnectionCleanup=False) 1765 -def download_annotation(request, annId, conn=None, **kwargs):
1766 """ Returns the file annotation as an http response for download """ 1767 ann = conn.getObject("Annotation", annId) 1768 if ann is None: 1769 return handlerInternalError(request, "Annotation does not exist (id:%s)." % (annId)) 1770 1771 rsp = ConnCleaningHttpResponse(ann.getFileInChunks()) 1772 rsp.conn = conn 1773 rsp['Content-Type'] = 'application/force-download' 1774 rsp['Content-Length'] = ann.getFileSize() 1775 rsp['Content-Disposition'] = 'attachment; filename=%s' % (ann.getFileName().replace(" ","_")) 1776 return rsp
1777
1778 1779 @login_required() 1780 -def download_orig_metadata(request, imageId, conn=None, **kwargs):
1781 """ Downloads the 'Original Metadata' as a text file """ 1782 1783 image = conn.getObject("Image", imageId) 1784 if image is None: 1785 raise Http404("No Image found with ID %s" % imageId) 1786 1787 om = image.loadOriginalMetadata() 1788 1789 txtLines = ["[Global Metadata]"] 1790 txtLines.extend( ["%s=%s" % (kv[0], kv[1]) for kv in om[1]] ) 1791 1792 txtLines.append("[Series Metadata]") 1793 txtLines.extend( ["%s=%s" % (kv[0], kv[1]) for kv in om[2]] ) 1794 rspText = "\n".join(txtLines) 1795 1796 rsp = HttpResponse(rspText) 1797 rsp['Content-Type'] = 'application/force-download' 1798 rsp['Content-Length'] = len(rspText) 1799 rsp['Content-Disposition'] = 'attachment; filename=Original_Metadata.txt' 1800 return rsp
1801
1802 1803 @login_required() 1804 @render_response() 1805 -def load_public(request, share_id=None, conn=None, **kwargs):
1806 """ Loads data for the tree in the 'public' main page. """ 1807 1808 # SUBTREE TODO: 1809 if share_id is None: 1810 share_id = request.REQUEST.get("o_id") is not None and long(request.REQUEST.get("o_id")) or None 1811 1812 # check view 1813 view = request.REQUEST.get("view") 1814 1815 if share_id is not None: 1816 if view == 'tree': 1817 template = "webclient/public/share_subtree.html" 1818 elif view == 'icon': 1819 template = "webclient/public/share_content_icon.html" 1820 controller = BaseShare(conn, share_id) 1821 controller.loadShareContent() 1822 1823 else: 1824 template = "webclient/public/share_tree.html" 1825 controller = BaseShare(conn) 1826 controller.getShares() 1827 1828 context = {'share':controller} 1829 context['isLeader'] = conn.isLeader() 1830 context['template'] = template 1831 return context
1832
1833 ################################################################## 1834 # Basket 1835 1836 @login_required(setGroupContext=True) 1837 @render_response() 1838 -def basket_action (request, action=None, conn=None, **kwargs):
1839 """ 1840 Various actions for creating a 'share' or 'discussion' (no images). 1841 1842 @param action: 'toshare', 'createshare' (form to create share and handling the action itself) 1843 'todiscuss', 'createdisc' (form to create discussion and handling the action itself) 1844 """ 1845 1846 if action == "toshare": 1847 template = "webclient/basket/basket_share_action.html" 1848 basket = BaseBasket(conn) 1849 basket.load_basket(request) 1850 experimenters = list(conn.getExperimenters()) 1851 experimenters.sort(key=lambda x: x.getOmeName().lower()) 1852 selected = [long(i) for i in request.REQUEST.getlist('image')] 1853 form = BasketShareForm(initial={'experimenters':experimenters, 'images':basket.imageInBasket, 'enable':True, 'selected':selected}) 1854 context = {'form':form} 1855 elif action == "createshare": 1856 if not request.method == 'POST': 1857 return HttpResponseRedirect(reverse("basket_action")) 1858 basket = BaseBasket(conn) 1859 basket.load_basket(request) 1860 experimenters = list(conn.getExperimenters()) 1861 experimenters.sort(key=lambda x: x.getOmeName().lower()) 1862 form = BasketShareForm(initial={'experimenters':experimenters, 'images':basket.imageInBasket}, data=request.REQUEST.copy()) 1863 if form.is_valid(): 1864 images = form.cleaned_data['image'] 1865 message = form.cleaned_data['message'] 1866 expiration = form.cleaned_data['expiration'] 1867 members = form.cleaned_data['members'] 1868 #guests = request.REQUEST['guests'] 1869 enable = toBoolean(form.cleaned_data['enable']) 1870 host = request.build_absolute_uri(reverse("load_template", args=["public"])) 1871 share = BaseShare(conn) 1872 share.createShare(host, conn.server_id, images, message, members, enable, expiration) 1873 return HttpResponse("success") 1874 else: 1875 template = "webclient/basket/basket_share_action.html" 1876 context = {'form':form} 1877 elif action == "todiscuss": 1878 template = "webclient/basket/basket_discussion_action.html" 1879 basket = BaseBasket(conn) 1880 experimenters = list(conn.getExperimenters()) 1881 experimenters.sort(key=lambda x: x.getOmeName().lower()) 1882 form = ShareForm(initial={'experimenters':experimenters, 'enable':True}) 1883 context = {'form':form} 1884 elif action == "createdisc": 1885 if not request.method == 'POST': 1886 return HttpResponseRedirect(reverse("basket_action")) 1887 basket = BaseBasket(conn) 1888 experimenters = list(conn.getExperimenters()) 1889 experimenters.sort(key=lambda x: x.getOmeName().lower()) 1890 form = ShareForm(initial={'experimenters':experimenters}, data=request.REQUEST.copy()) 1891 if form.is_valid(): 1892 message = form.cleaned_data['message'] 1893 expiration = form.cleaned_data['expiration'] 1894 members = form.cleaned_data['members'] 1895 #guests = request.REQUEST['guests'] 1896 enable = toBoolean(form.cleaned_data['enable']) 1897 host = request.build_absolute_uri(reverse("load_template", args=["public"])) 1898 share = BaseShare(conn) 1899 share.createDiscussion(host, conn.server_id, message, members, enable, expiration) 1900 return HttpResponse("success") 1901 else: 1902 template = "webclient/basket/basket_discussion_action.html" 1903 context = {'form':form} 1904 else: 1905 template = kwargs.get("template", "webclient/basket/basket.html") 1906 1907 basket = BaseBasket(conn) 1908 basket.load_basket(request) 1909 1910 context = {'basket':basket } 1911 context['template'] = template 1912 return context
1913
1914 @login_required() 1915 -def empty_basket(request, **kwargs):
1916 """ Empty the basket of images """ 1917 1918 try: 1919 del request.session['imageInBasket'] 1920 del request.session['basket_counter'] 1921 except KeyError: 1922 logger.error(traceback.format_exc()) 1923 1924 return HttpResponseRedirect(reverse("basket_action"))
1925
1926 @login_required() 1927 -def update_basket(request, **kwargs):
1928 """ Add or remove images to the set in the basket """ 1929 1930 action = None 1931 if request.method == 'POST': 1932 request.session.modified = True 1933 try: 1934 action = request.REQUEST['action'] 1935 except Exception, x: 1936 logger.error(traceback.format_exc()) 1937 return handlerInternalError(request, "Attribute error: 'action' is missed.") 1938 else: 1939 prod = request.REQUEST.get('productId') 1940 ptype = request.REQUEST.get('productType') 1941 if action == 'add': 1942 images = request.REQUEST.getlist('image') 1943 #datasets = request.REQUEST.getlist('datasets') 1944 for i in images: 1945 flag = False 1946 for item in request.session['imageInBasket']: 1947 if item == long(i): 1948 flag = True 1949 break 1950 if not flag: 1951 request.session['imageInBasket'].add(long(i)) 1952 #for i in datasets: 1953 # flag = False 1954 # for item in request.session['datasetInBasket']: 1955 # if item == long(i): 1956 # flag = True 1957 # break 1958 # if not flag: 1959 # request.session['datasetInBasket'].append(long(i)) 1960 elif action == 'del': 1961 if ptype == 'image': 1962 try: 1963 request.session['imageInBasket'].remove(long(prod)) 1964 except: 1965 rv = "Error: could not remove image from the basket." 1966 return HttpResponse(rv) 1967 #elif ptype == 'dataset': 1968 # try: 1969 # request.session['datasetInBasket'].remove(prod) 1970 # except: 1971 # rv = "Error: could not remove image from the basket." 1972 # return HttpResponse(rv) 1973 else: 1974 rv = "Error: This action is not available" 1975 return HttpResponse(rv) 1976 elif action == 'delmany': 1977 images = [long(i) for i in request.REQUEST.getlist('image')] 1978 for i in images: 1979 if i in request.session['imageInBasket']: 1980 request.session['imageInBasket'].remove(long(i)) 1981 else: 1982 rv = "Error: could not remove image from the basket." 1983 return HttpResponse(rv) 1984 1985 total = len(request.session['imageInBasket'])#+len(request.session['datasetInBasket']) 1986 request.session['basket_counter'] = total 1987 return HttpResponse(total) 1988 else: 1989 return handlerInternalError(request, "Request method error in Basket.")
1990
1991 @login_required(setGroupContext=True) 1992 @render_response() 1993 -def load_calendar(request, year=None, month=None, conn=None, **kwargs):
1994 """ 1995 Loads the calendar which is displayed in the left panel of the history page. 1996 Shows current month by default. Filter by experimenter 1997 """ 1998 1999 template = "webclient/history/calendar.html" 2000 filter_user_id = request.session.get('user_id') 2001 2002 if year is not None and month is not None: 2003 controller = BaseCalendar(conn=conn, year=year, month=month, eid=filter_user_id) 2004 else: 2005 today = datetime.datetime.today() 2006 controller = BaseCalendar(conn=conn, year=today.year, month=today.month, eid=filter_user_id) 2007 controller.create_calendar() 2008 2009 context = {'controller':controller} 2010 2011 context['template'] = template 2012 return context
2013
2014 2015 @login_required(setGroupContext=True) 2016 @render_response() 2017 -def load_history(request, year, month, day, conn=None, **kwargs):
2018 """ The data for a particular date that is loaded into the center panel """ 2019 2020 template = "webclient/history/history_details.html" 2021 2022 # get page 2023 page = int(request.REQUEST.get('page', 1)) 2024 2025 filter_user_id = request.session.get('user_id') 2026 controller = BaseCalendar(conn=conn, year=year, month=month, day=day, eid=filter_user_id) 2027 controller.get_items(page) 2028 2029 context = {'controller':controller} 2030 context['template'] = template 2031 return context
2032
2033 2034 -def getObjectUrl(conn, obj):
2035 """ 2036 This provides a url to browse to the specified omero.model.ObjectI P/D/I, S/P, FileAnnotation etc. 2037 used to display results from the scripting service 2038 E.g webclient/userdata/?path=image-12601 2039 If the object is a file annotation, try to browse to the parent P/D/I 2040 """ 2041 base_url = reverse(viewname="load_template", args=['userdata']) 2042 2043 blitz_obj = None 2044 url = None 2045 # if we have a File Annotation, then we want our URL to be for the parent object... 2046 if isinstance(obj, omero.model.FileAnnotationI): 2047 fa = conn.getObject("Annotation", obj.id.val) 2048 for ptype in ['project', 'dataset', 'image']: 2049 links = list(fa.getParentLinks(ptype)) 2050 if len(links) > 0: 2051 obj = links[0].parent 2052 break 2053 2054 if obj.__class__.__name__ in ("ImageI", "DatasetI", "ProjectI", "ScreenI", "PlateI"): 2055 otype = obj.__class__.__name__[:-1].lower() 2056 base_url += "?show=%s-%s" % (otype, obj.id.val) 2057 return base_url
2058
2059 2060 ###################### 2061 # Activities window & Progressbar 2062 @login_required() 2063 @render_response() 2064 -def activities(request, conn=None, **kwargs):
2065 """ 2066 This refreshes callback handles (delete, scripts, chgrp etc) and provides html to update Activities window & Progressbar. 2067 The returned html contains details for ALL callbacks in web session, regardless of their status. 2068 We also add counts of jobs, failures and 'in progress' to update status bar. 2069 """ 2070 2071 in_progress = 0 2072 failure = 0 2073 new_results = [] 2074 _purgeCallback(request) 2075 2076 2077 # test each callback for failure, errors, completion, results etc 2078 for cbString in request.session.get('callback').keys(): 2079 job_type = request.session['callback'][cbString]['job_type'] 2080 2081 status = request.session['callback'][cbString]['status'] 2082 if status == "failed": 2083 failure+=1 2084 2085 request.session.modified = True 2086 2087 # update chgrp 2088 if job_type == 'chgrp': 2089 if status not in ("failed", "finished"): 2090 rsp = None 2091 try: 2092 prx = omero.cmd.HandlePrx.checkedCast(conn.c.ic.stringToProxy(cbString)) 2093 rsp = prx.getResponse() 2094 close_handle = False 2095 try: 2096 # if response is None, then we're still in progress, otherwise... 2097 if rsp is not None: 2098 close_handle = True 2099 new_results.append(cbString) 2100 if isinstance(rsp, omero.cmd.ERR): 2101 request.session['callback'][cbString]['status'] = "failed" 2102 rsp_params = ", ".join(["%s: %s" % (k,v) for k,v in rsp.parameters.items()]) 2103 logger.error("chgrp failed with: %s" % rsp_params) 2104 request.session['callback'][cbString]['error'] = "%s %s" % (rsp.name, rsp_params) 2105 elif isinstance(rsp, omero.cmd.OK): 2106 request.session['callback'][cbString]['status'] = "finished" 2107 else: 2108 in_progress+=1 2109 finally: 2110 prx.close(close_handle) 2111 except: 2112 logger.info("Activities chgrp handle not found: %s" % cbString) 2113 continue 2114 2115 # update delete 2116 elif job_type == 'delete': 2117 if status not in ("failed", "finished"): 2118 try: 2119 handle = omero.cmd.HandlePrx.checkedCast(conn.c.ic.stringToProxy(cbString)) 2120 cb = omero.callbacks.CmdCallbackI(conn.c, handle) 2121 close_handle = False 2122 try: 2123 if not cb.block(0): # Response not available 2124 request.session['callback'][cbString]['derror'] = 0 2125 request.session['callback'][cbString]['status'] = "in progress" 2126 request.session['callback'][cbString]['dreport'] = _formatReport(handle) 2127 in_progress+=1 2128 else: # Response available 2129 close_handle = True 2130 err = isinstance(cb.getResponse(), omero.cmd.ERR) 2131 new_results.append(cbString) 2132 if err: 2133 request.session['callback'][cbString]['derror'] = 1 2134 request.session['callback'][cbString]['status'] = "failed" 2135 request.session['callback'][cbString]['dreport'] = _formatReport(handle) 2136 failure+=1 2137 else: 2138 request.session['callback'][cbString]['derror'] = 0 2139 request.session['callback'][cbString]['status'] = "finished" 2140 request.session['callback'][cbString]['dreport'] = _formatReport(handle) 2141 finally: 2142 cb.close(close_handle) 2143 except Ice.ObjectNotExistException, e: 2144 request.session['callback'][cbString]['derror'] = 0 2145 request.session['callback'][cbString]['status'] = "finished" 2146 request.session['callback'][cbString]['dreport'] = None 2147 except Exception, x: 2148 logger.error(traceback.format_exc()) 2149 logger.error("Status job '%s'error:" % cbString) 2150 request.session['callback'][cbString]['derror'] = 1 2151 request.session['callback'][cbString]['status'] = "failed" 2152 request.session['callback'][cbString]['dreport'] = str(x) 2153 failure+=1 2154 2155 # update scripts 2156 elif job_type == 'script': 2157 # if error on runScript, the cbString is not a ProcessCallback... 2158 if not cbString.startswith('ProcessCallback'): continue # ignore 2159 if status not in ("failed", "finished"): 2160 logger.info("Check callback on script: %s" % cbString) 2161 proc = omero.grid.ScriptProcessPrx.checkedCast(conn.c.ic.stringToProxy(cbString)) 2162 cb = omero.scripts.ProcessCallbackI(conn.c, proc) 2163 # check if we get something back from the handle... 2164 if cb.block(0): # ms. 2165 cb.close() 2166 try: 2167 results = proc.getResults(0, conn.SERVICE_OPTS) # we can only retrieve this ONCE - must save results 2168 request.session['callback'][cbString]['status'] = "finished" 2169 new_results.append(cbString) 2170 except Exception, x: 2171 logger.error(traceback.format_exc()) 2172 continue 2173 # value could be rstring, rlong, robject 2174 rMap = {} 2175 for key, value in results.items(): 2176 v = value.getValue() 2177 if key in ("stdout", "stderr", "Message"): 2178 if key in ('stderr', 'stdout'): 2179 v = v.id.val # just save the id of original file 2180 request.session['callback'][cbString][key] = v 2181 else: 2182 if hasattr(v, "id"): # do we have an object (ImageI, FileAnnotationI etc) 2183 obj_data = {'id': v.id.val, 'type': v.__class__.__name__[:-1]} 2184 obj_data['browse_url'] = getObjectUrl(conn, v) 2185 if v.isLoaded() and hasattr(v, "file"): 2186 #try: 2187 mimetypes = {'image/png':'png', 'image/jpeg':'jpeg', 'text/plain': 'text'} 2188 if v.file.mimetype.val in mimetypes: 2189 obj_data['fileType'] = mimetypes[v.file.mimetype.val] 2190 obj_data['fileId'] = v.file.id.val 2191 obj_data['name'] = v.file.name.val 2192 #except: 2193 # pass 2194 if v.isLoaded() and hasattr(v, "name"): # E.g Image, OriginalFile etc 2195 obj_data['name'] = v.name.val 2196 rMap[key] = obj_data 2197 else: 2198 rMap[key] = v 2199 request.session['callback'][cbString]['results'] = rMap 2200 else: 2201 in_progress+=1 2202 2203 # having updated the request.session, we can now prepare the data for http response 2204 rv = {} 2205 for cbString in request.session.get('callback').keys(): 2206 # make a copy of the map in session, so that we can replace non json-compatible objects, without modifying session 2207 rv[cbString] = copy.copy(request.session['callback'][cbString]) 2208 2209 # return json (not used now, but still an option) 2210 if 'template' in kwargs and kwargs['template'] == 'json': 2211 for cbString in request.session.get('callback').keys(): 2212 rv[cbString]['start_time'] = str(request.session['callback'][cbString]['start_time']) 2213 rv['inprogress'] = in_progress 2214 rv['failure'] = failure 2215 rv['jobs'] = len(request.session['callback']) 2216 return HttpResponse(simplejson.dumps(rv),mimetype='application/javascript') # json 2217 2218 jobs = [] 2219 for key, data in rv.items(): 2220 # E.g. key: ProcessCallback/39f77932-c447-40d8-8f99-910b5a531a25 -t:tcp -h 10.211.55.2 -p 54727:tcp -h 10.37.129.2 -p 54727:tcp -h 10.12.2.21 -p 54727 2221 # create id we can use as html id, E.g. 39f77932-c447-40d8-8f99-910b5a531a25 2222 if len(key.split(" ")) > 0: 2223 htmlId = key.split(" ")[0] 2224 if len(htmlId.split("/")) > 1: 2225 htmlId = htmlId.split("/")[1] 2226 rv[key]['id'] = htmlId 2227 rv[key]['key'] = key 2228 if key in new_results: 2229 rv[key]['new'] = True 2230 jobs.append(rv[key]) 2231 2232 jobs.sort(key=lambda x:x['start_time'], reverse=True) 2233 context = {'sizeOfJobs':len(request.session['callback']), 2234 'jobs':jobs, 2235 'inprogress':in_progress, 2236 'new_results':len(new_results), 2237 'failure':failure} 2238 2239 context['template'] = "webclient/activities/activitiesContent.html" 2240 return context
2241
2242 2243 @login_required() 2244 -def activities_update (request, action, **kwargs):
2245 """ 2246 If the above 'action' == 'clean' then we clear jobs from request.session['callback'] 2247 either a single job (if 'jobKey' is specified in POST) or all jobs (apart from those in progress) 2248 """ 2249 2250 request.session.modified = True 2251 2252 if action == "clean": 2253 if 'jobKey' in request.POST: 2254 jobId = request.POST.get('jobKey') 2255 rv = {} 2256 if jobId in request.session['callback']: 2257 del request.session['callback'][jobId] 2258 request.session.modified = True 2259 rv['removed'] = True 2260 else: 2261 rv['removed'] = False 2262 return HttpResponse(simplejson.dumps(rv),mimetype='application/javascript') 2263 else: 2264 for key, data in request.session['callback'].items(): 2265 if data['status'] != "in progress": 2266 del request.session['callback'][key] 2267 return HttpResponse("OK")
2268
2269 #################################################################################### 2270 # User Photo 2271 2272 @login_required() 2273 -def avatar(request, oid=None, conn=None, **kwargs):
2274 """ Returns the experimenter's photo """ 2275 photo = conn.getExperimenterPhoto(oid) 2276 return HttpResponse(photo, mimetype='image/jpeg')
2277
2278 #################################################################################### 2279 # webgateway extention 2280 2281 @login_required() 2282 -def image_viewer (request, iid, share_id=None, **kwargs):
2283 """ Delegates to webgateway, using share connection if appropriate """ 2284 kwargs['viewport_server'] = share_id is not None and reverse("webindex")+share_id or reverse("webindex") 2285 kwargs['viewport_server'] = kwargs['viewport_server'].rstrip('/') # remove any trailing slash 2286 return webgateway_views.full_viewer(request, iid, **kwargs)
2287
2288 2289 #################################################################################### 2290 # scripting service.... 2291 @login_required() 2292 @render_response() 2293 -def list_scripts (request, conn=None, **kwargs):
2294 """ List the available scripts - Just officical scripts for now """ 2295 scriptService = conn.getScriptService() 2296 scripts = scriptService.getScripts() 2297 2298 # group scripts into 'folders' (path), named by parent folder name 2299 scriptMenu = {} 2300 for s in scripts: 2301 scriptId = s.id.val 2302 path = s.path.val 2303 name = s.name.val 2304 fullpath = os.path.join(path, name) 2305 if fullpath in settings.SCRIPTS_TO_IGNORE: 2306 logger.info('Ignoring script %r' % fullpath) 2307 continue 2308 2309 # We want to build a hierarchical <ul> <li> structure 2310 # Each <ul> is a {}, each <li> is either a script 'name': <id> or directory 'name': {ul} 2311 2312 ul = scriptMenu 2313 dirs = fullpath.split("/"); 2314 for l, d in enumerate(dirs): 2315 if len(d) == 0: 2316 continue 2317 if d not in ul: 2318 # if last component in path: 2319 if l+1 == len(dirs): 2320 ul[d] = scriptId 2321 else: 2322 ul[d] = {} 2323 ul = ul[d] 2324 2325 # convert <ul> maps into lists and sort 2326 2327 def ul_to_list(ul): 2328 dir_list = [] 2329 for name, value in ul.items(): 2330 if isinstance(value, dict): 2331 # value is a directory 2332 dir_list.append({'name': name, 'ul': ul_to_list(value)}) 2333 else: 2334 dir_list.append({'name': name, 'id':value}) 2335 dir_list.sort(key=lambda x:x['name'].lower()) 2336 return dir_list
2337 2338 scriptList = ul_to_list(scriptMenu) 2339 2340 # If we have a single top-level directory, we can skip it 2341 if len(scriptList) == 1: 2342 scriptList = scriptList[0]['ul'] 2343 2344 return scriptList 2345
2346 2347 @login_required() 2348 @render_response() 2349 -def script_ui(request, scriptId, conn=None, **kwargs):
2350 """ 2351 Generates an html form for the parameters of a defined script. 2352 """ 2353 scriptService = conn.getScriptService() 2354 2355 try: 2356 params = scriptService.getParams(long(scriptId)) 2357 except Exception, ex: 2358 if ex.message.lower().startswith("no processor available"): 2359 return {'template':'webclient/scripts/no_processor.html', 'scriptId': scriptId} 2360 raise ex 2361 if params == None: 2362 return HttpResponse() 2363 2364 paramData = {} 2365 2366 paramData["id"] = long(scriptId) 2367 paramData["name"] = params.name.replace("_", " ") 2368 paramData["description"] = params.description 2369 paramData["authors"] = ", ".join([a for a in params.authors]) 2370 paramData["contact"] = params.contact 2371 paramData["version"] = params.version 2372 paramData["institutions"] = ", ".join([i for i in params.institutions]) 2373 2374 inputs = [] # use a list so we can sort by 'grouping' 2375 Data_TypeParam = None 2376 IDsParam = None 2377 for key, param in params.inputs.items(): 2378 i = {} 2379 i["name"] = key.replace("_", " ") 2380 i["key"] = key 2381 if not param.optional: 2382 i["required"] = True 2383 i["description"] = param.description 2384 if param.min: 2385 i["min"] = str(param.min.getValue()) 2386 if param.max: 2387 i["max"] = str(param.max.getValue()) 2388 if param.values: 2389 i["options"] = [v.getValue() for v in param.values.getValue()] 2390 if param.useDefault: 2391 i["default"] = unwrap(param.prototype) 2392 if isinstance(i["default"], omero.model.IObject): 2393 i["default"] = None 2394 pt = unwrap(param.prototype) 2395 if pt.__class__.__name__ == 'dict': 2396 i["map"] = True 2397 elif pt.__class__.__name__ == 'list': 2398 i["list"] = True 2399 if "default" in i: i["default"] = i["default"][0] 2400 elif pt.__class__ == type(True): 2401 i["boolean"] = True 2402 elif pt.__class__ == type(0) or pt.__class__ == type(long(0)): 2403 i["number"] = "number" # will stop the user entering anything other than numbers. 2404 elif pt.__class__ == type(float(0.0)): 2405 i["number"] = "float" 2406 2407 # if we got a value for this key in the page request, use this as default 2408 if request.REQUEST.get(key, None) is not None: 2409 i["default"] = request.REQUEST.get(key, None) 2410 2411 i["prototype"] = unwrap(param.prototype) # E.g "" (string) or [0] (int list) or 0.0 (float) 2412 i["grouping"] = param.grouping 2413 inputs.append(i) 2414 2415 if key == "IDs": IDsParam = i # remember these... 2416 if key == "Data_Type": Data_TypeParam = i 2417 inputs.sort(key=lambda i: i["grouping"]) 2418 2419 # if we have Data_Type param - use the request parameters to populate IDs 2420 if Data_TypeParam is not None and IDsParam is not None and "options" in Data_TypeParam: 2421 IDsParam["default"] = "" 2422 for dtype in Data_TypeParam["options"]: 2423 if request.REQUEST.get(dtype, None) is not None: 2424 Data_TypeParam["default"] = dtype 2425 IDsParam["default"] = request.REQUEST.get(dtype, "") 2426 break # only use the first match 2427 2428 # try to determine hierarchies in the groupings - ONLY handle 1 hierarchy level now (not recursive!) 2429 for i in range(len(inputs)): 2430 if len(inputs) <= i: # we may remove items from inputs as we go - need to check 2431 break 2432 param = inputs[i] 2433 grouping = param["grouping"] # E.g 03 2434 param['children'] = list() 2435 c = 1 2436 while len(inputs) > i+1: 2437 nextParam = inputs[i+1] 2438 nextGrp = inputs[i+1]["grouping"] # E.g. 03.1 2439 if nextGrp.split(".")[0] == grouping: 2440 param['children'].append(inputs[i+1]) 2441 inputs.pop(i+1) 2442 else: 2443 break 2444 2445 paramData["inputs"] = inputs 2446 2447 return {'template':'webclient/scripts/script_ui.html', 'paramData': paramData, 'scriptId': scriptId}
2448
2449 2450 @login_required(setGroupContext=True) # group ctx used for getting Tags etc. 2451 @render_response() 2452 -def figure_script(request, scriptName, conn=None, **kwargs):
2453 """ 2454 Show a UI for running figure scripts 2455 """ 2456 2457 imageIds = request.REQUEST.get('Image', None) # comma - delimited list 2458 datasetIds = request.REQUEST.get('Dataset', None) 2459 if imageIds is None and datasetIds is None: 2460 return HttpResponse("Need to specify /?Image=1,2 or /?Dataset=1,2") 2461 2462 def validateIds(dtype, ids): 2463 ints = [int(oid) for oid in ids.split(",")] 2464 validObjs = {} 2465 for obj in conn.getObjects(dtype, ints): 2466 validObjs[obj.id] = obj 2467 filteredIds = [iid for iid in ints if iid in validObjs.keys()] 2468 if len(filteredIds) == 0: 2469 raise Http404("No %ss found with IDs %s" % (dtype, ids)) 2470 return filteredIds, validObjs
2471 2472 context = {} 2473 2474 if imageIds is not None: 2475 imageIds, validImages = validateIds("Image", imageIds) 2476 context['idString'] = ",".join( [str(i) for i in imageIds] ) 2477 context['dtype'] = "Image" 2478 if datasetIds is not None: 2479 datasetIds, validDatasets = validateIds("Dataset", datasetIds) 2480 context['idString'] = ",".join( [str(i) for i in datasetIds] ) 2481 context['dtype'] = "Dataset" 2482 2483 if scriptName == "SplitView": 2484 scriptPath = "/omero/figure_scripts/Split_View_Figure.py" 2485 template = "webclient/scripts/split_view_figure.html" 2486 # Lookup Tags & Datasets (for row labels) 2487 imgDict = [] # A list of data about each image. 2488 for iId in imageIds: 2489 data = {'id':iId} 2490 img = validImages[iId] 2491 data['name'] = img.getName() 2492 tags = [ann.getTextValue() for ann in img.listAnnotations() if ann._obj.__class__ == omero.model.TagAnnotationI] 2493 data['tags'] = tags 2494 data['datasets'] = [d.getName() for d in img.listParents()] 2495 imgDict.append(data) 2496 2497 # Use the first image as a reference 2498 image = validImages[imageIds[0]] 2499 context['imgDict'] = imgDict 2500 context['image'] = image 2501 context['channels'] = image.getChannels() 2502 2503 elif scriptName == "Thumbnail": 2504 scriptPath = "/omero/figure_scripts/Thumbnail_Figure.py" 2505 template = "webclient/scripts/thumbnail_figure.html" 2506 #context['tags'] = BaseContainer(conn).getTagsByObject() # ALL tags 2507 2508 def loadImageTags(imageIds): 2509 tagLinks = conn.getAnnotationLinks("Image", parent_ids=imageIds) 2510 linkMap = {} # group tags. {imageId: [tags]} 2511 tagMap = {} 2512 for iId in imageIds: 2513 linkMap[iId] = [] 2514 for l in tagLinks: 2515 c = l.getChild() 2516 if c._obj.__class__ == omero.model.TagAnnotationI: 2517 tagMap[c.id] = c 2518 linkMap[l.getParent().id].append(c) 2519 imageTags = [] 2520 for iId in imageIds: 2521 imageTags.append({'id':iId, 'tags':linkMap[iId]}) 2522 tags = [] 2523 for tId, t in tagMap.items(): 2524 tags.append(t) 2525 return imageTags, tags 2526 2527 thumbSets = [] # multiple collections of images 2528 tags = [] 2529 figureName = "Thumbnail_Figure" 2530 if datasetIds is not None: 2531 for d in conn.getObjects("Dataset", datasetIds): 2532 figureName = d.getName() 2533 imgIds = [i.id for i in d.listChildren()] 2534 imageTags, ts = loadImageTags(imgIds) 2535 thumbSets.append({'name':d.getName(), 'imageTags': imageTags}) 2536 tags.extend(ts) 2537 else: 2538 imageTags, ts = loadImageTags(imageIds) 2539 thumbSets.append({'name':'images', 'imageTags': imageTags}) 2540 tags.extend(ts) 2541 figureName = conn.getObject("Image", imageIds[0]).getParent().getName() 2542 uniqueTagIds = set() # remove duplicates 2543 uniqueTags = [] 2544 for t in tags: 2545 if t.id not in uniqueTagIds: 2546 uniqueTags.append(t) 2547 uniqueTagIds.add(t.id) 2548 uniqueTags.sort(key=lambda x: x.getTextValue().lower()) 2549 context['thumbSets'] = thumbSets 2550 context['tags'] = uniqueTags 2551 context['figureName'] = figureName.replace(" ", "_") 2552 2553 scriptService = conn.getScriptService() 2554 scriptId = scriptService.getScriptID(scriptPath); 2555 if (scriptId < 0): 2556 raise AttributeError("No script found for path '%s'" % scriptPath) 2557 2558 context['template'] = template 2559 context['scriptId'] = scriptId 2560 return context 2561
2562 2563 @login_required() 2564 -def chgrp(request, conn=None, **kwargs):
2565 """ 2566 Moves data to a new group, using the chgrp queue. 2567 Handles submission of chgrp form: all data in POST. 2568 Adds the callback handle to the request.session['callback']['jobId'] 2569 """ 2570 2571 group_id = request.REQUEST.get('group_id', None) 2572 if group_id is None: 2573 raise AttributeError("chgrp: No group_id specified") 2574 group_id = long(group_id) 2575 2576 group = conn.getObject("ExperimenterGroup", group_id) 2577 target_id = request.REQUEST.get('target_id', None) # E.g. "dataset-234" 2578 container_id = target_id is not None and target_id.split("-")[1] or None 2579 dtypes = ["Project", "Dataset", "Image", "Screen", "Plate"] 2580 for dtype in dtypes: 2581 oids = request.REQUEST.get(dtype, None) 2582 if oids is not None: 2583 obj_ids = oids.split(",") 2584 logger.debug("chgrp to group:%s %s-%s" % (group_id, dtype, obj_ids)) 2585 handle = conn.chgrpObjects(dtype, obj_ids, group_id, container_id) 2586 jobId = str(handle) 2587 request.session['callback'][jobId] = { 2588 'job_type': "chgrp", 2589 'group': group.getName(), 2590 'dtype': dtype, 2591 'obj_ids': obj_ids, 2592 'job_name': "Change group", 2593 'start_time': datetime.datetime.now(), 2594 'status':'in progress'} 2595 request.session.modified = True 2596 2597 return HttpResponse("OK")
2598
2599 2600 @login_required(setGroupContext=True) 2601 -def script_run(request, scriptId, conn=None, **kwargs):
2602 """ 2603 Runs a script using values in a POST 2604 """ 2605 scriptService = conn.getScriptService() 2606 2607 inputMap = {} 2608 2609 sId = long(scriptId) 2610 2611 try: 2612 params = scriptService.getParams(sId) 2613 except Exception, x: 2614 if x.message and x.message.startswith("No processor available"): 2615 # Delegate to run_script() for handling 'No processor available' 2616 rsp = run_script(request, conn, sId, inputMap, scriptName='Script') 2617 return HttpResponse(simplejson.dumps(rsp), mimetype='json') 2618 else: 2619 raise 2620 params = scriptService.getParams(sId) 2621 scriptName = params.name.replace("_", " ").replace(".py", "") 2622 2623 logger.debug("Script: run with request.POST: %s" % request.POST) 2624 2625 for key, param in params.inputs.items(): 2626 prototype = param.prototype 2627 pclass = prototype.__class__ 2628 2629 # handle bool separately, since unchecked checkbox will not be in request.POST 2630 if pclass == omero.rtypes.RBoolI: 2631 value = key in request.POST 2632 inputMap[key] = pclass(value) 2633 continue 2634 2635 if pclass.__name__ == 'RMapI': 2636 keyName = "%s_key0" % key 2637 valueName = "%s_value0" % key 2638 row = 0 2639 paramMap = {} 2640 while keyName in request.POST: 2641 # the key and value don't have any data-type defined by scripts - just use string 2642 k = str(request.POST[keyName]) 2643 v = str(request.POST[valueName]) 2644 if len(k) > 0 and len(v) > 0: 2645 paramMap[str(k)] = str(v) 2646 row +=1 2647 keyName = "%s_key%d" % (key, row) 2648 valueName = "%s_value%d" % (key, row) 2649 if len(paramMap) > 0: 2650 inputMap[key] = wrap(paramMap) 2651 continue 2652 2653 if key in request.POST: 2654 if pclass == omero.rtypes.RListI: 2655 values = request.POST.getlist(key) 2656 if len(values) == 0: continue 2657 if len(values) == 1: # process comma-separated list 2658 if len(values[0]) == 0: continue 2659 values = values[0].split(",") 2660 2661 # try to determine 'type' of values in our list 2662 listClass = omero.rtypes.rstring 2663 l = prototype.val # list 2664 if len(l) > 0: # check if a value type has been set (first item of prototype list) 2665 listClass = l[0].__class__ 2666 if listClass == int(1).__class__: 2667 listClass = omero.rtypes.rint 2668 if listClass == long(1).__class__: 2669 listClass = omero.rtypes.rlong 2670 2671 # construct our list, using appropriate 'type' 2672 valueList = [] 2673 for v in values: 2674 try: 2675 obj = listClass(str(v.strip())) # convert unicode -> string 2676 except: 2677 logger.debug("Invalid entry for '%s' : %s" % (key, v)) 2678 continue 2679 if isinstance(obj, omero.model.IObject): 2680 valueList.append(omero.rtypes.robject(obj)) 2681 else: 2682 valueList.append(obj) 2683 inputMap[key] = omero.rtypes.rlist(valueList) 2684 2685 # Handle other rtypes: String, Long, Int etc. 2686 else: 2687 value = request.POST[key] 2688 if len(value) == 0: continue 2689 try: 2690 inputMap[key] = pclass(value) 2691 except: 2692 logger.debug("Invalid entry for '%s' : %s" % (key, value)) 2693 continue 2694 2695 logger.debug("Running script %s with params %s" % (scriptName, inputMap)) 2696 rsp = run_script(request, conn, sId, inputMap, scriptName) 2697 return HttpResponse(simplejson.dumps(rsp), mimetype='json')
2698
2699 2700 @login_required(setGroupContext=True) 2701 -def ome_tiff_script(request, imageId, conn=None, **kwargs):
2702 """ 2703 Uses the scripting service (Batch Image Export script) to generate OME-TIFF for an 2704 image and attach this as a file annotation to the image. 2705 Script will show up in the 'Activities' for users to monitor and download result etc. 2706 """ 2707 #if not request.method == 'POST': 2708 # return HttpResponse("Need to use POST") 2709 2710 scriptService = conn.getScriptService() 2711 sId = scriptService.getScriptID("/omero/export_scripts/Batch_Image_Export.py") 2712 2713 imageIds = [long(imageId)] 2714 inputMap = {'Data_Type': wrap('Image'), 'IDs': wrap(imageIds)} 2715 inputMap['Format'] = wrap('OME-TIFF') 2716 rsp = run_script(request, conn, sId, inputMap, scriptName='Create OME-TIFF') 2717 return HttpResponse(simplejson.dumps(rsp), mimetype='json')
2718
2719 2720 -def run_script(request, conn, sId, inputMap, scriptName='Script'):
2721 """ 2722 Starts running a script, adding details to the request.session so that it shows up 2723 in the webclient Activities panel and results are available there etc. 2724 """ 2725 request.session.modified = True 2726 scriptService = conn.getScriptService() 2727 try: 2728 handle = scriptService.runScript(sId, inputMap, None, conn.SERVICE_OPTS) 2729 # E.g. ProcessCallback/4ab13b23-22c9-4b5f-9318-40f9a1acc4e9 -t:tcp -h 10.37.129.2 -p 53154:tcp -h 10.211.55.2 -p 53154:tcp -h 10.12.1.230 -p 53154 2730 jobId = str(handle) 2731 status = 'in progress' 2732 request.session['callback'][jobId] = { 2733 'job_type': "script", 2734 'job_name': scriptName, 2735 'start_time': datetime.datetime.now(), 2736 'status':status} 2737 request.session.modified = True 2738 except Exception, x: 2739 jobId = str(time()) # E.g. 1312803670.6076391 2740 if x.message and x.message.startswith("No processor available"): # omero.ResourceError 2741 logger.info(traceback.format_exc()) 2742 error = "No Processor Available" 2743 status = 'no processor available' 2744 message = "" # template displays message and link 2745 else: 2746 logger.error(traceback.format_exc()) 2747 error = traceback.format_exc() 2748 status = 'failed' 2749 message = x.message 2750 # save the error to http session, for display in 'Activities' window 2751 request.session['callback'][jobId] = { 2752 'job_type': "script", 2753 'job_name': scriptName, 2754 'start_time': datetime.datetime.now(), 2755 'status':status, 2756 'Message': message, 2757 'error':error} 2758 return {'status': status, 'error': error} 2759 2760 return {'jobId': jobId, 'status': status}
2761
2762 @login_required() 2763 @render_response() 2764 -def ome_tiff_info(request, imageId, conn=None, **kwargs):
2765 """ 2766 Query to see if we have an OME-TIFF attached to the image (assume only 1, since Batch Image Export will delete old ones) 2767 """ 2768 # Any existing OME-TIFF will appear in list 2769 links = list( conn.getAnnotationLinks("Image", [imageId], ns=omero.constants.namespaces.NSOMETIFF) ) 2770 rv = {} 2771 if len(links) > 0: 2772 links.sort(key=lambda x: x.getId(), reverse=True) # use highest ID === most recent 2773 annlink = links[0] 2774 created = annlink.creationEventDate() 2775 annId = annlink.getChild().getId() 2776 from omeroweb.webgateway.templatetags.common_filters import ago 2777 download = reverse("download_annotation", args=[annId]) 2778 rv = {"created": str(created), "ago": ago(created), "id":annId, "download": download} 2779 return rv # will get returned as json by default
2780