I am developing an MRI file viewer app using dash and plotly. The way it works is I can select a specific MRI file from my dataset and the app will generate a slider that could take you through the different MRI slices. There's also a show ROI (ROI is short for region of interest) check box that if toggled will re-size the slicer to only slide through the MRI slices on which the ROI could be shown. The problem is when I uncheck the Display ROI box and go back to the normal view with the original slider the MRI slice gets reset to the first one and not the one I was viewing last. Is there a way to store the slider value when viewing the ROI so that when I uncheck the "Display ROI" box, the same slice I was viewing with the ROI remains selected for the full view? Here's the app's code: # Define the base directory where subdirectories and files are stored BASE_DIRECTORY = path # Function to get subdirectories from the base directory def get_subdirectories(directory): """Returns a sorted list of subdirectories within the given directory.""" return sorted([d for d in os.listdir(directory) if os.path.isdir(os.path.join(directory, d))]) # Function to get files within a selected subdirectory def get_files(directory, subdirectory): """ Returns a sorted list of files within a given subdirectory. Parameters: - directory: The base directory containing subdirectories. - subdirectory: The selected subdirectory to list files from. """ subdirectory_path = os.path.join(directory, subdirectory) # Full path to the selected subdirectory return sorted([f for f in os.listdir(subdirectory_path) if os.path.isfile(os.path.join(subdirectory_path, f))]) # Initialize the Dash app app = dash.Dash(__name__) # Define the layout of the Dash app app.layout = html.Div( # Styling for the entire page style={ "backgroundColor": "#f4f4f4", # Light gray background "padding": "20px", # Padding around content "borderRadius": "10px", # Rounded corners for smooth UI "fontfamily": "Arial, sans-serif", # Font styling "fontweight": "normal" # Normal font weight }, children=[ # Contains all UI elements within this Div # Main header html.H1("Interactive File Selector", style={"textAlign": "center", "color": "#333"}), # Centered header with dark color # Container for dropdowns and output html.Div( # Styling for the container style={ "maxWidth": "600px", # Restricts width for a neat appearance "margin": "auto", # Centers the container "padding": "20px", # Adds padding inside the container "backgroundColor": "white", # White background for contrast "borderRadius": "10px", # Rounded corners "boxShadow": "0 4px 10px rgba(0, 0, 0, 0.1)" # Light shadow for a modern look }, children=[ # Label for subdirectory selection html.Label("Select a Subdirectory:", style={"fontSize": "16px", "fontWeight": "bold"}), # Dropdown for selecting a subdirectory dcc.Dropdown( id='subdirectory-dropdown', # Unique ID to reference this component in callbacks options=[{'label': subdir, 'value': subdir} for subdir in get_subdirectories(BASE_DIRECTORY)], # Generates options dynamically based on available subdirectories placeholder="Select a subdirectory", # Default text displayed before selection style={"marginBottom": "10px"} # Adds spacing below the dropdown ), # Label for file selection html.Label("Select a File:", style={"fontSize": "16px", "fontWeight": "bold"}), # Dropdown for selecting a file (dynamically updated when a subdirectory is selected) dcc.Dropdown( id='file-dropdown', # Unique ID for referencing in callbacks options=[], # Initially empty, will be updated dynamically placeholder="Select an MRI file", # Default text before selection style={"marginBottom": "10px"} # Adds spacing below the dropdown ), html.Br(), # Adds a line break for better spacing # Div to display the selected file html.Div( id='selected-file-output', # Unique ID to update output dynamically style={"fontSize": "16px", "fontWeight": "bold", "color": "#007bff"}), # Display row data in a nice table format (using pandas DataFrame) html.Div(id='row-data-table-output', style={"marginTop": "20px", "marginBottom": "20px"}), # Label for file selection html.Label("Slide the handle to change the viewed slice:", style={"marginTop": "20px", "marginBottom": "10px", "fontSize": "16px", "fontWeight": "bold"}), html.Div( dcc.Slider( id='slice-slider', min=0, max=100, step=1, value=0, marks={i: str(i) for i in range(0, 101, 10)}, tooltip={"placement": "bottom", "always_visible": True} ), style={'width': '80%', 'margin': '20px auto'} # Increase width and center it ), html.Div([dcc.Graph(id='roi-slice-output')]), dcc.Checklist( id='roi-toggle', options=[{'label': 'Show ROI', 'value': 'show_roi'}], value=[], # Default is unchecked style={'marginTop': '10px'} ) ] ) ] ) # Callback to update the file dropdown when a subdirectory is selected @app.callback( Output('file-dropdown', 'options'), # Dynamically updates the file dropdown options Input('subdirectory-dropdown', 'value') # Runs when the user selects a subdirectory ) def update_file_dropdown(selected_subdirectory): """ Updates the file dropdown based on the selected subdirectory. If no subdirectory is selected, it returns an empty list. """ if not selected_subdirectory: return [] # Return empty list if no subdirectory is selected # Retrieve files from the selected subdirectory files = get_files(BASE_DIRECTORY, selected_subdirectory) # Returns a list of dictionaries where each file is {'label': file_name, 'value': file_name} return [{'label': f, 'value': f} for f in files] # Callback to display the selected file @app.callback( Output('selected-file-output', 'children'), # Updates the text display dynamically Input('file-dropdown', 'value') # Runs when a file is selected ) def display_selected_file(selected_file): """ Updates the text output to display the selected file name. If no file is selected, it displays 'No file selected.' """ if not selected_file: return "No file selected." # Default message if no file is chosen return f"Selected File: {selected_file}" # Displays the selected file name # Callback to update slider min, max, and marks dynamically @app.callback( [Output('slice-slider', 'min'), Output('slice-slider', 'max'), Output('slice-slider', 'marks'), Output('slice-slider', 'value')], [Input('file-dropdown', 'value'), Input('roi-toggle', 'value')] ) def update_slider(selected_file, show_roi): if selected_file: if 'show_roi' in show_roi: first_roi_slice = roi_dict[selected_file][1]['roiZ'] last_roi_slice = first_roi_slice + roi_dict[selected_file][1]['roiDepth'] - 1 # Get total slices marks = {i: str(i) for i in range(0, last_roi_slice - first_roi_slice + 1, 1)} return first_roi_slice, last_roi_slice, marks, first_roi_slice # Reset to first slice else: mri_data = roi_dict[selected_file][2] first_slice = 0 last_slice = mri_data.shape[0] marks = {i: str(i) for i in range(0, last_slice, 1)} return first_slice, last_slice, marks, first_slice # Reset to first slice return 0, 10, {0: '0', 10: '10'}, 0 # Default values if no file selected @app.callback( Output('row-data-table-output', 'children'), Input('file-dropdown', 'value') ) def display_mri_row_data(selected_mri_filename): """ This function retrieves the row data from the dictionary and formats it for display. It also fetches the corresponding ROI slice to display in the app. """ if not selected_mri_filename: return "", "", "" # Default message if no file is chosen # Get the corresponding row data from the dictionary roi_dict row = roi_dict[selected_mri_filename][1] # This is a pandas Series # Extract individual fields from the pandas Series examId = row['examId'] seriesNo = row['seriesNo'] aclDiagnosis = row['aclDiagnosis'] kneeLR = row['kneeLR'] roiX = row['roiX'] roiY = row['roiY'] roiZ = row['roiZ'] roiHeight = row['roiHeight'] roiWidth = row['roiWidth'] roiDepth = row['roiDepth'] volumeFilename = row['volumeFilename'] # Format the row data for display row_data_str = html.Div([ f"examId: {examId}", html.Br(), f"seriesNo: {seriesNo}", html.Br(), f"aclDiagnosis: {aclDiagnosis}", html.Br(), f"kneeLR: {kneeLR}", html.Br(), f"roiX: {roiX}", html.Br(), f"roiY: {roiY}", html.Br(), f"roiZ: {roiZ}", html.Br(), f"roiHeight: {roiHeight}", html.Br(), f"roiWidth: {roiWidth}", html.Br(), f"roiDepth: {roiDepth}", html.Br(), f"volumeFilename: {volumeFilename}" ]) # Create a table to display the row data in a structured format row_data_table = html.Table( children=[html.Tr([html.Th(col), html.Td(row[col])]) for col in row.index] ) # Return row data, table, and the ROI images return row_data_table # Callback to display the MRI row data and visualization using matplotlib @app.callback( Output('roi-slice-output', 'figure'), [Input('file-dropdown', 'value'), Input('slice-slider', 'value'), Input('roi-toggle', 'value')] ) def display_mri_with_mask(selected_mri_filename, selected_slice, show_roi): """ This callback function retrieves the MRI data and mask from the selected MRI file, and generates a visualization overlaying the mask on the MRI slice. """ if not selected_mri_filename: blank_image = np.ones((256, 256)) * 255 # White image (all pixels set to 255) return px.imshow(blank_image, color_continuous_scale='gray') # Return an empty figure if no file is chosen # Check if the selected filename exists in roi_dict if selected_mri_filename not in roi_dict: return go.Figure()# Return an empty figure if the filename is invalid # Retrieve the data and mask from roi_dict mri_data = roi_dict[selected_mri_filename][2] mask = roi_dict[selected_mri_filename][3] # Ensure the arrays are NumPy arrays if not isinstance(mri_data, np.ndarray): print(f"Error: MRI data for {selected_mri_filename} is not a numpy array") return go.Figure()# Return an empty figure if the MRI data is not a numpy array if not isinstance(mask, np.ndarray): print(f"Error: Mask for {selected_mri_filename} is not a numpy array") return go.Figure() # Return an empty figure if the mask is not a numpy array # Ensure the slice index is valid (e.g., check for 'roiZ' in metadata) if 'roiZ' not in roi_dict[selected_mri_filename][1]: print(f"Error: 'roiZ' not found in metadata for {selected_mri_filename}") return go.Figure() # Return an empty figure if roiZ is not available try: mri_slice = mri_data[selected_slice, :, :] mask_slice = mask[selected_slice, :, :] if 'show_roi' in show_roi: updated_mri_slice = mri_slice * mask_slice fig = px.imshow(updated_mri_slice, color_continuous_scale='gray', labels={'color': 'MRI Data'}) else: # Plot the MRI data using Plotly Express fig = px.imshow(mri_slice, color_continuous_scale='gray', labels={'color': 'MRI Data'}) except IndexError: print(f"Error: Invalid 'roiZ' index {roi_dict[selected_mri_filename][1]['roiZ']} for {selected_mri_filename}") return go.Figure() # Return an empty figure if the slice index is out of bounds # Update the layout of the figure fig.update_layout( title="MRI with Mask Overlay", coloraxis_showscale=False, template="plotly_white", xaxis={'showgrid': False, 'zeroline': False}, yaxis={'showgrid': False, 'zeroline': False}, dragmode='pan' ) return fig # Return the updated figure and the updated slice index # Run the Dash app if __name__ == '__main__': app.run_server(debug=True, use_reloader=False, port=8000) # Runs the app with debug mode enabled for easier troubleshooting I tried usind dcc.Store() as well as updating the 'value' of 'slice-slider' through my display_mri_with_mask() function by adding Output('slice-slider', 'value') to the callback, but nothing worked. I'm just stuck and any help would be much appreciated! Thank you in advance! Continue reading...