Skip to content

app

MIAPPE Metadata Editor - Dash Web UILink

A web-based UI to view, edit and bulk-fill the MIAPPE-aligned biological metadata (investigation / study / biologicalMaterial / observedVariable) of the scans of a local PlantDB (FSDB).

It offers two editing modes:

  • a bulk editor to set a single field's value on a chosen subset of scans (all / matching a scan-ID regexp / matching a metadata field value / an explicit checklist), and
  • a per-scan editor to inspect and edit the full tree of one scan.

Each field shows the MIAPPE definition as a tooltip on hover, and free-text fields suggest values already used across the database.

Usage exampleLink

metadata_app --db-path /data/ROMI/test_PI3_juillet_Alexis
# then open http://localhost:8050

main Link

main(db_path, port, debug)

Launch the MIAPPE metadata editor web UI.

Source code in plantdb/client/metadata_app/app.py
688
689
690
691
692
693
694
695
696
697
698
699
@click.command(context_settings=dict(help_option_names=["-h", "--help"]))
@click.option("--db-path", type=click.Path(exists=True, file_okay=False, dir_okay=True),
              help="Path to the local PlantDB (FSDB) to edit.")
@click.option("--port", type=int, default=8050, help="Port to run the web application on.")
@click.option("--debug", is_flag=True, help="Enable debug mode.")
def main(db_path: str | None, port: int, debug: bool) -> None:
    """Launch the MIAPPE metadata editor web UI."""
    # Pre-seed the store so the DB loads automatically without clicking Load.
    if db_path:
        DB_PATH_STORE.data = db_path

    app.run(host="0.0.0.0", port=port, debug=debug)

render_edit_form Link

render_edit_form(mode, scan_id, data)

Render the unified field form: values filled for a single scan, placeholders for bulk.

Source code in plantdb/client/metadata_app/app.py
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
@callback(
    Output("per-scan-form", "children"),
    [Input("edit-accordion", "active_item"),
     Input("scan-select", "value")],
    State("scans-store", "data"),
    prevent_initial_call=False
)
def render_edit_form(mode, scan_id, data):
    """Render the unified field form: values filled for a single scan, placeholders for bulk."""
    flat = {}
    if mode == "single" and scan_id and data and data.get("scans"):
        flat = data["scans"].get(scan_id, {})
    # One shared form is used for both edit modes: in "single" mode it is prefilled
    # with the scan's values, in "bulk" mode the fields stay empty as templates.
    section_accordion = dbc.Accordion([], id="field-accordion", always_open=True)
    for section in sections():
        specs = specs_for_section(section)
        if not specs:
            continue
        rows = []
        for spec in specs:
            path = spec["path"]
            value = flat.get(path)
            kind = {"type": "number"} if spec["type"] in ("int", "float") else {"type": "text"}
            rows.append(dbc.InputGroup([
                dbc.InputGroupText(html.Label(spec["label"]), className="align-self-center"),
                dbc.Input(id=_field_input_id(path), value="" if value is None else str(value), **kind),
                dbc.InputGroupText(html.I(className="bi bi-question-circle", title=_tooltip(spec)),
                                   className="align-self-center"),
            ], className="mb-1 field-group"))

        sec_acc = dbc.AccordionItem(rows,
                                    title=[html.I(className=f"{_SECTION_ICONS.get(section, 'bi-folder')} me-2"),
                                           section],
                                    className="mb-2")
        section_accordion.children.append(sec_acc)
    return section_accordion

scan_checklist Link

scan_checklist(scan_ids)

Build the scope checklist options for the given scan ids.

Parameters:

Name Type Description Default

scan_ids Link

list[str]

The scan ids to turn into checklist entries.

required

Returns:

Type Description
list[dict[str, str]]

{"label": id, "value": id} entries for a Dash checklist.

Source code in plantdb/client/metadata_app/app.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def scan_checklist(scan_ids: list[str]) -> list[dict[str, str]]:
    """Build the scope checklist options for the given scan ids.

    Parameters
    ----------
    scan_ids : list[str]
        The scan ids to turn into checklist entries.

    Returns
    -------
    list[dict[str, str]]
        ``{"label": id, "value": id}`` entries for a Dash checklist.
    """
    return [{"label": s, "value": s} for s in scan_ids]

set_db_path_from_button Link

set_db_path_from_button(n_clicks, db_path)

Write the entered path into the store, which triggers loading.

Source code in plantdb/client/metadata_app/app.py
387
388
389
390
391
392
393
394
395
396
397
@callback(
    Output("db-path-store", "data"),
    Input("load-btn", "n_clicks"),
    State("db-path", "value"),
    prevent_initial_call=True
)
def set_db_path_from_button(n_clicks, db_path):
    """Write the entered path into the store, which triggers loading."""
    if n_clicks and db_path:
        return db_path
    return None

set_db_path_value Link

set_db_path_value(db_path)

Update the text input value from the store.

Source code in plantdb/client/metadata_app/app.py
400
401
402
403
404
405
406
407
@callback(
    Output("db-path", "value"),
    Input("db-path-store", "data"),
    prevent_initial_call=False
)
def set_db_path_value(db_path):
    """Update the text input value from the store."""
    return db_path or ""