PKrMl~~fastgenomics/__init__.py""" FASTGenomics python helper """ from get_version import get_version __version__ = get_version(__file__) del get_version PK#oONFR R fastgenomics/app.pyimport jsonschema import json from pathlib import Path from .defaults import DEFAULT_APP_DIR from .parameters import FGParameters class FGApp(object): # files that are checked for existence that are not otherwise # explicitly used by the FGApp _mandatory_files = ["LICENSE", "LICENSE-THIRD-PARTY", "manifest.json"] def __init__(self, app_dir=DEFAULT_APP_DIR): if isinstance(app_dir, str): app_dir = Path(app_dir) self.app_dir = app_dir self.check_files() self.manifest = self.get_manifest() self.application = self.manifest["application"] self.default_parameters = FGParameters(self.application["parameters"]) self.app_type = self.application["type"] self.inputs = self.application["input"] self.outputs = self.application["output"] assert self.app_type in ["Calculation", "Visualization"] def get_manifest(self): manifest_file = self.app_dir / "manifest.json" manifest = json.loads(manifest_file.read_bytes()) check_manifest(manifest) return manifest def check_files(self): if not self.app_dir.exists(): raise FileNotFoundError(f"Could not find the App directory {self.app_dir}") not_found = [] for f in self._mandatory_files: absolute_path = self.app_dir / f if not absolute_path.exists(): not_found.append(f) if not_found: msg = [f'Could not find the following files in "{self.app_dir}":'] msg += [f"- {file}" for file in not_found] raise FileNotFoundError("\n".join(msg)) def check_manifest(config: dict): """ Asserts that the manifest (``manifest.json``) matches our JSON-Schema. If not a :py:exc:`jsonschema.ValidationError` will be raised. """ schema_file = Path(__file__).parent / "schemes" / "manifest_schema.json" schema = json.loads(schema_file.read_text()) jsonschema.validate(config, schema) input_types = config["application"]["input"] output_types = config["application"]["output"] def err_msg(x, y): return "'{}'-type not supported for {}-operations.".format(x, y) if input_types is not None: for name, properties in input_types.items(): if properties["type"] == "output_only": raise RuntimeError(err_msg(properties["type"], "input")) if output_types is not None: for name, properties in output_types.items(): if properties["type"] == "dataset_manifest": raise RuntimeError(err_msg(properties["type"], "output")) PK#oON:ssfastgenomics/data.pyfrom pathlib import Path from collections import OrderedDict import json from .defaults import DEFAULT_DATA_ROOT DATA_SUBDIRS = ["data", "config", "output", "summary"] class FGData(object): """This class stores the paths to data structured according to the fastgenomics specification. It also loads the input file mappings and checks if the files exist. """ _subdirs = DATA_SUBDIRS _mandatory_files = ["config/input_file_mapping.json"] + DATA_SUBDIRS def __init__(self, data_root=DEFAULT_DATA_ROOT): if isinstance(data_root, str): data_root = Path(data_root) self.root = data_root self.check_files() self.paths = self.get_paths() self.input_file_mapping = self.get_input_file_mapping() self.parameters = self.get_parameters() def get_input_file_mapping(self): mapping_file = self.paths["config"] / "input_file_mapping.json" return json.loads(mapping_file.read_bytes()) def get_paths(self): return {dir: self.root / dir for dir in self._subdirs} def get_parameters(self): params_file = self.paths["config"] / "parameters.json" if params_file.exists(): return json.loads(params_file.read_text()) else: return {} def check_files(self): if not self.root.exists(): raise FileNotFoundError( f"Could not find the data directory under {self.root}" ) not_found = [] for f in self._mandatory_files: absolute_path = self.root / f if not absolute_path.exists(): not_found.append(f) if not_found: msg = [ f'Could not find the following files or directories in "{self.root}":' ] msg += [f"- {file}" for file in not_found] raise FileNotFoundError("\n".join(msg)) PK#oONXAfastgenomics/defaults.pyimport os from pathlib import Path # set default paths DEFAULT_APP_DIR = Path(os.environ.get("FG_APP_DIR", "/app")) DEFAULT_DATA_ROOT = Path(os.environ.get("FG_DATA_ROOT", "/fastgenomics")) # summary key for the FGProcess.output SUMMARY_KEY = "summary" PK#oONFZfastgenomics/deprecated.pyfrom deprecated import deprecated from .process import FGProcess _PROCESS = None def get_process(): if _PROCESS is None: raise NameError( f"call set_paths(app_dir, data_root) before accessing the global process." ) else: return _PROCESS @deprecated(reason="Use FGProcess instead") def set_paths(app_dir, data_root): global _PROCESS _PROCESS = FGProcess(app_dir, data_root) _PROCESS.data.paths["app"] = app_dir @deprecated(reason="Use FGProcess.parameters instead") def get_parameter(key): return get_process().parameters[key] @deprecated(reason="Use FGProcess.input instead") def get_input_path(filename): return get_process().input[filename].path @deprecated(reason="Use FGProcess.output instead") def get_output_path(filename): return get_process().output[filename].path @deprecated(reason="Use FGProcess.summary or FGProcess.output['summary'] instead") def get_summary_path(): return get_process().output["summary"].path @deprecated(reason="Use FGProcess.data.paths instead") def get_paths(): return {**get_process().data.paths, "app": get_process().app.app_dir} @deprecated(reason="Use FGProcess.app.manifest instead") def get_app_manifest(): return get_process().app.manifest @deprecated(reason="Use FGProcess.parameters instead") def get_parameters(): return { name: param.value for name, param in get_process().parameters.parameter.items() } PK#oONh6> fastgenomics/io.py# coding: utf-8 from collections import OrderedDict from pathlib import Path import textwrap from logging import getLogger from .defaults import SUMMARY_KEY logger = getLogger("fastgenomics.common") class Files(object): def __getitem__(self, key): if key not in self.files: raise KeyError(f'Key "{key}" not defined in manifest.json of the App') else: return self.files[key] def __contains__(self, key): return key in self.files def __repr__(self): output = "Files:\n" for file in self.files.values(): output += textwrap.indent(file.__repr__(), " ") + "\n" return output def keys(self): return self.files.keys() class FilesInput(Files): def __init__(self, specs, root, mapping): self.files = OrderedDict() for name, spec in specs.items(): path = None isoptional = "optional" in spec and spec["optional"] if name in mapping: path = Path(root) / mapping[name] if not path.exists(): raise FileNotFoundError(f"File {name}: not found under {path}") elif isoptional: path = None else: raise KeyError( f'Non-optional file "{name}" missing from input_file_mapping.json' ) self.files[name] = FileInput( name=name, type=spec["type"], usage=spec["usage"], path=path, optional=isoptional, ) not_in_manifest = set(mapping) - set(specs) if not_in_manifest: logger.warning( f"Ignoring files defined in input_file_mapping.json: {not_in_manifest}" ) logger.debug(f"Files found in the manifest: {list(mapping)}") logger.debug(f"Files found in the input_file_mapping.json: {list(specs)}") class FilesOutput(Files): def __init__(self, specs, root, summary_path): self.files = OrderedDict() for name, spec in specs.items(): self.files[name] = FileOutput( name=name, type=spec["type"], usage=spec["usage"], path=Path(root) / spec["file_name"], ) self.files[SUMMARY_KEY] = FileOutput( name="summary", type="summary", usage="Summary file", path=summary_path ) class File(object): def __init__(self, name, type, usage, path): self.name = name self.type = type self.usage = usage self.path = path def __repr__(self): output = [f'"{arg.capitalize()}": {val}' for arg, val in vars(self).items()] return "\n".join(output) class FileInput(File): def __init__(self, name, type, usage, path, optional=False): super().__init__(name, type, usage, path) self.optional = optional def __repr__(self): return "\n".join( [super().__repr__(), "IO type: Input", f"Optional: {self.optional}"] ) class FileOutput(File): def __repr__(self): return "\n".join([super().__repr__(), "IO type: Output"]) PK#oON^&fastgenomics/parameters.pyimport copy class Parameter: type_mapping = { "float": (int, float), "integer": int, "bool": bool, "list": list, "dict": dict, "string": str, "enum": object, } def __init__(self, param: dict, name): self.name = name self.description = param["description"] self.type = self.type_mapping[param["type"]] self.enum = param.get("enum") self.optional = param.get("optional", False) self.value = param["default"] @property def value(self): return self._value @value.setter def value(self, val): if self.optional and val is None: self._value = val return if not isinstance(val, self.type): raise ValueError( f"Setting parameter {self.name} of type {self.type} with value {val}." ) elif self.enum and val not in self.enum: raise ValueError( f"Setting parameter {self.name} with a value {val} that is not in {self.enum}." ) self._value = val class FGParameters(object): def __init__(self, parameters: dict): self.parameter = {} for name, spec in parameters.items(): self.parameter[name] = Parameter(spec, name) def __getitem__(self, key): return self.parameter[key].value def keys(self): return self.parameter.keys() def update(self, values): for name, value in values.items(): if name not in self.parameter: raise KeyError(f"Parameter {name} not found in manifest.json.") self.parameter[name].value = value def copy(self): return copy.deepcopy(self) def check(self, name, checker, error_message): if not checker(self[name]): raise ValueError(f'Parameter "{name}" failed validation: {error_message}') PK#oON11fastgenomics/process.pyfrom .app import FGApp from .data import FGData from .io import FilesInput, FilesOutput from .summary import FGSummary from .defaults import DEFAULT_APP_DIR, DEFAULT_DATA_ROOT, SUMMARY_KEY from .utils import str_normalize from logging import getLogger class FGProcess(object): def __init__(self, app_dir=DEFAULT_APP_DIR, data_dir=DEFAULT_DATA_ROOT): self.data = FGData(data_dir) self.app = FGApp(app_dir) self.parameters = self.app.default_parameters.copy() self.parameters.update(self.data.parameters) app_name = str_normalize(self.app.application["name"]) self.logger = getLogger(f"fastgenomics.{app_name}") # log the updated parameter values info = "\n".join(f"{k}:{v.value}" for k, v in self.parameters.parameter.items()) self.logger.info(f"Parameters: \n{info}") self.input = FilesInput( specs=self.app.inputs, root=self.data.paths["data"], mapping=self.data.input_file_mapping, ) self.output = FilesOutput( specs=self.app.outputs, root=self.data.paths["output"], summary_path=self.data.paths[SUMMARY_KEY] / "summary.md", ) self.summary = FGSummary( output=self.output[SUMMARY_KEY], params=self.parameters ) PK#oON£fastgenomics/summary.pyimport jinja2 from pathlib import Path class FGSummary(object): def __init__(self, output, params): if output.type != "summary": raise TypeError(f'Expected a summary file but got "{output.type}"') self.output = output.path self.params = params self.footnote = self.summary_footnote() self.template = None def summary_footnote(self): footnote = "\n### Parameters\n" for name, param in self.params.parameter.items(): if param.type == str: value = f'"{param.value}"' else: value = param.value footnote += f"* __{name}__ = `{value}` _({param.description})_\n" return footnote def render_template(self, temp_file, **kwargs): summary = temp_file.read_text() + self.footnote temp = jinja2.Template(summary, undefined=jinja2.StrictUndefined) return temp.render(kwargs) def write(self, **kwargs): if self.template is None: raise AttributeError( "Please specify the template before calling `write` by setting the `template` parameter." ) template = Path(self.template) if not template.exists(): raise FileNotFoundError( f'Could not find the summary template under "{template}". You can change the template location by modifying the `template` argument.' ) summary = self.render_template(template, **kwargs) self.output.write_text(summary) PK#oONfastgenomics/testing.pydef cleanoutput(fg): """Deletes all the output files. Useful for cleanup before tests.""" for file in fg.output.keys(): path = fg.output[file].path if path.exists(): path.unlink() PK#oON#{{fastgenomics/utils.pyimport re def str_normalize(input): reg = r"[A-Za-z0-9_]" return "".join([i for i in input if re.match(reg, i)]) PK#oON!fastgenomics/external/__init__.pyPK#oONe\NN fastgenomics/external/anndata.pytry: import pandas as pd import numpy as np import scipy.sparse as sp from anndata import AnnData except ImportError as e: msg = f"Could not import some of the necessary modules ({e.name}). Please make sure to install anndata (https://github.com/theislab/anndata) with all its dependencies correctly (e.g. pandas, numpy, scipy)." raise ImportError(msg, name=e.name, path=e.path) raise e def get_path(file, content_type): """Returns a path if the file is of requested FASTGenomics type, otherwise throws a TypeError.""" if file is None: return None if file.type != content_type: raise TypeError( f'File "{file.name}" is of type "{file.type}" but expected "{content_type}".' ) return file.path def read_data( fgprocess, expr="expression_matrix", cell_meta="cell_metadata", gene_meta="gene_metadata", ): """Reads an anndata object by composing three files together: `expression_matrix`, `cell_metadata` and `gene_metadata`.""" expr_path = get_path(fgprocess.input[expr], content_type="expression_matrix") expr = pd.read_csv(expr_path) obs = read_cell_metadata(fgprocess, cell_meta, expr) var = read_gene_metadata(fgprocess, gene_meta, expr) counts = read_sparse_matrix(expr, obs, var) adata = AnnData(counts, obs=obs, var=var, dtype="float64") return adata def read_cell_metadata(fgprocess, cell_meta, expr): if cell_meta is None: cell_path = None else: cell_path = get_path(fgprocess.input[cell_meta], content_type="cell_metadata") expr_cell_id = expr.cell_id.unique() if cell_path is None: df = pd.DataFrame(expr_cell_id, columns=["cell_id"]).set_index("cell_id") else: df = pd.read_csv(cell_path, index_col="cell_id") if set(expr_cell_id) - set(df.index): raise Exception( f'Some cell_id\'s were present in the expression matrix but not in "{cell_meta}".' ) return df def read_gene_metadata(fgprocess, gene_meta, expr): gene_path = get_path(fgprocess.input[gene_meta], content_type="gene_metadata") expr_entrez_id = expr.entrez_id.unique() if gene_path is None: df = pd.DataFrame(expr_entrez_id, columns=["entrez_id"]).set_index("entrez_id") else: df = pd.read_csv(gene_path, index_col="entrez_id") if set(expr_entrez_id) - set(df.index): raise Exception( f'Some entrez_id\'s were present in the expression matrix but not in "{gene_meta}".' ) return df def read_sparse_matrix(expr, obs, var): cell_idx = pd.DataFrame( dict(cell_id=obs.index, cell_idx=np.arange(obs.shape[0])) ).set_index("cell_id") entrez_idx = pd.DataFrame( dict(entrez_id=var.index, entrez_idx=np.arange(var.shape[0])) ).set_index("entrez_id") expr = expr.merge(cell_idx, on="cell_id", copy=False) expr = expr.merge(entrez_idx, on="entrez_id", copy=False) counts = sp.coo_matrix( (expr.expression, (expr.cell_idx, expr.entrez_idx)), shape=(obs.shape[0], var.shape[0]), ).tocsr() return counts # Writing def write_exprs_csv(adata, csv_file): mat = adata.X.tocoo() df = pd.DataFrame.from_dict( dict(cell_id=mat.row, entrez_id=mat.col, expression=mat.data) ) df.to_csv(csv_file) def write_data(fgprocess, adata, expr=None, cell_meta=None, gene_meta=None): if expr is not None: exprs_path = get_path(fgprocess.output[expr], content_type="expression_matrix") write_exprs_csv(adata, exprs_path) if cell_meta is not None: cell_path = get_path(fgprocess.output[cell_meta], content_type="cell_metadata") adata.obs.to_csv(cell_path) if gene_meta is not None: gene_path = get_path(fgprocess.output[gene_meta], content_type="gene_metadata") adata.var.to_csv(gene_path) PKZM988)fastgenomics/schemes/manifest_schema.json{ "$schema": "http://json-schema.org/draft-04/schema", "required": [ "application", "schema" ], "definitions": { "input_entry": { "type": "object", "properties": { "type": { "description": "FASTGenomics Type of the file", "enum": [ "expression_matrix", "gene_metadata", "cell_metadata", "coordinates", "assignments", "data_quality", "batch_effects", "dense_matrix", "heatmap_info", "dataset_manifest", "output_only" ], "type": "string" }, "usage": { "description": "short description of the file usage", "examples": ["gene expression matrix", "classification of cells"], "type": "string" }, "optional": { "description": "marks an input file as optional, e.g., it doesn't have to be defined in the input_file_mapping nor be existing", "type": "boolean" } }, "required": ["type", "usage"] }, "output_entry": { "allOf": [ { "$ref": "#/definitions/input_entry"}, { "properties": { "file_name": { "description": "plain filename of the output-file without directory", "type": "string", "pattern": "^[a-zA-Z0-9_.]+$" } }, "required": ["file_name"] } ] }, "parameter_entry": { "type": "object", "properties": { "type": { "description": "Type of the value of the parameter", "enum": ["string", "integer", "float", "bool", "list", "dict", "enum"] }, "optional": { "description": "Accept null as parameter value in addition to values of the given type?", "type": "boolean" }, "description": { "description": "Description of the parameter", "type": "string" }, "enum": { "description": "Valid values of an enum type", "type": "array" }, "default": { "description": "Default value of the parameter" } }, "required": ["type", "description", "default"], "if": { "properties": { "type": { "enum": ["enum"] } } }, "then": { "required": ["enum"] } } }, "type": "object", "properties": { "schema": { "type": "string", "enum": ["1.0.0"], "description": "The version of the schema itself." }, "application": { "type": "object", "properties": { "author": { "type": "object", "properties": { "email": { "description": "E-mail address of app developer", "examples": ["john.doe@fastgenomics.org"], "type": "string" }, "name": { "description": "Name of the app developer", "examples": ["Jon Doe"], "type": "string" }, "organisation": { "description": "Organization of the developer", "examples": ["FASTGenomics"], "type": "string" } } }, "name": { "description": "The name of the application", "examples": ["Hello Genomics Sample App"], "type": "string" }, "type": { "description": "Type of the application", "enum": ["Calculation", "Visualization"] }, "description": { "description": "Description of the application - can be markdown", "type": "string" }, "demands": { "type": "array", "items": { "description": "Demands on the runtime environment", "enum": ["GPU"] } }, "input": { "type": "object", "patternProperties": { "^[a-zA-Z0-9_.]+$": { "$ref": "#/definitions/input_entry" } } }, "output": { "type": "object", "patternProperties": { "^[a-zA-Z0-9_.]+$": { "$ref": "#/definitions/output_entry" } } }, "parameters": { "type": "object", "patternProperties": { "^[a-zA-Z0-9_.]+$": { "$ref": "#/definitions/parameter_entry" } } } }, "required": ["author", "name", "type", "description", "demands", "input", "output", "parameters"] } } } PKZMB,fastgenomics/templates/docker-compose.yml.j2version: '3' # this file can be used to showcase the environment an app would see. # This file is for local development purposes only. FASTGenomics does not need to see this file. services: {{ app_name }}: build: context: . image: {% if docker_registry %}{{ docker_registry.rstrip('/') + '/' }}{% endif %}{{ app_name }}:dev volumes: - ./{{ sample_dir }}/config:/fastgenomics/config/:ro - ./{{ sample_dir }}/data:/fastgenomics/data/:ro {%- if app_type == 'Calculation' %} - ./{{ sample_dir }}/output:/fastgenomics/output/ - ./{{ sample_dir }}/summary:/fastgenomics/summary/ {% elif app_type == 'Visualization' %} ports: - "8000:8000" {% endif %} PKZMsb1fastgenomics/templates/input_file_mapping.json.j2{ {% for file_key, path_val in file_mapping.items() %} "{{ file_key }}": "{{ path_val }}"{{ "," if not loop.last }} {% endfor -%} } PKZM(~--$fastgenomics-2.0.0.dist-info/LICENSEMIT License Copyright (c) 2017 FASTGenomics Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!H>*RQ"fastgenomics-2.0.0.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,rzd&Y)r$[)T&UrPK!HU W%fastgenomics-2.0.0.dist-info/METADATAX[s~ǯ@3(LQR7M؞#B$$& e+@RMg^8wt"NL>Ic.g4z.D!g|%[R*w("^m]Kɼ]rR5DeUv6ebڗ>xT[A%`/Y fOݍEs3P't"q YZi'WuU􆽕61rpmxe&7 |rSǒ} kJIVnd W[>? 2:62r.==dA*S~VJ$F2u$ـ\ 1\!%t 5Z' A$׻^fPVU0.DTY? ۵EA }B>K9nt.QoJOޗ+m XIZA%2`n ~VUFo[ zWou3D8:F_[Y?4zs@Uދp۵tMh@r> Օ:7Uʧpt|>T'ƥ SG?Ӹ8t,}%̿S(KFОDoQ^2q?~@ꤦywh7!#E `&JxTR3vj[͔ӻ;vG (.rvPu)M7Cf{lJ;Blϸ[s>҈20hZql~z Kۦ? ^(-udH.y&4<`+ ܠȥYDZW|kEU*ǝ>a36UV J,!àWMq)Ncu#0eVuG`+~$3mI0(3^gުu26bXM bD [J}`uU-Re8ZtNBFla{԰yrL$x|QboGEw0sQ(ֺPS۟5yA7{r;vi*!HW~`!5[O,ՒOuۈ66揊 hZn5E! S-l0IԠDD*! & L1 A4,Z$ IR QZ)S1䙼K`42J,yȜQG46ʯge9g9\-jRdž%!#]Ҕش*mf կިރÂǿzUf>{h狢Y+a]^G31d6]>pf$~+3qW桟x[cɢ`[C-Xw=[/H^F9$0Тmp=\))_]X~\?65ay,ٲ(4kسķ#ǹxkyNvcc saP%FGæPw(O3Ŀ'] xQr`I8[9{Nއ%t6͞H@P754"=L *o7Y۠ZGNȱO5Tz]-)_]M೥U4^ٲ֓4yNӎs;9ݹݟ|f6T5cfI}.=n}jY0iAPu@xb})8,B}395f͌K}y}'BJDƓ(<8c¯wah4)l7SY)wo$]ZN C{ԜX0cB @ fovxH>"CYvZpPt*Ml# 7-v֦똔/m= @}`C9wӸI֯ڞpuX-H;FDwӏr O/n:ݟ}(///]0  `v ijT@oiHng|.mhZҏ$PK!Hr#fastgenomics-2.0.0.dist-info/RECORD˒J< ts@@@TA QBq/@>ag;_Vf_1V53*y`] (Mlʆ'>}}Bi'_R+.*<[<˒{x $Rć*1cVz<e GܧdUpGdrnaՋ)7 `(gRj2F|GZ;tgJ.Q,5- AQĒFL ʢ 4\ss@=GpO2_(Gp2+^Cu=<|Ow6*d)P5<5+,66n=4cKv5m՛62{atL2ܨCJܛS?ISOŋY&[#]4uF4>4%4) l1eȟ^>ϚMOe9+q(T6L=}C؄k@|pA!(Ĝ/=LG3O ϫ@_qcVrlr^[8:pv+}*`AUpMpƳ؄$j.C99GXLf'Hn"}"Pes?Gw[uRҬ=kȫ| \"f8ubMi.153= a[B}W+/}3ZCޚn޴uh4筅hUb<|G>e]gU\tI9o[ @ݐ4y̬p*qBV9M# N;ifF`7JUt\yB"0;WQۦbya 5ѲQ .+G5WcXc,LlE2m~PKrMl~~fastgenomics/__init__.pyPK#oONFR R fastgenomics/app.pyPK#oON:ss7 fastgenomics/data.pyPK#oONXAfastgenomics/defaults.pyPK#oONFZfastgenomics/deprecated.pyPK#oONh6> fastgenomics/io.pyPK#oON^&&fastgenomics/parameters.pyPK#oON11~.fastgenomics/process.pyPK#oON£3fastgenomics/summary.pyPK#oON:fastgenomics/testing.pyPK#oON#{{,;fastgenomics/utils.pyPK#oON!;fastgenomics/external/__init__.pyPK#oONe\NN <fastgenomics/external/anndata.pyPKZM988)Kfastgenomics/schemes/manifest_schema.jsonPKZMB,$^fastgenomics/templates/docker-compose.yml.j2PKZMsb1)afastgenomics/templates/input_file_mapping.json.j2PKZM(~--$bfastgenomics-2.0.0.dist-info/LICENSEPK!H>*RQ"offastgenomics-2.0.0.dist-info/WHEELPK!HU W%gfastgenomics-2.0.0.dist-info/METADATAPK!Hr#ofastgenomics-2.0.0.dist-info/RECORDPKs