cellmaps-utils 0.10.0__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.
- cellmaps_utils/__init__.py +9 -0
- cellmaps_utils/apms_readme.txt +85 -0
- cellmaps_utils/apms_zmadex_saint_readme.txt +165 -0
- cellmaps_utils/apmstool.py +945 -0
- cellmaps_utils/basecmdtool.py +108 -0
- cellmaps_utils/cellmaps_utilscmd.py +133 -0
- cellmaps_utils/challenge.py +1000 -0
- cellmaps_utils/constants.py +392 -0
- cellmaps_utils/crispr_readme.txt +19 -0
- cellmaps_utils/crisprtool.py +369 -0
- cellmaps_utils/ddotconverter.py +285 -0
- cellmaps_utils/exceptions.py +15 -0
- cellmaps_utils/hcx_utils.py +198 -0
- cellmaps_utils/hidefconverter.py +245 -0
- cellmaps_utils/hierdiff.py +142 -0
- cellmaps_utils/ifimage_readme.txt +31 -0
- cellmaps_utils/iftool.py +657 -0
- cellmaps_utils/logutils.py +214 -0
- cellmaps_utils/music_utils.py +246 -0
- cellmaps_utils/ndexupload.py +193 -0
- cellmaps_utils/provenance.py +890 -0
- cellmaps_utils/secms_readme.txt +81 -0
- cellmaps_utils/secmstool.py +262 -0
- cellmaps_utils/statsgenerator.py +237 -0
- cellmaps_utils/tabletool.py +399 -0
- cellmaps_utils-0.10.0.data/scripts/cellmaps_utilscmd.py +133 -0
- cellmaps_utils-0.10.0.dist-info/AUTHORS.rst +20 -0
- cellmaps_utils-0.10.0.dist-info/LICENSE +22 -0
- cellmaps_utils-0.10.0.dist-info/METADATA +275 -0
- cellmaps_utils-0.10.0.dist-info/RECORD +32 -0
- cellmaps_utils-0.10.0.dist-info/WHEEL +5 -0
- cellmaps_utils-0.10.0.dist-info/top_level.txt +1 -0
cellmaps_utils/iftool.py
ADDED
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import shutil
|
|
4
|
+
import uuid
|
|
5
|
+
from datetime import date
|
|
6
|
+
import warnings
|
|
7
|
+
import logging
|
|
8
|
+
import requests
|
|
9
|
+
from tqdm import tqdm
|
|
10
|
+
from multiprocessing import Pool
|
|
11
|
+
import pandas as pd
|
|
12
|
+
import cellmaps_utils
|
|
13
|
+
from cellmaps_utils.basecmdtool import BaseCommandLineTool
|
|
14
|
+
from cellmaps_utils.exceptions import CellMapsError
|
|
15
|
+
from cellmaps_utils import constants
|
|
16
|
+
from cellmaps_utils.provenance import ProvenanceUtil
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def download_file_skip_existing(downloadtuple):
|
|
22
|
+
"""
|
|
23
|
+
Downloads file in **downloadtuple** unless the file already exists
|
|
24
|
+
with a size greater then 0 bytes, in which case function
|
|
25
|
+
just returns
|
|
26
|
+
|
|
27
|
+
:param downloadtuple: (download link, dest file path)
|
|
28
|
+
:type downloadtuple: tuple
|
|
29
|
+
:return: None upon success otherwise:
|
|
30
|
+
(requests status code, text from request, downloadtuple)
|
|
31
|
+
:rtype: tuple
|
|
32
|
+
"""
|
|
33
|
+
if os.path.isfile(downloadtuple[1]) and os.path.getsize(downloadtuple[1]) > 0:
|
|
34
|
+
return None
|
|
35
|
+
return download_file(downloadtuple)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def download_file(downloadtuple):
|
|
39
|
+
"""
|
|
40
|
+
Downloads file pointed to by 'download_url' to
|
|
41
|
+
'destfile'
|
|
42
|
+
|
|
43
|
+
.. note::
|
|
44
|
+
|
|
45
|
+
Default download function used by :py:class:`~MultiProcessImageDownloader`
|
|
46
|
+
|
|
47
|
+
:param downloadtuple: `(download link, dest file path)`
|
|
48
|
+
:type downloadtuple: tuple
|
|
49
|
+
:return: None upon success otherwise:
|
|
50
|
+
`(requests status code, text from request, downloadtuple)`
|
|
51
|
+
:rtype: tuple
|
|
52
|
+
"""
|
|
53
|
+
logger.debug('Downloading ' + downloadtuple[0] + ' to ' + downloadtuple[1])
|
|
54
|
+
try:
|
|
55
|
+
with requests.get(downloadtuple[0], stream=True) as r:
|
|
56
|
+
if r.status_code != 200:
|
|
57
|
+
return r.status_code, r.text, downloadtuple
|
|
58
|
+
with open(downloadtuple[1], 'wb') as f:
|
|
59
|
+
for chunk in r.iter_content(chunk_size=8192):
|
|
60
|
+
if chunk: # filter out keep-alive new chunks
|
|
61
|
+
f.write(chunk)
|
|
62
|
+
return None
|
|
63
|
+
except requests.exceptions.HTTPError as e:
|
|
64
|
+
return -1, str(e), downloadtuple
|
|
65
|
+
except requests.exceptions.ConnectionError as e:
|
|
66
|
+
return -2, str(e), downloadtuple
|
|
67
|
+
except requests.exceptions.Timeout as e:
|
|
68
|
+
return -3, str(e), downloadtuple
|
|
69
|
+
except requests.exceptions.RequestException as e:
|
|
70
|
+
return -4, str(e), downloadtuple
|
|
71
|
+
except Exception as e:
|
|
72
|
+
return -5, str(e), downloadtuple
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ImageDownloader(object):
|
|
76
|
+
"""
|
|
77
|
+
Abstract class that defines interface for classes that download images
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
def __init__(self):
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
def download_images(self, download_list=None):
|
|
87
|
+
"""
|
|
88
|
+
Subclasses should implement
|
|
89
|
+
|
|
90
|
+
:param download_list: list of tuples where first element is
|
|
91
|
+
full URL of image to download and 2nd
|
|
92
|
+
element is destination path
|
|
93
|
+
:type download_list: list
|
|
94
|
+
:return:
|
|
95
|
+
"""
|
|
96
|
+
raise CellMapsError('Subclasses should implement this')
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class FakeImageDownloader(ImageDownloader):
|
|
100
|
+
"""
|
|
101
|
+
Creates fake download by downloading
|
|
102
|
+
the first image in each color from
|
|
103
|
+
`Human Protein Atlas <https://www.proteinatlas.org/>`__
|
|
104
|
+
and making renamed copies. The :py:func:`download_file` function
|
|
105
|
+
is used to download the first image of each color
|
|
106
|
+
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def __init__(self):
|
|
110
|
+
"""
|
|
111
|
+
Constructor
|
|
112
|
+
|
|
113
|
+
"""
|
|
114
|
+
super().__init__()
|
|
115
|
+
warnings.warn('This downloader generates FAKE images\n'
|
|
116
|
+
'You have been warned!!!\n'
|
|
117
|
+
'Have a nice day')
|
|
118
|
+
|
|
119
|
+
def download_images(self, download_list=None):
|
|
120
|
+
"""
|
|
121
|
+
Downloads 1st image from server and then
|
|
122
|
+
and makes renamed copies for subsequent images
|
|
123
|
+
|
|
124
|
+
:param download_list:
|
|
125
|
+
:type download_list: list of tuple
|
|
126
|
+
:return:
|
|
127
|
+
"""
|
|
128
|
+
num_to_download = len(download_list)
|
|
129
|
+
logger.info(str(num_to_download) + ' images to download')
|
|
130
|
+
t = tqdm(total=num_to_download, desc='Download',
|
|
131
|
+
unit='images')
|
|
132
|
+
|
|
133
|
+
src_image_dict = {}
|
|
134
|
+
# assume 1st four images are the colors for the first image
|
|
135
|
+
for entry in download_list[0:4]:
|
|
136
|
+
t.update()
|
|
137
|
+
if download_file(entry) is not None:
|
|
138
|
+
raise CellMapsError('Unable to download ' +
|
|
139
|
+
str(entry))
|
|
140
|
+
fname = os.path.basename(entry[1])
|
|
141
|
+
color = re.sub(r'\..*$', '', re.sub('^.*_', '', fname))
|
|
142
|
+
src_image_dict[color] = entry[1]
|
|
143
|
+
|
|
144
|
+
for entry in download_list[5:]:
|
|
145
|
+
t.update()
|
|
146
|
+
fname = os.path.basename(entry[1])
|
|
147
|
+
color = re.sub(r'\..*$', '', re.sub('^.*_', '', fname))
|
|
148
|
+
shutil.copy(src_image_dict[color], entry[1])
|
|
149
|
+
return []
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class MultiProcessImageDownloader(ImageDownloader):
|
|
153
|
+
"""
|
|
154
|
+
Uses multiprocess package to download images in parallel
|
|
155
|
+
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def __init__(self, poolsize=4, skip_existing=False,
|
|
159
|
+
override_dfunc=None):
|
|
160
|
+
"""
|
|
161
|
+
Constructor
|
|
162
|
+
|
|
163
|
+
.. warning::
|
|
164
|
+
|
|
165
|
+
Exceeding **poolsize** of ``4`` causes errors from Human Protein Atlas site
|
|
166
|
+
|
|
167
|
+
:param poolsize: Number of concurrent downloaders to use.
|
|
168
|
+
:type poolsize: int
|
|
169
|
+
:param skip_existing: If ``True`` skip download if image file exists and has size
|
|
170
|
+
greater then ``0``
|
|
171
|
+
:type skip_existing: bool
|
|
172
|
+
:param override_dfunc: Function that takes a tuple `(image URL, download str path)`
|
|
173
|
+
and downloads the image. If ``None`` :py:func:`download_file`
|
|
174
|
+
function is used
|
|
175
|
+
:type override_dfunc: :py:class:`function`
|
|
176
|
+
"""
|
|
177
|
+
super().__init__()
|
|
178
|
+
self._poolsize = poolsize
|
|
179
|
+
if override_dfunc is not None:
|
|
180
|
+
self._dfunc = override_dfunc
|
|
181
|
+
else:
|
|
182
|
+
self._dfunc = download_file
|
|
183
|
+
if skip_existing is True:
|
|
184
|
+
self._dfunc = download_file_skip_existing
|
|
185
|
+
|
|
186
|
+
def download_images(self, download_list=None):
|
|
187
|
+
"""
|
|
188
|
+
Downloads images returning a list of failed downloads
|
|
189
|
+
|
|
190
|
+
.. code-block::
|
|
191
|
+
|
|
192
|
+
from cellmaps_imagedownloader.runner import MultiProcessImageDownloader
|
|
193
|
+
|
|
194
|
+
dloader = MultiProcessImageDownloader(poolsize=2)
|
|
195
|
+
|
|
196
|
+
d_list = [('https://images.proteinatlas.org/992/1_A1_1_red.jpg',
|
|
197
|
+
'/tmp/1_A1_1_red.jpg')]
|
|
198
|
+
failed = dloader.download_images(download_list=d_list)
|
|
199
|
+
|
|
200
|
+
:param download_list: Each tuple of format `(image URL, dest file path)`
|
|
201
|
+
:type download_list: list of tuple
|
|
202
|
+
:return: Failed downloads, format of tuple
|
|
203
|
+
(`http status code`, `text of error`, (`link`, `destfile`))
|
|
204
|
+
:rtype: list of tuple
|
|
205
|
+
"""
|
|
206
|
+
failed_downloads = []
|
|
207
|
+
logger.debug('Poolsize for image downloader set to: ' +
|
|
208
|
+
str(self._poolsize))
|
|
209
|
+
with Pool(processes=self._poolsize) as pool:
|
|
210
|
+
num_to_download = len(download_list)
|
|
211
|
+
logger.info(str(num_to_download) + ' images to download')
|
|
212
|
+
t = tqdm(total=num_to_download, desc='Download',
|
|
213
|
+
unit='images')
|
|
214
|
+
for i in pool.imap_unordered(self._dfunc,
|
|
215
|
+
download_list):
|
|
216
|
+
t.update()
|
|
217
|
+
if i is not None:
|
|
218
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
219
|
+
logger.debug('Failed download: ' + str(i))
|
|
220
|
+
failed_downloads.append(i)
|
|
221
|
+
return failed_downloads
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class IFImageDataConverter(BaseCommandLineTool):
|
|
225
|
+
"""
|
|
226
|
+
Converts IF Image data into format consumable
|
|
227
|
+
by Cell Maps Pipeline
|
|
228
|
+
"""
|
|
229
|
+
COMMAND = 'ifconverter'
|
|
230
|
+
|
|
231
|
+
def __init__(self, theargs,
|
|
232
|
+
imgsuffix='.jpg',
|
|
233
|
+
provenance_utils=ProvenanceUtil(),
|
|
234
|
+
imagedownloader=None):
|
|
235
|
+
"""
|
|
236
|
+
Constructor
|
|
237
|
+
|
|
238
|
+
:param theargs: Command line arguments that at minimum need
|
|
239
|
+
to have the following attributes:
|
|
240
|
+
:type theargs: :py:class:`~python.argparse.Namespace`
|
|
241
|
+
"""
|
|
242
|
+
super().__init__()
|
|
243
|
+
self._outdir = os.path.abspath(theargs.outdir)
|
|
244
|
+
self._input = theargs.input
|
|
245
|
+
self._name = theargs.name
|
|
246
|
+
self._organization_name = theargs.organization_name
|
|
247
|
+
self._project_name = theargs.project_name
|
|
248
|
+
self._release = theargs.release
|
|
249
|
+
self._cell_line = theargs.cell_line
|
|
250
|
+
self._treatment = theargs.treatment
|
|
251
|
+
self._tissue = theargs.tissue
|
|
252
|
+
self._author = theargs.author
|
|
253
|
+
self._slice = theargs.slice
|
|
254
|
+
self._gene_set = theargs.gene_set
|
|
255
|
+
self._imgsuffix = imgsuffix
|
|
256
|
+
self._provenance_utils = provenance_utils
|
|
257
|
+
if imagedownloader is not None:
|
|
258
|
+
self._imagedownloader = imagedownloader
|
|
259
|
+
else:
|
|
260
|
+
self._imagedownloader = MultiProcessImageDownloader()
|
|
261
|
+
self._softwareid = None
|
|
262
|
+
self._image_dataset_ids = None
|
|
263
|
+
|
|
264
|
+
self._input_data_dict = theargs.__dict__
|
|
265
|
+
|
|
266
|
+
def run(self):
|
|
267
|
+
"""
|
|
268
|
+
Runs the process of converting IF Image data into a format consumable by the Cell Maps Pipeline.
|
|
269
|
+
This includes generating a directory path for the RO-Crate, creating the output directory, registering
|
|
270
|
+
the RO-Crate, filtering the input data based on criteria, downloading and organizing the images.
|
|
271
|
+
It also handles the registration of datasets and computations in the FAIRSCAPE ecosystem.
|
|
272
|
+
|
|
273
|
+
:return:
|
|
274
|
+
:rtype: int
|
|
275
|
+
"""
|
|
276
|
+
self._generate_rocrate_dir_path()
|
|
277
|
+
self._create_output_directory()
|
|
278
|
+
|
|
279
|
+
keywords = [self._project_name, self._release,
|
|
280
|
+
self._cell_line, self._treatment, 'IF microscopy', 'images',
|
|
281
|
+
self._tissue]
|
|
282
|
+
|
|
283
|
+
if self._gene_set is not None:
|
|
284
|
+
keywords.append(self._gene_set)
|
|
285
|
+
|
|
286
|
+
description = ' '.join(keywords)
|
|
287
|
+
|
|
288
|
+
info_dict = {
|
|
289
|
+
constants.DATASET_NAME: self._name,
|
|
290
|
+
constants.DATASET_ORGANIZATION_NAME: self._organization_name,
|
|
291
|
+
constants.DATASET_PROJECT_NAME: self._project_name,
|
|
292
|
+
constants.DATASET_RELEASE: self._release,
|
|
293
|
+
constants.DATASET_CELL_LINE: self._cell_line,
|
|
294
|
+
constants.DATASET_TREATMENT: self._treatment,
|
|
295
|
+
constants.DATASET_TISSUE: self._tissue,
|
|
296
|
+
constants.DATASET_AUTHOR: self._author,
|
|
297
|
+
constants.DATASET_SLICE: self._slice,
|
|
298
|
+
constants.DATASET_GENE_SET: self._gene_set
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
self.save_dataset_info_to_json(self._outdir, info_dict, constants.DATASET_INFO_FILE)
|
|
302
|
+
|
|
303
|
+
self._provenance_utils.register_rocrate(self._outdir,
|
|
304
|
+
name=self._name,
|
|
305
|
+
organization_name=self._organization_name,
|
|
306
|
+
project_name=self._project_name,
|
|
307
|
+
description=description,
|
|
308
|
+
keywords=keywords,
|
|
309
|
+
guid=self._get_fairscape_id())
|
|
310
|
+
gen_dsets = []
|
|
311
|
+
|
|
312
|
+
filtered_df = self._filter_apms_data()
|
|
313
|
+
|
|
314
|
+
# copy over readme
|
|
315
|
+
shutil.copy(os.path.join(os.path.dirname(__file__), 'ifimage_readme.txt'),
|
|
316
|
+
os.path.join(self._outdir, 'readme.txt'))
|
|
317
|
+
|
|
318
|
+
# download the images
|
|
319
|
+
if 'Baselink' in filtered_df:
|
|
320
|
+
baselink_name = 'Baselink'
|
|
321
|
+
else:
|
|
322
|
+
baselink_name = 'base_web_link'
|
|
323
|
+
|
|
324
|
+
self._download_data(filtered_df[baselink_name].values.tolist())
|
|
325
|
+
|
|
326
|
+
# remove Baselink column
|
|
327
|
+
filtered_df.drop(baselink_name, axis=1, inplace=True)
|
|
328
|
+
|
|
329
|
+
# remove Slice column if set
|
|
330
|
+
if self._slice is not None:
|
|
331
|
+
if 'Slice' in filtered_df:
|
|
332
|
+
filtered_df.drop('Slice', axis=1, inplace=True)
|
|
333
|
+
|
|
334
|
+
file_path = os.path.join(self._outdir, constants.ANTIBODY_GENE_TABLE_FILE)
|
|
335
|
+
|
|
336
|
+
filtered_df.to_csv(file_path, sep='\t', index=False)
|
|
337
|
+
|
|
338
|
+
file_desc = description + ' file'
|
|
339
|
+
file_keywords = keywords.copy()
|
|
340
|
+
file_keywords.extend(['file'])
|
|
341
|
+
dset_id = self._provenance_utils.register_dataset(rocrate_path=self._outdir, source_file=file_path,
|
|
342
|
+
skip_copy=True,
|
|
343
|
+
data_dict={'name': 'IF Image Gene file',
|
|
344
|
+
'description': file_desc,
|
|
345
|
+
'keywords': file_keywords,
|
|
346
|
+
'data-format': 'tsv',
|
|
347
|
+
'author': self._author,
|
|
348
|
+
'version': self._release,
|
|
349
|
+
'date-published': date.today().strftime('%Y-%m-%d')},
|
|
350
|
+
guid=self._get_fairscape_id())
|
|
351
|
+
gen_dsets.append(dset_id)
|
|
352
|
+
gen_dsets.extend(self._register_downloaded_images(description=description,
|
|
353
|
+
keywords=keywords))
|
|
354
|
+
self._register_software(keywords=keywords, description=description)
|
|
355
|
+
logger.info('Registering only 1st 1000 datasets into computation due to limitations of fairscape-cli')
|
|
356
|
+
self._register_computation(generated_dataset_ids=gen_dsets[:1000],
|
|
357
|
+
description=description,
|
|
358
|
+
keywords=keywords)
|
|
359
|
+
return 0
|
|
360
|
+
|
|
361
|
+
def _get_fairscape_id(self):
|
|
362
|
+
"""
|
|
363
|
+
Creates a unique id
|
|
364
|
+
:return:
|
|
365
|
+
"""
|
|
366
|
+
return str(uuid.uuid4()) + ':' + os.path.basename(self._outdir)
|
|
367
|
+
|
|
368
|
+
def _generate_rocrate_dir_path(self):
|
|
369
|
+
"""
|
|
370
|
+
Generates the directory path for the RO-Crate based on project-specific attributes such as project name,
|
|
371
|
+
gene set, cell line, treatment, and release version.
|
|
372
|
+
|
|
373
|
+
:return:
|
|
374
|
+
"""
|
|
375
|
+
dir_name = self._project_name.lower() + '_'
|
|
376
|
+
if self._gene_set is not None:
|
|
377
|
+
dir_name += self._gene_set.lower() + '_'
|
|
378
|
+
dir_name += self._cell_line.lower() + '_'
|
|
379
|
+
dir_name += self._treatment.lower() + '_ifimage_'
|
|
380
|
+
dir_name += self._release.lower()
|
|
381
|
+
|
|
382
|
+
dir_name = dir_name.replace(' ', '_')
|
|
383
|
+
self._outdir = os.path.join(self._outdir, dir_name)
|
|
384
|
+
|
|
385
|
+
def _create_output_directory(self):
|
|
386
|
+
"""
|
|
387
|
+
Creates output directory if it does not already exist
|
|
388
|
+
|
|
389
|
+
:raises CellmapsDownloaderError: If output directory is None or if directory already exists
|
|
390
|
+
"""
|
|
391
|
+
if os.path.isdir(self._outdir):
|
|
392
|
+
raise CellMapsError(self._outdir + ' already exists')
|
|
393
|
+
|
|
394
|
+
os.makedirs(self._outdir, mode=0o755)
|
|
395
|
+
for cur_color in constants.COLORS:
|
|
396
|
+
cdir = os.path.join(self._outdir, cur_color)
|
|
397
|
+
if not os.path.isdir(cdir):
|
|
398
|
+
logger.debug('Creating directory: ' + cdir)
|
|
399
|
+
os.makedirs(cdir,
|
|
400
|
+
mode=0o755)
|
|
401
|
+
|
|
402
|
+
def _get_color_download_map(self):
|
|
403
|
+
"""
|
|
404
|
+
Creates a dict where key is color name and value is directory
|
|
405
|
+
path for files for that color
|
|
406
|
+
|
|
407
|
+
``{'red': '/tmp/foo/red'}``
|
|
408
|
+
|
|
409
|
+
:return: map of colors to directory paths
|
|
410
|
+
:rtype: dict
|
|
411
|
+
"""
|
|
412
|
+
color_d_map = {}
|
|
413
|
+
for c in constants.COLORS:
|
|
414
|
+
color_d_map[c] = os.path.join(self._outdir, c)
|
|
415
|
+
return color_d_map
|
|
416
|
+
|
|
417
|
+
def _get_download_tuples(self, baselinks=None):
|
|
418
|
+
"""
|
|
419
|
+
Gets download list from **imageurlgen** object set via constructor
|
|
420
|
+
|
|
421
|
+
:return: list of (image download URL prefix,
|
|
422
|
+
file path where image should be written)
|
|
423
|
+
:rtype: list
|
|
424
|
+
"""
|
|
425
|
+
dtuples = []
|
|
426
|
+
color_map = self._get_color_download_map()
|
|
427
|
+
for link in baselinks:
|
|
428
|
+
for c in constants.COLORS:
|
|
429
|
+
link_w_filename = link + c + '.jpg'
|
|
430
|
+
dtuples.append((link_w_filename,
|
|
431
|
+
os.path.join(color_map[c],
|
|
432
|
+
os.path.basename(link_w_filename))))
|
|
433
|
+
logger.debug('Returning ' + str(len(dtuples)) + ' download tuples')
|
|
434
|
+
logger.debug(str(dtuples))
|
|
435
|
+
return dtuples
|
|
436
|
+
|
|
437
|
+
def _download_data(self, baselinks=None,
|
|
438
|
+
max_retry=5):
|
|
439
|
+
"""
|
|
440
|
+
Initiates the download of images based on a list of base URLs. It manages the download process, retries failed
|
|
441
|
+
downloads up to a maximum number of attempts, and logs any errors encountered.
|
|
442
|
+
|
|
443
|
+
:param baselinks: A list of base URLs for image download.
|
|
444
|
+
:type baselinks: list
|
|
445
|
+
:param max_retry: Maximum number of retries for failed downloads.
|
|
446
|
+
:type max_retry: int
|
|
447
|
+
:return: A tuple containing the status (0 for success) and a list of failed downloads.
|
|
448
|
+
:rtype: tuple
|
|
449
|
+
"""
|
|
450
|
+
dtuples = self._get_download_tuples(baselinks=baselinks)
|
|
451
|
+
|
|
452
|
+
failed_downloads = self._imagedownloader.download_images(dtuples)
|
|
453
|
+
retry_count = 0
|
|
454
|
+
while len(failed_downloads) > 0 and retry_count < max_retry:
|
|
455
|
+
retry_count += 1
|
|
456
|
+
logger.error(str(len(failed_downloads)) +
|
|
457
|
+
' images failed to download. Retrying #' + str(retry_count))
|
|
458
|
+
|
|
459
|
+
# try one more time with files that failed
|
|
460
|
+
failed_downloads = self._retry_failed_images(failed_downloads=failed_downloads)
|
|
461
|
+
|
|
462
|
+
if len(failed_downloads) > 0:
|
|
463
|
+
raise CellMapsError('Failed to download: ' +
|
|
464
|
+
str(len(failed_downloads)) + ' images')
|
|
465
|
+
return 0, failed_downloads
|
|
466
|
+
|
|
467
|
+
def _retry_failed_images(self, failed_downloads=None):
|
|
468
|
+
"""
|
|
469
|
+
Attempts to re-download images that failed in the initial download attempt. It organizes the failed downloads
|
|
470
|
+
based on error codes, logs the failure counts, and retries the downloads.
|
|
471
|
+
|
|
472
|
+
:param failed_downloads: A list of failed downloads to retry.
|
|
473
|
+
:type failed_downloads: list
|
|
474
|
+
:return: A list of downloads that failed after retrying.
|
|
475
|
+
:rtype: list
|
|
476
|
+
"""
|
|
477
|
+
downloads_to_retry = []
|
|
478
|
+
error_code_map = {}
|
|
479
|
+
for entry in failed_downloads:
|
|
480
|
+
if entry[0] not in error_code_map:
|
|
481
|
+
error_code_map[entry[0]] = 0
|
|
482
|
+
error_code_map[entry[0]] += 1
|
|
483
|
+
downloads_to_retry.append(entry[2])
|
|
484
|
+
logger.debug('Failed download counts by http error code: ' + str(error_code_map))
|
|
485
|
+
return self._imagedownloader.download_images(downloads_to_retry)
|
|
486
|
+
|
|
487
|
+
def _filter_apms_data(self):
|
|
488
|
+
"""
|
|
489
|
+
Loads input, set via constructor, as :py:class:`pandas.DataFrame`
|
|
490
|
+
and filters that :py:class:`pandas.DataFrame` to only
|
|
491
|
+
keep rows matching the slice and treatment passed in via
|
|
492
|
+
the constructor
|
|
493
|
+
|
|
494
|
+
:return: Filtered rows
|
|
495
|
+
:rtype: :py:class:`pandas.DataFrame`
|
|
496
|
+
"""
|
|
497
|
+
df = pd.read_csv(self._input, sep=',')
|
|
498
|
+
|
|
499
|
+
# keep only slice specified
|
|
500
|
+
if self._slice is not None:
|
|
501
|
+
if 'Slice' in df:
|
|
502
|
+
logger.info('Keeping only slice: ' + str(self._slice))
|
|
503
|
+
df = df[df['Slice'] == self._slice]
|
|
504
|
+
logger.debug(str(len(df)) + ' rows remain after slice filter')
|
|
505
|
+
else:
|
|
506
|
+
# attempt to find slice from base_web_link
|
|
507
|
+
if 'base_web_link' in df:
|
|
508
|
+
logger.info('Keeping only slice: ' + str(self._slice) + ' by parsing base_web_link')
|
|
509
|
+
df = df[df['base_web_link'].str.contains('_' + self._slice + '_', case=False)]
|
|
510
|
+
logger.debug(str(len(df)) + ' rows remain after slice filter')
|
|
511
|
+
|
|
512
|
+
# keep only treatment specified
|
|
513
|
+
logger.info('Keeping only treatment: ' + str(self._treatment))
|
|
514
|
+
df = df[df['Treatment'].str.contains(self._treatment, case=False)]
|
|
515
|
+
logger.debug(str(len(df)) + ' rows remain after treatment filter')
|
|
516
|
+
|
|
517
|
+
# remove negative-ctrl rows
|
|
518
|
+
logger.debug('Removing NEGATIVE rows')
|
|
519
|
+
df = df[df["Antibody ID"].str.contains('NEGATIVE') == False]
|
|
520
|
+
|
|
521
|
+
return df
|
|
522
|
+
|
|
523
|
+
def _register_downloaded_images(self,
|
|
524
|
+
description='',
|
|
525
|
+
keywords=[]):
|
|
526
|
+
"""
|
|
527
|
+
Registers all the downloaded images
|
|
528
|
+
:return:
|
|
529
|
+
"""
|
|
530
|
+
data_dict = {'name': cellmaps_utils.__name__ + ' downloaded image',
|
|
531
|
+
'description': description + ' IF image file',
|
|
532
|
+
'data-format': self._imgsuffix[1:],
|
|
533
|
+
'author': self._author,
|
|
534
|
+
'version': self._release,
|
|
535
|
+
'date-published': date.today().strftime('%Y-%m-%d')}
|
|
536
|
+
|
|
537
|
+
dset_ids = []
|
|
538
|
+
|
|
539
|
+
for c in constants.COLORS:
|
|
540
|
+
for entry in tqdm(os.listdir(os.path.join(self._outdir, c)), desc='FAIRSCAPE ' + c + ' images registration'):
|
|
541
|
+
if not entry.endswith(self._imgsuffix):
|
|
542
|
+
continue
|
|
543
|
+
fullpath = os.path.join(self._outdir, c, entry)
|
|
544
|
+
data_dict['name'] = entry + ' ' + c +\
|
|
545
|
+
' channel image'
|
|
546
|
+
if len(data_dict['name']) >= 64:
|
|
547
|
+
data_dict['name'] = '...' + data_dict['name'][-60:]
|
|
548
|
+
data_dict['keywords'] = keywords.copy()
|
|
549
|
+
data_dict['keywords'].extend([c, 'IF', 'image', constants.COLOR_LABELS_MAP[c]])
|
|
550
|
+
dset_ids.append(self._provenance_utils.register_dataset(self._outdir,
|
|
551
|
+
source_file=fullpath,
|
|
552
|
+
data_dict=data_dict,
|
|
553
|
+
skip_copy=True,
|
|
554
|
+
guid=self._get_fairscape_id()))
|
|
555
|
+
|
|
556
|
+
del data_dict['keywords']
|
|
557
|
+
|
|
558
|
+
return dset_ids
|
|
559
|
+
|
|
560
|
+
def _register_computation(self, generated_dataset_ids=[],
|
|
561
|
+
description='',
|
|
562
|
+
keywords=[]):
|
|
563
|
+
"""
|
|
564
|
+
Registers the computational process executed by this tool
|
|
565
|
+
# Todo: added in used dataset, software and what is being generated
|
|
566
|
+
:return:
|
|
567
|
+
"""
|
|
568
|
+
logger.debug('Getting id of input rocrate')
|
|
569
|
+
comp_keywords = keywords.copy()
|
|
570
|
+
comp_keywords.extend(['computation'])
|
|
571
|
+
description = description + ' run of ' + cellmaps_utils.__name__
|
|
572
|
+
self._provenance_utils.register_computation(self._outdir,
|
|
573
|
+
name='IF images',
|
|
574
|
+
run_by=str(self._provenance_utils.get_login()),
|
|
575
|
+
command=str(self._input_data_dict),
|
|
576
|
+
description=description,
|
|
577
|
+
keywords=comp_keywords,
|
|
578
|
+
used_software=[self._softwareid],
|
|
579
|
+
generated=generated_dataset_ids,
|
|
580
|
+
guid=self._get_fairscape_id())
|
|
581
|
+
|
|
582
|
+
def _register_software(self, description='',
|
|
583
|
+
keywords=[]):
|
|
584
|
+
"""
|
|
585
|
+
Registers this tool
|
|
586
|
+
|
|
587
|
+
:raises CellMapsImageEmbeddingError: If fairscape call fails
|
|
588
|
+
"""
|
|
589
|
+
software_keywords = keywords.copy()
|
|
590
|
+
software_keywords.extend(['tools', cellmaps_utils.__name__])
|
|
591
|
+
software_description = description + ' ' + \
|
|
592
|
+
cellmaps_utils.__description__
|
|
593
|
+
self._softwareid = self._provenance_utils.register_software(self._outdir,
|
|
594
|
+
name=cellmaps_utils.__name__,
|
|
595
|
+
description=software_description,
|
|
596
|
+
author=cellmaps_utils.__author__,
|
|
597
|
+
version=cellmaps_utils.__version__,
|
|
598
|
+
file_format='py',
|
|
599
|
+
keywords=software_keywords,
|
|
600
|
+
url=cellmaps_utils.__repo_url__,
|
|
601
|
+
guid=self._get_fairscape_id())
|
|
602
|
+
|
|
603
|
+
def add_subparser(subparsers):
|
|
604
|
+
"""
|
|
605
|
+
Adds command-line argument parsing for the IFImageDataConverter tool.
|
|
606
|
+
|
|
607
|
+
:return:
|
|
608
|
+
"""
|
|
609
|
+
desc = """
|
|
610
|
+
|
|
611
|
+
Version {version}
|
|
612
|
+
|
|
613
|
+
{cmd} Loads IF Image data into a RO-Crate
|
|
614
|
+
""".format(version=cellmaps_utils.__version__,
|
|
615
|
+
cmd=IFImageDataConverter.COMMAND)
|
|
616
|
+
|
|
617
|
+
parser = subparsers.add_parser(IFImageDataConverter.COMMAND,
|
|
618
|
+
help='Loads IF Image data into a RO-Crate',
|
|
619
|
+
description=desc,
|
|
620
|
+
formatter_class=constants.ArgParseFormatter)
|
|
621
|
+
parser.add_argument('outdir',
|
|
622
|
+
help='Directory where RO-Crate will be created')
|
|
623
|
+
parser.add_argument('--input', required=True,
|
|
624
|
+
help='Table file with the following '
|
|
625
|
+
'fields: [Antibody ID, ENSEMBL ID|ENSG, Treatment, Well, Region, Slice, Baselink|base_web_link] ')
|
|
626
|
+
parser.add_argument('--author', default='Lundberg Lab',
|
|
627
|
+
help='Author that created this data')
|
|
628
|
+
parser.add_argument('--name', default='IF images',
|
|
629
|
+
help='Name of this run, needed for FAIRSCAPE')
|
|
630
|
+
parser.add_argument('--organization_name', default='Lundberg Lab',
|
|
631
|
+
help='Name of organization running this tool, needed '
|
|
632
|
+
'for FAIRSCAPE. Usually set to lab')
|
|
633
|
+
parser.add_argument('--project_name', default='CM4AI',
|
|
634
|
+
help='Name of project running this tool, needed for '
|
|
635
|
+
'FAIRSCAPE. Usually set to funding source')
|
|
636
|
+
parser.add_argument('--release', required=True,
|
|
637
|
+
help='Version of release. For example: 0.1 alpha')
|
|
638
|
+
parser.add_argument('--treatment', default='untreated',
|
|
639
|
+
choices=['paclitaxel', 'vorinostat', 'untreated'],
|
|
640
|
+
help='Treatment of sample.')
|
|
641
|
+
parser.add_argument('--cell_line', default='MDA-MB-468',
|
|
642
|
+
help='Name of cell line. For example MDA-MB-468')
|
|
643
|
+
parser.add_argument('--gene_set', choices=['chromatin', 'metabolic'],
|
|
644
|
+
help='Gene set for dataset, standard names are '
|
|
645
|
+
+ 'chromatin, metabolic, or leave it unset')
|
|
646
|
+
parser.add_argument('--tissue', choices=['undifferentiated', 'neuron',
|
|
647
|
+
'cardiomyocytes', ''],
|
|
648
|
+
default='breast; mammary gland',
|
|
649
|
+
help='Tissue for dataset. Since the default --cell_line '
|
|
650
|
+
'is MDA-MB-468, this value is set to the tissue '
|
|
651
|
+
'for that cell line')
|
|
652
|
+
parser.add_argument('--slice',
|
|
653
|
+
help='Slice to keep. Example names are z01, z02. If unset '
|
|
654
|
+
+ 'all slices are kept')
|
|
655
|
+
|
|
656
|
+
return parser
|
|
657
|
+
|