View Javadoc
1   /*** VictorTextFileDataSetLoader.java - part of the MirkE (say murky) application for colormetric analysis emphesizing 
2    kinetics.
3    
4    Created by: Scott Menor on 21 July, 2004.
5    
6    Copyright (c) 2004 Arizona State University - Cancer Research Institute. All rights reserved.
7    
8    MirkE is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2 of the License, or
11   (at your option) any later version.
12   
13   MirkE is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17   
18   You should have received a copy of the GNU General Public License
19   along with MirkE; if not, write to the Free Software
20   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
21   */
22  
23  package edu.asu.cri.MirkE.io;
24  
25  import java.io.BufferedReader;
26  import java.io.File;
27  import java.io.FileReader;
28  import java.io.IOException;
29  import java.util.Arrays;
30  import java.util.GregorianCalendar;
31  import java.util.HashMap;
32  import java.util.Iterator;
33  import java.util.LinkedHashMap;
34  import java.util.LinkedHashSet;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.TreeSet;
38  import java.util.Vector;
39  
40  import net.sf.hibernate.Session;
41  import net.sf.hibernate.Transaction;
42  import edu.asu.cri.MirkE.dataStructures.DataSet;
43  import edu.asu.cri.MirkE.dataStructures.PlateDescription;
44  import edu.asu.cri.MirkE.dataStructures.PlateWellDataPoint;
45  import edu.asu.cri.MirkE.dataStructures.PlateWellDescriptor;
46  import edu.asu.cri.MirkE.exceptions.MirkEApplicationException;
47  import edu.asu.cri.MirkE.exceptions.MirkESystemException;
48  
49  /*** Reads a text file generated by Victor's software (TODO - find its name) and returns a DataSet
50   
51   */
52  public class VictorTextFileDataSetLoader {
53      // TODO - replace the following with a typesafe enum (waiting till I get Java 1.5)
54      private final static int COLUMN_FORMATTED_DATA = 0;
55      private final static int PLATE_LAYOUT_DATA = 1;
56      private final static int PROTOCOL_DESCRIPTION = 2;
57      private final static int PLATE_MAP = 3;
58      private final static int IGNORE = 4;
59          
60      /*** load a DataSet from a Victor text dump
61       *
62       * @param filename
63       *
64       * @return dataSet
65       * @throws IOException
66       * @throws MirkEApplicationException
67       * @throws MirkESystemException 
68       */
69      public static DataSet load(String filename) 
70      throws IOException, MirkEApplicationException, MirkESystemException{
71          File file = new File(filename);
72          DataSet ds = load(file);
73          return ds;
74      }
75      
76      /*** load a DataSet from a Victor text dump
77       
78       @param files
79       
80       @return dataSet
81       * @throws IOException
82       * @throws MirkEApplicationException
83       * @throws MirkESystemException 
84       */
85      public static DataSet load(File[] files) 
86      throws IOException, MirkEApplicationException, MirkESystemException
87      {
88          DataSet dataSet = new DataSet();
89          
90          for (int fileNumber=0;fileNumber<files.length;fileNumber++) {
91              dataSet = load(files[fileNumber], 
92                      dataSet);
93          }
94          
95          return dataSet;
96      }
97      
98      /*** load a DataSet from a Victor text dump
99       
100      @param file
101      
102      @return dataSet
103      * @throws IOException
104      * @throws MirkEApplicationException
105      * @throws MirkESystemException 
106      */
107     public static DataSet load(File file) throws IOException, MirkEApplicationException, MirkESystemException {
108         DataSet dataSet = new DataSet();        
109         return load(file, dataSet);
110     }
111     
112     /*** load a DataSet from a Victor text dump
113      
114      @param file
115      @param dataSet <code>DataSet</code> to import <code>DataPoint</code>s into
116      
117      @return dataSet
118      * @throws IOException
119      * @throws MirkEApplicationException
120      */
121     public static DataSet load(File file,
122             DataSet dataSet) throws IOException, MirkEApplicationException {
123         
124         BufferedReader bufferedReader = null;
125         // open BufferedReader backed by a FileReader for the data file
126         
127         bufferedReader = new BufferedReader(new FileReader(file));
128         //TODO - inform the user that the file was not found/
129         // this will need to be done from the aspect that catches
130         // the thrown exception
131         
132         
133         bufferedReader = new BufferedReader(new FileReader(file));
134         //TODO - inform the user that the file was not found/
135         // this will need to be done from the aspect that catches
136         // the thrown exception
137         String plateIdentifier;
138         Map protocolDescriptionMap = new HashMap();
139         
140         TreeSet plateRowsTreeSet;
141         TreeSet plateColumnsTreeSet;
142         
143         LinkedHashSet observables = new LinkedHashSet();
144         
145         
146         Vector columnIndices = new Vector(Arrays.asList(bufferedReader.readLine().split("//t"))); // this is a bit of a kludge
147         Map indexToObservableColumnMap = new LinkedHashMap();
148         int timeIndexColumnNumber = 0;
149         int largestObservableColumnNumber = 0;
150         
151         while (timeIndexColumnNumber >= 0) {
152             timeIndexColumnNumber = columnIndices.indexOf("Time", timeIndexColumnNumber + 1);
153             
154             if (timeIndexColumnNumber >= 0) { // TODO - clean up the logic here (should work but not very elegant)
155                 indexToObservableColumnMap.put(columnIndices.elementAt(timeIndexColumnNumber + 1), new Integer(timeIndexColumnNumber));
156                 if (timeIndexColumnNumber > largestObservableColumnNumber) {
157                     largestObservableColumnNumber = timeIndexColumnNumber + 1;
158                 }
159             }
160         }
161         
162         plateIdentifier = file.getName(); // TODO - choose a better plateIdentifier
163         
164         // TODO - should we rework to get rid of the following:?
165         plateRowsTreeSet = new TreeSet();
166         plateColumnsTreeSet = new TreeSet();
167         
168         String well = null;
169         String plateRow = null;
170         Integer plateColumn = null;
171         
172         // TODO - clean this up
173         String previousWellColumnElement = "";
174         // end TODO
175         
176         int victorTextFileSubsection = COLUMN_FORMATTED_DATA; 
177         
178         while (bufferedReader.ready()) { // TODO - clean this up (poor design; mostly legacy code from MirkE 0.1.x )
179             String rawLine = bufferedReader.readLine();
180             String line = rawLine.trim();
181             
182             switch (victorTextFileSubsection) {
183             case COLUMN_FORMATTED_DATA: { // read the tab-delimited columns of datapoints 
184                 Vector lineTokens = new Vector(Arrays.asList(line.split("//t"))); // split the line into tokens separated by tabs
185 
186                 if (lineTokens.contains("Plate")) {
187                     victorTextFileSubsection = PLATE_LAYOUT_DATA;
188                 } else {
189                     
190                     String wellColumnElement = previousWellColumnElement;
191                     
192                     if (columnIndices.size() == lineTokens.size()) {
193                         //  a good line
194                         wellColumnElement = (String)lineTokens.elementAt(columnIndices.indexOf("Well")); 
195                         previousWellColumnElement = wellColumnElement;
196                         
197                     } else {
198                         // multiple blanks - more special case handling for Victor (I really love those people  :-)
199                         
200                     }
201                     
202                     //String wellColumnElement = (String)lineTokens.elementAt(columnIndices.indexOf("Well")); 
203                     
204                     if (wellColumnElement.equals("")) {
205                         // wellColumnElement is empty - 
206                     } else { 
207                         well = wellColumnElement;
208                         plateRow = well.substring(0, 1);
209                         plateRowsTreeSet.add(plateRow);
210                         
211                         plateColumn = Integer.valueOf(well.substring(1));
212                         plateColumnsTreeSet.add(plateColumn);
213                     }
214                     
215                     Iterator observableIterator = indexToObservableColumnMap.entrySet().iterator();
216                     while (observableIterator.hasNext()) {
217                         Map.Entry entry = (Map.Entry)observableIterator.next();
218                         
219                         int observableTimeIndexColumnNumber = ((Integer)entry.getValue()).intValue();
220                         
221                         if (columnIndices.size() == lineTokens.size()) {
222                             
223                         } else {
224                             observableTimeIndexColumnNumber += -(columnIndices.size() - lineTokens.size()); // TODO - ugly kludge to deal with Victor datasets with blank spaces in the platewell column 
225                         }
226                         
227                         String observableName = (String)entry.getKey();
228                         
229                         if (lineTokens.size() >= observableTimeIndexColumnNumber + 2) { // need +2 here because the maximum index is size-1 and the index of interest is observableTimeIndexColumnNumber + 1 (stupid but took a few minutes for me to figure that one out)
230                             String time = (String)lineTokens.elementAt(observableTimeIndexColumnNumber);
231                             String observationValue = (String)lineTokens.elementAt(observableTimeIndexColumnNumber + 1);
232                             
233                             if (observationValue != null) {
234                                 if (!(observationValue.equals("")||observationValue.equals("N/A"))) { // only store dataPoints with actual values
235                                     PlateWellDataPoint plateWellDataPoint = new PlateWellDataPoint();
236                                     plateWellDataPoint.setTimestamp(toTimestamp(time)); // TODO - need to update these timestamps to real time (Measured on time + the datapoint timestamp)
237                                     plateWellDataPoint.setPlateIdentifier(plateIdentifier);
238                                     plateWellDataPoint.setPlateRow(plateRow.trim());
239                                     plateWellDataPoint.setPlateColumn(plateColumn.toString());
240                                     
241                                     plateWellDataPoint.setObservableName(observableName.trim()); // TODO - verify that this is correct
242                                     observables.add(observableName.trim());
243                                     
244                                     plateWellDataPoint.setObservedValue(Double.parseDouble(observationValue.trim()));
245                                     
246                                     dataSet.save(plateWellDataPoint);
247                                 }
248                             }
249                         }
250                     }								
251                 }
252                 
253             } break;
254             
255             case PLATE_LAYOUT_DATA: {
256                 // for now, just ignoring the plate layout segment
257                 if (line.equals("Protocol description")) {
258                     victorTextFileSubsection = PROTOCOL_DESCRIPTION;
259                 }
260             } break;
261             
262             case PROTOCOL_DESCRIPTION: {
263                 //						DataPoint protocolDescriptionDataPoint = new DataPoint(); // TODO - would be nice to make a single DataPoint per plate (this will make one per line in the protocol description; not ideal)
264                 
265                 //						Map protocolDescriptionMap = protocolDescriptionDataPoint.getDescriptorMap();
266                 protocolDescriptionMap.put("plateIdentifier", plateIdentifier);
267                 protocolDescriptionMap.put("dataPointType", "protocolDescription");
268                 
269                 if (line.startsWith("Plate map of")) {
270                     victorTextFileSubsection = PLATE_MAP;
271                 } else {
272                     String[] protocolDescriptionKeyValuePair = line.split("[//.]{2,}"); // separators must have at least two points (one alone is insufficient; perhaps I should use fixed length parsing instead (don't like that idea, though))
273                     
274                     if (protocolDescriptionKeyValuePair.length>1) {
275                         // TODO - add to protocol description 
276                         protocolDescriptionMap.put(protocolDescriptionKeyValuePair[0].trim(),
277                                 protocolDescriptionKeyValuePair[1].trim());
278                     }
279                 }		
280                 
281                 //						dataSet.save(protocolDescriptionDataPoint);
282                 
283             } break;
284             
285             case PLATE_MAP: {
286                 if (line.length() == 0) {
287                     victorTextFileSubsection = PROTOCOL_DESCRIPTION;
288                 } else {
289                     String[] splitLine = line.split("//|");
290                     
291                     if (splitLine.length==2) {
292                         String plateMapRow = splitLine[0].trim();
293                         
294                         plateRowsTreeSet.add(plateMapRow);
295                         
296                         Vector plateMapRowVector = new Vector(Arrays.asList(splitLine[1].split("[//s]+")));
297                         
298                         for (int columnNumber=1;columnNumber<plateMapRowVector.size();columnNumber++) {
299                             Integer columnNumberInteger = new Integer(columnNumber);
300                             
301                             PlateWellDescriptor plateWellDescriptor = new PlateWellDescriptor();
302                             plateWellDescriptor.setPlateIdentifier(plateIdentifier);
303                             plateWellDescriptor.setPlateRow(plateMapRow);
304                             plateWellDescriptor.setPlateColumn(columnNumberInteger.toString());
305                             plateWellDescriptor.setPlateWellType(plateMapRowVector.elementAt(columnNumber).toString());
306                             dataSet.save(plateWellDescriptor);
307                             
308                             plateColumnsTreeSet.add(columnNumberInteger);
309                         }
310                         
311                     } else {
312                         // shouldn't get here; TODO - handle cases that do
313                     }							
314                 }
315             } break;
316             
317             case IGNORE: {
318                 
319             } break;
320             }
321             
322         }
323         
324         
325         
326         // TODO - handle IOException gracefully again 
327         // we need the apect to invoke a message window
328         //informing of the exception
329         
330         
331         
332         PlateDescription plateDescription = new PlateDescription();
333         plateDescription.setPlateIdentifier(plateIdentifier);
334         plateDescription.setNumberOfRows(plateRowsTreeSet.size());
335         plateDescription.setNumberOfColumns(plateColumnsTreeSet.size());
336         
337         plateDescription.setTimestamp(toTimestamp(protocolDescriptionMap.get("Measured on").toString()));
338         
339         dataSet.save(plateDescription);
340         
341         processAndGuessLabelWavelengths(dataSet,
342                 new Vector(observables),
343                 protocolDescriptionMap);
344         
345         return dataSet;
346     }
347     
348     /***
349      
350      @param time 
351      
352      @return timestamp
353      */
354     public static java.sql.Timestamp toTimestamp(String time) {
355         java.sql.Timestamp timestamp = null;
356         
357         if (time != null) {
358             if (time.split("/").length > 1) {
359                 String[] whitespaceSplitDate = time.split("//s");
360                 String[] dateComponents = whitespaceSplitDate[0].split("/");
361                 
362                 int month = Integer.valueOf(dateComponents[0]).intValue();
363                 int day = Integer.valueOf(dateComponents[1]).intValue();
364                 int year = Integer.valueOf(dateComponents[2]).intValue();
365                 
366                 String[] hourMinuteSecond = whitespaceSplitDate[1].split(":");
367                 
368                 int hour = Integer.valueOf(hourMinuteSecond[0]).intValue();
369                 int minute = Integer.valueOf(hourMinuteSecond[1]).intValue();
370                 double second = Double.valueOf(hourMinuteSecond[2]).doubleValue();
371                 
372                 if (whitespaceSplitDate[2].equals("PM")) {
373                     hour += 12;
374                 }
375                 
376                 GregorianCalendar gregorianCalendar = new GregorianCalendar(year,
377                         month,
378                         day,
379                         hour,
380                         minute,
381                         (int)Math.round(second));
382                 
383                 timestamp = new java.sql.Timestamp(gregorianCalendar.getTime().getTime()); // nice that Calendar.getTime returns a Date while Date.getTime returns a long 
384                 
385             } else {
386                 String[] hourMinuteSecond = time.split(":");
387                 
388                 int hour = Integer.valueOf(hourMinuteSecond[0]).intValue();
389                 int minute = Integer.valueOf(hourMinuteSecond[1]).intValue();
390                 double second = Double.valueOf(hourMinuteSecond[2]).doubleValue();
391                 
392                 GregorianCalendar gregorianCalendar = new GregorianCalendar(1970,
393                         1,
394                         1,
395                         hour,
396                         minute,
397                         (int)Math.round(second));
398                 
399                 timestamp = new java.sql.Timestamp(gregorianCalendar.getTime().getTime());
400             }
401         }
402         
403         return timestamp;
404     }
405     
406     // TODO - adapt the following (from MirkE 0.2.x ) to replace cryptic observable names with nicer ones ("A 720nm" or "E 540nm", for ecample):
407     /***
408      * @param dataSet
409      * @param observables
410      * @param protocolDescriptionMap
411      * @throws MirkEApplicationException
412      */
413     public static void processAndGuessLabelWavelengths(DataSet dataSet, 
414             Vector observables,
415             Map protocolDescriptionMap) throws MirkEApplicationException{
416         
417         String labelTechnology = protocolDescriptionMap.get("Label technology").toString();
418         
419         Map observableToWavelengthAndTypeMap = new HashMap();
420         
421         // TODO - automate the selection / filling process (this is fairly ugly)
422         
423         if (labelTechnology.equals("Prompt fluorometry")) {
424             //String cwLampFilterName = protocolDescriptionMap.get("CW-lamp filter name").toString();
425             //Object secondCWLampFilterName = protocolDescriptionMap.get("Second meas. CW-lamp filter name");
426             String cwEmissionFilterName = protocolDescriptionMap.get("Emission filter name").toString();
427             if (cwEmissionFilterName != null) {
428                 String firstEmissionMeasurementWavelengthAndType = parseObservableWavelengthAndType(cwEmissionFilterName.toString());
429                 observableToWavelengthAndTypeMap.put(observables.elementAt(0),
430                         firstEmissionMeasurementWavelengthAndType);
431             }
432             
433             Object secondCWEmissionFilterName = protocolDescriptionMap.get("Second meas. emission filter name");
434             if (secondCWEmissionFilterName != null) {
435                 String secondEmissionMeasurementWavelengthAndType = parseObservableWavelengthAndType(secondCWEmissionFilterName.toString());
436                 observableToWavelengthAndTypeMap.put(observables.elementAt(1),
437                         secondEmissionMeasurementWavelengthAndType);
438             }
439         }
440         
441         if (labelTechnology.equals("Photometry")) {
442             Object cwLampFilterName = protocolDescriptionMap.get("CW-lamp filter name");
443             if (cwLampFilterName != null) {
444                 String firstAbsorbanceMeasurementWavelengthAndType = parseObservableWavelengthAndType(cwLampFilterName.toString());
445                 observableToWavelengthAndTypeMap.put(observables.elementAt(0),
446                         firstAbsorbanceMeasurementWavelengthAndType);
447             }
448             
449             Object secondCWLampFilterName = protocolDescriptionMap.get("Second meas. CW-lamp filter name");
450             if (secondCWLampFilterName != null) {
451                 String secondAbsorbanceMeasurementWavelengthAndType = parseObservableWavelengthAndType(secondCWLampFilterName.toString());
452                 observableToWavelengthAndTypeMap.put(observables.elementAt(1),
453                         secondAbsorbanceMeasurementWavelengthAndType);
454             }
455         }
456         
457         try{
458             
459             Session session = dataSet.getSession();
460             
461             Transaction transaction = session.beginTransaction();
462             
463             List dataPointsList = session.find("from PlateWellDataPoint p"); 
464             
465             Iterator dataPointsListIterator = dataPointsList.iterator();
466             while (dataPointsListIterator.hasNext()) {
467                 PlateWellDataPoint plateWellDataPoint = (PlateWellDataPoint)dataPointsListIterator.next();
468                 
469                 if (observableToWavelengthAndTypeMap.containsKey(plateWellDataPoint.getObservableName())) {
470                     plateWellDataPoint.setObservableName(observableToWavelengthAndTypeMap.get(plateWellDataPoint.getObservableName()).toString());
471                     
472                 } // TODO - figure out why we can get here (it seems like the iterator is returning the same PlateWellDataPoint twice )
473             }
474             
475             transaction.commit();
476             session.close();
477         } catch(net.sf.hibernate.HibernateException hibernateException){
478             new MirkEApplicationException("from PlateWellDataPoint p -- dataPointsListIterator", hibernateException);
479         } 
480     }
481     
482     /***
483      
484      @param victorDatasetObservableName
485      
486      @return formattedObservable 
487      */
488     public static String parseObservableWavelengthAndType(String victorDatasetObservableName) {
489         String wavelength = victorDatasetObservableName.substring(1);
490         
491         String observableType = victorDatasetObservableName.substring(0,
492                 1);
493         
494         String type;
495         
496         if (observableType.equals("P")) { // Photometry
497             type = "A";
498             
499         } else if (observableType.equals("E")) { // Emission
500             type = "E";
501             
502         } else if (observableType.equals("F")) { // Fluorescence
503             type = "E";
504             
505         } else {
506             type = "";
507             
508         }
509         
510         String formattedObservable = type + " " + wavelength.trim() + "nm";
511         
512         return formattedObservable;
513     }
514 }