extensions/net.sf.basedb.reggie/trunk/src/net/sf/basedb/reggie/pdf/ScanBReportWorker.java

Code
Comments
Other
Rev Date Author Line
5647 08 Oct 19 nicklas 1 package net.sf.basedb.reggie.pdf;
5647 08 Oct 19 nicklas 2
6092 14 Dec 20 nicklas 3 import java.io.ByteArrayOutputStream;
6058 17 Nov 20 nicklas 4 import java.io.FileInputStream;
5647 08 Oct 19 nicklas 5 import java.io.IOException;
5647 08 Oct 19 nicklas 6 import java.text.DecimalFormat;
5647 08 Oct 19 nicklas 7 import java.text.DecimalFormatSymbols;
5647 08 Oct 19 nicklas 8 import java.text.NumberFormat;
6449 20 Oct 21 nicklas 9 import java.time.Instant;
6449 20 Oct 21 nicklas 10 import java.time.LocalDateTime;
6449 20 Oct 21 nicklas 11 import java.time.ZoneId;
6449 20 Oct 21 nicklas 12 import java.time.temporal.ChronoUnit;
6027 28 Oct 20 nicklas 13 import java.util.ArrayList;
5647 08 Oct 19 nicklas 14 import java.util.Date;
5647 08 Oct 19 nicklas 15 import java.util.List;
5647 08 Oct 19 nicklas 16 import java.util.Map;
5694 31 Oct 19 nicklas 17 import java.util.Set;
6027 28 Oct 20 nicklas 18 import java.util.TreeSet;
6056 13 Nov 20 nicklas 19 import java.util.regex.Matcher;
6056 13 Nov 20 nicklas 20 import java.util.regex.Pattern;
5647 08 Oct 19 nicklas 21
7024 07 Feb 23 nicklas 22 import org.apache.commons.lang3.time.FastDateFormat;
7024 07 Feb 23 nicklas 23
6058 17 Nov 20 nicklas 24 import com.itextpdf.kernel.colors.Color;
6058 17 Nov 20 nicklas 25 import com.itextpdf.kernel.colors.ColorConstants;
6058 17 Nov 20 nicklas 26 import com.itextpdf.kernel.colors.DeviceRgb;
6058 17 Nov 20 nicklas 27
5647 08 Oct 19 nicklas 28 import net.sf.basedb.core.BioMaterial;
5647 08 Oct 19 nicklas 29 import net.sf.basedb.core.BioSource;
6081 25 Nov 20 nicklas 30 import net.sf.basedb.core.ConfigurationException;
5647 08 Oct 19 nicklas 31 import net.sf.basedb.core.DbControl;
5647 08 Oct 19 nicklas 32 import net.sf.basedb.core.Extract;
6216 16 Apr 21 nicklas 33 import net.sf.basedb.core.Protocol;
5647 08 Oct 19 nicklas 34 import net.sf.basedb.core.RawBioAssay;
5647 08 Oct 19 nicklas 35 import net.sf.basedb.core.Sample;
6031 29 Oct 20 nicklas 36 import net.sf.basedb.core.plugin.ExportOutputStream;
6056 13 Nov 20 nicklas 37 import net.sf.basedb.reggie.Reggie;
5647 08 Oct 19 nicklas 38 import net.sf.basedb.reggie.Site;
5647 08 Oct 19 nicklas 39 import net.sf.basedb.reggie.converter.DateToStringConverter;
6027 28 Oct 20 nicklas 40 import net.sf.basedb.reggie.converter.MultiplyFloatConverter;
6029 28 Oct 20 nicklas 41 import net.sf.basedb.reggie.converter.Translater;
5694 31 Oct 19 nicklas 42 import net.sf.basedb.reggie.dao.AlignedSequences;
5647 08 Oct 19 nicklas 43 import net.sf.basedb.reggie.dao.Annotationtype;
6027 28 Oct 20 nicklas 44 import net.sf.basedb.reggie.dao.DemuxedSequences;
6027 28 Oct 20 nicklas 45 import net.sf.basedb.reggie.dao.MaskedSequences;
6027 28 Oct 20 nicklas 46 import net.sf.basedb.reggie.dao.MergedSequences;
5647 08 Oct 19 nicklas 47 import net.sf.basedb.reggie.dao.Rawbioassay;
5647 08 Oct 19 nicklas 48 import net.sf.basedb.reggie.dao.Rna;
5647 08 Oct 19 nicklas 49 import net.sf.basedb.reggie.dao.RnaQc;
6027 28 Oct 20 nicklas 50 import net.sf.basedb.reggie.dao.SequencingRun;
5647 08 Oct 19 nicklas 51 import net.sf.basedb.reggie.dao.Subtype;
6058 17 Nov 20 nicklas 52 import net.sf.basedb.reggie.pdf.PdfUtil7.Align;
6069 19 Nov 20 nicklas 53 import net.sf.basedb.reggie.pdf.PdfUtil7.Options;
6037 02 Nov 20 nicklas 54 import net.sf.basedb.reggie.script.ScanBReport;
6036 02 Nov 20 nicklas 55 import net.sf.basedb.reggie.script.ScriptResult;
5647 08 Oct 19 nicklas 56 import net.sf.basedb.reggie.ssp.SspModel;
5647 08 Oct 19 nicklas 57 import net.sf.basedb.util.MD5;
5694 31 Oct 19 nicklas 58 import net.sf.basedb.util.Values;
6056 13 Nov 20 nicklas 59 import net.sf.basedb.util.formatter.Formatter;
5647 08 Oct 19 nicklas 60
5647 08 Oct 19 nicklas 61 /**
5647 08 Oct 19 nicklas 62   Worker implementation that runs the 'Scan-B report' R script and
5647 08 Oct 19 nicklas 63   generates a PDF document with the plots and other information.
5647 08 Oct 19 nicklas 64   
5647 08 Oct 19 nicklas 65   @author nicklas
5647 08 Oct 19 nicklas 66   @since 4.24
5647 08 Oct 19 nicklas 67 */
5647 08 Oct 19 nicklas 68 public class ScanBReportWorker 
5647 08 Oct 19 nicklas 69   extends PdfReportWorker
5647 08 Oct 19 nicklas 70 {
5647 08 Oct 19 nicklas 71
5647 08 Oct 19 nicklas 72   /**
5647 08 Oct 19 nicklas 73     The default file name for the generated pdf.
5647 08 Oct 19 nicklas 74   */
5647 08 Oct 19 nicklas 75   public static final String DEFAULT_PDF_NAME = "scanbreport.pdf";
5647 08 Oct 19 nicklas 76   
5647 08 Oct 19 nicklas 77   public static final DateToStringConverter DATE_FORMAT = 
7024 07 Feb 23 nicklas 78       new DateToStringConverter(FastDateFormat.getInstance("yyyy-MM-dd"));
5647 08 Oct 19 nicklas 79
6056 13 Nov 20 nicklas 80   private static final float MARGIN_RIGHT = 585;
5647 08 Oct 19 nicklas 81   private static final float MARGIN_LEFT = 14;
6056 13 Nov 20 nicklas 82   private static final float MARGIN_BOTTOM = 5;
5647 08 Oct 19 nicklas 83
6056 13 Nov 20 nicklas 84   // HEADER section; 4 rows with 6 columns
6056 13 Nov 20 nicklas 85   private static final float[] HEADER_COLS = { 94, 161, 235, 314, 394, 479 };
6056 13 Nov 20 nicklas 86   private static final float[] HEADER_ROWS = { 800, 773, 746, 718 };
5647 08 Oct 19 nicklas 87   
6027 28 Oct 20 nicklas 88   // SSP section
6056 13 Nov 20 nicklas 89   private static final float[] SSP_COLS = { 105, 122 };
6057 16 Nov 20 nicklas 90   private static final float[] SSP_ROWS = { 648, 628.5f, 502.5f, 396.5f, 293.5f, 192.5f, 91.5f };
6027 28 Oct 20 nicklas 91   
6057 16 Nov 20 nicklas 92   // ROR
6057 16 Nov 20 nicklas 93   private static final float[] ROR_COLS = { 290, 335, 349, 363 };
6057 16 Nov 20 nicklas 94   private static final float[] ROR_ROWS = { 629, 610, 635, 615 };
6057 16 Nov 20 nicklas 95   private static final float ROR_RADIUS = 4.5f;
6075 23 Nov 20 nicklas 96   private static final Color ROR_DARK_GREEN = new DeviceRgb(0, 100, 0);  // Matches ROR T0 line in plot
6080 25 Nov 20 nicklas 97   private static final Color ROR_ORANGE = new DeviceRgb(250, 140, 0);    // Matches ROR T1 line in plot
6075 23 Nov 20 nicklas 98   private static final Color ROR_GREEN = new DeviceRgb(50, 225, 150);    // Colors for the green-yellow-red node-status circles
6075 23 Nov 20 nicklas 99   private static final Color ROR_YELLOW = new DeviceRgb(235, 235, 50);
6075 23 Nov 20 nicklas 100   private static final Color ROR_RED = new DeviceRgb(235, 75, 25);
6056 13 Nov 20 nicklas 101   
6075 23 Nov 20 nicklas 102   // CC15 icon X and Y coordinates
6065 18 Nov 20 nicklas 103   private static final float[] CC15 = { 230, 559 };
6056 13 Nov 20 nicklas 104   
6075 23 Nov 20 nicklas 105   // Plot locations -- Aligned to the RIGHT
6075 23 Nov 20 nicklas 106   private static final float PLOT_RIGHT = 566;
6075 23 Nov 20 nicklas 107   private static final float[] PLOT_ROWS = { 605, 466, 362, 258, 157, 56 };
6075 23 Nov 20 nicklas 108 //  private static final float[] PLOT_WIDTH = { 194, 206 };
6075 23 Nov 20 nicklas 109 //  private static final float PLOT_HEIGHT = 80;
5647 08 Oct 19 nicklas 110
5647 08 Oct 19 nicklas 111   private final String config;
5647 08 Oct 19 nicklas 112   private final String parameterSet;
6037 02 Nov 20 nicklas 113   private ScanBReport script;
6081 25 Nov 20 nicklas 114   private List<TemplateFilePage> templates;
6027 28 Oct 20 nicklas 115   private NumberFormat noDecimal;
5647 08 Oct 19 nicklas 116   private NumberFormat oneDecimal;
5647 08 Oct 19 nicklas 117
5647 08 Oct 19 nicklas 118   ScanBReportWorker(PdfReportTemplate template, String config, String parameterSet)
5647 08 Oct 19 nicklas 119   {
5647 08 Oct 19 nicklas 120     super(template);
5647 08 Oct 19 nicklas 121     this.config = config;
5647 08 Oct 19 nicklas 122     this.parameterSet = parameterSet;
5647 08 Oct 19 nicklas 123   }
5647 08 Oct 19 nicklas 124
5647 08 Oct 19 nicklas 125   /**
5647 08 Oct 19 nicklas 126     Run the gene report script for the given raw bioassay and then
5647 08 Oct 19 nicklas 127     create the pdf.
5647 08 Oct 19 nicklas 128   */
5647 08 Oct 19 nicklas 129   @Override
6036 02 Nov 20 nicklas 130   public ScriptResult runAndCreatePdf(DbControl dc, Rawbioassay raw, ExportOutputStream out, ReportOptions options)
5647 08 Oct 19 nicklas 131     throws IOException
5647 08 Oct 19 nicklas 132   {
6027 28 Oct 20 nicklas 133     if (script == null) 
5647 08 Oct 19 nicklas 134     {
5647 08 Oct 19 nicklas 135       // Initialize script in the first call to this method
6075 23 Nov 20 nicklas 136       // NOTE! List of genes MUST be in this order since it is hard-coded into the R script
6075 23 Nov 20 nicklas 137       // We need the list here also in order to load the expression values from 'gene.tsv'
6075 23 Nov 20 nicklas 138       script = new ScanBReport(config, parameterSet, "ESR1", "PGR", "ERBB2", "MKI67");
6081 25 Nov 20 nicklas 139       templates = TemplateFilePage.fromConfig(config+"/templates", script.getScriptDir());
6081 25 Nov 20 nicklas 140       if (templates.size() == 0)
6081 25 Nov 20 nicklas 141       {
6081 25 Nov 20 nicklas 142         throw new ConfigurationException("No 'file' elements specified in reggie-config.xml[/" + config + "/templates]");
6081 25 Nov 20 nicklas 143       }
6027 28 Oct 20 nicklas 144       noDecimal = createNumberFormat(0);
5647 08 Oct 19 nicklas 145       oneDecimal = createNumberFormat(1);
5647 08 Oct 19 nicklas 146     }
6037 02 Nov 20 nicklas 147     ScanBReport.Result result = script.run(dc, raw);
5647 08 Oct 19 nicklas 148     if (result.getExitStatus() == 0)
5647 08 Oct 19 nicklas 149     {
5647 08 Oct 19 nicklas 150       toPdf(dc, result, out, options);
5647 08 Oct 19 nicklas 151     }
5647 08 Oct 19 nicklas 152     return result;
5647 08 Oct 19 nicklas 153   }
5647 08 Oct 19 nicklas 154
6092 14 Dec 20 nicklas 155   @Override
6092 14 Dec 20 nicklas 156   public byte[] redactBeforePersonalInformation(DbControl dc, Rawbioassay raw, PdfUtil7 pdf) 
6092 14 Dec 20 nicklas 157     throws IOException 
6092 14 Dec 20 nicklas 158   {
6100 12 Jan 21 nicklas 159     ByteArrayOutputStream out = new ByteArrayOutputStream(1024*1000);
6092 14 Dec 20 nicklas 160     pdf.open(out);
6100 12 Jan 21 nicklas 161     //pdf.setRedactColor(ColorConstants.PINK, 0.5f); // For debugging 
6100 12 Jan 21 nicklas 162     pdf.redactRect(1, HEADER_COLS[0]-1, HEADER_ROWS[3]-2, HEADER_COLS[1]-2, HEADER_ROWS[1]+20); // First header column
6100 12 Jan 21 nicklas 163     pdf.redactRect(1, HEADER_COLS[2]-1, HEADER_ROWS[1]-2, HEADER_COLS[3]-2, HEADER_ROWS[1]+20); // Arrival date
6100 12 Jan 21 nicklas 164     pdf.redactRect(1, HEADER_COLS[4]-1, HEADER_ROWS[0]-2, MARGIN_RIGHT-2, HEADER_ROWS[0]+10); // Assay
6449 20 Oct 21 nicklas 165     pdf.redactRect(1, 100, MARGIN_BOTTOM-2, PdfUtil7.FULL_WIDTH, MARGIN_BOTTOM+8);
6100 12 Jan 21 nicklas 166     pdf.redactAndClose(true, false); // Change to pdf.redactAndClose(true, true) for debugging
6092 14 Dec 20 nicklas 167     return out.toByteArray();
6092 14 Dec 20 nicklas 168   }
5647 08 Oct 19 nicklas 169
6092 14 Dec 20 nicklas 170   @Override
6092 14 Dec 20 nicklas 171   public void addPersonalInformation(DbControl dc, Rawbioassay raw, PdfUtil7 pdf) 
6092 14 Dec 20 nicklas 172     throws IOException 
6092 14 Dec 20 nicklas 173   {
6449 20 Oct 21 nicklas 174     Map<Subtype, BioMaterial> parents = raw.findParentBioMaterial(dc, Subtype.RNA, Subtype.SPECIMEN, Subtype.CASE, Subtype.PATIENT);
6092 14 Dec 20 nicklas 175     BioSource patient = (BioSource)parents.get(Subtype.PATIENT);
6092 14 Dec 20 nicklas 176     Sample theCase = (Sample)parents.get(Subtype.CASE);
6092 14 Dec 20 nicklas 177     Sample specimen = (Sample)parents.get(Subtype.SPECIMEN);
6449 20 Oct 21 nicklas 178     Extract rna = (Extract)parents.get(Subtype.RNA);
6449 20 Oct 21 nicklas 179     
6449 20 Oct 21 nicklas 180     Date rnaQcDate = null;
6449 20 Oct 21 nicklas 181     if (rna != null)
6449 20 Oct 21 nicklas 182     {
6449 20 Oct 21 nicklas 183       List<RnaQc> rnaqc = RnaQc.findByRna(dc, Rna.get(rna));
6449 20 Oct 21 nicklas 184       RnaQc lastRnaQc = RnaQc.findLast(dc, null, rnaqc);
6449 20 Oct 21 nicklas 185       if (lastRnaQc != null) 
6449 20 Oct 21 nicklas 186       {
6449 20 Oct 21 nicklas 187         rnaQcDate = lastRnaQc.getQcRunDate(dc, null);
6449 20 Oct 21 nicklas 188       }
6449 20 Oct 21 nicklas 189     }
6449 20 Oct 21 nicklas 190
6449 20 Oct 21 nicklas 191     AlignedSequences aligned = raw.getAlignedSequences(dc);
6449 20 Oct 21 nicklas 192     MaskedSequences masked = aligned.getMaskedSequences(dc);
6449 20 Oct 21 nicklas 193     MergedSequences merged = masked.getMergedSequences(dc);
6449 20 Oct 21 nicklas 194     List<DemuxedSequences> demux = merged.getDemuxedSequences(dc);
6449 20 Oct 21 nicklas 195     List<SequencingRun> sequencingRuns = new ArrayList<SequencingRun>();
6449 20 Oct 21 nicklas 196     for (DemuxedSequences dx : demux)
6449 20 Oct 21 nicklas 197     {
6449 20 Oct 21 nicklas 198       sequencingRuns.add(SequencingRun.getByDemuxedSequences(dc, dx));
6449 20 Oct 21 nicklas 199     }
6449 20 Oct 21 nicklas 200
6449 20 Oct 21 nicklas 201     
6100 12 Jan 21 nicklas 202     pdf.setTitle("SCAN-B report: " + raw.getName());
6092 14 Dec 20 nicklas 203     
6092 14 Dec 20 nicklas 204     float headerTitleOffset = 14f;
6092 14 Dec 20 nicklas 205     Options col1 = new Options().maxTextWidth(HEADER_COLS[1]-HEADER_COLS[0]-5);
6092 14 Dec 20 nicklas 206     
6092 14 Dec 20 nicklas 207     pdf.addText("Personal number", 8, HEADER_COLS[0], HEADER_ROWS[1] + headerTitleOffset);
6092 14 Dec 20 nicklas 208     pdf.addText((String)Annotationtype.PERSONAL_NUMBER.getAnnotationValue(dc, patient), 12, HEADER_COLS[0], HEADER_ROWS[1], col1);
6092 14 Dec 20 nicklas 209
6092 14 Dec 20 nicklas 210     pdf.addText("PAD", 8, HEADER_COLS[0], HEADER_ROWS[2] + headerTitleOffset);
6092 14 Dec 20 nicklas 211     pdf.addText((String)Annotationtype.PAD.getAnnotationValue(dc, specimen), 12, HEADER_COLS[0], HEADER_ROWS[2], col1);
6092 14 Dec 20 nicklas 212
6092 14 Dec 20 nicklas 213     // If a yellow-label icon is requried we need to make sure there is room for it
6092 14 Dec 20 nicklas 214     // The specimen name will typically fit in the assigned area, but not if we also need the icon
6092 14 Dec 20 nicklas 215     pdf.addText("Sample", 8, HEADER_COLS[0], HEADER_ROWS[3] + headerTitleOffset);
6092 14 Dec 20 nicklas 216     boolean needYellowLabelIcon = Annotationtype.YELLOW_LABEL.getAnnotationValue(dc, specimen) != null;
6092 14 Dec 20 nicklas 217     if (needYellowLabelIcon) col1.maxTextWidth(col1.getMaxTextWidth()-11);
6092 14 Dec 20 nicklas 218     float fontSize = pdf.addText(specimen == null ? null : specimen.getName(), 12, HEADER_COLS[0], HEADER_ROWS[3], col1);
6092 14 Dec 20 nicklas 219     if (needYellowLabelIcon)
6092 14 Dec 20 nicklas 220     {
6092 14 Dec 20 nicklas 221       addYellowLabelIcon(pdf, specimen.getName(), fontSize);
6092 14 Dec 20 nicklas 222     }
6092 14 Dec 20 nicklas 223       
6092 14 Dec 20 nicklas 224     pdf.addText("Sampling date", 8, HEADER_COLS[2], HEADER_ROWS[1] + headerTitleOffset);
6092 14 Dec 20 nicklas 225     if (specimen != null)
6092 14 Dec 20 nicklas 226     {
6092 14 Dec 20 nicklas 227       pdf.addText(DATE_FORMAT.convert(specimen.getCreationEvent().getEventDate()), 12, HEADER_COLS[2], HEADER_ROWS[1]);
6092 14 Dec 20 nicklas 228     }
6092 14 Dec 20 nicklas 229
6092 14 Dec 20 nicklas 230     pdf.addText(raw.getName(), 12, HEADER_COLS[4], HEADER_ROWS[0]);
6449 20 Oct 21 nicklas 231     
6449 20 Oct 21 nicklas 232     // Extra info at bottom -- Days from Sampling to: Arrival (x), Sequencing (y), Gene expression (w), RNA-QC (q). Report date: xxxx-xx-xx 
6449 20 Oct 21 nicklas 233     LocalDateTime samplingDate = specimen == null ? null : toDay(specimen.getCreationEvent().getEventDate());
6449 20 Oct 21 nicklas 234     LocalDateTime arrivalDate = toDay((Date)Annotationtype.ARRIVAL_DATE.getAnnotationValue(dc, specimen));
6449 20 Oct 21 nicklas 235     LocalDateTime seqDate = toDay(getLatestSequencingEndedDate(dc, sequencingRuns));
6449 20 Oct 21 nicklas 236     LocalDateTime expDate = toDay(raw.getItem().getEntryDate());
6449 20 Oct 21 nicklas 237     
6449 20 Oct 21 nicklas 238     StringBuilder text = new StringBuilder();
6449 20 Oct 21 nicklas 239     if (samplingDate != null)
6449 20 Oct 21 nicklas 240     {
6449 20 Oct 21 nicklas 241       text.append("Days from Sampling to: "); 
6449 20 Oct 21 nicklas 242       text.append(" Arrival (").append(daysBetween(samplingDate, arrivalDate)).append(") ");
6449 20 Oct 21 nicklas 243       text.append("Sequencing (").append(daysBetween(samplingDate, seqDate)).append(") ");
6449 20 Oct 21 nicklas 244       text.append("Gene expression (").append(daysBetween(samplingDate, expDate)).append(") ");
6449 20 Oct 21 nicklas 245       text.append("RNA-QC (").append(daysBetween(samplingDate, toDay(rnaQcDate))).append(") ");
6449 20 Oct 21 nicklas 246       text.append(". ");
6449 20 Oct 21 nicklas 247     }
6449 20 Oct 21 nicklas 248     text.append("Report date: ").append(DATE_FORMAT.format(pdf.getCreationDate()));
6449 20 Oct 21 nicklas 249     pdf.addText(text.toString(), 8, MARGIN_RIGHT, MARGIN_BOTTOM, Options.ALIGN_RIGHT);
6092 14 Dec 20 nicklas 250   }
6092 14 Dec 20 nicklas 251
6037 02 Nov 20 nicklas 252   private void toPdf(DbControl dc, ScanBReport.Result result, ExportOutputStream out, ReportOptions options)
5647 08 Oct 19 nicklas 253     throws IOException 
5647 08 Oct 19 nicklas 254   {
5647 08 Oct 19 nicklas 255     Rawbioassay raw = result.raw;
5647 08 Oct 19 nicklas 256     
6027 28 Oct 20 nicklas 257     AlignedSequences aligned = raw.getAlignedSequences(dc);
6027 28 Oct 20 nicklas 258     MaskedSequences masked = aligned.getMaskedSequences(dc);
6027 28 Oct 20 nicklas 259     MergedSequences merged = masked.getMergedSequences(dc);
6027 28 Oct 20 nicklas 260     List<DemuxedSequences> demux = merged.getDemuxedSequences(dc);
6056 13 Nov 20 nicklas 261     Set<String> readStrings = getReadStrings(dc, demux);
6027 28 Oct 20 nicklas 262     List<SequencingRun> sequencingRuns = new ArrayList<SequencingRun>();
6027 28 Oct 20 nicklas 263     for (DemuxedSequences dx : demux)
6027 28 Oct 20 nicklas 264     {
6027 28 Oct 20 nicklas 265       sequencingRuns.add(SequencingRun.getByDemuxedSequences(dc, dx));
6027 28 Oct 20 nicklas 266     }
6027 28 Oct 20 nicklas 267     Set<String> sequencerModels = getSequencerModels(dc, sequencingRuns);
6027 28 Oct 20 nicklas 268     
6027 28 Oct 20 nicklas 269     Map<Subtype, BioMaterial> parents = raw.findParentBioMaterial(dc, Subtype.PATIENT, Subtype.CASE, Subtype.SPECIMEN, Subtype.LYSATE, Subtype.RNA, Subtype.LIBRARY);
6027 28 Oct 20 nicklas 270     Extract lib = (Extract)parents.get(Subtype.LIBRARY);
6216 16 Apr 21 nicklas 271     Protocol libProtocol = lib == null ? null : lib.getProtocol();
5647 08 Oct 19 nicklas 272     Extract rna = (Extract)parents.get(Subtype.RNA);
6027 28 Oct 20 nicklas 273     Extract lysate = (Extract)parents.get(Subtype.LYSATE);
5647 08 Oct 19 nicklas 274     Sample specimen = (Sample)parents.get(Subtype.SPECIMEN);
5647 08 Oct 19 nicklas 275     Sample theCase = (Sample)parents.get(Subtype.CASE);
5647 08 Oct 19 nicklas 276     BioSource patient = (BioSource)parents.get(Subtype.PATIENT);
5647 08 Oct 19 nicklas 277     Site site = Site.findByCaseName(raw.getName());
5647 08 Oct 19 nicklas 278     RawBioAssay rba = raw.getRawBioAssay();
6027 28 Oct 20 nicklas 279         
5647 08 Oct 19 nicklas 280     String externalName = raw.getName();
6068 19 Nov 20 nicklas 281     if (specimen != null && specimen.getExternalId() != null) 
5647 08 Oct 19 nicklas 282     {
5647 08 Oct 19 nicklas 283       externalName = externalName.replace(specimen.getName(), specimen.getExternalId());
5647 08 Oct 19 nicklas 284     }
5647 08 Oct 19 nicklas 285     
6027 28 Oct 20 nicklas 286     Float rqsOrRin = null;
6449 20 Oct 21 nicklas 287     Date rnaQcDate = null;
6027 28 Oct 20 nicklas 288     if (rna != null)
6027 28 Oct 20 nicklas 289     {
6027 28 Oct 20 nicklas 290       List<RnaQc> rnaqc = RnaQc.findByRna(dc, Rna.get(rna));
6027 28 Oct 20 nicklas 291       RnaQc lastRnaQc = RnaQc.findLast(dc, null, rnaqc);
6449 20 Oct 21 nicklas 292       if (lastRnaQc != null) 
6449 20 Oct 21 nicklas 293       {
6449 20 Oct 21 nicklas 294         rqsOrRin = lastRnaQc.getRqsOrRin(dc, null);
6449 20 Oct 21 nicklas 295         rnaQcDate = lastRnaQc.getQcRunDate(dc, null);
6449 20 Oct 21 nicklas 296       }
6027 28 Oct 20 nicklas 297     }
6027 28 Oct 20 nicklas 298     
6056 13 Nov 20 nicklas 299     NumericFormatter millions = new NumericFormatter(new MultiplyFloatConverter(1/1000000f), noDecimal, "", "");
6027 28 Oct 20 nicklas 300     NumericFormatter percent = new NumericFormatter(null, noDecimal, "%", "");
6027 28 Oct 20 nicklas 301     NumericFormatter count = new NumericFormatter(null, noDecimal);
6027 28 Oct 20 nicklas 302     NumericFormatter fractionAsPercent = new NumericFormatter(new MultiplyFloatConverter(100), noDecimal, "%", "");
6056 13 Nov 20 nicklas 303     NumericFormatter toMillig = new NumericFormatter(new MultiplyFloatConverter(1/1000f), oneDecimal, "", "");
6068 19 Nov 20 nicklas 304     NumericFormatter singleDecimal = new NumericFormatter(null, oneDecimal);
6057 16 Nov 20 nicklas 305     RoRFormatter rorFormatter = new RoRFormatter();
6027 28 Oct 20 nicklas 306     
6029 28 Oct 20 nicklas 307     Map<String, String> translations = new Translater();
6076 24 Nov 20 nicklas 308     // Laterality
6029 28 Oct 20 nicklas 309     translations.put("LEFT", "Left");
6029 28 Oct 20 nicklas 310     translations.put("RIGHT", "Right");
6076 24 Nov 20 nicklas 311     // Pipeline
6029 28 Oct 20 nicklas 312     translations.put("RNAseq/Hisat/StringTie", "Hisat/StringTie");
6076 24 Nov 20 nicklas 313     // BiopsyType
6076 24 Nov 20 nicklas 314     translations.put("SpecimenCoreBiopsy", "Core");
6076 24 Nov 20 nicklas 315     translations.put("SpecimenCoreBiopsy2nd", "CB 2nd");
6076 24 Nov 20 nicklas 316     translations.put("SpecimenFineNeedleAspiration", "FNA");
6076 24 Nov 20 nicklas 317     translations.put("SpecimenSurgery", "OP");
6076 24 Nov 20 nicklas 318     // Subtype
6076 24 Nov 20 nicklas 319     translations.put("Basal", "Basal-like");
6076 24 Nov 20 nicklas 320     translations.put("Her2", "HER2-enriched");
6076 24 Nov 20 nicklas 321     translations.put("LumA", "Luminal A");
6076 24 Nov 20 nicklas 322     translations.put("LumB", "Luminal B");
6076 24 Nov 20 nicklas 323     translations.put("Normal", "Normal-like");
6076 24 Nov 20 nicklas 324     // Grade
6029 28 Oct 20 nicklas 325     translations.put("Grade1", "Grade 1");
6029 28 Oct 20 nicklas 326     translations.put("Grade2", "Grade 2");
6029 28 Oct 20 nicklas 327     translations.put("Grade3", "Grade 3");
6076 24 Nov 20 nicklas 328     // HER2 status
6067 19 Nov 20 nicklas 329     translations.put("HER2nERn", "Negative");
6067 19 Nov 20 nicklas 330     translations.put("HER2pERn", "Positive");
6067 19 Nov 20 nicklas 331     translations.put("HER2nERp", "Negative");
6067 19 Nov 20 nicklas 332     translations.put("HER2pERp", "Positive");
6076 24 Nov 20 nicklas 333     // Library protocol
6409 20 Sep 21 nicklas 334     translations.put("CMD TruSeq mRNA v1", "TruSeq CMD");
6076 24 Nov 20 nicklas 335     translations.put("CTG TruSeq mRNA", "TruSeq CTG");
6093 14 Dec 20 nicklas 336     translations.put("Biomek TruSeq mRNA", "TruSeq BM");
6076 24 Nov 20 nicklas 337     translations.put("NeoPrep_TrueSeq_mRNA_normalization_v1.4", "TruSeq NP");
6076 24 Nov 20 nicklas 338     translations.put("NeoPrep TruSeq mRNA V1", "TruSeq NP");
6076 24 Nov 20 nicklas 339     translations.put("TruSeq mRNA", "TruSeq");
6076 24 Nov 20 nicklas 340     translations.put("dUTP", "dUTP"); // Not really needed, just for completeness
6029 28 Oct 20 nicklas 341     
6058 17 Nov 20 nicklas 342     PdfUtil7 pdfUtil = null;
5647 08 Oct 19 nicklas 343     try
5647 08 Oct 19 nicklas 344     {
6058 17 Nov 20 nicklas 345       pdfUtil = new PdfUtil7("SCAN-B report: " + externalName, result.getScript());
5647 08 Oct 19 nicklas 346       pdfUtil.open(out);
5647 08 Oct 19 nicklas 347       
6081 25 Nov 20 nicklas 348       pdfUtil.importPdf(templates.get(0), 0, 0, Float.NaN, Float.NaN, Align.LEFT);
6029 28 Oct 20 nicklas 349       
6056 13 Nov 20 nicklas 350       // To make it easer to see what we add here and what is already on the template
6028 28 Oct 20 nicklas 351       if (options.debugMode())
6028 28 Oct 20 nicklas 352       {
6058 17 Nov 20 nicklas 353         pdfUtil.setColor(ColorConstants.BLUE);
6075 23 Nov 20 nicklas 354         pdfUtil.setClearColor(new DeviceRgb(248, 248, 248), Float.NaN);
6028 28 Oct 20 nicklas 355       }
6056 13 Nov 20 nicklas 356       /*
6027 28 Oct 20 nicklas 357       for (int r = 0; r < HEADER_ROWS.length; r++)
6027 28 Oct 20 nicklas 358       {
6056 13 Nov 20 nicklas 359         pdfUtil.clearRect(HEADER_COLS[r == 0 ? 4 : 0]-(r == 3 ? 70 : 5), HEADER_ROWS[r]-5, PdfUtil.MARGIN_RIGHT+15, HEADER_ROWS[r]+12);
6027 28 Oct 20 nicklas 360       }
6027 28 Oct 20 nicklas 361       pdfUtil.clearRect(SSP_COLS[0]-5, SSP_ROWS[1]-5, 210, 670);
6027 28 Oct 20 nicklas 362       pdfUtil.clearRect(SSP_COLS[1]-5, SSP_ROWS[6]-5, 210, 560);
6056 13 Nov 20 nicklas 363       pdfUtil.clearRect(300, 608, 340, 642);
6058 17 Nov 20 nicklas 364       pdfUtil.clearRect(PLOT_X1, PLOT_START_Y-4*PLOT_DELTA_Y, PLOT_X1+PLOT_WIDTH, PLOT_START_Y+PLOT_HEIGHT);
6030 28 Oct 20 nicklas 365       pdfUtil.clearRect(MARGIN_LEFT-5, MARGIN_BOTTOM-5, MARGIN_RIGHT+5, MARGIN_BOTTOM+9);
6056 13 Nov 20 nicklas 366       */
6027 28 Oct 20 nicklas 367
6027 28 Oct 20 nicklas 368       // Header section
6027 28 Oct 20 nicklas 369       String[] header = new String[6];
6027 28 Oct 20 nicklas 370       // Row 1
6056 13 Nov 20 nicklas 371       header[4] = externalName;
6027 28 Oct 20 nicklas 372       addHeaderRow(pdfUtil, header, HEADER_ROWS[0]);
5647 08 Oct 19 nicklas 373       // Row 2
6056 13 Nov 20 nicklas 374       header[0] = patient == null ? null : patient.getExternalId();
6056 13 Nov 20 nicklas 375       header[1] = site == Site.UNKNOWN ? null : site.getName();
6056 13 Nov 20 nicklas 376       header[2] = DATE_FORMAT.convert((Date)Annotationtype.ARRIVAL_DATE.getAnnotationValue(dc, specimen));
6056 13 Nov 20 nicklas 377       header[3] = lysate != null && specimen != null ? toMillig.format(lysate.getCreationEvent().getUsedQuantity(specimen)) : null;
6056 13 Nov 20 nicklas 378       header[4] = Values.getString(sequencerModels, ", ", true);
6082 26 Nov 20 nicklas 379       header[5] = millions.format(dc, Annotationtype.PF_READS, merged)+"/"+millions.format(dc, Annotationtype.PM_READS, masked)+"/"+millions.format(dc, Annotationtype.ALIGNED_PAIRS, aligned);
6027 28 Oct 20 nicklas 380       addHeaderRow(pdfUtil, header, HEADER_ROWS[1]);
6027 28 Oct 20 nicklas 381       // Row 3
6056 13 Nov 20 nicklas 382       header[0] = theCase == null ? null : theCase.getExternalId();
6056 13 Nov 20 nicklas 383       header[1] = (String)Annotationtype.CONSENT.getAnnotationValue(dc, theCase);
6056 13 Nov 20 nicklas 384       header[2] = DATE_FORMAT.convert((Date)Annotationtype.QIACUBE_DATE.getAnnotationValue(dc, rna));
6068 19 Nov 20 nicklas 385       header[3] = singleDecimal.format(rqsOrRin);
6078 24 Nov 20 nicklas 386       header[4] = Values.getString(readStrings, ", ", true);
6056 13 Nov 20 nicklas 387       header[5] = fractionAsPercent.format(dc, Annotationtype.FRACTION_DUPLICATION, aligned)+", "+count.format(dc, Annotationtype.QC_GENOTYPE_COUNT, aligned) + " (" + percent.format(dc, Annotationtype.QC_GENOTYPE_HET_PCT, aligned) + ")";
6056 13 Nov 20 nicklas 388       addHeaderRow(pdfUtil, header, HEADER_ROWS[2]);
6056 13 Nov 20 nicklas 389       // Row 4
6056 13 Nov 20 nicklas 390       header[0] = specimen == null ? null : specimen.getExternalId();
6056 13 Nov 20 nicklas 391       header[1] = translations.get(Annotationtype.LATERALITY.getAnnotationValue(dc, specimen));
6076 24 Nov 20 nicklas 392       header[2] = lib == null ? null : DATE_FORMAT.convert(lib.getCreationEvent().getEventDate());
6216 16 Apr 21 nicklas 393       header[3] = libProtocol == null ? null : translations.get(libProtocol.getName());
6056 13 Nov 20 nicklas 394       header[4] = count.format(dc, Annotationtype.FRAGMENT_SIZE_AVG, aligned) + "/" + count.format(dc, Annotationtype.FRAGMENT_SIZE_STDEV, aligned);
6029 28 Oct 20 nicklas 395       header[5] = translations.get(Annotationtype.PIPELINE.getAnnotationValue(dc, rba));
6056 13 Nov 20 nicklas 396       addHeaderRow(pdfUtil, header, HEADER_ROWS[3]);
5647 08 Oct 19 nicklas 397
6056 13 Nov 20 nicklas 398       // Extra info about biopsy type to the left of "Sample"
6076 24 Nov 20 nicklas 399       String biopsyType = translations.get(Annotationtype.BIOPSY_TYPE.getAnnotationValue(dc, specimen));
6069 19 Nov 20 nicklas 400       pdfUtil.addText(biopsyType, 12, HEADER_COLS[0]-3, HEADER_ROWS[3], new Options(Align.RIGHT).underline());
6092 14 Dec 20 nicklas 401       
6056 13 Nov 20 nicklas 402       // YellowLabel icon
6056 13 Nov 20 nicklas 403       if (Annotationtype.YELLOW_LABEL.getAnnotationValue(dc, specimen) != null)
6056 13 Nov 20 nicklas 404       {
6092 14 Dec 20 nicklas 405         addYellowLabelIcon(pdfUtil, specimen.getExternalId(), 12);
6056 13 Nov 20 nicklas 406       }
6056 13 Nov 20 nicklas 407       
6056 13 Nov 20 nicklas 408       // SSP classifications
6027 28 Oct 20 nicklas 409       Annotationtype subtype4 = Annotationtype.sspResultAnnotation(SspModel.getModelByName("Subtype"));
6027 28 Oct 20 nicklas 410       Annotationtype subtype5 = Annotationtype.sspResultAnnotation(SspModel.getModelByName("PAM50 subtype"));
6027 28 Oct 20 nicklas 411       Annotationtype erStatus = Annotationtype.sspResultAnnotation(SspModel.getModelByName("ER"));
6027 28 Oct 20 nicklas 412       Annotationtype nhg = Annotationtype.sspResultAnnotation(SspModel.getModelByName("NHG"));
6027 28 Oct 20 nicklas 413       Annotationtype ki67Status = Annotationtype.sspResultAnnotation(SspModel.getModelByName("Ki67"));
6027 28 Oct 20 nicklas 414       Annotationtype pgrStatus = Annotationtype.sspResultAnnotation(SspModel.getModelByName("PR"));
6027 28 Oct 20 nicklas 415       Annotationtype her2Status = Annotationtype.sspResultAnnotation(SspModel.getModelByName("ERBB2"));
6056 13 Nov 20 nicklas 416       Annotationtype ror = Annotationtype.sspResultAnnotation(SspModel.getModelByName("ROR asT0"));
6056 13 Nov 20 nicklas 417       Annotationtype cc15 = Annotationtype.sspResultAnnotation(SspModel.getModelByName("CC15"));
6027 28 Oct 20 nicklas 418       
6067 19 Nov 20 nicklas 419       // Change the HER2 status model to use depending on the ER status
6067 19 Nov 20 nicklas 420       String er = (String)erStatus.getAnnotationValue(dc, rba);
6067 19 Nov 20 nicklas 421       if ("Positive".equals(er))
6067 19 Nov 20 nicklas 422       {
6067 19 Nov 20 nicklas 423         her2Status = Annotationtype.sspResultAnnotation(SspModel.getModelByName("ERBB2_ERp"));
6067 19 Nov 20 nicklas 424       }
6067 19 Nov 20 nicklas 425       else if ("Negative".equals(er))
6067 19 Nov 20 nicklas 426       {
6067 19 Nov 20 nicklas 427         her2Status = Annotationtype.sspResultAnnotation(SspModel.getModelByName("ERBB2_ERn"));
6067 19 Nov 20 nicklas 428       }
6067 19 Nov 20 nicklas 429       
6069 19 Nov 20 nicklas 430       pdfUtil.addText(translations.get(subtype4.getAnnotationValue(dc, rba)), 14, SSP_COLS[0], SSP_ROWS[0]);
6069 19 Nov 20 nicklas 431       pdfUtil.addText(translations.get(subtype5.getAnnotationValue(dc, rba)), 14, SSP_COLS[0], SSP_ROWS[1]);
6069 19 Nov 20 nicklas 432       pdfUtil.addText(translations.get(er), 14, SSP_COLS[1], SSP_ROWS[2]);
6069 19 Nov 20 nicklas 433       pdfUtil.addText(translations.get(pgrStatus.getAnnotationValue(dc, rba)), 14, SSP_COLS[1], SSP_ROWS[3]);
6069 19 Nov 20 nicklas 434       pdfUtil.addText(translations.get(her2Status.getAnnotationValue(dc, rba)), 14, SSP_COLS[1], SSP_ROWS[4]);
6069 19 Nov 20 nicklas 435       pdfUtil.addText(translations.get(ki67Status.getAnnotationValue(dc, rba)), 14, SSP_COLS[1], SSP_ROWS[5]);
6069 19 Nov 20 nicklas 436       pdfUtil.addText(translations.get(nhg.getAnnotationValue(dc, rba)), 14, SSP_COLS[1], SSP_ROWS[6]);
6027 28 Oct 20 nicklas 437       
6056 13 Nov 20 nicklas 438       // ROR is outputted twice but with different ranges for <=20mm and >20mm
6056 13 Nov 20 nicklas 439       String rorAsTo = (String)ror.getAnnotationValue(dc, rba);
6057 16 Nov 20 nicklas 440       if (rorAsTo != null)
6057 16 Nov 20 nicklas 441       {
6075 23 Nov 20 nicklas 442         pdfUtil.addText(rorFormatter.format(rorAsTo+"-4"), 14, ROR_COLS[0], ROR_ROWS[0], new Options().color(ROR_DARK_GREEN));
6075 23 Nov 20 nicklas 443         pdfUtil.addText(rorFormatter.format(rorAsTo+"+5"), 14, ROR_COLS[0], ROR_ROWS[1], new Options().color(ROR_ORANGE));
6057 16 Nov 20 nicklas 444         
6057 16 Nov 20 nicklas 445         int r = rorFormatter.getRor();
6057 16 Nov 20 nicklas 446         pdfUtil.drawCircle(ROR_COLS[1], ROR_ROWS[2], ROR_RADIUS, r <= 40 ? ROR_GREEN : r <= 60 ? ROR_YELLOW : ROR_RED); // N0
6057 16 Nov 20 nicklas 447         pdfUtil.drawCircle(ROR_COLS[2], ROR_ROWS[2], ROR_RADIUS, r <= 15 ? ROR_GREEN : r <= 40 ? ROR_YELLOW : ROR_RED); // N1-3
6057 16 Nov 20 nicklas 448         pdfUtil.drawCircle(ROR_COLS[3], ROR_ROWS[2], ROR_RADIUS, ROR_RED); // N4
6057 16 Nov 20 nicklas 449         r += 5; // T1
6057 16 Nov 20 nicklas 450         pdfUtil.drawCircle(ROR_COLS[1], ROR_ROWS[3], ROR_RADIUS, r <= 40 ? ROR_GREEN : r <= 60 ? ROR_YELLOW : ROR_RED); // N0
6057 16 Nov 20 nicklas 451         pdfUtil.drawCircle(ROR_COLS[2], ROR_ROWS[3], ROR_RADIUS, r <= 15 ? ROR_GREEN : r <= 40 ? ROR_YELLOW : ROR_RED); // N1-3
6057 16 Nov 20 nicklas 452         pdfUtil.drawCircle(ROR_COLS[3], ROR_ROWS[3], ROR_RADIUS, ROR_RED); // N4
6057 16 Nov 20 nicklas 453       }
6056 13 Nov 20 nicklas 454       
6056 13 Nov 20 nicklas 455       // CC15 is outputted as an icon
6056 13 Nov 20 nicklas 456       String cc15Class = (String)cc15.getAnnotationValue(dc, rba);
6056 13 Nov 20 nicklas 457       if (cc15Class != null)
6056 13 Nov 20 nicklas 458       {
6065 18 Nov 20 nicklas 459         String cc15Img = "Norm15".equals(cc15Class) ? "warning.svg" : "ok.svg";
6065 18 Nov 20 nicklas 460         pdfUtil.importSvg(Reggie.class.getResourceAsStream("/net/sf/basedb/reggie/pdf/"+cc15Img), CC15[0], CC15[1], 14, 12, Align.RIGHT);
6056 13 Nov 20 nicklas 461       }
6056 13 Nov 20 nicklas 462       
6027 28 Oct 20 nicklas 463       java.io.File workDir = result.getWorkDir();
6075 23 Nov 20 nicklas 464       String[] plots = { "sMS_ROR.pdf", "ER_ESR1.pdf", "PgR_PGR.pdf", "HER2_ERBB2.pdf", "Ki67_MKI67.pdf", "NHG_MKI67.pdf" };
6075 23 Nov 20 nicklas 465       for (int plotNo=0; plotNo < plots.length; plotNo++)
5647 08 Oct 19 nicklas 466       {
6076 24 Nov 20 nicklas 467         if (plotNo == 0 && rorAsTo == null) continue; // Skip ROR plot
6075 23 Nov 20 nicklas 468         java.io.File f2 = new java.io.File(workDir, plots[plotNo]);
6027 28 Oct 20 nicklas 469         if (f2.exists())
5647 08 Oct 19 nicklas 470         {
6075 23 Nov 20 nicklas 471           pdfUtil.importPdf(new FileInputStream(f2), 1, PLOT_RIGHT, PLOT_ROWS[plotNo], Float.NaN, Float.NaN, Align.RIGHT);
5647 08 Oct 19 nicklas 472         }
5647 08 Oct 19 nicklas 473       }
6024 26 Oct 20 nicklas 474
6449 20 Oct 21 nicklas 475       // Extra info at bottom -- Days from Arrival to: Sequencing (y), Gene expression (z), RNA-QC (w). Report date: xxxx-xx-xx 
6449 20 Oct 21 nicklas 476       LocalDateTime arrivalDate = toDay((Date)Annotationtype.ARRIVAL_DATE.getAnnotationValue(dc, specimen));
6449 20 Oct 21 nicklas 477       LocalDateTime seqDate = toDay(getLatestSequencingEndedDate(dc, sequencingRuns));
6449 20 Oct 21 nicklas 478       LocalDateTime expDate = toDay(rba.getEntryDate());
6449 20 Oct 21 nicklas 479       StringBuilder text = new StringBuilder();
6449 20 Oct 21 nicklas 480       if (arrivalDate != null)
6449 20 Oct 21 nicklas 481       {
6449 20 Oct 21 nicklas 482         text.append("Days from Arrival to: "); 
6449 20 Oct 21 nicklas 483         text.append("Sequencing (").append(daysBetween(arrivalDate, seqDate)).append(") ");
6449 20 Oct 21 nicklas 484         text.append("Gene expression (").append(daysBetween(arrivalDate, expDate)).append(") ");
6449 20 Oct 21 nicklas 485         text.append("RNA-QC (").append(daysBetween(arrivalDate, toDay(rnaQcDate))).append(") ");
6449 20 Oct 21 nicklas 486         text.append(". ");
6449 20 Oct 21 nicklas 487       }
6449 20 Oct 21 nicklas 488       text.append("Report date: ").append(DATE_FORMAT.format(new Date()));
6449 20 Oct 21 nicklas 489       pdfUtil.addText(text.toString(), 8, MARGIN_RIGHT, MARGIN_BOTTOM, Options.ALIGN_RIGHT);
6028 28 Oct 20 nicklas 490       
6028 28 Oct 20 nicklas 491       if (options.debugMode()) pdfUtil.drawGrid();
6030 28 Oct 20 nicklas 492       
6081 25 Nov 20 nicklas 493       // Page 2, etc.
6081 25 Nov 20 nicklas 494       for (int pageNo = 1; pageNo < templates.size(); pageNo++)
6081 25 Nov 20 nicklas 495       {
6081 25 Nov 20 nicklas 496         pdfUtil.newPage();
6081 25 Nov 20 nicklas 497         pdfUtil.importPdf(templates.get(pageNo), 0, 0, Float.NaN, Float.NaN, Align.LEFT);
6081 25 Nov 20 nicklas 498       }
5647 08 Oct 19 nicklas 499     }
6027 28 Oct 20 nicklas 500     catch (Exception ex)
6027 28 Oct 20 nicklas 501     {
6027 28 Oct 20 nicklas 502       ex.printStackTrace(System.out);
6027 28 Oct 20 nicklas 503       throw ex;
6027 28 Oct 20 nicklas 504     }
5647 08 Oct 19 nicklas 505     finally
5647 08 Oct 19 nicklas 506     {
5647 08 Oct 19 nicklas 507       if (pdfUtil != null) pdfUtil.close();
5647 08 Oct 19 nicklas 508     }
5647 08 Oct 19 nicklas 509   }
5647 08 Oct 19 nicklas 510   
6058 17 Nov 20 nicklas 511   private void addHeaderRow(PdfUtil7 pdfUtil, String[] text, float y)
6027 28 Oct 20 nicklas 512   {
6078 24 Nov 20 nicklas 513     float maxX = MARGIN_RIGHT;
6078 24 Nov 20 nicklas 514     for (int i = text.length-1; i >= 0; i--)
6027 28 Oct 20 nicklas 515     {
6078 24 Nov 20 nicklas 516       if (text[i] != null) 
6078 24 Nov 20 nicklas 517       {
6078 24 Nov 20 nicklas 518         float maxTextWidth = maxX - HEADER_COLS[i];
6078 24 Nov 20 nicklas 519         pdfUtil.addText(text[i], 12, HEADER_COLS[i], y, new Options().maxTextWidth(maxTextWidth));
6078 24 Nov 20 nicklas 520         maxX = HEADER_COLS[i] - 5;
6078 24 Nov 20 nicklas 521       }
6027 28 Oct 20 nicklas 522     }
6027 28 Oct 20 nicklas 523   }
6027 28 Oct 20 nicklas 524   
6092 14 Dec 20 nicklas 525   /**
6092 14 Dec 20 nicklas 526     Adds the "YellowLabel" icon. The "X" location is automatically calculated
6092 14 Dec 20 nicklas 527     so that it fits after the "afterText" that is assumed to be written at
6092 14 Dec 20 nicklas 528     HEADER_COL[0] and HEADER_ROW[3].
6092 14 Dec 20 nicklas 529   */
6092 14 Dec 20 nicklas 530   private void addYellowLabelIcon(PdfUtil7 pdfUtil, String afterText, float fontSize)
6092 14 Dec 20 nicklas 531     throws IOException
6092 14 Dec 20 nicklas 532   {
6092 14 Dec 20 nicklas 533     float imgX = HEADER_COLS[0]+1;
6092 14 Dec 20 nicklas 534     float imgY = HEADER_ROWS[3]-1;
6092 14 Dec 20 nicklas 535     if (afterText != null) 
6092 14 Dec 20 nicklas 536     {
6092 14 Dec 20 nicklas 537       imgX += pdfUtil.getDefaultFont().getWidth(afterText, fontSize);
6092 14 Dec 20 nicklas 538     }
6092 14 Dec 20 nicklas 539     pdfUtil.importSvg(Reggie.class.getResourceAsStream("/net/sf/basedb/reggie/pdf/yellow-label.svg"), imgX, imgY, 11, 11, Align.LEFT);
6092 14 Dec 20 nicklas 540   }
6092 14 Dec 20 nicklas 541   
5647 08 Oct 19 nicklas 542   private NumberFormat createNumberFormat(int numDecimals)
5647 08 Oct 19 nicklas 543   {
5647 08 Oct 19 nicklas 544     DecimalFormatSymbols sym = new DecimalFormatSymbols();
6027 28 Oct 20 nicklas 545     sym.setDecimalSeparator('.');
5647 08 Oct 19 nicklas 546     String format = "0";
5647 08 Oct 19 nicklas 547     if (numDecimals > 0) format += "." + MD5.leftPad("", '0', numDecimals);
5647 08 Oct 19 nicklas 548     DecimalFormat df = new DecimalFormat(format, sym);
5647 08 Oct 19 nicklas 549     return df;
5647 08 Oct 19 nicklas 550   }
5647 08 Oct 19 nicklas 551
6027 28 Oct 20 nicklas 552   private Set<String> getSequencerModels(DbControl dc, List<SequencingRun> sequencingRuns)
6027 28 Oct 20 nicklas 553   {
6027 28 Oct 20 nicklas 554     Set<String> models = new TreeSet<>();
6027 28 Oct 20 nicklas 555     for (SequencingRun sr : sequencingRuns)
6027 28 Oct 20 nicklas 556     {
6027 28 Oct 20 nicklas 557       String m = (String)Annotationtype.HARDWARE_MODEL.getAnnotationValue(dc, sr.getItem().getHardware());
6027 28 Oct 20 nicklas 558       //String m = sr.getItem().getHardware().getName();
6027 28 Oct 20 nicklas 559       if (m != null) models.add(m);
6027 28 Oct 20 nicklas 560     }
6027 28 Oct 20 nicklas 561     return models;
6027 28 Oct 20 nicklas 562   }
6449 20 Oct 21 nicklas 563   
6449 20 Oct 21 nicklas 564   private Date getLatestSequencingEndedDate(DbControl dc, List<SequencingRun> sequencingRuns)
6449 20 Oct 21 nicklas 565   {
6449 20 Oct 21 nicklas 566     Date lastDate = null;
6449 20 Oct 21 nicklas 567     for (SequencingRun sr : sequencingRuns)
6449 20 Oct 21 nicklas 568     {
6449 20 Oct 21 nicklas 569       Date d = (Date)Annotationtype.SEQUENCING_END.getAnnotationValue(dc, sr.getItem());
6449 20 Oct 21 nicklas 570       if (lastDate == null || (d != null && d.after(lastDate)))
6449 20 Oct 21 nicklas 571       {
6449 20 Oct 21 nicklas 572         lastDate = d;
6449 20 Oct 21 nicklas 573       }
6449 20 Oct 21 nicklas 574     }
6449 20 Oct 21 nicklas 575     return lastDate;
6449 20 Oct 21 nicklas 576   }
6027 28 Oct 20 nicklas 577
6056 13 Nov 20 nicklas 578   private Set<String> getReadStrings(DbControl dc, List<DemuxedSequences> demux)
5694 31 Oct 19 nicklas 579   {
6056 13 Nov 20 nicklas 580     Set<String> readStrings = new TreeSet<>();
6078 24 Nov 20 nicklas 581     Formatter<String> readStringFormatter = new ReadStringFormatter();
6056 13 Nov 20 nicklas 582     for (DemuxedSequences dx : demux)
5694 31 Oct 19 nicklas 583     {
6056 13 Nov 20 nicklas 584       String rs = (String)Annotationtype.READ_STRING.getAnnotationValue(dc, dx.getItem());
6078 24 Nov 20 nicklas 585       if (rs != null) readStrings.add(readStringFormatter.format(rs));
5694 31 Oct 19 nicklas 586     }
6056 13 Nov 20 nicklas 587     return readStrings;
6056 13 Nov 20 nicklas 588   }
6056 13 Nov 20 nicklas 589
6449 20 Oct 21 nicklas 590   private LocalDateTime toDay(Date date)
6449 20 Oct 21 nicklas 591   {
6449 20 Oct 21 nicklas 592     if (date == null) return null;
6449 20 Oct 21 nicklas 593     // IMPORTANT! We must use Instant.ofEpochMilli() instead of Date.toInstant() 
6449 20 Oct 21 nicklas 594     // since java.sql.Date.toInstant() throw UnsupporterOperationException
6449 20 Oct 21 nicklas 595     LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
6449 20 Oct 21 nicklas 596     return dateTime.truncatedTo(ChronoUnit.DAYS);
6449 20 Oct 21 nicklas 597   }
6449 20 Oct 21 nicklas 598   
6449 20 Oct 21 nicklas 599   private String daysBetween(LocalDateTime d1, LocalDateTime d2)
6449 20 Oct 21 nicklas 600   {
6449 20 Oct 21 nicklas 601     return d1 == null || d2 == null ? "-" : Long.toString(ChronoUnit.DAYS.between(d1, d2));
6449 20 Oct 21 nicklas 602   }
6056 13 Nov 20 nicklas 603
6056 13 Nov 20 nicklas 604   /**
6056 13 Nov 20 nicklas 605     Formatter implementation for ReadString value that keep
6056 13 Nov 20 nicklas 606     the number of T's only. Example: 54T1S6B1S52T1S --> 54+52
6056 13 Nov 20 nicklas 607   */
6056 13 Nov 20 nicklas 608   public static class ReadStringFormatter
6056 13 Nov 20 nicklas 609     implements Formatter<String>
6056 13 Nov 20 nicklas 610   {
6056 13 Nov 20 nicklas 611     private final Pattern p;
6056 13 Nov 20 nicklas 612     public ReadStringFormatter()
5694 31 Oct 19 nicklas 613     {
6056 13 Nov 20 nicklas 614       this.p = Pattern.compile("(\\d+)T");
5694 31 Oct 19 nicklas 615     }
6056 13 Nov 20 nicklas 616
6056 13 Nov 20 nicklas 617     @Override
6056 13 Nov 20 nicklas 618     public String format(String readString) 
6056 13 Nov 20 nicklas 619     {
6056 13 Nov 20 nicklas 620       Matcher m = p.matcher(readString);
6056 13 Nov 20 nicklas 621       StringBuilder sb = new StringBuilder();
6056 13 Nov 20 nicklas 622       while (m.find())
6056 13 Nov 20 nicklas 623       {
6056 13 Nov 20 nicklas 624         if (sb.length() > 0) sb.append("+");
6056 13 Nov 20 nicklas 625         sb.append(m.group(1));
6056 13 Nov 20 nicklas 626       }
6056 13 Nov 20 nicklas 627       return sb.toString();
6056 13 Nov 20 nicklas 628     }
6056 13 Nov 20 nicklas 629
6056 13 Nov 20 nicklas 630     @Override
6056 13 Nov 20 nicklas 631     public String parseString(String s) 
6056 13 Nov 20 nicklas 632     {
6056 13 Nov 20 nicklas 633       throw new UnsupportedOperationException("parseString");
6056 13 Nov 20 nicklas 634     }
5694 31 Oct 19 nicklas 635   }
5647 08 Oct 19 nicklas 636
5694 31 Oct 19 nicklas 637   /**
6056 13 Nov 20 nicklas 638     Formatter for converting RoR values to intervals. RoR classes are
6056 13 Nov 20 nicklas 639     values with pattern cNNN where NNN is a numeric value typically an
6056 13 Nov 20 nicklas 640     even multiple of 5. The value should be combined with a range (positive or
6056 13 Nov 20 nicklas 641     negative).
6056 13 Nov 20 nicklas 642     Examples: c020-4 --> 16-20, c020+5 --> 21-25
5694 31 Oct 19 nicklas 643   */
6056 13 Nov 20 nicklas 644   public static class RoRFormatter
6056 13 Nov 20 nicklas 645     implements Formatter<String>
5694 31 Oct 19 nicklas 646   {
6056 13 Nov 20 nicklas 647     private final Pattern p;
6057 16 Nov 20 nicklas 648     private int r;
6056 13 Nov 20 nicklas 649     public RoRFormatter()
5694 31 Oct 19 nicklas 650     {
6056 13 Nov 20 nicklas 651       this.p = Pattern.compile("c(\\d+)([+-]\\d)?");
5694 31 Oct 19 nicklas 652     }
6056 13 Nov 20 nicklas 653   
5694 31 Oct 19 nicklas 654     @Override
6056 13 Nov 20 nicklas 655     public String format(String ror) 
5694 31 Oct 19 nicklas 656     {
6056 13 Nov 20 nicklas 657       Matcher m = p.matcher(ror);
6056 13 Nov 20 nicklas 658       if (m.matches())
6056 13 Nov 20 nicklas 659       {
6057 16 Nov 20 nicklas 660         r = Values.getInt(m.group(1));
6056 13 Nov 20 nicklas 661         int range = Values.getInt(m.group(2), -4);
6056 13 Nov 20 nicklas 662         if (range < 0)
6056 13 Nov 20 nicklas 663         {
6056 13 Nov 20 nicklas 664           ror = (r+range)+"-"+r;
6056 13 Nov 20 nicklas 665         }
6056 13 Nov 20 nicklas 666         else
6056 13 Nov 20 nicklas 667         {
6056 13 Nov 20 nicklas 668           ror = (r+1)+"-"+(r+range);
6056 13 Nov 20 nicklas 669         }
6056 13 Nov 20 nicklas 670       }
6056 13 Nov 20 nicklas 671       return ror;
5694 31 Oct 19 nicklas 672     }
5694 31 Oct 19 nicklas 673   
6056 13 Nov 20 nicklas 674     @Override
6056 13 Nov 20 nicklas 675     public String parseString(String s) 
5694 31 Oct 19 nicklas 676     {
6056 13 Nov 20 nicklas 677       throw new UnsupportedOperationException("parseString");
5694 31 Oct 19 nicklas 678     }
6057 16 Nov 20 nicklas 679     
6057 16 Nov 20 nicklas 680     /**
6057 16 Nov 20 nicklas 681       Get the last ROR value.
6057 16 Nov 20 nicklas 682     */
6057 16 Nov 20 nicklas 683     public int getRor()
6057 16 Nov 20 nicklas 684     {
6057 16 Nov 20 nicklas 685       return r;
6057 16 Nov 20 nicklas 686     }
6057 16 Nov 20 nicklas 687   }
5694 31 Oct 19 nicklas 688
5647 08 Oct 19 nicklas 689 }