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/compute/utils.py
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
from ..utils import *
|
|
2
|
+
import traceback
|
|
3
|
+
|
|
4
|
+
def load_analysis_object(analysis):
|
|
5
|
+
if not analysis in prefs['compute']['analysis'].keys():
|
|
6
|
+
print(f'\n\nCould not find [{analysis}] analysis.\n\t The analysis are {list(prefs["compute"]["analysis"].keys())}\n\n')
|
|
7
|
+
raise ValueError('Add the analysis to the "compute" section of the preference file {analysis_name:analysis_object}.\n\n')
|
|
8
|
+
|
|
9
|
+
import labdata
|
|
10
|
+
return eval(prefs['compute']['analysis'][analysis])
|
|
11
|
+
|
|
12
|
+
def handle_compute(job_id):
|
|
13
|
+
from ..schema import ComputeTask
|
|
14
|
+
jobinfo = pd.DataFrame((ComputeTask() & dict(job_id = job_id)).fetch())
|
|
15
|
+
if not len(jobinfo):
|
|
16
|
+
print(f'No task with id: {job_id}')
|
|
17
|
+
jobinfo = jobinfo.iloc[0]
|
|
18
|
+
if jobinfo.task_waiting == 0:
|
|
19
|
+
print(f'Task {job_id} is running on {jobinfo.task_host}')
|
|
20
|
+
obj = load_analysis_object(jobinfo.task_name)(jobinfo.job_id)
|
|
21
|
+
return obj
|
|
22
|
+
|
|
23
|
+
def parse_analysis(analysis, job_id = None,
|
|
24
|
+
subject = None,
|
|
25
|
+
session = None,
|
|
26
|
+
secondary_args = [],
|
|
27
|
+
parameter_number = None,
|
|
28
|
+
full_command = None,
|
|
29
|
+
launch_singularity = False,
|
|
30
|
+
force_submit = False,
|
|
31
|
+
**kwargs):
|
|
32
|
+
|
|
33
|
+
obj = load_analysis_object(analysis)(job_id)
|
|
34
|
+
obj.secondary_parse(secondary_args, parameter_number)
|
|
35
|
+
if obj.job_id is None:
|
|
36
|
+
# then we have to create jobs and assign
|
|
37
|
+
from ..schema import ComputeTask
|
|
38
|
+
# first check if there is a task that has already been submitted with the exact same command.
|
|
39
|
+
# this has a caveat: if the order of the arguments is switched, it wont work..
|
|
40
|
+
if not full_command is None:
|
|
41
|
+
task_command = str(full_command)
|
|
42
|
+
if len(full_command) >500:
|
|
43
|
+
task_command = task_command[:499]
|
|
44
|
+
submittedjobs = (ComputeTask() & dict(task_cmd = task_command))
|
|
45
|
+
if len(submittedjobs) and not force_submit:
|
|
46
|
+
print(f'{tcolor["r"]("A similar task command is already submitted:")} \n {submittedjobs}')
|
|
47
|
+
return [], None
|
|
48
|
+
if not subject is None or not session is None:
|
|
49
|
+
datasets = obj.find_datasets(subject_name = subject, session_name = session)
|
|
50
|
+
else:
|
|
51
|
+
datasets = None
|
|
52
|
+
job_ids = obj.place_tasks_in_queue(datasets,task_cmd = full_command, force_submit = force_submit)
|
|
53
|
+
# now we have the job ids, need to figure out how to launch the jobs
|
|
54
|
+
return job_ids, obj # returns the job ids and the task
|
|
55
|
+
|
|
56
|
+
def check_archived(jobid, check_local=False):
|
|
57
|
+
from ..schema import ComputeTask,File
|
|
58
|
+
# check if the files are archived before launching remote jobs
|
|
59
|
+
files = File() & (ComputeTask.AssignedFiles() & f'job_id = {jobid}')
|
|
60
|
+
if not len(files):
|
|
61
|
+
return False # there are no files to fetch
|
|
62
|
+
if check_local:
|
|
63
|
+
localfiles, _ = files.check_if_files_local()
|
|
64
|
+
if len(self) == len(localfiles):
|
|
65
|
+
return False # all files are local
|
|
66
|
+
files_archived = files.check_if_files_archived(restore = False, suppress_error = True)
|
|
67
|
+
if files_archived:
|
|
68
|
+
response = None
|
|
69
|
+
while response not in ['y','yes','n','no']:
|
|
70
|
+
response = input(f'Files are archived for {jobid}, do you want to unarchive?' + " [Y/N]: ")
|
|
71
|
+
response = response.lower()
|
|
72
|
+
if response in ['y','yes']:
|
|
73
|
+
files_archived = files.check_if_files_archived(restore = True, suppress_error = True)
|
|
74
|
+
return files_archived
|
|
75
|
+
|
|
76
|
+
def run_analysis(target, jobids, compute_obj):
|
|
77
|
+
'''
|
|
78
|
+
Launches a set if analysis on a specific target
|
|
79
|
+
'''
|
|
80
|
+
|
|
81
|
+
from .singularity import run_on_apptainer
|
|
82
|
+
container_file = (Path(prefs['compute']['containers']['local'])/compute_obj.container).with_suffix('.sif')
|
|
83
|
+
def _get_cmds(jobids,
|
|
84
|
+
container_file = container_file,
|
|
85
|
+
bind = [],
|
|
86
|
+
bind_from_prefs = True):
|
|
87
|
+
cmds = []
|
|
88
|
+
from shutil import which
|
|
89
|
+
for j in jobids:
|
|
90
|
+
if container_file.exists() and which('apptainer'):
|
|
91
|
+
cmds.append(run_on_apptainer(container_file,
|
|
92
|
+
command = f'labdata2 task {j}',
|
|
93
|
+
cuda = compute_obj.cuda,
|
|
94
|
+
bind = bind,
|
|
95
|
+
bind_from_prefs = bind_from_prefs,
|
|
96
|
+
dry_run = True))
|
|
97
|
+
else:
|
|
98
|
+
cmds.append(f'labdata2 task {j}')
|
|
99
|
+
return cmds
|
|
100
|
+
task_host = prefs['hostname']
|
|
101
|
+
from ..schema import ComputeTask
|
|
102
|
+
if target == 'slurm': # run with slurm
|
|
103
|
+
from .schedulers import slurm_exists, slurm_submit
|
|
104
|
+
if slurm_exists():
|
|
105
|
+
for jid,cmd in zip(jobids,_get_cmds(jobids)):
|
|
106
|
+
begin = None
|
|
107
|
+
files_archived = check_archived(jid,check_local = True)
|
|
108
|
+
if files_archived:
|
|
109
|
+
begin = "now+5hour"
|
|
110
|
+
print('Delaying job for 5 hours (retrieve archive). ')
|
|
111
|
+
ComputeTask.update1(dict(job_id = j,task_status = 'WAITING (ARCHIVE)'))
|
|
112
|
+
|
|
113
|
+
if container_file.exists():
|
|
114
|
+
cmd += f' | apptainer exec {container_file} labdata2 logpipe {jid}'
|
|
115
|
+
else:
|
|
116
|
+
cmd += f' | labdata2 logpipe {jid}'
|
|
117
|
+
slurmjob = slurm_submit(compute_obj.name,
|
|
118
|
+
cmd,
|
|
119
|
+
begin = begin,
|
|
120
|
+
ntasks = 1,
|
|
121
|
+
ncpuspertask = DEFAULT_N_JOBS, # change later
|
|
122
|
+
gpus = 1 if compute_obj.cuda else None)
|
|
123
|
+
print(f'Submitted {tcolor["g"](compute_obj.name)} {tcolor["y"](jid)} to slurm [{tcolor["y"](slurmjob)}]')
|
|
124
|
+
ComputeTask.update1(dict(job_id = jid,
|
|
125
|
+
task_host = task_host + f'@{slurmjob}',
|
|
126
|
+
task_target = target))
|
|
127
|
+
else:
|
|
128
|
+
print(f'{tcolor["r"]("Could not find SLURM: did not submit compute tasks:")}')
|
|
129
|
+
print('\t\n'.join(cmds))
|
|
130
|
+
elif target == 'local': # run locally without scheduler
|
|
131
|
+
for cmd in _get_cmds(jobids,compute_obj.cuda):
|
|
132
|
+
os.system(cmd)
|
|
133
|
+
elif 'ec2' in target: # launch dedicated instance on AWS
|
|
134
|
+
# TODO: delayed begin not used here.
|
|
135
|
+
from .ec2 import ec2_cmd_for_launch,ec2_create_instance,ec2_connect
|
|
136
|
+
session,ec2 = ec2_connect()
|
|
137
|
+
for jid, cmd in zip(jobids,_get_cmds(
|
|
138
|
+
jobids,
|
|
139
|
+
compute_obj.cuda,
|
|
140
|
+
container_file = Path('idontexist'))):
|
|
141
|
+
cmd = ec2_cmd_for_launch(compute_obj.container,
|
|
142
|
+
cmd,
|
|
143
|
+
singularity_cuda = compute_obj.cuda,
|
|
144
|
+
append_log = jid)
|
|
145
|
+
# check if the target contains the words small or large
|
|
146
|
+
|
|
147
|
+
instance_type = target.replace('ec2-','')
|
|
148
|
+
if instance_type in compute_obj.ec2.keys():
|
|
149
|
+
instance_opts = compute_obj.ec2[instance_type]
|
|
150
|
+
else:
|
|
151
|
+
# using small instance
|
|
152
|
+
instance_opts = compute_obj.ec2['small']
|
|
153
|
+
ins = ec2_create_instance(ec2, user_data = cmd,
|
|
154
|
+
**instance_opts)
|
|
155
|
+
print(f'Submitted job to {tcolor["r"](ins["id"])} on an ec2 {instance_opts}')
|
|
156
|
+
task_host = ins["id"]
|
|
157
|
+
ComputeTask.update1(dict(job_id = jid,
|
|
158
|
+
task_host = task_host,
|
|
159
|
+
task_target = target))
|
|
160
|
+
else:
|
|
161
|
+
# check if there are remote services to launch
|
|
162
|
+
if 'remotes' in prefs['compute'].keys():
|
|
163
|
+
names = prefs['compute']['remotes'].keys()
|
|
164
|
+
targetname = str(target)
|
|
165
|
+
if target in names:
|
|
166
|
+
target = prefs['compute']['remotes'][target]
|
|
167
|
+
else:
|
|
168
|
+
raise(ValueError(f'Could not find target [{target}]'))
|
|
169
|
+
from .schedulers import ssh_connect,slurm_schedule_remote
|
|
170
|
+
container_file = f"$LABDATA_PATH/containers/{compute_obj.container}.sif"
|
|
171
|
+
with ssh_connect(target['address'],target['user'],target['permission_key']) as conn:
|
|
172
|
+
for j in jobids:
|
|
173
|
+
begin = None
|
|
174
|
+
files_archived = check_archived(j)
|
|
175
|
+
if files_archived:
|
|
176
|
+
begin = "now+5hour"
|
|
177
|
+
print('Delaying job for 5 hours (retrieve archive). ')
|
|
178
|
+
ComputeTask.update1(dict(job_id = j,task_status = 'WAITING (ARCHIVE)'))
|
|
179
|
+
# needs to have LABDATA_PATH defined in the remote
|
|
180
|
+
cmd = run_on_apptainer(container_file,
|
|
181
|
+
command = f'labdata2 task {j}',
|
|
182
|
+
cuda = compute_obj.cuda,
|
|
183
|
+
dry_run = True)
|
|
184
|
+
# generate slurm cmd and launch
|
|
185
|
+
cmd += f' | apptainer exec {container_file} labdata2 logpipe {j}'
|
|
186
|
+
opts = dict()
|
|
187
|
+
nt = str(targetname)
|
|
188
|
+
if compute_obj.name in target['analysis_options']:
|
|
189
|
+
opts = target['analysis_options'][compute_obj.name]
|
|
190
|
+
nt = f'{targetname}@{opts["queue"]}'
|
|
191
|
+
slurmjob = slurm_schedule_remote(cmd,
|
|
192
|
+
conn = conn,
|
|
193
|
+
begin = begin,
|
|
194
|
+
jobname = compute_obj.name+f'_{j}',
|
|
195
|
+
pre_cmds = target['pre_cmds'],
|
|
196
|
+
**opts)
|
|
197
|
+
if not slurmjob is None:
|
|
198
|
+
print(f'Submitted {tcolor["r"](compute_obj.name)} job {tcolor["y"](j)} to {tcolor["y"](nt)}[{tcolor["y"](slurmjob)}]')
|
|
199
|
+
ComputeTask.update1(dict(job_id = j,
|
|
200
|
+
task_target = nt))
|
|
201
|
+
|
|
202
|
+
# this class will execute compute jobs, it should be independent from the CLI but work with it.
|
|
203
|
+
class BaseCompute():
|
|
204
|
+
name = None
|
|
205
|
+
container = 'labdata-base'
|
|
206
|
+
cuda = False
|
|
207
|
+
ec2 = dict(small = dict(instance_type = 'g4dn.2xlarge'), # 8 cpus, 32 GB mem, 200 GB nvme, 1 gpu
|
|
208
|
+
large = dict(instance_type = 'g6.4xlarge',
|
|
209
|
+
availability_zone = 'us-west-2b')) # 16 cpus, 64 GB mem, 600 GB nvme, 1 gpu
|
|
210
|
+
|
|
211
|
+
def __init__(self,job_id, allow_s3 = None):
|
|
212
|
+
'''
|
|
213
|
+
Executes a computation on a dataset, that can be remote or local
|
|
214
|
+
Uses a singularity/apptainer image if possible
|
|
215
|
+
'''
|
|
216
|
+
self.file_filters = ['.'] # selects all files...
|
|
217
|
+
self.parameters = dict()
|
|
218
|
+
|
|
219
|
+
self.job_id = job_id
|
|
220
|
+
if not self.job_id is None:
|
|
221
|
+
self._check_if_taken()
|
|
222
|
+
|
|
223
|
+
self.paths = None
|
|
224
|
+
self.local_path = Path(prefs['local_paths'][0])
|
|
225
|
+
self.scratch_path = Path(prefs['scratch_path'])
|
|
226
|
+
self.assigned_files = None
|
|
227
|
+
self.dataset_key = None
|
|
228
|
+
self.is_container = False
|
|
229
|
+
if allow_s3 is None:
|
|
230
|
+
self.allow_s3 = prefs['allow_s3_download']
|
|
231
|
+
if 'LABDATA_CONTAINER' in os.environ.keys():
|
|
232
|
+
# then it is running inside a container
|
|
233
|
+
self.is_container = True
|
|
234
|
+
#self.is_ec2 = False # then files should be taken from s3
|
|
235
|
+
|
|
236
|
+
def _init_job(self): # to run in the init function
|
|
237
|
+
if not self.job_id is None:
|
|
238
|
+
from ..schema import ComputeTask,dj
|
|
239
|
+
with dj.conn().transaction:
|
|
240
|
+
self.jobquery = (ComputeTask() & dict(job_id = self.job_id))
|
|
241
|
+
job_status = self.jobquery.fetch(as_dict = True)
|
|
242
|
+
if len(job_status):
|
|
243
|
+
if not job_status[0]['task_waiting']:
|
|
244
|
+
if 'SLURM_RESTART_COUNT' in os.environ.keys():
|
|
245
|
+
# then the job is running on slurm.. its a putative restart, try to run it..
|
|
246
|
+
self.set_job_status(job_status = 'WORKING',
|
|
247
|
+
job_waiting = 0)
|
|
248
|
+
else:
|
|
249
|
+
print(f"Compute task [{self.job_id}] is already taken.")
|
|
250
|
+
print(job_status, flush = True)
|
|
251
|
+
return # exit.
|
|
252
|
+
else:
|
|
253
|
+
self.set_job_status(job_status = 'WORKING',
|
|
254
|
+
job_waiting = 0)
|
|
255
|
+
|
|
256
|
+
def cleanup_function(job_id = self.job_id):
|
|
257
|
+
# if it quits then register as canceled and put as waiting
|
|
258
|
+
from ..schema import ComputeTask
|
|
259
|
+
status = (ComputeTask() & dict(job_id = job_id)).fetch(as_dict = True)[0]
|
|
260
|
+
if status['task_status'] in ['WORKING']:
|
|
261
|
+
ComputeTask.update1(dict(job_id = job_id,
|
|
262
|
+
task_status = 'CANCELLED',
|
|
263
|
+
task_waiting = 1,
|
|
264
|
+
task_endtime = datetime.now()))
|
|
265
|
+
|
|
266
|
+
self.cleanup_function = cleanup_function
|
|
267
|
+
self.register_safe_exit()
|
|
268
|
+
par = json.loads(job_status[0]['task_parameters'])
|
|
269
|
+
for k in par.keys():
|
|
270
|
+
self.parameters[k] = par[k]
|
|
271
|
+
self.assigned_files = pd.DataFrame((ComputeTask.AssignedFiles() & dict(job_id = self.job_id)).fetch())
|
|
272
|
+
self.dataset_key = dict(subject_name = job_status[0]['subject_name'],
|
|
273
|
+
session_name = job_status[0]['session_name'],
|
|
274
|
+
dataset_name = job_status[0]['dataset_name'])
|
|
275
|
+
else:
|
|
276
|
+
# that should just be a problem to fix
|
|
277
|
+
raise ValueError(f'job_id {self.job_id} does not exist.')
|
|
278
|
+
|
|
279
|
+
def register_safe_exit(self):
|
|
280
|
+
import safe_exit
|
|
281
|
+
safe_exit.register(self.cleanup_function)
|
|
282
|
+
|
|
283
|
+
def unregister_safe_exit(self):
|
|
284
|
+
import safe_exit
|
|
285
|
+
safe_exit.unregister(self.cleanup_function)
|
|
286
|
+
|
|
287
|
+
def get_files(self, dset, allowed_extensions=[]):
|
|
288
|
+
'''
|
|
289
|
+
Gets the paths and downloads from S3 if needed.
|
|
290
|
+
'''
|
|
291
|
+
if type(dset) is list:
|
|
292
|
+
# then it is a list of dicts, convert to DataFrame
|
|
293
|
+
dset = pd.DataFrame(dset)
|
|
294
|
+
print(dset)
|
|
295
|
+
files = dset.file_path.values
|
|
296
|
+
storage = dset.storage.values
|
|
297
|
+
localpath = Path(prefs['local_paths'][0])
|
|
298
|
+
self.files_existed = True
|
|
299
|
+
localfiles = [find_local_filepath(f,
|
|
300
|
+
allowed_extensions = allowed_extensions) for f in files]
|
|
301
|
+
localfiles = np.unique(list(filter(lambda x: not x is None,localfiles)))
|
|
302
|
+
if not len(localfiles):
|
|
303
|
+
# then you can try downloading the files
|
|
304
|
+
if self.allow_s3: # get the files from s3
|
|
305
|
+
from ..s3 import copy_from_s3
|
|
306
|
+
for s in np.unique(storage):
|
|
307
|
+
# so it can work with multiple storages
|
|
308
|
+
srcfiles = [f for f in files[storage == s]]
|
|
309
|
+
dstfiles = [localpath/f for f in srcfiles]
|
|
310
|
+
print(f'Downloading {len(srcfiles)} files from S3 [{s}].')
|
|
311
|
+
copy_from_s3(srcfiles,dstfiles,storage_name = s)
|
|
312
|
+
localfiles = np.unique([find_local_filepath(
|
|
313
|
+
f,
|
|
314
|
+
allowed_extensions = allowed_extensions) for f in files])
|
|
315
|
+
if len(localfiles):
|
|
316
|
+
self.files_existed = False # delete the files in the end if they were not local.
|
|
317
|
+
else:
|
|
318
|
+
print(files, localpath)
|
|
319
|
+
raise(ValueError('Files not found locally, set allow_s3 in the preferences to download.'))
|
|
320
|
+
return localfiles
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def place_tasks_in_queue(self,datasets,task_cmd = None, force_submit = False):
|
|
324
|
+
''' This will put the tasks in the queue for each dataset.
|
|
325
|
+
If the task and parameters are the same it will return the job_id instead.
|
|
326
|
+
'''
|
|
327
|
+
from ..schema import ComputeTask, Dataset,dj
|
|
328
|
+
job_ids = []
|
|
329
|
+
if datasets is None:
|
|
330
|
+
datasets = [None]
|
|
331
|
+
|
|
332
|
+
for dataset in datasets:
|
|
333
|
+
if not dataset is None: # then there are no associated files.
|
|
334
|
+
files = pd.DataFrame((Dataset.DataFiles() & dataset).fetch())
|
|
335
|
+
idx = []
|
|
336
|
+
for f in self.file_filters:
|
|
337
|
+
idx += list(filter(lambda x: not x is None,[i if f in s else None for i,s in enumerate(
|
|
338
|
+
files.file_path.values)]))
|
|
339
|
+
if len(idx) == 0:
|
|
340
|
+
raise ValueError(f'Could not find valid Dataset.DataFiles for {dataset}')
|
|
341
|
+
files = files.iloc[idx]
|
|
342
|
+
key = dict(dataset,task_name = self.name)
|
|
343
|
+
exists = ComputeTask() & key
|
|
344
|
+
if len(exists):
|
|
345
|
+
d = pd.DataFrame(exists.fetch())
|
|
346
|
+
idx = np.where(np.array(d.task_parameters.values) == json.dumps(self.parameters))[0]
|
|
347
|
+
if len(idx):
|
|
348
|
+
job_id = d.iloc[idx].job_id.values[0]
|
|
349
|
+
print(f'There is a task to analyse dataset {key} with the same parameters. [{job_id}]')
|
|
350
|
+
if force_submit:
|
|
351
|
+
print('Deleting the previous job because force_submit is set.')
|
|
352
|
+
(ComputeTask() & f'job_id = {job_id}').delete(safemode = False) # deleting a previous job because of force_submit
|
|
353
|
+
else:
|
|
354
|
+
continue
|
|
355
|
+
else:
|
|
356
|
+
key = dict(task_name = self.name)
|
|
357
|
+
files = None
|
|
358
|
+
|
|
359
|
+
with dj.conn().transaction:
|
|
360
|
+
job_id = ComputeTask().fetch('job_id')
|
|
361
|
+
if len(job_id):
|
|
362
|
+
job_id = np.max(job_id) + 1
|
|
363
|
+
else:
|
|
364
|
+
job_id = 1
|
|
365
|
+
if not task_cmd is None:
|
|
366
|
+
if len(task_cmd) >500:
|
|
367
|
+
task_cmd = task_cmd[:499]
|
|
368
|
+
ComputeTask().insert1(dict(key,
|
|
369
|
+
job_id = job_id,
|
|
370
|
+
task_waiting = 1,
|
|
371
|
+
task_status = "WAITING",
|
|
372
|
+
task_target = None,
|
|
373
|
+
task_host = None,
|
|
374
|
+
task_cmd = task_cmd,
|
|
375
|
+
task_parameters = json.dumps(self.parameters),
|
|
376
|
+
task_log = None))
|
|
377
|
+
if not files is None:
|
|
378
|
+
ComputeTask.AssignedFiles().insert([dict(job_id = job_id,
|
|
379
|
+
storage = f.storage,
|
|
380
|
+
file_path = f.file_path)
|
|
381
|
+
for i,f in files.iterrows()])
|
|
382
|
+
job_ids.append(job_id)
|
|
383
|
+
return job_ids
|
|
384
|
+
|
|
385
|
+
def find_datasets(self,subject_name = None, session_name = None, dataset_name = None):
|
|
386
|
+
'''
|
|
387
|
+
Find datasets to analyze, this function will search in the proper tables if datasets are available.
|
|
388
|
+
Has to be implemented per Compute class since it varies.
|
|
389
|
+
'''
|
|
390
|
+
raise NotImplementedError('The find_datasets method has to be implemented.')
|
|
391
|
+
|
|
392
|
+
def secondary_parse(self,secondary_arguments,parameter_number = None):
|
|
393
|
+
self._secondary_parse(secondary_arguments,parameter_number)
|
|
394
|
+
|
|
395
|
+
def _secondary_parse(self,secondary_arguments):
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
def _check_if_taken(self):
|
|
399
|
+
if not self.job_id is None:
|
|
400
|
+
from ..schema import ComputeTask, File, dj
|
|
401
|
+
self.jobquery = (ComputeTask() & dict(job_id = self.job_id))
|
|
402
|
+
job_status = self.jobquery.fetch(as_dict = True)
|
|
403
|
+
if len(job_status):
|
|
404
|
+
if job_status[0]['task_waiting']:
|
|
405
|
+
return
|
|
406
|
+
else:
|
|
407
|
+
print(job_status, flush = True)
|
|
408
|
+
raise ValueError(f'job_id {self.job_id} is already taken.')
|
|
409
|
+
return # exit.
|
|
410
|
+
else:
|
|
411
|
+
raise ValueError(f'job_id {self.job_id} does not exist.')
|
|
412
|
+
# get the paths?
|
|
413
|
+
#self.src_paths = pd.DataFrame((ComputeTask.AssignedFiles() &
|
|
414
|
+
# dict(job_id = self.job_id)).fetch())
|
|
415
|
+
#if not len(self.src_paths):
|
|
416
|
+
# self.set_job_status(job_status = 'FAILED',
|
|
417
|
+
# job_log = f'Could not find files for {self.job_id} in ComputeTask.AssignedFiles.')
|
|
418
|
+
# raise ValueError(f'Could not find files for {self.job_id} in ComputeTask.AssignedFiles.')
|
|
419
|
+
else:
|
|
420
|
+
raise ValueError(f'Compute: job_id not specified.')
|
|
421
|
+
|
|
422
|
+
def compute(self):
|
|
423
|
+
'''This calls the compute function.
|
|
424
|
+
If "use_s3" is true it will download the files from s3 when needed.'''
|
|
425
|
+
from ..schema import ComputeTask
|
|
426
|
+
try:
|
|
427
|
+
if not self.job_id is None:
|
|
428
|
+
dd = dict(job_id = self.job_id,
|
|
429
|
+
task_starttime = datetime.now())
|
|
430
|
+
ComputeTask().update1(dd)
|
|
431
|
+
self._compute() # can use the src_paths
|
|
432
|
+
except Exception as err:
|
|
433
|
+
# log the error
|
|
434
|
+
print(f'There was an error processing job {self.job_id}.')
|
|
435
|
+
err = str(traceback.format_exc()) + "ERROR" +str(err)
|
|
436
|
+
print(err)
|
|
437
|
+
|
|
438
|
+
if len(err) > 1999: # then get only the last part of the error.
|
|
439
|
+
err = err[-1900:]
|
|
440
|
+
self.set_job_status(job_status = 'FAILED',
|
|
441
|
+
job_log = f'{err}')
|
|
442
|
+
return
|
|
443
|
+
self._post_compute() # so the rules can insert tables and all.
|
|
444
|
+
# get the job from the DB if the status is not failed, mark completed (remember to clean the log)
|
|
445
|
+
self.jobquery = (ComputeTask() & dict(job_id = self.job_id))
|
|
446
|
+
job_status = self.jobquery.fetch(as_dict = True)
|
|
447
|
+
if not job_status[0]['task_status'] in ['FAILED']:
|
|
448
|
+
self.set_job_status(job_status = 'COMPLETED')
|
|
449
|
+
if not self.job_id is None:
|
|
450
|
+
dd = dict(job_id = self.job_id,
|
|
451
|
+
task_endtime = datetime.now())
|
|
452
|
+
ComputeTask().update1(dd)
|
|
453
|
+
|
|
454
|
+
def set_job_status(self, job_status = None, job_log = None,job_waiting = 0):
|
|
455
|
+
from ..schema import ComputeTask
|
|
456
|
+
if not self.job_id is None:
|
|
457
|
+
dd = dict(job_id = self.job_id,
|
|
458
|
+
task_waiting = job_waiting,
|
|
459
|
+
task_host = prefs['hostname']) # so we know where it failed.)
|
|
460
|
+
if not job_status is None:
|
|
461
|
+
dd['task_status'] = job_status
|
|
462
|
+
if not job_log is None:
|
|
463
|
+
dd['task_log'] = job_log
|
|
464
|
+
ComputeTask.update1(dd)
|
|
465
|
+
if not job_status is None:
|
|
466
|
+
if not 'WORK' in job_status: # display the message
|
|
467
|
+
print(f'Check job_id {self.job_id} : {job_status}')
|
|
468
|
+
|
|
469
|
+
def _post_compute(self):
|
|
470
|
+
'''
|
|
471
|
+
Inserts the data to the database
|
|
472
|
+
'''
|
|
473
|
+
return
|
|
474
|
+
|
|
475
|
+
def _compute(self):
|
|
476
|
+
'''
|
|
477
|
+
Runs the compute job on a scratch folder.
|
|
478
|
+
'''
|
|
479
|
+
return
|
|
480
|
+
|
|
481
|
+
class PopulateCompute(BaseCompute):
|
|
482
|
+
container = 'labdata-base'
|
|
483
|
+
cuda = False
|
|
484
|
+
name = 'populate'
|
|
485
|
+
url = 'http://github.com/jcouto/labdata'
|
|
486
|
+
def __init__(self,job_id, allow_s3 = None, delete_results = False, **kwargs):
|
|
487
|
+
super(PopulateCompute,self).__init__(job_id, allow_s3 = allow_s3)
|
|
488
|
+
self.file_filters = None
|
|
489
|
+
# default parameters
|
|
490
|
+
self.parameters = dict(imports = 'labdata.schema',
|
|
491
|
+
table = 'UnitMetrics',
|
|
492
|
+
processes = 10)
|
|
493
|
+
|
|
494
|
+
self._init_job() # gets the parameters
|
|
495
|
+
|
|
496
|
+
def _secondary_parse(self,arguments,parameter_number):
|
|
497
|
+
'''
|
|
498
|
+
Handles parsing the command line interface
|
|
499
|
+
'''
|
|
500
|
+
import argparse
|
|
501
|
+
parser = argparse.ArgumentParser(
|
|
502
|
+
description = 'Populate arbitrary tables',
|
|
503
|
+
usage = 'populate -- -t <TABLE> -i <IMPORTS>')
|
|
504
|
+
|
|
505
|
+
parser.add_argument('table',action='store',type = str,
|
|
506
|
+
help = 'Table to populate')
|
|
507
|
+
parser.add_argument('-s','--stop-on-errors',action='store_true',default= False,
|
|
508
|
+
help = 'Stop on errors (negates suppress_errors)')
|
|
509
|
+
parser.add_argument('-r','--restrictions',action='store',default= '',
|
|
510
|
+
help = 'Restrictions to the populate table (dict(X = "x")) or completed_today (to run for sessions that were completed less than 24h ago)')
|
|
511
|
+
parser.add_argument('-i','--imports',action='store',default= 'labdata.schema',type = str,
|
|
512
|
+
help = 'import modules to load the table')
|
|
513
|
+
parser.add_argument('-p','--processes',
|
|
514
|
+
action='store', default=1, type = int,
|
|
515
|
+
help = "Required imports.")
|
|
516
|
+
parser.add_argument
|
|
517
|
+
|
|
518
|
+
args = parser.parse_args(arguments[1:])
|
|
519
|
+
self.parameters = dict(table = args.table,
|
|
520
|
+
imports = args.imports,
|
|
521
|
+
processes = args.processes,
|
|
522
|
+
suppress_errors = not args.stop_on_errors,
|
|
523
|
+
restrictions = args.restrictions)
|
|
524
|
+
# try the import and check if the default container exists.
|
|
525
|
+
if not self.parameters["imports"] in ['none','']:
|
|
526
|
+
to_import = self.parameters["table"]
|
|
527
|
+
if '.' in to_import: # to import plugins
|
|
528
|
+
to_import = to_import.split('.')[0]
|
|
529
|
+
exec(f'from {self.parameters["imports"]} import {to_import}')
|
|
530
|
+
table = eval(f'{self.parameters["table"]}')
|
|
531
|
+
if hasattr(table,'default_container'):
|
|
532
|
+
self.container = table.default_container
|
|
533
|
+
print(self.parameters)
|
|
534
|
+
|
|
535
|
+
def find_datasets(self):
|
|
536
|
+
return
|
|
537
|
+
|
|
538
|
+
def _compute(self):
|
|
539
|
+
# import
|
|
540
|
+
if not self.parameters["imports"] in ['none','']:
|
|
541
|
+
to_import = self.parameters["table"]
|
|
542
|
+
if '.' in to_import: # to import plugins
|
|
543
|
+
to_import = to_import.split('.')[0]
|
|
544
|
+
exec(f'from {self.parameters["imports"]} import {to_import}')
|
|
545
|
+
processes = 1
|
|
546
|
+
# check nprocesses
|
|
547
|
+
if 'processes' in self.parameters.keys():
|
|
548
|
+
processes = int(self.parameters['processes'])
|
|
549
|
+
# submit populate
|
|
550
|
+
suppress_errors = self.parameters['suppress_errors']
|
|
551
|
+
if self.parameters['restrictions'] == '':
|
|
552
|
+
exec(f'{self.parameters["table"]}.populate(suppress_errors={suppress_errors}, processes = {processes}, display_progress = True)')
|
|
553
|
+
else:
|
|
554
|
+
if self.parameters['restrictions'] == 'completed_today': # this will look for uploads that happened less than 24h ago
|
|
555
|
+
from ..schema import Session, UploadJob
|
|
556
|
+
restrictions = (Session() & (UploadJob() & 'job_status = "COMPLETED"') & 'session_datetime > DATE_SUB(CURDATE(), INTERVAL 24 HOUR)').proj().fetch(as_dict = True)
|
|
557
|
+
else:
|
|
558
|
+
restrictions = eval(self.parameters['restrictions'])
|
|
559
|
+
exec(f'{self.parameters["table"]}.populate({restrictions}, suppress_errors={suppress_errors}, processes = {processes}, display_progress = True)')
|
|
560
|
+
|
|
561
|
+
|