Skip to content

Improve portability and inference workflow - #1

Open
addz9015 wants to merge 1 commit into
mainfrom
improve-portability-and-inference
Open

Improve portability and inference workflow#1
addz9015 wants to merge 1 commit into
mainfrom
improve-portability-and-inference

Conversation

@addz9015

@addz9015 addz9015 commented Mar 25, 2026

Copy link
Copy Markdown
Owner

Summary
This PR improves the hand-sign recognition workflow by making the dataset and training scripts more portable, and by fixing the live webcam inference setup so it behaves correctly for real-time use.

Changes
Removed hardcoded local paths from the dataset creation flow
Added CLI arguments for dataset input/output configuration
Made dataset preview optional instead of forcing a blocking plot for every image
Updated hand inference to use MediaPipe live-video settings instead of static-image mode
Added automatic camera selection for inference
Improved model loading and camera error handling
Added safer bounding box rendering and resource cleanup
Added dataset existence and empty-data checks before training
Made training reproducible with fixed random seeds
Added the missing Python dependencies to requirements.txt
Why
The current scripts are tied to one local machine and include a few runtime issues that make them harder to reuse:

dataset generation depends on a hardcoded Windows path
inference is configured like a static image pipeline even though it runs on webcam frames
training assumes required files always exist
the repository did not declare its Python dependencies
These changes make the project easier to run on another machine and reduce avoidable runtime failures.

Testing
Verified the updated Python files have no editor-detected errors
Verified the branch was committed and pushed successfully
Impact
This PR should not change the intended project flow, but it makes the existing workflow more reliable and easier to set up for other users.

Summary by CodeRabbit

  • New Features

    • Added command-line interface for dataset creation with configurable input/output paths and optional visualization
    • Implemented camera index auto-detection and manual selection for inference
    • Enhanced error handling with descriptive exception messages
  • Bug Fixes

    • Improved resource cleanup to prevent resource leaks
    • Fixed coordinate boundary clamping for visual overlays
  • Chores

    • Added runtime dependencies for core functionality
    • Refactored scripts into structured, deterministic execution patterns

@coderabbitai

coderabbitai Bot commented Mar 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Scripts refactored from hardcoded, top-level execution to CLI-driven programs with structured functions. Dataset creation, model inference, and training now accept configurable arguments, improved error handling, deterministic seeding, and resource cleanup patterns. Dependencies added to requirements.txt to support functionality.

Changes

Cohort / File(s) Summary
CLI Refactoring & Function Extraction
create_dataset.py, inference_classifier.py
Added parse_args(), main() entrypoints and helper functions for structured CLI programs. Replaced hardcoded paths/configurations with command-line flags (--data-dir, --output, --camera-index, --preview). Improved resource cleanup via try/finally blocks and proper function scoping.
Error Handling & Validation
create_dataset.py, inference_classifier.py, train_classifier.py
Replaced print-and-exit patterns with exceptions (FileNotFoundError, RuntimeError, ValueError). Added explicit existence/validity checks for input files and directories.
Model & Camera Configuration
inference_classifier.py
Changed MediaPipe Hands from static_image_mode=True to False with max_num_hands=1 and tracking confidence. Added camera index probing to find first available device. Improved label mapping and coordinate clamping for frame bounds.
Path Management & Determinism
train_classifier.py
Introduced DATA_PATH and MODEL_PATH relative to script directory. Added random_state=42 to train/test split and model initialization for reproducibility. Improved file handling with context managers.
Runtime Dependencies
requirements.txt
Added 10 dependencies: keras-facenet, matplotlib, mediapipe, mtcnn, numpy, opencv-python, qrcode[pil], scikit-learn, tensorflow, ultralytics.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Hops through code with glee so bright,
CLI flags now set things right!
No more hardcoded paths to fear,
Deterministic seeds are here!
With proper cleanup, error care,
Scripts are cleaner—what a pair! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: refactoring for portability (removing hardcoded paths, adding CLI arguments, relative path derivation) and fixing the inference workflow (MediaPipe live-video settings, camera auto-detection, error handling, resource cleanup).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch improve-portability-and-inference

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
create_dataset.py (2)

53-100: Consider using try/finally for MediaPipe cleanup.

If an exception occurs during dataset building (e.g., a corrupt image causing an unexpected error), hands.close() on line 99 would be skipped. Using a try/finally block ensures cleanup even on failure.

♻️ Proposed refactor for safer cleanup
 def build_dataset(data_dir, preview):
     mp_hands = mp.solutions.hands
     hands = mp_hands.Hands(static_image_mode=True, min_detection_confidence=0.3)
     data = []
     labels = []
 
-    for class_dir in sorted(data_dir.iterdir()):
-        # ... existing loop code ...
-
-    hands.close()
-    return data, labels
+    try:
+        for class_dir in sorted(data_dir.iterdir()):
+            # ... existing loop code ...
+        return data, labels
+    finally:
+        hands.close()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@create_dataset.py` around lines 53 - 100, The build_dataset function may skip
calling hands.close() if an exception occurs; wrap the processing loop in a
try/finally: create the MediaPipe Hands instance (hands = mp_hands.Hands(...))
as you do now, then put the image-processing loop and all logic inside a try
block and call hands.close() inside the finally block so cleanup always runs;
ensure hands is defined before the try so the finally can reference it safely.

78-91: Coordinate lists accumulate across multiple hands.

If an image contains multiple hands, x_coords and y_coords accumulate landmarks from all hands before computing min_x/min_y, which could produce inconsistent feature vectors. For single-hand sign datasets this works, but consider resetting these lists inside the for hand_landmarks loop if multi-hand images are possible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@create_dataset.py` around lines 78 - 91, x_coords and y_coords are populated
outside the per-hand loop so landmarks from multiple hands are mixed before
computing min_x/min_y; reset x_coords and y_coords at the start of the for
hand_landmarks in results.multi_hand_landmarks loop (and compute min_x/min_y
immediately per hand) so data_aux entries for each hand use that hand's local
coordinates (refer to variables x_coords, y_coords, data_aux and the for
hand_landmarks in results.multi_hand_landmarks loop).
train_classifier.py (1)

14-44: Consider wrapping in a main() function for consistency.

Unlike create_dataset.py and inference_classifier.py, this script executes at module level without a main() function or if __name__ == "__main__" guard. This means:

  • Importing this module triggers training
  • Functions cannot be reused programmatically
  • Pattern is inconsistent with other refactored scripts
♻️ Proposed refactor to match other scripts
 DATA_PATH = os.path.join(os.path.dirname(__file__), "data.pickle")
 MODEL_PATH = os.path.join(os.path.dirname(__file__), "model.p")
 
 
-if not os.path.exists(DATA_PATH):
-	raise FileNotFoundError(f"Dataset file '{DATA_PATH}' not found.")
-
-with open(DATA_PATH, "rb") as file_obj:
-	data_dict = pickle.load(file_obj)
-
-# ... rest of training code ...
-
-with open(MODEL_PATH, "wb") as file_obj:
-	pickle.dump({"model": model}, file_obj)
+def main():
+    if not os.path.exists(DATA_PATH):
+        raise FileNotFoundError(f"Dataset file '{DATA_PATH}' not found.")
+
+    with open(DATA_PATH, "rb") as file_obj:
+        data_dict = pickle.load(file_obj)
+
+    # ... rest of training code ...
+
+    with open(MODEL_PATH, "wb") as file_obj:
+        pickle.dump({"model": model}, file_obj)
+
+
+if __name__ == "__main__":
+    main()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@train_classifier.py` around lines 14 - 44, Move the module-level training
code into a new main() (or train_model()) function and add an if __name__ ==
"__main__": main() guard so importing train_classifier.py doesn't trigger
training; specifically, wrap the DATA_PATH existence check, pickle load of
data_dict, train_test_split call, model creation (RandomForestClassifier),
training (model.fit), prediction/accuracy logic (y_predict, accuracy_score) and
the final pickle.dump to MODEL_PATH inside main(), and keep DATA_PATH and
MODEL_PATH as module-level constants so other modules can call the new
train_model()/main() function programmatically.
requirements.txt (1)

1-10: Consider pinning dependency versions for reproducibility.

Unpinned dependencies can lead to non-reproducible builds and unexpected breakages when upstream packages release incompatible updates. Consider specifying version constraints (e.g., mediapipe>=0.10.0,<0.11 or exact pins).

Additionally, some dependencies appear unused in the provided code:

  • keras-facenet, mtcnn, ultralytics — not imported in the reviewed scripts
  • qrcode[pil] — unrelated to hand-sign recognition

If these are needed for other parts of the project, that's fine. Otherwise, consider removing unused dependencies to reduce install footprint and potential security surface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@requirements.txt` around lines 1 - 10, The requirements file currently lists
unpinned packages which harms reproducibility and includes likely-unused
entries; update requirements.txt to pin versions for each dependency used by the
project (e.g., add exact or range constraints for mediapipe, numpy,
opencv-python, scikit-learn, tensorflow, matplotlib) and remove any truly unused
packages (inspect imports/usages for keras-facenet, mtcnn, ultralytics,
qrcode[pil] and delete them if not required); ensure any retained packages have
appropriate version constraints (e.g., mediapipe >= and < constraints) and run a
fresh install + test to confirm the pinned set works.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@inference_classifier.py`:
- Around line 40-42: The pickle.load call that deserializes the model from
model_path (returning model_dict["model"]) is unsafe for untrusted files; update
the code around the deserialization (the function that opens model_path) to
either (1) explicitly validate/trust the source before loading (add a clear
check/flag like require_trusted_model or validate checksum/signature) and log a
security warning when loading external files, or (2) replace pickle with a safer
serialization approach for models (e.g., use joblib or a vetted safe Unpickler),
and document in the function docstring that model files must be trusted; ensure
you reference model_path and the function that returns model_dict["model"] so
reviewers can locate the change.
- Line 11: The LABELS dict uses integer keys but model predictions are stored as
string class names, so int(prediction[0]) will raise a ValueError; fix by either
(A) changing LABELS to use string keys matching class names (e.g., LABELS =
{"A": "A", "C": "C", "L": "L"}) so lookups with prediction[0] succeed, or (B)
remove the unnecessary lookup and use prediction[0] (the predicted class string)
directly where it's consumed — update references to LABELS and any code that
does int(prediction[0]) to align with the chosen approach (look for LABELS and
the variable prediction/prediction[0]).

---

Nitpick comments:
In `@create_dataset.py`:
- Around line 53-100: The build_dataset function may skip calling hands.close()
if an exception occurs; wrap the processing loop in a try/finally: create the
MediaPipe Hands instance (hands = mp_hands.Hands(...)) as you do now, then put
the image-processing loop and all logic inside a try block and call
hands.close() inside the finally block so cleanup always runs; ensure hands is
defined before the try so the finally can reference it safely.
- Around line 78-91: x_coords and y_coords are populated outside the per-hand
loop so landmarks from multiple hands are mixed before computing min_x/min_y;
reset x_coords and y_coords at the start of the for hand_landmarks in
results.multi_hand_landmarks loop (and compute min_x/min_y immediately per hand)
so data_aux entries for each hand use that hand's local coordinates (refer to
variables x_coords, y_coords, data_aux and the for hand_landmarks in
results.multi_hand_landmarks loop).

In `@requirements.txt`:
- Around line 1-10: The requirements file currently lists unpinned packages
which harms reproducibility and includes likely-unused entries; update
requirements.txt to pin versions for each dependency used by the project (e.g.,
add exact or range constraints for mediapipe, numpy, opencv-python,
scikit-learn, tensorflow, matplotlib) and remove any truly unused packages
(inspect imports/usages for keras-facenet, mtcnn, ultralytics, qrcode[pil] and
delete them if not required); ensure any retained packages have appropriate
version constraints (e.g., mediapipe >= and < constraints) and run a fresh
install + test to confirm the pinned set works.

In `@train_classifier.py`:
- Around line 14-44: Move the module-level training code into a new main() (or
train_model()) function and add an if __name__ == "__main__": main() guard so
importing train_classifier.py doesn't trigger training; specifically, wrap the
DATA_PATH existence check, pickle load of data_dict, train_test_split call,
model creation (RandomForestClassifier), training (model.fit),
prediction/accuracy logic (y_predict, accuracy_score) and the final pickle.dump
to MODEL_PATH inside main(), and keep DATA_PATH and MODEL_PATH as module-level
constants so other modules can call the new train_model()/main() function
programmatically.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d3d18861-15d5-4561-9236-8ac419654d4d

📥 Commits

Reviewing files that changed from the base of the PR and between d8d694c and a8ba8b6.

📒 Files selected for processing (4)
  • create_dataset.py
  • inference_classifier.py
  • requirements.txt
  • train_classifier.py

Comment thread inference_classifier.py
cv2.destroyAllWindows() No newline at end of file

DEFAULT_MODEL_PATH = os.path.join(os.path.dirname(__file__), "model.p")
LABELS = {0: "A", 1: "C", 2: "L"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Label type mismatch will cause runtime failure.

The LABELS dict uses integer keys, but create_dataset.py stores labels as strings (directory names like "A", "C", "L"). The model trained on these string labels will predict strings, not integers.

At line 109, int(prediction[0]) will raise ValueError when prediction[0] is a string like "A".

🐛 Proposed fix: Use string keys or remove the lookup

Option 1: Use string keys to match the actual predictions:

-LABELS = {0: "A", 1: "C", 2: "L"}
+LABELS = {"A": "A", "C": "C", "L": "L"}  # Or simply use prediction directly

Option 2: Simplify by using the prediction directly (since it's already the class name):

-                prediction = model.predict([np.asarray(data_aux)])
-                predicted_character = LABELS.get(int(prediction[0]), str(prediction[0]))
+                prediction = model.predict([np.asarray(data_aux)])
+                predicted_character = str(prediction[0])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@inference_classifier.py` at line 11, The LABELS dict uses integer keys but
model predictions are stored as string class names, so int(prediction[0]) will
raise a ValueError; fix by either (A) changing LABELS to use string keys
matching class names (e.g., LABELS = {"A": "A", "C": "C", "L": "L"}) so lookups
with prediction[0] succeed, or (B) remove the unnecessary lookup and use
prediction[0] (the predicted class string) directly where it's consumed — update
references to LABELS and any code that does int(prediction[0]) to align with the
chosen approach (look for LABELS and the variable prediction/prediction[0]).

Comment thread inference_classifier.py
Comment on lines +40 to +42
with open(model_path, "rb") as file_obj:
model_dict = pickle.load(file_obj)
return model_dict["model"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Pickle deserialization security consideration.

Loading pickle files from potentially user-provided paths carries risk of arbitrary code execution if the file is malicious. For this local inference tool loading self-trained models, the risk is low, but worth documenting if the tool might be used with untrusted model files.

🧰 Tools
🪛 Ruff (0.15.6)

[error] 41-41: pickle and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue

(S301)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@inference_classifier.py` around lines 40 - 42, The pickle.load call that
deserializes the model from model_path (returning model_dict["model"]) is unsafe
for untrusted files; update the code around the deserialization (the function
that opens model_path) to either (1) explicitly validate/trust the source before
loading (add a clear check/flag like require_trusted_model or validate
checksum/signature) and log a security warning when loading external files, or
(2) replace pickle with a safer serialization approach for models (e.g., use
joblib or a vetted safe Unpickler), and document in the function docstring that
model files must be trusted; ensure you reference model_path and the function
that returns model_dict["model"] so reviewers can locate the change.

@addz9015 addz9015 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review Copilot

Security and code quality review of hand-sign recognition workflow improvements

Posted inline comments: 4

Comment thread create_dataset.py
parser.add_argument(
"--output",
default=Path(__file__).resolve().parent / "data.pickle",
type=Path,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] [quality] The dataset creation script does not handle exceptions that may occur during image processing. Consider adding try-except blocks to handle potential errors.

Suggestion: Add try-except blocks to handle exceptions during image processing.

Comment thread inference_classifier.py
model = load_model(args.model)

camera_index = args.camera_index if args.camera_index is not None else find_camera_index()
if camera_index is None:

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] [quality] The inference classifier script does not validate the model file before loading it. Consider adding a check to ensure the model file exists and is valid.

Suggestion: Add a check to ensure the model file exists and is valid before loading it.

Comment thread train_classifier.py
with open(DATA_PATH, "rb") as file_obj:
data_dict = pickle.load(file_obj)

data = np.asarray(data_dict["data"])

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] [quality] The training script uses a fixed random seed for reproducibility. Consider using a command-line argument or environment variable to make the seed configurable.

Suggestion: Make the random seed configurable using a command-line argument or environment variable.

Comment thread requirements.txt
@@ -0,0 +1,10 @@
keras-facenet

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] [quality] The requirements file does not specify the version of the dependencies. Consider adding version numbers to ensure reproducibility.

Suggestion: Add version numbers to the dependencies in the requirements file.

@addz9015 addz9015 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review Copilot

Security and quality review of hand-sign recognition workflow improvements

Posted inline comments: 4

Comment thread create_dataset.py
parser.add_argument(
"--output",
default=Path(__file__).resolve().parent / "data.pickle",
type=Path,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] [quality] The dataset creation script does not handle exceptions that may occur during image processing. Consider adding try-except blocks to handle potential errors.

Suggestion: Add try-except blocks to handle exceptions during image processing, e.g., try: img = cv2.imread(str(image_path)); except Exception as e: print(f'Error reading image {image_path}: {e}')

Comment thread inference_classifier.py
model = load_model(args.model)

camera_index = args.camera_index if args.camera_index is not None else find_camera_index()
if camera_index is None:

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] [quality] The inference script does not validate the model file before loading it. Consider adding a check to ensure the model file exists and is valid.

Suggestion: Add a check to ensure the model file exists and is valid before loading it, e.g., if not os.path.exists(model_path): raise FileNotFoundError(f'Model file {model_path} not found.')

Comment thread train_classifier.py
with open(DATA_PATH, "rb") as file_obj:
data_dict = pickle.load(file_obj)

data = np.asarray(data_dict["data"])

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] [quality] The training script does not handle the case where the dataset file is empty. Consider adding a check to ensure the dataset is not empty before training.

Suggestion: Add a check to ensure the dataset is not empty before training, e.g., if len(data) == 0 or len(labels) == 0: raise ValueError('Dataset is empty. Collect data and create data.pickle before training.')

Comment thread requirements.txt
@@ -0,0 +1,10 @@
keras-facenet

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] [quality] The requirements file does not specify the version of the dependencies. Consider adding version numbers to ensure reproducibility.

Suggestion: Add version numbers to the dependencies in the requirements file, e.g., numpy==1.20.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant