cc96ac40af87442674c7a72bfc8b8d826d9a8e95
[jalview.git] / src / jalview / ws / datamodel / alphafold / MappableContactMatrix.java
1 package jalview.ws.datamodel.alphafold;
2
3 import java.awt.Color;
4 import java.util.ArrayList;
5 import java.util.BitSet;
6
7 import jalview.datamodel.ContactListI;
8 import jalview.datamodel.ContactListImpl;
9 import jalview.datamodel.ContactListProviderI;
10 import jalview.datamodel.ContactMatrixI;
11 import jalview.datamodel.GroupSet;
12 import jalview.datamodel.GroupSetI;
13 import jalview.datamodel.Mapping;
14 import jalview.datamodel.SequenceI;
15 import jalview.util.MapList;
16 import jalview.ws.datamodel.MappableContactMatrixI;
17
18 public abstract class MappableContactMatrix<T extends MappableContactMatrix<T>>
19         implements MappableContactMatrixI
20 {
21   /**
22    * the matrix that is being mapped to
23    */
24   protected ContactMatrixI mappedMatrix=null;
25   
26   public ContactListI getContactList(int column)
27   {
28     return mappedMatrix.getContactList(column);
29   }
30
31   public float getMin()
32   {
33     return mappedMatrix.getMin();
34   }
35
36   public float getMax()
37   {
38     return mappedMatrix.getMax();
39   }
40
41   public int getWidth()
42   {
43     return mappedMatrix.getWidth();
44   }
45
46   public int getHeight()
47   {
48     return mappedMatrix.getHeight();
49   }
50
51   @Override
52   public ContactMatrixI getMappedMatrix()
53   {
54     return mappedMatrix;
55   }
56   
57   @Override
58   public GroupSetI getGroupSet()
59   {
60     return mappedMatrix.getGroupSet();
61   };
62
63   @Override
64   public void setGroupSet(GroupSet makeGroups)
65   {
66     mappedMatrix.setGroupSet(makeGroups);
67   }
68   
69   /**
70    * the sequence and how it is mapped to the matrix
71    */
72
73   SequenceI refSeq = null;
74
75   MapList toSeq = null;
76
77   /**
78    * the length that refSeq is expected to be (excluding gaps, of course)
79    */
80   int length;
81
82   @Override
83   public boolean hasReferenceSeq()
84   {
85     return (refSeq != null);
86   }
87
88   @Override
89   public SequenceI getReferenceSeq()
90   {
91     return refSeq;
92   }
93
94   @Override
95   public MapList getMapFor(SequenceI mapSeq)
96   {
97     if (refSeq != null)
98     {
99       while (mapSeq != refSeq && mapSeq.getDatasetSequence() != null)
100       {
101         mapSeq = mapSeq.getDatasetSequence();
102       }
103       if (mapSeq != refSeq)
104       {
105         return null;
106       }
107     }
108     else
109     {
110       if (mapSeq != null)
111       {
112         // our MapList does not concern this seq
113         return null;
114       }
115     }
116
117     return toSeq;
118   }
119
120   /**
121    * set the reference sequence and construct the mapping between the start-end
122    * positions of given sequence and row/columns of contact matrix
123    * 
124    * @param _refSeq
125    */
126   public void setRefSeq(SequenceI _refSeq)
127   {
128     refSeq = _refSeq;
129     while (refSeq.getDatasetSequence() != null)
130     {
131       refSeq = refSeq.getDatasetSequence();
132     }
133     length = _refSeq.getEnd() - _refSeq.getStart() + 1;
134     // if (length!=refSeq.getLength() || _refSeq.getStart()!=1)
135     {
136       toSeq = new MapList(
137               new int[]
138               { _refSeq.getStart(), _refSeq.getEnd() },
139               new int[]
140               { 0, length - 1 }, 1, 1);
141     }
142   }
143
144   public T liftOver(SequenceI newRefSeq, Mapping sp2sq)
145   {
146     if (sp2sq.getMappedWidth() != sp2sq.getWidth())
147     {
148       // TODO: employ getWord/MappedWord to transfer annotation between cDNA and
149       // Protein reference frames
150       throw new Error(
151               "liftOver currently not implemented for transfer of annotation between different types of seqeunce");
152     }
153     boolean mapIsTo = (sp2sq != null) ? (sp2sq.getTo() == refSeq) : false;
154
155     /**
156      * map from matrix to toSeq's coordinate frame
157      */
158     int[] refMap = toSeq.locateInFrom(0, length - 1);
159     ArrayList<Integer> newFromMap = new ArrayList<Integer>();
160     int last = -1;
161     for (int i = 0; i < refMap.length; i += 2)
162     {
163       /*
164        * for each contiguous range in toSeq, locate corresponding range in sequence mapped to toSeq by sp2sq
165        */
166       int[] sp2map = mapIsTo
167               ? sp2sq.getMap().locateInFrom(refMap[i], refMap[i + 1])
168               : sp2sq.getMap().locateInTo(refMap[i], refMap[i + 1]);
169       if (sp2map == null)
170       {
171         continue;
172       }
173
174       for (int spm = 0; spm < sp2map.length; spm += 2)
175       {
176
177         if (last > -1)
178         {
179           if (sp2map[spm] != last + 1)
180           {
181             newFromMap.add(sp2map[spm]);
182           }
183           else
184           {
185             newFromMap.remove(newFromMap.size() - 1);
186           }
187         }
188         else
189         {
190           newFromMap.add(sp2map[spm]);
191         }
192         last = sp2map[spm + 1];
193         newFromMap.add(last);
194       }
195     }
196     if ((newFromMap.size() % 2) != 0)
197     {
198       // should have had an even number of int ranges!
199       throw new Error("PAEMatrix liftover failed.");
200     }
201     int fromIntMap[] = new int[newFromMap.size()];
202     int ipos = 0;
203     for (Integer i : newFromMap)
204     {
205       fromIntMap[ipos++] = i;
206     }
207     MapList newFromMapList = new MapList(fromIntMap,
208             new int[]
209             { 0, length - 1 }, 1, 1);
210
211     T newCM = newMappableContactMatrix(newRefSeq, newFromMapList);
212     return newCM;
213   }
214
215   protected abstract T newMappableContactMatrix(SequenceI newRefSeq,
216           MapList newFromMapList);
217
218   @Override
219   public int[] getMappedPositionsFor(final SequenceI localFrame,
220           final int column)
221   {
222     return getMappedPositionsFor(localFrame, column, column);
223   }
224
225   @Override
226   public int[] getMappedPositionsFor(final SequenceI localFrame, int from,
227           int to)
228   {
229     if (localFrame == null)
230     {
231       throw new Error("Unimplemented when no local sequence given.");
232     }
233     SequenceI lf = localFrame, uf = refSeq;
234
235     // check that localFrame is derived from refSeq
236     // just look for dataset sequences and check they are the same.
237     // in future we could use DBRefMappings/whatever.
238     while (lf.getDatasetSequence() != null
239             || uf.getDatasetSequence() != null)
240     {
241       if (lf.getDatasetSequence() != null)
242       {
243         lf = lf.getDatasetSequence();
244       }
245       if (uf.getDatasetSequence() != null)
246       {
247         uf = uf.getDatasetSequence();
248       }
249     }
250     if (lf != uf)
251     {
252       // could try harder to find a mapping
253       throw new Error("This Matrix associated with '" + refSeq.getName()
254               + "' is not mappable for the given localFrame sequence. ("
255               + localFrame.getName() + ")");
256     }
257     
258     // now look up from-to matrix columns in toSeq frame
259     
260     if (toSeq == null)
261     {
262       // no mapping - so we assume 1:1
263       return new int[] { from, to };
264     }
265     // from-to are matrix columns
266     // first locate on reference sequence
267
268     int[] mappedPositions = toSeq.locateInFrom(from, to);
269     if (mappedPositions==null)
270     {
271       return null;
272     }
273     
274     // and now map to localFrame
275     // from-to columns on the associated sequence should be
276     // i. restricted to positions in localFrame
277     // ii. 
278
279 //    int s = -1, e = -1;
280 //    for (int p = 0; p < mappedPositions.length; p++)
281 //    {
282 //      if (s == -1 && mappedPositions[p] >= localFrame.getStart())
283 //      {
284 //        s = p; // remember first position within local frame
285 //      }
286 //      if (e == -1 || mappedPositions[p] <= localFrame.getEnd())
287 //      {
288 //        // update end pointer
289 //        e = p;
290 //        // compute local map
291 //        mappedPositions[p] = localFrame.findIndex(mappedPositions[p]);
292 //      }
293 //    }
294 //    int[] _trimmed = new int[e - s + 1];
295 //    return _trimmed;
296     return mappedPositions;
297   }
298
299   @Override
300   public ContactListI getMappableContactList(final SequenceI localFrame,
301           final int column)
302   {
303     final int _column;
304     final int _lcolumn;
305     if (localFrame == null)
306     {
307       throw new Error("Unimplemented when no local sequence given.");
308     }
309     // return a ContactListI for column
310     // column is index into localFrame
311     // 1. map column to corresponding column in matrix
312     final MappableContactMatrix us = this;
313     _lcolumn = localFrame.findPosition(column);
314
315     if (toSeq != null)
316     {
317       SequenceI lf = localFrame, uf = refSeq;
318
319       // just look for dataset sequences and check they are the same.
320       // in future we could use DBRefMappings/whatever.
321       while (lf.getDatasetSequence() != null
322               || uf.getDatasetSequence() != null)
323       {
324         if (lf.getDatasetSequence() != null)
325         {
326           lf = lf.getDatasetSequence();
327         }
328         if (uf.getDatasetSequence() != null)
329         {
330           uf = uf.getDatasetSequence();
331         }
332       }
333       if (lf != uf)
334       {
335         // could try harder to find a mapping
336         throw new Error("This Matrix associated with '" + refSeq.getName()
337                 + "' is not mappable for the given localFrame sequence. ("
338                 + localFrame.getName() + ")");
339       }
340       // check the mapping to see if localFrame _lcolumn exists
341       int[] word = toSeq.locateInTo(_lcolumn, _lcolumn);
342       if (word == null)
343       {
344         return null;
345       }
346       _column = word[0];
347     }
348     else
349     {
350       // no mapping
351       _column = _lcolumn;
352     }
353
354     // TODO - remove ? this may be a redundant check
355     if (_column < 0 || ((toSeq != null && _column > toSeq.getToHighest())
356             || (toSeq == null && getHeight() <= _column)))
357     {
358       return null;
359     }
360
361     // 2. resolve ranges in matrix corresponding to range in localFrame
362     final int[] matrixRange = toSeq == null
363             ? new int[]
364             { localFrame.getStart(), localFrame.getEnd() }
365             : toSeq.locateInTo(localFrame.getStart(), localFrame.getEnd());
366
367     int h = 0;
368     for (int p = 0; p < matrixRange.length; p += 2)
369     {
370       h += 1 + Math.abs(matrixRange[p + 1] - matrixRange[p]);
371     }
372     final int rangeHeight = h;
373     // 3. Construct ContactListImpl instance for just those segments.
374
375     return new ContactListImpl(new ContactListProviderI()
376     {
377
378       public int getColumn()
379       {
380         return column;
381       }
382
383       @Override
384       public int getPosition()
385       {
386         return _column;
387       }
388
389       @Override
390       public int getContactHeight()
391       {
392         return rangeHeight;
393       }
394
395       @Override
396       public double getContactAt(int mcolumn)
397       {
398         if (mcolumn < 0 || mcolumn >= rangeHeight)
399         {
400           return -1;
401         }
402         return getElementAt(_column, locateInRange(mcolumn));
403
404         // this code maps from mcolumn to localFrame - but that isn't what's
405         // needed
406         // int loccolumn = localFrame.findPosition(mcolumn);
407         // int[] lcolumn=(toSeq==null) ? new int[] {mcolumn} :
408         // toSeq.locateInTo(loccolumn,loccolumn);
409         // if (lcolumn==null || lcolumn[0] < 0 || lcolumn[0] >= rangeHeight)
410         // {
411         // return -1;
412         // }
413         // return getElementAt(_column,lcolumn[0]);
414       }
415
416       @Override
417       public int[] getMappedPositionsFor(int cStart, int cEnd)
418       {
419         if (!hasReferenceSeq())
420         {
421           return ContactListProviderI.super.getMappedPositionsFor(cStart,
422                   cEnd);
423         }
424         // map into segment of matrix being shown
425         int realCstart = locateInRange(cStart);
426         int realCend = locateInRange(cEnd);
427
428         // TODO account for discontinuities in the mapping
429
430         int[] mappedPositions = toSeq.locateInFrom(realCstart, realCend);
431         if (mappedPositions != null)
432         {
433           int s = -1, e = -1;
434           for (int p = 0; p < mappedPositions.length; p++)
435           {
436             if (s == -1 && mappedPositions[p] >= localFrame.getStart())
437             {
438               s = p; // remember first position within local frame
439             }
440             if (e == -1 || mappedPositions[p] <= localFrame.getEnd())
441             {
442               // update end pointer
443               e = p;
444               // compute local map
445               mappedPositions[p] = localFrame.findIndex(mappedPositions[p]);
446             }
447           }
448         }
449         return mappedPositions;
450       }
451
452       /**
453        * @return the mcolumn'th position in the matrixRange window on the matrix
454        */
455       private int locateInRange(int mcolumn)
456       {
457
458         int h = 0, p = 0;
459         while (h < mcolumn && p + 2 < matrixRange.length)
460         {
461           h += 1 + Math.abs(matrixRange[p + 1] - matrixRange[p]);
462           p += 2;
463         }
464         return matrixRange[p] + mcolumn - h;
465       }
466
467       @Override
468       public Color getColourForGroup()
469       {
470         BitSet gp = us.getGroupsFor(_column);
471         Color col = us.getColourForGroup(gp);
472         return col;
473       }
474     });
475   }
476
477   /**
478    * get a specific element of the underlying contact matrix in its data-local coordinates
479    * rather than the mapped frame. Implementations are allowed to throw
480    * RunTimeExceptions if _column/i are out of bounds
481    * 
482    * @param _column
483    * @param i
484    * @return
485    */
486   public double getElementAt(int _column, int i) {
487     return mappedMatrix.getElementAt(_column, i);
488   }
489
490 }