diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/filelist/__init__.py b/filelist/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/filelist/filesettings.py b/filelist/filesettings.py new file mode 100644 index 0000000..9e8d82b --- /dev/null +++ b/filelist/filesettings.py @@ -0,0 +1,9 @@ +IMG_LOCS = {"cimages":"C:\\images\\"} +RL_LOCS = {"crunlogs":"C:\\runlogs\\"} +STATIC_DIR = "./static/" +#THUMB_DIR = "C:\\thumb\\" +PNG_DIR = "C:\\django\\django\\clinamen\\static\\png\\" +PNG_URL = "../../../static/png/" +THUMB_DIR = "C:\\django\\django\\clinamen\\static\\thumb\\" +THUMB_URL = "../../../static/thumb/" +THUMB_SIZE = 64 diff --git a/filelist/filetools.py b/filelist/filetools.py new file mode 100644 index 0000000..ec76ecc --- /dev/null +++ b/filelist/filetools.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +"""Functions to easily process file names.""" + +import os +import glob +import re + + +def sort_files_by_date(filelist, newestfirst=True): + """Return a list of files sorted by time, newest first by default""" + + mod_time_file = [(os.lstat(item).st_mtime, item) for item in filelist] + + if newestfirst: + mod_time_file.sort(reverse=True) + else: + mod_time_file.sort() + + return [file[1] for file in mod_time_file] + + +def get_files_in_dir(dirname, ext='TIF', globexpr=None, sort=True): + """Return a list of all files in a directory with extension ext + + When ``globexpr`` is given, ``ext`` is ignored and the Python glob module + is used to search for all files with the given pattern. + + **Inputs** + + * dirname: string, full path to the directory + * ext: string, extension of the files to process + * globexpr: string, glob search expression (can contain wildcards) + * sort: bool, if True the results are sorted by file date/time, newest + first + + **Outputs** + + * imgs: list of strings, each string in the list is the complete path to + a file + + """ + + if globexpr: + imgs = glob.glob(os.path.join(dirname, ''.join(globexpr))) + else: + imgs = glob.glob(os.path.join(dirname, ''.join(['*.', ext]))) + if sort: + imgs = sort_files_by_date(imgs) + + return imgs + + +def find_imgnames(imglist, startstr, stopstr): + """Finds names from imglist between startstr and stopstr in time-ordered way + + **Inputs** + + * imglist: list of str, containing paths of images on disc + * startstr: str, part of the name of the oldest image by date that is + wanted + * stopstr: str, part of the name of the newest image by date that is + wanted + + **Outputs** + + * imgs: list of str, containing the found paths to image files + + """ + + imgs = sort_files_by_date(imglist, newestfirst=False) + for img in imgs: + found_one = re.search(startstr, img) + if found_one: + start_idx = imgs.index(img) + break + for img in imgs: + found_one = re.search(stopstr, img) + if found_one: + stop_idx = imgs.index(img) + break + + return imgs[start_idx:stop_idx+1] diff --git a/filelist/models.py b/filelist/models.py new file mode 100644 index 0000000..b1bc608 --- /dev/null +++ b/filelist/models.py @@ -0,0 +1,207 @@ +import os +import numpy as np +import scipy as sp +import re +import inspect +import collections +#from odysseus.imageio import imgimport_intelligent, list_of_frames +os.environ['DJANGO_SETTINGS_MODULE'] = "settings" +from django.db import models +import filelist.filesettings as filesettings +import Image + +class RunLogInfo(models.Model): + #path = models.FilePathField(filesettings.RL_DIR, primary_key=True) + path = models.CharField(max_length=200, primary_key=True) + loc_key = models.CharField(max_length=100) + time = models.DateTimeField() + sequencepath = models.CharField(max_length=200) + listiterationnumber = models.IntegerField(null=True, blank=True) + liststarttime = models.DateTimeField(null=True,blank=True) + sequenceduration = models.FloatField(null=True,blank=True) + description = models.TextField(null=True, blank=True) + exceptions = models.TextField(null=True, blank=True) + class Meta: + ordering=['-time'] + +class VariableValue(models.Model): + name = models.CharField(max_length=30) + value = models.FloatField() + runlog = models.ForeignKey('RunLogInfo') + +class ImageInfo(models.Model): + #path = models.FilePathField(filesettings.IMG_DIR, primary_key=True) + path = models.CharField(max_length=100, primary_key=True) + loc_key = models.CharField(max_length=100) + time = models.DateTimeField() + height = models.IntegerField(null=True, blank=True) + width = models.IntegerField(null=True, blank=True) + number_of_frames = models.IntegerField(null=True, blank=True) + imgtype = models.ForeignKey('ImageType', null=True, blank=True) + runlog = models.ForeignKey('RunLogInfo', null=True, blank=True) + def makeRawFrames(self): + frames = np.dstack(list_of_frames(self.path)) + + self.width = np.size(frames,0) + self.height = np.size(frames,1) + self.number_of_frames = np.size(frames,2) + + for ii in range(np.size(frames,2)): + filename = os.path.splitext(os.path.split(self.path)[1])[0]+'_'+str(ii)+'.png' + + newframe = RawFrame(sourceimage=self, framenumber=ii) + newframe.saveframe(frames[:,:,ii], filename) + newframe.save() + def deleteProcFrames(self): + ProcessedFrame.objects.filter(sourceimage=self).delete() + def ProcessImage(self): + for method in self.imgtype.methods.all(): + procmodule = __import__(method.modulename) + procmethod = getattr(procmodule, method.name) + argdict = {} + for param in TypeParameters.objects.filter(imagetype=self.imgtype, methodargument__method=method): + argdict[param.methodargument.name]=param.value + for ROIparam in TypeROI.objects.filter(imagetype=self.imgtype, methodargument__method=method): + argdict[ROIParam.methodargument.name]=ROIparam.ToDict() + result=procmethod(self.path, **argdict) + if not isinstance(result,tuple): + result = (result,) + for ii in range(len(result)): + if isarray(result[ii]): + filename=os.path.splitext(os.path.split(self.path)[1])[0]+'_'+method.name+'_'+str(ii)+'.png' + + newrecord = ProcessedFrame(sourceimage=self, method=method, framenumber=ii) + newrecord.saveframe(result[ii], filename) + newrecord.save() + else: + newrecord = ProcessedValue(sourceimage=self, method=method, value=float(item[ii]), index=ii) + newrecord.save() + def getClosestSequence(self): + sql = "SELECT * FROM filelist_runloginfo ORDER BY ABS(TIMESTAMPDIFF(SECOND, time,'" + self.time.strftime('%Y-%m-%d %H:%M:%S') + "')) LIMIT 1" + for retrunlog in RunLogInfo.objects.raw(sql): + self.runlog = retrunlog + def getTypeFromDescription(self): + matchObj = re.search("imgtype=(\w+)", self.runlog.description) + if matchObj: + self.imgtype= matchObj.group(1) + class Meta: + ordering=['-time'] + +class FrameInfo(models.Model): + pngpath = models.CharField(max_length=100, primary_key=True) + pngurl = models.CharField(max_length=100) + pngheight = models.IntegerField() + pngwidth = models.IntegerField() + thumbpath = models.CharField(max_length=100) + thumburl = models.CharField(max_length=100) + thumbheight = models.IntegerField() + thumbwidth = models.IntegerField() + framenumber = models.IntegerField(null=True, blank=True) + sourceimage = models.ForeignKey('ImageInfo', null=True, blank=True) + def saveframe(self, frame, filename): + self.pngpath=os.path.join(filesettings.PNG_DIR,filename) + self.pngurl=filesettings.PNG_URL+filename + self.thumbpath=os.path.join(filesettings.THUMB_DIR,filename) + self.thumburl=filesettings.THUMB_URL+filename + + im=sp.misc.toimage(frame) + im.save(self.pngpath) + + self.pngwidth = np.size(frame,0) + self.pngheight = np.size(frame,1) + aspect=float(self.pngwidth)/float(self.pngheight) + + if aspect>1: + self.thumbwidth = filesettings.THUMB_SIZE + self.thumbheight = int(filesettings.THUMB_SIZE/aspect) + else: + self.thumbwidth = int(filesettings.THUMB_SIZE * aspect) + self.thumbheight = filesettings.THUMB_SIZE + im.thumbnail([self.thumbwidth, self.thumbheight], Image.ANTIALIAS) + im.save(self.thumbpath) + class Meta: + abstract=True + ordering=['framenumber'] + +class ProcessingMethod(models.Model): + modulename = models.CharField(max_length=100) + name = models.CharField(max_length=30) + def getargs(self): + procmodule = __import__(self.modulename) + #procmethod = procmodule.getAttr(procmodule, self.name) + procmethod = getattr(procmodule, self.name) + argspec = inspect.getargspec(procmethod) + for keyword in argspec.args[1:]: + newmetharg, created = MethodArgument.objects.get_or_create(method=self, name=keyword) + if re.search("roi_(\w+)", keyword): + newmetharg.isROI = True + newmetharg.save() + class Meta: + unique_together = ("modulename", "name") + +class MethodArgument(models.Model): + method = models.ForeignKey('ProcessingMethod') + name = models.CharField(max_length=30) + isROI = models.NullBooleanField(null=True, blank=True) + +class ImageType(models.Model): + name = models.CharField(max_length=30, primary_key=True) + methods = models.ManyToManyField('ProcessingMethod') + parameters = models.ManyToManyField('MethodArgument', through='TypeParameters') + sample = models.ForeignKey('RawFrame', null=True, blank=True) + def ClearProcessed(self): + ProcessedFrame.objects.filter(sourceimage__imgtype=self).delete() + def ProcessType(self): + #imtype = ImageType.objects.get(name=self.name) + #for iminfo in imtype.ImageInfo_set.all(): + for iminfo in self.imageinfo_set.all(): + iminfo.ProcessImage() + +class TypeParameters(models.Model): + imagetype = models.ForeignKey('ImageType') + methodargument = models.ForeignKey('MethodArgument') + value = models.FloatField() + class Meta: + unique_together = ("imagetype", "methodargument") + +class TypeROI(models.Model): + imagetype = models.ForeignKey('ImageType') + methodargument = models.ForeignKey('MethodArgument') + x1 = models.IntegerField(null=True, blank=True) + x2 = models.IntegerField(null=True, blank=True) + y1 = models.IntegerField(null=True, blank=True) + y2 = models.IntegerField(null=True, blank=True) + def ToDict(self): + return {"x1": x1, "x2": x2, "y1": y1, "y2": y2} + class Meta: + unique_together = ("imagetype", "methodargument") + +class ProcessedFrame(FrameInfo): + method = models.ForeignKey('ProcessingMethod', null=True, blank=True) + +class RawFrame(FrameInfo): + class Meta: + abstract=False + +class ProcessedValue(models.Model): + sourceimage = models.ForeignKey('ImageInfo') + method = models.ForeignKey('ProcessingMethod', null=True, blank=True) + value = models.FloatField() + index = models.IntegerField(null=True, blank=True) + class Meta: + unique_together = ("sourceimage", "method") + +def get_iterable(x): + if isinstance(x, collections.Iterable): + return x + else: + return (x,) + +def isarray(s): + try: + len(s) + return True + except TypeError: + return False + + \ No newline at end of file diff --git a/filelist/odysseus/imageio.py b/filelist/odysseus/imageio.py new file mode 100644 index 0000000..5f4f065 --- /dev/null +++ b/filelist/odysseus/imageio.py @@ -0,0 +1,324 @@ +#!/usr/bin/env python +"""I/O functions for several image formats. + +The relevant formats are TIF, hdf5 and ascii. For ascii and binary numpy formats +no separate functions are provided for saving an image. This is because saving +in these formats requires just a single command: +ascii: np.savetxt('filename', img) +binary (.npy): np.save('filename', img) + +""" + +import os + +import numpy as np +import scipy as sp +# if available, use Zach Pincus' pil_lite which has correct 16-bit TIFF loading +try: + import pil_lite.pil_core.Image as Image +except ImportError: + import Image +#import tables + + +def list_of_frames(img_name): + """Return the list of frames for an image file. + + Details are as described in the imgimport_intelligent docstring. + + """ + + img = Image.open(img_name) + imglist = [] + + try: + for i in xrange(8): + if img.mode == 'I': + imdata = np.asarray(img, dtype=np.int16) + else: + imdata = np.asarray(img, dtype=np.float32) + # fix 3-channel TIFF images + if np.rank(imdata)==3: + imdata = imdata[:,:,0] + 256*imdata[:,:,1] + 65536*imdata[:,:,2] + imglist.append(imdata) + img.seek(i+1) # next frame + except EOFError: + pass + + if not imglist: + raise ImportError, 'No frames detected in file %s' %img_name + else: + return imglist + + +def imgimport_intelligent(img_name, foo=3, bar=4, roi_baz=[12,24,56,78]): + """Opens an image file containing one or more frames + + The number of frames in the image is automatically detected. If it is a + single frame, it is assumed to be a transmission image. If there are three + frames, the first one is assumed to be probe with atoms (pwa), the second + one probe without atoms (pwoa) and the third one a dark field (df). + For four frames, it is assumed to be (pwoa, pwa, df1, df2). + For six frames, the first two are discarded (they are for clearing the + CCD charge on the Coolsnap camera), three to six are (pwoa, pwa, df1, df2). + + **Inputs** + + * img_name: string containing the full path to an image + + **Outputs** + + * img_array: 3D array, containing the three or four frames of the image, + in the order (pwa, pwoa, df, df2). + + **Notes** + + The datatype has to be set to float in Winview, otherwise there is a + strange problem reading the frames; support for 16-bit tif files is + lacking a bit in PIL. Note: when pil_lite is available this does work. + + The same support is lacking in MS .Net apparently, hence the weird check + for 3-channel TIFFs. What happens here is that XCamera can output multipage + 8-bit RGB TIFFs. Each page is of shape (M,N,3), where the 8-bit color + channels combine to output 24-bit B/W data. + + """ + + imglist = list_of_frames(img_name) + + if len(imglist)==1: + return imglist[0] + elif len(imglist) in [3, 4]: + # make an array from the list of frames, with shape (img[0], img[1], 3) + img_array = np.dstack(imglist) + elif len(imglist)==6: + # get rid of first two frames, they're junk. then swap pwoa, pwa. + img_array = np.dstack([imglist[3], imglist[2], imglist[5], imglist[4]]) + elif len(imglist)==8: + # get rid of first two frames, then 2x PWA, 2x DF, 2x PWOA (swap DF, PWOA) + img_array = np.dstack([imglist[2], imglist[3], imglist[6], imglist[7], + imglist[4], imglist[5]]) + else: + raise ImportError, 'Number of frames is %s' %(len(imglist)) + + return img_array + + +def import_rawframes(img_name): + """Opens an image file containing three frames + + The datatype has to be set to float in Winview, otherwise there is a + strange problem reading the frames; support for 16-bit tif files is + lacking a bit in PIL. + + **Inputs** + + * img_name: string containing the full path to an image + + **Outputs** + + * img_array: 3D array, containing the three frames of the image + + """ + + img = Image.open(img_name) + # note the reversed order because Image and asarray have reversed order + img_array = np.zeros((img.size[1], img.size[0], 3), dtype=np.float32) + + img_array[:, :, 0] = np.asarray(img, dtype=np.float32) + try: + img.seek(1) # next frame + img_array[:, :, 1] = np.asarray(img, dtype=np.float32) + img.seek(2) # next frame + img_array[:, :, 2] = np.asarray(img, dtype=np.float32) + except EOFError: + print 'This image contains less than 3 frames' + return None + + return img_array + + +def import_rawimage(img_name): + """Opens an image file and returns it as an array.""" + + im = Image.open(img_name) + + return np.asarray(im) + + +def import_xcamera(img_name, ext='xraw'): + """Load the three .xraw files from XCamera + + It is assumed that the file with extension .xraw0 contains the probe + with atoms (pwa), the one with extension .xraw1 the probe without atoms + (pwoa), and the one with extension .xraw2 the dark field (df). + + **Inputs** + + * img_name: str, name of the image with or without extension + (the extension is stripped and replaced by `ext`. + * ext: str, the extension of the XCamera file. Normally xraw or xroi. + + **Outputs** + + * raw_array: 3D array, containing the three raw frames (pwa, pwoa, df) + + """ + + if ext=='xraw': + rawext = ['.xraw0', '.xraw1', '.xraw2'] + elif ext=='xroi': + rawext = ['.xroi0', '.xroi1', '.xroi2'] + else: + raise ValueError, 'Unknown extension for XCamera file' + + basename = os.path.splitext(img_name)[0] + try: + pwa = np.loadtxt(''.join([basename, rawext[0]]), dtype=np.int16) + pwoa = np.loadtxt(''.join([basename, rawext[1]]), dtype=np.int16) + df = np.loadtxt(''.join([basename, rawext[2]]), dtype=np.int16) + except IOError, e: + print e + return None + + raw_array = np.dstack([pwa, pwoa, df]) + + return raw_array + + +def save_tifimage(imgarray, fname, dirname=None): + """Save a single image in TIF format + + **Inputs** + + * imgarray: 2D array, containing a single frame image + * fname: str, filename of the file to save, optionally including + the full path to the directory + * dirname: str, if not None, fname will be appended to dirname to + obtain the full path of the file to save. + + **Notes** + + Multiple frame tif images are not supported. For such data hdf5 is the + recommended format. + + """ + + if dirname: + fname = os.path.join(dirname, fname) + fname = ''.join([os.path.splitext(fname)[0], '.tif']) + + im = sp.misc.toimage(imgarray, mode='F') + im.save(fname, mode='F') + + +def save_hdfimage(imgarray, fname, dirname=None): + """Save an image to an hdf5 file + + **Inputs** + + * imgarray: ndarray, containing the image data. If the array is 2D, + it is assumed that this is a single frame image. If it is + 3D, the frames will be saved as separate arrays: + ('pwa', 'pwoa', 'df'), and if there is a fourth frame this + is df2. + * fname: str, filename of the file to save, optionally including + the full path to the directory + * dirname: str, if not None, fname will be appended to dirname to + obtain the full path of the file to save. + + """ + + if dirname: + fname = os.path.join(dirname, fname) + fname = ''.join([os.path.splitext(fname)[0], '.h5']) + + # Open a new empty HDF5 file + h5file = tables.openFile(fname, mode='w') + + if len(imgarray.shape)==2: + # Get the root group + root = h5file.root + # Save image in the HDF5 file + h5file.createArray(root, 'img', imgarray, title='Transmission image') + + elif len(imgarray.shape)==3: + # image frames to be saved + pwa = imgarray[:, :, 0] + pwoa = imgarray[:, :, 1] + df = imgarray[:, :, 2] + # Get the root group + root = h5file.createGroup("/", 'rawframes', 'The raw image frames') + # Save image in the HDF5 file + h5file.createArray(root, 'pwa', pwa, title='Probe with atoms') + h5file.createArray(root, 'pwoa', pwoa, title='Probe without atoms') + h5file.createArray(root, 'df', df, title='Dark field') + + if imgarray.shape[2] > 3: + df2 = imgarray[:, :, 3] + h5file.createArray(root, 'df2', df2, title='Dark field 2') + + else: + print 'imgarray does not have the right dimensions, shape is: ', \ + imgarray.shape + + h5file.close() + + +def load_hdfimage(fname, dirname=None, ext_replace=True): + """Load an image from an hdf5 file + + **Inputs** + + * fname: str, filename of the file to save, optionally including + the full path to the directory + * dirname: str, if not None, fname will be appended to dirname to + obtain the full path of the file to save. + * ext_replace: bool, if True replaces the extension of fname with `.h5` + + **Outputs** + + * transimg: ndarray, the image data + + """ + + if dirname: + fname = os.path.join(dirname, fname) + if ext_replace: + fname = ''.join([os.path.splitext(fname)[0], '.h5']) + + h5file = tables.openFile(fname, mode='r') + + try: + transimg = np.asarray(h5file.root.img) + return transimg + + except tables.NoSuchNodeError: + pwa = np.asarray(h5file.root.rawframes.pwa) + pwoa = np.asarray(h5file.root.rawframes.pwoa) + df = np.asarray(h5file.root.rawframes.df) + imgarray = np.dstack([pwa, pwao, df]) + return imgarray + + finally: + h5file.close() + + +def convert_xcamera_to_hdf5(imglist, ext='xraw'): + """Convert every file in imglist to an hdf5 file. + + The raw files are saved in the hdf5 file as + `root.rawframes.pwa`, `root.rawframes.pwoa`, `root.rawframes.df`. + Their dtype is uint16, which results in files of a third smaller than + the xcamera text files. + + **Inputs** + + * imglist: list of str, paths to .xraw0 files + * ext: str, the extension of the XCamera file. Normally xraw or xroi. + + """ + + for img in imglist: + imgarray = import_xcamera(img, ext=ext) + save_hdfimage(imgarray, img) diff --git a/filelist/odysseus/imageprocess.py b/filelist/odysseus/imageprocess.py new file mode 100644 index 0000000..99587b6 --- /dev/null +++ b/filelist/odysseus/imageprocess.py @@ -0,0 +1,590 @@ +#!/usr/bin/env python +"""Image processing functions + +Some functionality is independent of the type of image, for example +smoothing, thresholding and interpolation. Other functionality is specific +to cold atom experiments, for example calculating optical density and +transmission for absorption images. + +""" + +import os + +import scipy as sp +import scipy.ndimage as ndimage +import numpy as np +import Image +import matplotlib as mpl +import pylab + + +def trans2od(transimg, maxod=3.5): + """Calculates the optical density image from a transmission image + + For pixels with strange values due to noise, replace the value of that pixel + by the maximum OD that can be experimentally measured. + + """ + + odimg = np.where(transimg>np.exp(-maxod), -np.log(transimg), maxod) + + return odimg + + +def od2trans(odimg, maxod=3.5): + """Calculates the transmission image from an optical density image + + For pixels with strange values due to noise, replace the value of that pixel + by the maximum OD that can be experimentally measured. + + """ + + transimg = np.where(odimg od_prof.size: + raise ValueError, 'Input array is smaller than min_cutoff' + + return cutoff + + +def radial_interpolate(img, com, dr, phi=None, elliptic=None, full_output=False): + """Does radial averaging around the center of mass of the image. + + Radial averaging of the image data on circles spaced by dr around the + center of mass. The number of points on each circle is dphi*sqrt(i+1), + with i the circle index. A bilinear interpolation method is used. + + **Inputs** + + * img: 2D array, normally containing image data + * com: 1D array with two elements, the center of mass coordinates in + pixels + * dr: radial step size in pixels + + **Outputs** + + * rcoord: 1D array containing the radial coordinate + * rad_profile: 1D array containing the averaged profile + + **Optional inputs** + + * phi: 1D array, the angles along which line profiles are taken. More + values in phi means a more precise radial average; default is + 2*pi times the maximum radius in pixels + * elliptic: tuple, containing two elements. the first one is the + ellipticity (or ratio of major and minor axes), the second one is the + angle by which the major axis is rotated from the y-axis. + * full_output: bool, selects whether rprofiles and phi are returned + + """ + + xsize, ysize = img.shape + # number of used points in radial direction, stops if image edge is reached + rmax = np.array([xsize-com[0], com[0], ysize-com[1], com[1]]).min() + rcoord = np.arange(0, rmax, dr) + if not phi: + phi = np.linspace(0, 2*np.pi, rmax*np.pi) + + rprofiles = lineprofiles(img, com, rcoord, phi, elliptic=elliptic) + rad_profile = rprofiles.mean(axis=1) + + if full_output: + return rcoord, rad_profile, rprofiles, phi + else: + return rcoord, rad_profile + + +def lineprofiles(img, com, rcoord, phi, elliptic=None): + """Generate radial profiles around center of mass + + Line profiles without any averaging are generated. This is useful for + comparing the radially averaged profile with, to make sure that that is a + valid procedure. + + **Inputs** + + * img: 2D array, normally containing image data + * com: 1D array with two elements, the center of mass coordinates in pixels + * rcoord: 1D array, radial coordinate for line profiles + this is usually obtained from radial_interpolate + * phi: 1D array, angles along which line profiles are required + + **Outputs** + + * rprofiles: 2D array, containing radial profiles along angles + + **Optional inputs** + + * elliptic: tuple, containing two elements. the first one is the + ellipticity (or ratio of major and minor axes), the second one is the + angle by which the major axis is rotated from the y-axis. This should + be the same as used for radial averaging. + + **Notes** + + The form used for mapping an ellipse to (x,y) coordinates is: + x = a\cos\phi\cos\alpha - b\sin\phi\sin\alpha + y = b\sin\phi\cos\alpha + a\cos\phi\sin\alpha + + """ + + indshape = (phi.size, rcoord.size) + + if elliptic: + (ell, rot) = elliptic + else: + (ell, rot) = (1, 0) + + xr = com[0] + (np.ones(indshape)*rcoord).transpose()*np.cos(phi)*np.cos(rot) - \ + ell*(np.ones(indshape)*rcoord).transpose()*np.sin(phi)*np.sin(rot) + yr = com[1] + ell*(np.ones(indshape)*rcoord).transpose()*np.sin(phi)*np.cos(rot) - \ + (np.ones(indshape)*rcoord).transpose()*np.cos(phi)*np.sin(rot) + + rprofiles = bilinear_interpolate(xr, yr, img) + + return rprofiles + + +# move out the plotting part! +def radialprofile_errors(odprofiles, angles, od_prof, od_cutoff, \ + showfig=False, savefig_name=None, report=True): + """Calculate errors in radial profiles as a function of angle + + **Inputs** + + * odprofiles: 2D array, containing radial OD profiles along angles + * angles: 1D array, angles at which radial profiles are taken + (zero is postive x-axis) + * od_prof: 1D array, radially averaged optical density + * od_cutoff: integer, index of profiles at which maximum fit-OD is reached + + **Outputs** + + * av_err: float, sum of absolute values of errors in errsum + + **Optional inputs** + + * showfig: bool, determines if a figure is shown with density profile + and fit + * report: bool, if True print the sums of the mean and rms errors + * savefig_name: string, if not None and showfig is True, the figure is + not shown but saved as png with this string as filename. + + """ + + err = (odprofiles[od_cutoff:, :].transpose() - \ + od_prof[od_cutoff:]).sum(axis=1) + + av_err = np.abs(err).mean() + + if report: + print 'mean error is ', err.mean() + print 'rms error is ', av_err + + if showfig: + # angular plot of errors, red for positive, blue for negative values + pylab.figure() + poserr = pylab.find(err>0) + negerr = pylab.find(err<0) + + pylab.polar(angles[negerr], np.abs(err[negerr]), 'ko', \ + angles[poserr], np.abs(err[poserr]), 'wo') + pylab.title('Angular dependence of fit error') + pylab.text(np.pi/2+0.3, np.abs(err).max()*0.85, \ + r'$\sum_{\phi}|\sum_r\Delta_{OD}|=%1.1f$'%av_err) + if not savefig_name: + pylab.show() + else: + pylab.savefig(''.join([os.path.splitext(savefig_name)[0], '.png'])) + + return av_err + + +def bilinear_interpolate(xr, yr, img): + """Do a bi-linear interpolation to get the value at image coordinates + + **Inputs** + * xr: array-like, the x-coordinates of the point to be interpolated + * yr: array-like, the y-coordinates of the point to be interpolated + * img: 2d-array, the image data + + **Outputs** + * ans: array-like, the result of the interpolation + + """ + + ans = ndimage.map_coordinates(img, np.array([xr, yr]), order=1, \ + mode='nearest') + return ans + + +def imgslice(img, cpoint, angle=0, width=None): + """Take a line profile through the centerpoint + + **Inputs** + * img: 2D array, the image data + * cpoint: 1D array, the center point coordinates of the required slice + + **Outputs** + * lprof_coord: 1D array, the slice indices in units of pixels + * lprof: 1D array, the slice data + + **Optional inputs** + + * angle: float, the angle under which the slice is taken in degrees + * width: float, the width over which the slice is averaged + + """ + + angle = angle*np.pi/180 # deg to rad + # determine coefficients a and b of center line y=ax+b + a = np.tan(angle) + b = cpoint[1] - cpoint[0]*a + # max of indices + xmax, ymax = img.shape + xmax = xmax-1 + ymax = ymax-1 + # determine begin and end points of center line + if 0 < b < ymax: + startpt = (0, b) + elif 0 < -b/a < xmax: + startpt = (-b/a, 0) + else: + startpt = ((ymax-b)/a, ymax) + if 0 < a*xmax+b < ymax: + stoppt = (xmax, a*xmax+b) + elif 0 < (ymax-b)/a < xmax: + stoppt = ((ymax-b)/a, ymax) + else: + stoppt = (-b/a, 0) + + def dist(a, b): + """Distance between points""" + d = a - b + return np.sqrt(np.dot(d,d)) + + slicelen = min(dist(startpt, cpoint), dist(stoppt, cpoint))*0.8 + + # generate the slice coordinates, discard endpoint (safer for interpolation) + npts = 3 + xslice = cpoint[0] + np.arange(-slicelen, slicelen, 1./npts)*np.cos(angle) + yslice = cpoint[1] + np.arange(-slicelen, slicelen, 1./npts)*np.sin(angle) + + if width: + try: + perpstep = np.linspace(-width/2., width/2., num=round(width)) + lprof = np.zeros((xslice.size, perpstep.size)) + for i in xrange(perpstep.size): + xx_slice = xslice + perpstep[i]*np.cos(angle+np.pi/2) + yy_slice = yslice + perpstep[i]*np.sin(angle+np.pi/2) + lprof[:, i] = bilinear_interpolate(xx_slice, yy_slice, img) + lprof = lprof.mean(axis=1) + except IndexError: + print 'Index out of bounds, may be an error in the if/else code above' + raise IndexError + else: + xslice = xslice[1:-1] + yslice = yslice[1:-1] + lprof = bilinear_interpolate(xslice, yslice, img) + + lprof_coord = np.arange(lprof.size)-lprof.size/2. + + return lprof_coord, lprof, npts + + +def mirror_line(linedata, negative_mirror=False): + """Mirrors a 1D array around its first element + + **Inputs** + + * linedata: 1D array, the array to be mirrored + * negative_mirror: bool, if True the mirrors elements are multiplied by + -1. This is useful to mirror the x-axis of a plot. + + **Outputs** + + * mirrored: 1D array, the output array, which is now symmetric around its + midpoint. + + """ + + mir_size = linedata.shape[0] -1 + mirrored = np.zeros(mir_size*2 + 1) + mirror_idx = np.arange(mir_size, 0, -1) + mirrored[:mir_size] = linedata[mirror_idx] + mirrored[mir_size:] = linedata + + if negative_mirror: + mirrored[:mir_size] *= -1 + + return mirrored + + +def smooth(x, window_len=10, window='hanning'): + """Smooth the data using a window with requested size. + + This method is based on the convolution of a scaled window with the signal. + The signal is prepared by introducing reflected copies of the signal + (with the window size) in both ends so that transient parts are minimized + in the begining and end part of the output signal. + + Adapted from the Scipy Cookbook by Ralf Gommers. + + **Inputs** + + * x: 1D array, data that needs to be smoothed + + **Outputs** + + * x_smooth: 1D array, the smoothed signal + + **Optional inputs** + + * window_len: int, the size of the smoothing window + * window: str, the type of window from 'flat', 'hanning', 'hamming', + 'bartlett', 'blackman'. A flat window will produce a + moving average smoothing. + + """ + + if x.ndim != 1: + raise ValueError, "smooth only accepts 1 dimension arrays." + + if x.size < window_len: + window_len = round(x.size/2) + + if window_len<3: + return x + + if not window in ['flat', 'hanning', 'hamming', 'bartlett', 'blackman']: + raise ValueError, \ + """Window is on of 'flat', 'hanning', 'hamming', + 'bartlett', 'blackman'""" + + s = np.r_[2*x[0]-x[window_len:1:-1], x, 2*x[-1]-x[-1:-window_len:-1]] + + if window=='flat': # moving average + w = np.ones(window_len, 'd') + else: + w = eval('np.'+window+'(window_len)') + + y = np.convolve(w/w.sum(), s, mode='same') + x_smooth = y[window_len-1:-window_len+1] + + return x_smooth + + +def maxod_correct(odimg, odmax): + """Corrects calculated OD from an absorption image for finite OD_max + + This idea was taken from Brian DeMarco's thesis, but it does not seem to + make much of a difference at low OD. For high-OD images it causes errors + because there will be data points with measured OD higher than the maximum + observable OD due to noise in the image. + + It is left in here for completeness, but it is recommended to not use this + method. Instead, images should be taken in a regime where this correction + is negligibly small anyway (i.e. below an OD of 1.5). + + """ + + c = np.exp(odmax) - 1 + realOD = -np.log((c+1.)/c*np.exp(-odimg) - 1./c) + + return realOD + + +def normalize_img(img, com, size): + """Mask off the atoms, then fit linear slopes to the image and normalize + + We assume that there are no atoms left outside 1.5 times the size. This + seems to be a reasonable assumption, it does not influence the result of + the normalization. + + **Inputs** + + * img: 2D array, containing the image + * com: tuple, center of mass coordinates + * size: float, radial size of the cloud + + **Outputs** + + * normimg: 2D array, the normalized image + + """ + + xmax, ymax = img.shape + + # create mask + x_ind1 = round(com[0] - 1.5*size) + x_ind2 = round(com[0] + 1.5*size) + y_ind1 = round(com[1] - 1.5*size) + y_ind2 = round(com[1] + 1.5*size) + + # fit first order polynomial along x and y (do not use quadratic terms!!) + if x_ind1>0 and x_ind20 and y_ind2 + + + + + + + + + +
+
+

Start Time:

+

End Time:

+

Sequence Name Contains:

+

Description Contains:

+

Variable Name Contains: and + + +

+

+
+
+
+

+ + \ No newline at end of file diff --git a/htroot/processing.htm b/htroot/processing.htm new file mode 100644 index 0000000..0e02d6f --- /dev/null +++ b/htroot/processing.htm @@ -0,0 +1,221 @@ + + + + + + + + + + + +
+
+

Image Type:

+

+ +
+

+ +
+

+ +
+
+ + +
+ +
+ + \ No newline at end of file diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..ad1c082 --- /dev/null +++ b/settings.py @@ -0,0 +1,98 @@ +# Django settings for clinamen project. + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. + 'NAME': 'filelist', # Or path to database file if using sqlite3. + 'USER': 'root', # Not used with sqlite3. + 'PASSWORD': 'w0lfg4ng', # Not used with sqlite3. + 'HOST': 'localhost', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + } +} + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# On Unix systems, a value of None will cause Django to use the same +# timezone as the operating system. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/Chicago' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# If you set this to False, Django will not format dates, numbers and +# calendars according to the current locale +USE_L10N = True + +# Absolute filesystem path to the directory that will hold user-uploaded files. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = './static/' + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash if there is a path component (optional in other cases). +# Examples: "http://media.lawrence.com", "http://example.com/media/" +MEDIA_URL = 'static/' + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/media/' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = '81_-&kt2=s7*u!xs40vgxp%rv^7k=*a(z23g5a6w)%n0%_-sg^' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +# 'django.template.loaders.eggs.Loader', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', +) + +ROOT_URLCONF = 'clinamen.urls' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + "C:/django/django/clinamen/templates" +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'clinamen.filelist', + # Uncomment the next line to enable the admin: + # 'django.contrib.admin', + # Uncomment the next line to enable admin documentation: + # 'django.contrib.admindocs', +) diff --git a/templates/imglist.html b/templates/imglist.html new file mode 100644 index 0000000..689ca2d --- /dev/null +++ b/templates/imglist.html @@ -0,0 +1,24 @@ + + + +{% for imginfo in img_list %} + + +{% endfor %} +
Image time +Processed frames +Raw frames +
{{imginfo.time|date:"M-d-Y H:i:s"}} + +{% for thumbinfo in imginfo.thumbinfo_set.all %} +{% if thumbinfo.framenumber < 0 %} + +{% endif %} +{% endfor %} + +{% for thumbinfo in imginfo.thumbinfo_set.all %} +{% if thumbinfo.framenumber >= 0 %} + +{% endif %} +{% endfor %} +
\ No newline at end of file diff --git a/templates/methodlist.html b/templates/methodlist.html new file mode 100644 index 0000000..ba0907e --- /dev/null +++ b/templates/methodlist.html @@ -0,0 +1,18 @@ +
Image type: {{activetype.name}}
+ +

Add a processing method: + +

+

+ +

Active methods:

+{% for method in active_list %} +

Module name: {{method.modulename}} Function name: {{method.name}}

+{% endfor %} + +

+ diff --git a/templates/paramlist.html b/templates/paramlist.html new file mode 100644 index 0000000..5389631 --- /dev/null +++ b/templates/paramlist.html @@ -0,0 +1,15 @@ +
Parameters for method: {{activemethod.modulename}}.{{activemethod.name}}
+ +{% for arg, value in arg_params.items %} +

{{arg}}: +{% endfor %} + +{% for arg, value in arg_rois.items %} +

{{arg}}: +{% endfor %} + +

+ + + + diff --git a/templates/runloglist.html b/templates/runloglist.html new file mode 100644 index 0000000..33d7469 --- /dev/null +++ b/templates/runloglist.html @@ -0,0 +1,47 @@ + + + +{% for runloginfo in runlog_list %} + + +{% endfor %} +
Run time +Sequence path +Duration +Description +Variables +Image time +Image type +Raw frames +Processed frames +
{{runloginfo.time|date:"M-d-Y H:i:s"}} +{{runloginfo.sequencepath}} +{{runloginfo.sequenceduration}} +{{runloginfo.description}} + +{% for varvalue in runloginfo.variablevalue_set.all %} +{{varvalue.name}} = {{varvalue.value}}
+{% endfor %} +
+{% for imginfo in runloginfo.imageinfo_set.all|slice:":2" %} +{{imginfo.time}}
+{% endfor %} +
+{% for imginfo in runloginfo.imageinfo_set.all|slice:":2" %} +{{imginfo.imgtype.name}}
+{% endfor %} +
+{% for imginfo in runloginfo.imageinfo_set.all|slice:":2" %} +{% for frameinfo in imginfo.rawframe_set.all %} + +{% endfor %} +
+{% endfor %} +
+{% for imginfo in runloginfo.imageinfo_set.all|slice:":2" %} +{% for frameinfo in imginfo.processedframe_set.all %} + +{% endfor %} +
+{% endfor %} +
\ No newline at end of file diff --git a/templates/sampleimages.html b/templates/sampleimages.html new file mode 100644 index 0000000..3d4d9a2 --- /dev/null +++ b/templates/sampleimages.html @@ -0,0 +1,3 @@ +{% for frameinfo in sampleimage.rawframe_set.all %} + +{% endfor %} \ No newline at end of file diff --git a/urls.py b/urls.py new file mode 100644 index 0000000..7c42b64 --- /dev/null +++ b/urls.py @@ -0,0 +1,31 @@ +from django.conf.urls.defaults import * + +# Uncomment the next two lines to enable the admin: +# from django.contrib import admin +# admin.autodiscover() + +urlpatterns = patterns('', + (r'^clinamen/images/([A-Za-z]+)', 'clinamen.filelist.views.imagelist'), + (r'^clinamen/runlogs/([A-Za-z]+)', 'clinamen.filelist.views.runloglist'), + (r'^clinamen/filters/([A-Za-z]+)', 'clinamen.filelist.views.filteredlist'), + # Example: + # (r'^clinamen/', include('clinamen.foo.urls')), + + (r'^clinamen/imgproc/methodlist', 'clinamen.filelist.views.methodlist'), + (r'^clinamen/imgproc/remove', 'clinamen.filelist.views.removemethod'), + (r'^clinamen/imgproc/add', 'clinamen.filelist.views.addmethod'), + (r'^clinamen/imgproc/edit', 'clinamen.filelist.views.editparams'), + (r'^clinamen/imgproc/update', 'clinamen.filelist.views.updateparams'), + (r'^clinamen/imgproc/process', 'clinamen.filelist.views.processtype'), + + (r'^clinamen/imgproc/countsample', 'clinamen.filelist.views.countsample'), + (r'^clinamen/imgproc/getsample', 'clinamen.filelist.views.getsample'), + (r'^clinamen/imgproc/setsample', 'clinamen.filelist.views.setsample'), + + # Uncomment the admin/doc line below to enable admin documentation: + # (r'^admin/doc/', include('django.contrib.admindocs.urls')), + + # Uncomment the next line to enable the admin: + # (r'^admin/', include(admin.site.urls)), + (r'^static/(?P.*)$', 'django.views.static.serve', {'document_root': 'static'}), +) \ No newline at end of file