Skip to content

Commit

Permalink
Add invalidate button
Browse files Browse the repository at this point in the history
  • Loading branch information
aristizabal95 committed Dec 22, 2023
1 parent 17e295b commit 395d4a1
Showing 1 changed file with 146 additions and 37 deletions.
183 changes: 146 additions & 37 deletions scripts/monitor-dset.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,32 @@ def on_modified(self, event):
self.report_state.update()


class InvalidHandler(FileSystemEventHandler):
def __init__(self, invalid_path: str, textual_app):
self.invalid_path = invalid_path
self.app = textual_app

def manual_execute(self):
if os.path.exists(self.invalid_path):
self.update()

def on_modified(self, event):
if event.src_path == self.invalid_path:
self.update()

def update(self):
with open(self.invalid_path, "r") as f:
invalid_subjects = set([id.strip() for id in f.readlines()])
self.app.update_invalid(invalid_subjects)


class PromptHandler(FileSystemEventHandler):
def __init__(self, dset_data_path: str, textual_app):
self.dset_data_path = dset_data_path
self.prompt_path = os.path.join(dset_data_path, ".prompt.txt")
self.app = textual_app

def manual_execute(self):
if os.path.exists(self.prompt_path):
self.display_prompt()

Expand Down Expand Up @@ -364,12 +385,18 @@ def __init__(self, report: dict, highlight: set, dset_path: str):
self.dset_path = dset_path
super().__init__()

class InvalidSubjectsUpdated(Message):
def __init__(self, invalid_subjects):
self.invalid_subjects = invalid_subjects
super().__init__()


class Summary(Static):
"""Displays a summary of the report"""

report = None
report = pd.DataFrame()
dset_path = ""
invalid_subjects = set()

def compose(self) -> ComposeResult:
yield Static("Report Status")
Expand All @@ -384,14 +411,23 @@ def on_report_updated(self, message: ReportUpdated) -> None:
report = message.report
self.dset_path = message.dset_path
if len(report) > 0:
self.update_summary(message.report)
report_df = pd.DataFrame(report)
self.report = report_df
self.update_summary()

def update_summary(self, report: dict):
report_df = pd.DataFrame(report)
self.report = report_df
def on_invalid_subjects_updated(self, message: InvalidSubjectsUpdated) -> None:
self.invalid_subjects = message.invalid_subjects
self.update_summary()

def update_summary(self):
report_df = self.report
if report_df.empty:
return
package_btn = self.query_one("#package-btn", Button)
# Generate progress bars for all states
status_percents = report_df["status_name"].value_counts() / len(report_df)
display_report_df = report_df.copy(deep=True)
display_report_df.loc[list(self.invalid_subjects), "status_name"] = "INVALIDATED"
status_percents = display_report_df["status_name"].value_counts() / len(report_df)
if "DONE" not in status_percents:
# Attach
status_percents["DONE"] = 0.0
Expand Down Expand Up @@ -422,17 +458,23 @@ def on_button_pressed(self, event: Button.Pressed) -> None:
class SubjectListView(ListView):
report = {}
highlight = set()
invalid_subjects = set()

def on_report_updated(self, message: ReportUpdated) -> None:
report = message.report
self.report = message.report
highlight = message.highlight
self.highlight = self.highlight.union(highlight)
if len(report) > 0:
self.update_list(report)
if len(self.report) > 0:
self.update_list()

def update_list(self, report: dict):
def on_invalid_subjects_updated(self, message: InvalidSubjectsUpdated) -> None:
self.invalid_subjects = message.invalid_subjects
self.update_list()

def update_list(self):
# Check for content differences with old report
# apply alert class to listitem
report = self.report
report_df = pd.DataFrame(report)

subjects = ["SUMMARY"] + list(report_df.index)
Expand All @@ -443,6 +485,8 @@ def update_list(self, report: dict):
widget = ListItem(Label(f"{subject}"))
else:
status = report_df.loc[subject]["status_name"]
if subject in self.invalid_subjects:
status = "Invalid"
widget = ListItem(
Label(subject),
Label(status.capitalize().replace("_", " "), classes="subtitle")
Expand All @@ -457,7 +501,6 @@ def update_list(self, report: dict):

self.mount(*widgets)
self.index = current_idx
self.report = report


class CopyableItem(Static):
Expand Down Expand Up @@ -499,6 +542,10 @@ def on_button_pressed(self, event: Button.Pressed) -> None:


class SubjectDetails(Static):
invalid_subjects = set()
subject = pd.Series()
dset_path = ''

def compose(self) -> ComposeResult:
with Center(id="subject-title"):
yield Static(id="subject-name")
Expand Down Expand Up @@ -535,10 +582,20 @@ def compose(self) -> ComposeResult:
id="brainmask-review-warning",
classes="warning",
)
yield Button("Invalidate", id="valid-btn")

def update_subject(self, subject: pd.Series, dset_path: str):
self.subject = subject
self.dset_path = dset_path
def on_invalid_subjects_updated(self, message: InvalidSubjectsUpdated) -> None:
self.invalid_subjects = message.invalid_subjects
self.update_subject()

def set_invalid_path(self, invalid_path):
self.invalid_path = invalid_path

def update_subject(self):
subject = self.subject
dset_path = self.dset_path
if subject.empty:
return
wname = self.query_one("#subject-name", Static)
wstatus = self.query_one("#subject-status", Static)
wdocs = self.query_one("#docs-url", Static)
Expand All @@ -556,6 +613,10 @@ def update_subject(self, subject: pd.Series, dset_path: str):
wname.update(subject.name)
wstatus.update(subject["status_name"])
wmsg.update(subject["comment"])
if subject.name in self.invalid_subjects:
msg = "Subject has been invalidated and will be skipped from the preparation procedure. If you want to include the subject again, revalidate it"
wstatus.update("INVALIDATED")
wmsg.update(msg)
wdata.update(to_local_path(subject["data_path"], dset_path))
wlabels.update(to_local_path(subject["labels_path"], labels_path))
if subject["docs_url"]:
Expand All @@ -581,6 +642,7 @@ def __update_buttons(self):
review_button = self.query_one("#review-button", Button)
reviewed_button = self.query_one("#reviewed-button", Button)
brainmask_button = self.query_one("#brainmask-review-button", Button)
valid_btn = self.query_one("#valid-btn", Button)

if self.__can_review():
review_msg.display = "none"
Expand All @@ -591,6 +653,10 @@ def __update_buttons(self):
if self.__can_review_brain():
brainmask_button.label = "Review brain mask"
brainmask_button.disabled = False
if self.subject.name in self.invalid_subjects:
valid_btn.label = "Validate"
else:
valid_btn.label = "Invalidate"

def __can_review(self):
review_command_path = shutil.which(REVIEW_COMMAND)
Expand Down Expand Up @@ -683,17 +749,45 @@ def __finalize(self):
shutil.copyfile(under_review_filepath, finalized_filepath)
self.notify("Subject finalized")

def __validate(self):
with open(self.invalid_path, "r") as f:
invalid_subjects = set([id.strip() for id in f.readlines()])
if self.subject.name not in invalid_subjects:
return

invalid_subjects.remove(self.subject.name)
with open(self.invalid_path, "w") as f:
f.write("\n".join(invalid_subjects))

def __invalidate(self):
with open(self.invalid_path, "r") as f:
invalid_subjects = set([id.strip() for id in f.readlines()])
if self.subject.name in invalid_subjects:
return

invalid_subjects.add(self.subject.name)
with open(self.invalid_path, "w") as f:
f.write("\n".join(invalid_subjects))

def on_button_pressed(self, event: Button.Pressed) -> None:
review_brainmask_button = self.query_one("#brainmask-review-button", Button)
review_button = self.query_one("#review-button", Button)
reviewed_button = self.query_one("#reviewed-button", Button)
validate_button = self.query_one("#valid-btn", Button)

if event.control == review_brainmask_button:
self.__review_brainmask()
elif event.control == review_button:
self.__review_tumor()
elif event.control == reviewed_button:
self.__finalize()
elif event.control == validate_button:
if self.subject.name in self.invalid_subjects:
self.__validate()
else:
self.__invalidate()

self.__update_buttons()


class Subjectbrowser(App):
Expand All @@ -709,19 +803,20 @@ class Subjectbrowser(App):
subjects = var([])
report = reactive({})
pbars = var([])
invalid_subjects = reactive(set())
prompt = ""

def set_dset_data_path(self, dset_data_path: str):
def set_vars(self, dset_data_path, stages_path, reviewed_watchdog, output_path, invalid_path, invalid_watchdog, prompt_watchdog):
self.dset_data_path = dset_data_path

def set_stages_path(self, stages_path: str):
self.stages_path = stages_path

def set_reviewed_watchdog(self, reviewed_watchdog: ReviewedHandler):
self.reviewed_watchdog = reviewed_watchdog

def set_output_path(self, output_path: str):
self.output_path = output_path
self.invalid_path = invalid_path
self.invalid_watchdog = invalid_watchdog
self.prompt_watchdog = prompt_watchdog

def update_invalid(self, invalid_subjects):
self.invalid_subjects = invalid_subjects

def compose(self) -> ComposeResult:
"""Compose our UI."""
Expand Down Expand Up @@ -762,9 +857,13 @@ def on_mount(self):
with open(report_path, "r") as f:
self.report = yaml.safe_load(f)

# Set reviewed watchdog for summary view
summary = self.query_one("#summary", Summary)
summary.set_reviewed_watchdog(self.reviewed_watchdog)
# Set invalid path for subject view
subject_details = self.query_one("#details", SubjectDetails)
subject_details.set_invalid_path(self.invalid_path)

# Execute handlers
self.prompt_watchdog.manual_execute()
self.invalid_watchdog.manual_execute()

def on_list_view_selected(self, event: ListView.Selected) -> None:
"""Called when the user click a subject in the list."""
Expand All @@ -787,7 +886,10 @@ def on_list_view_selected(self, event: ListView.Selected) -> None:
report = pd.DataFrame(self.report)
subject = report.loc[subject_idx]
subject_view = self.query_one("#details", SubjectDetails)
subject_view.update_subject(subject, self.dset_data_path)
subject_view.subject = subject
subject_view.dset_path = self.dset_data_path

subject_view.update_subject()

def on_button_pressed(self, event: Button.Pressed) -> None:
y_button = self.query_one("#confirm-approve", Button)
Expand All @@ -801,14 +903,21 @@ def on_button_pressed(self, event: Button.Pressed) -> None:
def update_prompt(self, prompt: str):
self.prompt = prompt
show_prompt = bool(len(prompt))
try:
prompt_details = self.query_one("#confirm-details", Static)
prompt_details.update(prompt)
container = self.query_one("#confirm-prompt", Container)
container.display = show_prompt
container.focus()
except:
return
prompt_details = self.query_one("#confirm-details", Static)
prompt_details.update(prompt)
container = self.query_one("#confirm-prompt", Container)
container.display = show_prompt
container.focus()

def watch_invalid_subjects(self, invalid_subjects: set) -> None:
subject_list = self.query_one("#subjects-list", SubjectListView)
summary = self.query_one("#summary", Summary)
subject_details = self.query_one("#details", SubjectDetails)

msg = InvalidSubjectsUpdated(invalid_subjects)
subject_list.post_message(msg)
summary.post_message(msg)
subject_details.post_message(msg)

def watch_report(self, old_report: dict, report: dict) -> None:
highlight_subjects = set()
Expand Down Expand Up @@ -885,6 +994,7 @@ def main(

report_path = os.path.join(dset_path, "report.yaml")
dset_data_path = os.path.join(dset_path, "data")
invalid_path = os.path.join(dset_path, "metadata/.invalid.txt")

if not os.path.exists(report_path):
print(
Expand All @@ -894,21 +1004,20 @@ def main(
exit()

app = Subjectbrowser()
app.set_dset_data_path(dset_data_path)
app.set_stages_path(stages_path)
app.set_output_path(output_path)

report_state = ReportState(report_path, app)
report_watchdog = ReportHandler(report_state)
prompt_watchdog = PromptHandler(dset_data_path, app)
reviewed_watchdog = ReviewedHandler(dset_data_path, app)
invalid_watchdog = InvalidHandler(invalid_path, app)

app.set_reviewed_watchdog(reviewed_watchdog)
app.set_vars(dset_data_path, stages_path, reviewed_watchdog, output_path, invalid_path, invalid_watchdog, prompt_watchdog)

observer = Observer()
observer.schedule(report_watchdog, dset_path)
observer.schedule(prompt_watchdog, os.path.join(dset_path, "data"))
observer.schedule(reviewed_watchdog, ".")
observer.schedule(invalid_watchdog, os.path.dirname(invalid_path))
observer.start()
app.run()

Expand Down

0 comments on commit 395d4a1

Please sign in to comment.