Skip to content

Commit

Permalink
update readme and GUI
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonfan1997 committed Oct 18, 2024
1 parent ff0f0e2 commit ef0254f
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 51 deletions.
180 changes: 154 additions & 26 deletions GUI_cal_metrics.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,57 @@
import argparse
import subprocess
from nicegui import ui, app
from nicegui import ui, app, events
from typing import Optional
from pathlib import Path
import platform


def run_program():
output_area.value = "" # Clear the output area
csv_file = csv_file_input.value
save_metrics = save_metrics_input.value
save_plot = save_plot_input.value

selected_metrics = [metric for metric, checkbox in metrics_checkboxes.items() if checkbox.value]
selected_metrics = [metric for metric, checkbox in metrics_checkboxes.items()
if checkbox.value]

if not selected_metrics or not csv_file:
ui.notify("Error: Please select at least one metric and provide a CSV file.",
type="error")
return

metric_arg = "all" if not selected_metrics else ",".join(selected_metrics)

if perform_bootstrap_checkbox.value and int(n_bootstrap_input.value) == 0:
ui.notify("Error: Number of bootstrap samples must be greater than zero when performing bootstrap.",
type="error")
return

args = [
"--csv_file", str(csv_file),
"--metrics", str(metric_arg),
"--n_bootstrap", str(int(n_bootstrap_input.value)),
"--bootstrap_ci", str(bootstrap_ci_input.value),
"--class_to_calculate", str(class_to_calculate_input.value),
"--num_bins", str(num_bins_input.value),
"--save_metrics", str(save_metrics),
"--plot_bins", str(plot_bins_input.value),
]

if save_metrics_input.value:
args.append("--save_metrics")
args.append(str(save_metrics))

if perform_bootstrap_checkbox.value:
args.append("--n_bootstrap")
args.append(str(int(n_bootstrap_input.value)))
args.append("--bootstrap_ci")
args.append(str(bootstrap_ci_input.value))

if prevalence_adjustment_checkbox.value:
args.append("--prevalence_adjustment")

if plot_checkbox.value:
if not save_plot:
ui.notify("Error: Please provide a path to save the plot.", type="error")
return
args.append("--plot")
args.append("--save_plot")
args.append(str(save_plot))
Expand All @@ -36,8 +62,9 @@ def run_program():

command = ["python", "cal_metrics.py"] + args
print("Running command:", " ".join(command))

process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)

process = subprocess.Popen(command, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, universal_newlines=True)
output, error = process.communicate()

output_area.value = output
Expand All @@ -47,11 +74,13 @@ def run_program():
if plot_checkbox.value:
display_plot(save_plot)


def display_plot(plot_path):
plot_image.clear()
plot_image.set_source(plot_path)
ui.update(plot_image)


def clear_cache():
ui.run_javascript('''
function clearCache() {
Expand All @@ -78,35 +107,58 @@ def clear_cache():
clearCache();
''')
ui.notify('Browser cache cleared')

plot_image.clear()
ui.update(plot_image)


def update_csv_file_input(file_path):
csv_file_input.value = file_path


async def pick_file() -> None:
result = await local_file_picker('~', multiple=False)
update_csv_file_input(result[0])


with ui.row().classes('w-full justify-center'):
with ui.column().classes('w-1/3 p-4'):
ui.label('Calibration Metrics GUI').classes('text-h4')
csv_file_input = ui.input(label='CSV File', placeholder='Enter file path').classes('w-full')
ui.label('calzone GUI').classes('text-h4')
csv_file_input = ui.input(label='CSV File',
placeholder='Enter absolute file path').classes('w-full')
ui.button('choose file', on_click=pick_file)
ui.label('Metrics:')
metrics_options = ["all", "SpiegelhalterZ", "ECE-H", "MCE-H", "HL-H", "ECE-C", "MCE-C", "HL-C", "COX", "Loess"]
metrics_checkboxes = {metric: ui.checkbox(metric, on_change=lambda m=metric: update_checkboxes(m)) for metric in metrics_options}

metrics_options = ["all", "SpiegelhalterZ", "ECE-H", "MCE-H", "HL-H",
"ECE-C", "MCE-C", "HL-C", "COX", "Loess"]
metrics_checkboxes = {metric: ui.checkbox(metric, on_change=lambda m=metric: update_checkboxes(m))
for metric in metrics_options}

with ui.column().classes('w-1/3 p-4'):
ui.label('Bootstrap:')
n_bootstrap_input = ui.number(label='Number of Bootstrap Samples', value=0, min=0)
bootstrap_ci_input = ui.number(label='Bootstrap Confidence Interval', value=0.95, min=0, max=1, step=0.01)
ui.label('Setting:')
class_to_calculate_input = ui.number(label='Class to Calculate Metrics for', value=1, step=1)
num_bins_input = ui.number(label='Number of Bins for ECE/MCE/HL Test', value=10, min=2, step=1)
save_metrics_input = ui.input(label='Save Metrics to', placeholder='Enter file path').classes('w-full')

with ui.column().classes('w-1/3 p-4'):
perform_bootstrap_checkbox = ui.checkbox('Perform Bootstrap', value=False)
n_bootstrap_input = ui.number(label='Number of Bootstrap Samples',
value=0, min=0)
bootstrap_ci_input = ui.number(label='Bootstrap Confidence Interval',
value=0.95, min=0, max=1, step=0.01)
ui.label('Prevalence Adjustment:')
prevalence_adjustment_checkbox = ui.checkbox('Perform Prevalence Adjustment', value=False)
prevalence_adjustment_checkbox = ui.checkbox('Perform Prevalence Adjustment',
value=False)
ui.label('Plot:')
plot_checkbox = ui.checkbox('Plot Reliability Diagram', value=False)
plot_bins_input = ui.number(label='Number of Bins for Reliability Diagram', value=10, min=2, step=1)
save_plot_input = ui.input(label='Save Plot to', placeholder='Enter file path').classes('w-full')
plot_bins_input = ui.number(label='Number of Bins for Reliability Diagram',
value=10, min=2, step=1)
save_plot_input = ui.input(label='Save Plot to',
placeholder='Enter file path. Must ends with image extension').classes('w-full')
ui.label('Setting:')
class_to_calculate_input = ui.number(label='Class to Calculate Metrics for',
value=1, step=1)
num_bins_input = ui.number(label='Number of Bins for ECE/MCE/HL Test',
value=10, min=2, step=1)

with ui.column().classes('w-1/3 p-4'):
ui.label('Output Paths:')
save_metrics_input = ui.input(label='Save Metrics to',
placeholder='Enter file path. Must ends with csv.').classes('w-full')
verbose_checkbox = ui.checkbox('Print Verbose Output', value=True)
topclass_checkbox = ui.checkbox('Transform to Top-class Problem', value=False)
ui.button('Run', on_click=run_program).classes('w-full')
Expand All @@ -118,6 +170,7 @@ def clear_cache():
with ui.column().classes('w-1/3 p-4'):
plot_image = ui.image().classes('w-full')


def update_checkboxes(changed_metric):
if changed_metric == "all":
all_checked = metrics_checkboxes["all"].value
Expand All @@ -127,7 +180,82 @@ def update_checkboxes(changed_metric):
checkbox.disabled = all_checked
else:
metrics_checkboxes["all"].value = False
any_checked = any(checkbox.value for metric, checkbox in metrics_checkboxes.items() if metric != "all")
any_checked = any(checkbox.value for metric, checkbox in metrics_checkboxes.items()
if metric != "all")
metrics_checkboxes["all"].disabled = any_checked

ui.run()

ui.run()


class local_file_picker(ui.dialog):

def __init__(self, directory: str, *,
upper_limit: Optional[str] = ..., multiple: bool = False,
show_hidden_files: bool = False) -> None:
"""
This local file picker is developed by nicegui author.
"""
super().__init__()

self.path = Path(directory).expanduser()
if upper_limit is None:
self.upper_limit = None
else:
self.upper_limit = Path(directory if upper_limit == ... else upper_limit).expanduser()
self.show_hidden_files = show_hidden_files

with self, ui.card():
self.add_drives_toggle()
self.grid = ui.aggrid({
'columnDefs': [{'field': 'name', 'headerName': 'File'}],
'rowSelection': 'multiple' if multiple else 'single',
}, html_columns=[0]).classes('w-96').on('cellDoubleClicked', self.handle_double_click)
with ui.row().classes('w-full justify-end'):
ui.button('Cancel', on_click=self.close).props('outline')
ui.button('Ok', on_click=self._handle_ok)
self.update_grid()

def add_drives_toggle(self):
if platform.system() == 'Windows':
import win32api
drives = win32api.GetLogicalDriveStrings().split('\000')[:-1]
self.drives_toggle = ui.toggle(drives, value=drives[0],
on_change=self.update_drive)

def update_drive(self):
self.path = Path(self.drives_toggle.value).expanduser()
self.update_grid()

def update_grid(self) -> None:
paths = list(self.path.glob('*'))
if not self.show_hidden_files:
paths = [p for p in paths if not p.name.startswith('.')]
paths.sort(key=lambda p: p.name.lower())
paths.sort(key=lambda p: not p.is_dir())

self.grid.options['rowData'] = [
{
'name': f'📁 <strong>{p.name}</strong>' if p.is_dir() else p.name,
'path': str(p),
}
for p in paths
]
if self.upper_limit is None and self.path != self.path.parent or \
self.upper_limit is not None and self.path != self.upper_limit:
self.grid.options['rowData'].insert(0, {
'name': '📁 <strong>..</strong>',
'path': str(self.path.parent),
})
self.grid.update()

def handle_double_click(self, e: events.GenericEventArguments) -> None:
self.path = Path(e.args['data']['path'])
if self.path.is_dir():
self.update_grid()
else:
self.submit([str(self.path)])

async def _handle_ok(self):
rows = await self.grid.get_selected_rows()
self.submit([r['path'] for r in rows])
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,14 @@ run `python cal_metrics.py -h` to see the help information and usage. To use the

To use GUI, run `python GUI_cal_metrics.py` (GUI is under development, use with caution.)

<img src="gui.png" width="500" alt="GUI">

## Documentation

For detailed documentation and API reference, please visit our [documentation pdf](https://github.com/DIDSR/calzone/blob/main/docs/build/latex/calzone.pdf).

## Support
If you encounter any issues or have questions about the package, please open an issue on our [GitHub repository](https://github.com/DIDSR/calzone/issues) or contact us at [kwoklung.fan@fda.hhs.gov](kwoklung.fan@fda.hhs.gov) or [qian.cao@fda.hhs.gov](qian.cao@fda.hhs.gov).

## Disclaimer
This software and documentation (the "Software") were developed at the Food and Drug Administration (FDA) by employees of the Federal Government in the course of their official duties. Pursuant to Title 17, Section 105 of the United States Code, this work is not subject to copyright protection and is in the public domain. Permission is hereby granted, free of charge, to any person obtaining a copy of the Software, to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, or sell copies of the Software or derivatives, and to permit persons to whom the Software is furnished to do so. FDA assumes no responsibility whatsoever for use by other parties of the Software, its source code, documentation or compiled executables, and makes no guarantees, expressed or implied, about its quality, reliability, or any other characteristic. Further, use of this code in no way implies endorsement by the FDA or confers any advantage in regulatory decisions. Although this software can be redistributed and/or modified freely, we ask that any derivative works bear some notice that they are derived from it, and any modified versions bear some notice that they have been modified.
Loading

0 comments on commit ef0254f

Please sign in to comment.