1818from .base_predictor import _check_flank_inputs
1919from .binding_prediction import BindingPrediction
2020from .binding_prediction_collection import BindingPredictionCollection
21- from .pred import Prediction , Kind
21+ from .pred import Kind , Prediction
2222from .unsupported_allele import UnsupportedAllele
2323
2424logger = logging .getLogger (__name__ )
@@ -99,9 +99,10 @@ class MHCflurry(BasePredictor):
9999 """
100100 MHCflurry predictor using the modern Class1PresentationPredictor API.
101101
102- Produces both ``pMHC_affinity`` and ``pMHC_presentation`` predictions
103- per peptide-allele pair. The legacy ``predict_peptides`` method returns
104- BindingPrediction objects based on affinity values for backward compat.
102+ Produces per-allele ``pMHC_affinity`` predictions and one haplotype-level
103+ ``pMHC_presentation`` prediction per peptide. The legacy
104+ ``predict_peptides`` method returns BindingPrediction objects based on
105+ affinity values for backward compat.
105106
106107 See https://github.com/openvax/mhcflurry
107108 """
@@ -214,11 +215,12 @@ def predict(self, peptides, n_flanks=None, c_flanks=None):
214215 """
215216 Predict for a list of peptide sequences.
216217
217- Returns a list of PeptideResult, each containing both
218- pMHC_affinity and pMHC_presentation Prediction objects per allele.
218+ Returns a list of PeptideResult, each containing one pMHC_affinity
219+ Prediction per allele and one haplotype-level pMHC_presentation
220+ Prediction with best_allele attribution when MHCflurry reports it.
219221
220- Uses batch prediction across all alleles in a single call for
221- both affinity and presentation scores.
222+ Uses batch prediction across alleles for affinity and a genotype-level
223+ presentation call for presentation scores.
222224 """
223225 from .pred import PeptideResult
224226
@@ -243,34 +245,38 @@ def predict(self, peptides, n_flanks=None, c_flanks=None):
243245 include_percentile_ranks = self .include_affinity_percentile_ranks ,
244246 )
245247
246- # Per-allele presentation calls (presentation predictor does
247- # deconvolution across alleles, so we call per-allele to get
248- # per-allele presentation scores). Key by mhcflurry's output
249- # allele string so lookups with aff_df.allele always match.
250- pres_by_pep_allele = {}
251- for input_allele in allele_list :
252- kwargs = {
253- "peptides" : peptide_list ,
254- "alleles" : [input_allele ],
255- "include_affinity_percentile" : False ,
256- "verbose" : 0 ,
257- }
258- if n_flank_list is not None :
259- kwargs ["n_flanks" ] = n_flank_list
260- if c_flank_list is not None :
261- kwargs ["c_flanks" ] = c_flank_list
262- df = self .predictor .predict (** kwargs )
263- if len (df ) != len (peptide_list ):
248+ # The presentation predictor deconvolves across the full genotype.
249+ # Preserve that haplotype-level contract here: one presentation
250+ # Prediction per peptide, with best_allele carried as attribution
251+ # when MHCflurry reports it.
252+ kwargs = {
253+ "peptides" : peptide_list ,
254+ "alleles" : allele_list ,
255+ "include_affinity_percentile" : False ,
256+ "verbose" : 0 ,
257+ }
258+ if n_flank_list is not None :
259+ kwargs ["n_flanks" ] = n_flank_list
260+ if c_flank_list is not None :
261+ kwargs ["c_flanks" ] = c_flank_list
262+ pres_df = self .predictor .predict (** kwargs )
263+ if len (pres_df ) != len (peptide_list ):
264+ raise ValueError (
265+ "MHCflurry returned %d presentation row(s) for %d "
266+ "peptide input(s)" % (len (pres_df ), len (peptide_list )))
267+
268+ pres_by_peptide_index = {}
269+ for row_position , row in enumerate (pres_df .itertuples (index = False )):
270+ row_index = int (getattr (row , "peptide_num" , row_position ))
271+ if row_index in pres_by_peptide_index :
264272 raise ValueError (
265- "MHCflurry returned %d presentation row(s) for %d "
266- "peptide input(s) and allele '%s'" % (
267- len (df ), len (peptide_list ), input_allele ))
268- for row_index , row in enumerate (df .itertuples (index = False )):
269- output_allele = getattr (row , 'allele' , input_allele )
270- pres_by_pep_allele [(row_index , output_allele )] = (
271- row .presentation_score ,
272- row .presentation_percentile ,
273- )
273+ "MHCflurry returned duplicate presentation row for "
274+ "peptide index %d" % row_index )
275+ pres_by_peptide_index [row_index ] = (
276+ row .presentation_score ,
277+ row .presentation_percentile ,
278+ getattr (row , "best_allele" , getattr (row , "allele" , "" )),
279+ )
274280
275281 groups = [list () for _ in peptide_list ]
276282 for row_index , row in zip (batch_indices , aff_df .itertuples (index = False )):
@@ -301,19 +307,21 @@ def predict(self, peptides, n_flanks=None, c_flanks=None):
301307 predictor_name = "mhcflurry" ,
302308 ))
303309
304- key = ( row_index , allele )
305- if key not in pres_by_pep_allele :
310+ for row_index , pep in enumerate ( peptide_list ):
311+ if row_index not in pres_by_peptide_index :
306312 raise ValueError (
307313 "MHCflurry: missing presentation score for "
308- "peptide='%s' allele='%s' (this indicates an allele or "
309- "peptide string mismatch between the affinity and "
310- "presentation predictor outputs)" % (pep , allele ))
311- pres_score , pres_pct = pres_by_pep_allele [key ]
314+ "peptide index %d, peptide='%s'" % (row_index , pep ))
315+ pres_score , pres_pct , best_allele = pres_by_peptide_index [row_index ]
316+ n_flank = (
317+ n_flank_list [row_index ] if n_flank_list is not None else "" )
318+ c_flank = (
319+ c_flank_list [row_index ] if c_flank_list is not None else "" )
312320 groups [row_index ].append (Prediction (
313321 kind = Kind .pMHC_presentation ,
314322 score = pres_score ,
315323 peptide = pep ,
316- allele = allele ,
324+ allele = best_allele or "" ,
317325 n_flank = n_flank ,
318326 c_flank = c_flank ,
319327 percentile_rank = pres_pct ,
@@ -331,6 +339,18 @@ def predict_with_flanks(self, peptides, n_flanks, c_flanks):
331339 def _default_pred_kind (self ):
332340 return Kind .pMHC_affinity
333341
342+ def kind_support (self ):
343+ return {
344+ Kind .pMHC_affinity : {
345+ "mhc_dependence" : "single_allele" ,
346+ "mhc_class" : "I" ,
347+ },
348+ Kind .pMHC_presentation : {
349+ "mhc_dependence" : "haplotype" ,
350+ "mhc_class" : "I" ,
351+ },
352+ }
353+
334354
335355class MHCflurry_Affinity (BasePredictor ):
336356 """
0 commit comments