Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/sphinx-docs/source/user/menu_bar.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ The File option allows you load data into *SasView* for analysis, or to save the
Data can be loaded one file at a time, or by selecting multiple files, or by loading an entire folder of
files (in which case *SasView* will attempt to make an intelligent guess as to what to load based on the
file formats it recognises in the folder!). Data can also be loaded by dragging and dropping files directly
onto Data Explorer.
onto Data Explorer. Additionally, datasets can be downloaded directly from the SASBDB (Small Angle Scattering
Biological Data Bank) using **File > Load from SASBDB...** (see :ref:`SASBDB_Download` for details).

A *SasView* session can also be saved and reloaded as an 'Analysis' (an individual model fit or invariant
calculation, etc), or as a 'Project' (everything you have done since starting your *SasView* session).
Expand Down
2 changes: 2 additions & 0 deletions docs/sphinx-docs/source/user/tools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ Tools & Utilities

MuMag Tool <qtgui/Utilities/MuMag/mumag_help>

SASBDB Download <qtgui/Utilities/SASBDB/sasbdb_download_help>

233 changes: 233 additions & 0 deletions src/sas/qtgui/MainWindow/GuiManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,7 @@ def addTriggers(self):
# File
self._workspace.actionLoadData.triggered.connect(self.actionLoadData)
self._workspace.actionLoad_Data_Folder.triggered.connect(self.actionLoad_Data_Folder)
self._workspace.actionLoad_SASBDB.triggered.connect(self.actionLoad_SASBDB)
self._workspace.actionOpen_Project.triggered.connect(self.actionOpen_Project)
self._workspace.actionOpen_Analysis.triggered.connect(self.actionOpen_Analysis)
self._workspace.actionSave.triggered.connect(self.actionSave_Project)
Expand Down Expand Up @@ -801,6 +802,238 @@ def actionLoad_Data_Folder(self):
"""
self.filesWidget.loadFolder()

def actionLoad_SASBDB(self):
"""
Menu File/Load from SASBDB

Opens a dialog to download and load a dataset from SASBDB.
"""
from sas.qtgui.Utilities.SASBDB.SASBDBDownloadDialog import SASBDBDownloadDialog

dialog = SASBDBDownloadDialog(parent=self._workspace)
if dialog.exec():
# Get the downloaded file path and metadata
filepath = dialog.getDownloadedFilepath()
dataset_info = dialog.getDatasetInfo()

if filepath and os.path.exists(filepath):
try:
# Load the downloaded file into SasView
# readData returns (output_dict, message)
loaded_data, load_message = self.filesWidget.readData([filepath])

# Populate additional metadata from SASBDB into loaded data
if dataset_info and loaded_data:
self._populateSASBDBMetadata(loaded_data, dataset_info)

# Log metadata summary
if dataset_info:
entry_id = dataset_info.code or dataset_info.entry_id
logger.info(f"Successfully loaded SASBDB dataset {entry_id} from {filepath}")
if dataset_info.title:
logger.info(f" Title: {dataset_info.title}")
if dataset_info.rg is not None:
logger.info(f" Rg: {dataset_info.rg:.2f} Å")
if dataset_info.molecular_weight is not None:
logger.info(f" MW: {dataset_info.molecular_weight:.1f} kDa")
else:
logger.info(f"Successfully loaded SASBDB dataset from {filepath}")

except Exception as e:
logger.error(f"Error loading downloaded SASBDB dataset: {e}", exc_info=True)
QMessageBox.warning(
self._workspace,
"Load Error",
f"Failed to load downloaded dataset:\n{str(e)}"
)

def _populateSASBDBMetadata(self, loaded_data: dict, dataset_info):
"""
Populate SASBDB metadata into loaded data objects.

Updates the sample, source, and other metadata properties of loaded
data with information from SASBDB.

:param loaded_data: Dictionary of loaded data objects (keyed by id)
:param dataset_info: SASBDBDatasetInfo object with parsed metadata
"""
from sasdata.dataloader.data_info import Sample, Source

for data_id, data in loaded_data.items():
try:
# Debug: log what we have in dataset_info
logger.debug(f"Populating metadata for {data_id}: molecule_name={dataset_info.molecule_name}, "
f"sample_name={dataset_info.sample_name}, temperature={dataset_info.temperature}, "
f"concentration={dataset_info.concentration}, buffer={dataset_info.buffer_description}")

# Update title
if dataset_info.title and not data.title:
data.title = dataset_info.title

# Update instrument from SASBDB instrument metadata
if dataset_info.instrument and not data.instrument:
data.instrument = dataset_info.instrument

# Update run info with SASBDB code
if dataset_info.code:
if not data.run:
data.run = []
if dataset_info.code not in data.run:
data.run.append(dataset_info.code)

# Populate sample information from Molecule metadata
if data.sample is None:
data.sample = Sample()

# Ensure sample.details is initialized
if not hasattr(data.sample, 'details') or data.sample.details is None:
data.sample.details = []

# Map molecule short name into Sample ID when available.
sample_id = ""
if dataset_info.molecule_short_name:
sample_id = dataset_info.molecule_short_name
elif dataset_info.sample_name:
# Fallback for entries without molecule short name.
sample_id = dataset_info.sample_name
elif dataset_info.code:
sample_id = dataset_info.code
if sample_id:
data.sample.ID = sample_id
logger.debug(f"Set sample.ID to: {sample_id}")

# Build human-readable sample details from remaining metadata.
sample_details = []
if dataset_info.molecule_name:
molecule_str = f"Molecule: {dataset_info.molecule_name}"
if dataset_info.molecule_type:
molecule_str += f" ({dataset_info.molecule_type})"
sample_details.append(molecule_str)
elif dataset_info.sample_name:
sample_details.append(f"Sample: {dataset_info.sample_name}")

if dataset_info.sample_description:
sample_details.append(
f"Description: {dataset_info.sample_description}"
)

if dataset_info.sequence:
sample_details.append(f"Sequence: {dataset_info.sequence}")

if dataset_info.uniprot_code:
sample_details.append(f"UniProt: {dataset_info.uniprot_code}")

oligomerization = (
dataset_info.oligomerization or dataset_info.oligomeric_state
)
if oligomerization:
sample_details.append(f"Oligomerization: {oligomerization}")

if dataset_info.number_of_molecules:
sample_details.append(
f"Number of molecules: {dataset_info.number_of_molecules}"
)

if dataset_info.source_organism:
sample_details.append(
f"Source organism: {dataset_info.source_organism}"
)

# Set temperature
if dataset_info.temperature is not None:
data.sample.temperature = dataset_info.temperature
if dataset_info.temperature_unit:
data.sample.temperature_unit = dataset_info.temperature_unit
logger.debug(f"Set sample.temperature to: {dataset_info.temperature} {dataset_info.temperature_unit}")
temp_unit = dataset_info.temperature_unit or ""
temp_str = f"Temperature: {dataset_info.temperature}"
if temp_unit:
temp_str += f" {temp_unit}"
sample_details.append(temp_str)

# Store concentration in sample details
if dataset_info.concentration is not None:
conc_str = f"Concentration: {dataset_info.concentration}"
if dataset_info.concentration_unit:
conc_str += f" {dataset_info.concentration_unit}"
sample_details.append(conc_str)

# Add buffer info to sample details
if dataset_info.buffer_description:
buffer_str = f"Buffer: {dataset_info.buffer_description}"
if dataset_info.ph is not None:
buffer_str += f" (pH {dataset_info.ph})"
sample_details.append(buffer_str)

for detail in sample_details:
if detail and detail not in data.sample.details:
data.sample.details.append(detail)
logger.debug(f"Added to sample.details: {detail}")

# Log sample info for debugging
logger.debug(f"Sample populated for {data_id}: name={getattr(data.sample, 'name', None)}, "
f"temperature={getattr(data.sample, 'temperature', None)}, "
f"details={getattr(data.sample, 'details', [])}")

# Populate source information
if data.source is None:
data.source = Source()

if dataset_info.wavelength is not None:
data.source.wavelength = dataset_info.wavelength
data.source.wavelength_unit = dataset_info.wavelength_unit

# Store additional SASBDB metadata in meta_data dictionary
if not hasattr(data, 'meta_data') or data.meta_data is None:
data.meta_data = {}

# SASBDB-specific metadata
data.meta_data['SASBDB_code'] = dataset_info.code or dataset_info.entry_id

if dataset_info.rg is not None:
data.meta_data['SASBDB_Rg'] = dataset_info.rg
if dataset_info.rg_error is not None:
data.meta_data['SASBDB_Rg_error'] = dataset_info.rg_error

if dataset_info.i0 is not None:
data.meta_data['SASBDB_I0'] = dataset_info.i0
if dataset_info.i0_error is not None:
data.meta_data['SASBDB_I0_error'] = dataset_info.i0_error

if dataset_info.dmax is not None:
data.meta_data['SASBDB_Dmax'] = dataset_info.dmax

if dataset_info.molecular_weight is not None:
data.meta_data['SASBDB_MW'] = dataset_info.molecular_weight
if dataset_info.molecular_weight_method:
data.meta_data['SASBDB_MW_method'] = dataset_info.molecular_weight_method

if dataset_info.porod_volume is not None:
data.meta_data['SASBDB_Porod_volume'] = dataset_info.porod_volume

if dataset_info.molecule_name:
data.meta_data['SASBDB_molecule'] = dataset_info.molecule_name

if dataset_info.molecule_type:
data.meta_data['SASBDB_molecule_type'] = dataset_info.molecule_type

if dataset_info.oligomeric_state:
data.meta_data['SASBDB_oligomeric_state'] = dataset_info.oligomeric_state

if dataset_info.publication_doi:
data.meta_data['SASBDB_DOI'] = dataset_info.publication_doi

if dataset_info.publication_pmid:
data.meta_data['SASBDB_PMID'] = dataset_info.publication_pmid

if dataset_info.authors:
data.meta_data['SASBDB_authors'] = ', '.join(dataset_info.authors)

logger.debug(f"Populated SASBDB metadata for data: {data_id}")

except Exception as e:
logger.warning(f"Error populating metadata for data {data_id}: {e}")

def actionOpen_Project(self):
"""
Menu Open Project
Expand Down
13 changes: 13 additions & 0 deletions src/sas/qtgui/MainWindow/UI/MainWindowUI.ui
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
</property>
<addaction name="actionLoadData"/>
<addaction name="actionLoad_Data_Folder"/>
<addaction name="actionLoad_SASBDB"/>
<addaction name="separator"/>
<addaction name="actionOpen_Project"/>
<addaction name="actionOpen_Analysis"/>
Expand Down Expand Up @@ -310,6 +311,18 @@
<string>Load Data Folder</string>
</property>
</action>
<action name="actionLoad_SASBDB">
<property name="icon">
<iconset>
<normaloff>:/res/file_send-128.png</normaloff>:/res/file_send-128.png</iconset>
</property>
<property name="text">
<string>Load from SASBDB...</string>
</property>
<property name="toolTip">
<string>Download and load dataset from SASBDB</string>
</property>
</action>
<action name="actionOpen_Project">
<property name="text">
<string>Open Project</string>
Expand Down
Loading
Loading