VibecoderMcSwaggins commited on
Commit
1493232
Β·
1 Parent(s): fbf73ff

feat(api): FastAPI REST backend for React frontend

Browse files

## Summary
- Add FastAPI REST API endpoints for stroke segmentation
- Endpoints: GET /api/cases, POST /api/segment, GET /files/{path}
- CORS configured for HF Spaces frontend
- Sync endpoint handlers for proper threadpool execution
- Update Dockerfile for FastAPI deployment
- 8 backend tests, 58 frontend tests passing

## Changes
- Add src/stroke_deepisles_demo/api/ module (main.py, routes.py, schemas.py)
- Add tests/api/test_endpoints.py
- Update pyproject.toml with [api] extra dependencies
- Update Dockerfile CMD for uvicorn
- Update CI to install api extras

Addresses CodeRabbit feedback:
- Fixed flaky waitFor test
- Changed async def to def for sync handlers
- Fixed exception handling
- Updated spec documentation

README.md CHANGED
@@ -1,73 +1,65 @@
1
- # React + TypeScript + Vite
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
 
5
- Currently, two official plugins are available:
6
 
7
- - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
8
- - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
 
10
- ## React Compiler
11
 
12
- The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
 
 
13
 
14
- ## Expanding the ESLint configuration
15
 
16
- If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
17
 
18
- ```js
19
- export default defineConfig([
20
- globalIgnores(['dist']),
21
- {
22
- files: ['**/*.{ts,tsx}'],
23
- extends: [
24
- // Other configs...
 
 
 
 
 
 
 
 
 
 
25
 
26
- // Remove tseslint.configs.recommended and replace with this
27
- tseslint.configs.recommendedTypeChecked,
28
- // Alternatively, use this for stricter rules
29
- tseslint.configs.strictTypeChecked,
30
- // Optionally, add this for stylistic rules
31
- tseslint.configs.stylisticTypeChecked,
32
 
33
- // Other configs...
34
- ],
35
- languageOptions: {
36
- parserOptions: {
37
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
38
- tsconfigRootDir: import.meta.dirname,
39
- },
40
- // other options...
41
- },
42
- },
43
- ])
44
  ```
45
 
46
- You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
47
 
48
- ```js
49
- // eslint.config.js
50
- import reactX from 'eslint-plugin-react-x'
51
- import reactDom from 'eslint-plugin-react-dom'
52
 
53
- export default defineConfig([
54
- globalIgnores(['dist']),
55
- {
56
- files: ['**/*.{ts,tsx}'],
57
- extends: [
58
- // Other configs...
59
- // Enable lint rules for React
60
- reactX.configs['recommended-typescript'],
61
- // Enable lint rules for React DOM
62
- reactDom.configs.recommended,
63
- ],
64
- languageOptions: {
65
- parserOptions: {
66
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
67
- tsconfigRootDir: import.meta.dirname,
68
- },
69
- // other options...
70
- },
71
- },
72
- ])
73
  ```
 
1
+ ---
2
+ title: Stroke Lesion Viewer
3
+ emoji: 🧠
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: static
7
+ app_file: dist/index.html
8
+ app_build_command: npm run build
9
+ # CRITICAL: Vite 6 requires Node.js >= 20. HF Spaces defaults to Node 18.
10
+ # Without this, the build will fail or produce warnings.
11
+ nodejs_version: "20"
12
+ pinned: false
13
+ ---
14
 
15
+ # Stroke Lesion Segmentation Viewer
16
 
17
+ Interactive 3D viewer for stroke lesion segmentation results using NiiVue.
18
 
19
+ Built with React, TypeScript, Tailwind CSS, and Vite.
 
20
 
21
+ ## Features
22
 
23
+ - **NiiVue WebGL2 Viewer**: Pan, zoom, and navigate through NIfTI volumes
24
+ - **Real-time Segmentation**: Run DeepISLES inference on ISLES24 dataset cases
25
+ - **Metrics Display**: Dice score, volume (mL), processing time
26
 
27
+ ## Architecture
28
 
29
+ This is the **frontend Static Space** of a two-Space deployment:
30
 
31
+ ```
32
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
33
+ β”‚ HuggingFace Static Space β”‚ ← You are here
34
+ β”‚ stroke-viewer-frontend β”‚
35
+ β”‚ β”‚
36
+ β”‚ React 19 + TypeScript + Tailwind β”‚
37
+ β”‚ @niivue/niivue for 3D viewing β”‚
38
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
39
+ β”‚ HTTPS API calls
40
+ β–Ό
41
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
42
+ β”‚ HuggingFace Docker Space β”‚
43
+ β”‚ stroke-viewer-api β”‚
44
+ β”‚ β”‚
45
+ β”‚ FastAPI + DeepISLES + PyTorch β”‚
46
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
47
+ ```
48
 
49
+ ## Local Development
 
 
 
 
 
50
 
51
+ ```bash
52
+ npm install
53
+ npm run dev # Start dev server at http://localhost:5173
54
+ npm test # Run unit tests
55
+ npm run test:e2e # Run E2E tests
56
+ npm run build # Production build
 
 
 
 
 
57
  ```
58
 
59
+ ## Environment Variables
60
 
61
+ Set `VITE_API_URL` to point to your backend:
 
 
 
62
 
63
+ ```bash
64
+ VITE_API_URL=http://localhost:7860 npm run dev
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  ```
src/components/__tests__/NiiVueViewer.test.tsx CHANGED
@@ -1,5 +1,5 @@
1
  import { describe, it, expect, vi, beforeEach } from 'vitest'
2
- import { render, screen } from '@testing-library/react'
3
  import { NiiVueViewer } from '../NiiVueViewer'
4
 
5
  // Store mock function references so tests can verify calls
@@ -119,8 +119,8 @@ describe('NiiVueViewer', () => {
119
 
120
  render(<NiiVueViewer {...defaultProps} onError={onError} />)
121
 
122
- // Wait for error callback to be invoked
123
- await vi.waitFor(() => {
124
  expect(onError).toHaveBeenCalledWith(errorMessage)
125
  })
126
  })
@@ -152,9 +152,9 @@ describe('NiiVueViewer', () => {
152
  // Now reject the second load (stale)
153
  rejectSecondLoad!(new Error('Stale load error'))
154
 
155
- // Wait a tick and verify onError was NOT called with stale error
156
- await vi.waitFor(() => {
157
- expect(onError).not.toHaveBeenCalledWith('Stale load error')
158
- })
159
  })
160
  })
 
1
  import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+ import { render, screen, waitFor } from '@testing-library/react'
3
  import { NiiVueViewer } from '../NiiVueViewer'
4
 
5
  // Store mock function references so tests can verify calls
 
119
 
120
  render(<NiiVueViewer {...defaultProps} onError={onError} />)
121
 
122
+ // Wait for error callback to be invoked (use RTL's waitFor, not vi.waitFor)
123
+ await waitFor(() => {
124
  expect(onError).toHaveBeenCalledWith(errorMessage)
125
  })
126
  })
 
152
  // Now reject the second load (stale)
153
  rejectSecondLoad!(new Error('Stale load error'))
154
 
155
+ // Flush async work (let rejection be processed) before asserting
156
+ // Using waitFor with negative assertions is flaky - it passes immediately
157
+ await new Promise(resolve => setTimeout(resolve, 0))
158
+ expect(onError).not.toHaveBeenCalledWith('Stale load error')
159
  })
160
  })