Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App: implement managing multiple projects (#107) #109

Merged
merged 38 commits into from
Dec 20, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
05f13c4
App: implement managing multiple projects (#107)
keggsmurph21 Dec 18, 2019
ca46724
App: save `project_hash` after initializing a Project
keggsmurph21 Dec 18, 2019
7c46ccb
Project: change constructor pattern & save location
keggsmurph21 Dec 18, 2019
1c15aca
Create directories in 0o755 mode (instead of default 0o777)
keggsmurph21 Dec 18, 2019
fd59bfd
Project: remove old comments
keggsmurph21 Dec 18, 2019
b2d62bf
Project: check if root_path is a directory
keggsmurph21 Dec 18, 2019
746f9ac
App: Add types to constructor args, allow "headless" mode
keggsmurph21 Dec 19, 2019
274beae
App: add tests
keggsmurph21 Dec 19, 2019
30c5c7a
Trace+XHair: fix type errors when running `pytest`
keggsmurph21 Dec 19, 2019
61a18b6
Update ultratrace2/model/files/bundle.py
keggsmurph21 Dec 19, 2019
f0f6154
FileBundleList: Make `cls.exclude_dirs` a `FrozenSet`
keggsmurph21 Dec 19, 2019
5ef78a0
Merge branch 'manage-multiple-projects' of https://github.com/swatpho…
keggsmurph21 Dec 19, 2019
6235875
Update ultratrace2/model/files/bundle.py
keggsmurph21 Dec 19, 2019
25aae65
Update ultratrace2/model/files/bundle.py
keggsmurph21 Dec 19, 2019
67424ae
Update ultratrace2/model/project.py
keggsmurph21 Dec 19, 2019
2ea02f0
Update ultratrace2/model/project.py
keggsmurph21 Dec 19, 2019
2bee499
Project: Use more descriptive error messages in ::get_by_path()
keggsmurph21 Dec 19, 2019
3929e00
Merge branch 'manage-multiple-projects' of https://github.com/swatpho…
keggsmurph21 Dec 19, 2019
935d941
Update ultratrace2/model/project.py
keggsmurph21 Dec 19, 2019
cc4b696
Update ultratrace2/model/project.py
keggsmurph21 Dec 19, 2019
5f2b6f8
Update ultratrace2/model/project.py
keggsmurph21 Dec 19, 2019
dc181b5
Update ultratrace2/model/project.py
keggsmurph21 Dec 19, 2019
f7b7f7a
App: be more explicit about allowed parameters to App constructor
keggsmurph21 Dec 19, 2019
0cb2668
App: change parameter name in one test
keggsmurph21 Dec 19, 2019
c6b590a
Project: add tests for ::load()
keggsmurph21 Dec 19, 2019
be61113
nox: Also calculate test coverage when running pytest
keggsmurph21 Dec 19, 2019
a518eb8
FileBundle: Simplify logic of `has_*_impl` initialization
keggsmurph21 Dec 19, 2019
318c618
Files: simplify inheritance of file implementations
keggsmurph21 Dec 19, 2019
04c8af2
Remove `python-magic` dependency in favor of `mimetypes`
keggsmurph21 Dec 19, 2019
cf17cff
Registry: Implement plugin-based architecture for loading files
keggsmurph21 Dec 19, 2019
a452e98
FileLoaderBase: constrain ::from_file return type
keggsmurph21 Dec 20, 2019
e38b084
Update ultratrace2/model/files/loaders/base.py
keggsmurph21 Dec 20, 2019
336f414
DICOMLoader: raise FileLoadError from InvalidDicomError
keggsmurph21 Dec 20, 2019
fa457a1
Registry: remove unnecessary `global` statements
keggsmurph21 Dec 20, 2019
7450611
Project: Don't funnel ::load() errors into RuntimeErrors
keggsmurph21 Dec 20, 2019
e6895ba
FileBundle: Pass responsibility for handling FileLoadError to caller
keggsmurph21 Dec 20, 2019
2669b54
FileLoaderBase: Require `path: str` abstract attribute
keggsmurph21 Dec 20, 2019
d2b3759
Merge branch 'master' into manage-multiple-projects
keggsmurph21 Dec 20, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ultratrace2/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ def __init__(self, args): # FIXME: be more granular here
else:
path = args.path

self.project = Project(path)
self.project: Project = Project.get_by_path(path)
self.gui = GUI(theme=args.theme)

def main(self):
def main(self) -> None:
pass


Expand Down
47 changes: 26 additions & 21 deletions ultratrace2/model/files/bundle.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import os

from typing import Dict, List, Set
from typing import Dict, Sequence, Set

from .impls import Sound, Alignment, ImageSet

Expand Down Expand Up @@ -41,30 +41,43 @@ class FileBundleList:
".git",
"node_modules",
"__pycache__",
".ultratrace",
# FIXME: add more ignoreable dirs
]
)

def __init__(self, path: str, extra_exclude_dirs: List[str] = []):
def __init__(self, bundles: Dict[str, FileBundle]):

# FIXME: implement `extra_exclude_dirs` as a command-line arg
for extra_exclude_dir in extra_exclude_dirs:
self.exclude_dirs.add(extra_exclude_dir)
self.current_bundle = None
self.bundles: Dict[str, FileBundle] = bundles

self.path = path
self.has_alignment_impl = False
self.has_image_impl = False
self.has_sound_impl = False
self.has_alignment_impl: bool = False
self.has_image_impl: bool = False
self.has_sound_impl: bool = False

self.current_bundle = None
for filename, bundle in bundles.items():
keggsmurph21 marked this conversation as resolved.
Show resolved Hide resolved
if not self.has_alignment_impl and bundle.alignment_file.has_impl():
self.has_alignment_impl = True
if not self.has_image_impl and bundle.image_file.has_impl():
self.has_image_impl = True
if not self.has_sound_impl and bundle.sound_file.has_impl():
self.has_sound_impl = True

@classmethod
def build_from_dir(
cls, root_path: str, extra_exclude_dirs: Sequence[str] = []
) -> "FileBundleList":

# FIXME: implement `extra_exclude_dirs` as a command-line arg
exclude_dirs = cls.exclude_dirs.copy().union(extra_exclude_dirs)
keggsmurph21 marked this conversation as resolved.
Show resolved Hide resolved

bundles: Dict[str, FileBundle] = {}

# NB: `topdown=True` increases runtime cost from O(n) -> O(n^2), but it allows us to
# modify `dirs` in-place so that we can skip certain directories. For more info,
# see https://stackoverflow.com/questions/19859840/excluding-directories-in-os-walk
for path, dirs, filenames in os.walk(path, topdown=True):
dirs[:] = [d for d in dirs if d not in self.exclude_dirs]
for path, dirs, filenames in os.walk(root_path, topdown=True):
dirs[:] = [d for d in dirs if d not in exclude_dirs]
keggsmurph21 marked this conversation as resolved.
Show resolved Hide resolved

for filename in filenames:

Expand All @@ -83,12 +96,4 @@ def __init__(self, path: str, extra_exclude_dirs: List[str] = []):
if not bundles[name].interpret(filepath):
logger.warning(f"unrecognized filetype: {filepath}")

# FIXME: do this when we add to our data structure
for filename, bundle in bundles.items():
# build up self.bundles here
if not self.has_alignment_impl and bundle.alignment_file.has_impl():
self.has_alignment_impl = True
if not self.has_image_impl and bundle.image_file.has_impl():
self.has_image_impl = True
if not self.has_sound_impl and bundle.sound_file.has_impl():
self.has_sound_impl = True
return FileBundleList(bundles)
keggsmurph21 marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion ultratrace2/model/files/impls/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def load(self) -> None:
"Invalid DICOM ({self.path}), unknown shape {pixels.shape}"
)

os.mkdir(self.png_path)
os.mkdir(self.png_path, mode=0o755)
for i in tqdm(range(frames), desc="converting to PNG"):
filename = os.path.join(self.png_path, f"{i:06}.png")
arr = pixels[i, :, :] if is_greyscale else pixels[i, :, :, :]
Expand Down
61 changes: 53 additions & 8 deletions ultratrace2/model/project.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,68 @@
import logging
import os
import pickle

from .trace import TraceList
from .files.bundle import FileBundleList

logger = logging.getLogger(__name__)


class Project:
def __init__(self, path: str):
if not os.path.exists(path):
raise ValueError(f"cannot initialize project at {path}")
self.root_path = os.path.realpath(os.path.abspath(path)) # absolute path
self.traces = TraceList()
self.files = FileBundleList(self.root_path)
def __init__(self, traces: TraceList, files: FileBundleList):
"""
Internal function: to construct a Project, either call ::get_by_path()
"""
self.traces = traces
self.files = files

def save(self):
raise NotImplementedError()

@classmethod
def load(cls):
raise NotImplementedError()
def load(cls, save_file) -> "Project":
keggsmurph21 marked this conversation as resolved.
Show resolved Hide resolved
try:
with open(save_file, "rb") as fp:
project = pickle.load(fp)
assert isinstance(project, Project)
return project
except Exception as e:
logger.error(e)
raise RuntimeError(str(e))
keggsmurph21 marked this conversation as resolved.
Show resolved Hide resolved

@classmethod
def get_by_path(cls, root_path) -> "Project":
keggsmurph21 marked this conversation as resolved.
Show resolved Hide resolved

root_path = os.path.realpath(os.path.abspath(root_path)) # absolute path
if not os.path.exists(root_path) or not os.path.isdir(root_path):
raise ValueError(
f"cannot initialize project at {root_path}: not a directory"
keggsmurph21 marked this conversation as resolved.
Show resolved Hide resolved
)

save_dir = Project.get_save_dir(root_path)
keggsmurph21 marked this conversation as resolved.
Show resolved Hide resolved
if not os.path.exists(save_dir):
os.mkdir(save_dir, mode=0o755)

save_file = Project.get_save_file(root_path)
keggsmurph21 marked this conversation as resolved.
Show resolved Hide resolved
try:
return Project.load(save_file)
keggsmurph21 marked this conversation as resolved.
Show resolved Hide resolved
except RuntimeError:
logger.info(
f"Unable to find existing project at {root_path}, creating new one..."
)

traces = TraceList()
file_bundles = FileBundleList.build_from_dir(root_path)
return Project(traces, file_bundles)
keggsmurph21 marked this conversation as resolved.
Show resolved Hide resolved

@staticmethod
def get_save_dir(path: str) -> str:
return os.path.join(path, ".ultratrace")

@staticmethod
def get_save_file(path: str) -> str:
save_dir = Project.get_save_dir(path)
return os.path.join(save_dir, "project.pkl")

def filepath(self):
raise NotImplementedError()
Expand Down