From b04c7b8c95b97ec1ac8fd49453fd101099cac00f Mon Sep 17 00:00:00 2001 From: GotthardG <51994228+GotthardG@users.noreply.github.com> Date: Wed, 26 Feb 2025 15:11:20 +0100 Subject: [PATCH] Add Image models and clean up test code structure Introduced `ImageCreate` and `Image` models to handle image-related data in the backend. Improved the organization and readability of the testing notebook by consolidating and formatting code into distinct sections with markdown cells. --- backend/app/routers/sample.py | 39 ++++++++ backend/app/schemas.py | 14 +++ backend/main.py | 4 + frontend/src/App.tsx | 2 +- frontend/src/components/ResultGrid.tsx | 114 ++++++++++++++++++++++ frontend/src/components/SampleTracker.tsx | 2 +- frontend/src/pages/ResultsView.tsx | 11 ++- testfunctions.ipynb | 16 +-- 8 files changed, 191 insertions(+), 11 deletions(-) create mode 100644 frontend/src/components/ResultGrid.tsx diff --git a/backend/app/routers/sample.py b/backend/app/routers/sample.py index 201df60..f5a6786 100644 --- a/backend/app/routers/sample.py +++ b/backend/app/routers/sample.py @@ -11,12 +11,14 @@ from app.schemas import ( Sample, Image, ImageCreate, + SampleResult, ) from app.models import ( Puck as PuckModel, Sample as SampleModel, SampleEvent as SampleEventModel, Image as ImageModel, + Dewar as DewarModel, ) from app.dependencies import get_db import logging @@ -165,3 +167,40 @@ async def upload_sample_image( # Returning the mapped SQLAlchemy object, which will be converted to the # Pydantic response model. return new_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 + samples = ( + db.query(SampleModel) + .join(SampleModel.puck) + .join(PuckModel.dewar) + .filter(DewarModel.pgroups == active_pgroup) + .all() + ) + if not samples: + raise HTTPException( + status_code=404, detail="No samples found for the active pgroup" + ) + + results = [] + for sample in samples: + # Query images associated with each sample + images = db.query(ImageModel).filter(ImageModel.sample_id == sample.id).all() + 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 + if (sample.puck and sample.puck.dewar) + else None, + "images": [ + {"id": img.id, "filepath": img.filepath, "comment": img.comment} + for img in images + ], + } + ) + + return results diff --git a/backend/app/schemas.py b/backend/app/schemas.py index b143415..c31aba6 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -804,3 +804,17 @@ class Image(ImageCreate): class Config: from_attributes = True + + +class ImageInfo(BaseModel): + id: int + filepath: str + comment: Optional[str] = None + + +class SampleResult(BaseModel): + sample_id: int + sample_name: str + puck_name: Optional[str] + dewar_name: Optional[str] + images: List[ImageInfo] diff --git a/backend/main.py b/backend/main.py index 050d328..c4b9484 100644 --- a/backend/main.py +++ b/backend/main.py @@ -4,6 +4,7 @@ import tomllib from pathlib import Path from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles from app import ssl_heidi from app.routers import ( proposal, @@ -159,6 +160,9 @@ app.include_router(spreadsheet.router, tags=["spreadsheet"]) app.include_router(logistics.router, prefix="/logistics", tags=["logistics"]) app.include_router(sample.router, prefix="/samples", tags=["samples"]) +app.mount("/images", StaticFiles(directory="images"), name="images") + + if __name__ == "__main__": import sys from dotenv import load_dotenv diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fdc5154..bae4f15 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -84,7 +84,7 @@ const App: React.FC = () => { } />} /> } />} /> } />} /> - } />} /> + } />} /> diff --git a/frontend/src/components/ResultGrid.tsx b/frontend/src/components/ResultGrid.tsx new file mode 100644 index 0000000..1f0bdd3 --- /dev/null +++ b/frontend/src/components/ResultGrid.tsx @@ -0,0 +1,114 @@ +import React, { useEffect, useState } from 'react'; +import { DataGrid, GridColDef } from '@mui/x-data-grid'; +import { OpenAPI, SamplesService } from '../../openapi'; + +interface ImageInfo { + id: number; + filepath: string; + comment?: string; +} + +interface SampleResult { + sample_id: number; + sample_name: string; + puck_name?: string; + dewar_name?: string; + images: ImageInfo[]; +} + +interface ResultGridProps { + activePgroup: string; +} + +const ResultGrid: React.FC = ({ activePgroup }) => { + const [rows, setRows] = useState([]); + + useEffect(() => { + 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); + }); + }, [activePgroup]); + + 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, + renderCell: (params) => { + const imageList: ImageInfo[] = params.value; + if (imageList && imageList.length) { + const primaryImage = imageList[0]; + // Define the base path to your backend images directory + const basePath = "https://localhost:8000/"; + const imageUrl = basePath + primaryImage.filepath; + + console.log("Local relative path:", imageUrl); + + console.log("Updated image URL:", imageUrl); + + return ( +
+ sample + {imageList.length > 1 && ( +
+ +{imageList.length - 1} +
+ {imageList.slice(1).map((img) => { + const tooltipImageUrl = basePath + img.filepath; + return ( + sample + ); + })} +
+
+ )} +
+ ); + } + return null; + }, + }, + ]; + + // Map each row so that DataGrid can use a unique "id" prop; here we use sample_id. + const gridRows = rows.map((row) => ({ ...row, id: row.sample_id })); + + return ( +
+ +
+ ); +}; + +export default ResultGrid; diff --git a/frontend/src/components/SampleTracker.tsx b/frontend/src/components/SampleTracker.tsx index c7f9e63..aba025d 100644 --- a/frontend/src/components/SampleTracker.tsx +++ b/frontend/src/components/SampleTracker.tsx @@ -50,7 +50,7 @@ const SampleTracker: React.FC = () => { // Set up polling every 1 second const interval = setInterval(() => { fetchPucks(); - }, 1000); + }, 100000); // Clear interval on component unmount return () => clearInterval(interval); diff --git a/frontend/src/pages/ResultsView.tsx b/frontend/src/pages/ResultsView.tsx index 632898e..b75db2e 100644 --- a/frontend/src/pages/ResultsView.tsx +++ b/frontend/src/pages/ResultsView.tsx @@ -2,13 +2,22 @@ import React from 'react'; import SampleTracker from '../components/SampleTracker'; +import ResultGrid from '../components/ResultGrid'; + +interface ResultsViewProps { + activePgroup: string; +} + +const ResultsView: React.FC = ({activePgroup +}) => { -const ResultsView: React.FC = () => { return (

Results Page

+
+ ); }; diff --git a/testfunctions.ipynb b/testfunctions.ipynb index 7f9ccf0..24581f0 100644 --- a/testfunctions.ipynb +++ b/testfunctions.ipynb @@ -3,8 +3,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-02-26T12:02:38.993926Z", - "start_time": "2025-02-26T12:02:38.991283Z" + "end_time": "2025-02-26T13:09:58.719218Z", + "start_time": "2025-02-26T13:09:58.716771Z" } }, "cell_type": "code", @@ -46,7 +46,7 @@ ] } ], - "execution_count": 74 + "execution_count": 86 }, { "metadata": {}, @@ -528,8 +528,8 @@ { "metadata": { "ExecuteTime": { - "end_time": "2025-02-26T12:29:51.615501Z", - "start_time": "2025-02-26T12:29:51.592886Z" + "end_time": "2025-02-26T13:17:13.591355Z", + "start_time": "2025-02-26T13:17:13.561947Z" } }, "cell_type": "code", @@ -546,7 +546,7 @@ " mime_type = \"application/octet-stream\"\n", "\n", "# Sample ID (ensure this exists on your backend)\n", - "sample_id = 58\n", + "sample_id = 16\n", "\n", "# Build the URL for the upload endpoint.\n", "url = f\"https://127.0.0.1:8000/samples/{sample_id}/upload-images\"\n", @@ -580,7 +580,7 @@ "text": [ "API Response:\n", "200\n", - "{'pgroup': 'p20001, p20002', 'sample_id': 58, 'filepath': 'images/p20001, p20002/2025-02-26/Dewar One/PUCK007/12/IMG_1942.jpg', 'status': 'active', 'comment': None, 'id': 1}\n" + "{'pgroup': 'p20001', 'sample_id': 16, 'filepath': 'images/p20001/2025-02-26/Dewar One/PUCK-001/16/IMG_1942.jpg', 'status': 'active', 'comment': None, 'id': 3}\n" ] }, { @@ -592,7 +592,7 @@ ] } ], - "execution_count": 85 + "execution_count": 88 }, { "metadata": {},