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.
@@ -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
+