Code indexing in gitaly is broken and leads to code not being visible to the user. We work on the issue with highest priority.

Skip to content
Snippets Groups Projects
Commit 2e1d87c3 authored by GotthardG's avatar GotthardG
Browse files

Update dependencies and migrate to Node 18 minimum support

Upgraded multiple package versions, including `@esbuild` and dependencies like `@mui/x-data-grid-premium`. Adjusted `node` engine requirement to `>=18` for compatibility. This ensures modernization and alignment with current toolchain standards.
parent c3cf463f
No related branches found
No related tags found
No related merge requests found
Pipeline #51553 failed
......@@ -260,6 +260,14 @@ class Image(Base):
sample_id = Column(Integer, ForeignKey("samples.id"), nullable=False)
class ExperimentParameters(Base):
__tablename__ = "experiment_parameters"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
run_number = Column(Integer, nullable=False)
beamline_parameters = Column(JSON, nullable=True)
sample_id = Column(Integer, ForeignKey("samples.id"), nullable=False)
# class Results(Base):
# __tablename__ = "results"
#
......
......@@ -12,6 +12,8 @@ from app.schemas import (
Image,
ImageCreate,
SampleResult,
ExperimentParametersCreate,
ExperimentParametersRead,
)
from app.models import (
Puck as PuckModel,
......@@ -19,6 +21,7 @@ from app.models import (
SampleEvent as SampleEventModel,
Image as ImageModel,
Dewar as DewarModel,
ExperimentParameters as ExperimentParametersModel,
)
from app.dependencies import get_db
import logging
......@@ -192,7 +195,7 @@ async def upload_sample_image(
@router.get("/results", response_model=List[SampleResult])
async def get_sample_results(active_pgroup: str, db: Session = Depends(get_db)):
# Query samples for the active pgroup using joins
# Query samples for the active pgroup using joins.
samples = (
db.query(SampleModel)
.join(SampleModel.puck)
......@@ -207,8 +210,18 @@ async def get_sample_results(active_pgroup: str, db: Session = Depends(get_db)):
results = []
for sample in samples:
# Query images associated with each sample
# Query images associated with the sample.
images = db.query(ImageModel).filter(ImageModel.sample_id == sample.id).all()
# Query experiment parameters (which include beamline parameters) for the
# sample.
experiment_parameters = (
db.query(ExperimentParametersModel)
.filter(ExperimentParametersModel.sample_id == sample.id)
.all()
)
print("Experiment Parameters for sample", sample.id, experiment_parameters)
results.append(
{
"sample_id": sample.id,
......@@ -221,7 +234,52 @@ async def get_sample_results(active_pgroup: str, db: Session = Depends(get_db)):
{"id": img.id, "filepath": img.filepath, "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,
}
for ex in experiment_parameters
],
}
)
return results
@router.post(
"/samples/{sample_id}/experiment_parameters",
response_model=ExperimentParametersRead,
)
def create_experiment_parameters_for_sample(
sample_id: int,
exp_params: ExperimentParametersCreate,
db: Session = Depends(get_db),
):
# Calculate the new run_number for the given sample.
# This assumes that the run_number is computed as one plus the maximum
# current value.
last_exp = (
db.query(ExperimentParametersModel)
.filter(ExperimentParametersModel.sample_id == sample_id)
.order_by(ExperimentParametersModel.run_number.desc())
.first()
)
new_run_number = last_exp.run_number + 1 if last_exp else 1
# Create a new ExperimentParameters record. The beamline_parameters are
# stored as JSON.
new_exp = ExperimentParametersModel(
run_number=new_run_number,
beamline_parameters=exp_params.beamline_parameters.dict()
if exp_params.beamline_parameters
else None,
sample_id=sample_id,
)
db.add(new_exp)
db.commit()
db.refresh(new_exp)
return new_exp
......@@ -812,9 +812,97 @@ class ImageInfo(BaseModel):
comment: Optional[str] = None
class RotationParameters(BaseModel):
omegaStart_deg: float
omegaStep: float
chi: float
phi: float
numberOfImages: int
exposureTime_s: float
class gridScanParamers(BaseModel):
xStart: float
xStep: float
yStart: float
yStep: float
zStart: float
zStep: float
numberOfImages: int
exposureTime_s: float
class jetParameters(BaseModel):
hplc_pump_ml_min: float
pressure_bar: float
jetDiameter_um: int
jetSpeed_mm_s: float
exposureTime_s: float
class detector(BaseModel):
manufacturer: str
model: str
type: str
serialNumber: str
detectorDistance_mm: float
beamCenterX_px: float
beamCenterY_px: float
pixelSizeX_um: float
pixelSizeY_um: float
class BeamlineParameters(BaseModel):
synchrotron: str
beamline: str
detector: detector
wavelength: float
# energy: float
ringCurrent_A: float
ringMode: str
undulator: Optional[str] = None
undulatorgap_mm: Optional[float] = None
monochromator: str
# bandwidth_percent: float
transmission: float
focusingOptic: str
beamlineFluxAtSample_ph_s: Optional[float] = None
beamSizeWidth: Optional[float] = None
beamSizeHeight: Optional[float] = None
# dose_MGy: float
rotation: Optional[RotationParameters] = None
gridScan: Optional[gridScanParamers] = None
jet: Optional[jetParameters] = None
cryojetTemperature_K: Optional[float] = None
humidifierTemperature_K: Optional[float] = None
humidifierHumidity: Optional[float] = None
# experimentalHutchTemerature_K: Optional[float] = None
# experimentalHutchHumidity_percent: Optional[float] = None
# beamstopDistance_mm: Optional[float] = None
# beamstopDiameter_mm: Optional[float] = None
class ExperimentParametersBase(BaseModel):
run_number: int
beamline_parameters: Optional[BeamlineParameters] = None
sample_id: int
class ExperimentParametersCreate(ExperimentParametersBase):
run_number: Optional[int] = None
class ExperimentParametersRead(ExperimentParametersBase):
id: int
class Config:
from_attributes = True
class SampleResult(BaseModel):
sample_id: int
sample_name: str
puck_name: Optional[str]
dewar_name: Optional[str]
images: List[ImageInfo]
experiment_runs: Optional[List[ExperimentParametersRead]] = []
......@@ -139,6 +139,15 @@ def on_startup():
print(f"{environment.capitalize()} environment: Regenerating database.")
# 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)
# tables_exist = inspector.get_table_names()
#
# if ExperimentParameters.__tablename__ not in tables_exist:
# print("Creating missing table: ExperimentParameters")
# ExperimentParameters.__table__.create(bind=engine)
#
if environment == "dev":
from app.database import load_sample_data
......
This diff is collapsed.
......@@ -27,7 +27,8 @@
"@mui/icons-material": "^6.1.5",
"@mui/material": "^6.1.6",
"@mui/system": "^6.1.6",
"@mui/x-data-grid": "^7.23.3",
"@mui/x-data-grid-premium": "^7.27.2",
"@mui/x-tree-view": "^7.26.0",
"axios": "^1.7.7",
"chokidar": "^4.0.1",
"dayjs": "^1.11.13",
......@@ -59,7 +60,7 @@
"openapi-typescript": "^7.4.2",
"typescript": "^5.5.3",
"typescript-eslint": "^8.7.0",
"vite": "^5.4.8",
"vite": "^6.2.0",
"vite-plugin-svgr": "^4.2.0"
}
}
// TypeScript (ResultGrid.tsx snippet)
import React, { useEffect, useState } from 'react';
import { DataGrid, GridColDef } from '@mui/x-data-grid';
import { DataGridPremium, GridColDef } from '@mui/x-data-grid-premium';
import IconButton from '@mui/material/IconButton';
import InfoIcon from '@mui/icons-material/Info';
import { OpenAPI, SamplesService } from '../../openapi';
import './SampleImage.css';
// Extend your image info interface if needed.
interface ImageInfo {
id: number;
filepath: string;
comment?: string;
}
// This represents an experiment run as returned by your API.
interface ExperimentParameters {
id: number;
run_number: number;
sample_id: number;
beamline_parameters: {
beamSizeHeight: number;
beamSizeWidth: number;
beamline: string;
beamlineFluxAtSample_ph_s: number;
cryojetTemperature_K?: number | null;
detector: {
beamCenterX_px: number;
beamCenterY_px: number;
detectorDistance_mm: number;
manufacturer: string;
model: string;
pixelSizeX_um: number;
pixelSizeY_um: number;
serialNumber: string;
type: string;
};
rotation?: {
chi: number;
exposureTime_s: number;
numberOfImages: number | null;
omegaStart_deg: number;
omegaStep: number;
phi: number;
} | null;
gridScan?: {
xStart: number;
xStep: number;
yStart: number;
yStep: number;
zStart: number;
zStep: number;
numberOfImages: number;
exposureTime_s: number;
} | null;
focusingOptic?: string;
humidifierHumidity?: number | null;
humidifierTemperature_K?: number | null;
jet?: any;
monochromator?: string;
ringCurrent_A?: number;
ringMode?: string;
synchrotron?: string;
transmission?: number;
undulator?: any;
undulatorgap_mm?: any;
wavelength?: number;
};
}
// Represents a sample returned by the API.
interface SampleResult {
sample_id: number;
sample_name: string;
puck_name?: string;
dewar_name?: string;
images: ImageInfo[];
experiment_runs?: ExperimentParameters[];
}
// A flat tree row type for the grid.
interface TreeRow {
id: string;
hierarchy: (string | number)[];
type: 'sample' | 'run';
sample_id: number;
sample_name?: string;
puck_name?: string;
dewar_name?: string;
images?: ImageInfo[];
run_number?: number;
beamline_parameters?: ExperimentParameters['beamline_parameters'];
experimentType?: string;
numberOfImages?: number;
}
interface ResultGridProps {
activePgroup: string;
}
// Helper function to safely get the number of images.
const getNumberOfImages = (run: ExperimentParameters): number => {
const params = run.beamline_parameters;
if (params.rotation && params.rotation.numberOfImages != null) {
return params.rotation.numberOfImages;
} else if (params.gridScan && params.gridScan.numberOfImages != null) {
return params.gridScan.numberOfImages;
}
return 0;
};
// Helper function to determine the experiment type.
const getExperimentType = (run: ExperimentParameters): string => {
const params = run.beamline_parameters;
if (params.rotation && params.rotation.numberOfImages != null && params.rotation.omegaStep != null) {
const numImages = params.rotation.numberOfImages;
const omegaStep = params.rotation.omegaStep;
if ([1, 2, 4].includes(numImages) && omegaStep === 90) {
return "Characterization";
}
return "Rotation";
} else if (params.gridScan) {
return "Grid Scan";
}
return "Rotation";
};
const ResultGrid: React.FC<ResultGridProps> = ({ activePgroup }) => {
const [rows, setRows] = useState<SampleResult[]>([]);
const [rows, setRows] = useState<TreeRow[]>([]);
const [basePath, setBasePath] = useState('');
useEffect(() => {
......@@ -44,99 +145,124 @@ const ResultGrid: React.FC<ResultGridProps> = ({ activePgroup }) => {
console.log('Environment Mode:', mode);
console.log('Resolved OpenAPI.BASE:', OpenAPI.BASE);
// Update the base path for images
setBasePath(`${OpenAPI.BASE}/`);
}, []);
useEffect(() => {
const fetchData = () => {
console.log("Fetching sample results for active_pgroup:", activePgroup);
SamplesService.getSampleResultsSamplesResultsGet(activePgroup)
.then((response: SampleResult[]) => {
console.log("Response received:", response);
setRows(response);
})
.catch((err: Error) => {
console.error("Error fetching sample results:", err);
});
};
console.log('Fetching sample results for active_pgroup:', activePgroup);
SamplesService.getSampleResultsSamplesResultsGet(activePgroup)
.then((response: SampleResult[]) => {
console.log('Response received:', response);
const treeRows: TreeRow[] = [];
// Fetch data initially.
fetchData();
response.forEach((sample) => {
// Add the sample row.
const sampleRow: TreeRow = {
id: `sample-${sample.sample_id}`,
hierarchy: [sample.sample_id],
type: 'sample',
sample_id: sample.sample_id,
sample_name: sample.sample_name,
puck_name: sample.puck_name,
dewar_name: sample.dewar_name,
images: sample.images,
};
treeRows.push(sampleRow);
// Set up an interval to refresh data every 15 seconds (adjust as needed)
const intervalId = setInterval(fetchData, 15000);
// Add experiment run rows.
if (sample.experiment_runs) {
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',
sample_id: sample.sample_id,
run_number: run.run_number,
beamline_parameters: run.beamline_parameters,
experimentType,
numberOfImages: numImages,
images: sample.images,
};
treeRows.push(runRow);
});
}
});
// Clean up when component unmounts or activePgroup changes.
return () => clearInterval(intervalId);
setRows(treeRows);
})
.catch((error) => {
console.error('Error fetching sample results:', error);
});
}, [activePgroup]);
// Define the grid columns, including the new processing results column.
const columns: GridColDef[] = [
{ field: 'sample_id', headerName: 'ID', width: 70 },
{ field: 'sample_name', headerName: 'Sample Name', width: 150 },
{ field: 'puck_name', headerName: 'Puck Name', width: 150 },
{ field: 'dewar_name', headerName: 'Dewar Name', width: 150 },
{
field: 'images',
headerName: 'Images',
width: 300,
field: 'sample_name',
headerName: 'Sample Name',
width: 200,
renderCell: (params) => (params.row.type === 'sample' ? params.value : null),
},
{
field: 'puck_name',
headerName: 'Puck Name',
width: 150,
renderCell: (params) => (params.row.type === 'sample' ? params.value : null),
},
{
field: 'dewar_name',
headerName: 'Dewar Name',
width: 150,
renderCell: (params) => (params.row.type === 'sample' ? params.value : null),
},
{
field: 'experimentType',
headerName: 'Experiment Type',
width: 150,
renderCell: (params) => (params.row.type === 'run' ? params.value : null),
},
{
field: 'numberOfImages',
headerName: 'Number of Images',
width: 150,
renderCell: (params) => (params.row.type === 'run' ? params.value : null),
},
{
field: 'processingResults',
headerName: 'Processing Results',
width: 180,
renderCell: (params) => {
const imageList: ImageInfo[] = params.value;
if (!imageList || imageList.length === 0) {
return null;
if (params.row.type === 'run') {
return (
<IconButton
aria-label="processing results placeholder"
onClick={() => {
// Placeholder for processing results details.
console.log('Clicked processing details for run', params.row.run_number);
}}
>
<InfoIcon />
</IconButton>
);
}
// Filter the images to include only the two bb_raster images
const filteredImages = imageList.filter(
(img) =>
img.filepath.includes("bb_raster_0") ||
img.filepath.includes("bb_raster_90")
);
if (filteredImages.length === 0) {
return null;
}
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
{filteredImages.map((img) => {
const url = basePath + img.filepath;
return (
<img
key={img.id}
src={url}
alt={img.comment || 'sample'}
className="zoom-image"
style={{
width: 40,
height: 40,
marginRight: 5,
borderRadius: 4,
}}
/>
);
})}
</div>
);
return null;
},
},
];
return (
<DataGrid
rows={rows}
columns={columns}
pageSize={10}
getRowId={(row) => row.sample_id}
sx={{
'& .MuiDataGrid-cell': {
overflow: 'visible',
},
}}
/>
<div style={{ height: 600, width: '100%' }}>
<DataGridPremium
rows={rows}
columns={columns}
treeData
getTreeDataPath={(row: TreeRow) => row.hierarchy}
defaultGroupingExpansionDepth={-1}
getRowId={(row) => row.id}
/>
</div>
);
};
......
import {SimpleTreeView, TreeItem} from "@mui/x-tree-view";
import React from "react";
interface ExperimentParameters {
id: number;
run_number: number;
beamline_parameters: {
synchrotron: string;
beamline: string;
detector: {
manufacturer: string;
model: string;
type: string;
serial_number: string;
detector_distance_mm: number;
beam_center_x_px: number;
beam_center_y_px: number;
pixel_size_x_um: number;
pixel_size_y_um: number;
number_of_images: number;
exposure_time_s: number;
};
// Add additional fields if needed.
};
}
<SimpleTreeView
defaultCollapseIcon="▾"
defaultExpandIcon="▸"
sx={{ fontSize: '0.875rem' }}
>
<TreeItem nodeId="detector-group" label={<strong>Detector Details</strong>}>
<TreeItem nodeId="detector-group" label={<strong>Detector Details</strong>}>
<TreeItem
nodeId="detector-manufacturer"
label={`Manufacturer: ${detector?.manufacturer || 'N/A'}`}
/>
<TreeItem
nodeId="detector-model"
label={`Model: ${detector?.model || 'N/A'}`}
/>
<TreeItem
nodeId="detector-type"
label={`Type: ${detector?.type || 'N/A'}`}
/>
<TreeItem
nodeId="detector-serial"
label={`Serial Number: ${detector?.serial_number || 'N/A'}`}
/>
<TreeItem
nodeId="detector-distance"
label={`Distance (mm): ${detector?.detector_distance_mm ?? 'N/A'}`}
/>
<TreeItem
nodeId="beam-center"
label={
detector
? `Beam Center: x:${detector.beam_center_x_px}, y:${detector.beam_center_y_px}`
: 'Beam Center: N/A'
}
/>
<TreeItem
nodeId="pixel-size"
label={
detector
? `Pixel Size (µm): x:${detector.pixel_size_x_um}, y:${detector.pixel_size_y_um}`
: 'Pixel Size: N/A'
}
/>
<TreeItem
nodeId="img-count"
label={`Number of Images: ${detector?.number_of_images ?? 'N/A'}`}
/>
<TreeItem
nodeId="exposure-time"
label={`Exposure Time (s): ${detector?.exposure_time_s ?? 'N/A'}`}
/>
</TreeItem>
</TreeItem>
</SimpleTreeView>
\ No newline at end of file
......@@ -52,7 +52,7 @@ const SampleTracker: React.FC<SampleTrackerProps> = ({ activePgroup }) => {
fetchPucks();
const interval = setInterval(() => {
fetchPucks();
}, 1000);
}, 100000);
return () => clearInterval(interval);
}, [activePgroup]);
......
......@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "aareDB"
version = "0.1.0a23"
version = "0.1.0a24"
description = "Backend for next gen sample management system"
authors = [{name = "Guillaume Gotthard", email = "guillaume.gotthard@psi.ch"}]
license = {text = "MIT"}
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment