diff --git a/.travis.yml b/.travis.yml index b000eb2..2d0e18a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ sudo: true language: python python: - "2.7" - - "3.6" + - "3.5" git: # don't need the default depth of 50 # but don't want to use a depth of 1 since that affects @@ -57,7 +57,7 @@ addons: install: - > conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION - numpy nose pylint cython + numpy nose pylint cython tensorflow tensorboard - source activate test-environment # install pysam from conda because I'm having trouble installing Cython # for Python 3 on Travis diff --git a/setup.py b/setup.py index dfd7fec..e3ddee7 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,3 @@ -# Copyright (c) 2016-2018. Mount Sinai School of Medicine -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -45,12 +43,12 @@ name='vaxrank', version=version, description="Mutant peptide ranking for personalized cancer vaccines", - author="Alex Rubinsteyn", - author_email="alex.rubinsteyn@gmail.com", - url="https://github.com/hammerlab/vaxrank", + author="Alex Rubinsteyn, Julia Kodysh", + author_email="alex@openvax.org, julia@openvax.org", + url="https://github.com/openvax/vaxrank", license="http://www.apache.org/licenses/LICENSE-2.0.html", classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 4 - Beta', 'Environment :: Console', 'Operating System :: OS Independent', 'Intended Audience :: Science/Research', diff --git a/test/test_cancer_driver_gene.py b/test/test_cancer_driver_gene.py index 3e4552e..b9a3fe9 100644 --- a/test/test_cancer_driver_gene.py +++ b/test/test_cancer_driver_gene.py @@ -1,3 +1,16 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + from vaxrank.gene_pathway_check import ( GenePathwayCheck, _IFNG_RESPONSE_COLUMN_NAME, diff --git a/test/test_epitope_prediction.py b/test/test_epitope_prediction.py index 789a338..72c03a5 100644 --- a/test/test_epitope_prediction.py +++ b/test/test_epitope_prediction.py @@ -1,5 +1,3 @@ -# Copyright (c) 2016-2018. Mount Sinai School of Medicine -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -19,7 +17,7 @@ from mhctools import RandomBindingPredictor from pyensembl import genome_for_reference_name from varcode import Variant -from vaxrank.epitope_prediction import predict_epitopes +from vaxrank.epitope_prediction import predict_epitopes, EpitopePrediction from vaxrank.mutant_protein_fragment import MutantProteinFragment from vaxrank.vaccine_peptide import VaccinePeptide @@ -89,3 +87,20 @@ def predict_subsequences(self, x): genome=mouse_genome) eq_(0, len(epitope_predictions)) + +def test_EpitopePrediction_json_serialization(): + e = EpitopePrediction( + allele="HLA-A*02:01", + peptide_sequence="SIINFEQL", + ic50=2.0, + wt_peptide_sequence="SIINFEKL", + wt_ic50=2000.0, + percentile_rank=0.3, + prediction_method_name="ImaginationMHCpan", + overlaps_mutation=True, + source_sequence="SSIINFEQL", + offset=1, + occurs_in_reference=False) + json = e.to_json() + e2 = EpitopePrediction.from_json(json) + eq_(e, e2) diff --git a/test/test_manufacturability.py b/test/test_manufacturability.py index 740ef4d..fc39535 100644 --- a/test/test_manufacturability.py +++ b/test/test_manufacturability.py @@ -1,3 +1,16 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + from vaxrank.manufacturability import ManufacturabilityScores diff --git a/test/test_mutant_protein_sequence.py b/test/test_mutant_protein_sequence.py index 598acce..55cc822 100644 --- a/test/test_mutant_protein_sequence.py +++ b/test/test_mutant_protein_sequence.py @@ -1,5 +1,3 @@ -# Copyright (c) 2016-2018. Mount Sinai School of Medicine -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/test/test_shell_script.py b/test/test_shell_script.py index acfde3f..02465e7 100644 --- a/test/test_shell_script.py +++ b/test/test_shell_script.py @@ -1,3 +1,16 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + from os.path import getsize from mock import patch from nose.plugins.attrib import attr diff --git a/test/testing_helpers.py b/test/testing_helpers.py index 22248a1..3b241ac 100644 --- a/test/testing_helpers.py +++ b/test/testing_helpers.py @@ -1,5 +1,3 @@ -# Copyright (c) 2016-2018. Mount Sinai School of Medicine -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/vaxrank/__init__.py b/vaxrank/__init__.py index 7863915..6849410 100644 --- a/vaxrank/__init__.py +++ b/vaxrank/__init__.py @@ -1 +1 @@ -__version__ = "1.0.2" +__version__ = "1.1.0" diff --git a/vaxrank/cli.py b/vaxrank/cli.py index d0426e9..51cc09d 100644 --- a/vaxrank/cli.py +++ b/vaxrank/cli.py @@ -1,5 +1,3 @@ -# Copyright (c) 2016-2018. Mount Sinai School of Medicine -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -42,8 +40,8 @@ make_csv_report, make_minimal_neoepitope_report, TemplateDataCreator, - PatientInfo, ) +from .patient_info import PatientInfo logger = logging.getLogger(__name__) diff --git a/vaxrank/core_logic.py b/vaxrank/core_logic.py index 6db6a26..9cfc904 100644 --- a/vaxrank/core_logic.py +++ b/vaxrank/core_logic.py @@ -1,5 +1,3 @@ -# Copyright (c) 2016-2018. Mount Sinai School of Medicine -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/vaxrank/epitope_prediction.py b/vaxrank/epitope_prediction.py index eebd6b2..c133e1e 100644 --- a/vaxrank/epitope_prediction.py +++ b/vaxrank/epitope_prediction.py @@ -1,5 +1,3 @@ -# Copyright (c) 2016-2018. Mount Sinai School of Medicine -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -13,33 +11,57 @@ # limitations under the License. from __future__ import absolute_import, print_function, division -from collections import namedtuple, OrderedDict +from collections import OrderedDict import traceback import logging import numpy as np +from serializable import Serializable from .reference_proteome import ReferenceProteome -logger = logging.getLogger(__name__) -EpitopePredictionBase = namedtuple("EpitopePrediction", [ - "allele", - "peptide_sequence", - "wt_peptide_sequence", - "length", - "ic50", - "wt_ic50", - "percentile_rank", - "prediction_method_name", - "overlaps_mutation", - "source_sequence", - "offset", - "occurs_in_reference", -]) +logger = logging.getLogger(__name__) -class EpitopePrediction(EpitopePredictionBase): +class EpitopePrediction(Serializable): + def __init__( + self, + allele, + peptide_sequence, + wt_peptide_sequence, + ic50, + wt_ic50, + percentile_rank, + prediction_method_name, + overlaps_mutation, + source_sequence, + offset, + occurs_in_reference): + self.allele = allele + self.peptide_sequence = peptide_sequence + self.wt_peptide_sequence = wt_peptide_sequence + self.length = len(peptide_sequence) + self.ic50 = ic50 + self.wt_ic50 = wt_ic50 + self.percentile_rank = percentile_rank + self.prediction_method_name = prediction_method_name + self.overlaps_mutation = overlaps_mutation + self.source_sequence = source_sequence + self.offset = offset + self.overlaps_mutation = overlaps_mutation + self.occurs_in_reference = occurs_in_reference + + @classmethod + def from_dict(cls, d): + """ + Deserialize EpitopePrediction from a dictionary of keywords. + """ + d = d.copy() + if "length" in d: + # length argument removed in version 1.1.0 + del d["length"] + return cls(**d) def logistic_epitope_score( self, @@ -70,6 +92,56 @@ def logistic_epitope_score( return logistic / normalizer + def slice_source_sequence(self, start_offset, end_offset): + """ + + Parameters + ---------- + start_offset : int + + end_offset : int + + Return EpitopePrediction object with source sequence and offset + adjusted. If this slicing would shorten the mutant peptide, then + return None. + """ + if self.offset < start_offset: + # this peptide starts before the requested slice through the + # source sequence + return None + + if self.offset + self.length > end_offset: + # this peptide goes beyond the end of the requested slice + # through the source sequence + return None + + return EpitopePrediction( + allele=self.allele, + peptide_sequence=self.peptide_sequence, + wt_peptide_sequence=self.wt_peptide_sequence, + ic50=self.ic50, + wt_ic50=self.wt_ic50, + percentile_rank=self.percentile_rank, + prediction_method_name=self.prediction_method_name, + overlaps_mutation=self.overlaps_mutation, + source_sequence=self.source_sequence[start_offset:end_offset], + offset=self.offset - start_offset, + occurs_in_reference=self.occurs_in_reference) + + +def slice_epitope_predictions( + epitope_predictions, + start_offset, + end_offset): + """ + Return subset of EpitopePrediction objects which overlap the given interval + and slice through their source sequences and adjust their offset. + """ + return [ + p.slice_source_sequence(start_offset, end_offset) + for p in epitope_predictions + if p.offset >= start_offset and p.offset + p.length <= end_offset + ] def predict_epitopes( mhc_predictor, @@ -150,10 +222,11 @@ def predict_epitopes( 'MHC prediction for WT peptides errored, with traceback: %s', traceback.format_exc()) - wt_predictions_grouped = {} # break it out: (peptide, allele) -> prediction - for wt_prediction in wt_predictions: - wt_predictions_grouped[(wt_prediction.peptide, wt_prediction.allele)] = wt_prediction + wt_predictions_grouped = { + (wt_prediction.peptide, wt_prediction.allele): wt_prediction + for wt_prediction in wt_predictions + } # convert from mhctools.BindingPrediction objects to EpitopePrediction # which differs primarily by also having a boolean field @@ -181,7 +254,8 @@ def predict_epitopes( # compute WT epitope sequence, if this epitope overlaps the mutation if overlaps_mutation: wt_peptide = wt_peptides[peptide] - wt_prediction = wt_predictions_grouped.get((wt_peptide, binding_prediction.allele)) + wt_prediction = wt_predictions_grouped.get( + (wt_peptide, binding_prediction.allele)) wt_ic50 = None if wt_prediction is None: # this can happen in a stop-loss variant: do we want to check that here? @@ -200,7 +274,6 @@ def predict_epitopes( allele=binding_prediction.allele, peptide_sequence=peptide, wt_peptide_sequence=wt_peptide, - length=len(peptide), ic50=binding_prediction.value, wt_ic50=wt_ic50, percentile_rank=binding_prediction.percentile_rank, @@ -209,6 +282,7 @@ def predict_epitopes( source_sequence=protein_fragment.amino_acids, offset=peptide_start_offset, occurs_in_reference=occurs_in_reference) + if epitope_prediction.logistic_epitope_score() >= min_epitope_score: key = (epitope_prediction.peptide_sequence, epitope_prediction.allele) results[key] = epitope_prediction @@ -221,30 +295,3 @@ def predict_epitopes( num_occurs_in_reference, num_low_scoring) return results - - -def slice_epitope_predictions( - epitope_predictions, - start_offset, - end_offset): - """ - Return subset of EpitopePrediction objects which overlap the given interval - and slice through their source sequences and adjust their offset. - """ - return [ - EpitopePrediction( - allele=p.allele, - peptide_sequence=p.peptide_sequence, - wt_peptide_sequence=p.wt_peptide_sequence, - length=p.length, - ic50=p.ic50, - wt_ic50=p.wt_ic50, - percentile_rank=p.percentile_rank, - prediction_method_name=p.prediction_method_name, - overlaps_mutation=p.overlaps_mutation, - source_sequence=p.source_sequence[start_offset:end_offset], - offset=p.offset - start_offset, - occurs_in_reference=p.occurs_in_reference) - for p in epitope_predictions - if p.offset >= start_offset and p.offset + p.length <= end_offset - ] diff --git a/vaxrank/gene_pathway_check.py b/vaxrank/gene_pathway_check.py index 698ef39..91131f7 100644 --- a/vaxrank/gene_pathway_check.py +++ b/vaxrank/gene_pathway_check.py @@ -1,5 +1,3 @@ -# Copyright (c) 2018. Mount Sinai School of Medicine -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/vaxrank/manufacturability.py b/vaxrank/manufacturability.py index 5034c03..36d9d70 100644 --- a/vaxrank/manufacturability.py +++ b/vaxrank/manufacturability.py @@ -1,5 +1,3 @@ -# Copyright (c) 2016. Mount Sinai School of Medicine -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/vaxrank/mutant_protein_fragment.py b/vaxrank/mutant_protein_fragment.py index e60c9b0..0b90c4b 100644 --- a/vaxrank/mutant_protein_fragment.py +++ b/vaxrank/mutant_protein_fragment.py @@ -1,5 +1,3 @@ -# Copyright (c) 2016. Mount Sinai School of Medicine -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -14,57 +12,77 @@ from __future__ import absolute_import, print_function, division -from collections import namedtuple import logging from varcode.effects import top_priority_effect - +from serializable import Serializable logger = logging.getLogger(__name__) -# using a namedtuple base class for the immutable fields of a MutantProteinFragment -# since it makes it clearer what the essential information is and provides -# useful comparison/hashing methods - -MutantProteinFragmentBase = namedtuple("MutantProteinFragment", ( - # varcode.Variant - "variant", - # gene and transcript(s) which were used to translate one or more - # variant cDNA sequences into the following amino acids - "gene_name", - - ### - # Translated protein sequence, aggregated from possibly multiple - # synonymous coding sequences - ### - - "amino_acids", - # offsets of amino acids which differ due to the mutation - "mutant_amino_acid_start_offset", - "mutant_amino_acid_end_offset", - - # PyEnsembl Transcript objects for reference transcripts which - # were used to establish the reading frame of coding sequence(s) - # detected from RNA - "supporting_reference_transcripts", - - ### - # RNA evidence - ### - - # number of reads overlapping the variant locus - "n_overlapping_reads", - # number of reads supporting the variant - "n_alt_reads", - # number of reads supporting the reference allele - "n_ref_reads", - - # number of RNA reads fully spanning the cDNA sequence(s) from which we - # translated this amino acid sequence. - "n_alt_reads_supporting_protein_sequence", -)) - -class MutantProteinFragment(MutantProteinFragmentBase): + +class MutantProteinFragment(Serializable): + def __init__( + self, + variant, + gene_name, + amino_acids, + mutant_amino_acid_start_offset, + mutant_amino_acid_end_offset, + supporting_reference_transcripts, + n_overlapping_reads, + n_alt_reads, + n_ref_reads, + n_alt_reads_supporting_protein_sequence): + """ + Parameters + ---------- + variant : varcode.Variant + Somatic mutation. + + gene_name : str + Gene from which we used a transcript to translate this mutation. + + amino_acids : str + Translated protein sequence, aggregated from possibly multiple + synonymous coding sequences. + + mutant_amino_acid_start_offset : int + Starting offset of amino acids which differ due to the mutation + + mutant_amino_acid_end_offset : int + End offset of amino acids which differ due to the mutation + + supporting_reference_transcripts : list of pyensembl.Transcript + PyEnsembl Transcript objects for reference transcripts which + were used to establish the reading frame of coding sequence(s) + detected from RNA. + + n_overlapping_reads : int + Number of reads overlapping the variant locus. + + n_alt_reads : int + Number of reads supporting the variant. + + n_ref_reads : int + Number of reads supporting the reference allele. + + n_alt_reads_supporting_protein_sequence : int + Number of RNA reads fully spanning the cDNA sequence(s) from which + we translated this amino acid sequence. + """ + self.variant = variant + self.gene_name = gene_name + self.amino_acids = amino_acids + self.mutant_amino_acid_start_offset = mutant_amino_acid_start_offset + self.mutant_amino_acid_end_offset = mutant_amino_acid_end_offset + self.supporting_reference_transcripts = \ + supporting_reference_transcripts + self.n_overlapping_reads = n_overlapping_reads + self.n_alt_reads = n_alt_reads + self.n_ref_reads = n_ref_reads + self.n_alt_reads_supporting_protein_sequence = \ + n_alt_reads_supporting_protein_sequence + @classmethod def from_isovar_protein_sequence(cls, variant, protein_sequence): return cls( @@ -185,7 +203,8 @@ def global_start_pos(self): # position of mutation start relative to the full amino acid sequence global_mutation_start_pos = self.predicted_effect().aa_mutation_start_offset if global_mutation_start_pos is None: - logger.error('Could not find mutation start pos for variant %s', + logger.error( + 'Could not find mutation start pos for variant %s', self.variant) return -1 diff --git a/vaxrank/patient_info.py b/vaxrank/patient_info.py new file mode 100644 index 0000000..6418247 --- /dev/null +++ b/vaxrank/patient_info.py @@ -0,0 +1,21 @@ +from serializable import Serializable + +class PatientInfo(Serializable): + def __init__( + self, + patient_id, + vcf_paths, + bam_path, + mhc_alleles, + num_somatic_variants, + num_coding_effect_variants, + num_variants_with_rna_support, + num_variants_with_vaccine_peptides): + self.patient_id = patient_id + self.vcf_paths = vcf_paths + self.bam_path = bam_path + self.mhc_alleles = mhc_alleles + self.num_somatic_variants = num_somatic_variants + self.num_coding_effect_variants = num_coding_effect_variants + self.num_variants_with_rna_support = num_variants_with_rna_support + self.num_variants_with_vaccine_peptides = num_variants_with_vaccine_peptides diff --git a/vaxrank/reference_proteome.py b/vaxrank/reference_proteome.py index ed2f79f..4f15d45 100644 --- a/vaxrank/reference_proteome.py +++ b/vaxrank/reference_proteome.py @@ -1,5 +1,3 @@ -# Copyright (c) 2016-2018. Mount Sinai School of Medicine -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/vaxrank/report.py b/vaxrank/report.py index 63bd1a2..9b4560f 100644 --- a/vaxrank/report.py +++ b/vaxrank/report.py @@ -1,5 +1,3 @@ -# Copyright (c) 2016-2018. Mount Sinai School of Medicine -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -12,8 +10,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +# pylint: disable=abstract-class-instantiated +# For more details see https://github.com/PyCQA/pylint/issues/3060 + from __future__ import absolute_import, division -from collections import namedtuple, OrderedDict +from collections import OrderedDict from importlib import import_module import logging import os @@ -42,18 +43,6 @@ ) -PatientInfo = namedtuple("PatientInfo", ( - "patient_id", - "vcf_paths", - "bam_path", - "mhc_alleles", - "num_somatic_variants", - "num_coding_effect_variants", - "num_variants_with_rna_support", - "num_variants_with_vaccine_peptides", -)) - - class TemplateDataCreator(object): def __init__( self, diff --git a/vaxrank/vaccine_peptide.py b/vaxrank/vaccine_peptide.py index a8785c0..4aeefe6 100644 --- a/vaxrank/vaccine_peptide.py +++ b/vaxrank/vaccine_peptide.py @@ -1,5 +1,3 @@ -# Copyright (c) 2016. Mount Sinai School of Medicine -# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -15,60 +13,58 @@ from __future__ import absolute_import, print_function, division -from collections import namedtuple from operator import attrgetter import numpy as np +from serializable import Serializable from .manufacturability import ManufacturabilityScores -VaccinePeptideBase = namedtuple( - "VaccinePeptide", [ - "mutant_protein_fragment", - "mutant_epitope_predictions", - "wildtype_epitope_predictions", - "mutant_epitope_score", - "wildtype_epitope_score", - "num_mutant_epitopes_to_keep", - "manufacturability_scores"]) - -class VaccinePeptide(VaccinePeptideBase): +class VaccinePeptide(Serializable): """ VaccinePeptide combines the sequence information of MutantProteinFragment with MHC binding predictions for subsequences of the protein fragment. - The resulting lists of mutant and wildtype epitope predictions are sorted by ic50. + The resulting lists of mutant and wildtype epitope predictions + are sorted by affinity. """ - def __new__( - cls, + + def __init__( + self, mutant_protein_fragment, epitope_predictions, - num_mutant_epitopes_to_keep=10000): + num_mutant_epitopes_to_keep=10000, + sort_predictions_by='ic50'): + self.mutant_protein_fragment = mutant_protein_fragment + self.epitope_predictions = epitope_predictions + self.num_mutant_epitopes_to_keep = num_mutant_epitopes_to_keep + self.sort_predictions_by = sort_predictions_by + + sort_key = attrgetter(sort_predictions_by) + # only keep the top k epitopes - mutant_epitope_predictions = sorted([ - p for p in epitope_predictions if p.overlaps_mutation and not p.occurs_in_reference - ], key=attrgetter('ic50'))[:num_mutant_epitopes_to_keep] - wildtype_epitope_predictions = sorted([ - p for p in epitope_predictions if not p.overlaps_mutation or p.occurs_in_reference - ], key=attrgetter('ic50')) - - wildtype_epitope_score = sum( - p.logistic_epitope_score() for p in wildtype_epitope_predictions) + self.mutant_epitope_predictions = sorted([ + p for p in epitope_predictions + if p.overlaps_mutation and not p.occurs_in_reference + ], key=sort_key)[:self.num_mutant_epitopes_to_keep] + + self.wildtype_epitope_predictions = sorted([ + p for p in epitope_predictions + if not p.overlaps_mutation or p.occurs_in_reference + ], key=sort_key) + + self.wildtype_epitope_score = sum( + p.logistic_epitope_score() + for p in self.wildtype_epitope_predictions) # only keep the top k epitopes for the purposes of the score - mutant_epitope_score = sum( - p.logistic_epitope_score() for p in mutant_epitope_predictions) - - return VaccinePeptideBase.__new__( - cls, - mutant_protein_fragment=mutant_protein_fragment, - mutant_epitope_predictions=mutant_epitope_predictions, - wildtype_epitope_predictions=wildtype_epitope_predictions, - mutant_epitope_score=mutant_epitope_score, - wildtype_epitope_score=wildtype_epitope_score, - num_mutant_epitopes_to_keep=num_mutant_epitopes_to_keep, - manufacturability_scores=ManufacturabilityScores.from_amino_acids( - mutant_protein_fragment.amino_acids)) + self.mutant_epitope_score = sum( + p.logistic_epitope_score() + for p in self.mutant_epitope_predictions) + + self.manufacturability_scores = \ + ManufacturabilityScores.from_amino_acids( + self.mutant_protein_fragment.amino_acids) def peptide_synthesis_difficulty_score_tuple( self, @@ -207,4 +203,12 @@ def to_dict(self): "mutant_protein_fragment": self.mutant_protein_fragment, "epitope_predictions": epitope_predictions, "num_mutant_epitopes_to_keep": self.num_mutant_epitopes_to_keep, + "sort_predictions_by": self.sort_predictions_by, } + + @classmethod + def from_dict(cls, d): + d = d.copy() + if "sort_predictions_by" not in d: + d["sort_predictions_by"] = "ic50" + return cls(**d)