Partial fix to #22. Laydi will require a project directory to run. If the

project directory does not exist, i.e. if a new project should be created, the
new -n switch is required. New syntax to run laydi is now
{{{
laydi [options] </path/to/project>
}}}
This commit is contained in:
Einar Ryeng 2011-03-06 23:08:09 +00:00
parent b5d2e8e181
commit dc8da8823e
2 changed files with 314 additions and 142 deletions

View File

@ -3,7 +3,7 @@
from getopt import getopt from getopt import getopt
import os import os
import sys import sys
from laydi import laydi, projectview, workflow, main from laydi import laydi, project, projectview, workflow, main
#import workflows #import workflows
from laydi import cfgparse from laydi import cfgparse
import optparse import optparse
@ -73,11 +73,20 @@ def parse_options():
action='store_true', action='store_true',
help='Generate configuration file ~/.laydi if it does not exist.') help='Generate configuration file ~/.laydi if it does not exist.')
op.add_option('-n', '--new-project',
action='store_true',
help='Create new project directory.')
for cf in conf_files: for cf in conf_files:
if os.path.isfile(cf): if os.path.isfile(cf):
cp.add_file(cf) cp.add_file(cf)
return cp.parse(op) options, params = cp.parse(op)
if len(params) != 1:
print "error: project directory must be specified."
sys.exit(1)
return options, params
if __name__ == '__main__': if __name__ == '__main__':
import gtk import gtk
@ -86,6 +95,8 @@ if __name__ == '__main__':
gnome.program_init(PROGRAM_NAME, VERSION) gnome.program_init(PROGRAM_NAME, VERSION)
options, params = parse_options() options, params = parse_options()
## Workflow setup
main.options = options main.options = options
for dir in main.options.workflowdir.split(';'): for dir in main.options.workflowdir.split(';'):
@ -112,8 +123,18 @@ if __name__ == '__main__':
main.set_options(options) main.set_options(options)
app = laydi.LaydiApp() app = laydi.LaydiApp()
## Project setup
prjroot = params[0]
if not project.is_project_directory(prjroot):
if options.new_project:
project.make_project_directory(prjroot)
else:
print "error: project directory not found: %s" % prjroot
print "notice: use the -n option to make a new project"
sys.exit(2)
main.set_application(app) main.set_application(app)
main.set_projectview(projectview.ProjectView()) main.set_projectview(projectview.ProjectView(prjroot))
app.set_projectview(main.projectview) app.set_projectview(main.projectview)
app.show() app.show()

View File

@ -1,154 +1,305 @@
import os import os, os.path
import scipy import sys
import gobject import configobj
import gtk import time
import laydi
import logger, dataset, plots, main
class Project: import dataset
"""A Project contains datasets, selections etc.
The project, of which the application has only one at any given time, NAME = "laydi-cmd"
is the container for all datasets, plots and selections in use. The data VERSION = "0.1.0"
in the project is organized in a gtk.TreeStrore that is displayed in the PROJECT_VERSION_STRING = "Laydi project version 1"
navigator.
def is_project_directory(dirname):
"""Verifies that a directory is a laydi project"""
if not os.path.isdir(dirname):
return False
## Verify that the version is correct.
version_fn = os.path.join(dirname, "VERSION")
if not os.path.exists(version_fn):
return False
fd = open(version_fn)
line = fd.readline()
fd.close()
if line.strip() != PROJECT_VERSION_STRING:
return False
## Require directories to be present.
if not os.path.isdir(os.path.join(dirname, "annotations")):
return False
if not os.path.isdir(os.path.join(dirname, "data")):
return False
if not os.path.isdir(os.path.join(dirname, "selections")):
return False
if not os.path.isdir(os.path.join(dirname, "exports")):
return False
## If no tests failed, return True
return True
def make_project_directory(dirname, force=False):
"""Creates a project directory
force: ignore that directory exists and proceed anyway.
""" """
if os.path.exists(dirname) and not force:
return False
def __init__(self, name="Testing"): rootdir = dirname
self.data_tree = gtk.TreeStore(str, anndir = os.path.join(dirname, "annotations")
str, seldir = os.path.join(dirname, "selections")
object, datadir = os.path.join(dirname, "data")
str, exportdir = os.path.join(dirname, "exports")
str, version_file_path = os.path.join(dirname, "VERSION")
gobject.TYPE_OBJECT,
float)
os.makedirs(rootdir)
for d in [anndir, seldir, datadir, exportdir]:
os.mkdir(d)
fd = open(version_file_path, "w")
print >> fd, PROJECT_VERSION_STRING
fd.close()
class Universe(object):
"""A Universe is a collection of all existing identifiers in a set of datasets"""
def __init__(self):
self.refcount = {}
def register_dim(self, dim):
"""Increase reference count for identifiers in Dimension object dim"""
d = self.refcount.get(dim.name, None)
if d == None:
d = {}
self.refcount[dim.name] = d
for i in dim:
d[i] = d.get(i, 0) + 1
def register_ds(self, ds):
"""Increase reference count for identifiers in all Dimensions of dataset ds"""
for dim in ds.dims:
self.register_dim(dim)
def unregister_dim(self, dim):
"""Update reference count for identifiers in Dimension object dim
Update reference count for identifiers in Dimension object dim, and remove all
identifiers with a reference count of 0, as they do not (by definition) exist
any longer.
"""
ids = self.refcount[dim.name]
for i in dim:
refcount = ids[i]
if refcount == 1:
ids.pop(i)
else:
ids[i] -= 1
if len(ids) == 0:
self.refcount.pop(dim.name)
def unregister_ds(self, ds):
"""Update reference count for identifiers along Dimensions in Dataset ds.
Update reference count for identifiers along all Dimensions in
Dataset ds, and remove all identifiers with a reference count of 0,
as they do not (by definition) exist any longer.
"""
for dim in ds:
self.register_dim(dim)
def register(self, obj):
if isinstance(obj, Dataset):
self.register_ds(obj)
else:
self.register_dim(obj)
def unregister(self, obj):
if isinstance(obj, Dataset):
self.unregister_ds(obj)
else:
self.unregister_dim(obj)
def __getent___(self, dimname):
return set(self.references[dimname].keys())
def __iter__(self):
return self.references.keys().__iter__()
class Dimension(object):
"""A Dimension represents the set of identifiers an object has along an axis.
"""
def __init__(self, name, ids=[]):
self.name = name self.name = name
self.dim_names = [] self.idset = set(ids)
self._selection_observers = [] self.idlist = list(ids)
self._dataset_observers = []
self.current_data = []
self.datasets = []
self.sel_obj = dataset.Selection('Current Selection')
self.selections = []
self._last_selection = None
self._dataset_iter_map = {}
def add_selection_observer(self, observer): if len(self.idset) != len(self.idlist):
self._selection_observers.append(observer) raise Exception("Duplicate identifiers are not allowed")
observer.selection_changed(None, self.get_selection())
def notify_selection_listeners(self, dim_name): def __getitem__(self, element):
"""Notifies observers""" return self.idlist[element]
for observer in self._selection_observers:
observer.selection_changed(dim_name, self.get_selection())
def add_dataset_observer(self, observer): def __getslice__(self, start, end):
self._dataset_observers.append(observer) return self.idlist[start:end]
observer.dataset_changed()
def notify_dataset_listeners(self): def __contains__(self, element):
"""Notifies observers when new datasets are added""" return self.idset.__contains__(element)
for observer in self._dataset_observers:
observer.dataset_changed()
def set_selection(self, dim_name, selection): def __str__(self):
"""Sets a current selection and notify observers""" return "%s: %s" % (self.name, str(self.idlist))
self.sel_obj[dim_name] = set(selection)
self.notify_selection_listeners(dim_name)
self._last_selection = selection
def get_selection(self): def __len__(self):
"""Returns the current selection object""" return len(self.idlist)
return self.sel_obj
def delete_data(self, it): def __iter__(self):
"""Delete elements from the project.""" return iter(self.idlist)
child = self.data_tree.iter_children(it)
while child != None:
c = self.data_tree.iter_next(child)
self.delete_data(child)
child = c
main.application.main_view.remove_view(self.data_tree.get(it, 2)[0])
self.data_tree.remove(it)
def add_data(self, parents, data, fun='Function'): def intersection(self, dim):
"""Adds a set of data and plots to the navigator. return self.idset.intersection(dim.idset)
This method is usually called after a Function in a workflow def as_tuple(self):
has finished and returns its output.""" return (self.name, self.idlist)
if len(parents) > 0:
parent_iter = self._dataset_iter_map[parents[0]] class Directory(object):
def __init__(self, path):
self.path = path
self.files = set()
self.timestamp = -1
self.update()
def update(self):
now = time.time()
newfiles = set(os.listdir(self.path))
for fn in newfiles - self.files:
if os.path.isdir(os.path.join(self.path, fn)):
self.dir_created(fn)
else: else:
parent_iter = None self.file_created(fn)
for fn in self.files - newfiles:
# Add the function node to the tree if os.path.isdir(os.path.join(self.path, fn)):
icon = laydi.icon_factory.get("folder_grey") self.dir_deleted(fn)
it = self.data_tree_insert(parent_iter, fun, None, None, "black", icon)
# Add all returned datasets/plots/selections
for d in data:
# Any kind of dataset
if isinstance(d, dataset.Dataset):
if isinstance(d, dataset.GraphDataset):
icon = laydi.icon_factory.get("graph_dataset")
elif isinstance(d, dataset.CategoryDataset):
icon = laydi.icon_factory.get("category_dataset")
else: else:
icon = laydi.icon_factory.get("dataset") self.file_removed(fn)
for fn in self.files.intersection(newfiles):
filepath = os.path.join(self.path, fn)
if os.path.getctime(filepath) >= self.timestamp:
if os.path.isdir(filepath):
self.dir_changed(fn)
else:
self.file_changed(fn)
self.files = newfiles
self.timestamp = now
self.add_dataset(d) def file_created(self, fn):
self.data_tree_insert(it, d.get_name(), d, None, "black", icon) print "file created: %s" % fn
pass
# Any kind of plot def file_changed(self, fn):
elif isinstance(d, plots.Plot): print "file changed: %s" % fn
icon = laydi.icon_factory.get("line_plot") pass
self.data_tree_insert(it, d.get_title(), d, None, "black", icon)
d.set_selection_listener(self.set_selection)
self._selection_observers.append(d)
# Selections are not added to the data tree def file_removed(self, fn):
elif isinstance(d, dataset.Selection): print "file removed: %s" % fn
self.add_selection(d) pass
def data_tree_insert(self, parent, text, data, bg, fg, icon, selected = 0): def dir_created(self, fn):
"""Inserts data into the tree view. print "directory created: %s" % fn
@param text: The title of the object. pass
@param data: A dataset, plot or function object.
@param bg: Background color. def dir_changed(self, fn):
@param fg: Foreground (font) color. print "directory changed: %s" % fn
@param icon: Pixmap icon. pass
def dir_removed(self, fn):
print "directory removed: %s" % fn
pass
class DataDirectory(Directory):
def __init__(self, dirname, project):
self.project = project
self.datasets= []
self.dsfiles = {}
Directory.__init__(self, dirname)
def file_created(self, fn):
"""Called from update() when new files are created.
Load new datasets that have appeared since last update.
""" """
tree = self.data_tree filepath = os.path.join(self.path, fn)
it = tree.append(parent) name, ext = os.path.splitext(fn)
tree[it] = [text, type(data), data, bg, fg, icon, selected] if ext == ".ftsv":
self._dataset_iter_map[data] = it ds = dataset.read_ftsv(filepath)
return it self.datasets.append(ds)
self.dsfiles[fn] = ds
def add_dataset(self, dataset): def file_changed(self, fn):
"""Appends a new Dataset to the project.""" """Called from update() when files are changed.
logger.log('debug','Adding dataset: %s' %dataset.get_name())
self.datasets.append(dataset)
for dim_name in dataset.get_all_dims():
if dim_name not in self.dim_names:
self.dim_names.append(dim_name)
self.sel_obj[dim_name] = set()
self.notify_selection_listeners(dim_name)
self.notify_dataset_listeners()
def add_selection(self, selection): Delete old dataset and load the new one when dataset files
"""Adds a new selection to the project.""" have been changed.
self.selections.append(selection) """
self.notify_dataset_listeners() filepath = os.path.join(self.path, fn)
name, ext = os.path.splitext(fn)
print self.datasets
if ext == ".ftsv":
oldds = self.dsfiles[fn]
self.datasets.remove(oldds)
def object_at(self, path): ds = dataset.read_ftsv(filepath)
"""Returns the object at a given path in the tree.""" self.datasets.append(ds)
it = self.get_iter(path)
obj = self[it][2]
if obj:
obj.show()
return obj
#def set_current_data(self, obj): self.dsfiles[fn] = ds
# self.current_data = obj print self.datasets
def file_removed(self, fn):
"""Called from update() when a file is deleted
Removes the associated dataset if a dataset file is removed.
"""
filepath = os.path.join(self.path, fn)
name, ext = os.path.splitext(fn)
if ext == ".ftsv":
ds = self.dsfiles[fn]
self.datasets.remove(ds)
self.dsfiles.pop(fn)
def dir_created(self, fn):
"""Called from update() when a subdirectory is created.
Instantiate new handlers for the directory if possible.
"""
filepath = os.path.join(self.path, fn)
def SelectionDirectory(object):
def __init__(self, dirname):
pass
class Project(object):
def __init__(self, dirname):
"""Opens a project directory. The directory must exist and be a valid project."""
## Set path names.
self.rootdir = dirname
self.anndir = os.path.join(dirname, "annotations")
self.seldir = os.path.join(dirname, "selections")
self.datadir = os.path.join(dirname, "data")
self.exportdir = os.path.join(dirname, "exports")
version_file_path = os.path.join(dirname, "VERSION")
self.universe = Universe()
self.data = DataDirectory(self.datadir, self)
def update(self):
print "updating project"
self.data.update()