diff --git a/MANIFEST.in b/MANIFEST.in index beccd01..922abd3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,6 +5,7 @@ include *.py recursive-include docs *.md recursive-include docs *.png recursive-include docs *.svg +recursive-include rsp_vision *.css exclude *.yml exclude *.cruft.json diff --git a/README.md b/README.md index 34908a0..7f2f7fc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ # RSP vision Tools for analyzing responses of visual neurons to drifting gratings, based on the original work of Lee Cossell. +## Get Started Quickly +In your environment... +Install: `pip install .` +Make config file: `python3 setup_for_demo.py` +Edit config file and insert your path: `nano rsp_vision/config/config.yml` +Run analysis: `python3 demo_cli.py` +Run dashboard: `python3 demo_dash.py` + ## Installation Clone the repository and install the dependencies in your environment with: @@ -23,6 +31,11 @@ Please edit the `allen_dff` path to point to the folder where you stored the pre Finally, run `python3 demo_cli.py` to run the analysis. The script will create a file containing the analysis output which will then be used by the dashboard. +### Run the analysis +The script `demo_cli.py` runs the analysis and stores the output. You can start it with: +```bash +python3 demo_cli.py +``` ### Data processing The original data is stored as a nested dictionary, usually referred to as the data_raw attribute. It contains the following keys: `day`, `imaging`, `f`, `is_cell`, `r_neu`, `stim`, `trig`. For our analysis, we focus mainly on `f` and `stim`. @@ -97,4 +110,32 @@ After the fit, the Gaussian is sampled to generate a 6x6 and a 100x100 matrix, w ### Schema of the current analysis pipeline A schematic representation of the process is shown below: -![Responsiveness analysis diagram](https://github.com/neuroinformatics-unit/rsp-vision/assets/29216006/e4af3107-2f3b-431f-98f7-8ead20e3de04) + + +### Data saving +The output of the analysis is saved in the folder specified in the config file as `path:output`. Folder names are generated automatically according to [SWC_Blueprint specifications](https://swc-blueprint.neuroinformatics.dev/). Filenames do not follow the convention right now. + +Schematic representation of the output folder structure: +``` +rsp_vision +├── logs/ +│ ├── log_file_1.log +├── devirvatives/ +│ ├── analysis_log.csv +│ ├── subject_level_folder/ +│ │ ├── session_level_folder/ +│ │ │ ├── metadata.yml +│ │ │ ├── roi_1.pkl +``` +The files are currently saved as pickle files. Each session folder, in addition to the pickle files, contains a `metadata.yml` file, with the parameters that were used for that analysis. + +In the same project folder, you will find in addition to the `deirvatives` folder, a `logs` folder, where the logs of the analysis are saved. Inside the derivatives folder, you will find the `analysis_log.csv` file, a table containing the records of which datasets have already been analyised. This file is updated at the end of each analysis. + +Check `rsp_vision/save/save_data.py` and `rsp_vision/objects/SWC_Blueprint.py` for more details. + +## Dashboard +You can browse the analysed data via a Dash app. The app is currently in development, but you can already run it. +Run the following command in the terminal: +``` +python3 demo_dash.py +``` diff --git a/demo_dash.py b/demo_dash.py new file mode 100644 index 0000000..582736d --- /dev/null +++ b/demo_dash.py @@ -0,0 +1,5 @@ +from rsp_vision.dashboard.app import app + +if __name__ == "__main__": + # app = get_app() + app.run_server(debug=True) diff --git a/pyproject.toml b/pyproject.toml index 6df1aaf..d881664 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,9 @@ dependencies = [ "numba", "tqdm", "GitPython", + "dash", + "dash_mantine_components", + "dash_bootstrap_components", ] [project.urls] @@ -52,6 +55,8 @@ dev = [ "ruff", "setuptools_scm", "check-manifest", + "pytest-mock", + "GitPython", ] [build-system] diff --git a/rsp_vision/analysis/spatial_freq_temporal_freq.py b/rsp_vision/analysis/spatial_freq_temporal_freq.py index 11ccb08..da6396b 100644 --- a/rsp_vision/analysis/spatial_freq_temporal_freq.py +++ b/rsp_vision/analysis/spatial_freq_temporal_freq.py @@ -634,7 +634,7 @@ def manage_fitting( if best_result is None: logging.warning( - f"ROI {roi_id} and direction {dir} failed to fit." + f"ROI {roi_id} and direction {direction} failed to fit." + "Skipping..." ) best_result = OptimizeResult() diff --git a/rsp_vision/dashboard/app.py b/rsp_vision/dashboard/app.py new file mode 100644 index 0000000..f23f124 --- /dev/null +++ b/rsp_vision/dashboard/app.py @@ -0,0 +1,48 @@ +# This file is used to create the Dash app and the scaffold layout. +import dash_mantine_components as dmc +from dash import Dash, dcc, html, page_container + +# I am using Open Sans font from Google Fonts for the whole app. +# The design of the app is controlled by the CSS file in +# rsp_vision/dashboard/assets/style.css. +google_fonts_link = ( + "https://fonts.googleapis.com/css2?family=Open+Sans&display=swap" +) +external_stylesheets = [ + { + "href": google_fonts_link, + "rel": "stylesheet", + } +] +# fmt: off +app = Dash( + __name__, + use_pages=True, + external_stylesheets=external_stylesheets, + suppress_callback_exceptions=True, +) +# fmt: on + +header = dmc.Header( + height=70, + children=[ + dmc.Title( + "RSP vision 👁️", + order=1, + className="main-title", + ) + ], + className="header", +) + +# Here I define the layout of the app. The layout is a composition of +# Mantine components and Dash components. +# Importantly, here I define the `Store` component, which is fundamental +# to share information between pages. +app.layout = html.Div( + [ + dcc.Store(id="store", data={}), + header, + page_container, + ] +) diff --git a/rsp_vision/dashboard/assets/styles.css b/rsp_vision/dashboard/assets/styles.css new file mode 100644 index 0000000..9d308d8 --- /dev/null +++ b/rsp_vision/dashboard/assets/styles.css @@ -0,0 +1,82 @@ +.header { + background-color: #0c0223; + margin: auto; + width: 100%; + height: 50px; +} + +.main-title { + color: #ffffff; + text-align: center; +} + +.page-title{ + color: #0c0223; + text-align: center; +} + +.page{ + padding: 1%; +} + +.table{ + padding: 2%; +} + +.selected-data-container{ + width: 40%; + padding: 1%; + margin: auto; +} + +.load-data-button{ + margin: 1%; + + border-radius: 8px; + border-width: 0; + text-align: center; +} + +a:-webkit-any-link { + text-align: center; + text-decoration: none !important; + text-rendering: auto; + text-transform: none; + text-indent: 0px; + text-shadow: none; + + letter-spacing: normal; + word-spacing: normal; + line-height: normal; + font: -webkit-small-control; + + color: rgba(0, 0, 0); + border-color: rgba(0, 0, 0, 0.3); + + appearance: auto; + display: inline-block; + align-items: flex-start; + cursor: default; + margin: 1%; + padding: 1px 6px; + + box-sizing: border-box; + border-radius: 8px; + border-width: 0; + border-width: 2px; + border-style: outset; + border-color: buttonborder; + border-image: initial; +} + +.responsive-switch{ + margin: 5%; +} + +.responsive-switch-text{ + margin: 5%; +} + +.show-hide { + display: none; +} diff --git a/rsp_vision/dashboard/pages/data_table.py b/rsp_vision/dashboard/pages/data_table.py new file mode 100644 index 0000000..dc9826a --- /dev/null +++ b/rsp_vision/dashboard/pages/data_table.py @@ -0,0 +1,154 @@ +from pathlib import Path + +import dash_bootstrap_components as dbc +import dash_mantine_components as dmc +import pandas as pd +from dash import Input, Output, callback, dash_table, html, register_page +from decouple import config + +from rsp_vision.load.load_data import read_config_file +from rsp_vision.objects.SWC_Blueprint import ( + SessionFolder, + SubjectFolder, + SWC_Blueprint_Spec, +) + +# The following code lives outside of the callback +# because it is executed only once, when the app starts +# It reads the config file and creates the SWC_Blueprint_Spec object +# that is used to load the data. +CONFIG_PATH = config("CONFIG_PATH") +config_path = Path(__file__).parents[2] / CONFIG_PATH +config = read_config_file(config_path) +swc_blueprint_spec = SWC_Blueprint_Spec( + project_name="rsp_vision", + raw_data=False, + derivatives=True, + local_path=Path(config["paths"]["output"]), +) +with open(swc_blueprint_spec.path / "analysis_log.csv", "r") as file: + col = file.readline().split(",") + col[0] = "index" + columns = [{"name": idx, "id": idx} for idx in col] + dataframe = pd.read_csv(file, names=col, index_col=0) + data = dataframe.to_dict(orient="records") + + +register_page(__name__, path="/") + + +layout = html.Div( + [ + dmc.Title( + "Select dataset to be loaded 👇", + order=2, + className="page-title", + ), + dmc.Container( + children=[ + dmc.Text( + id="selected_data_str", + ), + dbc.Button( + "Load selected dataset ✨", + id="button", + className="load-data-button", + href="/murakami_plot", + n_clicks=0, + ), + html.Br(), + html.Br(), + dash_table.DataTable( + id="table", + columns=columns, + data=data, + editable=False, + filter_action="native", + sort_action="native", + sort_mode="multi", + row_selectable="single", + row_deletable=False, + selected_columns=[], + selected_rows=[], + page_action="native", + page_current=0, + page_size=20, + hidden_columns=[ + "index", + "sub", + "ses", + "mouse_line", + "mouse_id", + "hemisphere", + "brain_region", + "monitor_position", + "fov", + "cre", + "analysed", + "commit_hash", + "microscope", + ], + ), + ], + className="table", + ), + ], + className="page", +) + + +@callback( + [ + Output("selected_data_str", "children"), + Output("store", "data"), + Output("button", "disabled"), + ], + Input("table", "selected_rows"), +) +def update_storage(selected_rows: list) -> tuple: + """This callback is triggered when the user selects a row in the table. + It updates the `Store` component with the information about the selected + row, i.e. the dataset that the user wants to load. + + Parameters + ---------- + selected_rows : list + List of selected rows in the Dash table. + + Returns + ------- + tuple + A tuple containing the following elements: + - A string with the name of the selected dataset. + - A dictionary containing the information about the selected dataset. + - A boolean indicating whether the button for loading the data should + be disabled or not. + """ + if selected_rows is None or len(selected_rows) == 0: + return "No row selected 🤷🏻‍♀️", {}, True + + else: + sub_folder = SubjectFolder( + swc_blueprint_spec, + dataframe.iloc[selected_rows[0]].to_dict(), + ) + session_folder = SessionFolder( + sub_folder, + dataframe.iloc[selected_rows[0]].to_dict(), + ) + + store = { + "data": dataframe.iloc[selected_rows[0]], + "path": str(swc_blueprint_spec.path), + "config": config, + "subject_folder_path": str(sub_folder.sub_folder_path), + "session_folder_path": str(session_folder.ses_folder_path), + } + + folder_name = dataframe.iloc[selected_rows[0]]["folder_name"] + + return ( + f"Dataset selected: {folder_name}", + store, + False, + ) diff --git a/rsp_vision/dashboard/pages/murakami_plot.py b/rsp_vision/dashboard/pages/murakami_plot.py new file mode 100644 index 0000000..241553f --- /dev/null +++ b/rsp_vision/dashboard/pages/murakami_plot.py @@ -0,0 +1,511 @@ +import pickle +from pathlib import Path + +import dash_mantine_components as dmc +import numpy as np +import pandas as pd +import plotly.graph_objects as go +from dash import Input, Output, callback, dcc, html, register_page + +from rsp_vision.analysis.gaussians_calculations import ( + get_gaussian_matrix_to_be_plotted, +) + +register_page(__name__, path="/murakami_plot") + +layout = html.Div( + [ + dmc.Title( + "Murakami Plot", + className="page-title", + ), + dmc.Grid( + children=[ + dmc.Col( + [ + dmc.Text( + id="selected_data_str_murakami", + ), + html.Br(), + dmc.NavLink( + label="Back to Data Table", + href="/", + className="navlink", + ), + dmc.NavLink( + label="Murakami plot", + href="/murakami_plot", + className="navlink", + disabled=True, + ), + dmc.NavLink( + label="SF-TF facet plot and gaussians", + href="/sf_tf_facet_plot", + className="navlink", + ), + dmc.NavLink( + label="Polar plots", + href="/polar_plots", + className="navlink", + ), + html.Br(), + dmc.Switch( + id="show-only-responsive", + label="Show only responsive ROIs", + checked=True, + className="responsive-switch", + ), + html.Br(), + dmc.Text( + "Responsive ROIs are shown in red, " + + "non-responsive ROIs are shown in black.", + size="xs", + color="grey", + className="responsive-switch-text", + ), + dmc.Alert( + "No responsive ROIs found", + id="responsive-rois-warnings", + title="Warning", + color="yellow", + hide=True, + ), + ], + span=2, + ), + dmc.Col( + dmc.Center( + html.Div( + id="murakami-plot", + className="murakami-plot", + ), + ), + span="auto", + offset=1, + ), + ], + className="murakami-container", + ), + ], + className="page", +) + + +@callback( + Output("responsive-rois-warnings", "hide"), + Input("store", "data"), +) +def responsive_rois_warnings(store: dict) -> bool: + """This callback hides the warning message if there are responsive ROIs, + and shows it if there are not. By default, the warning message is hidden. + Parameters + ---------- + store : dict + The store contains the data that is loaded from the data table. + + Returns + ------- + bool + Whether to hide the warning message or not. + """ + if store == {}: + return True + else: + data = load_data(store) + responsive_rois = data["responsive_rois"] + if (responsive_rois == 0) | (responsive_rois == set()): + return False + else: + return True + + +@callback( + Output("selected_data_str_murakami", "children"), + Input("store", "data"), +) +def update_selected_data_str(store: dict) -> str: + """This callback updates the text that shows the dataset that has been + loaded. + + Parameters + ---------- + store : dict + The store contains the data that is loaded from the data table. + + Returns + ------- + str + The name of the dataset that has been choosen. + """ + if store == {}: + return "No data selected" + else: + return f'Dataset loaded is: {store["data"][0]}' + + +@callback( + Output("murakami-plot", "children"), + [ + Input("store", "data"), + Input("show-only-responsive", "checked"), + ], +) +def murakami_plot(store: dict, show_only_responsive: bool) -> dcc.Graph: + """This callback generates the Murakami plot. It is called Murakami plot + because it is inspired by the visualization on Murakami et al. (2017). + Peak responses for each roi are shown as a "scatter plot" in a 2D space, + where the x-axis is the spatial frequency and the y-axis is the temporal + frequency. The dots are connected by lines to the median dot creating + a start shape. + The peak responses represent the spatial and temporal frequency that + maximizes the response of the roi. + + Parameters + ---------- + store : dict + The store contains the data that is loaded from the data table. + show_only_responsive : bool + Whether to show only the responsive ROIs or not. + + Returns + ------- + dcc.Graph + The Murakami plot. + """ + if store == {}: + return "No data to plot" + + data = load_data(store) + + # prepare data + responsive_rois = data["responsive_rois"] + n_roi = data["n_roi"] + matrix_definition = 100 + spatial_frequencies = store["config"]["spatial_frequencies"] + temporal_frequencies = store["config"]["temporal_frequencies"] + fit_outputs = data["fit_outputs"] + fitted_gaussian_matrix = call_get_gaussian_matrix_to_be_plotted( + n_roi, + fit_outputs, + spatial_frequencies, + temporal_frequencies, + matrix_definition, + ) + + total_roi = responsive_rois if show_only_responsive else list(range(n_roi)) + + # plot + fig = go.Figure() + fig = add_data_in_figure( + all_roi=total_roi, + fig=fig, + matrix_definition=matrix_definition, + responsive_rois=responsive_rois, + fitted_gaussian_matrix=fitted_gaussian_matrix, + spatial_frequencies=spatial_frequencies, + temporal_frequencies=temporal_frequencies, + ) + fig = prettify_murakami_plot( + fig, spatial_frequencies, temporal_frequencies + ) + + return dcc.Graph(figure=fig) + + +def prettify_murakami_plot( + fig: go.Figure, + spatial_frequencies: np.ndarray, + temporal_frequencies: np.ndarray, +) -> go.Figure: + """This method takes a figure and edits its aesthetics by manipulating + the properties of the figure. It is specific for the Murakami plot. + + Parameters + ---------- + fig : go.Figure + The figure to be edited. + spatial_frequencies : np.ndarray + The spatial frequencies that are used in the experiment. + temporal_frequencies : np.ndarray + The temporal frequencies that are used in the experiment. + + Returns + ------- + go.Figure + The edited figure. + """ + fig.update_layout( + yaxis_title="Spatial frequency (cycles/deg)", + xaxis_title="Temporal frequency (Hz)", + legend_title="ROI", + plot_bgcolor="rgba(0, 0, 0, 0)", + paper_bgcolor="rgba(0, 0, 0, 0)", + autosize=False, + width=600, + height=600, + margin=dict(t=50, b=50, l=50, r=50), + ) + + fig.update_xaxes( + range=[-0.05, 17], + title_text="Temporal frequency (Hz)", + showgrid=False, + zeroline=False, + tickvals=[], + ) + fig.update_yaxes( + range=[0, 0.33], + title_text="Spatial frequency (cycles/deg)", + showgrid=False, + zeroline=False, + tickvals=[], + ) + + # draw horizontal lines + for i in spatial_frequencies: + fig.add_shape( + type="line", + x0=0.25, + y0=i, + x1=16.1, + y1=i, + line=dict(color="Grey", width=1), + ) + # add annotations for horizontal lines + fig.add_annotation( + x=0.05, + y=i, + text=f"{i}", + showarrow=False, + yshift=0, + xshift=-10, + font=dict(color="Black"), + ) + + # draw vertical lines + for i in temporal_frequencies: + fig.add_shape( + type="line", + x0=i, + y0=0.001, + x1=i, + y1=0.33, + line=dict(color="Grey", width=1), + ) + # add annotations for vertical lines + fig.add_annotation( + x=i, + y=0.001, + text=f"{i}", + showarrow=False, + yshift=510, + xshift=0, + font=dict(color="Black"), + ) + return fig + + +def add_data_in_figure( + all_roi: list, + fig: go.Figure, + matrix_definition: int, + responsive_rois: list, + fitted_gaussian_matrix: pd.DataFrame, + spatial_frequencies: np.ndarray, + temporal_frequencies: np.ndarray, +) -> go.Figure: + """For each roi, this method adds a dot in the Murakami plot (representing + the peak response of the roi) and the lines connecting the dots to the + median dot. + + Parameters + ---------- + all_roi : list + The list of all the ROIs. + fig : go.Figure + The figure to which the data is to be added. + matrix_definition : int + The matrix definition used in the experiment. It specifies + the precision of the sf/tf peaks that are to be found. Needs to match + the matrix definition used to generate the fitted_gaussian_matrix. + responsive_rois : list + The list of responsive ROIs. + fitted_gaussian_matrix : pd.DataFrame + The fitted gaussian matrix obtained from the precalculated fits. + spatial_frequencies : np.ndarray + The spatial frequencies that are used in the experiment. + temporal_frequencies : np.ndarray + The temporal frequencies that are used in the experiment. + + Returns + ------- + go.Figure + The figure with the data added. + """ + peaks = { + roi_id: find_peak_coordinates( + fitted_gaussian_matrix=fitted_gaussian_matrix[(roi_id, "pooled")], + spatial_frequencies=np.asarray(spatial_frequencies), + temporal_frequencies=np.asarray(temporal_frequencies), + matrix_definition=matrix_definition, + ) + for roi_id in all_roi + } + + p = pd.DataFrame( + { + "roi_id": roi_id, + "temporal_frequency": peaks[roi_id][0], + "spatial_frequency": peaks[roi_id][1], + } + for roi_id in all_roi + ) + + median_peaks = p.median() + + # dots for ROIs + for roi_id in all_roi: + row = p[(p.roi_id == roi_id)].iloc[0] + tf = row["temporal_frequency"] + sf = row["spatial_frequency"] + fig.add_trace( + go.Scatter( + x=[tf, median_peaks["temporal_frequency"]], + y=[sf, median_peaks["spatial_frequency"]], + mode="markers", + marker=dict( + color="red" if roi_id in responsive_rois else "black", + size=10, + ), + name=f"ROI {roi_id + 1}", + showlegend=False, + ) + ) + + # lines to connect to the median dot + fig.add_trace( + go.Scatter( + x=[tf, median_peaks["temporal_frequency"]], + y=[sf, median_peaks["spatial_frequency"]], + mode="lines", + line=dict(color="Grey", width=1), + showlegend=False, + ) + ) + + return fig + + +def load_data(store: dict) -> dict: + """This method loads the data from the pickle file. + + Parameters + ---------- + store : dict + The store object. + + Returns + ------- + dict + The data from the pickle file. + """ + path = ( + Path(store["path"]) + / store["subject_folder_path"] + / store["session_folder_path"] + / "gaussians_fits_and_roi_info.pickle" + ) + with open(path, "rb") as f: + data = pickle.load(f) + + return data + + +def find_peak_coordinates( + fitted_gaussian_matrix: np.ndarray, + spatial_frequencies: np.ndarray, + temporal_frequencies: np.ndarray, + matrix_definition: int, +) -> tuple: + """This method finds the peak coordinates of the fitted gaussian matrix. + + Parameters + ---------- + fitted_gaussian_matrix : np.ndarray + The fitted gaussian matrix obtained from the precalculated fits. + spatial_frequencies : np.ndarray + The spatial frequencies that are used in the experiment. + temporal_frequencies : np.ndarray + The temporal frequencies that are used in the experiment. + matrix_definition : int + The matrix definition used to generate the fitted_gaussian_matrix. + + Returns + ------- + tuple + The peak coordinates of the fitted gaussian matrix. + """ + peak_indices = np.unravel_index( + np.argmax(fitted_gaussian_matrix), fitted_gaussian_matrix.shape + ) + + spatial_freq_linspace = np.linspace( + spatial_frequencies.min(), + spatial_frequencies.max(), + matrix_definition, + ) + temporal_freq_linspace = np.linspace( + temporal_frequencies.min(), + temporal_frequencies.max(), + matrix_definition, + ) + + sf = spatial_freq_linspace[peak_indices[0]] + tf = temporal_freq_linspace[peak_indices[1]] + return tf, sf + + +def call_get_gaussian_matrix_to_be_plotted( + n_roi: int, + fit_outputs: dict, + spatial_frequencies: np.ndarray, + temporal_frequencies: np.ndarray, + matrix_definition: int, +) -> dict: + """This method is a wrapper for the get_gaussian_matrix_to_be_plotted + method that iterates over all the ROIs. + + Parameters + ---------- + n_roi : int + The number of ROIs. + fit_outputs : dict + The fit outputs obtained from the precalculated fits. + spatial_frequencies : np.ndarray + The spatial frequencies that are used in the experiment. + temporal_frequencies : np.ndarray + The temporal frequencies that are used in the experiment. + matrix_definition : int + The matrix definition used to generate the fitted_gaussian_matrix. + + Returns + ------- + dict + The fitted gaussian matrix obtained from the precalculated fits. + """ + fitted_gaussian_matrix = {} + + for roi_id in range(n_roi): + fitted_gaussian_matrix[ + (roi_id, "pooled") + ] = get_gaussian_matrix_to_be_plotted( + kind="custom", + roi_id=roi_id, + fit_output=fit_outputs, + sfs=np.asarray(spatial_frequencies), + tfs=np.asarray(temporal_frequencies), + pooled_directions=True, + matrix_definition=matrix_definition, + ) + + return fitted_gaussian_matrix diff --git a/rsp_vision/dashboard/pages/polar_plots.py b/rsp_vision/dashboard/pages/polar_plots.py new file mode 100644 index 0000000..6315a3e --- /dev/null +++ b/rsp_vision/dashboard/pages/polar_plots.py @@ -0,0 +1,69 @@ +import dash_mantine_components as dmc +from dash import html, register_page + +register_page(__name__, path="/polar_plots") + +layout = html.Div( + [ + dmc.Title( + "Polar plots", + className="page-title", + ), + dmc.Grid( + children=[ + dmc.Col( + [ + dmc.Text( + id="selected_data_str_polar", + ), + html.Br(), + html.Br(), + html.Br(), + dmc.NavLink( + label="Back to Data Table", + href="/", + className="navlink", + ), + dmc.NavLink( + label="Murakami plot", + href="/murakami_plot", + className="navlink", + ), + dmc.NavLink( + label="SF-TF facet plot and gaussians", + href="/sf_tf_facet_plot", + className="navlink", + ), + dmc.NavLink( + label="Polar plots", + href="/polar_plots", + className="navlink", + disabled=True, + ), + ], + span=2, + ), + dmc.Col( + [ + html.Div( + id="polar-plots-grid", + className="sf-tf-plot", + ), + ], + span="auto", + ), + dmc.Col( + [ + html.Div( + id="polar-plots", + className="sf-tf-plot", + ), + ], + span=3, + ), + ], + className="sf-tf-container", + ), + ], + className="page", +) diff --git a/rsp_vision/dashboard/pages/sf_tf_facet_plot.py b/rsp_vision/dashboard/pages/sf_tf_facet_plot.py new file mode 100644 index 0000000..3ab8e16 --- /dev/null +++ b/rsp_vision/dashboard/pages/sf_tf_facet_plot.py @@ -0,0 +1,69 @@ +import dash_mantine_components as dmc +from dash import html, register_page + +register_page(__name__, path="/sf_tf_facet_plot") + +layout = html.Div( + [ + dmc.Title( + "SF-TF facet plot and gaussians", + className="page-title", + ), + dmc.Grid( + children=[ + dmc.Col( + [ + dmc.Text( + id="selected_data_str_sf_tf", + ), + html.Br(), + html.Br(), + html.Br(), + dmc.NavLink( + label="Back to Data Table", + href="/", + className="navlink", + ), + dmc.NavLink( + label="Murakami plot", + href="/murakami_plot", + className="navlink", + ), + dmc.NavLink( + label="SF-TF facet plot and gaussians", + href="/sf_tf_facet_plot", + className="navlink", + disabled=True, + ), + dmc.NavLink( + label="Polar plots", + href="/polar_plots", + className="navlink", + ), + ], + span=2, + ), + dmc.Col( + [ + html.Div( + id="sf-tf-plot", + className="sf-tf-plot", + ), + ], + span="auto", + ), + dmc.Col( + [ + html.Div( + id="gaussian-plot", + className="sf-tf-plot", + ), + ], + span=3, + ), + ], + className="sf-tf-container", + ), + ], + className="page", +) diff --git a/rsp_vision/objects/SWC_Blueprint.py b/rsp_vision/objects/SWC_Blueprint.py index 91703d1..22d7fcd 100644 --- a/rsp_vision/objects/SWC_Blueprint.py +++ b/rsp_vision/objects/SWC_Blueprint.py @@ -85,7 +85,7 @@ def __init__( self, swc_blueprint_spec: SWC_Blueprint_Spec, folder_or_table: Union[FolderNamingSpecs, dict], - sub_num: int, + sub_num: int = 0, ): self.swc_blueprint_spec = swc_blueprint_spec if isinstance(folder_or_table, FolderNamingSpecs): @@ -169,7 +169,7 @@ def __init__( self, subject_folder: SubjectFolder, folder_or_table: Union[FolderNamingSpecs, dict], - ses_num: int, + ses_num: int = 0, ): self.subject_folder = subject_folder if isinstance(folder_or_table, FolderNamingSpecs):