VibecoderMcSwaggins commited on
Commit
0424dcc
Β·
unverified Β·
1 Parent(s): 80cbb1a

fix(docker): relocate app to /home/user/demo to preserve DeepISLES modules (#17)

Browse files

* fix(docker): relocate app to /home/user/demo to preserve DeepISLES modules

Root cause: Our Dockerfile was copying src/ to /app/src/, which overwrote
DeepISLES's isles22_ensemble.py module in the base image.

Fix:
- Changed WORKDIR from /app to /home/user/demo
- Added DEEPISLES_PATH=/app env var for explicit path resolution
- Updated direct.py to check DEEPISLES_PATH first, then fallback paths

The isleschallenge/deepisles base image stores DeepISLES at:
/app/main.py
/app/src/isles22_ensemble.py
/app/weights/

Our app now lives at /home/user/demo without conflicts.

Fixes: DeepISLES modules not found on HF Spaces
See: docs/specs/09-bug-deepisles-not-installed-hf-spaces.md

* docs: update Dockerfile examples to use /home/user/demo path

Update both Dockerfile examples in 07-hf-spaces-deployment.md to reflect
the correct app location (/home/user/demo instead of /app) to prevent
overwriting DeepISLES modules in the base image.

Related: fix/deepisles-docker-path

* refactor: address CodeRabbit review feedback

- Add language specifier to code block in bug doc (markdownlint)
- Deduplicate paths in _get_deepisles_search_paths() when DEEPISLES_PATH=/app

Dockerfile CHANGED
@@ -4,6 +4,9 @@
4
  #
5
  # IMPORTANT: During Docker build, GPU is NOT available.
6
  # All GPU operations happen at runtime only.
 
 
 
7
 
8
  # NOTE: isleschallenge/deepisles only publishes 'latest' tag on Docker Hub.
9
  # For reproducibility, consider using a SHA digest if available:
@@ -21,20 +24,21 @@ ENV PYTHONDONTWRITEBYTECODE=1
21
  # Create user if not exists (DeepISLES image may already have a user)
22
  RUN useradd -m -u 1000 user 2>/dev/null || true
23
 
24
- # Set working directory
25
- WORKDIR /app
 
26
 
27
  # Copy requirements first for better layer caching
28
- COPY --chown=1000:1000 requirements.txt /app/requirements.txt
29
 
30
  # Install Python dependencies (extras only - DeepISLES image has PyTorch, nnUNet, etc.)
31
  RUN pip install --no-cache-dir -r requirements.txt
32
 
33
  # Copy application source code and package files
34
- COPY --chown=1000:1000 pyproject.toml /app/pyproject.toml
35
- COPY --chown=1000:1000 README.md /app/README.md
36
- COPY --chown=1000:1000 src/ /app/src/
37
- COPY --chown=1000:1000 app.py /app/app.py
38
 
39
  # Install the package itself (makes stroke_deepisles_demo importable)
40
  # Using --no-deps since requirements.txt already installed dependencies
@@ -44,12 +48,17 @@ RUN pip install --no-cache-dir --no-deps -e .
44
  # This allows the app to detect runtime environment and use direct invocation
45
  ENV HF_SPACES=1
46
  ENV DEEPISLES_DIRECT_INVOCATION=1
 
 
 
 
 
47
  # Ensure HuggingFace cache uses our writable directory
48
- ENV HF_HOME=/app/cache
49
 
50
  # Create directories for data with proper permissions
51
- RUN mkdir -p /app/data /app/results /app/cache && \
52
- chown -R 1000:1000 /app
53
 
54
  # Switch to non-root user (required by HF Spaces)
55
  USER user
 
4
  #
5
  # IMPORTANT: During Docker build, GPU is NOT available.
6
  # All GPU operations happen at runtime only.
7
+ #
8
+ # CRITICAL: DeepISLES code lives at /app/src/ in the base image.
9
+ # We install our demo at /home/user/demo to avoid overwriting DeepISLES.
10
 
11
  # NOTE: isleschallenge/deepisles only publishes 'latest' tag on Docker Hub.
12
  # For reproducibility, consider using a SHA digest if available:
 
24
  # Create user if not exists (DeepISLES image may already have a user)
25
  RUN useradd -m -u 1000 user 2>/dev/null || true
26
 
27
+ # IMPORTANT: Use /home/user/demo for our app, NOT /app
28
+ # /app contains DeepISLES code (main.py, src/, weights/) that we must NOT overwrite
29
+ WORKDIR /home/user/demo
30
 
31
  # Copy requirements first for better layer caching
32
+ COPY --chown=1000:1000 requirements.txt /home/user/demo/requirements.txt
33
 
34
  # Install Python dependencies (extras only - DeepISLES image has PyTorch, nnUNet, etc.)
35
  RUN pip install --no-cache-dir -r requirements.txt
36
 
37
  # Copy application source code and package files
38
+ COPY --chown=1000:1000 pyproject.toml /home/user/demo/pyproject.toml
39
+ COPY --chown=1000:1000 README.md /home/user/demo/README.md
40
+ COPY --chown=1000:1000 src/ /home/user/demo/src/
41
+ COPY --chown=1000:1000 app.py /home/user/demo/app.py
42
 
43
  # Install the package itself (makes stroke_deepisles_demo importable)
44
  # Using --no-deps since requirements.txt already installed dependencies
 
48
  # This allows the app to detect runtime environment and use direct invocation
49
  ENV HF_SPACES=1
50
  ENV DEEPISLES_DIRECT_INVOCATION=1
51
+
52
+ # Point to DeepISLES location for direct invocation
53
+ # DeepISLES code is at /app in the base image
54
+ ENV DEEPISLES_PATH=/app
55
+
56
  # Ensure HuggingFace cache uses our writable directory
57
+ ENV HF_HOME=/home/user/demo/cache
58
 
59
  # Create directories for data with proper permissions
60
+ RUN mkdir -p /home/user/demo/data /home/user/demo/results /home/user/demo/cache && \
61
+ chown -R 1000:1000 /home/user/demo
62
 
63
  # Switch to non-root user (required by HF Spaces)
64
  USER user
docs/specs/07-hf-spaces-deployment.md CHANGED
@@ -498,17 +498,33 @@ For showcasing to others. Spin up when needed, pause when done.
498
 
499
  ```dockerfile
500
  # Dockerfile for HF Spaces
 
 
501
  FROM isleschallenge/deepisles:latest
502
 
 
 
 
 
 
 
503
  # Add our application
504
- COPY requirements.txt /app/
505
- RUN pip install -r /app/requirements.txt
506
 
507
- COPY src/ /app/src/
508
- COPY app.py /app/
 
 
509
 
510
- WORKDIR /app
 
 
 
 
 
511
  EXPOSE 7860
 
512
  CMD ["python", "-m", "stroke_deepisles_demo.ui.app"]
513
  ```
514
 
@@ -681,19 +697,34 @@ uv run stroke-demo run --case sub-stroke0001
681
 
682
  ```dockerfile
683
  # Dockerfile
 
 
684
  FROM isleschallenge/deepisles:latest
685
 
 
 
 
 
 
 
686
  # Install additional dependencies
687
- COPY requirements.txt /app/
688
- RUN pip install --no-cache-dir -r /app/requirements.txt
689
 
690
  # Copy application code
691
- COPY src/ /app/src/
692
- COPY app.py /app/
 
 
693
 
694
- WORKDIR /app
695
- EXPOSE 7860
 
 
696
 
 
 
 
697
  CMD ["python", "-m", "stroke_deepisles_demo.ui.app"]
698
  ```
699
 
 
498
 
499
  ```dockerfile
500
  # Dockerfile for HF Spaces
501
+ # CRITICAL: DeepISLES code lives at /app/src/ in the base image.
502
+ # We install our demo at /home/user/demo to avoid overwriting DeepISLES.
503
  FROM isleschallenge/deepisles:latest
504
 
505
+ # HF Spaces runs containers with user ID 1000
506
+ RUN useradd -m -u 1000 user 2>/dev/null || true
507
+
508
+ # IMPORTANT: Use /home/user/demo for our app, NOT /app
509
+ WORKDIR /home/user/demo
510
+
511
  # Add our application
512
+ COPY --chown=1000:1000 requirements.txt /home/user/demo/requirements.txt
513
+ RUN pip install --no-cache-dir -r requirements.txt
514
 
515
+ COPY --chown=1000:1000 pyproject.toml /home/user/demo/pyproject.toml
516
+ COPY --chown=1000:1000 src/ /home/user/demo/src/
517
+ COPY --chown=1000:1000 app.py /home/user/demo/app.py
518
+ RUN pip install --no-cache-dir --no-deps -e .
519
 
520
+ # Environment variables for HF Spaces + direct invocation
521
+ ENV HF_SPACES=1
522
+ ENV DEEPISLES_DIRECT_INVOCATION=1
523
+ ENV DEEPISLES_PATH=/app
524
+
525
+ USER user
526
  EXPOSE 7860
527
+ ENTRYPOINT []
528
  CMD ["python", "-m", "stroke_deepisles_demo.ui.app"]
529
  ```
530
 
 
697
 
698
  ```dockerfile
699
  # Dockerfile
700
+ # CRITICAL: DeepISLES code lives at /app/src/ in the base image.
701
+ # We install our demo at /home/user/demo to avoid overwriting DeepISLES.
702
  FROM isleschallenge/deepisles:latest
703
 
704
+ # HF Spaces runs containers with user ID 1000
705
+ RUN useradd -m -u 1000 user 2>/dev/null || true
706
+
707
+ # IMPORTANT: Use /home/user/demo for our app, NOT /app
708
+ WORKDIR /home/user/demo
709
+
710
  # Install additional dependencies
711
+ COPY --chown=1000:1000 requirements.txt /home/user/demo/requirements.txt
712
+ RUN pip install --no-cache-dir -r requirements.txt
713
 
714
  # Copy application code
715
+ COPY --chown=1000:1000 pyproject.toml /home/user/demo/pyproject.toml
716
+ COPY --chown=1000:1000 src/ /home/user/demo/src/
717
+ COPY --chown=1000:1000 app.py /home/user/demo/app.py
718
+ RUN pip install --no-cache-dir --no-deps -e .
719
 
720
+ # Environment variables for HF Spaces + direct invocation
721
+ ENV HF_SPACES=1
722
+ ENV DEEPISLES_DIRECT_INVOCATION=1
723
+ ENV DEEPISLES_PATH=/app
724
 
725
+ USER user
726
+ EXPOSE 7860
727
+ ENTRYPOINT []
728
  CMD ["python", "-m", "stroke_deepisles_demo.ui.app"]
729
  ```
730
 
docs/specs/09-bug-deepisles-not-installed-hf-spaces.md ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Bug Spec: DeepISLES Path Conflict in Docker Build
2
+
3
+ **Status:** Root Cause Found β†’ Fix Ready
4
+ **Priority:** P0 (Blocks inference)
5
+ **Branch:** `fix/deepisles-docker-path`
6
+ **Date:** 2025-12-08
7
+
8
+ ## Executive Summary
9
+
10
+ Our Dockerfile was **overwriting DeepISLES modules** by copying our app to `/app/src/`, which is where the base image stores DeepISLES code. The fix is to install our app at `/home/user/demo` instead.
11
+
12
+ ## Root Cause
13
+
14
+ The `isleschallenge/deepisles:latest` Docker image has this structure:
15
+ ```text
16
+ /app/
17
+ β”œβ”€β”€ main.py
18
+ β”œβ”€β”€ requirements.txt
19
+ β”œβ”€β”€ src/ ← DeepISLES Python modules
20
+ β”‚ └── isles22_ensemble.py
21
+ └── weights/ ← Model weights (~GB)
22
+ ```
23
+
24
+ Our original Dockerfile:
25
+ ```dockerfile
26
+ FROM isleschallenge/deepisles:latest
27
+ WORKDIR /app
28
+ COPY src/ /app/src/ ← OVERWRITES DeepISLES modules!
29
+ ```
30
+
31
+ This replaced `/app/src/isles22_ensemble.py` (DeepISLES) with `/app/src/stroke_deepisles_demo/` (our app).
32
+
33
+ ## The Fix
34
+
35
+ 1. **Changed app directory** from `/app` to `/home/user/demo`
36
+ 2. **Added `DEEPISLES_PATH=/app`** environment variable
37
+ 3. **Updated `direct.py`** to check `DEEPISLES_PATH` first
38
+
39
+ ### Dockerfile Changes
40
+ ```dockerfile
41
+ # Before: WORKDIR /app
42
+ # After:
43
+ WORKDIR /home/user/demo
44
+
45
+ # Before: COPY src/ /app/src/
46
+ # After:
47
+ COPY src/ /home/user/demo/src/
48
+
49
+ # New:
50
+ ENV DEEPISLES_PATH=/app
51
+ ```
52
+
53
+ ### direct.py Changes
54
+ ```python
55
+ def _get_deepisles_search_paths() -> list[str]:
56
+ paths = []
57
+ # Check environment variable first (set in Dockerfile)
58
+ env_path = os.environ.get("DEEPISLES_PATH")
59
+ if env_path:
60
+ paths.append(env_path)
61
+ # Add common installation locations
62
+ paths.extend(["/app", "/DeepIsles", ...])
63
+ return paths
64
+ ```
65
+
66
+ ## Investigation Process
67
+
68
+ 1. Pulled `isleschallenge/deepisles:latest` locally
69
+ 2. Inspected WORKDIR: `/app`
70
+ 3. Listed `/app` contents: found `src/`, `weights/`, `main.py`
71
+ 4. Realized our `COPY src/ /app/src/` was overwriting DeepISLES
72
+
73
+ ## Files Changed
74
+
75
+ - `Dockerfile` - Use `/home/user/demo`, add `DEEPISLES_PATH`
76
+ - `src/stroke_deepisles_demo/inference/direct.py` - Dynamic search paths
77
+
78
+ ## Testing
79
+
80
+ - All 125 unit tests pass
81
+ - Need to test on HF Spaces to verify inference works
82
+
83
+ ## References
84
+
85
+ - [DeepISLES GitHub](https://github.com/ezequieldlrosa/DeepIsles)
86
+ - [Docker Hub Image](https://hub.docker.com/r/isleschallenge/deepisles)
87
+ - [HuggingFace Docker Spaces](https://huggingface.co/docs/hub/en/spaces-sdks-docker)
88
+
89
+ ## Sources
90
+
91
+ - [Docker Blog: Build ML Apps with HuggingFace](https://www.docker.com/blog/build-machine-learning-apps-with-hugging-faces-docker-spaces/)
92
+ - [HuggingFace Docker Spaces Docs](https://huggingface.co/docs/hub/en/spaces-sdks-docker)
src/stroke_deepisles_demo/inference/direct.py CHANGED
@@ -16,6 +16,7 @@ See:
16
 
17
  from __future__ import annotations
18
 
 
19
  import sys
20
  import time
21
  from dataclasses import dataclass
@@ -27,13 +28,30 @@ from stroke_deepisles_demo.inference.deepisles import find_prediction_mask
27
 
28
  logger = get_logger(__name__)
29
 
30
- # Paths where DeepISLES source might be located in the Docker image
31
- DEEPISLES_SEARCH_PATHS = [
32
- "/app",
33
- "/DeepIsles",
34
- "/opt/deepisles",
35
- "/home/user/DeepIsles",
36
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
 
39
  @dataclass(frozen=True)
@@ -54,7 +72,9 @@ def _ensure_deepisles_importable() -> str:
54
  Raises:
55
  DeepISLESError: If DeepISLES cannot be found
56
  """
57
- for path in DEEPISLES_SEARCH_PATHS:
 
 
58
  if Path(path).exists():
59
  if path not in sys.path:
60
  sys.path.insert(0, path)
@@ -70,7 +90,7 @@ def _ensure_deepisles_importable() -> str:
70
  raise DeepISLESError(
71
  "DeepISLES modules not found. Direct invocation requires running "
72
  "inside the DeepISLES Docker image. Searched paths: "
73
- f"{DEEPISLES_SEARCH_PATHS}"
74
  )
75
 
76
 
 
16
 
17
  from __future__ import annotations
18
 
19
+ import os
20
  import sys
21
  import time
22
  from dataclasses import dataclass
 
28
 
29
  logger = get_logger(__name__)
30
 
31
+
32
+ def _get_deepisles_search_paths() -> list[str]:
33
+ """Get paths to search for DeepISLES modules.
34
+
35
+ Checks DEEPISLES_PATH environment variable first, then falls back to
36
+ common installation locations.
37
+ """
38
+ paths = []
39
+
40
+ # Check environment variable first (set in Dockerfile)
41
+ env_path = os.environ.get("DEEPISLES_PATH")
42
+ if env_path:
43
+ paths.append(env_path)
44
+
45
+ # Add common installation locations (excluding any already added via env var)
46
+ fallback_paths = [
47
+ "/app", # Default location in isleschallenge/deepisles Docker image
48
+ "/DeepIsles",
49
+ "/opt/deepisles",
50
+ "/home/user/DeepIsles",
51
+ ]
52
+ paths.extend(p for p in fallback_paths if p not in paths)
53
+
54
+ return paths
55
 
56
 
57
  @dataclass(frozen=True)
 
72
  Raises:
73
  DeepISLESError: If DeepISLES cannot be found
74
  """
75
+ search_paths = _get_deepisles_search_paths()
76
+
77
+ for path in search_paths:
78
  if Path(path).exists():
79
  if path not in sys.path:
80
  sys.path.insert(0, path)
 
90
  raise DeepISLESError(
91
  "DeepISLES modules not found. Direct invocation requires running "
92
  "inside the DeepISLES Docker image. Searched paths: "
93
+ f"{search_paths}"
94
  )
95
 
96