def dryrun(self, token, arg): level=0; tnx=0; tny=0; tsz=512; vs = arg.split(',', 4) if len(vs)>0 and vs[0].isdigit(): level = int(vs[0]) if len(vs)>1 and vs[1].isdigit(): tnx = int(vs[1]) if len(vs)>2 and vs[2].isdigit(): tny = int(vs[2]) if len(vs)>3 and vs[3].isdigit(): tsz = int(vs[3]) dims = token.dims or {} width = dims.get('image_num_x', 0) height = dims.get('image_num_y', 0) if width<=tsz and height<=tsz: log.debug('Dryrun tile: Image is smaller than requested tile size, passing the whole image...') return token x = tnx * tsz y = tny * tsz if x>=width or y>=height: raise ImageServiceException(400, 'Tile: tile position outside of the image: %s,%s'%(tnx, tny)) # the new tile service does not change the number of z points in the image and if contains all z will perform the operation info = { 'image_num_x': tsz if width-x >= tsz else width-x, 'image_num_y': tsz if height-y >= tsz else height-y, #'image_num_z': 1, #'image_num_t': 1, } base_name = '%s.tiles'%(token.data) ofname = os.path.join(base_name, '%s_%.3d_%.3d_%.3d' % (tsz, level, tnx, tny)) return token.setImage(ofname, fmt=default_format, dims=info)
def action(self, token, arg): ang = arg.lower() angles = [ '0', '90', '-90', '270', '180', 'guess', 'flip_ud', 'mirror_lr' ] if ang == '270': ang = '-90' if ang not in angles: raise ImageServiceException( 400, 'rotate: angle value not yet supported') ifile = token.first_input_file() ofile = '%s.rotated_%s' % (token.data, ang) log.debug('Rotate %s: %s to %s', token.resource_id, ifile, ofile) if ang == '0': ofile = ifile dims = token.dims or {} w, h = compute_rotated_size(int(dims.get('image_num_x', 0)), int(dims.get('image_num_y', 0)), ang) info = { 'image_num_x': w, 'image_num_y': h, } command = ['-rotate', ang] if ang == 'flip_ud': command = ['-flip'] if ang == 'mirror_lr': command = ['-mirror'] return self.server.enqueue(token, 'rotate', ofile, fmt=default_format, command=command, dims=info)
def action(self, token, arg): arg = arg.lower() if arg not in ['min', 'max']: raise ImageServiceException( 400, 'IntensityProjection: parameter must be either "max" or "min"') ifile = token.first_input_file() ofile = '%s.iproject_%s' % (token.data, arg) log.debug('IntensityProjection %s: %s to %s with [%s]', token.resource_id, ifile, ofile, arg) if arg == 'max': command = ['-projectmax'] else: command = ['-projectmin'] info = { 'image_num_z': 1, 'image_num_t': 1, } return self.server.enqueue(token, 'intensityprojection', ofile, fmt=default_format, command=command, dims=info)
def action(self, token, arg): if not token.isFile(): raise ImageServiceException( 400, 'Rearrange3D: input is not an image...') log.debug('Rearrange3D %s: %s', token.resource_id, arg) arg = arg.lower() if arg not in ['xzy', 'yzx']: raise ImageServiceException( 400, 'Rearrange3D: method is unsupported: [%s]' % arg) # if the image must be 3D, either z stack or t series dims = token.dims or {} x = dims['image_num_x'] y = dims['image_num_y'] z = dims['image_num_z'] t = dims['image_num_t'] if (z > 1 and t > 1) or (z == 1 and t == 1): raise ImageServiceException( 400, 'Rearrange3D: only supports 3D images') nz = y if arg == 'xzy' else x info = { 'image_num_x': x if arg == 'xzy' else y, 'image_num_y': z, 'image_num_z': nz if z > 1 else 1, 'image_num_t': nz if t > 1 else 1, } ifile = token.first_input_file() ofile = '%s.rearrange3d_%s' % (token.data, arg) command = token.drainQueue() if not os.path.exists(ofile): command.extend(['-rearrange3d', '%s' % arg]) # dima: fix the case of a fliped output for if arg == 'yzx': command.extend(['-flip']) self.server.imageconvert(token, ifile, ofile, fmt=default_format, extra=command) return token.setImage(ofile, fmt=default_format, dims=info, input=ofile)
def action(self, token, arg): method = 'a' num_channels = 3 arg = arg.lower() if arg == 'display': args = ['-fusemeta'] argenc = 'display' elif arg == 'gray' or arg == 'grey': args = ['-fusegrey'] num_channels = 1 argenc = 'gray' else: args = ['-fusergb', arg] if ':' in arg: (arg, method) = arg.split(':', 1) elif '.' in arg: (arg, method) = arg.split('.', 1) argenc = ''.join([ hex(int(i)).replace('0x', '') for i in arg.replace(';', ',').split(',') if i is not '' ]) # test if all channels are valid dims = token.dims or {} img_num_c = misc.safeint(dims.get('image_num_c'), 0) groups = [i for i in arg.split(';') if i is not ''] # if there are more groups than input channels if len(groups) > img_num_c: groups = groups[img_num_c:] for i, g in enumerate(groups): channels = [misc.safeint(i, 0) for i in g.split(',')] # we can skip 0 weighted channels even if they are outside of the image channel range if max(channels) > 0: raise ImageServiceException( 400, 'Fuse: requested fusion of channel outside bounds %s>%s (%s)' % (img_num_c + i, img_num_c, g)) if method != 'a': args.extend(['-fusemethod', method]) info = { 'image_num_c': num_channels, } ifile = token.first_input_file() ofile = '%s.fuse_%s_%s' % (token.data, argenc, method) log.debug('Fuse %s: %s to %s with [%s:%s]', token.resource_id, ifile, ofile, arg, method) return self.server.enqueue(token, 'fuse', ofile, fmt=default_format, command=args, dims=info)
def action(self, token, arg): arg = arg.lower() args = arg.split(',') transform = args[0] params = args[1:] ifile = token.first_input_file() ofile = '%s.transform_%s'%(token.data, arg) log.debug('Transform %s: %s to %s with [%s]', token.resource_id, ifile, ofile, arg) if not transform in transforms: raise ImageServiceException(400, 'transform: requested transform is not yet supported') dims = token.dims or {} for n,v in transforms[transform]['require'].iteritems(): if v != dims.get(n): raise ImageServiceException(400, 'transform: input image is incompatible, %s must be %s but is %s'%(n, v, dims.get(n)) ) extra = copy.deepcopy(transforms[transform]['command']) if len(params)>0: extra.extend([','.join(params)]) info = copy.deepcopy(transforms[transform]['info']) return self.server.enqueue(token, 'transform', ofile, fmt=default_format, command=extra, dims=info)
def action(self, token, arg): arg = arg.lower() or 'avg' if arg not in ['odd', 'even', 'avg']: raise ImageServiceException( 400, 'Deinterlace: parameter must be either "odd", "even" or "avg"') ifile = token.first_input_file() ofile = '%s.deinterlace_%s' % (token.data, arg) log.debug('Deinterlace %s: %s to %s', token.resource_id, ifile, ofile) return self.server.enqueue(token, 'deinterlace', ofile, fmt=default_format, command=['-deinterlace', arg])
def action(self, token, arg): ms = ['f', 'd', 't', 'e', 'c', 'n', 'hounsfield'] ds = ['8', '16', '32', '64'] fs = ['u', 's', 'f'] fs_map = { 'u': 'unsigned integer', 's': 'signed integer', 'f': 'floating point' } cm = ['cs', 'cc'] granularity = ['patch', 'plane'] #, 'volume', 'whole'] d='d'; m='8'; f='u'; c='cs'; g='plane' arg = arg.lower() args = arg.split(',') if len(args)>0: d = args[0] if len(args)>1: m = args[1] if len(args)>2: f = args[2] or 'u' if len(args)>3: c = args[3] or 'cs' if m == 'hounsfield': if len(args)>4: window_center = args[4] or None if len(args)>5: window_width = args[5] or None else: if len(args)>4: g = args[4] or None if d is None or d not in ds: raise ImageServiceException(400, 'Depth: depth is unsupported: %s'%d) if m is None or m not in ms: raise ImageServiceException(400, 'Depth: method is unsupported: %s'%m ) if f is not None and f not in fs: raise ImageServiceException(400, 'Depth: format is unsupported: %s'%f ) if c is not None and c not in cm: raise ImageServiceException(400, 'Depth: channel mode is unsupported: %s'%c ) if g is not None and g not in granularity: raise ImageServiceException(400, 'Depth: granularity mode is unsupported: %s'%g ) if m == 'hounsfield' and (window_center is None or window_width is None): raise ImageServiceException(400, 'Depth: hounsfield enhancement requires window center and width' ) ifile = token.first_input_file() ofile = '%s.depth_%s'%(token.data, arg) log.debug('Depth %s: %s to %s with [%s]', token.resource_id, ifile, ofile, arg) extra=[] if m == 'hounsfield': extra.extend(['-hounsfield', '%s,%s,%s,%s'%(d,f,window_center,window_width)]) else: extra.extend(['-depth', arg]) if g == 'patch': token.histogram = None dims = { 'image_pixel_depth': d, 'image_pixel_format': fs_map[f], } return self.server.enqueue(token, 'depth', ofile, fmt=default_format, command=extra, dims=dims)
def action(self, token, arg): if not token.isFile(): raise ImageServiceException( 400, 'Pixelcounter: input is not an image...') arg = safeint(arg.lower(), 256) - 1 ifile = token.first_input_file() ofile = '%s.pixelcounter_%s.xml' % (token.data, arg) log.debug('Pixelcounter %s: %s to %s with [%s]', token.resource_id, ifile, ofile, arg) command = token.drainQueue() if not os.path.exists(ofile): command.extend(['-pixelcounts', str(arg)]) self.server.imageconvert(token, ifile, ofile, extra=command) return token.setXmlFile(fname=ofile)
def action(self, token, arg): if not token.isFile(): raise ImageServiceException(400, 'Histogram: input is not an image...') ifile = token.first_input_file() ofile = '%s.histogram.xml' % (token.data) log.debug('Histogram %s: %s to %s', token.resource_id, ifile, ofile) command = token.drainQueue() if not os.path.exists(ofile): # use resolution level if available to find the best estimate for very large images command.extend(['-ohstxml', ofile]) dims = token.dims or {} num_x = int(dims.get('image_num_x', 0)) num_y = int(dims.get('image_num_y', 0)) width = 1024 # image sizes good enough for histogram estimation height = 1024 # image sizes good enough for histogram estimation # if image has multiple resolution levels find the closest one and request it num_l = dims.get('image_num_resolution_levels', 1) if num_l > 1 and '-res-level' not in token.getQueue(): try: scales = [ float(i) for i in dims.get( 'image_resolution_level_scales', '').split(',') ] sizes = [(round(num_x * i), round(num_y * i)) for i in scales] relatives = [ max(width / float(sz[0]), height / float(sz[1])) for sz in sizes ] relatives = [i if i <= 1 else 0 for i in relatives] level = relatives.index(max(relatives)) command.extend(['-res-level', str(level)]) except (Exception): pass self.server.imageconvert(token, ifile, None, extra=command) return token.setXmlFile(fname=ofile)
def action(self, token, arg): arg = arg.lower() args = arg.split(',') if len(args) < 1: raise ImageServiceException( 400, 'Threshold: requires at least one parameter') method = 'both' if len(args) > 1: method = args[1] arg = '%s,%s' % (args[0], method) ifile = token.first_input_file() ofile = '%s.threshold_%s' % (token.data, arg) log.debug('Threshold %s: %s to %s with [%s]', token.resource_id, ifile, ofile, arg) return self.server.enqueue(token, 'threshold', ofile, fmt=default_format, command=['-threshold', arg])
def action(self, token, arg): if not arg: raise ImageServiceException( 400, 'SampleFrames: no frames to skip provided') ifile = token.first_input_file() ofile = '%s.framessampled_%s' % (token.data, arg) log.debug('SampleFrames %s: %s to %s with [%s]', token.resource_id, ifile, ofile, arg) info = { 'image_num_z': 1, 'image_num_t': int(token.dims.get('image_num_p', 0)) / int(arg), } return self.server.enqueue(token, 'sampleframes', ofile, fmt=default_format, command=['-sampleframes', arg], dims=info)
def action(self, token, arg): if not token.isFile(): raise ImageServiceException(400, 'Resize3D: input is not an image...') log.debug('Resize3D %s: %s', token.resource_id, arg) #size = tuple(map(int, arg.split(','))) ss = arg.split(',') size = [0, 0, 0] method = 'TC' aspectRatio = '' maxBounding = False textAddition = '' if len(ss) > 0 and ss[0].isdigit(): size[0] = int(ss[0]) if len(ss) > 1 and ss[1].isdigit(): size[1] = int(ss[1]) if len(ss) > 2 and ss[2].isdigit(): size[2] = int(ss[2]) if len(ss) > 3: method = ss[3].upper() if len(ss) > 4: textAddition = ss[4].upper() if len(ss) > 4 and (textAddition == 'AR'): aspectRatio = ',AR' if len(ss) > 4 and (textAddition == 'MX'): maxBounding = True aspectRatio = ',AR' if size[0] <= 0 and size[1] <= 0 and size[2] <= 0: raise ImageServiceException( 400, 'Resize3D: size is unsupported: [%s]' % arg) if method not in ['NN', 'TL', 'TC']: raise ImageServiceException( 400, 'Resize3D: method is unsupported: [%s]' % arg) # if the image is smaller and MX is used, skip resize dims = token.dims or {} w = dims.get('image_num_x', 0) h = dims.get('image_num_y', 0) z = dims.get('image_num_z', 1) t = dims.get('image_num_t', 1) d = max(z, t) if w == size[0] and h == size[1] and d == size[2]: return token if maxBounding and w <= size[0] and h <= size[1] and d <= size[2]: return token if (z > 1 and t > 1) or (z == 1 and t == 1): raise ImageServiceException(400, 'Resize3D: only supports 3D images') ifile = token.first_input_file() ofile = '%s.size3d_%d,%d,%d,%s,%s' % (token.data, size[0], size[1], size[2], method, textAddition) log.debug('Resize3D %s: %s to %s', token.resource_id, ifile, ofile) width, height = compute_new_size(w, h, size[0], size[1], aspectRatio != '', maxBounding) zrestag = 'pixel_resolution_z' if z > 1 else 'pixel_resolution_t' info = { 'image_num_x': width, 'image_num_y': height, 'image_num_z': size[2] if z > 1 else 1, 'image_num_t': size[2] if t > 1 else 1, 'pixel_resolution_x': dims.get('pixel_resolution_x', 0) * (w / float(width)), 'pixel_resolution_y': dims.get('pixel_resolution_y', 0) * (h / float(height)), zrestag: dims.get(zrestag, 0) * (d / float(size[2])), } command = token.drainQueue() if not os.path.exists(ofile): # if image has multiple resolution levels find the closest one and request it num_l = dims.get('image_num_resolution_levels', 1) if num_l > 1 and '-res-level' not in command: try: scales = [ float(i) for i in dims.get( 'image_resolution_level_scales', '').split(',') ] #log.debug('scales: %s', scales) sizes = [(round(w * i), round(h * i)) for i in scales] #log.debug('scales: %s', sizes) relatives = [ max(size[0] / float(sz[0]), size[1] / float(sz[1])) for sz in sizes ] #log.debug('relatives: %s', relatives) relatives = [i if i <= 1 else 0 for i in relatives] #log.debug('relatives: %s', relatives) level = relatives.index(max(relatives)) command.extend(['-res-level', str(level)]) except (Exception): pass command.extend([ '-resize3d', '%s,%s,%s,%s%s' % (size[0], size[1], size[2], method, aspectRatio) ]) self.server.imageconvert(token, ifile, ofile, fmt=default_format, extra=command) return token.setImage(ofile, fmt=default_format, dims=info, input=ofile)
def action(self, token, arg): log.debug('Resize %s: %s', token.resource_id, arg) #size = tuple(map(int, arg.split(','))) ss = arg.split(',') size = [0, 0] method = 'BL' aspectRatio = '' maxBounding = False textAddition = '' if len(ss) > 0 and ss[0].isdigit(): size[0] = int(ss[0]) if len(ss) > 1 and ss[1].isdigit(): size[1] = int(ss[1]) if len(ss) > 2: method = ss[2].upper() if len(ss) > 3: textAddition = ss[3].upper() if len(ss) > 3 and (textAddition == 'AR'): aspectRatio = ',AR' if len(ss) > 3 and (textAddition == 'MX'): maxBounding = True aspectRatio = ',AR' if size[0] <= 0 and size[1] <= 0: raise ImageServiceException( 400, 'Resize: size is unsupported: [%s]' % arg) if method not in ['NN', 'BL', 'BC']: raise ImageServiceException( 400, 'Resize: method is unsupported: [%s]' % arg) # if the image is smaller and MX is used, skip resize dims = token.dims or {} num_x = int(dims.get('image_num_x', 0)) num_y = int(dims.get('image_num_y', 0)) if maxBounding and num_x <= size[0] and num_y <= size[1]: log.debug( 'Resize: Max bounding resize requested on a smaller image, skipping...' ) return token if token.dryrun != True and (num_x <= 0 or num_y <= 0): raise ImageServiceException( 400, 'Resize: image improperly decoded, has side of 0px') if token.dryrun != True and (size[0] <= 0 or size[1] <= 0) and (num_x <= 0 or num_y <= 0): raise ImageServiceException( 400, 'Resize: new image size cannot be guessed due to missing info') ifile = token.first_input_file() ofile = '%s.size_%d,%d,%s,%s' % (token.data, size[0], size[1], method, textAddition) args = [ '-resize', '%s,%s,%s%s' % (size[0], size[1], method, aspectRatio) ] try: width = height = 1 width, height = compute_new_size(num_x, num_y, size[0], size[1], aspectRatio != '', maxBounding) except ZeroDivisionError: if token.dryrun == True: log.warning( 'Resize warning while guessing size %s: [%sx%s] to [%sx%s]', token.resource_id, num_x, num_y, width, height) else: raise ImageServiceException( 400, 'Resize: new image size cannot be guessed due to missing info' ) log.debug('Resize %s: [%sx%s] to [%sx%s] for [%s] to [%s]', token.resource_id, num_x, num_y, width, height, ifile, ofile) # if image has multiple resolution levels find the closest one and request it num_l = dims.get('image_num_resolution_levels', 1) if num_l > 1 and '-res-level' not in token.getQueue(): try: scales = [ float(i) for i in dims.get('image_resolution_level_scales', '').split(',') ] #log.debug('scales: %s', scales) sizes = [(round(num_x * i), round(num_y * i)) for i in scales] #log.debug('scales: %s', sizes) relatives = [ max(width / float(sz[0]), height / float(sz[1])) for sz in sizes ] #log.debug('relatives: %s', relatives) relatives = [i if i <= 1 else 0 for i in relatives] #log.debug('relatives: %s', relatives) level = relatives.index(max(relatives)) args.extend(['-res-level', str(level)]) except (Exception): pass info = { 'image_num_x': width, 'image_num_y': height, 'pixel_resolution_x': dims.get('pixel_resolution_x', 0) * (num_x / float(width)), 'pixel_resolution_y': dims.get('pixel_resolution_y', 0) * (num_y / float(height)), } return self.server.enqueue(token, 'resize', ofile, fmt=default_format, command=args, dims=info)
def action(self, token, arg): '''arg = l,tnx,tny,tsz''' if not token.isFile(): raise ImageServiceException(400, 'Tile: input is not an image...' ) level=0; tnx=0; tny=0; tsz=512; vs = arg.split(',', 4) if len(vs)>0 and vs[0].isdigit(): level = int(vs[0]) if len(vs)>1 and vs[1].isdigit(): tnx = int(vs[1]) if len(vs)>2 and vs[2].isdigit(): tny = int(vs[2]) if len(vs)>3 and vs[3].isdigit(): tsz = int(vs[3]) log.debug( 'Tile: l:%d, tnx:%d, tny:%d, tsz:%d' % (level, tnx, tny, tsz) ) # if input image is smaller than the requested tile size dims = token.dims or {} width = dims.get('image_num_x', 0) height = dims.get('image_num_y', 0) if width<=tsz and height<=tsz: log.debug('Image is smaller than requested tile size, passing the whole image...') return token # construct a sliced filename ifname = token.first_input_file() base_name = '%s.tiles'%(token.data) _mkdir( base_name ) ofname = os.path.join(base_name, '%s_%.3d_%.3d_%.3d' % (tsz, level, tnx, tny)) hist_name = os.path.join(base_name, '%s_histogram'%(tsz)) # if input image does not contain tile pyramid, create one and pass it along if dims.get('image_num_resolution_levels', 0)<2 or dims.get('tile_num_x', 0)<1: pyramid = '%s.pyramid.tif'%(token.data) command = token.drainQueue() if not os.path.exists(pyramid): #command.extend(['-ohst', hist_name]) command.extend(['-options', 'compression lzw tiles %s pyramid subdirs'%default_tile_size]) log.debug('Generate tiled pyramid %s: from %s to %s with %s', token.resource_id, ifname, pyramid, command ) r = self.server.imageconvert(token, ifname, pyramid, fmt=default_format, extra=command) if r is None: raise ImageServiceException(500, 'Tile: could not generate pyramidal file' ) # ensure the file was created with Locks(pyramid, failonread=(not block_tile_reads)) as l: if l.locked is False: # dima: never wait, respond immediately fff = (width*height) / (10000*10000) raise ImageServiceFuture((15*fff,30*fff)) # compute the number of pyramidal levels # sz = max(width, height) # num_levels = math.ceil(math.log(sz, 2)) - math.ceil(math.log(min_level_size, 2)) + 1 # scales = [1/float(pow(2,i)) for i in range(0, num_levels)] # info = { # 'image_num_resolution_levels': num_levels, # 'image_resolution_level_scales': ',',join([str(i) for i in scales]), # 'tile_num_x': default_tile_size, # 'tile_num_y': default_tile_size, # 'converter': ConverterImgcnv.name, # } # load the number of pyramidal levels from the file info2 = self.server.getImageInfo(filename=pyramid) info = { 'image_num_resolution_levels': info2.get('image_num_resolution_levels'), 'image_resolution_level_scales': info2.get('image_resolution_level_scales'), 'tile_num_x': info2.get('tile_num_x'), 'tile_num_y': info2.get('tile_num_y'), 'converter': info2.get('converter'), } log.debug('Updating original input to pyramidal version %s: %s -> %s', token.resource_id, ifname, pyramid ) token.setImage(ofname, fmt=default_format, dims=info, input=pyramid) ifname = pyramid # compute output tile size dims = token.dims or {} x = tnx * tsz y = tny * tsz if x>=width or y>=height: raise ImageServiceException(400, 'Tile: tile position outside of the image: %s,%s'%(tnx, tny)) # the new tile service does not change the number of z points in the image and if contains all z will perform the operation info = { 'image_num_x': tsz if width-x >= tsz else width-x, 'image_num_y': tsz if height-y >= tsz else height-y, #'image_num_z': 1, #'image_num_t': 1, } #log.debug('Inside pyramid dims: %s', dims) #log.debug('Inside pyramid input: %s', token.first_input_file() ) #log.debug('Inside pyramid data: %s', token.data ) # extract individual tile from pyramidal tiled image if dims.get('image_num_resolution_levels', 0)>1 and dims.get('tile_num_x', 0)>0: # dima: maybe better to test converter, if imgcnv then enqueue, otherwise proceed with the converter path if dims.get('converter', '') == ConverterImgcnv.name: c = self.server.converters[ConverterImgcnv.name] r = c.tile(token, ofname, level, tnx, tny, tsz) if r is not None: if not os.path.exists(hist_name): # write the histogram file is missing c.writeHistogram(token, ofnm=hist_name) # if decoder returned a list of operations for imgcnv to enqueue if isinstance(r, list): #r.extend([ '-ihst', hist_name]) token.histogram = hist_name return self.server.enqueue(token, 'tile', ofname, fmt=default_format, command=r, dims=info) # try other decoders to read tiles ofname = '%s.tif'%ofname if os.path.exists(ofname): return token.setImage(ofname, fmt=default_format, dims=info, hist=hist_name, input=ofname) else: r = None for n,c in self.server.converters.iteritems(): if n == ConverterImgcnv.name: continue if callable( getattr(c, "tile", None) ): r = c.tile(token, ofname, level, tnx, tny, tsz) if r is not None: if not os.path.exists(hist_name): # write the histogram file if missing c.writeHistogram(token, ofnm=hist_name) return token.setImage(ofname, fmt=default_format, dims=info, hist=hist_name, input=ofname) raise ImageServiceException(500, 'Tile could not be extracted')
def run(cls, ifnm, ofnm, args, **kw): '''converts input filename into output using exact arguments as provided in args''' if not cls.installed: return None failonread = kw.get('failonread') or (not block_reads) tmp = None with Locks(ifnm, ofnm, failonexist=True) as l: if l.locked: # the file is not being currently written by another process command = [cls.CONVERTERCOMMAND] command.extend(args) log.debug('Run dylib command: %s', misc.tounicode(command)) proceed = True if ofnm is not None and os.path.exists( ofnm) and os.path.getsize(ofnm) > 16: if kw.get('nooverwrite', False) is True: proceed = False log.warning( 'Run: output exists before command [%s], skipping', misc.tounicode(ofnm)) else: log.warning( 'Run: output exists before command [%s], overwriting', misc.tounicode(ofnm)) if proceed is True: #command, tmp = misc.start_nounicode_win(ifnm, command) retcode, out = call_imgcnvlib(command) #misc.end_nounicode_win(tmp) if retcode == 100 or retcode == 101: # some error in libbioimage, retry once log.error('Libioimage retcode %s: retry once: %s', retcode, command) retcode, out = call_imgcnvlib(command) if retcode == 99: # in case of a timeout log.info('Run: timed-out for [%s]', misc.tounicode(command)) if ofnm is not None and os.path.exists(ofnm): os.remove(ofnm) raise ImageServiceException( 412, 'Requested timeout reached') if retcode != 0: log.info('Run: returned [%s] for [%s]', retcode, misc.tounicode(command)) return None if ofnm is None: return str(retcode) # output file does not exist for some operations, like tiles # tile command does not produce a file with this filename # if not os.path.exists(ofnm): # log.error ('Run: output does not exist after command [%s]', ofnm) # return None elif l.locked is False: # dima: never wait, respond immediately raise ImageServiceFuture((1, 15)) # make sure the write of the output file have finished if ofnm is not None and os.path.exists(ofnm): with Locks(ofnm, failonread=failonread) as l: if l.locked is False: # dima: never wait, respond immediately raise ImageServiceFuture((1, 15)) # safeguard for incorrectly converted files, sometimes only the tiff header can be written # empty lock files are automatically removed before by lock code if os.path.exists( ofnm) and os.path.getsize(ofnm) < cls.MINIMUM_FILE_SIZE: log.error( 'Run: output file is smaller than %s bytes, probably an error, removing [%s]', cls.MINIMUM_FILE_SIZE, ofnm) os.remove(ofnm) return None return ofnm
def action(self, token, arg): if not token.isFile(): raise ImageServiceException(400, 'Roi: input is not an image...') rois = [] for a in arg.split(';'): vs = a.split(',', 4) x1 = int(vs[0]) if len(vs) > 0 and vs[0].isdigit() else 0 y1 = int(vs[1]) if len(vs) > 1 and vs[1].isdigit() else 0 x2 = int(vs[2]) if len(vs) > 2 and vs[2].isdigit() else 0 y2 = int(vs[3]) if len(vs) > 3 and vs[3].isdigit() else 0 rois.append((x1, y1, x2, y2)) x1, y1, x2, y2 = rois[0] if x1 <= 0 and x2 <= 0 and y1 <= 0 and y2 <= 0: raise ImageServiceException(400, 'ROI: region is not provided') ifile = token.first_input_file() otemp = token.data ofile = '%s.roi_%d,%d,%d,%d' % (token.data, x1 - 1, y1 - 1, x2 - 1, y2 - 1) log.debug('ROI %s: %s to %s', token.resource_id, ifile, ofile) if len(rois) == 1: info = { 'image_num_x': x2 - x1, 'image_num_y': y2 - y1, } command = [ '-roi', '%s,%s,%s,%s' % (x1 - 1, y1 - 1, x2 - 1, y2 - 1) ] return self.server.enqueue(token, 'roi', ofile, fmt=default_format, command=command, dims=info) # remove pre-computed ROIs rois = [ (_x1, _y1, _x2, _y2) for _x1, _y1, _x2, _y2 in rois if not os.path.exists('%s.roi_%d,%d,%d,%d' % (otemp, _x1 - 1, _y1 - 1, _x2 - 1, _y2 - 1)) ] lfile = '%s.rois' % (otemp) command = token.drainQueue() if not os.path.exists(ofile) or len(rois) > 0: # global ROI lock on this input since we can't lock on all individual outputs with Locks(ifile, lfile, failonexist=True) as l: if l.locked: # the file is not being currently written by another process s = ';'.join([ '%s,%s,%s,%s' % (x1 - 1, y1 - 1, x2 - 1, y2 - 1) for x1, y1, x2, y2 in rois ]) command.extend(['-roi', s]) command.extend( ['-template', '%s.roi_{x1},{y1},{x2},{y2}' % otemp]) self.server.imageconvert(token, ifile, ofile, fmt=default_format, extra=command) # ensure the virtual locking file is not removed with open(lfile, 'wb') as f: f.write('#Temporary locking file') elif l.locked is False: # dima: never wait, respond immediately raise ImageServiceFuture((1, 15)) # ensure the operation is finished if os.path.exists(lfile): with Locks(lfile, failonread=(not block_reads)) as l: if l.locked is False: # dima: never wait, respond immediately raise ImageServiceFuture((1, 15)) info = { 'image_num_x': x2 - x1, 'image_num_y': y2 - y1, } return token.setImage(ofile, fmt=default_format, dims=info, input=ofile)
def action(self, token, arg): if not token.isFile(): raise ImageServiceException(400, 'Format: input is not an image...') args = arg.lower().split(',') fmt = default_format if len(args) > 0: fmt = args.pop(0).lower() stream = False if 'stream' in args: stream = True args.remove('stream') # avoid doing anything if requested format is in requested format dims = token.dims or {} if dims.get('format', '').lower() == fmt and not token.hasQueue(): log.debug('%s: Input is in requested format, avoid reconvert...', token.resource_id) return token if fmt not in self.server.writable_formats: raise ImageServiceException( 400, 'Requested format [%s] is not writable' % fmt) name_extra = '' if len(args) <= 0 else '.%s' % '.'.join(args) ext = self.server.converters.defaultExtension(fmt) ifile = token.first_input_file() ofile = '%s.%s%s.%s' % (token.data, name_extra, fmt, ext) log.debug('Format %s: %s -> %s with %s opts=[%s]', token.resource_id, ifile, ofile, fmt, args) if not os.path.exists(ofile): extra = token.drainQueue() queue_size = len(extra) if len(args) > 0: extra.extend(['-options', (' ').join(args)]) elif fmt in ['jpg', 'jpeg']: extra.extend(['-options', 'quality 95 progressive yes']) r = None #if dims.get('converter', '') == ConverterImgcnv.name: # first try first converter that supports this output format c = self.server.writable_formats[fmt] first_name = c.name # if there are any operations to be run on the output if c.name == ConverterImgcnv.name or queue_size < 1: r = c.convert(token, ofile, fmt, extra=extra) # try using other converters directly if r is None: log.debug('%s could not convert [%s] to [%s] format' % (first_name, ifile, fmt)) log.debug('Trying other converters directly') for n, c in self.server.converters.iteritems(): if n == first_name: continue if n == ConverterImgcnv.name or queue_size < 1: r = c.convert(token, ofile, fmt, extra=extra) if r is not None and os.path.exists(ofile): break # using ome-tiff as intermediate if everything failed if r is None: log.debug( 'None of converters could connvert [%s] to [%s] format' % (ifile, fmt)) log.debug('Converting to OME-TIFF and then to desired output') r = self.server.imageconvert(token, ifile, ofile, fmt=fmt, extra=extra, try_imgcnv=False) if r is None: log.error( 'Format %s: %s could not convert with [%s] format [%s] -> [%s]', token.resource_id, c.CONVERTERCOMMAND, fmt, ifile, ofile) raise ImageServiceException( 415, 'Could not convert into %s format' % fmt) if stream: fpath = ofile.split('/') filename = '%s_%s.%s' % (token.resource_name, fpath[len(fpath) - 1], ext) token.setFile(fname=ofile) token.outFileName = filename token.input = ofile else: token.setImage(fname=ofile, fmt=fmt, input=ofile) # if (ofile != ifile) and (fmt != 'raw'): # try: # info = self.server.getImageInfo(filename=ofile) # if int(info['image_num_p'])>1: # if 'image_num_z' in token.dims: info['image_num_z'] = token.dims['image_num_z'] # if 'image_num_t' in token.dims: info['image_num_t'] = token.dims['image_num_t'] # token.dims = info # except Exception: # pass #log.debug('Token: %s', str(token)) if (ofile != ifile): info = { 'format': fmt, } if fmt == 'jpeg': info.update({ 'image_pixel_depth': 8, 'image_pixel_format': 'unsigned integer', 'image_num_c': min(4, int(dims.get('image_num_c', 0))), }) elif fmt not in [ 'tiff', 'bigtiff', 'ome-tiff', 'ome-bigtiff', 'raw' ]: info = self.server.getImageInfo(filename=ofile) info.update({ 'image_num_z': dims.get('image_num_z', ''), 'image_num_t': dims.get('image_num_t', ''), }) token.dims.update(info) return token
def slice(cls, token, ofnm, z, t, roi=None, **kw): '''extract Z,T plane from input filename into output in OME-TIFF format''' ifnm = token.first_input_file() series = token.series log.debug('Slice: z=%s t=%s roi=%s series=%s for [%s]', z, t, roi, series, ifnm) z1, z2 = z t1, t2 = t x1, x2, y1, y2 = roi fmt = kw.get('fmt', 'bigtiff') info = token.dims or {} #command = ['-o', ofnm, '-t', fmt] command = [] if token.series is not None and token.series != 0: command.extend(['-path', token.series]) if t2 == 0: t2 = t1 if z2 == 0: z2 = z1 pages = [] for ti in range(t1, t2 + 1): for zi in range(z1, z2 + 1): if info.get('image_num_t', 1) == 1: page_num = zi elif info.get('image_num_z', 1) == 1: page_num = ti elif info.get('dimensions', 'XYCZT').replace( ' ', '').startswith('XYCT') is False: page_num = (ti - 1) * info.get('image_num_z', 1) + zi else: page_num = (zi - 1) * info.get('image_num_t', 1) + ti pages.append(page_num) log.debug('slice pages: %s', pages) # separate normal and multi-file series if token.is_multifile_series() is False: log.debug('Slice for single-file series') #command.extend(['-i', ifnm]) command.extend(['-page', ','.join([str(p) for p in pages])]) else: # use first image of the series, need to check for separate channels here log.debug('Slice for multi-file series') files = token.input meta = token.meta or {} channels = meta.get('image_num_c', 0) #log.debug('Slice for multi-file series: %s', files) #if len(pages)==1 and (x1==x2 or y1==y2) and channels<=1: if len(pages) == 1 and channels <= 1: # in multi-file case and only one page is requested with no ROI, return with no re-conversion #misc.dolink(files[pages[0]-1], ofnm) token.input = files[pages[0] - 1] #command.extend(['-i', token.input]) else: # in case of many pages we might have to write input filenames as a file #fl = '%s.files'%ofnm #cls.write_files(files, fl) #command.extend(['-il', fl]) #command.extend(['-page', ','.join([str(p) for p in pages])]) if channels > 1: # dima: since we are writing a non ome-tiff file, proper geometry is irrelevant but number of channels is geom = '1,1,%s' % (channels) command.extend(['-geometry', geom]) cpages = [] for p in [p - 1 for p in pages]: for c in range(channels): cpages.append(p * channels + c) token.input = [files[p] for p in cpages] else: token.input = [files[p - 1] for p in pages] # roi if not x1 == x2 or not y1 == y2: if not x1 == x2: if x1 > 0: x1 = x1 - 1 if x2 > 0: x2 = x2 - 1 if not y1 == y2: if y1 > 0: y1 = y1 - 1 if y2 > 0: y2 = y2 - 1 command.extend(['-roi', '%s,%s,%s,%s' % (x1, y1, x2, y2)]) # other dimensions: -slice fov:345,rotation:23 nd = [] for k, v in kw.iteritems(): if k in cls.extended_dimension_names: if len(v) > 1: raise ImageServiceException( responses.UNPROCESSABLE_ENTITY, 'Ranges in extended dimensions are not yet supported') nd.append('%s:%s' % (k, v[0])) if len(nd) > 0: command.extend(['-slice', ','.join(nd)]) #return cls.run(ifnm, ofnm, command ) return command
def action(self, token, arg): ifile = token.first_input_file() metacache = '%s.meta' % (token.data) log.debug('Meta: %s -> %s', ifile, metacache) if not os.path.exists(metacache): meta = {} if not os.path.exists(ifile): raise ImageServiceException(404, 'Meta: Input file not found...') dims = token.dims or {} converter = None if dims.get('converter', None) in self.server.converters: converter = dims.get('converter') meta = self.server.converters[converter].meta(token) if meta is None: # exhaustively iterate over converters to find supporting one for c in self.server.converters.itervalues(): if c.name == dims.get('converter'): continue meta = c.meta(token) converter = c.name if meta is not None and len(meta) > 0: break if meta is None or len(meta) < 1: raise ImageServiceException( 415, 'Meta: file format is not supported...') # overwrite fileds forced by the fileds stored in the resource image_meta if token.meta is not None: meta.update(token.meta) meta['converter'] = converter if token.is_multifile_series() is True: meta['file_mode'] = 'multi-file' else: meta['file_mode'] = 'single-file' # construct an XML tree image = etree.Element('resource', uri='/%s/%s?meta' % (self.server.base_url, token.resource_id)) tags_map = {} for k, v in meta.iteritems(): if k.startswith('DICOM/'): continue k = safeunicode(k) v = safeunicode(v) tl = k.split('/') parent = image for i in range(0, len(tl)): tn = '/'.join(tl[0:i + 1]) if not tn in tags_map: tp = etree.SubElement(parent, 'tag', name=tl[i]) tags_map[tn] = tp parent = tp else: parent = tags_map[tn] try: parent.set('value', v) except ValueError: pass if meta['format'] == 'DICOM': node = etree.SubElement(image, 'tag', name='DICOM') ConverterImgcnv.meta_dicom(ifile, series=token.series, token=token, xml=node) log.debug('Meta %s: storing metadata into %s', token.resource_id, metacache) xmlstr = etree.tostring(image) with open(metacache, 'w') as f: f.write(xmlstr) return token.setXml(xmlstr) log.debug('Meta %s: reading metadata from %s', token.resource_id, metacache) return token.setXmlFile(metacache)
def action(self, token, arg): '''arg = x1-x2,y1-y2,z|z1-z2,t|t1-t2 or arg = z:V1-V2,fov:V,... ''' if not token.isFile(): raise ImageServiceException(400, 'Slice: input is not an image...') # parse arguments x1 = 0 x2 = 0 y1 = 0 y2 = 0 z1 = 0 z2 = 0 t1 = 0 t2 = 0 extended_dims = {} extended_str = '' if ':' not in arg: # the standard way of defining dimensions vs = [[safeint(i, 0) for i in vs.split('-', 1)] for vs in arg.split(',')] for v in vs: if len(v) < 2: v.append(0) for v in range(len(vs) - 4): vs.append([0, 0]) x1, x2 = vs[0] y1, y2 = vs[1] z1, z2 = vs[2] t1, t2 = vs[3] else: # the extended way of defining dimensions for a in arg.split(','): k, v = a.split(':', 1) if k not in self.dimension_names: continue extended_dims[k] = [safeint(i, 0) for i in v.split('-', 1)] # update x,y,z and t from the new style args x1, x2 = pop_dimension_range(extended_dims, 'x') y1, y2 = pop_dimension_range(extended_dims, 'y') z1, z2 = pop_dimension_range(extended_dims, 'z') t1, t2 = pop_dimension_range(extended_dims, 't') # update extended string part if len(extended_dims) > 0: # get a string of extended dimensions in the defined order ddd = [ get_dimension_string(extended_dims, k) for k in self.dimension_names if k in extended_dims ] extended_str = '.%s' % (','.join(ddd)) # in case slices request an exact copy, skip if x1 == 0 and x2 == 0 and y1 == 0 and y2 == 0 and z1 == 0 and z2 == 0 and t1 == 0 and t2 == 0 and len( extended_dims) < 1: return token dims = token.dims or {} if x1 <= 1 and x2 >= dims.get('image_num_x', 1): x1 = 0 x2 = 0 if y1 <= 1 and y2 >= dims.get('image_num_y', 1): y1 = 0 y2 = 0 if z1 <= 1 and z2 >= dims.get('image_num_z', 1): z1 = 0 z2 = 0 if t1 <= 1 and t2 >= dims.get('image_num_t', 1): t1 = 0 t2 = 0 # check image bounds if z1 > dims.get('image_num_z', 1): raise ImageServiceException( 400, 'Slice: requested Z slice outside of image bounds: [%s]' % z1) if z2 > dims.get('image_num_z', 1): raise ImageServiceException( 400, 'Slice: requested Z slice outside of image bounds: [%s]' % z2) if t1 > dims.get('image_num_t', 1): raise ImageServiceException( 400, 'Slice: requested T plane outside of image bounds: [%s]' % t1) if t2 > dims.get('image_num_t', 1): raise ImageServiceException( 400, 'Slice: requested T plane outside of image bounds: [%s]' % t2) # shortcuts are only possible with no ROIs are requested if x1 == x2 == 0 and y1 == y2 == 0: # shortcut if input image has only one T and Z if dims.get('image_num_z', 1) <= 1 and dims.get( 'image_num_t', 1) <= 1 and len(extended_dims) < 1: log.debug( 'Slice: plane requested on image with no T or Z planes, skipping...' ) return token # shortcut if asking for all slices with only a specific time point in an image with only one time pont if z1 == z2 == 0 and t1 <= 1 and dims.get( 'image_num_t', 1) <= 1 and len(extended_dims) < 1: log.debug( 'Slice: T plane requested on image with no T planes, skipping...' ) return token # shortcut if asking for all time points with only a specific z slice in an image with only one z slice if t1 == t2 == 0 and z1 <= 1 and dims.get( 'image_num_z', 1) <= 1 and len(extended_dims) < 1: log.debug( 'Slice: Z plane requested on image with no Z planes, skipping...' ) return token if z1 == z2 == 0: z1 = 1 z2 = dims.get('image_num_z', 1) if t1 == t2 == 0: t1 = 1 t2 = dims.get('image_num_t', 1) # construct a sliced filename ifname = token.first_input_file() ofname = '%s.%d-%d,%d-%d,%d-%d,%d-%d%s.ome.tif' % ( token.data, x1, x2, y1, y2, z1, z2, t1, t2, extended_str) log.debug('Slice %s: from [%s] to [%s]', token.resource_id, ifname, ofname) new_w = x2 - x1 new_h = y2 - y1 new_z = max(1, z2 - z1 + 1) new_t = max(1, t2 - t1 + 1) info = { 'image_num_z': new_z, 'image_num_t': new_t, } if new_w > 0: info['image_num_x'] = new_w + 1 if new_h > 0: info['image_num_y'] = new_h + 1 meta = token.meta or {} unsupported_multifile = False if token.is_multifile_series() is True and (z2 == 0 or z2 == z1) and ( t2 == 0 or t2 == t1) and x1 == x2 and y1 == y2 and meta.get( 'image_num_c', 0) == 0: unsupported_multifile = True if dims.get( 'converter', '') == ConverterImgcnv.name or unsupported_multifile is True: r = ConverterImgcnv.slice(token, ofname, z=(z1, z2), t=(t1, t2), roi=(x1, x2, y1, y2), fmt=default_format, **extended_dims) # if decoder returned a list of operations for imgcnv to enqueue if isinstance(r, list): return self.server.enqueue(token, 'slice', ofname, fmt=default_format, command=r, dims=info) # slice the image if not os.path.exists(ofname): intermediate = '%s.ome.tif' % token.data if 'converter' in dims and dims.get( 'converter') in self.server.converters: r = self.server.converters[dims.get('converter')].slice( token, ofname, z=(z1, z2), t=(t1, t2), roi=(x1, x2, y1, y2), fmt=default_format, intermediate=intermediate) # if desired converter failed, perform exhaustive conversion if r is None: for n, c in self.server.converters.iteritems(): if n in [ConverterImgcnv.name, dims.get('converter')]: continue r = c.slice(token, ofname, z=(z1, z2), t=(t1, t2), roi=(x1, x2, y1, y2), fmt=default_format, intermediate=intermediate) if r is not None: break if r is None: log.error('Slice %s: could not generate slice for [%s]', token.resource_id, ifname) raise ImageServiceException(415, 'Could not generate slice') # if decoder returned a list of operations for imgcnv to enqueue if isinstance(r, list): return self.server.enqueue(token, 'slice', ofname, fmt=default_format, command=r, dims=info) return token.setImage(ofname, fmt=default_format, dims=info, input=ofname)
def action(self, token, arg): ss = arg.split(',') size = [safeint(ss[0], 128) if len(ss)>0 else 128, safeint(ss[1], 128) if len(ss)>1 else 128] method = ss[2].upper() if len(ss)>2 and len(ss[2])>0 else 'BC' preproc = ss[3].lower() if len(ss)>3 and len(ss[3])>0 else '' preprocc = ',%s'%preproc if len(preproc)>0 else '' # attempt to keep the filename backward compatible fmt = ss[4].lower() if len(ss)>4 and len(ss[4])>0 else 'jpeg' if size[0]<=0 and size[1]<=0: raise ImageServiceException(400, 'Thumbnail: size is unsupported [%s]'%arg) if method not in ['NN', 'BL', 'BC']: raise ImageServiceException(400, 'Thumbnail: method is unsupported [%s]'%arg) if preproc not in ['', 'mid', 'mip', 'nip']: raise ImageServiceException(400, 'Thumbnail: method is unsupported [%s]'%arg) ext = self.server.converters.defaultExtension(fmt) ifile = token.first_input_file() ofile = '%s.thumb_%s,%s,%s%s.%s'%(token.data, size[0], size[1], method, preprocc, ext) dims = token.dims or {} num_x = int(dims.get('image_num_x', 0)) num_y = int(dims.get('image_num_y', 0)) try: width, height = compute_new_size(num_x, num_y, size[0], size[1], keep_aspect_ratio=True, no_upsample=True) except ZeroDivisionError: raise ImageServiceException(400, 'Thumbnail: new image size cannot be guessed due to missing info' ) info = { 'image_num_x': width, 'image_num_y': height, 'image_num_c': 3, 'image_num_z': 1, 'image_num_t': 1, 'image_pixel_depth': 8, 'format': fmt, } # if image can be decoded with imageconvert, enqueue if dims.get('converter', '') == ConverterImgcnv.name: r = ConverterImgcnv.thumbnail(token, ofile, size[0], size[1], method=method, preproc=preproc, fmt=fmt) if isinstance(r, list): return self.server.enqueue(token, 'thumbnail', ofile, fmt=fmt, command=r, dims=info) # if image requires other decoder if not os.path.exists(ofile): intermediate = '%s.ome.tif'%(token.data) r = None if 'converter' in dims and dims.get('converter') in self.server.converters: r = self.server.converters[dims.get('converter')].thumbnail(token, ofile, size[0], size[1], method=method, intermediate=intermediate, preproc=preproc, fmt=fmt) # if desired converter failed, perform exhaustive conversion if r is None: for n,c in self.server.converters.iteritems(): if n in [ConverterImgcnv.name, dims.get('converter')]: continue r = c.thumbnail(token, ofile, size[0], size[1], method=method, intermediate=intermediate, preproc=preproc, fmt=fmt) if r is not None: break if r is None: log.error('Thumbnail %s: could not generate thumbnail for [%s]', token.resource_id, ifile) raise ImageServiceException(415, 'Could not generate thumbnail' ) if isinstance(r, list): return self.server.enqueue(token, 'thumbnail', ofile, fmt=fmt, command=r, dims=info) return token.setImage(ofile, fmt=fmt, dims=info, input=ofile)