labdata 0.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- labdata/__init__.py +17 -0
- labdata/cli.py +499 -0
- labdata/compute/__init__.py +27 -0
- labdata/compute/ec2.py +198 -0
- labdata/compute/ephys.py +469 -0
- labdata/compute/pose.py +281 -0
- labdata/compute/schedulers.py +194 -0
- labdata/compute/singularity.py +95 -0
- labdata/compute/utils.py +561 -0
- labdata/copy.py +351 -0
- labdata/rules/__init__.py +78 -0
- labdata/rules/ephys.py +188 -0
- labdata/rules/imaging.py +618 -0
- labdata/rules/utils.py +290 -0
- labdata/s3.py +317 -0
- labdata/schema/__init__.py +24 -0
- labdata/schema/ephys.py +547 -0
- labdata/schema/general.py +647 -0
- labdata/schema/histology.py +309 -0
- labdata/schema/onephoton.py +93 -0
- labdata/schema/procedures.py +102 -0
- labdata/schema/tasks.py +66 -0
- labdata/schema/twophoton.py +142 -0
- labdata/schema/utils.py +25 -0
- labdata/schema/video.py +243 -0
- labdata/stacks.py +182 -0
- labdata/utils.py +598 -0
- labdata/widgets.py +412 -0
- labdata-0.0.3.dist-info/METADATA +42 -0
- labdata-0.0.3.dist-info/RECORD +36 -0
- labdata-0.0.3.dist-info/WHEEL +5 -0
- labdata-0.0.3.dist-info/entry_points.txt +2 -0
- labdata-0.0.3.dist-info/licenses/LICENSE +674 -0
- labdata-0.0.3.dist-info/top_level.txt +2 -0
- labdata_frontend/Home.py +39 -0
- labdata_frontend/__init__.py +0 -0
labdata/widgets.py
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
from PyQt5.QtWidgets import (QApplication,
|
|
2
|
+
QTableWidget,
|
|
3
|
+
QTableWidgetItem,
|
|
4
|
+
QWidget,
|
|
5
|
+
QMainWindow,
|
|
6
|
+
QDockWidget,
|
|
7
|
+
QFormLayout,
|
|
8
|
+
QHBoxLayout,
|
|
9
|
+
QGridLayout,
|
|
10
|
+
QVBoxLayout,
|
|
11
|
+
QPushButton,
|
|
12
|
+
QGroupBox,
|
|
13
|
+
QGridLayout,
|
|
14
|
+
QTreeWidgetItem,
|
|
15
|
+
QTreeView,
|
|
16
|
+
QTextEdit,
|
|
17
|
+
QPlainTextEdit,
|
|
18
|
+
QLineEdit,
|
|
19
|
+
QScrollArea,
|
|
20
|
+
QCheckBox,
|
|
21
|
+
QComboBox,
|
|
22
|
+
QListWidget,
|
|
23
|
+
QLabel,
|
|
24
|
+
QProgressBar,
|
|
25
|
+
QFileDialog,
|
|
26
|
+
QMessageBox,
|
|
27
|
+
QDesktopWidget,
|
|
28
|
+
QListWidgetItem,
|
|
29
|
+
QFileSystemModel,
|
|
30
|
+
QAbstractItemView,
|
|
31
|
+
QTabWidget,
|
|
32
|
+
QMenu,
|
|
33
|
+
QDialog,
|
|
34
|
+
QDialogButtonBox,
|
|
35
|
+
QAction)
|
|
36
|
+
|
|
37
|
+
from PyQt5 import QtCore
|
|
38
|
+
from PyQt5.QtGui import QStandardItem, QStandardItemModel,QColor
|
|
39
|
+
from PyQt5.QtCore import Qt, QTimer,QMimeData
|
|
40
|
+
GUI_UPDATE = QApplication.processEvents
|
|
41
|
+
|
|
42
|
+
from .utils import *
|
|
43
|
+
|
|
44
|
+
class ServerCopyWidget(QMainWindow):
|
|
45
|
+
def __init__(self,
|
|
46
|
+
src_filepaths,
|
|
47
|
+
name = None,
|
|
48
|
+
upload_rule = None,
|
|
49
|
+
local_path = None,
|
|
50
|
+
server_path = None,
|
|
51
|
+
upload_storage = None,
|
|
52
|
+
subject_name = None,
|
|
53
|
+
session_name = None,
|
|
54
|
+
dataset_name = None,
|
|
55
|
+
parse_filename = True,
|
|
56
|
+
overwrite = True,
|
|
57
|
+
user_confirmation = True,
|
|
58
|
+
**kwargs):
|
|
59
|
+
'''Function to copy data and show progress.
|
|
60
|
+
To run in a separate process
|
|
61
|
+
'''
|
|
62
|
+
super(ServerCopyWidget,self).__init__()
|
|
63
|
+
from pathlib import Path
|
|
64
|
+
from PyQt5.QtWidgets import QListWidget, QListWidgetItem, QStyle
|
|
65
|
+
|
|
66
|
+
name = '[labdata] server copy {name}'
|
|
67
|
+
self.setWindowTitle(f'Copy for {name}')
|
|
68
|
+
icon = self.style().standardIcon(QStyle.SP_DialogSaveButton)
|
|
69
|
+
self.setWindowIcon(icon)
|
|
70
|
+
|
|
71
|
+
doneicon = self.style().standardIcon(QStyle.SP_DialogApplyButton)
|
|
72
|
+
notyeticon = self.style().standardIcon(QStyle.SP_DialogCancelButton)
|
|
73
|
+
workingonit = self.style().standardIcon(QStyle.SP_ArrowForward)
|
|
74
|
+
|
|
75
|
+
from .schema import UploadJob,Setup,Subject,Session,Dataset,dj
|
|
76
|
+
if local_path is None: # get the local_path from the preferences
|
|
77
|
+
if not 'local_paths' in prefs.keys():
|
|
78
|
+
raise OSError('Local data path [local_paths] not specified in the preference file.')
|
|
79
|
+
local_path = Path(prefs['local_paths'][0])
|
|
80
|
+
if server_path is None: # get the upload_path from the preferences
|
|
81
|
+
if not 'upload_path' in prefs.keys():
|
|
82
|
+
raise OSError('Upload storage [upload_path], not specified in the preference file.')
|
|
83
|
+
server_path = prefs['upload_path']
|
|
84
|
+
if server_path is None:
|
|
85
|
+
server_path = local_path
|
|
86
|
+
if upload_storage is None: # get the upload_storage name from the preferences
|
|
87
|
+
if not 'upload_storage' in prefs.keys():
|
|
88
|
+
raise OSError('Upload storage [upload_storage], not specified in the preference file.')
|
|
89
|
+
upload_storage = prefs['upload_storage']
|
|
90
|
+
if not type(src_filepaths) is list: # Check if the filepaths are in a list
|
|
91
|
+
raise ValueError('Input filepaths must be a list of paths.')
|
|
92
|
+
# replace local_path if the user passed it like that by accident.
|
|
93
|
+
src_filepaths = [str(Path(f)).replace(str(local_path),'') for f in src_filepaths]
|
|
94
|
+
# remove trailing / or \
|
|
95
|
+
src_filepaths = [f if not f.startswith(pathlib.os.sep) else f[1:] for f in src_filepaths]
|
|
96
|
+
# make unique
|
|
97
|
+
src_filepaths = [f for f in np.unique(src_filepaths)]
|
|
98
|
+
|
|
99
|
+
from .copy import any_path_uploaded
|
|
100
|
+
replace = False
|
|
101
|
+
if not upload_rule is None:
|
|
102
|
+
if 'replace' in upload_rule:
|
|
103
|
+
replace = True
|
|
104
|
+
if any_path_uploaded(src_filepaths) and not replace:
|
|
105
|
+
msgBox = QMessageBox( icon=QMessageBox.Critical)
|
|
106
|
+
msgBox.setWindowTitle("Data copy: files already copied?")
|
|
107
|
+
msgBox.setText('At least one of the selected paths was already uploaded {0}'.format(
|
|
108
|
+
Path(src_filepaths[0]).parent))
|
|
109
|
+
msgBox.exec_()
|
|
110
|
+
raise(OSError('At least one of the selected paths was already uploaded {0}'.format(
|
|
111
|
+
Path(src_filepaths[0]).parent)))
|
|
112
|
+
|
|
113
|
+
args = dict(**kwargs)
|
|
114
|
+
args['subject_name'] = subject_name
|
|
115
|
+
args['session_name'] = session_name
|
|
116
|
+
args['dataset_name'] = dataset_name
|
|
117
|
+
if parse_filename: # parse filename based on the path rules
|
|
118
|
+
tmp = parse_filepath_parts(src_filepaths[0])
|
|
119
|
+
for k in tmp.keys():
|
|
120
|
+
args[k] = tmp[k]
|
|
121
|
+
|
|
122
|
+
for src in src_filepaths:
|
|
123
|
+
src = Path(local_path)/src
|
|
124
|
+
if not src.exists():
|
|
125
|
+
msgBox = QMessageBox( icon=QMessageBox.Critical)
|
|
126
|
+
msgBox.setWindowTitle("Data copy: source does not exist!")
|
|
127
|
+
msgBox.setText("File {0} does not exist?".format(src))
|
|
128
|
+
msgBox.exec_()
|
|
129
|
+
raise(OSError(f"{tcolor['r']('Upload failed')} {src} does not exist."))
|
|
130
|
+
# Add it to the upload table
|
|
131
|
+
filelist = QListWidget()
|
|
132
|
+
self.files_to_copy = [Path(f) for f in src_filepaths]
|
|
133
|
+
file_items = []
|
|
134
|
+
itemnames = []
|
|
135
|
+
for i,f in enumerate(self.files_to_copy):
|
|
136
|
+
itemnames.append(str(f.name))
|
|
137
|
+
file_items.append(QListWidgetItem(notyeticon,itemnames[-1]))
|
|
138
|
+
# file_items[-1].setIcon(workingonit)
|
|
139
|
+
filelist.addItem(file_items[-1])
|
|
140
|
+
if user_confirmation:
|
|
141
|
+
msgBox = QMessageBox(icon = QMessageBox.Information)
|
|
142
|
+
msgBox.setWindowTitle("[labdata] user input required")
|
|
143
|
+
msgBox.setText(f"Copying {len(self.files_to_copy)} files to the server for upload to {upload_storage} storage.")
|
|
144
|
+
msgBox.setInformativeText("Do you want to continue?")
|
|
145
|
+
msgBox.setStandardButtons( QMessageBox.Cancel | QMessageBox.Save );
|
|
146
|
+
msgBox.setDefaultButton(QMessageBox.Save)
|
|
147
|
+
msgBox.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
148
|
+
res = msgBox.exec_()
|
|
149
|
+
if res == QMessageBox.Cancel:
|
|
150
|
+
print(tcolor['r']("User CANCELED the server upload."))
|
|
151
|
+
return
|
|
152
|
+
# copy and compute checksum for all paths in parallel.
|
|
153
|
+
from .copy import _copyfile_to_upload_server
|
|
154
|
+
res = Parallel(n_jobs = DEFAULT_N_JOBS,
|
|
155
|
+
return_as = 'generator_unordered')(delayed(_copyfile_to_upload_server)(
|
|
156
|
+
path,
|
|
157
|
+
local_path = local_path,
|
|
158
|
+
server_path = server_path,
|
|
159
|
+
overwrite = overwrite) for path in src_filepaths)
|
|
160
|
+
layout = QVBoxLayout(self)
|
|
161
|
+
# self.setLayout(layout)
|
|
162
|
+
layout.addWidget(filelist)
|
|
163
|
+
self.setCentralWidget(filelist)
|
|
164
|
+
# now dow the actual copy
|
|
165
|
+
self.show()
|
|
166
|
+
GUI_UPDATE()
|
|
167
|
+
# does the checksum and copy
|
|
168
|
+
completed = []
|
|
169
|
+
for i,src in enumerate(res):
|
|
170
|
+
GUI_UPDATE()
|
|
171
|
+
item = file_items[itemnames.index(src['src_path'].split('/')[-1])]
|
|
172
|
+
item.setIcon(doneicon)
|
|
173
|
+
completed.append(src)
|
|
174
|
+
GUI_UPDATE()
|
|
175
|
+
# Add it to the upload table
|
|
176
|
+
# check the job id
|
|
177
|
+
with dj.conn().transaction:
|
|
178
|
+
if "setup_name" in args.keys():
|
|
179
|
+
Setup.insert1(args, skip_duplicates = True,ignore_extra_fields = True) # try to insert setup
|
|
180
|
+
if "dataset_name" in args.keys() and "session_name" in args.keys() and "subject_name" in args.keys():
|
|
181
|
+
if not len(Subject() & dict(subject_name=args['subject_name'])):
|
|
182
|
+
# subject not on the database..
|
|
183
|
+
msgBox = QMessageBox(icon = QMessageBox.Information)
|
|
184
|
+
msgBox.setWindowTitle("[labdata] user input required")
|
|
185
|
+
msgBox.setText(f"Subject {args['subject_name']} was not on the database. ")
|
|
186
|
+
msgBox.setInformativeText("You can add the subject now or skip associating the session with a Dataset. Did you add the subject?")
|
|
187
|
+
msgBox.setStandardButtons( QMessageBox.No | QMessageBox.Yes );
|
|
188
|
+
msgBox.setDefaultButton(QMessageBox.No)
|
|
189
|
+
msgBox.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
190
|
+
#print(args)
|
|
191
|
+
res = msgBox.exec_()
|
|
192
|
+
if res == QMessageBox.No:
|
|
193
|
+
print(tcolor['r']("User selected not to add the Dataset but files are still uploaded."))
|
|
194
|
+
args = dict()
|
|
195
|
+
# Subject.insert1(args, skip_duplicates = True,ignore_extra_fields = True) # try to insert subject, needs date of birth and sex
|
|
196
|
+
if not len(Session() & dict(subject_name=args['subject_name'],
|
|
197
|
+
session_name = args['session_name'])):
|
|
198
|
+
Session.insert1(args, skip_duplicates = True,ignore_extra_fields = True) # try to insert session
|
|
199
|
+
if not len(Dataset() & dict(subject_name = args['subject_name'],
|
|
200
|
+
session_name = args['session_name'],
|
|
201
|
+
dataset_name = args['dataset_name'])):
|
|
202
|
+
Dataset.insert1(args, skip_duplicates = True,ignore_extra_fields = True) # try to insert dataset
|
|
203
|
+
jobid = UploadJob().fetch('job_id')
|
|
204
|
+
if len(jobid):
|
|
205
|
+
jobid = np.max(jobid) + 1
|
|
206
|
+
else:
|
|
207
|
+
jobid = 1
|
|
208
|
+
jb = dict(job_id = jobid,
|
|
209
|
+
job_status = "ON SERVER",
|
|
210
|
+
upload_storage = upload_storage,
|
|
211
|
+
job_rule = upload_rule,
|
|
212
|
+
**args)
|
|
213
|
+
if 'upload_host' in prefs.keys():
|
|
214
|
+
if not 'job_host' in jb.keys():
|
|
215
|
+
jb['job_host'] = prefs['upload_host']
|
|
216
|
+
UploadJob.insert1(jb,
|
|
217
|
+
ignore_extra_fields = True) # Need to insert the dataset first if not there
|
|
218
|
+
res = [dict(r, job_id = jobid) for r in completed] # add dataset through kwargs
|
|
219
|
+
UploadJob.AssignedFiles.insert(res, ignore_extra_fields=True)
|
|
220
|
+
import time
|
|
221
|
+
# show it for some time
|
|
222
|
+
for t in range(60):
|
|
223
|
+
time.sleep(0.15)
|
|
224
|
+
GUI_UPDATE()
|
|
225
|
+
print(f"{tcolor['g']('Upload:')} {completed}")
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
def build_tree(item,parent):
|
|
229
|
+
for k in item.keys():
|
|
230
|
+
child = QStandardItem(k)
|
|
231
|
+
child.setFlags(child.flags() |
|
|
232
|
+
Qt.ItemIsSelectable |
|
|
233
|
+
Qt.ItemIsEnabled)
|
|
234
|
+
child.setEditable(False)
|
|
235
|
+
if type(item[k]) is dict:
|
|
236
|
+
build_tree(item[k],child)
|
|
237
|
+
parent.appendRow(child)
|
|
238
|
+
|
|
239
|
+
def make_tree(item, tree):
|
|
240
|
+
if len(item) == 1:
|
|
241
|
+
if not item[0] == '':
|
|
242
|
+
tree[item[0]] = item[0]
|
|
243
|
+
else:
|
|
244
|
+
head, tail = item[0], item[1:]
|
|
245
|
+
tree.setdefault(head, {})
|
|
246
|
+
make_tree(
|
|
247
|
+
tail,
|
|
248
|
+
tree[head])
|
|
249
|
+
class TableModel(QtCore.QAbstractTableModel):
|
|
250
|
+
|
|
251
|
+
def __init__(self, data):
|
|
252
|
+
super(TableModel, self).__init__()
|
|
253
|
+
self._data = data
|
|
254
|
+
|
|
255
|
+
def data(self, index, role):
|
|
256
|
+
if role == Qt.DisplayRole:
|
|
257
|
+
value = self._data.iloc[index.row(), index.column()]
|
|
258
|
+
return str(value)
|
|
259
|
+
|
|
260
|
+
def rowCount(self, index):
|
|
261
|
+
return self._data.shape[0]
|
|
262
|
+
|
|
263
|
+
def columnCount(self, index):
|
|
264
|
+
return self._data.shape[1]
|
|
265
|
+
|
|
266
|
+
def headerData(self, section, orientation, role):
|
|
267
|
+
# section is the index of the column/row.
|
|
268
|
+
if role == Qt.DisplayRole:
|
|
269
|
+
if orientation == Qt.Horizontal:
|
|
270
|
+
return str(self._data.columns[section])
|
|
271
|
+
|
|
272
|
+
if orientation == Qt.Vertical:
|
|
273
|
+
return str(self._data.index[section])
|
|
274
|
+
|
|
275
|
+
class TableView(QTreeView):
|
|
276
|
+
def __init__(self, *args):
|
|
277
|
+
QTreeView.__init__(self, *args)
|
|
278
|
+
self.header = ['folder','n_files','subject_name','session_name','dataset_name']
|
|
279
|
+
self.setHorizontalHeaderLabels(self.header)
|
|
280
|
+
|
|
281
|
+
def setData(self,data):
|
|
282
|
+
self.data = data
|
|
283
|
+
model = TableModel(pd.DataFrame(data))
|
|
284
|
+
|
|
285
|
+
self.setModel(model)
|
|
286
|
+
# for i,item in enumerate(self.data):
|
|
287
|
+
# for j,k in enumerate(self.header):
|
|
288
|
+
# newitem = QTableWidgetItem(item[k])
|
|
289
|
+
# self.insertRow(i+1)
|
|
290
|
+
# self.setItem(i, j, newitem)
|
|
291
|
+
self.resizeColumnsToContents()
|
|
292
|
+
self.resizeRowsToContents()
|
|
293
|
+
|
|
294
|
+
def get_tree_path(items,root = ''):
|
|
295
|
+
''' Get the paths from a QTreeView item'''
|
|
296
|
+
paths = []
|
|
297
|
+
for item in items:
|
|
298
|
+
level = 0
|
|
299
|
+
index = item
|
|
300
|
+
paths.append([index.data()])
|
|
301
|
+
while index.parent().isValid():
|
|
302
|
+
index = index.parent()
|
|
303
|
+
level += 1
|
|
304
|
+
paths[-1].append(index.data())
|
|
305
|
+
for i,p in enumerate(paths[-1]):
|
|
306
|
+
if p is None :
|
|
307
|
+
paths[-1][i] = ''
|
|
308
|
+
paths[-1] = '/'.join(paths[-1][::-1])
|
|
309
|
+
return paths
|
|
310
|
+
|
|
311
|
+
class FileView(QTreeView):
|
|
312
|
+
def __init__(self,prefs,parent=None):
|
|
313
|
+
super(FileView,self).__init__()
|
|
314
|
+
self.prefs = prefs
|
|
315
|
+
self.parent = parent
|
|
316
|
+
rootfolder = self.prefs['local_paths'][0]
|
|
317
|
+
self.fs_model = QFileSystemModel(self)
|
|
318
|
+
self.fs_model.setReadOnly(True)
|
|
319
|
+
self.setModel(self.fs_model)
|
|
320
|
+
self.folder = rootfolder
|
|
321
|
+
self.setRootIndex(self.fs_model.setRootPath(rootfolder))
|
|
322
|
+
self.fs_model.removeColumn(1)
|
|
323
|
+
self.setAlternatingRowColors(True)
|
|
324
|
+
self.setSelectionMode(3)
|
|
325
|
+
self.setDragEnabled(True)
|
|
326
|
+
self.setAcceptDrops(True)
|
|
327
|
+
self.setDragDropMode(QAbstractItemView.DragDrop)
|
|
328
|
+
self.setDropIndicatorShown(True)
|
|
329
|
+
[self.hideColumn(i) for i in range(1,4)]
|
|
330
|
+
self.setColumnWidth(0,int(self.width()*.4))
|
|
331
|
+
def pathnofolder(p):
|
|
332
|
+
return str(p).replace(rootfolder,'').strip(pathlib.os.sep)
|
|
333
|
+
def handle_click(val):
|
|
334
|
+
path = Path(get_tree_path([val])[0])
|
|
335
|
+
allfiles = list(filter(lambda x: x.is_file(),path.rglob('*')))
|
|
336
|
+
allfolders = np.unique(list(map(lambda x: x.parent,allfiles)))
|
|
337
|
+
to_upload = []
|
|
338
|
+
for f in allfolders:
|
|
339
|
+
f = str(f)
|
|
340
|
+
files = list(filter(lambda x: str(f) in str(x),list(allfiles)))
|
|
341
|
+
header = ['folder','n_files','subject_name','session_name','dataset_name','rule','paths']
|
|
342
|
+
ff = pathnofolder(f)
|
|
343
|
+
t = dict(folder = ff,
|
|
344
|
+
files = [pathnofolder(p) for p in files],
|
|
345
|
+
paths = files,
|
|
346
|
+
subject_name = None,
|
|
347
|
+
session_name = None,
|
|
348
|
+
dataset_name = None,
|
|
349
|
+
rule = None,
|
|
350
|
+
n_files = len(files))
|
|
351
|
+
to_upload.append(t)
|
|
352
|
+
self.parent.table.setData(to_upload)
|
|
353
|
+
|
|
354
|
+
# put this in the other side.
|
|
355
|
+
self.clicked.connect(handle_click)
|
|
356
|
+
|
|
357
|
+
class LABDATA_PUT(QMainWindow):
|
|
358
|
+
def __init__(self, preferences = None):
|
|
359
|
+
super(LABDATA_PUT,self).__init__()
|
|
360
|
+
self.setWindowTitle('labdata')
|
|
361
|
+
self.prefs = preferences
|
|
362
|
+
if self.prefs is None:
|
|
363
|
+
self.prefs = prefs
|
|
364
|
+
mainw = QWidget()
|
|
365
|
+
self.setCentralWidget(mainw)
|
|
366
|
+
lay = QHBoxLayout()
|
|
367
|
+
mainw.setLayout(lay)
|
|
368
|
+
# Add the main file view
|
|
369
|
+
self.table = TableView()
|
|
370
|
+
self.fs_view = FileView(self.prefs,parent=self)
|
|
371
|
+
lay.addWidget(self.fs_view)
|
|
372
|
+
|
|
373
|
+
w = QGroupBox('Database ingestion')
|
|
374
|
+
|
|
375
|
+
l = QFormLayout()
|
|
376
|
+
w.setLayout(l)
|
|
377
|
+
# self.skip_database = False
|
|
378
|
+
# skipwidget = QCheckBox()
|
|
379
|
+
# skipwidget.setChecked(self.skip_database)
|
|
380
|
+
# def _skipwidget(value):
|
|
381
|
+
# self.skip_database = value>0
|
|
382
|
+
# skipwidget.stateChanged.connect(_skipwidget)
|
|
383
|
+
# l.addRow(skipwidget,QLabel('Files only'))
|
|
384
|
+
l.addRow(self.table)
|
|
385
|
+
lay.addWidget(w)
|
|
386
|
+
self.show()
|
|
387
|
+
|
|
388
|
+
class FilesystemView(QTreeView):
|
|
389
|
+
def __init__(self,folder,parent=None):
|
|
390
|
+
super(FilesystemView,self).__init__()
|
|
391
|
+
self.parent = parent
|
|
392
|
+
self.fs_model = QFileSystemModel(self)
|
|
393
|
+
self.fs_model.setReadOnly(True)
|
|
394
|
+
self.setModel(self.fs_model)
|
|
395
|
+
self.folder = folder
|
|
396
|
+
self.setRootIndex(self.fs_model.setRootPath(folder))
|
|
397
|
+
#self.fs_model.removeColumn(1)
|
|
398
|
+
self.setAlternatingRowColors(True)
|
|
399
|
+
self.setSelectionMode(3)
|
|
400
|
+
self.setDragEnabled(False)
|
|
401
|
+
self.setAcceptDrops(False)
|
|
402
|
+
#self.setDragDropMode(QAbstractItemView.DragDrop)
|
|
403
|
+
#self.setDropIndicatorShown(True)
|
|
404
|
+
self.setColumnWidth(0,int(self.width()*.7))
|
|
405
|
+
self.expandAll()
|
|
406
|
+
def change_root(self):
|
|
407
|
+
folder = QFileDialog().getExistingDirectory(self,"Select directory",os.path.curdir)
|
|
408
|
+
self.setRootIndex(self.fs_model.setRootPath(folder))
|
|
409
|
+
self.expandAll()
|
|
410
|
+
self.folder = folder
|
|
411
|
+
if hasattr(self.parent,'folder'):
|
|
412
|
+
self.parent.folder.setText('{0}'.format(folder))
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: labdata
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: Package to manage data in experimental neuroscience labs
|
|
5
|
+
Author-email: Joao Couto <jpcouto@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/jcouto/labdata
|
|
7
|
+
Project-URL: Issues, https://github.com/jcouto/labdata/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.0
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: datajoint
|
|
14
|
+
Requires-Dist: boto3
|
|
15
|
+
Requires-Dist: safe-exit
|
|
16
|
+
Requires-Dist: joblib>=1.4.0
|
|
17
|
+
Requires-Dist: natsort
|
|
18
|
+
Requires-Dist: pandas
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
## labdata
|
|
22
|
+
|
|
23
|
+
Tools to copy data and manage data analysis in an experimental neuroscience lab.
|
|
24
|
+
|
|
25
|
+
This is built after [labdata-tools](github.com/jcouto/labdata-tools) and integrates with S3 and a database through datajoint.
|
|
26
|
+
|
|
27
|
+
### Concepts
|
|
28
|
+
Experiments often involve many computers in the lab and data has to be copied to a server(and sometimes backed up). In this process files can be corrupted, data loss and so on, specially if manually copying files. Further, in some cases, tasks have to be performed on the files (those might involve compressing, formatting, etc).
|
|
29
|
+
|
|
30
|
+
What ``labdata`` does to copy files:
|
|
31
|
+
1. After data are acquired, labdata performs a md5 checksum of the file to be copied and copies the file to a server.
|
|
32
|
+
2. After the copy, the checksum and datapath are placed on the ``Upload`` database table.
|
|
33
|
+
3. The computer/server that manages the copy to the permanent storage server will read the database table. The server will perform a checksum on the file to ensure the copy from the acquisition computer was ok.
|
|
34
|
+
4. If needed it will compress data or do whatever is specified in the preference file rules (see ``upload_rules``)
|
|
35
|
+
5. Data will be put in S3 and added to the ``Files``
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
### Instalation
|
|
39
|
+
|
|
40
|
+
Clone the repository to a folder in your computer and do:
|
|
41
|
+
|
|
42
|
+
``pip install .`` or ``pip install -e .`` to if you want that the source code follows git.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
labdata/__init__.py,sha256=4k4rk4Sn251VHbh6jhff6H7hpDqKyVfTRlQdnZ4qJOo,546
|
|
2
|
+
labdata/cli.py,sha256=aoDgs9X8quimzBe_BA1tuuQoACGbAizVOxQpe8nSR6w,22832
|
|
3
|
+
labdata/copy.py,sha256=G4pOI7tPtR8l_mr4qPGnTxG6_7cdQ3-c4JAm6Id6gHk,15100
|
|
4
|
+
labdata/s3.py,sha256=OIJzmu9QS3GCWjGMvQU3ceMN4amaynbMd2KVFxGaOBw,12322
|
|
5
|
+
labdata/stacks.py,sha256=1TGA790QXk40UWyfORvZ6lGpygu7TZHVqPyU0NYl11E,6487
|
|
6
|
+
labdata/utils.py,sha256=Cp6ToalHmEC85LGyqgatfO3_GY1lqO4oc8p0nu5ly2c,24537
|
|
7
|
+
labdata/widgets.py,sha256=5cgr4uynQzT80hHVaY6HQTNtU70e9VzJDdkk9_2ftOg,17988
|
|
8
|
+
labdata/compute/__init__.py,sha256=CT1kCqA3Yvu6Nz4Gjw1I98ot88g4qoGtTA9SlI5MMyE,915
|
|
9
|
+
labdata/compute/ec2.py,sha256=RzkkY9d9PVjJEsf8jxSLvuF3lKpXoAf7xjiuo9m2Geo,7486
|
|
10
|
+
labdata/compute/ephys.py,sha256=hbFWKHzLBnx-mLS_fgJpCqkBkGuvOs6sa6iddUqmWCo,25473
|
|
11
|
+
labdata/compute/pose.py,sha256=-P6XFz2OMgBgHpcxb5iaA9Peupas2BmS5yNrXr-ocno,14336
|
|
12
|
+
labdata/compute/schedulers.py,sha256=CV1RJWEYosQtCh6UN9Xm4SllNGzYlYo-9XixB7pPLZw,6886
|
|
13
|
+
labdata/compute/singularity.py,sha256=a2EgOqVVsPdpRap0rcjTOE2q2lo7XdnbWdIYZDUzhKw,3045
|
|
14
|
+
labdata/compute/utils.py,sha256=V746V-1cYemJ6z7VqC26kXfZqCGu18NkTj8XbuDpYng,28108
|
|
15
|
+
labdata/rules/__init__.py,sha256=DCSX9auO74TXUzskj1PXgE6i8rNijj2XUTJ59rNEDCg,2997
|
|
16
|
+
labdata/rules/ephys.py,sha256=pF5bGjjh1AhSUAuHZr7xFoX3EOE5KAWzssh9_yvPwow,9075
|
|
17
|
+
labdata/rules/imaging.py,sha256=wkmOzUOhSvqv8h4IZitKqVNh6b9Y8jWoi9HItHlHkXQ,28274
|
|
18
|
+
labdata/rules/utils.py,sha256=xwd1LOvWfbfT6U0_WHfowO9ELenh0ksq6GEU-6qx6kc,14358
|
|
19
|
+
labdata/schema/__init__.py,sha256=JDXQ7yRhwYri07js8YIQn3JzKQQaXMTP8qVYK4OydRY,903
|
|
20
|
+
labdata/schema/ephys.py,sha256=hAdtOtzv-Q4dl5FGIk0q0dY1KT30y30wi6cdNtbf7Ko,25169
|
|
21
|
+
labdata/schema/general.py,sha256=aRlvjFeyfgj2cWoe-tO6fW1R--Ro-nU3twgT6LVTg6c,26684
|
|
22
|
+
labdata/schema/histology.py,sha256=07ss5dPxphjfc9xWWvoPauT-F04_Cc_GYXvhCMAE76g,10287
|
|
23
|
+
labdata/schema/onephoton.py,sha256=U09hu40ZL-KKXIUaetplwos6Ha0OPKYmt-oSWs-ZWWs,3752
|
|
24
|
+
labdata/schema/procedures.py,sha256=eNTRmwVkaT2-Tavic1I-DwZQL77WQpFEPzrfQ1QJQU4,2590
|
|
25
|
+
labdata/schema/tasks.py,sha256=j2gV0x9nJokF48iUumZIjPx8l2lzppPpn0bsVfsfP_M,3486
|
|
26
|
+
labdata/schema/twophoton.py,sha256=K45RSQbZkJ2aMytUgV-t-QKTkCCZho9E6PzVJo1w83s,4932
|
|
27
|
+
labdata/schema/utils.py,sha256=TuYstTiYXs2h4PooPyhYPm6U01hIdx5U44LENlwuMk0,755
|
|
28
|
+
labdata/schema/video.py,sha256=8GkVfwGUvRQ9vG5gJzO6HcMXubD_Fd-gi0yqka56-VI,10849
|
|
29
|
+
labdata-0.0.3.dist-info/licenses/LICENSE,sha256=sfHVHGQbkFu5u5bGXuLD4DKwfvukIhPm_pzARealOik,35117
|
|
30
|
+
labdata_frontend/Home.py,sha256=QVRNxrGXfIRQM4-mzxY37Z966GImvRr9Tkv87tNKOwI,838
|
|
31
|
+
labdata_frontend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
|
+
labdata-0.0.3.dist-info/METADATA,sha256=-bZ8YtE2GNnqmLg-jUGGLgPia8qSK0M88aDsfyW8R14,2017
|
|
33
|
+
labdata-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
34
|
+
labdata-0.0.3.dist-info/entry_points.txt,sha256=Qt1t66bAaQ3OkSa44Gj2bKc6mPx_tTbCNVomkhaXmbE,46
|
|
35
|
+
labdata-0.0.3.dist-info/top_level.txt,sha256=7PwSeFxIExHdXFPar_7GQ9L2sKQYD2Cm4OCQ0kNJDcI,25
|
|
36
|
+
labdata-0.0.3.dist-info/RECORD,,
|