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/pose.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
from ..utils import *
|
|
2
|
+
from .utils import BaseCompute
|
|
3
|
+
|
|
4
|
+
class DeeplabcutCompute(BaseCompute):
|
|
5
|
+
container = 'labdata-deeplabcut'
|
|
6
|
+
cuda = True
|
|
7
|
+
name = 'deeplabcut'
|
|
8
|
+
url = 'http://github.com/DeepLabCut/DeepLabCut'
|
|
9
|
+
def __init__(self,job_id, allow_s3 = None, delete_results = True, **kwargs):
|
|
10
|
+
'''
|
|
11
|
+
Run deeplabcut on video or train a model
|
|
12
|
+
'''
|
|
13
|
+
super(DeeplabcutCompute,self).__init__(job_id, allow_s3 = allow_s3)
|
|
14
|
+
self.file_filters = ['.avi','.mov','.mp4','.zarr'] # allowed file extensions..
|
|
15
|
+
# default parameters
|
|
16
|
+
self.parameters = dict(algorithm = 'deeplabcut',
|
|
17
|
+
mode = None, # select 'train' or 'infer'
|
|
18
|
+
model_num = None,
|
|
19
|
+
label_set = None,
|
|
20
|
+
video_name = None,
|
|
21
|
+
net_type = 'resnet_50',
|
|
22
|
+
batch_size = 8,
|
|
23
|
+
iteractions = 100000)
|
|
24
|
+
self._init_job()
|
|
25
|
+
if not self.job_id is None:
|
|
26
|
+
self.add_parameter_key()
|
|
27
|
+
|
|
28
|
+
self.delete_results = delete_results
|
|
29
|
+
|
|
30
|
+
def add_parameter_key(self):
|
|
31
|
+
model_num, parameters = self._get_parameter_number()
|
|
32
|
+
from ..schema import PoseEstimationModel
|
|
33
|
+
if self.parameters['mode'] == 'train':
|
|
34
|
+
#if not model_num in parameters.model_num.values:
|
|
35
|
+
# PoseEstimationModel().insert1(dict(model_num = model_num,
|
|
36
|
+
# pose_label_set_num = self.parameters['label_set'],
|
|
37
|
+
# algorithm_name = self.name,
|
|
38
|
+
# parameters_dict = json.dumps(self.model_parameters),
|
|
39
|
+
# code_link = self.url),
|
|
40
|
+
# skip_duplicates=True) # these will be updated later
|
|
41
|
+
self.model_num = model_num
|
|
42
|
+
# check here if it was already infered with this model.
|
|
43
|
+
|
|
44
|
+
def _get_parameter_number(self):
|
|
45
|
+
self.model_parameters = dict(algorithm = self.parameters['algorithm'],
|
|
46
|
+
net_type = self.parameters['net_type'],
|
|
47
|
+
batch_size = self.parameters['batch_size'],
|
|
48
|
+
iteractions = self.parameters['iteractions'])
|
|
49
|
+
parameter_set_num = None
|
|
50
|
+
from ..schema import PoseEstimationModel
|
|
51
|
+
parameters = pd.DataFrame(PoseEstimationModel().fetch())
|
|
52
|
+
model_num = None
|
|
53
|
+
if self.parameters['mode'] == 'train':
|
|
54
|
+
for i,r in parameters.iterrows():
|
|
55
|
+
# go through every parameter and label_set
|
|
56
|
+
if (self.model_parameters == json.loads(r.parameters_dict) and
|
|
57
|
+
self.parameters['model_num'] is None and
|
|
58
|
+
self.parameters['pose_label_set_num'] == r['pose_label_set_num']):
|
|
59
|
+
model_num = r.model_num
|
|
60
|
+
if model_num is None:
|
|
61
|
+
if not self.parameters['model_num'] is None:
|
|
62
|
+
model_num = self.parameters['model_num']
|
|
63
|
+
elif len(parameters) == 0:
|
|
64
|
+
model_num = 1
|
|
65
|
+
else:
|
|
66
|
+
model_num = np.max(parameters.model_num.values)+1
|
|
67
|
+
self.parameters['model_num'] = model_num
|
|
68
|
+
return model_num,parameters
|
|
69
|
+
else:
|
|
70
|
+
return self.parameters['model_num'],parameters
|
|
71
|
+
|
|
72
|
+
def _secondary_parse(self,arguments,parameters = None):
|
|
73
|
+
'''
|
|
74
|
+
Handles parsing the command line interface
|
|
75
|
+
'''
|
|
76
|
+
if not parameters is None: # can just pass the parameters
|
|
77
|
+
self.parameters = parameters
|
|
78
|
+
else:
|
|
79
|
+
import argparse
|
|
80
|
+
parser = argparse.ArgumentParser(
|
|
81
|
+
description = 'Pose estimation analysis using DeepLabCut',
|
|
82
|
+
usage = '''
|
|
83
|
+
deeplabcut -a <SUBJECT> -s <SESSION> -- <TRAIN|INFER> <PARAMETERS>
|
|
84
|
+
|
|
85
|
+
Example for inference using a trained model (-m 1):
|
|
86
|
+
|
|
87
|
+
labdata2 run deeplabcut -a JC131 -s 20231025_194303 -- infer -m 1 -v side_cam
|
|
88
|
+
|
|
89
|
+
''')
|
|
90
|
+
|
|
91
|
+
parser.add_argument('mode',action='store', type = str,
|
|
92
|
+
help = '[required] Specifies what to do (train or infer)')
|
|
93
|
+
parser.add_argument('-v','--video-name',
|
|
94
|
+
action='store', type = str, default = None,
|
|
95
|
+
help = "Select files to analyze (DatasetVideo.video_name)")
|
|
96
|
+
parser.add_argument('-l','--label-set',
|
|
97
|
+
action='store', default=None, type = int,
|
|
98
|
+
help = "Label set to run training.")
|
|
99
|
+
parser.add_argument('-m','--model-num',
|
|
100
|
+
action='store', default=None, type = int,
|
|
101
|
+
help = "Model number to run inference.")
|
|
102
|
+
parser.add_argument('--net-type',
|
|
103
|
+
action='store', type = str, default = 'resnet_50',
|
|
104
|
+
help = "Network to run (has to be in the container - resnet_50; resnet_101)")
|
|
105
|
+
parser.add_argument('-i','--iteractions',
|
|
106
|
+
action='store', default=300000, type = int,
|
|
107
|
+
help = "Number of iteractions for training")
|
|
108
|
+
args = parser.parse_args(arguments[1:])
|
|
109
|
+
self.parameters['mode'] = args.mode
|
|
110
|
+
self.parameters['video_name'] = args.video_name
|
|
111
|
+
self.parameters['label_set'] = args.label_set
|
|
112
|
+
self.parameters['model_num'] = args.model_num
|
|
113
|
+
self.parameters['net_type'] = args.net_type
|
|
114
|
+
self.parameters['iteractions'] = args.iteractions
|
|
115
|
+
if 'train' in self.parameters['mode']:
|
|
116
|
+
if self.parameters['label_set'] is None:
|
|
117
|
+
raise(ValueError('Need to define a label-set to train a model.'))
|
|
118
|
+
else:
|
|
119
|
+
if self.parameters['model_num'] is None:
|
|
120
|
+
raise(ValueError('Need to specify a model.'))
|
|
121
|
+
from ..schema import PoseEstimationModel
|
|
122
|
+
if not len(PoseEstimationModel & f'model_num = {self.parameters["model_num"]}'):
|
|
123
|
+
raise(ValueError(f'Could not find model {self.parameters["model_num"]}'))
|
|
124
|
+
|
|
125
|
+
def find_datasets(self, subject_name = None, session_name = None):
|
|
126
|
+
'''
|
|
127
|
+
Searches for subjects and sessions
|
|
128
|
+
'''
|
|
129
|
+
if self.parameters['mode'] == 'train':
|
|
130
|
+
# check that the label set exists...
|
|
131
|
+
from ..schema import PoseEstimationLabelSet
|
|
132
|
+
pose_label_set = (PoseEstimationLabelSet() & f'pose_label_set_num = {self.parameters["label_set"]}').fetch()
|
|
133
|
+
return
|
|
134
|
+
if subject_name is None and session_name is None and self.parameters['mode'] == 'infer':
|
|
135
|
+
raise(ValueError('Need to select a dataset to infer using a deeplabcut model.'))
|
|
136
|
+
keys = []
|
|
137
|
+
if not subject_name is None:
|
|
138
|
+
if len(subject_name) > 1:
|
|
139
|
+
raise ValueError(f'Please submit one subject at a time {subject_name}.')
|
|
140
|
+
if not subject_name[0] == '':
|
|
141
|
+
subject_name = subject_name[0]
|
|
142
|
+
if not session_name is None:
|
|
143
|
+
for s in session_name:
|
|
144
|
+
if not s == '':
|
|
145
|
+
keys.append(dict(subject_name = subject_name,
|
|
146
|
+
session_name = s))
|
|
147
|
+
else:
|
|
148
|
+
raise(NotImplementedError('Specifying no session is not yet implemented'))
|
|
149
|
+
from ..schema import DatasetVideo
|
|
150
|
+
datasets = []
|
|
151
|
+
for k in keys:
|
|
152
|
+
datasets += (DatasetVideo()& k).fetch(as_dict = True)
|
|
153
|
+
datasets = [dict(subject_name = d['subject_name'],
|
|
154
|
+
session_name = d['session_name'],
|
|
155
|
+
dataset_name = d['dataset_name']) for d in datasets]
|
|
156
|
+
datasets = list({v['session_name']:v for v in datasets}.values())
|
|
157
|
+
return datasets
|
|
158
|
+
|
|
159
|
+
def _compute(self):
|
|
160
|
+
import deeplabcut
|
|
161
|
+
from ..schema import PoseEstimationLabelSet, PoseEstimationModel, DatasetVideo, File, PoseEstimation
|
|
162
|
+
if self.parameters['mode'] == 'train':
|
|
163
|
+
# check that the label set exists...
|
|
164
|
+
print(self.parameters)
|
|
165
|
+
print(f'pose_label_set_num = {self.parameters["label_set"]}')
|
|
166
|
+
pose_label_set = PoseEstimationLabelSet() & f'pose_label_set_num = {self.parameters["label_set"]}'
|
|
167
|
+
cfgfile = create_project(self.parameters,self.parameters['model_num'])
|
|
168
|
+
#print("Checking the labels.")
|
|
169
|
+
#deeplabcut.check_labels(cfgfile)
|
|
170
|
+
print("Generating the training dataset")
|
|
171
|
+
deeplabcut.create_training_dataset(cfgfile)
|
|
172
|
+
print("Training network")
|
|
173
|
+
deeplabcut.train_network(cfgfile, maxiters = self.parameters['iteractions'])
|
|
174
|
+
# once training completes, create a zip with the model and upload.
|
|
175
|
+
PoseEstimationModel().insert_model(self.parameters['model_num'],
|
|
176
|
+
model_folder=Path(cfgfile).parent,
|
|
177
|
+
pose_label_set_num = self.parameters["label_set"],
|
|
178
|
+
algorithm_name = self.parameters['algorithm'],
|
|
179
|
+
parameters = self.model_parameters,
|
|
180
|
+
training_datetime=datetime.now(),
|
|
181
|
+
container_name = self.container,
|
|
182
|
+
code_link = self.url)
|
|
183
|
+
# Save to PoseEstimationModel()
|
|
184
|
+
elif self.parameters['mode'] == 'infer':
|
|
185
|
+
# download the model if needed
|
|
186
|
+
cfgfile = (PoseEstimationModel() & f'model_num = {self.parameters["model_num"]}').get_model()
|
|
187
|
+
datasets = (File() & (DatasetVideo.File() & self.dataset_key)).fetch(as_dict = True)
|
|
188
|
+
if not len(datasets):
|
|
189
|
+
raise(ValueError(f"Could not find {self.dataset_key}"))
|
|
190
|
+
if len(datasets) > 1:
|
|
191
|
+
# select the video to analyse
|
|
192
|
+
datasets = (File() & (DatasetVideo.File() & dict(
|
|
193
|
+
self.dataset_key,
|
|
194
|
+
video_name = self.parameters['video_name']))).fetch(as_dict = True)
|
|
195
|
+
localfiles = self.get_files(datasets)
|
|
196
|
+
resfile = deeplabcut.analyze_videos(cfgfile,[str(f) for f in localfiles], videotype='.avi')
|
|
197
|
+
|
|
198
|
+
# Save the results to PoseEstimation()
|
|
199
|
+
if len(localfiles)>1:
|
|
200
|
+
print(f'Not sure how to insert multiple files.. Check the inputs {localfiles}.')
|
|
201
|
+
resfile = Path(str(localfiles[0].with_suffix(''))+resfile).with_suffix('.h5') # assuming there is only one file
|
|
202
|
+
bodyparts,xyl = read_dlc_file(resfile)
|
|
203
|
+
toinsert = []
|
|
204
|
+
for i,b in enumerate(bodyparts):
|
|
205
|
+
toinsert.append(dict(self.dataset_key,
|
|
206
|
+
video_name = self.parameters['video_name'],
|
|
207
|
+
model_num = self.parameters["model_num"],
|
|
208
|
+
label_name = b,
|
|
209
|
+
x = xyl[:,i,0],
|
|
210
|
+
y = xyl[:,i,1],
|
|
211
|
+
likelihood = xyl[:,i,2]))
|
|
212
|
+
PoseEstimation().insert(toinsert)
|
|
213
|
+
|
|
214
|
+
def create_project(parameters,model_num):
|
|
215
|
+
import deeplabcut
|
|
216
|
+
from ..schema import PoseEstimationLabelSet
|
|
217
|
+
print(f'Creating model {model_num} from pose_label_set_num {parameters["label_set"]}')
|
|
218
|
+
|
|
219
|
+
parameter_set_key = (PoseEstimationLabelSet() & f'pose_label_set_num = {parameters["label_set"]}').proj().fetch1()
|
|
220
|
+
parameter_set = (PoseEstimationLabelSet & parameter_set_key).fetch1()
|
|
221
|
+
labels = (PoseEstimationLabelSet.Label & parameter_set_key).fetch()
|
|
222
|
+
|
|
223
|
+
project_path,frames,frames_labels = (PoseEstimationLabelSet & parameter_set_key).export_labeling(
|
|
224
|
+
model_num = model_num,
|
|
225
|
+
export_only_labeled = True)
|
|
226
|
+
print(f'Exporting to {project_path}.')
|
|
227
|
+
F = frames['frame'].iloc[0]
|
|
228
|
+
project_path = project_path.parent.parent
|
|
229
|
+
bodyparts = [a for a in np.unique(labels['label_name'])]
|
|
230
|
+
|
|
231
|
+
cfg_file, ruamelFile = deeplabcut.utils.auxiliaryfunctions.create_config_template(multianimal= False)
|
|
232
|
+
|
|
233
|
+
cfg_file["multianimalproject"] = False
|
|
234
|
+
cfg_file["bodyparts"] = bodyparts
|
|
235
|
+
cfg_file["skeleton"] = [bodyparts,bodyparts]
|
|
236
|
+
cfg_file["default_augmenter"] = "default"
|
|
237
|
+
cfg_file["default_net_type"] = parameters['net_type']
|
|
238
|
+
|
|
239
|
+
# common parameters:
|
|
240
|
+
cfg_file["Task"] = f'model_{model_num}'
|
|
241
|
+
cfg_file["scorer"] = parameter_set['labeler']
|
|
242
|
+
cfg_file["video_sets"] = {f'label_set_{parameters["label_set"]}':dict(crop=[0,F.shape[0],0,F.shape[1]])}
|
|
243
|
+
cfg_file["project_path"] = str(project_path)
|
|
244
|
+
cfg_file["date"] = datetime.now().strftime('%b%d')
|
|
245
|
+
cfg_file["cropping"] = False
|
|
246
|
+
cfg_file["batch_size"] = parameters["batch_size"]
|
|
247
|
+
cfg_file["start"] = 0
|
|
248
|
+
cfg_file["stop"] = 1
|
|
249
|
+
cfg_file["numframes2pick"] = 20
|
|
250
|
+
cfg_file["TrainingFraction"] = [0.95]
|
|
251
|
+
cfg_file["iteration"] = 0
|
|
252
|
+
cfg_file["snapshotindex"] = -1
|
|
253
|
+
cfg_file["x1"] = 0
|
|
254
|
+
cfg_file["x2"] = 640
|
|
255
|
+
cfg_file["y1"] = 277
|
|
256
|
+
cfg_file["y2"] = 624
|
|
257
|
+
cfg_file["corner2move2"] = (50, 50)
|
|
258
|
+
cfg_file["move2corner"] = True
|
|
259
|
+
cfg_file["skeleton_color"] = "black"
|
|
260
|
+
cfg_file["pcutoff"] = 0.6
|
|
261
|
+
cfg_file["dotsize"] = 12 # for plots size of dots
|
|
262
|
+
cfg_file["alphavalue"] = 0.7 # for plots transparency of markers
|
|
263
|
+
cfg_file["colormap"] = "rainbow" # for plots type of colormap
|
|
264
|
+
for p in ['videos','training-datasets','dlc-models']:
|
|
265
|
+
(project_path/p).mkdir(exist_ok = True)
|
|
266
|
+
projconfigfile = os.path.join(str(project_path), "config.yaml")
|
|
267
|
+
# Write dictionary to yaml config file
|
|
268
|
+
deeplabcut.utils.auxiliaryfunctions.write_config(projconfigfile, cfg_file)
|
|
269
|
+
return projconfigfile
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def read_dlc_file(filepath):
|
|
273
|
+
posture = pd.read_hdf(filepath)
|
|
274
|
+
|
|
275
|
+
scorer = np.unique(posture.columns.get_level_values(0))[0]
|
|
276
|
+
bodyparts = np.unique(posture.columns.get_level_values(1))
|
|
277
|
+
xyl = []
|
|
278
|
+
for part in bodyparts:
|
|
279
|
+
xyl.append(np.vstack([posture[scorer][part]['x'].values,posture[scorer][part]['y'].values,posture[scorer][part]['likelihood'].values]))
|
|
280
|
+
xyl = np.stack(xyl).transpose(2,0,1) #frames,bodyparts,x-y-likelihood
|
|
281
|
+
return bodyparts, xyl
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# this has functions to interact with different schedulers.
|
|
2
|
+
|
|
3
|
+
from ..utils import *
|
|
4
|
+
import subprocess as sub
|
|
5
|
+
|
|
6
|
+
LABDATA_LOG_FOLDER = Path(LABDATA_FILE).parent/'logs'
|
|
7
|
+
|
|
8
|
+
def slurm_exists():
|
|
9
|
+
proc = sub.Popen('sinfo', shell=True, stdout=sub.PIPE, stderr = sub.PIPE)
|
|
10
|
+
out,err = proc.communicate()
|
|
11
|
+
if len(err):
|
|
12
|
+
return False
|
|
13
|
+
return True
|
|
14
|
+
|
|
15
|
+
def slurm_submit(jobname,
|
|
16
|
+
command,
|
|
17
|
+
ntasks = None,
|
|
18
|
+
ncpuspertask = None,
|
|
19
|
+
gpus = None,
|
|
20
|
+
memory = None,
|
|
21
|
+
walltime = None,
|
|
22
|
+
partition = None,
|
|
23
|
+
begin = None,
|
|
24
|
+
conda_environment = None,
|
|
25
|
+
module_environment = None,
|
|
26
|
+
mail = None,
|
|
27
|
+
sbatch_append = '',
|
|
28
|
+
**kwargs):
|
|
29
|
+
|
|
30
|
+
if ncpuspertask is None and ntasks is None:
|
|
31
|
+
from multiprocessing import cpu_count
|
|
32
|
+
ncpuspertask = 4
|
|
33
|
+
ntasks = 1
|
|
34
|
+
if ntasks is None:
|
|
35
|
+
ntasks = 1
|
|
36
|
+
if ncpuspertask is None:
|
|
37
|
+
ncpuspertask = 1
|
|
38
|
+
|
|
39
|
+
sjobfile = '''#!/bin/bash -login
|
|
40
|
+
#SBATCH --job-name={jobname}
|
|
41
|
+
#SBATCH --output={logfolder}/{jobname}_%j.stdout
|
|
42
|
+
#SBATCH --error={logfolder}/{jobname}_%j.stdout
|
|
43
|
+
|
|
44
|
+
#SBATCH --ntasks={ntasks}
|
|
45
|
+
#SBATCH --cpus-per-task={ncpus}
|
|
46
|
+
'''.format(jobname = jobname,
|
|
47
|
+
logfolder = LABDATA_LOG_FOLDER,
|
|
48
|
+
ntasks = ntasks,
|
|
49
|
+
ncpus = ncpuspertask)
|
|
50
|
+
if not walltime is None:
|
|
51
|
+
sjobfile += '#SBATCH --time={0} \n'.format(walltime)
|
|
52
|
+
if not memory is None:
|
|
53
|
+
sjobfile += '#SBATCH --mem={0} \n'.format(memory)
|
|
54
|
+
if not gpus is None:
|
|
55
|
+
sjobfile += '#SBATCH --gpus={0} \n'.format(gpus)
|
|
56
|
+
if not partition is None:
|
|
57
|
+
sjobfile += '#SBATCH --partition={0} \n'.format(partition)
|
|
58
|
+
if not begin is None:
|
|
59
|
+
sjobfile += '#SBATCH --begin={0} \n'.format(begin)
|
|
60
|
+
if not mail is None:
|
|
61
|
+
sjobfile += '#SBATCH --mail-user={0} \n#SBATCH --mail-type=END,FAIL \n'.format(mail)
|
|
62
|
+
if not module_environment is None:
|
|
63
|
+
sjobfile += '\n module purge\n'
|
|
64
|
+
sjobfile += '\n module load {0} \n'.format(module_environment)
|
|
65
|
+
if not conda_environment is None:
|
|
66
|
+
sjobfile += 'conda activate {0} \n'.format(conda_environment)
|
|
67
|
+
sjobfile += '''echo JOB {jobname} STARTED `date`
|
|
68
|
+
{cmd}
|
|
69
|
+
echo JOB FINISHED `date`
|
|
70
|
+
'''.format(jobname = jobname, cmd = command)
|
|
71
|
+
|
|
72
|
+
if not LABDATA_LOG_FOLDER.exists():
|
|
73
|
+
LABDATA_LOG_FOLDER.makedirs()
|
|
74
|
+
nfiles = len(list(LABDATA_LOG_FOLDER.glob('*.sh')))
|
|
75
|
+
filename = LABDATA_LOG_FOLDER/'{jobname}_{nfiles}.sh'.format(jobname = jobname,
|
|
76
|
+
nfiles = nfiles+1)
|
|
77
|
+
with open(filename,'w') as f:
|
|
78
|
+
f.write(sjobfile)
|
|
79
|
+
submit_cmd = 'cd {0} && sbatch {2} {1}'.format(filename.parent,
|
|
80
|
+
filename.name,
|
|
81
|
+
sbatch_append)
|
|
82
|
+
proc = sub.Popen(submit_cmd, shell=True, stdout=sub.PIPE)
|
|
83
|
+
out,err = proc.communicate()
|
|
84
|
+
|
|
85
|
+
if b'Submitted batch job' in out:
|
|
86
|
+
jobid = int(re.findall("Submitted batch job ([0-9]+)", str(out))[0])
|
|
87
|
+
return jobid
|
|
88
|
+
else:
|
|
89
|
+
print(out)
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def ssh_connect(address,user,permission_key=None):
|
|
94
|
+
try:
|
|
95
|
+
import paramiko
|
|
96
|
+
except:
|
|
97
|
+
raise(OSError('You need paramiko installed: "pip install paramiko"'))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
ssh = paramiko.SSHClient()
|
|
101
|
+
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
102
|
+
privkey = permission_key
|
|
103
|
+
if not permission_key is None:
|
|
104
|
+
keys_folder = Path(LABDATA_PATH)
|
|
105
|
+
key = list(keys_folder.rglob(permission_key+'*')) # doen't care where it is inside labdata
|
|
106
|
+
if len(key):
|
|
107
|
+
key = key[0]
|
|
108
|
+
with open(key,'r') as fd:
|
|
109
|
+
privkey = paramiko.RSAKey.from_private_key(fd)
|
|
110
|
+
ssh.connect(hostname = address,
|
|
111
|
+
username = user,
|
|
112
|
+
pkey = privkey)
|
|
113
|
+
return ssh
|
|
114
|
+
|
|
115
|
+
def slurm_schedule_remote(command,
|
|
116
|
+
address = None,
|
|
117
|
+
user = None,
|
|
118
|
+
permission_key=None,
|
|
119
|
+
jobname = 'unknown_1',
|
|
120
|
+
ncpus = None,
|
|
121
|
+
queue = None,
|
|
122
|
+
gpus = None,
|
|
123
|
+
memory = None,
|
|
124
|
+
walltime = None,
|
|
125
|
+
begin = None,
|
|
126
|
+
conda_environment = None,
|
|
127
|
+
module_environment = None,
|
|
128
|
+
mail = None,
|
|
129
|
+
exclusive = True,
|
|
130
|
+
pre_cmds = '',
|
|
131
|
+
remote_dir = '/shared/labdata/remote_jobs',
|
|
132
|
+
**kwargs):
|
|
133
|
+
sjobfile = f'''#!/bin/bash -login
|
|
134
|
+
#SBATCH --job-name={jobname}
|
|
135
|
+
#SBATCH --output={remote_dir}/{jobname}_%j.stdout
|
|
136
|
+
#SBATCH --error={remote_dir}/{jobname}_%j.stdout
|
|
137
|
+
#SBATCH --ntasks=1
|
|
138
|
+
'''
|
|
139
|
+
if not ncpus is None:
|
|
140
|
+
sjobfile += f'#SBATCH --cpus-per-task={ncpus}'
|
|
141
|
+
if not walltime is None:
|
|
142
|
+
sjobfile += '#SBATCH --time={0} \n'.format(walltime)
|
|
143
|
+
if not memory is None:
|
|
144
|
+
sjobfile += '#SBATCH --mem={0} \n'.format(memory)
|
|
145
|
+
if not gpus is None:
|
|
146
|
+
sjobfile += '#SBATCH --gpus={0} \n'.format(gpus)
|
|
147
|
+
if not queue is None:
|
|
148
|
+
sjobfile += '#SBATCH --partition={0} \n'.format(queue)
|
|
149
|
+
if not begin is None:
|
|
150
|
+
sjobfile += '#SBATCH --begin={0} \n'.format(begin)
|
|
151
|
+
if not mail is None:
|
|
152
|
+
sjobfile += '#SBATCH --mail-user={0} \n#SBATCH --mail-type=END,FAIL \n'.format(mail)
|
|
153
|
+
if exclusive:
|
|
154
|
+
sjobfile += '#SBATCH --exclusive \n'
|
|
155
|
+
if not module_environment is None:
|
|
156
|
+
sjobfile += '\n module purge\n'
|
|
157
|
+
sjobfile += '\n module load {0} \n'.format(module_environment)
|
|
158
|
+
if not conda_environment is None:
|
|
159
|
+
sjobfile += 'conda activate {0} \n'.format(conda_environment)
|
|
160
|
+
if not pre_cmds is None:
|
|
161
|
+
pre_cmds = '\n'.join(pre_cmds)
|
|
162
|
+
sjobfile += f'''echo JOB {jobname} STARTED \`date\`
|
|
163
|
+
tic=\`date +%s.%N\`
|
|
164
|
+
{pre_cmds}
|
|
165
|
+
{command}
|
|
166
|
+
echo JOB FINISHED \`date\`
|
|
167
|
+
toc=\`date +%s.%N\`
|
|
168
|
+
a=\`echo "(\$toc - \$tic)" | bc -l\`
|
|
169
|
+
b=\`printf %.3f \$a\`
|
|
170
|
+
echo JOB COMPLETED IN \$b min
|
|
171
|
+
'''
|
|
172
|
+
remote_command = f'''
|
|
173
|
+
mkdir -p {remote_dir}
|
|
174
|
+
cat > {remote_dir}/{jobname}.sh << EOL
|
|
175
|
+
{sjobfile}
|
|
176
|
+
EOL
|
|
177
|
+
|
|
178
|
+
sbatch {remote_dir}/{jobname}.sh
|
|
179
|
+
'''
|
|
180
|
+
# try to connect to ssh
|
|
181
|
+
#print(remote_command)
|
|
182
|
+
if not 'conn' in kwargs.keys():
|
|
183
|
+
conn = ssh_connect(address,user,permission_key)
|
|
184
|
+
else:
|
|
185
|
+
conn = kwargs['conn']
|
|
186
|
+
stdin,stdout,stderr = conn.exec_command(remote_command)
|
|
187
|
+
output = stdout.read().decode()
|
|
188
|
+
errors = stderr.read().decode()
|
|
189
|
+
if 'Submitted batch job' in output:
|
|
190
|
+
jobid = int(re.findall("Submitted batch job ([0-9]+)", str(output))[0])
|
|
191
|
+
return jobid
|
|
192
|
+
else:
|
|
193
|
+
print(output,errors)
|
|
194
|
+
return None
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from ..utils import *
|
|
2
|
+
|
|
3
|
+
def build_singularity_container(definition_file,
|
|
4
|
+
force = False,
|
|
5
|
+
output_folder = None):
|
|
6
|
+
'''
|
|
7
|
+
Builds a singularity container from a definition file.
|
|
8
|
+
|
|
9
|
+
The default output folder is prefs['compute_containers']['local_path']
|
|
10
|
+
|
|
11
|
+
e.g.
|
|
12
|
+
image = build_singularity_container('/home/joao/lib/labdata/containers/labdata-base.sdef')
|
|
13
|
+
|
|
14
|
+
Joao Couto - labdata 2024
|
|
15
|
+
'''
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import spython
|
|
19
|
+
except:
|
|
20
|
+
print("You'll need to install singularity-python:")
|
|
21
|
+
print(" pip install spython")
|
|
22
|
+
|
|
23
|
+
if output_folder is None:
|
|
24
|
+
output_folder = prefs['compute_containers']['local_path']
|
|
25
|
+
|
|
26
|
+
output_folder = Path(output_folder)
|
|
27
|
+
if not output_folder.exists():
|
|
28
|
+
output_folder.mkdir(parents=True,exist_ok = True)
|
|
29
|
+
|
|
30
|
+
definition_file = Path(definition_file)
|
|
31
|
+
container_file = (output_folder/definition_file.stem).with_suffix('.sif')
|
|
32
|
+
|
|
33
|
+
from spython.main import Client
|
|
34
|
+
|
|
35
|
+
image = Client.build(recipe = f'{definition_file}',
|
|
36
|
+
image = f'{container_file}',
|
|
37
|
+
stream = False, sudo = False, force = force,
|
|
38
|
+
options=["--fakeroot"])
|
|
39
|
+
|
|
40
|
+
# alternatively this could also be done with the CLI
|
|
41
|
+
#from subprocess import check_output
|
|
42
|
+
#check_output(f'singularity build --fakeroot {container_file} {definition_file}',shell= True)
|
|
43
|
+
|
|
44
|
+
return image
|
|
45
|
+
|
|
46
|
+
def run_on_singularity(containerfile,
|
|
47
|
+
command,
|
|
48
|
+
cuda = False,
|
|
49
|
+
bind = [],
|
|
50
|
+
bind_from_prefs = False,
|
|
51
|
+
dry_run = False):
|
|
52
|
+
nv = ''
|
|
53
|
+
if cuda:
|
|
54
|
+
nv = '--nv'
|
|
55
|
+
bind_str = ''
|
|
56
|
+
for b in bind:
|
|
57
|
+
bind_str += ' --bind {b}'
|
|
58
|
+
if bind_from_prefs:
|
|
59
|
+
local_paths = prefs['local_paths']
|
|
60
|
+
for p in local_paths:
|
|
61
|
+
bind_str += f' --bind {p}:{p}'
|
|
62
|
+
p = prefs['scratch_path']
|
|
63
|
+
bind_str += f' --bind {p}:{p}'
|
|
64
|
+
cmd = f"singularity exec {nv} {bind_str} {containerfile} {command}"
|
|
65
|
+
if not dry_run:
|
|
66
|
+
import subprocess as sub # run this
|
|
67
|
+
sub.run(cmd,shell = True)
|
|
68
|
+
else:
|
|
69
|
+
return cmd
|
|
70
|
+
|
|
71
|
+
def run_on_apptainer(containerfile,
|
|
72
|
+
command,
|
|
73
|
+
launch_cmd = 'exec',
|
|
74
|
+
cuda = False,
|
|
75
|
+
bind = [],
|
|
76
|
+
bind_from_prefs = False,
|
|
77
|
+
dry_run = False):
|
|
78
|
+
nv = ''
|
|
79
|
+
if cuda:
|
|
80
|
+
nv = '--nv'
|
|
81
|
+
bind_str = ''
|
|
82
|
+
for b in bind:
|
|
83
|
+
bind_str += ' --bind {b}'
|
|
84
|
+
if bind_from_prefs:
|
|
85
|
+
local_paths = prefs['local_paths']
|
|
86
|
+
for p in local_paths:
|
|
87
|
+
bind_str += f' --bind {p}:{p}'
|
|
88
|
+
p = prefs['scratch_path']
|
|
89
|
+
bind_str += f' --bind {p}:{p}'
|
|
90
|
+
cmd = f"apptainer {launch_cmd} {nv} {bind_str} {containerfile} {command}"
|
|
91
|
+
if not dry_run:
|
|
92
|
+
import subprocess as sub # run this
|
|
93
|
+
sub.run(cmd,shell = True)
|
|
94
|
+
else:
|
|
95
|
+
return cmd
|