Skip to content

Commit 71707c7

Browse files
committed
adding variant check progress bar
1 parent 398df36 commit 71707c7

File tree

15 files changed

+235
-117
lines changed

15 files changed

+235
-117
lines changed

book/src/help/img/ageParse.png

116 KB
Loading

book/src/help/img/external-age.png

-191 KB
Binary file not shown.

book/src/help/img/table-editor.png

92.2 KB
Loading

book/src/help/table-editor.md

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,72 @@ Some articles present information about groups (cohorts) of individuals in table
55
Then, each column is processed by right clicking on the column header or as needed on individual cells.
66

77
<figure>
8-
<img src="img/external-age.png" alt="Cohort editor" width="600">
8+
<img src="img/table-editor.png" alt="Cohort editor" width="600">
99
<figcaption>
10-
<strong>External table editor</strong>. Here, the user has right-clicked on the table header and is transforming the contents to Age entries.
10+
<strong>External table editor</strong>. Users should work through the columns one by one. Successfully transformed columns are shown in green. The follow sections explain how to use the tool.
1111
</figcaption>
1212
</figure>
1313

1414

15-
## Functionality
15+
## Excel import
16+
Clinical data about cohorts is often provided in tabular form in the medical genetics literature. Often, supplemental tables with clinical data about the cohort are provided as Excel files. Use the *Excel (Cols)* button to import Excel files in which
17+
data for each individual is represented by a column. Use *Excel (Rows)* button to import Excel files in which
18+
data for each individual is represented by a row.
1619

17-
The functions of phenoboard can be explored by right-clicking on column headers or cells.
20+
> **Warning**
21+
> Some excel files encountered in the literature can be difficult to parse. For instance, sometimes there are formatting errors such that the contents of one cell "spills over" into the next row. Sometimes, information about two different items
22+
is presented in the same cell (e.g., Age and Sex). Phenoboard provides functionality to merge and split cells that may help to
23+
deal with this, but it may be easier to manually edit the excel file in some cases.
1824

19-
## Saving
25+
The Excel import buttons are disactivated if there is no current cohort. Thus, if you are trying to create a cohort from scratch based on an Excel file, you first need to create the cohort data using the [New Cohort](newcohort.md) page.
2026

21-
When all columns have been processed, the user can add all rows to the current cohort (which must be previously entered!).
27+
## Saving and Loading ETL files
28+
29+
If you would like to save your work and come back later to finish, use the *Save ETL* button to save the file that contains the current state of the ETL file. Use the *Load ETL* button to load the file and resume work where you left off.
30+
31+
> **ETL**: Extract transform load
32+
> ETL is a three-phase computing process where data is extracted from an input source, transformed (including cleaning), and loaded into an output data container. In our case, we are extracting data from the original Excel file, transforming it into HPO terms and other data required for the phenopacket, and loading the transformed data into the Cohort data structure that phenoboard uses to store data about cohorts of individuals (phenopackets).
33+
34+
## PMID
35+
36+
Assign the PubMed identifier of the article from which the cohort data was taken.
37+
38+
## Add to cohort
39+
40+
When you are finished transforming each column, the next step is to load the data into the current cohort. This is not
41+
possible unless all columns have been transformed or marked as "Ignored" (except for the HPO Text Mining column, which is optional).
42+
43+
## Transforming columns
44+
Phenoboard offers a number of different functions for transforming columns, each of which can be started from the context menu that appears upon right click. The following sections describe the major functionalities. Many of the dialogs also provide help that can be accessed by clicking on the **?** symbol.
45+
46+
### Demographic information
47+
Each individual can be annotated with an individual identifier, sex, age of onset, age at last encounter, deceased status. Optionally, a column with a family identifier can be marked and merged with the individual identifier column. To do this, right click on the column header and navegate the context menu as shown below.
48+
49+
<figure>
50+
<img src="img/ageParse.png" alt="Ingesting age" width="600">
51+
<figcaption>
52+
<strong>Importing age entries</strong>.
53+
</figcaption>
54+
</figure>
55+
56+
57+
### Individual ID
58+
This entry must be unique in the cohort. For instance, it would be an error to have two rows with the identifier "Individual A".
59+
60+
### Sex column
61+
This column is used to specify the biological sex of the individual. A variety of formats are transformed into the phenopacket standard entries:
62+
- **M**: Male
63+
- **F**: Female
64+
- **O**: Other
65+
- **U**: Unknown (or not recorded, not available)
66+
67+
### Age of onset/Age at last encounter
68+
Enter the age at first manifestation of any clinical manifestation related to the disease (Onset) and the age at which the individual was last medically examined (last encounter). The ingest will try to transform the data in the column into a Gestational age, HPO term, or ISO 8601 string as appropriate. It is common to see that the age is provide in years (just the number without "y" or "years"). In this case, use the "Assume years" option to ingest the data.
69+
70+
### Deceased
71+
The data in this column must be in the phenopacket format
72+
- **yes**: deceased
73+
- **no**: alive
74+
- **na**: unknown/not available
75+
76+
Further information about these fields can be found in the [Phenopacket Schema documentation](https://phenopacket-schema.readthedocs.io/en/latest/).

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "phenoboard",
3-
"version": "0.5.99",
3+
"version": "0.5.100",
44
"scripts": {
55
"ng": "ng",
66
"start": "nx serve phenoboard --port 1420",
@@ -24,16 +24,16 @@
2424
"@angular/platform-browser": "^20.0.6",
2525
"@angular/platform-browser-dynamic": "^20.0.6",
2626
"@angular/router": "^20.0.6",
27-
"@tauri-apps/api": "^2.5.0",
28-
"@tauri-apps/plugin-dialog": "^2.4.0",
29-
"@tauri-apps/plugin-fs": "^2.4.2",
30-
"@tauri-apps/plugin-opener": "2.5.0",
27+
"@tauri-apps/api": "^2.10.1",
28+
"@tauri-apps/plugin-dialog": "^2.6.0",
29+
"@tauri-apps/plugin-fs": "^2.4.5",
30+
"@tauri-apps/plugin-opener": "2.5.3",
3131
"material-icons": "^1.13.14",
3232
"rxjs": "~7.8.0",
3333
"tslib": "^2.3.0",
3434
"tw-elements": "^2.0.0",
3535
"xlsx": "^0.18.5",
36-
"zone.js": "^0.15.1"
36+
"zone.js": "^0.16.0"
3737
},
3838
"devDependencies": {
3939
"@angular-devkit/build-angular": "^20.0.5",

src-tauri/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "phenoboard"
3-
version = "0.5.99"
3+
version = "0.5.100"
44
description = "Curate cohorts of GA4GH Phenopackets"
55
authors = ["Peter N Robinson"]
66
edition = "2021"
@@ -22,10 +22,10 @@ dirs = "6.0"
2222
html-escape = "0.2.13"
2323
fuzzy-matcher = "0.3"
2424
ontolius = "0.7.2"
25-
reqwest = { version = "0.13.1", features = ["json", "blocking"] }
25+
reqwest = { version = "0.13.2", features = ["json", "blocking"] }
2626
rfd = { version = "0.16.0", default-features = false}
2727
fenominal = { git = 'https://github.com/P2GX/fenominal.git', tag = '0.1.18' }
28-
ga4ghphetools = { git = 'https://github.com/P2GX/ga4ghphetools.git', tag = '0.5.21' }
28+
ga4ghphetools = { git = 'https://github.com/P2GX/ga4ghphetools.git', tag = '0.5.23' }
2929
#ga4ghphetools = { path = "../../ga4ghphetools" }
3030
serde = { version = "1.0.219", features = ["derive"] }
3131
serde_json = "1.0.148"

src-tauri/src/lib.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ use ga4ghphetools::{dto::{cohort_dto::{CohortData, CohortType, DiseaseData, Indi
99
use ga4ghphetools::dto::intergenic_variant::IntergenicHgvsVariant;
1010
use ontolius::ontology::MetadataAware;
1111
use phenoboard::PhenoboardSingleton;
12-
use tauri::{AppHandle, Emitter, Manager, WindowEvent};
12+
use tauri::{AppHandle, Emitter, Manager, Window, WindowEvent};
1313
use tauri_plugin_dialog::{DialogExt};
14-
use std::{collections::HashMap, fs, sync::{Arc, Mutex}};
14+
use std::{collections::{HashMap, HashSet}, fs, sync::{Arc, Mutex}};
1515
use tauri_plugin_fs::{init};
1616

1717

@@ -737,15 +737,39 @@ async fn get_variant_analysis(
737737
singleton.get_variant_analysis(cohort_dto)
738738
}
739739

740+
741+
#[derive(Clone, serde::Serialize)]
742+
struct ProgressPayload {
743+
current: u32,
744+
total: u32,
745+
}
746+
747+
/// Check all alleles in an ETL column and emit signals to show progress.
740748
#[tauri::command]
741-
fn process_allele_column(
749+
async fn process_allele_column(
742750
state: tauri::State<'_, Arc<AppState>>,
751+
window: Window,
743752
etl: EtlDto,
744753
col: usize
745754
) -> Result<EtlDto, String> {
746-
let singleton = state.phenoboard.lock()
747-
.map_err(|_| "Failed to acquire lock on HPO State".to_string())?;
748-
singleton.process_allele_column(etl, col)
755+
let app_handle = state.inner().clone();
756+
if col >= etl.table.columns.len() {
757+
return Err(format!("Attempt to access invalid column {} for table with {} columns", col, etl.table.columns.len()));
758+
}
759+
// Move work to background task so we can still send emits to front-end!
760+
tokio::task::spawn_blocking(move || {
761+
let singleton = app_handle.phenoboard.lock()
762+
.map_err(|_| "Failed to acquire lock".to_string())?;
763+
let total_alleles = etl.table.columns[col].values.len() as u32;
764+
let pb = |current: u32, q: u32| {
765+
let _ = window.emit("progress-update", ProgressPayload {
766+
current,
767+
total: total_alleles
768+
});
769+
};
770+
771+
singleton.process_allele_column(etl, col, pb)
772+
}).await.map_err(|e| e.to_string())?
749773
}
750774

751775
/// This command creates a CohortData object from the current EtlDto and should

src-tauri/src/phenoboard.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ impl PhenoboardSingleton {
636636
}
637637

638638
pub fn save_biocurator_orcid(&mut self, orcid: String) -> Result<StatusDto, String> {
639-
self.settings.save_biocurator_orcid(orcid);
639+
self.settings.save_biocurator_orcid(orcid)?;
640640
Ok(self.get_status())
641641
}
642642

@@ -647,13 +647,15 @@ impl PhenoboardSingleton {
647647
ga4ghphetools::variant::analyze_variants(cohort_dto)
648648
}
649649

650-
pub fn process_allele_column(
650+
pub fn process_allele_column<F>(
651651
&self,
652652
etl: EtlDto,
653-
col: usize
654-
) -> Result<EtlDto, String> {
653+
col: usize,
654+
progress_cb: F
655+
) -> Result<EtlDto, String> where F: FnMut(u32, u32) {
656+
655657
match &self.ontology {
656-
Some(hpo) => ga4ghphetools::etl::process_allele_column(hpo.clone(),etl, col),
658+
Some(hpo) => ga4ghphetools::etl::process_allele_column(hpo.clone(),etl, col, progress_cb),
657659
None => Err("HPO not initialized".to_string()),
658660
}
659661

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "phenoboard",
4-
"version": "0.5.99",
4+
"version": "0.5.100",
55
"identifier": "org.p2gx.phenoboard",
66
"build": {
77
"beforeDevCommand": "npx nx serve phenoboard --configuration=development --no-cloud",

src/app/services/app_status_service.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@ export class AppStatusService {
1717
// Derived UI states
1818
readonly hpoLoading = signal<boolean>(false);
1919
readonly hpoLoaded = computed(() => this.state().hpoLoaded);
20+
progress = signal<number>(0);
21+
2022

2123
constructor() {
2224
this.init();
2325
this.setupListeners();
26+
this.listen_alleles();
2427
}
2528

2629
private async init() {
@@ -32,6 +35,17 @@ export class AppStatusService {
3235
}
3336
}
3437

38+
private async listen_alleles() {
39+
await listen("progress-update", (event) => {
40+
const {current, total} = event.payload as {current: number, total: number};
41+
const percent = Math.round((current / total) * 100);
42+
this.ngZone.run(() => {
43+
this.progress.set(percent)
44+
});
45+
;
46+
});
47+
}
48+
3549
private async setupListeners() {
3650
await listen("hpo-load-event", (event) => {
3751
const { status, message, data } = event.payload as {

0 commit comments

Comments
 (0)