Skip to content

Commit

Permalink
Improve markdown parsing
Browse files Browse the repository at this point in the history
Use commonmark extension to remove yaml frontmatter, and insert it at the end of the doc.
Also add name and description (if available) as headings.
(This will require the latest QuPath v0.5.0 snapshot to add the yaml support)
  • Loading branch information
petebankhead committed Nov 8, 2023
1 parent a71132b commit 92d46ba
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 19 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ dependencies {
// Main QuPath user interface jar.
// Automatically includes other QuPath jars as subdependencies.
implementation "io.github.qupath:qupath-gui-fx:${qupathVersion}"
implementation 'io.github.qupath:qupath-fxtras:0.1.0'
implementation 'org.commonmark:commonmark:0.21.0'
implementation libs.qupath.fxtras
implementation libs.bundles.markdown

// For logging - the version comes from QuPath's version catalog at
// https://github.com/qupath/qupath/blob/main/gradle/libs.versions.toml
Expand Down
24 changes: 22 additions & 2 deletions src/main/java/qupath/ext/wsinfer/models/WSInferModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ public String getName() {
return hfRepoId;
}

/**
* Get a description, if available (may be null).
* @return
*/
public String getDescription() {
return description;
}

/**
* Get the configuration. Note that this may be null.
* @return the model configuration, or null.
Expand All @@ -69,8 +77,20 @@ public WSInferModelConfiguration getConfiguration() {
* Remove the cached model files.
*/
public synchronized void removeCache() {
getTorchScriptFile().delete();
getConfigFile().delete();
removeIfFound(getTorchScriptFile(), getConfigFile(), getReadMeFile());
}

private static void removeIfFound(File... files) {
for (var file : files) {
if (file != null && file.isFile()) {
try {
logger.debug("Deleting file {}", file);
file.delete();
} catch (Exception e) {
logger.error("Unable to delete file {}", file, e);
}
}
}
}

/**
Expand Down
72 changes: 62 additions & 10 deletions src/main/java/qupath/ext/wsinfer/ui/WSInferController.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import org.commonmark.ext.front.matter.YamlFrontMatterExtension;
import org.commonmark.ext.front.matter.YamlFrontMatterVisitor;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.controlsfx.control.PopOver;
Expand Down Expand Up @@ -75,7 +77,7 @@
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.ResourceBundle;
Expand Down Expand Up @@ -127,8 +129,9 @@ public class WSInferController {
private TextField tfModelDirectory;
@FXML
private TextField localModelDirectory;
@FXML
private PopOver infoPopover;

private WebView infoWebView = WebViews.create(true);
private PopOver infoPopover = new PopOver(infoWebView);

private final static ResourceBundle resources = ResourceBundle.getBundle("qupath.ext.wsinfer.ui.strings");

Expand Down Expand Up @@ -187,11 +190,15 @@ private void configureModelChoices() {
modelChoiceBox.getSelectionModel().selectedItemProperty().addListener(
(v, o, n) -> {
downloadButton.setDisable((n == null) || n.isValid());
infoButton.setDisable((n == null) || (!n.isValid()) || n instanceof WSInferModelLocal);
infoButton.setDisable((n == null) || (!n.isValid()) || !checkFileExists(n.getReadMeFile()));
infoPopover.hide();
});
}

private static boolean checkFileExists(File file) {
return file != null && file.isFile();
}

private void configureAvailableDevices() {
var available = PytorchManager.getAvailableDevices();
deviceChoices.getItems().setAll(available);
Expand Down Expand Up @@ -393,14 +400,59 @@ public void showInfo() throws IOException {
return;
}
WSInferModel model = modelChoiceBox.getSelectionModel().getSelectedItem();
Path mdFile = model.getReadMeFile().toPath();
var doc = Parser.builder().build().parse(Files.readString(mdFile));
WebView webView = WebViews.create(true);
webView.getEngine().loadContent(HtmlRenderer.builder().build().render(doc));
infoPopover.setContentNode(webView);
infoPopover.show(infoButton);
var file = model.getReadMeFile();
if (!checkFileExists(file)) {
logger.warn("ReadMe file not available: {}", file);
return;
}
try {
var markdown = Files.readString(file.toPath());

// Parse the initial markdown only, to extract any YAML front matter
var parser = Parser.builder()
.extensions(
Arrays.asList(YamlFrontMatterExtension.create())
).build();
var doc = parser.parse(markdown);
var visitor = new YamlFrontMatterVisitor();
doc.accept(visitor);
var metadata = visitor.getData();

// If we have YAML metadata, remove from the start (since it renders weirdly) and append at the end
if (!metadata.isEmpty()) {
doc.getFirstChild().unlink();
var sb = new StringBuilder();
sb.append("----\n\n");
sb.append("### Metadata\n\n");
for (var entry : metadata.entrySet()) {
sb.append("\n* **").append(entry.getKey()).append("**: ").append(entry.getValue());
}
doc.appendChild(parser.parse(sb.toString()));
}

// If the markdown doesn't start with a title, pre-pending the model title & description (if available)
if (!markdown.startsWith("#")) {
var sb = new StringBuilder();
sb.append("## ").append(model.getName()).append("\n\n");
var description = model.getDescription();
if (description != null && !description.isEmpty()) {
sb.append("_").append(description).append("_").append("\n\n");
}
sb.append("----\n\n");
doc.prependChild(parser.parse(sb.toString()));
}

infoWebView.getEngine().loadContent(
HtmlRenderer.builder().build().render(doc));
infoPopover.show(infoButton);
} catch (IOException e) {
logger.error("Error parsing readme file", e);
}
}




@FXML
private void selectAllAnnotations() {
Commands.selectObjectsByClass(imageDataProperty.get(), PathAnnotationObject.class);
Expand Down
5 changes: 0 additions & 5 deletions src/main/resources/qupath/ext/wsinfer/ui/wsinfer_control.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
<?import org.controlsfx.control.SearchableComboBox?>
<?import org.controlsfx.control.SegmentedButton?>

<?import org.controlsfx.control.PopOver?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="330" stylesheets="@wsinferstyles.css" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="qupath.ext.wsinfer.ui.WSInferController">
<!-- Processing Pane************************************************************-->
<TitledPane fx:id="pane1" animated="false" collapsible="false" text="%ui.processing.pane" VBox.vgrow="NEVER">
Expand Down Expand Up @@ -50,10 +49,6 @@
<Text styleClass="fa-icon" text="" />
</graphic>
</Button>
<fx:define>
<PopOver fx:id="infoPopover">
</PopOver>
</fx:define>
</children>
<styleClass>
<String fx:value="standard-spacing" />
Expand Down

0 comments on commit 92d46ba

Please sign in to comment.