diff --git a/src/aind_qc_portal/qc_project_app.py b/src/aind_qc_portal/qc_project_app.py index 9fd1661..1dcc79c 100644 --- a/src/aind_qc_portal/qc_project_app.py +++ b/src/aind_qc_portal/qc_project_app.py @@ -22,7 +22,8 @@ class Settings(param.Parameterized): pn.state.location.sync(settings, {"project_name": "project_name"}) -class ProjectView(): +class ProjectView(param.Parameterized): + subject_filter = param.String(default="") def __init__(self): self._get_assets() @@ -33,12 +34,9 @@ def _get_assets(self): records = get_project(settings.project_name) data = [] - groups = {} for record in records: raw_name = _raw_name_from_derived(record['name']) - - if raw_name not in groups: - groups[raw_name] = len(groups) + subject_id = record.get('subject', {}).get('subject_id') record_data = { '_id': record.get('_id'), @@ -48,9 +46,8 @@ def _get_assets(self): 'name': record.get('name'), 'session_start_time': record.get('session', {}).get('session_start_time'), 'session_type': record.get('session', {}).get('session_type'), - 'subject_id': record.get('subject', {}).get('subject_id'), + 'subject_id': subject_id, 'genotype': record.get('subject', {}).get('genotype'), - 'group': groups.get(raw_name), } data.append(record_data) @@ -59,26 +56,31 @@ def _get_assets(self): return self.data = pd.DataFrame(data) - self.data["timestamp"] = pd.to_datetime(self.data["session_start_time"]) + self.data["timestamp"] = pd.to_datetime(self.data["session_start_time"], format='mixed', utc=True) self.data["Date"] = self.data["timestamp"].dt.strftime("%Y-%m-%d %H:%M:%S") self.data["S3 link"] = self.data["location"].apply(lambda x: format_link(x, text="S3 link")) self.data["Subject view"] = self.data["_id"].apply(lambda x: format_link(f"/qc_asset_app?id={x}")) self.data["QC view"] = self.data["_id"].apply(lambda x: format_link(f"/qc_app?id={x}")) self.data.sort_values(by="timestamp", ascending=True, inplace=True) - unique_groups = self.data["group"].unique() - group_mapping = { - group: new_group for new_group, group in enumerate(unique_groups) - } + self.data.sort_values(by="subject_id", ascending=True, inplace=True) + + def get_subjects(self): + if self.data is None: + return [] - # Replace the 'group' column with the new group values - self.data["group"] = self.data["group"].map(group_mapping) - self.data.sort_values(by="group", ascending=True, inplace=True) + return self.data["subject_id"].unique() def get_data(self): if self.data is None: return None - return self.data[["subject_id", "Date", "S3 link", "Subject view", "QC view", "genotype", "session_type", "raw"]] + + if self.subject_filter: + filtered_df = self.data[self.data["subject_id"].str.contains(self.subject_filter, case=False, na=False)] + else: + filtered_df = self.data + + return filtered_df[["subject_id", "Date", "S3 link", "Subject view", "QC view", "genotype", "session_type", "raw"]] def history_panel(self): """Create a plot showing the history of this asset, showing how assets were derived from each other""" @@ -102,7 +104,7 @@ def history_panel(self): scale=alt.Scale(domain=[min_range, max_range]), axis=alt.Axis(format=format, tickCount=range_unit), ), - y=alt.Y("group:N", title="Raw asset"), + y=alt.Y("subject_id:N", title="Subject ID"), tooltip=[ "name", "session_type", @@ -112,26 +114,39 @@ def history_panel(self): ], color=alt.Color("subject_id:N"), ) + .properties(width=900) ) return pn.pane.Vega(chart, sizing_mode="stretch_width", styles=OUTER_STYLE) +project_view = ProjectView() md = f"""

- QC Portal - Project View + {settings.project_name}

-Main entrypoint for projects. Shows all assets flagged for each project and links to the QC pages. +{len(project_view.data)} data assets are associated with this project. """ header = pn.pane.Markdown(md, width=1000, styles=OUTER_STYLE) -project_view = ProjectView() chart_pane = project_view.history_panel() -df_pane = pn.pane.DataFrame(project_view.get_data(), width=1000, escape=False, index=False, styles=OUTER_STYLE) -col = pn.Column(header, chart_pane, df_pane) +df_pane = pn.pane.DataFrame(project_view.get_data(), width=950, escape=False, index=False) + + +def update_subject_filter(event): + project_view.subject_filter = event.new + df_pane.object = project_view.get_data() + + +subject_filter = pn.widgets.Select(name="Subject filter", options=list(project_view.get_subjects())) +subject_filter.param.watch(update_subject_filter, "value") + +df_col = pn.Column(subject_filter, df_pane, styles=OUTER_STYLE) + +col = pn.Column(header, chart_pane, df_col) row = pn.Row(pn.HSpacer(), col, pn.HSpacer()) row.servable(title="AIND QC - Project") diff --git a/src/aind_qc_portal/utils.py b/src/aind_qc_portal/utils.py index 68dbb35..65a0c8f 100644 --- a/src/aind_qc_portal/utils.py +++ b/src/aind_qc_portal/utils.py @@ -98,6 +98,8 @@ def df_timestamp_range(df, column="timestamp"): one_month = timedelta(days=30) three_months = timedelta(days=90) one_year = timedelta(days=365) + two_year = timedelta(days=730) + five_year = timedelta(days=1825) # Determine the minimum range if time_range < one_week: @@ -115,11 +117,23 @@ def df_timestamp_range(df, column="timestamp"): max_range = max_date + (three_months - time_range) / 2 unit = "week" format = "%b %d" - else: + elif time_range < one_year: min_range = min_date - (one_year - time_range) / 2 max_range = max_date + (one_year - time_range) / 2 unit = "month" format = "%b" + elif time_range < two_year: + min_range = min_date - (two_year - time_range) / 2 + max_range = max_date + (two_year - time_range) / 2 + unit = "year" + format = "%b" + elif time_range < five_year: + min_range = min_date - (five_year - time_range) / 2 + max_range = max_date + (five_year - time_range) / 2 + unit = "year" + format = "%Y" + else: + raise ValueError("Time range is too large") return (min_range, max_range, unit, format)