tyrwh commited on
Commit
85022a6
·
1 Parent(s): 70d6e34

Finishing up commits before moving to Github as main repo

Browse files
Files changed (3) hide show
  1. Dockerfile +29 -12
  2. app.py +27 -14
  3. requirements.txt +1 -0
Dockerfile CHANGED
@@ -1,10 +1,8 @@
1
  # Use an official Python runtime as a parent image
2
  FROM python:3.12
 
3
 
4
- # Set the working directory in the container
5
- WORKDIR /app
6
-
7
- # Install system dependencies required by OpenCV and other packages, plus Redis
8
  RUN apt-get update && apt-get install -y --no-install-recommends \
9
  libgl1 \
10
  libglib2.0-0 \
@@ -13,21 +11,40 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
13
  libxext6 \
14
  && rm -rf /var/lib/apt/lists/*
15
 
16
- # Copy the requirements file into the container at /app
17
- COPY requirements.txt .
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  # Install any needed packages specified in requirements.txt
20
  # --no-cache-dir: Disables the cache to reduce image size.
21
  # -r requirements.txt: Specifies the file containing the list of packages to install.
22
- RUN pip install --no-cache-dir -r requirements.txt
 
 
 
 
23
 
24
- # Create directories for uploads and results, and set permissions
25
- RUN mkdir -p /app/uploads /app/results /app/annotated && \
26
- chmod 777 /app/uploads /app/results /app/annotated
27
 
28
  # Copy the rest of the application code into the container at /app
29
  # This includes app.py, nemaquant.py, templates/, static/, etc.
30
- COPY . .
31
 
32
  # Make port 7860 available to the world outside this container
33
  # This is the port Flask will run on (as configured in app.py)
@@ -41,4 +58,4 @@ EXPOSE 7860
41
  # Use gunicorn for production deployment if preferred over Flask's development server
42
  # CMD ["gunicorn", "--bind", "0.0.0.0:7860", "app:app"]
43
  # For simplicity during development and typical HF Spaces use:
44
- CMD python app.py
 
1
  # Use an official Python runtime as a parent image
2
  FROM python:3.12
3
+ # FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04
4
 
5
+ # run updates before switching over to non-root user
 
 
 
6
  RUN apt-get update && apt-get install -y --no-install-recommends \
7
  libgl1 \
8
  libglib2.0-0 \
 
11
  libxext6 \
12
  && rm -rf /var/lib/apt/lists/*
13
 
14
+ # add new user with ID 1000 to avoid permission issues on HF spaces
15
+ RUN useradd -m -u 1000 user
16
+ USER user
17
+
18
+ # Set home to user's home dir and add local bin to PATH
19
+ ENV HOME=/home/user \
20
+ PATH=/user/user/.local/bin:$PATH
21
+
22
+ # Set the working directory in the container
23
+ WORKDIR $HOME/app
24
+
25
+ # Try and run pip command after setting the user with `USER user` to avoid permission issues with Python
26
+ # NOTE - this is from the HF Spaces docs, not sure if necessary
27
+ COPY --chown=user ./requirements.txt .
28
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
29
+
30
+ # Copy the current directory contents into the container at $HOME/app setting the owner to the user
31
+ COPY --chown=user . $HOME/app
32
 
33
  # Install any needed packages specified in requirements.txt
34
  # --no-cache-dir: Disables the cache to reduce image size.
35
  # -r requirements.txt: Specifies the file containing the list of packages to install.
36
+ # RUN pip install --no-cache-dir -r requirements.txt
37
+
38
+ # Create the necessary dirs
39
+ # we should not need to chown, since we are using USER user above
40
+ RUN mkdir -p uploads results annotated .yolo_config
41
 
42
+ # set the env var for YOLO user config directory
43
+ ENV YOLO_CONFIG_DIR=.yolo_config
 
44
 
45
  # Copy the rest of the application code into the container at /app
46
  # This includes app.py, nemaquant.py, templates/, static/, etc.
47
+ # COPY . .
48
 
49
  # Make port 7860 available to the world outside this container
50
  # This is the port Flask will run on (as configured in app.py)
 
58
  # Use gunicorn for production deployment if preferred over Flask's development server
59
  # CMD ["gunicorn", "--bind", "0.0.0.0:7860", "app:app"]
60
  # For simplicity during development and typical HF Spaces use:
61
+ CMD ["python", "app.py"]
app.py CHANGED
@@ -9,8 +9,9 @@ import cv2
9
  import csv
10
  import pickle
11
  import shutil
 
12
  from ultralytics import YOLO
13
- from ultralytics.utils import ThreadingLocked
14
  import numpy as np
15
  import pandas as pd
16
  from torch import cuda
@@ -23,11 +24,14 @@ from datetime import datetime
23
  from werkzeug.utils import secure_filename
24
  from yolo_utils import detect_in_image
25
 
26
- set_start_method('spawn')
27
-
28
  app = Flask(__name__)
29
  app.secret_key = os.environ.get('FLASK_SECRET_KEY', str(uuid.uuid4())) # For session security
30
 
 
 
 
 
 
31
  APP_ROOT = Path(__file__).parent
32
  UPLOAD_FOLDER = APP_ROOT / 'uploads'
33
  RESULTS_FOLDER = APP_ROOT / 'results'
@@ -44,6 +48,9 @@ app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'tif', 'tiff'}
44
  # RESULTS_FOLDER.mkdir(parents=True, exist_ok=True)
45
  # ANNOT_FOLDER.mkdir(parents=True, exist_ok=True)
46
 
 
 
 
47
  # need a global dict to hold async results objects
48
  # so you can check the progress of an abr
49
  # maybe there's a better way around this?
@@ -62,9 +69,6 @@ def handle_exception(e):
62
  def index():
63
  return render_template('index.html')
64
 
65
- # Load model once at startup, use CUDA if available
66
- MODEL_DEVICE = 'cuda' if cuda.is_available() else 'cpu'
67
-
68
  # save the uploaded files
69
  @app.route('/uploads', methods=['POST'])
70
  def upload_files():
@@ -389,6 +393,15 @@ def export_csv():
389
  print(error_message)
390
  return jsonify({"error": "Server error", "log": error_message}), 500
391
 
 
 
 
 
 
 
 
 
 
392
 
393
  def print_startup_info():
394
  print("----- NemaQuant Flask App Starting -----")
@@ -406,7 +419,7 @@ def print_startup_info():
406
 
407
  is_container = Path('/.dockerenv').exists() or 'DOCKER_HOST' in os.environ
408
  print(f"Running in container: {is_container}")
409
-
410
  if is_container:
411
  try:
412
  user_info = f"{os.getuid()}:{os.getgid()}"
@@ -425,15 +438,10 @@ def print_startup_info():
425
  else:
426
  print(f"Directory {path_str} does not exist.")
427
 
428
-
429
- @app.before_request
430
- def startup_routine():
431
- # Ensure session id exists
432
- if 'id' not in session:
433
- session['id'] = str(uuid.uuid4())
434
  print('Running periodic cleanup of old sessions...')
435
  # Cleanup old session folders
436
- max_age_hours = 24
437
  now = time.time()
438
  for base_dir in [UPLOAD_FOLDER, RESULTS_FOLDER, ANNOT_FOLDER]:
439
  for session_dir in Path(base_dir).iterdir():
@@ -441,6 +449,11 @@ def startup_routine():
441
  mtime = session_dir.stat().st_mtime
442
  if now - mtime > max_age_hours * 3600:
443
  shutil.rmtree(session_dir)
 
 
 
 
 
444
 
445
  if __name__ == '__main__':
446
  print_startup_info()
 
9
  import csv
10
  import pickle
11
  import shutil
12
+ import logging
13
  from ultralytics import YOLO
14
+ # from ultralytics.utils import ThreadingLocked
15
  import numpy as np
16
  import pandas as pd
17
  from torch import cuda
 
24
  from werkzeug.utils import secure_filename
25
  from yolo_utils import detect_in_image
26
 
 
 
27
  app = Flask(__name__)
28
  app.secret_key = os.environ.get('FLASK_SECRET_KEY', str(uuid.uuid4())) # For session security
29
 
30
+ # disable werkzeug logging - too noisy
31
+ # comment out these lines if you want to see full logs
32
+ log = logging.getLogger('werkzeug')
33
+ log.setLevel(logging.ERROR)
34
+
35
  APP_ROOT = Path(__file__).parent
36
  UPLOAD_FOLDER = APP_ROOT / 'uploads'
37
  RESULTS_FOLDER = APP_ROOT / 'results'
 
48
  # RESULTS_FOLDER.mkdir(parents=True, exist_ok=True)
49
  # ANNOT_FOLDER.mkdir(parents=True, exist_ok=True)
50
 
51
+ # Load model once at startup, use CUDA if available
52
+ MODEL_DEVICE = 'cuda' if cuda.is_available() else 'cpu'
53
+
54
  # need a global dict to hold async results objects
55
  # so you can check the progress of an abr
56
  # maybe there's a better way around this?
 
69
  def index():
70
  return render_template('index.html')
71
 
 
 
 
72
  # save the uploaded files
73
  @app.route('/uploads', methods=['POST'])
74
  def upload_files():
 
393
  print(error_message)
394
  return jsonify({"error": "Server error", "log": error_message}), 500
395
 
396
+ @app.before_request
397
+ def ensure_session():
398
+ if 'id' not in session:
399
+ session['id'] = uuid.uuid4().hex
400
+ print(f"New session started: {session['id']}")
401
+ else:
402
+ pass
403
+ # print(f"Existing session: {session['id']}")
404
+
405
 
406
  def print_startup_info():
407
  print("----- NemaQuant Flask App Starting -----")
 
419
 
420
  is_container = Path('/.dockerenv').exists() or 'DOCKER_HOST' in os.environ
421
  print(f"Running in container: {is_container}")
422
+
423
  if is_container:
424
  try:
425
  user_info = f"{os.getuid()}:{os.getgid()}"
 
438
  else:
439
  print(f"Directory {path_str} does not exist.")
440
 
441
+ # some cleanup steps - not sure quite where to put these
 
 
 
 
 
442
  print('Running periodic cleanup of old sessions...')
443
  # Cleanup old session folders
444
+ max_age_hours = 4
445
  now = time.time()
446
  for base_dir in [UPLOAD_FOLDER, RESULTS_FOLDER, ANNOT_FOLDER]:
447
  for session_dir in Path(base_dir).iterdir():
 
449
  mtime = session_dir.stat().st_mtime
450
  if now - mtime > max_age_hours * 3600:
451
  shutil.rmtree(session_dir)
452
+
453
+ print('App is running at the following local addresses:',
454
+ 'http://127.0.0.1:7860⁠',
455
+ 'http://localhost:7860⁠',
456
+ sep='\n')
457
 
458
  if __name__ == '__main__':
459
  print_startup_info()
requirements.txt CHANGED
@@ -5,5 +5,6 @@ pandas==2.3.1
5
  Pillow==11.3.0
6
  torch==2.7.1
7
  ultralytics==8.3.170
 
8
  Werkzeug==3.1.3
9
  gunicorn==21.2.0
 
5
  Pillow==11.3.0
6
  torch==2.7.1
7
  ultralytics==8.3.170
8
+ watchdog==6.0.0
9
  Werkzeug==3.1.3
10
  gunicorn==21.2.0