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
import os
import sys
from laydi import laydi, projectview, workflow, main
from laydi import laydi, project, projectview, workflow, main
#import workflows
from laydi import cfgparse
import optparse
@ -73,11 +73,20 @@ def parse_options():
action='store_true',
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:
if os.path.isfile(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__':
import gtk
@ -86,6 +95,8 @@ if __name__ == '__main__':
gnome.program_init(PROGRAM_NAME, VERSION)
options, params = parse_options()
## Workflow setup
main.options = options
for dir in main.options.workflowdir.split(';'):
@ -112,8 +123,18 @@ if __name__ == '__main__':
main.set_options(options)
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_projectview(projectview.ProjectView())
main.set_projectview(projectview.ProjectView(prjroot))
app.set_projectview(main.projectview)
app.show()

View File

@ -1,154 +1,305 @@
import os
import scipy
import gobject
import gtk
import laydi
import logger, dataset, plots, main
import os, os.path
import sys
import configobj
import time
class Project:
"""A Project contains datasets, selections etc.
The project, of which the application has only one at any given time,
is the container for all datasets, plots and selections in use. The data
in the project is organized in a gtk.TreeStrore that is displayed in the
navigator.
import dataset
NAME = "laydi-cmd"
VERSION = "0.1.0"
PROJECT_VERSION_STRING = "Laydi project version 1"
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"):
self.data_tree = gtk.TreeStore(str,
str,
object,
str,
str,
gobject.TYPE_OBJECT,
float)
rootdir = dirname
anndir = os.path.join(dirname, "annotations")
seldir = os.path.join(dirname, "selections")
datadir = os.path.join(dirname, "data")
exportdir = os.path.join(dirname, "exports")
version_file_path = os.path.join(dirname, "VERSION")
self.name = name
self.dim_names = []
self._selection_observers = []
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 = {}
os.makedirs(rootdir)
for d in [anndir, seldir, datadir, exportdir]:
os.mkdir(d)
def add_selection_observer(self, observer):
self._selection_observers.append(observer)
observer.selection_changed(None, self.get_selection())
fd = open(version_file_path, "w")
print >> fd, PROJECT_VERSION_STRING
fd.close()
def notify_selection_listeners(self, dim_name):
"""Notifies observers"""
for observer in self._selection_observers:
observer.selection_changed(dim_name, self.get_selection())
def add_dataset_observer(self, observer):
self._dataset_observers.append(observer)
observer.dataset_changed()
def notify_dataset_listeners(self):
"""Notifies observers when new datasets are added"""
for observer in self._dataset_observers:
observer.dataset_changed()
class Universe(object):
"""A Universe is a collection of all existing identifiers in a set of datasets"""
def set_selection(self, dim_name, selection):
"""Sets a current selection and notify observers"""
self.sel_obj[dim_name] = set(selection)
self.notify_selection_listeners(dim_name)
self._last_selection = selection
def __init__(self):
self.refcount = {}
def get_selection(self):
"""Returns the current selection object"""
return self.sel_obj
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 delete_data(self, it):
"""Delete elements from the project."""
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 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 add_data(self, parents, data, fun='Function'):
"""Adds a set of data and plots to the navigator.
This method is usually called after a Function in a workflow
has finished and returns its output."""
if len(parents) > 0:
parent_iter = self._dataset_iter_map[parents[0]]
else:
parent_iter = None
# Add the function node to the tree
icon = laydi.icon_factory.get("folder_grey")
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:
icon = laydi.icon_factory.get("dataset")
self.add_dataset(d)
self.data_tree_insert(it, d.get_name(), d, None, "black", icon)
# Any kind of plot
elif isinstance(d, plots.Plot):
icon = laydi.icon_factory.get("line_plot")
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
elif isinstance(d, dataset.Selection):
self.add_selection(d)
def data_tree_insert(self, parent, text, data, bg, fg, icon, selected = 0):
"""Inserts data into the tree view.
@param text: The title of the object.
@param data: A dataset, plot or function object.
@param bg: Background color.
@param fg: Foreground (font) color.
@param icon: Pixmap icon.
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.
"""
tree = self.data_tree
it = tree.append(parent)
tree[it] = [text, type(data), data, bg, fg, icon, selected]
self._dataset_iter_map[data] = it
return it
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 add_dataset(self, dataset):
"""Appends a new Dataset to the project."""
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):
"""Adds a new selection to the project."""
self.selections.append(selection)
self.notify_dataset_listeners()
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 object_at(self, path):
"""Returns the object at a given path in the tree."""
it = self.get_iter(path)
obj = self[it][2]
if obj:
obj.show()
return obj
def register(self, obj):
if isinstance(obj, Dataset):
self.register_ds(obj)
else:
self.register_dim(obj)
#def set_current_data(self, obj):
# self.current_data = 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.idset = set(ids)
self.idlist = list(ids)
if len(self.idset) != len(self.idlist):
raise Exception("Duplicate identifiers are not allowed")
def __getitem__(self, element):
return self.idlist[element]
def __getslice__(self, start, end):
return self.idlist[start:end]
def __contains__(self, element):
return self.idset.__contains__(element)
def __str__(self):
return "%s: %s" % (self.name, str(self.idlist))
def __len__(self):
return len(self.idlist)
def __iter__(self):
return iter(self.idlist)
def intersection(self, dim):
return self.idset.intersection(dim.idset)
def as_tuple(self):
return (self.name, self.idlist)
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:
self.file_created(fn)
for fn in self.files - newfiles:
if os.path.isdir(os.path.join(self.path, fn)):
self.dir_deleted(fn)
else:
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
def file_created(self, fn):
print "file created: %s" % fn
pass
def file_changed(self, fn):
print "file changed: %s" % fn
pass
def file_removed(self, fn):
print "file removed: %s" % fn
pass
def dir_created(self, fn):
print "directory created: %s" % fn
pass
def dir_changed(self, fn):
print "directory changed: %s" % fn
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.
"""
filepath = os.path.join(self.path, fn)
name, ext = os.path.splitext(fn)
if ext == ".ftsv":
ds = dataset.read_ftsv(filepath)
self.datasets.append(ds)
self.dsfiles[fn] = ds
def file_changed(self, fn):
"""Called from update() when files are changed.
Delete old dataset and load the new one when dataset files
have been changed.
"""
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)
ds = dataset.read_ftsv(filepath)
self.datasets.append(ds)
self.dsfiles[fn] = ds
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()