Compare commits
2 Commits
db6164ac3f
...
68f87f0d8d
Author | SHA1 | Date | |
---|---|---|---|
68f87f0d8d | |||
5a0047b6d5 |
@ -278,10 +278,14 @@ class Results(Base):
|
||||
__tablename__ = "results"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
# pgroup = Column(String(255), nullable=False)
|
||||
result = Column(JSON, nullable=True)
|
||||
result_id = Column(Integer, ForeignKey("experiment_parameters.id"), nullable=False)
|
||||
result = Column(JSON, nullable=False) # store the full result object as JSON
|
||||
sample_id = Column(Integer, ForeignKey("samples.id"), nullable=False)
|
||||
run_id = Column(Integer, ForeignKey("experiment_parameters.id"), nullable=False)
|
||||
|
||||
# optional relationships if you wish to query easily
|
||||
# sample = relationship("SampleModel", backref="results")
|
||||
# experiment_parameters = relationship("ExperimentParametersModel",
|
||||
# backref="results")
|
||||
|
||||
|
||||
# method = Column(String(255), nullable=False)
|
||||
|
@ -14,8 +14,10 @@ from app.schemas import (
|
||||
SampleResult,
|
||||
ExperimentParametersCreate,
|
||||
ExperimentParametersRead,
|
||||
# ResultResponse,
|
||||
# ResultCreate,
|
||||
ImageInfo,
|
||||
ResultResponse,
|
||||
ResultCreate,
|
||||
Results as ProcessingResults,
|
||||
)
|
||||
from app.models import (
|
||||
Puck as PuckModel,
|
||||
@ -25,7 +27,7 @@ from app.models import (
|
||||
Dewar as DewarModel,
|
||||
ExperimentParameters as ExperimentParametersModel,
|
||||
# ExperimentParameters,
|
||||
# Results,
|
||||
Results as ResultsModel,
|
||||
)
|
||||
from app.dependencies import get_db
|
||||
import logging
|
||||
@ -246,8 +248,13 @@ async def get_sample_results(active_pgroup: str, db: Session = Depends(get_db)):
|
||||
|
||||
results = []
|
||||
for sample in samples:
|
||||
# Query images associated with the sample.
|
||||
images = db.query(ImageModel).filter(ImageModel.sample_id == sample.id).all()
|
||||
# Query images associated with the sample, including the related event_type
|
||||
images = (
|
||||
db.query(ImageModel)
|
||||
.options(joinedload(ImageModel.sample_event))
|
||||
.filter(ImageModel.sample_id == sample.id)
|
||||
.all()
|
||||
)
|
||||
|
||||
# Query experiment parameters (which include beamline parameters) for the
|
||||
# sample.
|
||||
@ -259,27 +266,34 @@ async def get_sample_results(active_pgroup: str, db: Session = Depends(get_db)):
|
||||
print("Experiment Parameters for sample", sample.id, experiment_parameters)
|
||||
|
||||
results.append(
|
||||
{
|
||||
"sample_id": sample.id,
|
||||
"sample_name": sample.sample_name,
|
||||
"puck_name": sample.puck.puck_name if sample.puck else None,
|
||||
"dewar_name": sample.puck.dewar.dewar_name
|
||||
SampleResult(
|
||||
sample_id=sample.id,
|
||||
sample_name=sample.sample_name,
|
||||
puck_name=sample.puck.puck_name if sample.puck else None,
|
||||
dewar_name=sample.puck.dewar.dewar_name
|
||||
if (sample.puck and sample.puck.dewar)
|
||||
else None,
|
||||
"images": [
|
||||
{"id": img.id, "filepath": img.filepath, "comment": img.comment}
|
||||
images=[
|
||||
ImageInfo(
|
||||
id=img.id,
|
||||
filepath=img.filepath,
|
||||
event_type=img.sample_event.event_type
|
||||
if img.sample_event
|
||||
else "Unknown",
|
||||
comment=img.comment,
|
||||
)
|
||||
for img in images
|
||||
],
|
||||
"experiment_runs": [
|
||||
{
|
||||
"id": ex.id,
|
||||
"run_number": ex.run_number,
|
||||
"beamline_parameters": ex.beamline_parameters,
|
||||
"sample_id": ex.sample_id,
|
||||
}
|
||||
experiment_runs=[
|
||||
ExperimentParametersRead(
|
||||
id=ex.id,
|
||||
run_number=ex.run_number,
|
||||
beamline_parameters=ex.beamline_parameters,
|
||||
sample_id=ex.sample_id,
|
||||
)
|
||||
for ex in experiment_parameters
|
||||
],
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
return results
|
||||
@ -318,43 +332,72 @@ def create_experiment_parameters_for_sample(
|
||||
db.commit()
|
||||
db.refresh(new_exp)
|
||||
|
||||
# Create a "Collecting" sample event associated with the new experiment parameters
|
||||
new_event = SampleEventModel(
|
||||
sample_id=sample_id,
|
||||
event_type="Collecting", # The event type
|
||||
timestamp=datetime.now(), # Use current timestamp
|
||||
)
|
||||
db.add(new_event)
|
||||
db.commit()
|
||||
|
||||
return new_exp
|
||||
|
||||
|
||||
# @router.post("/results", response_model=ResultResponse)
|
||||
# def create_result(result: ResultCreate, db: Session = Depends(get_db)):
|
||||
# # Validate sample_id and result_id (optional but recommended)
|
||||
# sample = db.query(SampleModel).filter_by(id=result.sample_id).first()
|
||||
# if not sample:
|
||||
# raise HTTPException(status_code=404, detail="Sample not found")
|
||||
#
|
||||
# experiment = db.query(ExperimentParameters).filter_by(id=result.result_id).first()
|
||||
# if not experiment:
|
||||
# raise HTTPException(status_code=404, detail="Experiment parameters not found")
|
||||
#
|
||||
# # Create a new Results entry
|
||||
# result_obj = Results(
|
||||
# sample_id=result.sample_id,
|
||||
# result_id=result.result_id,
|
||||
# result=result.result
|
||||
# )
|
||||
# db.add(result_obj)
|
||||
# db.commit()
|
||||
# db.refresh(result_obj)
|
||||
#
|
||||
# return result_obj
|
||||
#
|
||||
# @router.get("/results", response_model=list[ResultResponse])
|
||||
# def get_results(sample_id: int, result_id: int, db: Session = Depends(get_db)):
|
||||
# query = db.query(Results)
|
||||
#
|
||||
# if sample_id:
|
||||
# query = query.filter(Results.sample_id == sample_id)
|
||||
# if result_id:
|
||||
# query = query.filter(Results.result_id == result_id)
|
||||
#
|
||||
# results = query.all()
|
||||
# if not results:
|
||||
# raise HTTPException(status_code=404, detail="No results found")
|
||||
#
|
||||
# return results
|
||||
@router.post("/processing-results", response_model=ResultResponse)
|
||||
def create_result(payload: ResultCreate, db: Session = Depends(get_db)):
|
||||
# Check experiment existence
|
||||
experiment = (
|
||||
db.query(ExperimentParametersModel)
|
||||
.filter(ExperimentParametersModel.id == payload.run_id)
|
||||
.first()
|
||||
)
|
||||
if not experiment:
|
||||
raise HTTPException(
|
||||
status_code=404, detail="Experiment parameters (run) not found"
|
||||
)
|
||||
|
||||
result_entry = ResultsModel(
|
||||
sample_id=payload.sample_id,
|
||||
run_id=payload.run_id,
|
||||
result=payload.result.model_dump(), # Serialize entire result to JSON
|
||||
)
|
||||
|
||||
db.add(result_entry)
|
||||
db.commit()
|
||||
db.refresh(result_entry)
|
||||
|
||||
return ResultResponse(
|
||||
id=result_entry.id,
|
||||
sample_id=result_entry.sample_id,
|
||||
run_id=result_entry.run_id,
|
||||
result=payload.result, # return original payload directly
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/processing-results/{sample_id}/{run_id}", response_model=List[ResultResponse]
|
||||
)
|
||||
async def get_results_for_run_and_sample(
|
||||
sample_id: int, run_id: int, db: Session = Depends(get_db)
|
||||
):
|
||||
results = (
|
||||
db.query(ResultsModel)
|
||||
.filter(ResultsModel.sample_id == sample_id, ResultsModel.run_id == run_id)
|
||||
.all()
|
||||
)
|
||||
|
||||
if not results:
|
||||
raise HTTPException(status_code=404, detail="Results not found.")
|
||||
|
||||
formatted_results = [
|
||||
ResultResponse(
|
||||
id=result.id,
|
||||
sample_id=result.sample_id,
|
||||
run_id=result.run_id,
|
||||
result=ProcessingResults(**result.result),
|
||||
)
|
||||
for result in results
|
||||
]
|
||||
|
||||
return formatted_results
|
||||
|
@ -352,16 +352,6 @@ class SampleEventCreate(BaseModel):
|
||||
event_type: Literal[
|
||||
"Mounting", "Centering", "Failed", "Lost", "Collecting", "Unmounting"
|
||||
]
|
||||
# event_type: str
|
||||
# Validate event_type against accepted event types
|
||||
# @field_validator("event_type", mode="before")
|
||||
# def validate_event_type(cls, value):
|
||||
# allowed = {"Mounting", "Centering", "Failed",
|
||||
# "Lost", "Collecting", "Unmounting"}
|
||||
# if value not in allowed:
|
||||
# raise ValueError(f"Invalid event_type: {value}.
|
||||
# Accepted values are: {allowed}")
|
||||
# return value
|
||||
|
||||
|
||||
class SampleEventResponse(SampleEventCreate):
|
||||
@ -374,10 +364,7 @@ class SampleEventResponse(SampleEventCreate):
|
||||
|
||||
|
||||
class Results(BaseModel):
|
||||
id: int
|
||||
pgroup: str
|
||||
sample_id: int
|
||||
method: str
|
||||
pipeline: str
|
||||
resolution: float
|
||||
unit_cell: str
|
||||
spacegroup: str
|
||||
@ -393,10 +380,6 @@ class Results(BaseModel):
|
||||
unique_refl: int
|
||||
comments: Optional[constr(max_length=200)] = None
|
||||
|
||||
# Define attributes for Results here
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ContactCreate(BaseModel):
|
||||
pgroups: str
|
||||
@ -822,6 +805,21 @@ class ImageInfo(BaseModel):
|
||||
id: int
|
||||
filepath: str
|
||||
comment: Optional[str] = None
|
||||
event_type: str
|
||||
# run_number: Optional[int]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class characterizationParameters(BaseModel):
|
||||
omegaStart_deg: float
|
||||
oscillation_deg: float
|
||||
omegaStep: float
|
||||
chi: float
|
||||
phi: float
|
||||
numberOfImages: int
|
||||
exposureTime_s: float
|
||||
|
||||
|
||||
class RotationParameters(BaseModel):
|
||||
@ -882,6 +880,7 @@ class BeamlineParameters(BaseModel):
|
||||
beamSizeWidth: Optional[float] = None
|
||||
beamSizeHeight: Optional[float] = None
|
||||
# dose_MGy: float
|
||||
characterization: Optional[characterizationParameters] = None
|
||||
rotation: Optional[RotationParameters] = None
|
||||
gridScan: Optional[gridScanParamers] = None
|
||||
jet: Optional[jetParameters] = None
|
||||
@ -922,15 +921,18 @@ class SampleResult(BaseModel):
|
||||
|
||||
class ResultCreate(BaseModel):
|
||||
sample_id: int
|
||||
result_id: int
|
||||
result: Optional[dict]
|
||||
run_id: int
|
||||
result: Results
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ResultResponse(BaseModel):
|
||||
id: int
|
||||
sample_id: int
|
||||
result_id: int
|
||||
result: Optional[dict]
|
||||
run_id: int
|
||||
result: Results
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
@ -156,8 +156,8 @@ def on_startup():
|
||||
load_slots_data(db)
|
||||
else: # dev or test environments
|
||||
print(f"{environment.capitalize()} environment: Regenerating database.")
|
||||
Base.metadata.drop_all(bind=engine)
|
||||
Base.metadata.create_all(bind=engine)
|
||||
# Base.metadata.drop_all(bind=engine)
|
||||
# Base.metadata.create_all(bind=engine)
|
||||
# from sqlalchemy.engine import reflection
|
||||
# from app.models import ExperimentParameters # adjust the import as needed
|
||||
# inspector = reflection.Inspector.from_engine(engine)
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "aareDB"
|
||||
version = "0.1.0a25"
|
||||
version = "0.1.0a26"
|
||||
description = "Backend for next gen sample management system"
|
||||
authors = [{name = "Guillaume Gotthard", email = "guillaume.gotthard@psi.ch"}]
|
||||
license = {text = "MIT"}
|
||||
|
0
backend/tests/test_results.py
Normal file
0
backend/tests/test_results.py
Normal file
@ -171,12 +171,12 @@ async function fetchAndGenerate() {
|
||||
const backendDirectory = (() => {
|
||||
switch (nodeEnv) {
|
||||
case 'prod':
|
||||
return path.resolve('/home/jungfrau/heidi-v2/backend/app'); // Production path
|
||||
return path.resolve('/home/jungfrau/aaredb/backend/app'); // Production path
|
||||
case 'test':
|
||||
return path.resolve('/home/jungfrau/heidi-v2/backend/app'); // Test path
|
||||
return path.resolve('/home/jungfrau/aaredb/backend/app'); // Test path
|
||||
case 'dev':
|
||||
default:
|
||||
return path.resolve('/Users/gotthardg/PycharmProjects/heidi-v2/backend/app'); // Development path
|
||||
return path.resolve('/Users/gotthardg/PycharmProjects/aaredb/backend/app'); // Development path
|
||||
}
|
||||
})();
|
||||
|
||||
|
@ -11,6 +11,8 @@ interface ImageInfo {
|
||||
id: number;
|
||||
filepath: string;
|
||||
comment?: string;
|
||||
event_type: string;
|
||||
run_number?:number;
|
||||
}
|
||||
|
||||
// This represents an experiment run as returned by your API.
|
||||
@ -83,11 +85,12 @@ interface TreeRow {
|
||||
id: string;
|
||||
hierarchy: (string | number)[];
|
||||
type: 'sample' | 'run';
|
||||
experimentId?: number;
|
||||
sample_id: number;
|
||||
sample_name?: string;
|
||||
puck_name?: string;
|
||||
dewar_name?: string;
|
||||
images?: ImageInfo[];
|
||||
images?: ImageInfo[]; // Images associated explicitly with this row (especially run items)
|
||||
run_number?: number;
|
||||
beamline_parameters?: ExperimentParameters['beamline_parameters'];
|
||||
experimentType?: string;
|
||||
@ -176,7 +179,13 @@ const ResultGrid: React.FC<ResultGridProps> = ({ activePgroup }) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch sample details and construct rows
|
||||
if (!OpenAPI.BASE) {
|
||||
console.error('OpenAPI.BASE is not set. Falling back to a default value.');
|
||||
return;
|
||||
}
|
||||
|
||||
setBasePath(`${OpenAPI.BASE}/`);
|
||||
|
||||
SamplesService.getSampleResultsSamplesResultsGet(activePgroup)
|
||||
.then((response: SampleResult[]) => {
|
||||
const treeRows: TreeRow[] = [];
|
||||
@ -190,28 +199,28 @@ const ResultGrid: React.FC<ResultGridProps> = ({ activePgroup }) => {
|
||||
sample_name: sample.sample_name,
|
||||
puck_name: sample.puck_name,
|
||||
dewar_name: sample.dewar_name,
|
||||
images: sample.images,
|
||||
images: sample.images.filter(img => img.event_type === "Centering"),
|
||||
};
|
||||
treeRows.push(sampleRow);
|
||||
|
||||
if (sample.experiment_runs) {
|
||||
sample.experiment_runs.forEach((run) => {
|
||||
sample.experiment_runs?.forEach(run => {
|
||||
const experimentType = getExperimentType(run);
|
||||
const numImages = getNumberOfImages(run);
|
||||
const runRow: TreeRow = {
|
||||
id: `run-${sample.sample_id}-${run.run_number}`,
|
||||
hierarchy: [sample.sample_id, run.run_number],
|
||||
type: 'run',
|
||||
experimentId: run.id,
|
||||
sample_id: sample.sample_id,
|
||||
run_number: run.run_number,
|
||||
beamline_parameters: run.beamline_parameters,
|
||||
experimentType,
|
||||
numberOfImages: numImages,
|
||||
images: sample.images,
|
||||
images: sample.images.filter(img =>
|
||||
img.event_type === "Collecting" ),
|
||||
};
|
||||
treeRows.push(runRow);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
setRows(treeRows);
|
||||
@ -221,6 +230,7 @@ const ResultGrid: React.FC<ResultGridProps> = ({ activePgroup }) => {
|
||||
});
|
||||
}, [activePgroup]);
|
||||
|
||||
|
||||
// Define the grid columns
|
||||
const columns: GridColDef[] = [
|
||||
{
|
||||
@ -299,8 +309,10 @@ const ResultGrid: React.FC<ResultGridProps> = ({ activePgroup }) => {
|
||||
return (
|
||||
<RunDetails
|
||||
run={params.row}
|
||||
runId={params.row.experimentId}
|
||||
sample_id={params.row.sample_id}
|
||||
basePath={basePath}
|
||||
onHeightChange={(height: number) => handleDetailPanelHeightChange(params.row.id, height)} // Pass callback for dynamic height
|
||||
onHeightChange={height => handleDetailPanelHeightChange(params.row.id, height)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,84 +1,154 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
Typography,
|
||||
Grid,
|
||||
Modal,
|
||||
Box
|
||||
Accordion, AccordionSummary, AccordionDetails, Typography, Grid, Modal, Box
|
||||
} from '@mui/material';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import './SampleImage.css';
|
||||
import { DataGridPremium, GridColDef } from "@mui/x-data-grid-premium";
|
||||
import { SamplesService } from "../../openapi";
|
||||
|
||||
interface RunDetailsProps {
|
||||
run: ExperimentParameters;
|
||||
run: TreeRow;
|
||||
runId: number;
|
||||
sample_id: number;
|
||||
basePath: string;
|
||||
onHeightChange?: (height: number) => void; // Callback to notify the parent about height changes
|
||||
onHeightChange?: (height: number) => void;
|
||||
}
|
||||
|
||||
const RunDetails: React.FC<RunDetailsProps> = ({ run, onHeightChange, basePath }) => {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null); // Ref to track component height
|
||||
|
||||
interface ExperimentParameters {
|
||||
run_number: number;
|
||||
id: number;
|
||||
sample_id: number;
|
||||
beamline_parameters: BeamlineParameters;
|
||||
images: Image[];
|
||||
}
|
||||
|
||||
|
||||
interface ProcessingResults {
|
||||
pipeline: string;
|
||||
resolution: number;
|
||||
unit_cell: string;
|
||||
spacegroup: string;
|
||||
rmerge: number;
|
||||
rmeas: number;
|
||||
isig: number;
|
||||
cc: number;
|
||||
cchalf: number;
|
||||
completeness: number;
|
||||
multiplicity: number;
|
||||
nobs: number;
|
||||
total_refl: number;
|
||||
unique_refl: number;
|
||||
comments?: string | null;
|
||||
}
|
||||
|
||||
const RunDetails: React.FC<RunDetailsProps> = ({ run, onHeightChange, basePath, runId, sample_id }) => {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [currentHeight, setCurrentHeight] = useState<number>(0);
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false); // For modal state
|
||||
const [selectedImage, setSelectedImage] = useState<string | null>(null); // Tracks the selected image for the modal
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
||||
const [expandedResults, setExpandedResults] = useState(false);
|
||||
const [processingResult, setProcessingResult] = useState<ProcessingResults[] | null>(null);
|
||||
|
||||
const {beamline_parameters, images} = run;
|
||||
const {synchrotron, beamline, detector} = beamline_parameters;
|
||||
|
||||
// Calculate and notify the parent about height changes
|
||||
useEffect(() => {
|
||||
fetchResults(sample_id, runId); // fetching based on experimentId
|
||||
}, [runId]);
|
||||
|
||||
const fetchResults = async (sample_id: number, runId: number) => {
|
||||
try {
|
||||
const results = await SamplesService.getResultsForRunAndSampleSamplesProcessingResultsSampleIdRunIdGet(sample_id, runId);
|
||||
|
||||
// Explicitly handle nested results
|
||||
const mappedResults: ProcessingResults[] = results.map((res): ProcessingResults => ({
|
||||
pipeline: res.result?.pipeline || 'N/A',
|
||||
resolution: res.result.resolution ?? 0,
|
||||
unit_cell: res.result?.unit_cell || 'N/A',
|
||||
spacegroup: res.result?.spacegroup || 'N/A',
|
||||
rmerge: res.result?.rmerge ?? 0,
|
||||
rmeas: res.result?.rmeas ?? 0,
|
||||
isig: res.result?.isig ?? 0,
|
||||
cc: res.result?.cc ?? 0,
|
||||
cchalf: res.result?.cchalf ?? 0,
|
||||
completeness: res.result?.completeness ?? 0,
|
||||
multiplicity: res.result?.multiplicity ?? 0,
|
||||
nobs: res.result?.nobs ?? 0,
|
||||
total_refl: res.result?.total_refl ?? 0,
|
||||
unique_refl: res.result?.unique_refl ?? 0,
|
||||
comments: res.result?.comments || null,
|
||||
}));
|
||||
|
||||
setProcessingResult(mappedResults);
|
||||
} catch (error) {
|
||||
console.error('Error fetching results:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const resultColumns: GridColDef[] = [
|
||||
{field: 'pipeline', headerName: 'Pipeline', flex: 1},
|
||||
{field: 'resolution', headerName: 'Resolution (Å)', flex: 1},
|
||||
{field: 'unit_cell', headerName: 'Unit Cell (Å)', flex: 1.5},
|
||||
{field: 'spacegroup', headerName: 'Spacegroup', flex: 1},
|
||||
{field: 'rmerge', headerName: 'Rmerge', flex: 1},
|
||||
{field: 'rmeas', headerName: 'Rmeas', flex: 1},
|
||||
{field: 'isig', headerName: 'I/sig(I)', flex: 1},
|
||||
{field: 'cc', headerName: 'CC', flex: 1},
|
||||
{field: 'cchalf', headerName: 'CC(1/2)', flex: 1},
|
||||
{field: 'completeness', headerName: 'Completeness (%)', flex: 1},
|
||||
{field: 'multiplicity', headerName: 'Multiplicity', flex: 1},
|
||||
{field: 'nobs', headerName: 'N obs.', flex: 1},
|
||||
{field: 'total_refl', headerName: 'Total Reflections', flex: 1},
|
||||
{field: 'unique_refl', headerName: 'Unique Reflections', flex: 1},
|
||||
{field: 'comments', headerName: 'Comments', flex: 2},
|
||||
];
|
||||
|
||||
const updateHeight = () => {
|
||||
if (containerRef.current) {
|
||||
const newHeight = containerRef.current.offsetHeight;
|
||||
if (newHeight !== currentHeight) {
|
||||
if (newHeight !== currentHeight && onHeightChange) {
|
||||
setCurrentHeight(newHeight);
|
||||
if (onHeightChange) {
|
||||
onHeightChange(newHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateHeight(); // Update height on initial render
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Update height whenever the component content changes
|
||||
const observer = new ResizeObserver(updateHeight);
|
||||
if (containerRef.current) {
|
||||
observer.observe(containerRef.current);
|
||||
}
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [containerRef]);
|
||||
if (containerRef.current) observer.observe(containerRef.current);
|
||||
return () => observer.disconnect();
|
||||
}, [containerRef.current, processingResult]);
|
||||
|
||||
const handleImageClick = (imagePath: string) => {
|
||||
setSelectedImage(imagePath);
|
||||
setModalOpen(true); // Open the modal when the image is clicked
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setSelectedImage(null); // Clear the current image
|
||||
setSelectedImage(null);
|
||||
setModalOpen(false);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="details-panel" // Add the class here
|
||||
ref={containerRef} // Attach the ref to the main container
|
||||
className="details-panel"
|
||||
ref={containerRef}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column', // Stack children vertically
|
||||
gap: '16px',
|
||||
padding: '16px',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '4px',
|
||||
alignItems: 'flex-start',
|
||||
}}
|
||||
>
|
||||
|
||||
{/* Wrap details and images together */}
|
||||
<div style={{display: 'flex', gap: '16px', alignItems: 'flex-start'}}>
|
||||
{/* Main Details Section */}
|
||||
<div style={{flexGrow: 1}}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
@ -95,30 +165,23 @@ const RunDetails: React.FC<RunDetailsProps> = ({ run, onHeightChange, basePath }
|
||||
aria-controls="detector-content"
|
||||
id="detector-header"
|
||||
>
|
||||
<Typography>
|
||||
<strong>Detector Details</strong>
|
||||
</Typography>
|
||||
<Typography><strong>Detector Details</strong></Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography>Manufacturer: {detector?.manufacturer || 'N/A'}</Typography>
|
||||
<Typography>Model: {detector?.model || 'N/A'}</Typography>
|
||||
<Typography>Type: {detector?.type || 'N/A'}</Typography>
|
||||
<Typography>
|
||||
Beam Center (px): x: {detector?.beamCenterX_px || 'N/A'}, y: {detector?.beamCenterY_px || 'N/A'}
|
||||
Beam Center (px): x: {detector?.beamCenterX_px || 'N/A'},
|
||||
y: {detector?.beamCenterY_px || 'N/A'}
|
||||
</Typography>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
||||
{/* Beamline Details Accordion */}
|
||||
<Accordion>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls="beamline-content"
|
||||
id="beamline-header"
|
||||
>
|
||||
<Typography>
|
||||
<strong>Beamline Details</strong>
|
||||
</Typography>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon/>}>
|
||||
<Typography><strong>Beamline Details</strong></Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography>Synchrotron: {beamline_parameters?.synchrotron || 'N/A'}</Typography>
|
||||
@ -134,24 +197,20 @@ const RunDetails: React.FC<RunDetailsProps> = ({ run, onHeightChange, basePath }
|
||||
|
||||
{/* Beam Characteristics Accordion */}
|
||||
<Accordion>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls="beam-content"
|
||||
id="beam-header"
|
||||
>
|
||||
<Typography>
|
||||
<strong>Beam Characteristics</strong>
|
||||
</Typography>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon/>}>
|
||||
<Typography><strong>Beam Characteristics</strong></Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography>Wavelength: {beamline_parameters?.wavelength || 'N/A'}</Typography>
|
||||
<Typography>Energy: {beamline_parameters?.energy || 'N/A'}</Typography>
|
||||
<Typography>Transmission: {beamline_parameters?.transmission || 'N/A'}</Typography>
|
||||
<Typography>
|
||||
Beam focus (µm): vertical: {beamline_parameters?.beamSizeHeight || 'N/A'}, horizontal:{' '}
|
||||
Beam focus (µm): vertical: {beamline_parameters?.beamSizeHeight || 'N/A'},
|
||||
horizontal:{' '}
|
||||
{beamline_parameters?.beamSizeWidth || 'N/A'}
|
||||
</Typography>
|
||||
<Typography>Flux at sample (ph/s): {beamline_parameters?.beamlineFluxAtSample_ph_s || 'N/A'}</Typography>
|
||||
<Typography>Flux at sample
|
||||
(ph/s): {beamline_parameters?.beamlineFluxAtSample_ph_s || 'N/A'}</Typography>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</div>
|
||||
@ -167,18 +226,16 @@ const RunDetails: React.FC<RunDetailsProps> = ({ run, onHeightChange, basePath }
|
||||
<Grid item xs={4} key={img.id}>
|
||||
<div
|
||||
className="image-container"
|
||||
onClick={() => handleImageClick(`${basePath || ''}${img.filepath}`)} // Open modal with image
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => handleImageClick(`${basePath || ''}${img.filepath}`)}
|
||||
style={{cursor: 'pointer'}}
|
||||
>
|
||||
<img
|
||||
src={`${basePath || ''}${img.filepath}`} // Ensure basePath
|
||||
src={`${basePath || ''}${img.filepath}`}
|
||||
alt={img.comment || 'Image'}
|
||||
className="zoom-image"
|
||||
style={{
|
||||
width: '100%', // Ensure the image takes the full width of its container
|
||||
maxWidth: '100%', // Prevent any overflow
|
||||
width: '100%',
|
||||
maxWidth: '100%',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
@ -192,6 +249,32 @@ const RunDetails: React.FC<RunDetailsProps> = ({ run, onHeightChange, basePath }
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Processing Results Accordion - Full Width Below */}
|
||||
<div style={{width: '100%'}}>
|
||||
<Accordion expanded={expandedResults} onChange={(e, expanded) => setExpandedResults(expanded)}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon/>}>
|
||||
<Typography><strong>Processing Results</strong></Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails style={{width: '100%', overflowX: 'auto'}}>
|
||||
{processingResult ? (
|
||||
<div style={{width: '100%'}}>
|
||||
<DataGridPremium
|
||||
rows={processingResult.map((res, idx) => ({id: idx, ...res}))}
|
||||
columns={resultColumns}
|
||||
autoHeight
|
||||
hideFooter
|
||||
columnVisibilityModel={{id: false}}
|
||||
disableColumnResize={false}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Typography variant="body2" color="textSecondary">Loading results...</Typography>
|
||||
)}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</div>
|
||||
|
||||
{/* Modal for Zoomed Image */}
|
||||
<Modal open={modalOpen} onClose={closeModal}>
|
||||
@ -224,5 +307,4 @@ const RunDetails: React.FC<RunDetailsProps> = ({ run, onHeightChange, basePath }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RunDetails;
|
1070
testfunctions.ipynb
1070
testfunctions.ipynb
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user