extensions/net.sf.basedb.reggie/trunk/src/net/sf/basedb/reggie/plugins/cmd/LibraryInfo.java

Code
Comments
Other
Rev Date Author Line
6200 08 Apr 21 nicklas 1 package net.sf.basedb.reggie.plugins.cmd;
6200 08 Apr 21 nicklas 2
6200 08 Apr 21 nicklas 3 import java.util.Date;
6205 12 Apr 21 nicklas 4 import java.util.List;
6205 12 Apr 21 nicklas 5 import java.util.regex.Matcher;
6205 12 Apr 21 nicklas 6 import java.util.regex.Pattern;
6200 08 Apr 21 nicklas 7
6205 12 Apr 21 nicklas 8 import net.sf.basedb.core.BioPlate;
6205 12 Apr 21 nicklas 9 import net.sf.basedb.core.BioWell;
6205 12 Apr 21 nicklas 10 import net.sf.basedb.core.DbControl;
6205 12 Apr 21 nicklas 11 import net.sf.basedb.core.ItemQuery;
6205 12 Apr 21 nicklas 12 import net.sf.basedb.core.Protocol;
6205 12 Apr 21 nicklas 13 import net.sf.basedb.core.Tag;
6200 08 Apr 21 nicklas 14 import net.sf.basedb.core.data.PlateCoordinate;
6205 12 Apr 21 nicklas 15 import net.sf.basedb.core.query.Annotations;
6205 12 Apr 21 nicklas 16 import net.sf.basedb.core.query.Expressions;
6205 12 Apr 21 nicklas 17 import net.sf.basedb.core.query.Hql;
6205 12 Apr 21 nicklas 18 import net.sf.basedb.core.query.Restrictions;
6205 12 Apr 21 nicklas 19 import net.sf.basedb.reggie.Reggie;
6205 12 Apr 21 nicklas 20 import net.sf.basedb.reggie.dao.Annotationtype;
6205 12 Apr 21 nicklas 21 import net.sf.basedb.reggie.dao.BioplateType;
6205 12 Apr 21 nicklas 22 import net.sf.basedb.reggie.dao.Pipeline;
6205 12 Apr 21 nicklas 23 import net.sf.basedb.reggie.dao.Subtype;
6510 03 Dec 21 nicklas 24 import net.sf.basedb.reggie.plugins.cmd.ScanBIdRef.SampleIdType;
6205 12 Apr 21 nicklas 25 import net.sf.basedb.util.Coordinate;
6200 08 Apr 21 nicklas 26
6200 08 Apr 21 nicklas 27 /**
6200 08 Apr 21 nicklas 28   Holds all information about a library. Validation will
6200 08 Apr 21 nicklas 29   be done at construction and errors are reported to
6200 08 Apr 21 nicklas 30   the JsonSection. Check the 'valid' flag before using
6200 08 Apr 21 nicklas 31   the information.
6200 08 Apr 21 nicklas 32   
6200 08 Apr 21 nicklas 33   @since 4.32
6200 08 Apr 21 nicklas 34 */
6200 08 Apr 21 nicklas 35 public class LibraryInfo 
6200 08 Apr 21 nicklas 36 {
6200 08 Apr 21 nicklas 37
6200 08 Apr 21 nicklas 38   public String plateId;
6200 08 Apr 21 nicklas 39   public PlateCoordinate well;
6200 08 Apr 21 nicklas 40   
6200 08 Apr 21 nicklas 41   public Date libDate;
6200 08 Apr 21 nicklas 42   public Integer libSize;
6200 08 Apr 21 nicklas 43   public Float libMolarity;
6200 08 Apr 21 nicklas 44   public Float qubitConc;
7286 15 Aug 23 nicklas 45   public Float quantItConc;
6714 29 Apr 22 nicklas 46   public Float usedQuantityFromRna_ng;
6200 08 Apr 21 nicklas 47   public String operator;
6200 08 Apr 21 nicklas 48   
6887 24 Nov 22 nicklas 49   public BarcodeInfo barcodeInfo;
6205 12 Apr 21 nicklas 50   public Protocol protocol;
6205 12 Apr 21 nicklas 51   public BioPlate libPlate;
6474 04 Nov 21 nicklas 52   public BioplateType libPlateType = BioplateType.EXTERNAL_LIBRARY;
6205 12 Apr 21 nicklas 53   
6200 08 Apr 21 nicklas 54   public boolean valid;
6200 08 Apr 21 nicklas 55   
6960 13 Dec 22 nicklas 56   public LibraryInfo(JsonSection section, RnaInfo rna, FastqInfo fastqInfo, MainInfo main, ImportContext ctx)
6200 08 Apr 21 nicklas 57   {
6205 12 Apr 21 nicklas 58     if (section != null)
6200 08 Apr 21 nicklas 59     {
6205 12 Apr 21 nicklas 60       plateId = section.getRequiredEntry("Plate name", PatternValidator.CMD_ID);
6474 04 Nov 21 nicklas 61       ValidPlateCoordinateWithType plateWell = section.getRequiredEntry("Plate well", PlateOrStripValidator.INSTANCE);
6474 04 Nov 21 nicklas 62       if (plateWell != null)
6474 04 Nov 21 nicklas 63       {
6474 04 Nov 21 nicklas 64         well = plateWell.plateCoordinate;
6474 04 Nov 21 nicklas 65         libPlateType = plateWell.plateType;
6474 04 Nov 21 nicklas 66       }
6200 08 Apr 21 nicklas 67   
6346 16 Aug 21 nicklas 68       if (plateId != null && well != null && ctx != null)
6346 16 Aug 21 nicklas 69       {
6346 16 Aug 21 nicklas 70         JsonSection duplicate = ctx.add("LIB:"+plateId+":"+well, section);
6346 16 Aug 21 nicklas 71         if (duplicate != null)
6346 16 Aug 21 nicklas 72         {
6346 16 Aug 21 nicklas 73           String coordinate = Coordinate.numericToAlpha(well.getRow()+1)+(well.getColumn()+1);
6474 04 Nov 21 nicklas 74           String msg = "Plate well ["+coordinate+"; ExternalRef="+plateId+"] duplicated in file: ";
6346 16 Aug 21 nicklas 75           section.addErrorMessage(msg+duplicate.getFile().getName());
6346 16 Aug 21 nicklas 76           duplicate.addErrorMessage(msg+section.getFile().getName());
6346 16 Aug 21 nicklas 77         }
6346 16 Aug 21 nicklas 78       }
6346 16 Aug 21 nicklas 79       
6474 04 Nov 21 nicklas 80       if (plateId != null) libPlate = findExistingLibPlate(plateId, libPlateType, section);
6205 12 Apr 21 nicklas 81       if (libPlate != null && well != null)
6205 12 Apr 21 nicklas 82       {
6205 12 Apr 21 nicklas 83         verifyEmptyPosition(libPlate, well, section);
6205 12 Apr 21 nicklas 84       }
6205 12 Apr 21 nicklas 85       
6893 25 Nov 22 nicklas 86       libDate = section.getRequiredEntry("Date", DateValidator.YYYY_MM_DD.warnIfFutureOrOlder(rna!=null?rna.qiacubeDate:null, main.refDate));
6474 04 Nov 21 nicklas 87       libSize = section.getRequiredEntry("Library size", IntValidator.POSITIVE.warnIf(150, 500));
6205 12 Apr 21 nicklas 88       libMolarity = section.getRequiredEntry("Library molarity", FloatValidator.POSITIVE);
7286 15 Aug 23 nicklas 89       qubitConc = section.getOptionalEntry("Qubit concentration (ng/ul)", NullValidator.allowNull(FloatValidator.POSITIVE));
7286 15 Aug 23 nicklas 90       quantItConc = section.getOptionalEntry("QuantIT concentration (ng/ul)", NullValidator.allowNull(FloatValidator.POSITIVE));
7286 15 Aug 23 nicklas 91       if (qubitConc == null && quantItConc == null)
7286 15 Aug 23 nicklas 92       {
7286 15 Aug 23 nicklas 93         section.addErrorMessage("Both Qubit and QuantIT concentrations are missing in "+section.getName()+" section");
7286 15 Aug 23 nicklas 94       }
7286 15 Aug 23 nicklas 95       
6212 14 Apr 21 nicklas 96       operator = section.getOptionalEntry("Operator", null);
6205 12 Apr 21 nicklas 97       
6887 24 Nov 22 nicklas 98       barcodeInfo = section.getRequiredEntry("Barcode", BarcodeValidator.INSTANCE);
6960 13 Dec 22 nicklas 99       
6960 13 Dec 22 nicklas 100       if (barcodeInfo != null && fastqInfo != null && fastqInfo.barcodeSequences != null)
6960 13 Dec 22 nicklas 101       {
6960 13 Dec 22 nicklas 102         String libSeq = barcodeInfo.seq1+"+"+BarcodeUtil.reverseComplement(barcodeInfo.seq2);
6960 13 Dec 22 nicklas 103         if (!BarcodeUtil.matches(fastqInfo.barcodeSequences, libSeq, 4, 2))
6960 13 Dec 22 nicklas 104         {
6960 13 Dec 22 nicklas 105           section.addErrorMessage("Library.Barcode doesn't match FASTQ header: "+libSeq+" != "+fastqInfo.barcodeSequences);
6960 13 Dec 22 nicklas 106         }
6960 13 Dec 22 nicklas 107       }
6960 13 Dec 22 nicklas 108       
6221 23 Apr 21 nicklas 109       protocol = section.getRequiredEntry("Protocol", ProtocolValidator.LIB_PROTOCOL);
6510 03 Dec 21 nicklas 110       
6510 03 Dec 21 nicklas 111       SampleIdType idType = main.idRef != null ? main.idRef.idType : null;
6510 03 Dec 21 nicklas 112       if (idType != SampleIdType.PRENORMALISED_RNA)
6510 03 Dec 21 nicklas 113       {
6714 29 Apr 22 nicklas 114         if (rna != null && rna.originalQuantity_ng != null && rna.remainingQuantity_ng != null)
6714 29 Apr 22 nicklas 115         {
6714 29 Apr 22 nicklas 116           usedQuantityFromRna_ng = rna.originalQuantity_ng-rna.remainingQuantity_ng;
6714 29 Apr 22 nicklas 117         }
6510 03 Dec 21 nicklas 118       }
6200 08 Apr 21 nicklas 119     }
6205 12 Apr 21 nicklas 120     valid = section != null && !section.hasError();
6200 08 Apr 21 nicklas 121   }
6200 08 Apr 21 nicklas 122   
6474 04 Nov 21 nicklas 123   private BioPlate findExistingLibPlate(String plateId, BioplateType plateType, JsonSection section)
6205 12 Apr 21 nicklas 124   {
6205 12 Apr 21 nicklas 125     BioPlate libPlate = null;
6205 12 Apr 21 nicklas 126     DbControl dc = section.getFile().dc();
6205 12 Apr 21 nicklas 127     ItemQuery<BioPlate> query = BioPlate.getQuery();
6205 12 Apr 21 nicklas 128     query.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT);
6474 04 Nov 21 nicklas 129     if (plateType != null)
6474 04 Nov 21 nicklas 130     {
6474 04 Nov 21 nicklas 131       plateType.addFilter(dc, query, false);
6474 04 Nov 21 nicklas 132     }
6205 12 Apr 21 nicklas 133     query.join(Annotations.innerJoin(null, Annotationtype.EXTERNAL_REF.load(dc), "eref"));
6205 12 Apr 21 nicklas 134     query.restrict(Restrictions.eq(Hql.alias("eref"), Expressions.string(plateId)));
6205 12 Apr 21 nicklas 135     List<BioPlate> list = query.list(dc);
6205 12 Apr 21 nicklas 136     if (list.size() > 1)
6205 12 Apr 21 nicklas 137     {
6205 12 Apr 21 nicklas 138       section.addErrorMessage("Found "+list.size()+" library plates with ExternalRef="+plateId);
6205 12 Apr 21 nicklas 139     }
6205 12 Apr 21 nicklas 140     else if (list.size() == 1)
6205 12 Apr 21 nicklas 141     {
6205 12 Apr 21 nicklas 142       libPlate = list.get(0);
6205 12 Apr 21 nicklas 143     }
6205 12 Apr 21 nicklas 144     return libPlate;
6205 12 Apr 21 nicklas 145   }
6205 12 Apr 21 nicklas 146   
6205 12 Apr 21 nicklas 147   /**
6205 12 Apr 21 nicklas 148     Verify that the given position on the libplate is empty.
6205 12 Apr 21 nicklas 149   */
6205 12 Apr 21 nicklas 150   private void verifyEmptyPosition(BioPlate libPlate, PlateCoordinate pos, JsonSection section)
6205 12 Apr 21 nicklas 151   {
6205 12 Apr 21 nicklas 152     String coordinate = Coordinate.numericToAlpha(pos.getRow()+1)+(pos.getColumn()+1);
6205 12 Apr 21 nicklas 153     BioWell well = libPlate.getBioWell(pos);
6205 12 Apr 21 nicklas 154     if (well == null)
6205 12 Apr 21 nicklas 155     {
6240 19 May 21 nicklas 156       section.addErrorMessage("Well not found: "+libPlate.getName()+"["+ coordinate+"; ExternalRef="+plateId+"]");
6205 12 Apr 21 nicklas 157     }
6205 12 Apr 21 nicklas 158     else if (!well.isEmpty())
6205 12 Apr 21 nicklas 159     {
6240 19 May 21 nicklas 160       section.addErrorMessage("Well is not empty: "+libPlate.getName()+"["+ coordinate+"; ExternalRef="+plateId+"]");
6205 12 Apr 21 nicklas 161     }
6205 12 Apr 21 nicklas 162   }
6205 12 Apr 21 nicklas 163
6205 12 Apr 21 nicklas 164   
6205 12 Apr 21 nicklas 165   /**
6205 12 Apr 21 nicklas 166     Find a barcode by looking at the UDI part of the value from the JSON file.
6887 24 Nov 22 nicklas 167     The entry in the JSON is typically similar to one of: 
6887 24 Nov 22 nicklas 168       * 04A UDI0025 (ACTAAGAT-AACCGCGG)
6887 24 Nov 22 nicklas 169       * 03B IDT10_UDI_Adapter2018 (ATCTGGCCAT-CCGCTGTCTA)
6205 12 Apr 21 nicklas 170     If a barcode is found we also check the Barcode sequences and issue
6205 12 Apr 21 nicklas 171     a warning if they don't match.
6205 12 Apr 21 nicklas 172   */
6205 12 Apr 21 nicklas 173   static class BarcodeValidator
6887 24 Nov 22 nicklas 174     implements ValueValidator<String, BarcodeInfo>
6205 12 Apr 21 nicklas 175   {
6205 12 Apr 21 nicklas 176   
6205 12 Apr 21 nicklas 177     static final BarcodeValidator INSTANCE = new BarcodeValidator();
6205 12 Apr 21 nicklas 178     
6205 12 Apr 21 nicklas 179     @Override
6205 12 Apr 21 nicklas 180     public Class<String> getExpectedClass() 
6205 12 Apr 21 nicklas 181     {
6205 12 Apr 21 nicklas 182       return String.class;
6205 12 Apr 21 nicklas 183     }
6205 12 Apr 21 nicklas 184   
6205 12 Apr 21 nicklas 185     @Override
6887 24 Nov 22 nicklas 186     public BarcodeInfo isValid(DbControl dc, String value, JsonSection section, String entryKey) 
6205 12 Apr 21 nicklas 187     {
6887 24 Nov 22 nicklas 188       Pattern p = Pattern.compile("\\w+\\s+((?:UDI\\d+)|(?:IDT10\\w+))\\s+\\(([ACGT]+)-([ACGT]+)\\)");
6205 12 Apr 21 nicklas 189       Matcher m = p.matcher(value);
6205 12 Apr 21 nicklas 190       if (!m.matches())
6205 12 Apr 21 nicklas 191       {
6887 24 Nov 22 nicklas 192         section.addErrorMessage("Invalid barcode in JSON: "+entryKey+"="+value+" (expected to find 'UDI[0-9]+' or 'IDT10...')");
6205 12 Apr 21 nicklas 193         return null;
6205 12 Apr 21 nicklas 194       }
6205 12 Apr 21 nicklas 195       
6887 24 Nov 22 nicklas 196       BarcodeInfo bc = new BarcodeInfo();
6887 24 Nov 22 nicklas 197       bc.raw = value;
6887 24 Nov 22 nicklas 198       bc.name = m.group(1);
6887 24 Nov 22 nicklas 199       bc.barcodeSet = bc.name.startsWith("IDT") ? "IDT10_UDI" : "TruSeqUniqueDual";
6887 24 Nov 22 nicklas 200       bc.seq1 = m.group(2);
6887 24 Nov 22 nicklas 201       bc.seq2 = m.group(3);
6887 24 Nov 22 nicklas 202       
6205 12 Apr 21 nicklas 203       ItemQuery<Tag> query = Tag.getQuery();
6205 12 Apr 21 nicklas 204       query.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT);
6205 12 Apr 21 nicklas 205       Subtype.BARCODE.addFilter(dc, query);
6205 12 Apr 21 nicklas 206       Pipeline.RNA_SEQ.addFilter(dc, query);
6887 24 Nov 22 nicklas 207       query.restrict(Restrictions.eq(Hql.property("name"), Expressions.string(bc.name)));
6205 12 Apr 21 nicklas 208       List<Tag> list = query.list(dc);
6887 24 Nov 22 nicklas 209       if (list.size() > 1)
6205 12 Apr 21 nicklas 210       {
6887 24 Nov 22 nicklas 211         section.addErrorMessage("Found "+list.size()+" barcodes: name="+bc.name);
6205 12 Apr 21 nicklas 212         return null;
6205 12 Apr 21 nicklas 213       }
6887 24 Nov 22 nicklas 214       else if (list.size() == 1)
6205 12 Apr 21 nicklas 215       {
6887 24 Nov 22 nicklas 216         bc.tag = list.get(0);
6887 24 Nov 22 nicklas 217         String seq1 = (String)Annotationtype.BARCODE_SEQUENCE.getAnnotationValue(dc, bc.tag);
6887 24 Nov 22 nicklas 218         String seq2 = (String)Annotationtype.BARCODE_SEQUENCE_2.getAnnotationValue(dc, bc.tag);
6887 24 Nov 22 nicklas 219         if (!bc.seq1.equals(seq1) || !bc.seq2.equals(seq2))
6205 12 Apr 21 nicklas 220         {
6887 24 Nov 22 nicklas 221           section.addWarningMessage("Barcode sequence mismatch: "+entryKey+"="+value+" (expected "+seq1+"-"+seq2+")");
6205 12 Apr 21 nicklas 222         }
6205 12 Apr 21 nicklas 223       }
6887 24 Nov 22 nicklas 224       return bc;
6205 12 Apr 21 nicklas 225     }
6205 12 Apr 21 nicklas 226   }
6205 12 Apr 21 nicklas 227
6474 04 Nov 21 nicklas 228   /**
6887 24 Nov 22 nicklas 229     Barcode information from the JSON file, optionally
6887 24 Nov 22 nicklas 230     mapped to a Tag item in BASE. The 'barcodeSet' is
6887 24 Nov 22 nicklas 231     determined by name pattern:
6887 24 Nov 22 nicklas 232     UDI... -> TruSeqUniqueDual
6887 24 Nov 22 nicklas 233     IDT10... -> IDT10_UDI
6887 24 Nov 22 nicklas 234   */
6887 24 Nov 22 nicklas 235   public static class BarcodeInfo
6887 24 Nov 22 nicklas 236   {
6887 24 Nov 22 nicklas 237     public String raw;
6887 24 Nov 22 nicklas 238     public String name;
6887 24 Nov 22 nicklas 239     public String seq1;
6887 24 Nov 22 nicklas 240     public String seq2;
6887 24 Nov 22 nicklas 241     public String barcodeSet;
6887 24 Nov 22 nicklas 242     public Tag tag;
6887 24 Nov 22 nicklas 243   }
6887 24 Nov 22 nicklas 244   
6887 24 Nov 22 nicklas 245   /**
6474 04 Nov 21 nicklas 246     Validator implementation that delegates to either STRIP_1x8 validator if
6474 04 Nov 21 nicklas 247     the coordinate is specified as 'digit:digit' or to PLATE_8x12 otherwise.
6474 04 Nov 21 nicklas 248     @since 4.33.4
6474 04 Nov 21 nicklas 249   */
6474 04 Nov 21 nicklas 250   static class PlateOrStripValidator
6474 04 Nov 21 nicklas 251     implements ValueValidator<String, ValidPlateCoordinateWithType>
6474 04 Nov 21 nicklas 252   {
6474 04 Nov 21 nicklas 253     static final PlateOrStripValidator INSTANCE = new PlateOrStripValidator();
6474 04 Nov 21 nicklas 254     
6474 04 Nov 21 nicklas 255     @Override
6474 04 Nov 21 nicklas 256     public Class<String> getExpectedClass() 
6474 04 Nov 21 nicklas 257     {
6474 04 Nov 21 nicklas 258       return String.class;
6474 04 Nov 21 nicklas 259     }
6474 04 Nov 21 nicklas 260
6474 04 Nov 21 nicklas 261     @Override
6474 04 Nov 21 nicklas 262     public ValidPlateCoordinateWithType isValid(DbControl dc, String value, JsonSection section, String entryKey) 
6474 04 Nov 21 nicklas 263     {
6474 04 Nov 21 nicklas 264       ValidPlateCoordinateWithType result = new ValidPlateCoordinateWithType();
6474 04 Nov 21 nicklas 265       PlateWellValidator validator;
6474 04 Nov 21 nicklas 266       if (value.matches("\\d\\:\\d"))
6474 04 Nov 21 nicklas 267       {
6474 04 Nov 21 nicklas 268         validator = PlateWellValidator.STRIP_1x8;
6474 04 Nov 21 nicklas 269         result.plateType = BioplateType.EXTERNAL_LIBRARY_STRIP;
6474 04 Nov 21 nicklas 270       }
6474 04 Nov 21 nicklas 271       else
6474 04 Nov 21 nicklas 272       {
6474 04 Nov 21 nicklas 273         validator = PlateWellValidator.PLATE_8x12;
6474 04 Nov 21 nicklas 274         result.plateType = BioplateType.EXTERNAL_LIBRARY;
6474 04 Nov 21 nicklas 275       }
6474 04 Nov 21 nicklas 276       result.plateCoordinate = validator.isValid(dc, value, section, entryKey);
6474 04 Nov 21 nicklas 277       return result;
6474 04 Nov 21 nicklas 278     }
6474 04 Nov 21 nicklas 279   }
6474 04 Nov 21 nicklas 280   
6474 04 Nov 21 nicklas 281   /**
6474 04 Nov 21 nicklas 282     Holds the validated plate type and coordinate.
6474 04 Nov 21 nicklas 283   */
6474 04 Nov 21 nicklas 284   static class ValidPlateCoordinateWithType
6474 04 Nov 21 nicklas 285   {
6474 04 Nov 21 nicklas 286     PlateCoordinate plateCoordinate;
6474 04 Nov 21 nicklas 287     BioplateType plateType;
6474 04 Nov 21 nicklas 288   }
6200 08 Apr 21 nicklas 289 }