diff --git a/mcq-cli/colorbars.R b/mcq-cli/colorbars.R new file mode 100755 index 00000000..d2df6b0b --- /dev/null +++ b/mcq-cli/colorbars.R @@ -0,0 +1,29 @@ +#! /usr/bin/env Rscript +library(lattice) +library(RColorBrewer) + +args = commandArgs(trailingOnly=TRUE) +if (length(args)==0) { + stop("At least one argument must be supplied (input file)\n") +} + +c <- read.csv(file=args[1], header=FALSE) +residues <- c[1,] +dotbrackets <- c[2,] +c <- c[3:nrow(c),1:ncol(c)] +models <- c[,1] +c <- c[1:nrow(c),2:ncol(c)] +c <- t(c) +m <- as.matrix(c) +mode(m) <- 'numeric' + +residues <- unlist(residues) +dotbrackets <- unlist(dotbrackets) +models <- unlist(models) + +m <- ifelse(m > 60, 60, m) +p <- colorRampPalette(brewer.pal(9, 'YlOrRd')) + +pdf('colorbars.pdf', width=11) +levelplot(m, col.regions=p, xlab='', ylab='', scales=list(x=list(at=0:nrow(m), labels=dotbrackets), y=list(at=1:ncol(m), labels=models))) +dev.off() diff --git a/mcq-cli/src/main/java/pl/poznan/put/mcq/cli/Global.java b/mcq-cli/src/main/java/pl/poznan/put/mcq/cli/Global.java index 15b7d5b9..c21a9aea 100644 --- a/mcq-cli/src/main/java/pl/poznan/put/mcq/cli/Global.java +++ b/mcq-cli/src/main/java/pl/poznan/put/mcq/cli/Global.java @@ -27,7 +27,7 @@ import pl.poznan.put.interfaces.Exportable; import pl.poznan.put.interfaces.Visualizable; import pl.poznan.put.matching.StructureSelection; -import pl.poznan.put.rna.torsion.RNATorsionAngleType; +import pl.poznan.put.torsion.MasterTorsionAngleType; import pl.poznan.put.types.DistanceMatrix; import pl.poznan.put.utility.ExecHelper; import pl.poznan.put.utility.svg.Format; @@ -50,7 +50,7 @@ public static void main(final String[] args) throws ParseException { final CommandLineParser parser = new DefaultParser(); final CommandLine commandLine = parser.parse(Global.OPTIONS, args); final List models = Helper.selectModels(commandLine); - final List angles = Helper.parseAngles(commandLine); + final List angles = Helper.parseAngles(commandLine); final MCQ mcq = new MCQ(angles); final long size = models.size(); @@ -94,7 +94,7 @@ public void complete(final GlobalMatrix matrix) { Listener.exportResults(directory, matrix); Listener.exportDrawing(directory, partitionalClustering); Listener.exportClustering(directory, partitionalClustering); - System.out.println("Results available in file: " + directory); + System.out.println("Results available in: " + directory); } catch (final IOException e) { System.err.println("Failed to store results"); e.printStackTrace(System.err); diff --git a/mcq-cli/src/main/java/pl/poznan/put/mcq/cli/Helper.java b/mcq-cli/src/main/java/pl/poznan/put/mcq/cli/Helper.java index ee0a81c9..0da8b882 100644 --- a/mcq-cli/src/main/java/pl/poznan/put/mcq/cli/Helper.java +++ b/mcq-cli/src/main/java/pl/poznan/put/mcq/cli/Helper.java @@ -27,6 +27,7 @@ import pl.poznan.put.pdb.analysis.PdbModel; import pl.poznan.put.rna.torsion.RNATorsionAngleType; import pl.poznan.put.structure.tertiary.StructureManager; +import pl.poznan.put.torsion.MasterTorsionAngleType; @Slf4j public final class Helper { @@ -186,9 +187,9 @@ private static String modelName(final File modelFile, final PdbModel model) { return StringUtils.isNotBlank(idCode) ? idCode : modelFile.getName(); } - public static List parseAngles(final CommandLine commandLine) { + public static List parseAngles(final CommandLine commandLine) { if (commandLine.hasOption(Helper.OPTION_ANGLES.getOpt())) { - final List angles = new ArrayList<>(); + final List angles = new ArrayList<>(); for (final String angleName : commandLine.getOptionValues(Helper.OPTION_ANGLES.getOpt())) { angles.add(RNATorsionAngleType.valueOf(angleName)); @@ -197,9 +198,8 @@ public static List parseAngles(final CommandLine commandLin return angles; } - return Arrays.stream(RNATorsionAngleType.mainAngles()) - .map(t -> (RNATorsionAngleType) t) - .collect(Collectors.toList()); + // do not use Arrays.asList because it creates unmodifiable list and this one is modified further + return Arrays.stream(RNATorsionAngleType.mainAngles()).collect(Collectors.toList()); } public static boolean isHelpRequested(final String[] args) throws ParseException { diff --git a/mcq-cli/src/main/java/pl/poznan/put/mcq/cli/Local.java b/mcq-cli/src/main/java/pl/poznan/put/mcq/cli/Local.java index fb2f97ed..d334e9e8 100644 --- a/mcq-cli/src/main/java/pl/poznan/put/mcq/cli/Local.java +++ b/mcq-cli/src/main/java/pl/poznan/put/mcq/cli/Local.java @@ -5,8 +5,9 @@ import java.io.IOException; import java.io.OutputStream; import java.util.List; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableModel; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; @@ -15,33 +16,36 @@ import org.apache.commons.io.FileUtils; import org.w3c.dom.svg.SVGDocument; import pl.poznan.put.comparison.MCQ; -import pl.poznan.put.comparison.local.MCQLocalResult; +import pl.poznan.put.comparison.exception.IncomparableStructuresException; +import pl.poznan.put.comparison.local.LocalComparator; +import pl.poznan.put.comparison.local.ModelsComparisonResult; +import pl.poznan.put.comparison.local.SelectedAngle; import pl.poznan.put.comparison.mapping.AngleDeltaMapper; +import pl.poznan.put.comparison.mapping.ComparisonMapper; +import pl.poznan.put.comparison.mapping.RangeDifferenceMapper; +import pl.poznan.put.interfaces.Exportable; import pl.poznan.put.matching.FragmentMatch; -import pl.poznan.put.matching.ResidueComparison; -import pl.poznan.put.matching.SelectionMatch; import pl.poznan.put.matching.StructureSelection; -import pl.poznan.put.pdb.PdbParsingException; +import pl.poznan.put.pdb.analysis.PdbCompactFragment; import pl.poznan.put.rna.torsion.RNATorsionAngleType; import pl.poznan.put.svg.SecondaryStructureVisualizer; -import pl.poznan.put.torsion.TorsionAngleDelta; +import pl.poznan.put.torsion.MasterTorsionAngleType; import pl.poznan.put.utility.ExecHelper; -import pl.poznan.put.utility.TabularExporter; import pl.poznan.put.utility.svg.Format; import pl.poznan.put.utility.svg.SVGHelper; -@SuppressWarnings({"UseOfSystemOutOrSystemErr", "MethodWithTooExceptionsDeclared"}) +@SuppressWarnings({"CallToSystemExit", "UseOfSystemOutOrSystemErr"}) public final class Local { private static final Options OPTIONS = new Options() .addOption(Helper.OPTION_TARGET) - .addOption(Helper.OPTION_MODEL) + .addOption(Helper.OPTION_MODELS) .addOption(Helper.OPTION_SELECTION_TARGET) .addOption(Helper.OPTION_SELECTION_MODEL) .addOption(Helper.OPTION_ANGLES); public static void main(final String[] args) - throws ParseException, IOException, PdbParsingException { + throws ParseException, IncomparableStructuresException { if (Helper.isHelpRequested(args)) { Helper.printHelp("local", Local.OPTIONS); return; @@ -49,71 +53,115 @@ public static void main(final String[] args) final CommandLineParser parser = new DefaultParser(); final CommandLine commandLine = parser.parse(Local.OPTIONS, args); - final StructureSelection s1 = Helper.selectTarget(commandLine); - final StructureSelection s2 = Helper.selectModel(commandLine); + final StructureSelection target = Helper.selectTarget(commandLine); + final List models = Helper.selectModels(commandLine); + + // check for gaps + Stream.concat(Stream.of(target), models.stream()) + .parallel() + .filter(selection -> selection.getCompactFragments().size() != 1) + .peek( + selection -> + System.err.println("The following structure contains gaps: " + selection.getName())) + .findAny() + .ifPresent(selection -> System.exit(1)); + + // check for size + final int expectedSize = target.getResidues().size(); + models + .parallelStream() + .filter(selection -> selection.getResidues().size() != expectedSize) + .peek( + selection -> + System.err.printf( + "The following structure has different size (%d) than the target (%d)%n", + selection.getResidues().size(), expectedSize)) + .findAny() + .ifPresent(selection -> System.exit(1)); + + final PdbCompactFragment targetFragment = target.getCompactFragments().get(0); + final List modelFragments = + models + .stream() + .map(selection -> selection.getCompactFragments().get(0)) + .collect(Collectors.toList()); + + // rename + targetFragment.setName(target.getName()); + IntStream.range(0, models.size()) + .forEach(i -> modelFragments.get(i).setName(models.get(i).getName())); + + final List angleTypes = Helper.parseAngles(commandLine); + angleTypes.add(RNATorsionAngleType.getAverageOverMainAngles()); + + final LocalComparator mcq = new MCQ(angleTypes); + final ModelsComparisonResult comparisonResult = + mcq.compareModels(targetFragment, modelFragments); + final File directory = Local.exportResults(comparisonResult); + System.out.println("Results are available in: " + directory); + } - final List angleTypes = Helper.parseAngles(commandLine); - final MCQ mcq = new MCQ(angleTypes); - final MCQLocalResult result = (MCQLocalResult) mcq.comparePair(s1, s2); + private static File exportResults(final ModelsComparisonResult comparisonResult) { + try { + final File directory = ExecHelper.createRandomDirectory(); + Local.exportTable(directory, comparisonResult); + comparisonResult + .getFragmentMatches() + .parallelStream() + .forEach(fragmentMatch -> Local.exportModelResults(directory, fragmentMatch)); + return directory; + } catch (final IOException e) { + throw new IllegalArgumentException("Failed to export results", e); + } + } - final File directory = ExecHelper.createRandomDirectory(); - final File csvFile = new File(directory, "results.csv"); + private static void exportModelResults( + final File parentDirectory, final FragmentMatch fragmentMatch) { + try { + final String name = fragmentMatch.getModelFragment().getName(); + final File directory = new File(parentDirectory, name); + FileUtils.forceMkdir(directory); + + Local.exportSecondaryStructureImage( + fragmentMatch, directory, "delta.svg", AngleDeltaMapper.getInstance()); + Local.exportSecondaryStructureImage( + fragmentMatch, directory, "range.svg", RangeDifferenceMapper.getInstance()); + Local.exportDifferences(fragmentMatch, directory); + } catch (final IOException e) { + throw new IllegalArgumentException("Failed to export results", e); + } + } - try (final OutputStream stream = new FileOutputStream(csvFile)) { - result.export(stream); + private static void exportDifferences(final Exportable fragmentMatch, final File directory) + throws IOException { + final File file = new File(directory, "differences.csv"); + try (final OutputStream stream = new FileOutputStream(file)) { + fragmentMatch.export(stream); } + } - final SelectionMatch selectionMatch = result.getSelectionMatch(); - final List fragmentMatches = selectionMatch.getFragmentMatches(); - - for (int i = 0, fragmentMatchesSize = fragmentMatches.size(); i < fragmentMatchesSize; i++) { - final FragmentMatch fragmentMatch = fragmentMatches.get(i); - - // FIXME: creating charts is now a domain of mcq4structures - // final File chartFile = new File(directory, String.format("chart-%02d.svg", i)); - // final SVGDocument chartSvg = fragmentMatch.visualize(1052, 744); - // FileUtils.writeByteArrayToFile(chartFile, SVGHelper.export(chartSvg, Format.SVG)); - - final File secondaryFile = new File(directory, String.format("secondary-%02d.svg", i)); - final SVGDocument secondarySvg = - SecondaryStructureVisualizer.visualize(fragmentMatch, AngleDeltaMapper.getInstance()); - FileUtils.writeByteArrayToFile(secondaryFile, SVGHelper.export(secondarySvg, Format.SVG)); - - for (final RNATorsionAngleType angleType : angleTypes) { - final File angleRangesFile = - new File(directory, String.format("angle-%s-%02d.csv", angleType.getExportName(), i)); - final List residueComparisons = fragmentMatch.getResidueComparisons(); - final Object[][] data = new Object[fragmentMatch.getResidueCount()][]; - - for (int j = 0, size = residueComparisons.size(); j < size; j++) { - final ResidueComparison residueComparison = residueComparisons.get(j); - final TorsionAngleDelta angleDelta = residueComparison.getAngleDelta(angleType); - - data[j] = new Object[8]; - data[j][0] = residueComparison.getTarget(); - data[j][1] = angleDelta.getTarget().getDegrees(); - data[j][2] = angleType.getRange(angleDelta.getTarget()).getDisplayName(); - data[j][3] = residueComparison.getModel(); - data[j][4] = angleDelta.getModel().getDegrees(); - data[j][5] = angleType.getRange(angleDelta.getModel()).getDisplayName(); - data[j][6] = angleDelta.getDelta().getDegrees(); - data[j][7] = angleDelta.getRangeDifference(); - } - - final String[] columns = { - "Target Residue", "Target Value", "Target Range", - "Model Residue", "Model Value", "Model Range", - "Value Difference", "Range Difference" - }; - final TableModel tableModel = new DefaultTableModel(data, columns); - - try (final OutputStream stream = new FileOutputStream(angleRangesFile)) { - TabularExporter.export(tableModel, stream); - } - } + private static void exportSecondaryStructureImage( + final FragmentMatch fragmentMatch, + final File directory, + final String filename, + final ComparisonMapper deltaMapper) + throws IOException { + final SVGDocument svg = SecondaryStructureVisualizer.visualize(fragmentMatch, deltaMapper); + final File file = new File(directory, filename); + + try (final OutputStream stream = new FileOutputStream(file)) { + stream.write(SVGHelper.export(svg, Format.SVG)); } + } - System.out.println("Results are available in: " + directory); + private static void exportTable( + final File directory, final ModelsComparisonResult comparisonResult) throws IOException { + final File file = new File(directory, "table.csv"); + try (final OutputStream stream = new FileOutputStream(file)) { + final SelectedAngle selectedAngle = + comparisonResult.selectAngle(RNATorsionAngleType.getAverageOverMainAngles()); + selectedAngle.export(stream); + } } private Local() { diff --git a/mcq-core/src/main/java/pl/poznan/put/comparison/MCQ.java b/mcq-core/src/main/java/pl/poznan/put/comparison/MCQ.java index d9fc3091..87376565 100644 --- a/mcq-core/src/main/java/pl/poznan/put/comparison/MCQ.java +++ b/mcq-core/src/main/java/pl/poznan/put/comparison/MCQ.java @@ -180,7 +180,7 @@ public final ModelsComparisonResult compareModels( if ((fragment.getMoleculeType() != target.getMoleculeType()) || (fragment.getResidues().size() != target.getResidues().size())) { throw new IncomparableStructuresException( - "All models must be of the same type and size as the " + "reference structure"); + "All models must be of the same type and size as the reference structure"); } } diff --git a/mcq-core/src/main/java/pl/poznan/put/comparison/local/SelectedAngle.java b/mcq-core/src/main/java/pl/poznan/put/comparison/local/SelectedAngle.java index 1df176c0..f0ed3cc7 100644 --- a/mcq-core/src/main/java/pl/poznan/put/comparison/local/SelectedAngle.java +++ b/mcq-core/src/main/java/pl/poznan/put/comparison/local/SelectedAngle.java @@ -3,14 +3,14 @@ import java.io.File; import java.io.IOException; import java.io.OutputStream; -import java.nio.charset.Charset; -import java.text.SimpleDateFormat; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Date; import java.util.List; import javax.swing.table.TableModel; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.tuple.Pair; import org.jumpmind.symmetric.csv.CsvWriter; import pl.poznan.put.interfaces.Exportable; @@ -20,6 +20,13 @@ import pl.poznan.put.matching.ResidueComparison; import pl.poznan.put.pdb.analysis.PdbCompactFragment; import pl.poznan.put.pdb.analysis.PdbResidue; +import pl.poznan.put.structure.secondary.CanonicalStructureExtractor; +import pl.poznan.put.structure.secondary.formats.BpSeq; +import pl.poznan.put.structure.secondary.formats.Converter; +import pl.poznan.put.structure.secondary.formats.DotBracket; +import pl.poznan.put.structure.secondary.formats.InvalidStructureException; +import pl.poznan.put.structure.secondary.formats.LevelByLevelConverter; +import pl.poznan.put.structure.secondary.pseudoknots.elimination.MinGain; import pl.poznan.put.torsion.MasterTorsionAngleType; import pl.poznan.put.torsion.TorsionAngleDelta; import pl.poznan.put.utility.NonEditableDefaultTableModel; @@ -33,29 +40,44 @@ public class SelectedAngle implements Exportable, Tabular, MatchCollection { private final List fragmentMatches; @Override - public List getFragmentMatches() { + public final List getFragmentMatches() { return Collections.unmodifiableList(fragmentMatches); } @Override - public void export(final OutputStream stream) throws IOException { - final CsvWriter csvWriter = new CsvWriter(stream, ',', Charset.forName("UTF-8")); - csvWriter.write(null); + public final void export(final OutputStream stream) throws IOException { + final CsvWriter csvWriter = new CsvWriter(stream, ',', StandardCharsets.UTF_8); - for (final PdbCompactFragment model : models) { - csvWriter.write(model.toString()); + // first row + csvWriter.write(null); + for (final PdbResidue residue : target.getResidues()) { + csvWriter.write(residue.toString()); } - csvWriter.endRecord(); - for (int i = 0; i < target.getResidues().size(); i++) { - final PdbResidue residue = target.getResidues().get(i); - csvWriter.write(residue.toString()); + try { + final BpSeq bpSeq = CanonicalStructureExtractor.getCanonicalSecondaryStructure(target); + final Converter converter = new LevelByLevelConverter(new MinGain(), 1); + final DotBracket dotBracket = converter.convert(bpSeq); - for (int j = 0; j < models.size(); j++) { - final FragmentMatch fragmentMatch = fragmentMatches.get(j); - final ResidueComparison residueComparison = fragmentMatch.getResidueComparisons().get(i); - final TorsionAngleDelta delta = residueComparison.getAngleDelta(angleType); + // second row + csvWriter.write(null); + for (final char c : dotBracket.getStructure().toCharArray()) { + csvWriter.write(Character.toString(c)); + } + csvWriter.endRecord(); + } catch (final InvalidStructureException e) { + SelectedAngle.log.warn("Failed to extract secondary structure", e); + } + + for (int i = 0; i < models.size(); i++) { + final PdbCompactFragment model = models.get(i); + final FragmentMatch match = fragmentMatches.get(i); + csvWriter.write(model.getName()); + + for (int j = 0; j < target.getResidues().size(); j++) { + final ResidueComparison comparison = match.getResidueComparisons().get(j); + final TorsionAngleDelta delta = comparison.getAngleDelta(angleType); csvWriter.write(delta.toExportString()); } @@ -66,9 +88,9 @@ public void export(final OutputStream stream) throws IOException { } @Override - public File suggestName() { - final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm"); - final StringBuilder builder = new StringBuilder(sdf.format(new Date())); + public final File suggestName() { + final StringBuilder builder = + new StringBuilder(DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.format(new Date())); builder.append("-Local-Distance-Multi"); for (final PdbCompactFragment model : models) { @@ -81,12 +103,12 @@ public File suggestName() { } @Override - public TableModel asExportableTableModel() { + public final TableModel asExportableTableModel() { return asTableModel(false); } @Override - public TableModel asDisplayableTableModel() { + public final TableModel asDisplayableTableModel() { return asTableModel(true); } @@ -120,7 +142,7 @@ private TableModel asTableModel(final boolean isDisplay) { return new NonEditableDefaultTableModel(data, columnNames); } - public Pair getMinMax() { + public final Pair getMinMax() { double min = Double.POSITIVE_INFINITY; double max = Double.NEGATIVE_INFINITY; diff --git a/mcq-core/src/main/java/pl/poznan/put/matching/FragmentMatch.java b/mcq-core/src/main/java/pl/poznan/put/matching/FragmentMatch.java index 7f61cd5a..df7fc6e1 100644 --- a/mcq-core/src/main/java/pl/poznan/put/matching/FragmentMatch.java +++ b/mcq-core/src/main/java/pl/poznan/put/matching/FragmentMatch.java @@ -1,12 +1,20 @@ package pl.poznan.put.matching; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; +import javax.swing.table.TableModel; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.DateFormatUtils; import pl.poznan.put.circular.Angle; +import pl.poznan.put.interfaces.Exportable; +import pl.poznan.put.interfaces.Tabular; import pl.poznan.put.pdb.analysis.MoleculeType; import pl.poznan.put.pdb.analysis.PdbCompactFragment; import pl.poznan.put.pdb.analysis.PdbResidue; @@ -19,10 +27,13 @@ import pl.poznan.put.structure.secondary.formats.LevelByLevelConverter; import pl.poznan.put.structure.secondary.pseudoknots.elimination.MinGain; import pl.poznan.put.torsion.MasterTorsionAngleType; +import pl.poznan.put.torsion.TorsionAngleDelta; +import pl.poznan.put.utility.NonEditableDefaultTableModel; +import pl.poznan.put.utility.TabularExporter; @Data @Slf4j -public class FragmentMatch { +public class FragmentMatch implements Exportable, Tabular { private final PdbCompactFragment targetFragment; private final PdbCompactFragment modelFragment; private final boolean isTargetSmaller; @@ -147,4 +158,60 @@ public final List generateLabelsWithResidueNames() { } return result; } + + @Override + public final File suggestName() { + return new File( + DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.format(new Date()) + "-fragment.csv"); + } + + @Override + public final void export(final OutputStream stream) throws IOException { + TabularExporter.export(asExportableTableModel(), stream); + } + + @Override + public final TableModel asExportableTableModel() { + return asTableModel(false); + } + + @Override + public final TableModel asDisplayableTableModel() { + return asTableModel(true); + } + + @SuppressWarnings("AssignmentToNull") + private TableModel asTableModel(final boolean isDisplay) { + final List angleTypes = fragmentComparison.getAngleTypes(); + final int size = angleTypes.size(); + + final String[] columnNames = new String[(size * 2) + 1]; + columnNames[0] = isDisplay ? "" : null; + + for (int i = 0; i < size; i++) { + final MasterTorsionAngleType angle = angleTypes.get(i); + final String angleName = isDisplay ? angle.getLongDisplayName() : angle.getExportName(); + columnNames[i + 1] = angleName; + columnNames[size + i + 1] = angleName; + } + + final List residueComparisons = fragmentComparison.getResidueComparisons(); + final String[][] data = new String[residueComparisons.size()][]; + + for (int i = 0; i < residueComparisons.size(); i++) { + final ResidueComparison residueComparison = residueComparisons.get(i); + data[i] = new String[(size * 2) + 1]; + data[i][0] = + String.format("%s / %s", residueComparison.getTarget(), residueComparison.getModel()); + + for (int j = 0; j < size; j++) { + final MasterTorsionAngleType angle = angleTypes.get(j); + final TorsionAngleDelta delta = residueComparison.getAngleDelta(angle); + data[i][j + 1] = delta.toString(isDisplay); + data[i][size + j + 1] = delta.getRangeDifference().toString(); + } + } + + return new NonEditableDefaultTableModel(data, columnNames); + } }