Skip to content

Commit 670424e

Browse files
stevenweaverclaude
andcommitted
Add HIVAnnotate feature for network annotation
- Add annotation features to the pipeline with optional activation - Integrate hivannotate_rs_web for node attribute annotation - Add UI for toggling annotation and uploading custom attributes/schema - Include default attributes and schema files in example data - Enhance download options to include annotated network results - Update readme to reflect new annotation feature 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
1 parent a486d6b commit 670424e

4 files changed

Lines changed: 221 additions & 8 deletions

File tree

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
# HIV-TRACE WASM
22
A WebAssembly implementation of the HIV-TRACE pipeline.
33

4-
Uses [cawlign](https://github.com/veg/cawlign/), [tn93](https://github.com/veg/tn93), and [hivnetworkcsv](https://github.com/veg/hivclustering). Implemented using [Biowasm](https://biowasm.com/), [Pyodide](https://pyodide.org/), and [Emscripten](https://emscripten.org/).
4+
Uses [cawlign](https://github.com/veg/cawlign/), [tn93](https://github.com/veg/tn93), [hivcluster-rs](https://github.com/veg/hivcluster-rs), and [hivannotate-rs](https://github.com/veg/hivannotate-rs). Implemented using [Biowasm](https://biowasm.com/) and [WebAssembly](https://webassembly.org/).
5+
6+
## Features
7+
8+
- **HIV-TRACE Pipeline**: Complete end-to-end sequence analysis in the browser
9+
- **Reference Selection**: Choose from multiple references or upload custom reference sequences
10+
- **Network Annotation**: Annotate network nodes with patient attributes and metadata
11+
- **Customizable Parameters**: Adjust distance threshold, overlap, and other pipeline parameters
12+
- **Downloadable Results**: Get results in JSON, FASTA, and CSV formats for further analysis

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"bootstrap": "^5.3.3",
1414
"bootstrap-icons": "^1.11.3",
1515
"hivcluster_rs_web": "^0.1.0",
16+
"hivannotate_rs_web": "^0.1.0",
1617
"react": "^19.0.0",
1718
"react-dom": "^19.0.0"
1819
},

src/App.jsx

Lines changed: 209 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import React, { Component, Fragment, useEffect, useState } from "react";
22
import init, { build_network } from "hivcluster_rs_web";
3+
import hivannotateInit, { annotate_network_json } from "hivannotate_rs_web";
34

45
// Fallback initialization and methods in case npm package fails
56
let hivclusterInit = init;
67
let hivclusterBuildNetwork = build_network;
8+
let hivannotateAnnotateNetwork = annotate_network_json;
79

810
import {
11+
ATTRIBUTES_PATH,
912
AVAILABLE_REFERENCES,
1013
CAWLIGN_TEST_DATA_PATH,
1114
CAWLIGN_VERSION,
1215
CLEAR_LOG,
1316
GET_TIME_WITH_MILLISECONDS,
1417
HIVCLUSTER_RS_VERSION,
1518
OUTPUT_ID,
19+
SCHEMA_PATH,
1620
SEATTLE_FASTA_PATH,
1721
TN93_VERSION,
1822
} from "./constants";
@@ -42,13 +46,59 @@ export class App extends Component {
4246
inputData: null,
4347
alignmentData: null,
4448
pairwiseDistances: null,
49+
annotatedNetworkData: null,
50+
attributesData: null,
51+
schemaData: null,
52+
useAnnotation: false,
53+
customAttributesFile: undefined,
54+
customSchemaFile: undefined,
4555
};
4656
}
4757

4858
componentDidMount() {
4959
this.initHivclusterRS();
60+
this.initHivannotate();
5061
this.initBiowasm();
5162
}
63+
64+
initHivannotate = async () => {
65+
try {
66+
this.log("Initializing HIVAnnotate WASM...");
67+
await hivannotateInit();
68+
this.log("HIVAnnotate WASM initialized successfully.");
69+
70+
// Load default attributes and schema
71+
this.loadDefaultAnnotationData();
72+
} catch (error) {
73+
this.log(`Error initializing HIVAnnotate WASM: ${error.message || error}`);
74+
console.error("HIVAnnotate init error:", error);
75+
}
76+
}
77+
78+
loadDefaultAnnotationData = async () => {
79+
try {
80+
// Fetch default attributes and schema
81+
const attributesResponse = await fetch(`${import.meta.env.BASE_URL || ""}${ATTRIBUTES_PATH}`);
82+
const schemaResponse = await fetch(`${import.meta.env.BASE_URL || ""}${SCHEMA_PATH}`);
83+
84+
if (!attributesResponse.ok || !schemaResponse.ok) {
85+
throw new Error("Failed to fetch annotation data");
86+
}
87+
88+
const attributesData = await attributesResponse.json();
89+
const schemaData = await schemaResponse.json();
90+
91+
this.setState({
92+
attributesData: JSON.stringify(attributesData),
93+
schemaData: JSON.stringify(schemaData)
94+
});
95+
96+
this.log("Default annotation data loaded successfully");
97+
} catch (error) {
98+
this.log(`Error loading annotation data: ${error.message || error}`);
99+
console.error("Annotation data loading error:", error);
100+
}
101+
}
52102

53103
setInputFile = (event) => {
54104
this.setState({ inputFile: event.target.files[0] });
@@ -87,6 +137,24 @@ export class App extends Component {
87137
this.log(`Custom reference file selected: ${event.target.files[0].name}`);
88138
}
89139
};
140+
141+
toggleAnnotation = (event) => {
142+
this.setState({ useAnnotation: event.target.checked });
143+
};
144+
145+
setCustomAttributesFile = (event) => {
146+
if (event.target.files && event.target.files[0]) {
147+
this.setState({ customAttributesFile: event.target.files[0] });
148+
this.log(`Custom attributes file selected: ${event.target.files[0].name}`);
149+
}
150+
};
151+
152+
setCustomSchemaFile = (event) => {
153+
if (event.target.files && event.target.files[0]) {
154+
this.setState({ customSchemaFile: event.target.files[0] });
155+
this.log(`Custom schema file selected: ${event.target.files[0].name}`);
156+
}
157+
};
90158

91159
initHivclusterRS = async () => {
92160
try {
@@ -267,7 +335,10 @@ export class App extends Component {
267335
ambiguityFraction: 0.015,
268336
removeDrams: "no",
269337
reference: "HXB2_pol", // Default reference for the example data
270-
customReferenceFile: undefined
338+
customReferenceFile: undefined,
339+
useAnnotation: true, // Enable annotation for example data
340+
customAttributesFile: undefined,
341+
customSchemaFile: undefined
271342
}, () => {
272343
this.log("Example data and parameters loaded successfully.");
273344
// Display the file name in the UI
@@ -586,6 +657,12 @@ export class App extends Component {
586657
}
587658

588659
this.log("HIVCluster-RS processing completed successfully");
660+
661+
// Run annotation if enabled
662+
if (this.state.useAnnotation) {
663+
await this.runAnnotation(jsonOutput);
664+
}
665+
589666
console.log("Network data:", networkData);
590667
} catch (jsonError) {
591668
this.log(`Error parsing network JSON: ${jsonError.message}`);
@@ -605,6 +682,72 @@ export class App extends Component {
605682
}
606683
};
607684

685+
runAnnotation = async (networkJson) => {
686+
this.log("Running HIVAnnotate on network data...");
687+
688+
try {
689+
// Get attributes and schema data - either from custom upload or default
690+
let attributesJson, schemaJson;
691+
692+
if (this.state.customAttributesFile) {
693+
// Read from custom file
694+
this.log("Reading custom attributes file...");
695+
attributesJson = await this.state.customAttributesFile.text();
696+
} else if (this.state.attributesData) {
697+
// Use pre-loaded data
698+
attributesJson = this.state.attributesData;
699+
this.log("Using default attributes data");
700+
} else {
701+
throw new Error("No attributes data available");
702+
}
703+
704+
if (this.state.customSchemaFile) {
705+
// Read from custom file
706+
this.log("Reading custom schema file...");
707+
schemaJson = await this.state.customSchemaFile.text();
708+
} else if (this.state.schemaData) {
709+
// Use pre-loaded data
710+
schemaJson = this.state.schemaData;
711+
this.log("Using default schema data");
712+
} else {
713+
throw new Error("No schema data available");
714+
}
715+
716+
// Run annotation
717+
this.log("Annotating network...");
718+
const annotatedJson = hivannotateAnnotateNetwork(networkJson, attributesJson, schemaJson);
719+
720+
if (!annotatedJson) {
721+
throw new Error("Annotation failed - no output returned");
722+
}
723+
724+
// Store annotated data
725+
this.setState({ annotatedNetworkData: annotatedJson });
726+
727+
// Parse to get stats
728+
const annotatedData = JSON.parse(annotatedJson);
729+
const hasTraceResults = annotatedData.trace_results !== undefined;
730+
const rootObj = hasTraceResults ? annotatedData.trace_results : annotatedData;
731+
732+
// Count nodes with annotations
733+
if (rootObj.Nodes) {
734+
const totalNodes = rootObj.Nodes.length || 0;
735+
const nodesWithAttributes = rootObj.Nodes.filter(n =>
736+
n.patient_attributes !== undefined).length || 0;
737+
738+
this.log(`Annotation complete: ${nodesWithAttributes} of ${totalNodes} nodes annotated (${Math.round(nodesWithAttributes/totalNodes*100)}%)`);
739+
}
740+
741+
this.log("HIVAnnotate processing completed successfully");
742+
return annotatedJson;
743+
744+
} catch (error) {
745+
this.log(`Error in annotation process: ${error.message || error}`);
746+
console.error("Annotation error:", error);
747+
return null;
748+
}
749+
};
750+
608751
downloadData = (filename, content) => {
609752
const blob = new Blob([content], { type: 'text/plain' });
610753
const url = URL.createObjectURL(blob);
@@ -791,6 +934,56 @@ export class App extends Component {
791934
Upload a custom reference sequence file for alignment. This will override the selection above.
792935
</small>
793936
</div>
937+
938+
<hr className="mt-4 mb-4" />
939+
940+
<div className="mb-3">
941+
<div className="form-check form-switch">
942+
<input
943+
className="form-check-input"
944+
type="checkbox"
945+
id="use-annotation"
946+
checked={this.state.useAnnotation}
947+
onChange={this.toggleAnnotation}
948+
/>
949+
<label className="form-check-label" htmlFor="use-annotation">
950+
Enable Network Annotation
951+
</label>
952+
</div>
953+
<small className="text-muted d-block mt-1">
954+
Annotate network nodes with additional metadata from attributes file
955+
</small>
956+
</div>
957+
958+
{this.state.useAnnotation && (
959+
<div className="annotation-options">
960+
<div className="mb-3">
961+
<label htmlFor="custom-attributes" className="form-label">Custom Attributes JSON (Optional)</label>
962+
<input
963+
className="form-control"
964+
type="file"
965+
id="custom-attributes"
966+
onChange={this.setCustomAttributesFile}
967+
/>
968+
<small className="text-muted d-block mt-1">
969+
Upload a custom attributes file or use the default example data
970+
</small>
971+
</div>
972+
973+
<div className="mb-3">
974+
<label htmlFor="custom-schema" className="form-label">Custom Schema JSON (Optional)</label>
975+
<input
976+
className="form-control"
977+
type="file"
978+
id="custom-schema"
979+
onChange={this.setCustomSchemaFile}
980+
/>
981+
<small className="text-muted d-block mt-1">
982+
Upload a custom schema file or use the default example data
983+
</small>
984+
</div>
985+
</div>
986+
)}
794987
</div>
795988
<button
796989
className="btn btn-warning mt-4 w-100"
@@ -852,12 +1045,21 @@ export class App extends Component {
8521045
<div className="downloads mt-4">
8531046
<h4>Download Results</h4>
8541047
<div className="download-buttons">
855-
<button
856-
className="btn btn-success me-2"
857-
onClick={() => this.downloadData('network_results.json', JSON.stringify(this.state.networkData, null, 2))}
858-
>
859-
<i className="bi bi-download me-2"></i>Network Results (JSON)
860-
</button>
1048+
{this.state.annotatedNetworkData ? (
1049+
<button
1050+
className="btn btn-success me-2"
1051+
onClick={() => this.downloadData('annotated_network_results.json', this.state.annotatedNetworkData)}
1052+
>
1053+
<i className="bi bi-download me-2"></i>Annotated Network Results (JSON)
1054+
</button>
1055+
) : (
1056+
<button
1057+
className="btn btn-success me-2"
1058+
onClick={() => this.downloadData('network_results.json', JSON.stringify(this.state.networkData, null, 2))}
1059+
>
1060+
<i className="bi bi-download me-2"></i>Network Results (JSON)
1061+
</button>
1062+
)}
8611063

8621064
{this.state.inputData && (
8631065
<button

src/constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export const GET_TIME_WITH_MILLISECONDS = (date) => {
2222

2323
export const CAWLIGN_TEST_DATA_PATH = 'data/HIV1-pol-326-modified.fa';
2424
export const SEATTLE_FASTA_PATH = 'data/seattle.fasta';
25+
export const ATTRIBUTES_PATH = 'data/annotation/attributes.json';
26+
export const SCHEMA_PATH = 'data/annotation/schema.json';
2527

2628
// List of available references from data/references/ directory
2729
export const AVAILABLE_REFERENCES = [

0 commit comments

Comments
 (0)