Skip to content
Merged
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
8 changes: 4 additions & 4 deletions laue_portal/components/validation_alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
validation_alerts = html.Div([
dbc.Alert(
dbc.Row([
dbc.Col(html.H4("Validation status", className="alert-heading mb-0"), width=2),
dbc.Col(html.H4("Validation: Info", className="alert-heading mb-0"), width=2),
dbc.Col(html.P("Click 'Validate' button to check inputs.", className="mb-0"), width="auto"),
], align="center"),
id="alert-validation-info",
Expand All @@ -24,7 +24,7 @@
),
dbc.Alert(
dbc.Row([
dbc.Col(html.H4("Validate status: Success!", className="alert-heading mb-0"), width=2),
dbc.Col(html.H4("Validation: Success", className="alert-heading mb-0"), width=2),
dbc.Col(html.P("All inputs are valid. You can submit the job.", className="mb-0"), width="auto"),
], align="center"),
id="alert-validation-success",
Expand All @@ -35,7 +35,7 @@
),
dbc.Alert(
dbc.Row([
dbc.Col(html.H4("Validate status: Error!", className="alert-heading mb-0"), width=3),
dbc.Col(html.H4("Validation: Error", className="alert-heading mb-0"), width=3),
dbc.Col(html.Div(id="alert-validation-error-message", className="mb-0"), width="auto"),
], align="center"),
id="alert-validation-error",
Expand All @@ -46,7 +46,7 @@
),
dbc.Alert(
dbc.Row([
dbc.Col(html.H4("Validate status: Warning!", className="alert-heading mb-0"), width=3),
dbc.Col(html.H4("Validation: Warning", className="alert-heading mb-0"), width=3),
dbc.Col(html.Div(id="alert-validation-warning-message", className="mb-0"), width="auto"),
], align="center"),
id="alert-validation-warning",
Expand Down
2 changes: 1 addition & 1 deletion laue_portal/database/models/peakindex.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class PeakIndex(Base):

# Peak Index Metadata
peakindex_id: Mapped[int] = mapped_column(primary_key=True)
scanNumber: Mapped[int] = mapped_column(ForeignKey("metadata.scanNumber"))
scanNumber: Mapped[int | None] = mapped_column(ForeignKey("metadata.scanNumber"), nullable=True)
job_id: Mapped[int] = mapped_column(ForeignKey("job.job_id"), unique=True)

filefolder: Mapped[str] = mapped_column(String) # infile
Expand Down
2 changes: 1 addition & 1 deletion laue_portal/database/models/wire_recon.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class WireRecon(Base):

# Wire Recon Metadata
wirerecon_id: Mapped[int] = mapped_column(primary_key=True)
scanNumber: Mapped[int] = mapped_column(ForeignKey("metadata.scanNumber"))
scanNumber: Mapped[int | None] = mapped_column(ForeignKey("metadata.scanNumber"), nullable=True)
# calib_id: Mapped[int] = mapped_column(ForeignKey("calib.calib_id"))
job_id: Mapped[int] = mapped_column(ForeignKey("job.job_id"), unique=True)

Expand Down
155 changes: 148 additions & 7 deletions laue_portal/pages/create_peakindexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,9 @@ def validate_peakindexing_inputs(ctx):
add_validation_message(validation_result, 'successes', 'root_path')

# Parse IDnumber to get scanNumber, wirerecon_id, recon_id, peakindex_id
# Mark IDnumber as handled so the general loop skips it
parsed_fields['IDnumber'] = IDnumber

if IDnumber:
try:
id_dict = parse_IDnumber(IDnumber, session)
Expand All @@ -455,11 +458,19 @@ def validate_peakindexing_inputs(ctx):
parsed_fields[key] = parse_parameter(value, num_inputs)
add_validation_message(validation_result, 'successes', 'IDnumber')
except ValueError as e:
add_validation_message(validation_result, 'errors', 'IDnumber',
custom_message=f"ID Number parsing error: {str(e)}")
error_message = str(e)
# Check if this is a "not found" error - treat as warning instead of error
if "not found in database" in error_message:
add_validation_message(validation_result, 'warnings', 'IDnumber',
custom_message=f"ID Number warning: {error_message}. This will create an unlinked peak indexing.")
else:
# Other parsing errors (invalid format, etc.) remain as errors
add_validation_message(validation_result, 'errors', 'IDnumber',
custom_message=f"ID Number parsing error: {error_message}")
else:
# IDnumber is optional - if not provided, just skip
pass
# IDnumber is optional - if not provided, show warning about unlinked indexing
add_validation_message(validation_result, 'warnings', 'IDnumber',
custom_message="No ID Number provided. This will create an unlinked peak indexing.")

# Validate all other fields by iterating over all_fields
for field_name, field_value in all_fields.items():
Expand Down Expand Up @@ -1738,6 +1749,7 @@ def load_scan_data_from_url(href):
With recon_id: /create-peakindexing?scan_id={scan_id}&recon_id={recon_id}
With wirerecon_id: /create-peakindexing?scan_id={scan_id}&wirerecon_id={wirerecon_id}
With peakindex_id: /create-peakindexing?scan_id={scan_id}&peakindex_id={peakindex_id}
With peakindex_id only (unlinked): /create-peakindexing?peakindex_id={peakindex_id}
With both recon_id and peakindex_id: /create-peakindexing?scan_id={scan_id}&recon_id={recon_id}&peakindex_id={peakindex_id}
"""
if not href:
Expand All @@ -1754,6 +1766,131 @@ def load_scan_data_from_url(href):

root_path = DEFAULT_VARIABLES.get("root_path", "")

# Handle case where only peakindex_id is provided (unlinked peakindex)
if not scan_id_str and peakindex_id_str:
with Session(session_utils.get_engine()) as session:
try:
peakindex_ids = [int(pid) if pid and pid.lower() != 'none' else None for pid in (peakindex_id_str.split(',') if peakindex_id_str else [])]

peakindex_form_data_list = []
found_items = []
not_found_items = []

for current_peakindex_id in peakindex_ids:
if not current_peakindex_id:
continue

# Query peakindex data directly
peakindex_data = session.query(db_schema.PeakIndex).filter(
db_schema.PeakIndex.peakindex_id == current_peakindex_id
).first()

if peakindex_data:
found_items.append(f"peak index {current_peakindex_id}")

# Use existing peakindex data as the base
peakindex_form_data = peakindex_data

# Get the scan/recon IDs from the peakindex if they exist
current_scan_id = peakindex_data.scanNumber
current_wirerecon_id = peakindex_data.wirerecon_id
current_recon_id = peakindex_data.recon_id

# Build output folder template
outputFolder = build_output_folder_template(
scan_num_int=current_scan_id,
data_path=None,
wirerecon_id_int=current_wirerecon_id,
recon_id_int=current_recon_id
)

# Clear peakindex_id since we're creating a NEW peakindex
peakindex_form_data.peakindex_id = None
peakindex_form_data.outputFolder = outputFolder

# Convert file paths to relative paths
peakindex_form_data.geoFile = remove_root_path_prefix(peakindex_data.geoFile, root_path)
peakindex_form_data.crystFile = remove_root_path_prefix(peakindex_data.crystFile, root_path)
if peakindex_data.filefolder:
peakindex_form_data.data_path = remove_root_path_prefix(peakindex_data.filefolder, root_path)
if peakindex_data.filenamePrefix:
peakindex_form_data.filenamePrefix = peakindex_data.filenamePrefix

# Add root_path
peakindex_form_data.root_path = root_path

peakindex_form_data_list.append(peakindex_form_data)
else:
not_found_items.append(f"peak index {current_peakindex_id}")

# Create pooled peakindex_form_data by combining values from all peakindexes
if peakindex_form_data_list:
pooled_peakindex_form_data = db_schema.PeakIndex()

# Pool all attributes
all_attrs = list(db_schema.PeakIndex.__table__.columns.keys()) + ['root_path', 'data_path', 'filenamePrefix']

for attr in all_attrs:
values = []
for d in peakindex_form_data_list:
if hasattr(d, attr):
values.append(getattr(d, attr))

if values:
pooled_value = _merge_field_values(values)
setattr(pooled_peakindex_form_data, attr, pooled_value)

# User text
pooled_peakindex_form_data.author = DEFAULT_VARIABLES['author']
pooled_peakindex_form_data.notes = DEFAULT_VARIABLES['notes']

# Populate the form
set_peakindex_form_props(pooled_peakindex_form_data)

# Set alert based on what was found
if not_found_items:
set_props("alert-scan-loaded", {
'is_open': True,
'children': f"Loaded data for {len(found_items)} items. Could not find: {', '.join(not_found_items)}.",
'color': 'warning'
})
else:
set_props("alert-scan-loaded", {
'is_open': True,
'children': f"Successfully loaded and merged data from {len(found_items)} items into the form.",
'color': 'success'
})
else:
# No valid peakindexes found
set_props("alert-scan-loaded", {
'is_open': True,
'children': f"Could not find any of the requested items: {', '.join(not_found_items)}. Displaying default values.",
'color': 'danger'
})

# Show defaults
peakindex_form_data = db_schema.PeakIndex(
# User text
author=DEFAULT_VARIABLES['author'],
notes=DEFAULT_VARIABLES['notes'],
# Processing parameters
**{k: v for k, v in PEAKINDEX_DEFAULTS.items()}
)
peakindex_form_data.root_path = root_path
peakindex_form_data.data_path = ""
peakindex_form_data.filenamePrefix = ""
set_peakindex_form_props(peakindex_form_data)

except Exception as e:
set_props("alert-scan-loaded", {
'is_open': True,
'children': f'Error loading peakindex data: {str(e)}',
'color': 'danger'
})

return datetime.datetime.now().isoformat()

# Original behavior: scan_id is provided
if scan_id_str:
with Session(session_utils.get_engine()) as session:
# # Get next peakindex_id
Expand Down Expand Up @@ -1836,9 +1973,13 @@ def load_scan_data_from_url(href):
# Use existing peakindex data as the base
peakindex_form_data = peakindex_data
# Update only the necessary fields
# peakindex_form_data.scanNumber = current_scan_id
# peakindex_form_data.recon_id = current_recon_id
# peakindex_form_data.wirerecon_id = current_wirerecon_id
# Override the scan/recon IDs with what was passed in the URL
# This ensures the ID Number field shows the underlying scan, not the peakindex
peakindex_form_data.scanNumber = current_scan_id
peakindex_form_data.recon_id = current_recon_id
peakindex_form_data.wirerecon_id = current_wirerecon_id
# Clear peakindex_id since we're creating a NEW peakindex, not editing the existing one
peakindex_form_data.peakindex_id = None
peakindex_form_data.outputFolder = outputFolder
# Convert file paths to relative paths
peakindex_form_data.geoFile = remove_root_path_prefix(peakindex_data.geoFile, root_path)
Expand Down
35 changes: 16 additions & 19 deletions laue_portal/pages/create_wire_reconstruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,21 +285,7 @@ def validate_wire_reconstruction_inputs(ctx):
# Extract individual field values
root_path = all_fields.get('root_path', '')
IDnumber = all_fields.get('IDnumber', '')
# Old individual ID fields (now parsed from IDnumber):
# scanNumber = all_params.get('scanNumber')
# wirerecon_id = all_params.get('wirerecon_id')

# Other parameters (kept as comments for reference):
# data_path = all_fields.get('data_path')
# filenamePrefix = all_params.get('filenamePrefix')
# scanPoints = all_params.get('scanPoints')
# geoFile = all_params.get('geoFile')
# depth_start = all_params.get('depth_start')
# depth_end = all_params.get('depth_end')
# depth_resolution = all_params.get('depth_resolution')
# percent_brightest = all_params.get('percent_brightest')
# outputFolder = all_params.get('outputFolder')
# scanNumber = all_params.get('scanNumber')


# Validate root_path directory exists
if not root_path:
Expand All @@ -312,6 +298,9 @@ def validate_wire_reconstruction_inputs(ctx):
add_validation_message(validation_result, 'successes', 'root_path')

# Parse IDnumber to get scanNumber, wirerecon_id
# Mark IDnumber as handled so the general loop skips it
parsed_fields['IDnumber'] = IDnumber

if IDnumber:
try:
id_dict = parse_IDnumber(IDnumber, session)
Expand All @@ -321,11 +310,19 @@ def validate_wire_reconstruction_inputs(ctx):
parsed_fields[key] = parse_parameter(value, num_inputs)
add_validation_message(validation_result, 'successes', 'IDnumber')
except ValueError as e:
add_validation_message(validation_result, 'errors', 'IDnumber',
custom_message=f"ID Number parsing error: {str(e)}")
error_message = str(e)
# Check if this is a "not found" error - treat as warning instead of error
if "not found in database" in error_message:
add_validation_message(validation_result, 'warnings', 'IDnumber',
custom_message=f"ID Number warning: {error_message}. This will create an unlinked wire reconstruction.")
else:
# Other parsing errors (invalid format, etc.) remain as errors
add_validation_message(validation_result, 'errors', 'IDnumber',
custom_message=f"ID Number parsing error: {error_message}")
else:
# IDnumber is optional - if not provided, just skip
pass
# IDnumber is optional - if not provided, show warning about unlinked reconstruction
add_validation_message(validation_result, 'warnings', 'IDnumber',
custom_message="No ID Number provided. This will create an unlinked wire reconstruction.")

# Validate all other fields by iterating over all_fields
for field_name, field_value in all_fields.items():
Expand Down
24 changes: 18 additions & 6 deletions laue_portal/pages/peakindexings.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ def _get_peakindexings():
col_def['cellRenderer'] = 'DatasetIdScanLinkRenderer'
elif field_key == 'scanNumber':
col_def['cellRenderer'] = 'ScanLinkRenderer' # Use the custom JS renderer
# Handle NULL scanNumber for unlinked indexes
col_def['valueGetter'] = {"function": "params.data.scanNumber == null ? 'Unlinked' : params.data.scanNumber"}
elif field_key in ['submit_time', 'start_time', 'finish_time']:
col_def['cellRenderer'] = 'DateFormatter' # Use the date formatter for datetime fields
elif field_key == 'status':
Expand Down Expand Up @@ -254,18 +256,28 @@ def selected_peakindex_href(rows,href):
scan_ids, wirerecon_ids, recon_ids, peakindex_ids = [], [], [], []

for row in rows:
# scanNumber can be None for unlinked peakindexes - that's okay
if row.get('scanNumber'):
scan_ids.append(str(row['scanNumber']))
else:
return base_href
# Note: We don't return base_href here anymore - unlinked peakindexes are valid

wirerecon_ids.append(str(row['wirerecon_id']) if row.get('wirerecon_id') else '')
recon_ids.append(str(row['recon_id']) if row.get('recon_id') else '')
peakindex_ids.append(str(row['peakindex_id']) if row.get('peakindex_id') else '')

query_params = [f"scan_id={','.join(scan_ids)}"]
if any(wirerecon_ids): query_params.append(f"wirerecon_id={','.join(wirerecon_ids)}")
if any(recon_ids): query_params.append(f"recon_id={','.join(recon_ids)}")
if any(peakindex_ids): query_params.append(f"peakindex_id={','.join(peakindex_ids)}")
# Build query params - only include non-empty lists
query_params = []
if any(scan_ids):
query_params.append(f"scan_id={','.join(scan_ids)}")
if any(wirerecon_ids):
query_params.append(f"wirerecon_id={','.join(wirerecon_ids)}")
if any(recon_ids):
query_params.append(f"recon_id={','.join(recon_ids)}")
if any(peakindex_ids):
query_params.append(f"peakindex_id={','.join(peakindex_ids)}")

# If no query params at all, return base href
if not query_params:
return base_href

return f"{base_href}?{'&'.join(query_params)}"
Loading