Skip to content

Commit e499f16

Browse files
authored
Merge pull request #293 from HyperInspire/develop
Adapt Python-SDK to download models from modelscope
2 parents 7464b3d + 1879101 commit e499f16

File tree

10 files changed

+265
-15
lines changed

10 files changed

+265
-15
lines changed

ci/quick_test_linux_x86_usual_python_native_interface.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ cp build/${BUILD_DIRNAME}/lib/libInspireFace.so python/inspireface/modules/core/
3838
pip install opencv-python
3939
pip install click
4040
pip install loguru
41+
pip install filelock
42+
pip install modelscope
4143

4244
cd python/
4345

cpp/inspireface/middleware/any_net_adapter.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ class INSPIRE_API AnyNetAdapter {
3838
*/
3939
explicit AnyNetAdapter(std::string name) : m_name_(std::move(name)) {
4040
m_processor_ = nexus::ImageProcessor::Create(INSPIREFACE_CONTEXT->GetImageProcessingBackend());
41+
#if defined(ISF_ENABLE_RKNN)
4142
m_processor_->SetAlignedWidth(INSPIREFACE_CONTEXT->GetImageProcessAlignedWidth());
43+
#endif
4244
}
4345

4446
~AnyNetAdapter() {

python/.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,4 @@ share/python-wheels/
2626
.pytest_cache/
2727
.idea/
2828
*.db
29-
version.txt
30-
post
29+
version.txt

python/inspireface/modules/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
HF_PK_AUTO_INCREMENT, HF_PK_MANUAL_INPUT, HF_SEARCH_MODE_EAGER, HF_SEARCH_MODE_EXHAUSTIVE, \
99
ignore_check_latest_model, set_cuda_device_id, get_cuda_device_id, print_cuda_device_info, get_num_cuda_devices, check_cuda_device_support, terminate, \
1010
InspireFaceError, InvalidInputError, SystemNotReadyError, ProcessingError, ResourceError, HardwareError, FeatureHubError, \
11-
switch_image_processing_backend, HF_IMAGE_PROCESSING_CPU, HF_IMAGE_PROCESSING_RGA, set_image_process_aligned_width
11+
switch_image_processing_backend, HF_IMAGE_PROCESSING_CPU, HF_IMAGE_PROCESSING_RGA, set_image_process_aligned_width, use_oss_download

python/inspireface/modules/inspireface.py

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from dataclasses import dataclass
77
from loguru import logger
88
from .utils import ResourceManager
9+
from .utils.resource import set_use_oss_download
910
from . import herror as errcode
1011
# Exception system
1112
from .exception import (
@@ -22,6 +23,14 @@ def ignore_check_latest_model(ignore: bool):
2223
global IGNORE_VERIFICATION_OF_THE_LATEST_MODEL
2324
IGNORE_VERIFICATION_OF_THE_LATEST_MODEL = ignore
2425

26+
def use_oss_download(use_oss: bool = True):
27+
"""Enable OSS download instead of ModelScope (for backward compatibility)
28+
29+
Args:
30+
use_oss (bool): If True, use OSS download; if False, use ModelScope (default)
31+
"""
32+
set_use_oss_download(use_oss)
33+
2534
class ImageStream(object):
2635
"""
2736
ImageStream class handles the conversion of image data from various sources into a format compatible with the InspireFace library.
@@ -303,15 +312,18 @@ def __init__(self, param, detect_mode: int = HF_DETECT_MODE_ALWAYS_DETECT,
303312
SystemNotReadyError: If InspireFace is not launched.
304313
ProcessingError: If session creation fails.
305314
"""
315+
# Initialize _sess to None first to prevent AttributeError in __del__
316+
self._sess = None
317+
self.multiple_faces = None
318+
self.param = param
319+
306320
# If InspireFace is not initialized, run launch() use Pikachu model
307321
if not query_launch_status():
308322
ret = launch()
309323
if not ret:
310324
raise SystemNotReadyError("Failed to launch InspireFace automatically")
311325

312-
self.multiple_faces = None
313326
self._sess = HFSession()
314-
self.param = param
315327

316328
if isinstance(self.param, SessionCustomParameter):
317329
ret = HFCreateInspireFaceSession(self.param._c_struct(), detect_mode, max_detect_num, detect_pixel_level,
@@ -700,6 +712,38 @@ def __del__(self):
700712

701713

702714
# == Global API ==
715+
716+
def _check_modelscope_availability():
717+
"""
718+
Check if ModelScope is available when needed and provide helpful error message if not.
719+
720+
Exits the program if ModelScope is needed but not available and OSS is not enabled.
721+
"""
722+
import sys
723+
from .utils.resource import USE_OSS_DOWNLOAD
724+
725+
# Dynamic check for ModelScope availability (don't rely on cached MODELSCOPE_AVAILABLE)
726+
modelscope_available = True
727+
try:
728+
from modelscope.hub.snapshot_download import snapshot_download
729+
print("ModelScope import successful")
730+
except Exception as e:
731+
modelscope_available = False
732+
print(f"ModelScope import failed: {e}")
733+
734+
if not USE_OSS_DOWNLOAD and not modelscope_available:
735+
print("ModelScope is not available, cannot download models!")
736+
print("\nPlease choose one of the following solutions:")
737+
print("1. Reinstall ModelScope with all dependencies:")
738+
print(" pip install --upgrade modelscope")
739+
print("\n2. Install missing dependencies manually:")
740+
print(" pip install filelock")
741+
print("\n3. Switch to OSS download mode:")
742+
print(" import inspireface as isf")
743+
print(" isf.use_oss_download(True) # Execute before calling launch()")
744+
print("\nNote: OSS download requires stable international network connection")
745+
sys.exit(1)
746+
703747
def launch(model_name: str = "Pikachu", resource_path: str = None) -> bool:
704748
"""
705749
Launches the InspireFace system with the specified resource directory.
@@ -715,7 +759,13 @@ def launch(model_name: str = "Pikachu", resource_path: str = None) -> bool:
715759
SystemNotReadyError: If launch fails due to resource issues.
716760
"""
717761
if resource_path is None:
718-
sm = ResourceManager()
762+
from .utils.resource import USE_OSS_DOWNLOAD
763+
764+
# Check if ModelScope is available when needed
765+
_check_modelscope_availability()
766+
767+
# Use ModelScope by default unless OSS is forced
768+
sm = ResourceManager(use_modelscope=not USE_OSS_DOWNLOAD)
719769
resource_path = sm.get_model(model_name, ignore_verification=IGNORE_VERIFICATION_OF_THE_LATEST_MODEL)
720770
path_c = String(bytes(resource_path, encoding="utf8"))
721771
ret = HFLaunchInspireFace(path_c)
@@ -737,7 +787,12 @@ def pull_latest_model(model_name: str = "Pikachu") -> str:
737787
Returns:
738788
str: Path to the downloaded model.
739789
"""
740-
sm = ResourceManager()
790+
from .utils.resource import USE_OSS_DOWNLOAD
791+
792+
# Check if ModelScope is available when needed
793+
_check_modelscope_availability()
794+
795+
sm = ResourceManager(use_modelscope=not USE_OSS_DOWNLOAD)
741796
resource_path = sm.get_model(model_name, re_download=True)
742797
return resource_path
743798

@@ -753,7 +808,12 @@ def reload(model_name: str = "Pikachu", resource_path: str = None) -> bool:
753808
bool: True if reload was successful.
754809
"""
755810
if resource_path is None:
756-
sm = ResourceManager()
811+
from .utils.resource import USE_OSS_DOWNLOAD
812+
813+
# Check if ModelScope is available when needed
814+
_check_modelscope_availability()
815+
816+
sm = ResourceManager(use_modelscope=not USE_OSS_DOWNLOAD)
757817
resource_path = sm.get_model(model_name, ignore_verification=IGNORE_VERIFICATION_OF_THE_LATEST_MODEL)
758818
path_c = String(bytes(resource_path, encoding="utf8"))
759819
ret = HFReloadInspireFace(path_c)

python/inspireface/modules/utils/resource.py

Lines changed: 138 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,43 @@
1+
"""
2+
InspireFace Resource Manager
3+
4+
This module provides model downloading functionality with two modes:
5+
1. Original mode: Download models from COS (Tencent Cloud Object Storage)
6+
2. ModelScope mode: Download models from ModelScope platform
7+
8+
ModelScope mode usage:
9+
rm = ResourceManager(use_modelscope=True, modelscope_model_id="tunmxy/InspireFace")
10+
model_path = rm.get_model("Gundam_RV1106")
11+
12+
Requirements for ModelScope mode:
13+
pip install modelscope
14+
"""
15+
116
import os
217
import sys
318
from pathlib import Path
419
import urllib.request
520
import ssl
621
import hashlib
722

23+
try:
24+
from modelscope.hub.snapshot_download import snapshot_download
25+
MODELSCOPE_AVAILABLE = True
26+
except ImportError:
27+
MODELSCOPE_AVAILABLE = False
28+
29+
# Global configuration for resource downloading
30+
USE_OSS_DOWNLOAD = False # If True, force use OSS download instead of ModelScope
31+
32+
def set_use_oss_download(use_oss: bool):
33+
"""Set whether to use OSS download instead of ModelScope
34+
35+
Args:
36+
use_oss (bool): If True, use OSS download; if False, use ModelScope (default)
37+
"""
38+
global USE_OSS_DOWNLOAD
39+
USE_OSS_DOWNLOAD = use_oss
40+
841
def get_file_hash_sha256(file_path):
942
sha256 = hashlib.sha256()
1043
with open(file_path, 'rb') as f:
@@ -13,15 +46,35 @@ def get_file_hash_sha256(file_path):
1346
return sha256.hexdigest()
1447

1548
class ResourceManager:
16-
def __init__(self):
17-
"""Initialize resource manager and create necessary directories"""
49+
def __init__(self, use_modelscope: bool = True, modelscope_model_id: str = "tunmxy/InspireFace"):
50+
"""Initialize resource manager and create necessary directories
51+
52+
Args:
53+
use_modelscope: Whether to download models from ModelScope platform
54+
modelscope_model_id: ModelScope model ID (default: tunmxy/InspireFace)
55+
"""
1856
self.user_home = Path.home()
1957
self.base_dir = self.user_home / '.inspireface'
2058
self.models_dir = self.base_dir / 'models'
2159

60+
# ModelScope configuration
61+
self.use_modelscope = use_modelscope
62+
self.modelscope_model_id = modelscope_model_id
63+
self.modelscope_cache_dir = self.base_dir / 'ms'
64+
2265
# Create directories
2366
self.base_dir.mkdir(exist_ok=True)
2467
self.models_dir.mkdir(exist_ok=True)
68+
if self.use_modelscope:
69+
self.modelscope_cache_dir.mkdir(exist_ok=True)
70+
71+
# Check ModelScope availability
72+
if self.use_modelscope and not MODELSCOPE_AVAILABLE:
73+
raise ImportError(
74+
"ModelScope is not available. You have two options:\n"
75+
"1. Install ModelScope: pip install modelscope\n"
76+
"2. Switch to OSS download mode by calling: inspireface.use_oss_download(True) before using InspireFace"
77+
)
2578

2679
# Model URLs
2780
self._MODEL_LIST = {
@@ -52,6 +105,39 @@ def __init__(self):
52105
}
53106
}
54107

108+
def _download_from_modelscope(self, model_name: str) -> str:
109+
"""Download model from ModelScope platform
110+
111+
Args:
112+
model_name: Name of the model to download
113+
114+
Returns:
115+
str: Path to the downloaded model file
116+
"""
117+
if not MODELSCOPE_AVAILABLE:
118+
raise ImportError("ModelScope is not available. Please install it with: pip install modelscope")
119+
120+
print(f"Downloading model '{model_name}' from ModelScope...")
121+
122+
try:
123+
# Download specific model file from ModelScope
124+
cache_dir = snapshot_download(
125+
model_id=self.modelscope_model_id,
126+
cache_dir=str(self.modelscope_cache_dir),
127+
allow_file_pattern=[model_name] # Only download the specific model file
128+
)
129+
130+
model_file_path = Path(cache_dir) / model_name
131+
132+
if not model_file_path.exists():
133+
raise FileNotFoundError(f"Model file '{model_name}' not found in downloaded repository")
134+
135+
print(f"ModelScope download completed: {model_file_path}")
136+
return str(model_file_path)
137+
138+
except Exception as e:
139+
raise RuntimeError(f"Failed to download model from ModelScope: {e}")
140+
55141
def get_model(self, name: str, re_download: bool = False, ignore_verification: bool = False) -> str:
56142
"""
57143
Get model path. Download if not exists or re_download is True.
@@ -64,6 +150,15 @@ def get_model(self, name: str, re_download: bool = False, ignore_verification: b
64150
Returns:
65151
str: Full path to model file
66152
"""
153+
# Check global OSS setting first, then instance setting
154+
use_oss = USE_OSS_DOWNLOAD
155+
use_modelscope_actual = self.use_modelscope and not use_oss
156+
157+
# Use ModelScope download if enabled and OSS is not forced
158+
if use_modelscope_actual:
159+
return self._download_from_modelscope_with_cache(name, re_download)
160+
161+
# Original download logic for backwards compatibility
67162
if name not in self._MODEL_LIST:
68163
raise ValueError(f"Model '{name}' not found. Available models: {list(self._MODEL_LIST.keys())}")
69164

@@ -124,13 +219,51 @@ def get_model(self, name: str, re_download: bool = False, ignore_verification: b
124219
if downloading_flag.exists():
125220
downloading_flag.unlink()
126221
raise RuntimeError(f"Failed to download model: {e}")
222+
223+
def _download_from_modelscope_with_cache(self, name: str, re_download: bool = False) -> str:
224+
"""Download model from ModelScope with local caching logic
225+
226+
Args:
227+
name: Model name
228+
re_download: Force re-download if True
229+
230+
Returns:
231+
str: Path to the model file
232+
"""
233+
# Check if model exists in ModelScope cache
234+
model_file_path = self.modelscope_cache_dir / name
127235

128-
# Usage example
236+
if model_file_path.exists() and not re_download:
237+
print(f"Using cached model '{name}' from ModelScope")
238+
return str(model_file_path)
239+
240+
# Download from ModelScope
241+
return self._download_from_modelscope(name)
242+
243+
# Usage examples
129244
if __name__ == "__main__":
130245
try:
246+
# Example 1: Default mode (ModelScope)
247+
print("=== Default mode (ModelScope) ===")
131248
rm = ResourceManager()
132-
model_path = rm.get_model("Pikachu")
133-
print(f"Model path: {model_path}")
249+
model_path = rm.get_model("Gundam_RV1106")
250+
print(f"ModelScope model path: {model_path}")
251+
252+
# Example 2: Force OSS mode using global setting
253+
print("\n=== OSS mode (global setting) ===")
254+
set_use_oss_download(True)
255+
rm_oss = ResourceManager()
256+
model_path_oss = rm_oss.get_model("Pikachu")
257+
print(f"OSS model path: {model_path_oss}")
258+
259+
# Reset to default
260+
set_use_oss_download(False)
261+
262+
# Example 3: Explicit ModelScope mode
263+
print("\n=== Explicit ModelScope mode ===")
264+
rm_ms = ResourceManager(use_modelscope=True, modelscope_model_id="tunmxy/InspireFace")
265+
model_path_ms = rm_ms.get_model("Gundam_RV1106")
266+
print(f"Explicit ModelScope model path: {model_path_ms}")
134267

135268
except Exception as e:
136269
print(f"Error: {e}")

python/post

Whitespace-only changes.

python/sample_face_detection.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import cv2
33
import inspireface as isf
44
import click
5-
import numpy as np
65

76
race_tags = ["Black", "Asian", "Latino/Hispanic", "Middle Eastern", "White"]
87
gender_tags = ["Female", "Male"]

python/setup.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ def get_target_platform_for_envs():
122122
},
123123
install_requires=[
124124
'numpy',
125-
'loguru'
125+
'loguru',
126+
'filelock',
127+
'modelscope'
126128
],
127129
author='Jingyu Yan',
128130
author_email='tunmxy@163.com',

0 commit comments

Comments
 (0)