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