From 18b24dcafac54fca7199faf91802262510816f90 Mon Sep 17 00:00:00 2001 From: jwkim Date: Thu, 27 Nov 2025 15:27:22 +0900 Subject: [PATCH] =?UTF-8?q?edit:=20api=20=EB=B3=80=EA=B2=BD=20-=20?= =?UTF-8?q?=EB=B2=A1=ED=84=B0=EC=9D=B4=EB=AF=B8=EC=A7=80=EA=B2=80=EC=83=89?= =?UTF-8?q?=20->=20=EA=B2=B0=EA=B3=BC=EB=AC=BC=EC=97=90=20parts=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20=ED=8C=8C=EC=B8=A0=EA=B2=80=EC=83=89=20ap?= =?UTF-8?q?i=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +- .../FEATURE_VECTOR_SIMILARITY_FAISS/const.py | 21 +++- .../faiss_functions.py | 75 +++++++++++- .../faiss_similarity_search.py | 100 ++++++++------- .../FEATURE_VECTOR_SIMILARITY_FAISS/utils.py | 31 +++++ main_rest/app/models.py | 35 +++++- main_rest/app/routes/dev.py | 72 +++++++++++ main_rest/app/routes/services.py | 114 +++++++++--------- vector_rest/app/models.py | 25 ++-- vector_rest/app/routes/services.py | 52 ++++++-- 10 files changed, 384 insertions(+), 145 deletions(-) create mode 100644 custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/utils.py diff --git a/.gitignore b/.gitignore index b301023..cbbfe00 100644 --- a/.gitignore +++ b/.gitignore @@ -145,8 +145,6 @@ log *output *temp -datas/eyewear_all/* -datas/clip* -datas/openai* +datas/* *result \ No newline at end of file diff --git a/custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/const.py b/custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/const.py index 11af321..f7356fb 100755 --- a/custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/const.py +++ b/custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/const.py @@ -18,11 +18,28 @@ ViTL14_336 = "clip-vit-large-patch14-336" #download/save path PRETRAINED_MODEL_PATH = "./datas" -FAISS_VECTOR_PATH = "./datas" -VECTOR_IMG_LIST_PATH = "./datas" +FAISS_VECTOR_PATH = "./datas/Eyewear_001~500_ComponentsCapture_20251121" +VECTOR_IMG_LIST_PATH = "./datas/Eyewear_001~500_ComponentsCapture_20251121" INDEX_IMAGES_PATH = "./datas/eyewear_all" +class VectorSearchItem: + glass = "glass" + bridge = "Bridge" + hinge = "Hinges" + lens = "Lens" + nosepad = "Nose pad" + other = "other" + padarm = "Padarm" + rim = "Rim" + rivet = "Rivet" + soltex = "Soltex" + temple = "Temple" + templetip = "Temple tip" + +class ImageDepths: + parts = "Parts" + thumnails = "Thumbnails" # report image consts class ReportInfoConst: diff --git a/custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/faiss_functions.py b/custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/faiss_functions.py index b07c8b0..b99f7d1 100755 --- a/custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/faiss_functions.py +++ b/custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/faiss_functions.py @@ -100,21 +100,37 @@ def save_report_image(report_info, report_image_path): table_setting(report_info, report_image_path) -def get_clip_info(model, query_image_path, top_k=4): +def get_clip_info(model, query_image_path, item_info, top_k=4): """ 이미지 유사도 검색 """ - index_file_path = os.path.join(VECTOR_IMG_LIST_PATH,f'{model.name}_{os.path.basename(model.value[1].trained_model)}_{model.value[1].index_type}_{DEFAULT_INDEX_NAME_SUFFIX}') - txt_file_path = os.path.join(VECTOR_IMG_LIST_PATH,f'{model.name}_{os.path.basename(model.value[1].trained_model)}_{model.value[1].index_type}.txt') + index_file_path = os.path.join(VECTOR_IMG_LIST_PATH,f'{model.name}_{os.path.basename(model.value[1].trained_model)}_{model.value[1].index_type}_{item_info}_{DEFAULT_INDEX_NAME_SUFFIX}') + txt_file_path = os.path.join(VECTOR_IMG_LIST_PATH,f'{model.name}_{os.path.basename(model.value[1].trained_model)}_{model.value[1].index_type}_{item_info}.txt') vector_model = VectorSimilarity(model_info=model, index_file_path=index_file_path, txt_file_path=txt_file_path) + image_lists = [] + if not os.path.exists(txt_file_path): + if item_info == VectorSearchItem.glass: + image_lists = find_glass_folder_images(VECTOR_IMG_LIST_PATH) + else: + image_lists = find_parts_folder_images(VECTOR_IMG_LIST_PATH, item_info) + + if image_lists: + with open(txt_file_path, 'w') as f: + for img_path in image_lists: + f.write(f"{img_path}\n") + else: + with open(txt_file_path, 'r') as f: + image_lists = [line.strip() for line in f.readlines()] + # vector_model.create_feature_extraction_model(FEMUsageInfo.openaiclipvit) - vector_model.save_index_from_files(images_path=INDEX_IMAGES_PATH, + vector_model.save_index_from_files(images_path_lists=image_lists, save_index_dir=FAISS_VECTOR_PATH, save_txt_dir=VECTOR_IMG_LIST_PATH, + item_info=item_info, index_type=model.value[1].index_type) inference_times, result_img_paths, result_percents = vector_model.query_faiss(query_image_path, top_k=top_k) @@ -131,5 +147,56 @@ def get_clip_info(model, query_image_path, top_k=4): ) return report_info + +def find_glass_folder_images(root_folder): + """ + glass(thumnails) 폴더 내의 모든 이미지 파일 경로를 재귀적으로 탐색하여 리스트로 반환합니다. + + Args: + root_folder (str): 탐색을 시작할 최상위 폴더 + + Returns: + list: 발견된 이미지 파일 전체 경로의 리스트. + """ + image_paths = [] + + for dirpath, dirnames, filenames in os.walk(root_folder): + + if os.path.basename(dirpath) == ImageDepths.thumnails: + + for filename in filenames: + if filename.lower().endswith(('.png', '.jpg', '.jpeg')): + + # 파일의 전체 경로를 생성하여 리스트에 추가 + full_path = os.path.join(dirpath, filename) + image_paths.append(full_path) + + return image_paths + + +def find_parts_folder_images(root_folder, parts): + """ + parts 폴더 내의 해당하는 파츠 이미지 파일 경로를 재귀적으로 탐색하여 리스트로 반환합니다. + + Args: + root_folder (str): 탐색을 시작할 최상위 폴더 + + Returns: + list: 발견된 이미지 파일 경로의 리스트. + """ + image_paths = [] + + for dirpath, dirnames, filenames in os.walk(root_folder): + + if os.path.basename(dirpath) == ImageDepths.parts: + + for filename in filenames: + if filename.lower().endswith(('.png', '.jpg', '.jpeg')) and parts in filename: + + # 파일의 전체 경로를 생성하여 리스트에 추가 + full_path = os.path.join(dirpath, filename) + image_paths.append(full_path) + + return image_paths diff --git a/custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/faiss_similarity_search.py b/custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/faiss_similarity_search.py index 059dac2..37b324a 100755 --- a/custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/faiss_similarity_search.py +++ b/custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/faiss_similarity_search.py @@ -234,73 +234,67 @@ class VectorSimilarity: feature_vectors = self.fem_model.image_embedding(image_data_np) return feature_vectors - def save_index_from_files(self, images_path=None, save_index_dir=None, save_txt_dir=None, index_type=INDEX_TYPE_L2): + def save_index_from_files(self, images_path_lists=None, save_index_dir=None, save_txt_dir=None, item_info=None, index_type=INDEX_TYPE_L2): """ 인덱스 정보 저장 :param images_path: 이미지 파일 경로 :param save_index_dir: 인덱스 저장 디렉토리 :param save_txt_dir: 이미지 경로 저장 디렉토리 + :param item_info: 아이템 정보 :param index_type: 인덱스 타입 (L2, Cosine) :return: 이미지 임베딩 차원 정보 """ assert self.fem_model is not None, 'invalid fem_model' - assert images_path is not None, 'images_path is required' + assert images_path_lists is not None, 'images_path is required' - if not os.path.exists(images_path): - log.error(f'image_path={images_path} does not exist') - - image_folder = images_path result = None - # 모든 이미지 임베딩 모으기 - if os.path.exists(image_folder): - image_paths = [os.path.join(image_folder, image_file) for image_file in os.listdir(image_folder)] - image_vectors = np.vstack([self.image_embedding_from_file(image_file) for image_file in image_paths]) - log.debug(f'image_vectors.shape={image_vectors.shape}') - result = image_vectors.shape + image_paths = images_path_lists + image_vectors = np.vstack([self.image_embedding_from_file(image_file) for image_file in image_paths]) + log.debug(f'image_vectors.shape={image_vectors.shape}') + result = image_vectors.shape - # 인덱스 생성 (L2) - dimension = image_vectors.shape[1] + # 인덱스 생성 (L2) + dimension = image_vectors.shape[1] - if index_type == INDEX_TYPE_L2: - index = faiss.IndexFlatL2(dimension) # L2 - elif index_type == INDEX_TYPE_COSINE: - index = faiss.IndexFlatIP(dimension) # cosine - faiss.normalize_L2(image_vectors) - else: - raise Exception(f'Invalid index_type={index_type}') - - index.add(image_vectors) - - # 인덱스 저장 - if save_index_dir is None: - save_index_path = os.path.join(FILE_BASE_PATH,f'{self.fem_model_name}_{os.path.basename(self.fem_model_args.trained_model)}_{index_type}_{DEFAULT_SAVE_INDEX_SUFFIX}') - self.index_file_path = save_index_path - else: - save_index_path = os.path.join(save_index_dir,f'{self.fem_model_name}_{os.path.basename(self.fem_model_args.trained_model)}_{index_type}_{DEFAULT_SAVE_INDEX_SUFFIX}') - os.makedirs(save_index_dir, exist_ok=True) - - # 이미지 경로 파일 저장 - if save_txt_dir is None: - save_txt_path = os.path.join(FILE_BASE_PATH, f'{self.fem_model_name}_{os.path.basename(self.fem_model_args.trained_model)}_{index_type}.txt') - self.txt_file_path = save_txt_path - else: - save_txt_path = os.path.join(save_txt_dir, f'{self.fem_model_name}_{os.path.basename(self.fem_model_args.trained_model)}_{index_type}.txt') - os.makedirs(save_txt_dir, exist_ok=True) - - if not os.path.exists(save_txt_path): - faiss.write_index(index, save_index_path) - log.debug(f'save_index_path={save_index_path}') - - if not os.path.exists(save_txt_path): - # 이미지 경로 저장 - with open(save_txt_path, 'w') as f: - for path in image_paths: - f.write(f"{path}\n") - log.debug(f'save_txt_path={save_txt_path}') - + if index_type == INDEX_TYPE_L2: + index = faiss.IndexFlatL2(dimension) # L2 + elif index_type == INDEX_TYPE_COSINE: + index = faiss.IndexFlatIP(dimension) # cosine + faiss.normalize_L2(image_vectors) else: - log.error(f'Image folder {image_folder} does not exist') + raise Exception(f'Invalid index_type={index_type}') + + index.add(image_vectors) + + # 인덱스 저장 + if save_index_dir is None: + save_index_path = os.path.join(FILE_BASE_PATH,f'{self.fem_model_name}_{os.path.basename(self.fem_model_args.trained_model)}_{index_type}_{item_info}_{DEFAULT_SAVE_INDEX_SUFFIX}') + self.index_file_path = save_index_path + else: + save_index_path = os.path.join(save_index_dir,f'{self.fem_model_name}_{os.path.basename(self.fem_model_args.trained_model)}_{index_type}_{item_info}_{DEFAULT_SAVE_INDEX_SUFFIX}') + os.makedirs(save_index_dir, exist_ok=True) + + # # 이미지 경로 파일 저장 + # if save_txt_dir is None: + # save_txt_path = os.path.join(FILE_BASE_PATH, f'{self.fem_model_name}_{os.path.basename(self.fem_model_args.trained_model)}_{index_type}.txt') + # self.txt_file_path = save_txt_path + # else: + # save_txt_path = os.path.join(save_txt_dir, f'{self.fem_model_name}_{os.path.basename(self.fem_model_args.trained_model)}_{index_type}.txt') + # os.makedirs(save_txt_dir, exist_ok=True) + + if not os.path.exists(save_index_path): + faiss.write_index(index, save_index_path) + log.debug(f'save_index_path={save_index_path}') + + # if not os.path.exists(save_txt_path): + # # 이미지 경로 저장 + # with open(save_txt_path, 'w') as f: + # for path in image_paths: + # f.write(f"{path}\n") + # log.debug(f'save_txt_path={save_txt_path}') + + return result def set_index_path(self, index_path): @@ -394,7 +388,7 @@ def test(): # cm.create_feature_extraction_model(FEMUsageInfo.openaiclipvit) - cm.save_index_from_files(images_path=INDEX_IMAGES_PATH, + cm.save_index_from_files(images_path_lists=INDEX_IMAGES_PATH, save_index_dir=FAISS_VECTOR_PATH, save_txt_dir=VECTOR_IMG_LIST_PATH, index_type=model.value[1].index_type) diff --git a/custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/utils.py b/custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/utils.py new file mode 100644 index 0000000..7c2dca8 --- /dev/null +++ b/custom_apps/FEATURE_VECTOR_SIMILARITY_FAISS/utils.py @@ -0,0 +1,31 @@ +import os +from pathlib import Path + +from custom_apps.FEATURE_VECTOR_SIMILARITY_FAISS.const import * + +def search_glass_parts(image_path_list): + result = [] + + for image_path in image_path_list: + parts_path = Path(os.path.join(os.path.dirname(os.path.dirname(image_path)),ImageDepths.parts)) + parts_files_generator = parts_path.rglob('*.png') + + parts_list = [] + # parts_list = [str(p.resolve()) for p in parts_files_generator] # 풀경로 + + for p in parts_files_generator: + # name,ext = os.path.splitext(p.name) + parts_list.append(p.name) + result.append(parts_list) + + return result + +def file_name_to_parts(file_path): + filename, ext = os.path.splitext(os.path.basename(file_path)) + + name_list = filename.split('_') + + result = name_list[2:][0] + + return result + \ No newline at end of file diff --git a/main_rest/app/models.py b/main_rest/app/models.py index d3c2ab3..ad1b7fe 100644 --- a/main_rest/app/models.py +++ b/main_rest/app/models.py @@ -30,7 +30,7 @@ from main_rest.app.common.consts import ( DEFAULT_USER_ACCOUNT_PW ) from main_rest.app.utils.date_utils import D - +from custom_apps.FEATURE_VECTOR_SIMILARITY_FAISS.const import VectorSearchItem class SWInfo(BaseModel): """ @@ -581,6 +581,11 @@ class VitModelType: b16 = "b16" l14 = "l14" l14_336 = "l14_336" + + +class PartsType(str, Enum): + bridge = VectorSearchItem.bridge + hinge = VectorSearchItem.hinge class ImageGenerateReq(BaseModel): @@ -616,7 +621,7 @@ class VectorImageSearchVitReq(BaseModel): searchNum : int = Field(4, description='검색결과 이미지 갯수', example=4) -class VectorImageSearchVitInputImgReq(BaseModel): +class VectorGlassesImageSearchVitInputImgReq(BaseModel): """ ### [Request] vector image search vit - input image """ @@ -626,6 +631,14 @@ class VectorImageSearchVitInputImgReq(BaseModel): searchNum : int = Field(4, description='검색결과 이미지 갯수', example=4) +class VectorPartsImageSearchVitInputImgReq(VectorGlassesImageSearchVitInputImgReq): + """ + ### [Request] vector image search vit data - input image + """ + inputImage : str = Field(description='파츠이미지 이름', example='') + + + class VectorImageSearchVitDataReq(VectorImageSearchVitReq): """ ### [Request] vector image search vit data @@ -642,11 +655,17 @@ class VectorImageSearchVitReportReq(BaseModel): indexType : str = Field(VitIndexType.l2, description='인덱스 타입', example=VitIndexType.l2) -class VectorImageResult(BaseModel): +class VectorGlassesImageResult(BaseModel): image : str = Field("", description='이미지 데이터', example='') percents: float = Field(0.0, description='percents 값', example='') imageInfo : str = Field("", description='원본이미지 이름', example='') + parts: list = Field([], description='안경 파츠 정보', example=[]) + +class VectorPartsImageResult(BaseModel): + image : str = Field("", description='이미지 데이터', example='') + percents: float = Field(0.0, description='percents 값', example='') + imageInfo : str = Field("", description='원본이미지 이름', example='') #=============================================================================== #=============================================================================== #=============================================================================== @@ -716,12 +735,12 @@ class BingCookieSetRes(ResponseBase): return BingCookieSetRes -class VectorImageSerachDataRes(ResponseBase): +class VectorGlassesImageSerachDataRes(ResponseBase): """ ### vector image data response """ queryImage : str = Field("", description='쿼리 이미지', example="") - vectorResult : List[VectorImageResult] = Field([], description='벡터 검색 결과', example=[]) + vectorResult : List[VectorGlassesImageResult] = Field([], description='벡터 검색 결과', example=[]) @staticmethod def set_error(error,vector_result=[],query_img=""): @@ -739,4 +758,8 @@ class VectorImageSerachDataRes(ResponseBase): ImageGenerateRes.vectorResult = vector_result ImageGenerateRes.queryImage = query_img - return ImageGenerateRes \ No newline at end of file + return ImageGenerateRes + + +class VectorPartsImageSerachDataRes(VectorGlassesImageSerachDataRes): + vectorResult : List[VectorPartsImageResult] = Field([], description='벡터 검색 결과', example=[]) \ No newline at end of file diff --git a/main_rest/app/routes/dev.py b/main_rest/app/routes/dev.py index 9832de0..0e6a5e1 100644 --- a/main_rest/app/routes/dev.py +++ b/main_rest/app/routes/dev.py @@ -135,3 +135,75 @@ async def test(request: Request): return a +# @router.post("/vectorImageSearch/vit/imageGenerate/imagen/report", summary="벡터 이미지 검색(clip-vit) - imagen, report 생성", response_model=M.ResponseBase) +# async def vactor_vit_report(request: Request, request_body_info: M.VectorImageSearchVitReportReq): + # """ + # ## 벡터 이미지 검색(clip-vit) - imagen, report 생성 + # > imagen AI를 이용하여 이미지 생성 후 vector 검색 그후 종합결과 이미지 생성 + + # ### Requriements + # > - googlecli 설치(https://cloud.google.com/sdk/docs/install?hl=ko#linux) + + # ### options + # > - modelType -> b32,b16,l14,l14_336 + # > - indexType -> l2,cos + + # """ + # response = M.ResponseBase() + # try: + # if request_body_info.modelType not in [M.VitModelType.b32, M.VitModelType.b16, M.VitModelType.l14, M.VitModelType.l14_336]: + # raise Exception(f"modelType is invalid (current value = {request_body_info.modelType})") + + # if request_body_info.indexType not in [M.VitIndexType.cos, M.VitIndexType.l2]: + # raise Exception(f"indexType is invalid (current value = {request_body_info.indexType})") + + # # query_image_path = imagen_generate_temp_image_path(image_prompt=request_body_info.prompt) #imagen + # query_image_path = gemini_image(request_body_info.prompt) #gemini + # report_image_path = f"{os.path.splitext(query_image_path)[0]}_report.png" + + # vactor_request_data = {'query_image_path' : query_image_path, + # 'index_type' : request_body_info.indexType, + # 'model_type' : request_body_info.modelType, + # 'report_path' : report_image_path} + + # vactor_response = requests.post('http://localhost:51002/api/services/faiss/vector/search/vit/report', data=json.dumps(vactor_request_data)) + + # if vactor_response.status_code != 200: + # raise Exception(f"response error: {json.loads(vactor_response.text)['error']}") + + # if json.loads(vactor_response.text)["error"] != None: + # raise Exception(f"vector error: {json.loads(vactor_response.text)['error']}") + + # if rest_config.config != 'release': + # # remote 폴더에 이미지 저장 + # sftp_client.remote_copy_data(local_path=report_image_path, + # remote_path=os.path.join(rest_config.remote_folder, f"imagen_report_vit_{request_body_info.prompt}_{D.date_file_name()}.png")) + # else: + # shutil.copy(report_image_path, os.path.join(rest_config.local_folder, f"imagen_report_vit_{request_body_info.prompt}_{D.date_file_name()}.png")) + + # # Clean up temporary files + # if 'query_image_path' in locals(): + # if os.path.exists(query_image_path): + # os.remove(query_image_path) + # del query_image_path + # if 'report_image_path' in locals(): + # if os.path.exists(report_image_path): + # os.remove(report_image_path) + # del report_image_path + + # return response.set_message() + + # except Exception as e: + # LOG.error(traceback.format_exc()) + + # # Clean up temporary files + # if 'query_image_path' in locals(): + # if os.path.exists(query_image_path): + # os.remove(query_image_path) + # del query_image_path + # if 'report_image_path' in locals(): + # if os.path.exists(report_image_path): + # os.remove(report_image_path) + # del report_image_path + + # return response.set_error(error=e) \ No newline at end of file diff --git a/main_rest/app/routes/services.py b/main_rest/app/routes/services.py index b1208bd..3ae581d 100644 --- a/main_rest/app/routes/services.py +++ b/main_rest/app/routes/services.py @@ -30,6 +30,8 @@ from utils.custom_sftp import sftp_client from config import rest_config from const import TEMP_FOLDER +from custom_apps.FEATURE_VECTOR_SIMILARITY_FAISS.const import * + router = APIRouter(prefix="/services") # @router.post("/bing/cookie/set", summary="bing 관련 쿠키 set", response_model=M.BingCookieSetRes) @@ -471,10 +473,10 @@ async def image_generator(request: Request, request_body_info: M.ImageGenerateRe return response.set_error(error=e) -@router.post("/vectorImageSearch/vit/inputImage/data", summary="벡터 이미지 검색(clip-vit) - input image(data)", response_model=M.VectorImageSerachDataRes) -async def vactor_vit_input_img_data(request: Request, request_body_info: M.VectorImageSearchVitInputImgReq): +@router.post("/vectorImageSearch/vit/inputImage/data/glasses", summary="벡터 이미지 검색(clip-vit) - input image(data): glasses", response_model=M.VectorGlassesImageSerachDataRes) +async def vactor_vit_input_glasses_img_data(request: Request, request_body_info: M.VectorGlassesImageSearchVitInputImgReq): """ - ## 벡터 이미지 검색(clip-vit) - inputimage + ## 벡터 이미지 검색(clip-vit) - inputimage : glasses > 입력된 이미지로 vector 검색 그후 결과 이미지데이터 return ### Input @@ -494,7 +496,7 @@ async def vactor_vit_input_img_data(request: Request, request_body_info: M.Vecto > - indexType , modelType - 새로운 조합 (ex l14, cos)으로 처음 요청할경우 모델을 빌드하는 과정이 추가됨 """ - response = M.VectorImageSerachDataRes() + response = M.VectorGlassesImageSerachDataRes() query_image_data = '' try: @@ -526,15 +528,16 @@ async def vactor_vit_input_img_data(request: Request, request_body_info: M.Vecto result_image_paths = vector_response_dict.get('img_list').get('result_image_paths') result_percents = vector_response_dict.get('img_list').get('result_percents') + result_parts = vector_response_dict.get('img_list').get('result_parts_list') # 이미지 데이터 생성 vector_image_results = [] - for img, percents in zip(result_image_paths, result_percents): + for img, percents, parts in zip(result_image_paths, result_percents, result_parts): b64_data = image_to_base64_string(img) float_percent = float(percents) - info = M.VectorImageResult(image=b64_data, percents=float_percent, imageInfo=os.path.split(img)[-1]) + info = M.VectorGlassesImageResult(image=b64_data, percents=float_percent, imageInfo=os.path.split(img)[-1], parts=parts) vector_image_results.append(info) @@ -558,75 +561,72 @@ async def vactor_vit_input_img_data(request: Request, request_body_info: M.Vecto return response.set_error(error=e) -# @router.post("/vectorImageSearch/vit/imageGenerate/imagen/report", summary="벡터 이미지 검색(clip-vit) - imagen, report 생성", response_model=M.ResponseBase) -# async def vactor_vit_report(request: Request, request_body_info: M.VectorImageSearchVitReportReq): +@router.post("/vectorImageSearch/vit/inputImage/data/parts", summary="벡터 이미지 검색(clip-vit) - input image(data): parts", response_model=M.VectorPartsImageSerachDataRes) +async def vactor_vit_input_parts_img_data(request: Request, request_body_info: M.VectorPartsImageSearchVitInputImgReq): """ - ## 벡터 이미지 검색(clip-vit) - imagen, report 생성 - > imagen AI를 이용하여 이미지 생성 후 vector 검색 그후 종합결과 이미지 생성 + ## 벡터 이미지 검색(clip-vit) - inputImage : parts + > 입력된 이미지로 vector 검색 그후 결과 이미지데이터 return - ### Requriements - > - googlecli 설치(https://cloud.google.com/sdk/docs/install?hl=ko#linux) + ### Input + > 입력이미지(inputImage)는 파츠이미지 이름 (확장자 포함) - ### options - > - modelType -> b32,b16,l14,l14_336 - > - indexType -> l2,cos + ### Output + > - 결과(vectorResult)는 base64로 변환된 데이터(image), 유사도(percents)가 쌍으로 나오며, 요청한 searchNum 갯수에 맞춰서 결과가 나옴 + > - queryImage는 Input시 입력한 inputImage 이미지 데이터 + ### Options + > - modelType -> b32, b16, l14, l14_336 (기본값: b32) + > - indexType -> l2, cos (기본값: l2) + > - searchNum -> 결과이미지 갯수 (기본값: 4) + + ### Notice + > - 일반검색시 indexType , modelType 은 기본값으로 사용 + > - indexType , modelType, inputImage - 새로운 조합 (ex l14, cos, Lens)으로 처음 요청할경우 모델을 빌드하는 과정이 추가됨 """ - response = M.ResponseBase() - try: + response = M.VectorPartsImageSerachDataRes() + try: if request_body_info.modelType not in [M.VitModelType.b32, M.VitModelType.b16, M.VitModelType.l14, M.VitModelType.l14_336]: raise Exception(f"modelType is invalid (current value = {request_body_info.modelType})") if request_body_info.indexType not in [M.VitIndexType.cos, M.VitIndexType.l2]: raise Exception(f"indexType is invalid (current value = {request_body_info.indexType})") - - # query_image_path = imagen_generate_temp_image_path(image_prompt=request_body_info.prompt) #imagen - query_image_path = gemini_image(request_body_info.prompt) #gemini - report_image_path = f"{os.path.splitext(query_image_path)[0]}_report.png" - - vactor_request_data = {'query_image_path' : query_image_path, + + glass_name = f"{request_body_info.inputImage.split('_')[0]}_{request_body_info.inputImage.split('_')[1]}" + query_image_path = os.path.join(VECTOR_IMG_LIST_PATH, glass_name, ImageDepths.parts, request_body_info.inputImage) + query_image_data = image_to_base64_string(query_image_path) + + vector_request_data = {'query_image_path' : query_image_path, 'index_type' : request_body_info.indexType, 'model_type' : request_body_info.modelType, - 'report_path' : report_image_path} + 'search_num' : request_body_info.searchNum} - vactor_response = requests.post('http://localhost:51002/api/services/faiss/vector/search/vit/report', data=json.dumps(vactor_request_data)) + vector_response = requests.post('http://localhost:51002/api/services/faiss/vector/search/vit/parts', data=json.dumps(vector_request_data)) - if vactor_response.status_code != 200: - raise Exception(f"response error: {json.loads(vactor_response.text)['error']}") + vector_response_dict = json.loads(vector_response.text) - if json.loads(vactor_response.text)["error"] != None: - raise Exception(f"vector error: {json.loads(vactor_response.text)['error']}") + if vector_response.status_code != 200: + raise Exception(f"search server error: {vector_response_dict['error']}") - if rest_config.config != 'release': - # remote 폴더에 이미지 저장 - sftp_client.remote_copy_data(local_path=report_image_path, - remote_path=os.path.join(rest_config.remote_folder, f"imagen_report_vit_{request_body_info.prompt}_{D.date_file_name()}.png")) - else: - shutil.copy(report_image_path, os.path.join(rest_config.local_folder, f"imagen_report_vit_{request_body_info.prompt}_{D.date_file_name()}.png")) - - # Clean up temporary files - if 'query_image_path' in locals(): - if os.path.exists(query_image_path): - os.remove(query_image_path) - del query_image_path - if 'report_image_path' in locals(): - if os.path.exists(report_image_path): - os.remove(report_image_path) - del report_image_path + if vector_response_dict["error"] != None: + raise Exception(f"search result error: {vector_response_dict['error']}") - return response.set_message() + result_image_paths = vector_response_dict.get('img_list').get('result_image_paths') + result_percents = vector_response_dict.get('img_list').get('result_percents') + + # 이미지 데이터 생성 + vector_image_results = [] + for img, percents in zip(result_image_paths, result_percents): + + b64_data = image_to_base64_string(img) + float_percent = float(percents) + + info = M.VectorPartsImageResult(image=b64_data, percents=float_percent, imageInfo=os.path.split(img)[-1]) + + vector_image_results.append(info) + + return response.set_message(vector_result=vector_image_results,query_img=query_image_data) except Exception as e: LOG.error(traceback.format_exc()) - - # Clean up temporary files - if 'query_image_path' in locals(): - if os.path.exists(query_image_path): - os.remove(query_image_path) - del query_image_path - if 'report_image_path' in locals(): - if os.path.exists(report_image_path): - os.remove(report_image_path) - del report_image_path - return response.set_error(error=e) \ No newline at end of file + return response.set_error(error=e) diff --git a/vector_rest/app/models.py b/vector_rest/app/models.py index 85dd339..380c0c1 100644 --- a/vector_rest/app/models.py +++ b/vector_rest/app/models.py @@ -583,7 +583,7 @@ class VitModelType(str, Enum): l14_336 = "l14_336" -class VactorSearchReq(BaseModel): +class VectorSearchReq(BaseModel): """ ### [Request] vector 검색 """ @@ -592,7 +592,7 @@ class VactorSearchReq(BaseModel): search_num : int = Field(4, description='검색결과 이미지 갯수', example=4) -class VactorSearchVitReportReq(BaseModel): +class VectorSearchVitReportReq(BaseModel): """ ### [Request] vector 검색(vit) 후 리포트 이미지 생성 """ @@ -602,7 +602,7 @@ class VactorSearchVitReportReq(BaseModel): report_path : str = Field(description='리포트 이미지 저장 경로', example='path') -class VactorSearchVitReq(BaseModel): +class VectorSearchVitReq(BaseModel): """ ### [Request] vector 검색(vit) 후 이미지 생성 """ @@ -611,22 +611,21 @@ class VactorSearchVitReq(BaseModel): model_type : VitModelType = Field(VitModelType.l14, description='pretrained 모델 정보', example=VitModelType.l14) search_num : int = Field(4, description='검색결과 이미지 갯수', example=4) - -class VactorSearchVitRes(ResponseBase): +class VectorSearchVitRes(ResponseBase): img_list : dict = Field({}, description='이미지 결과 리스트', example={}) @staticmethod def set_error(error): - VactorSearchVitRes.img_list = {} - VactorSearchVitRes.result = False - VactorSearchVitRes.error = str(error) + VectorSearchVitRes.img_list = {} + VectorSearchVitRes.result = False + VectorSearchVitRes.error = str(error) - return VactorSearchVitRes + return VectorSearchVitRes @staticmethod def set_message(msg): - VactorSearchVitRes.img_list = msg - VactorSearchVitRes.result = True - VactorSearchVitRes.error = None + VectorSearchVitRes.img_list = msg + VectorSearchVitRes.result = True + VectorSearchVitRes.error = None - return VactorSearchVitRes + return VectorSearchVitRes diff --git a/vector_rest/app/routes/services.py b/vector_rest/app/routes/services.py index 15a9ce7..7b48865 100644 --- a/vector_rest/app/routes/services.py +++ b/vector_rest/app/routes/services.py @@ -23,12 +23,14 @@ from custom_apps.faiss_imagenet.main import search_idxs from custom_apps.FEATURE_VECTOR_SIMILARITY_FAISS.faiss_functions import get_clip_info, save_report_image, get_models from custom_apps.FEATURE_VECTOR_SIMILARITY_FAISS.faiss_similarity_search import FEMUsageInfo +from custom_apps.FEATURE_VECTOR_SIMILARITY_FAISS.const import VectorSearchItem +from custom_apps.FEATURE_VECTOR_SIMILARITY_FAISS.utils import search_glass_parts, file_name_to_parts router = APIRouter(prefix="/services") @router.post("/faiss/vector/search/imagenet", summary="imagenet search", response_model=M.ResponseBase) -async def vactor_search(request: Request, request_body_info: M.VactorSearchReq): +async def vactor_search(request: Request, request_body_info: M.VectorSearchReq): """ ## 벡터검색 @@ -54,7 +56,7 @@ async def vactor_search(request: Request, request_body_info: M.VactorSearchReq): @router.post("/faiss/vector/search/vit/report", summary="vit search report", response_model=M.ResponseBase) -async def vactor_report_vit(request: Request, request_body_info: M.VactorSearchVitReportReq): +async def vactor_report_vit(request: Request, request_body_info: M.VectorSearchVitReportReq): """ 이미지 경로를 입력받아 repport image 저장 """ @@ -75,23 +77,59 @@ async def vactor_report_vit(request: Request, request_body_info: M.VactorSearchV return response.set_error(e) -@router.post("/faiss/vector/search/vit", summary="vit search", response_model=M.VactorSearchVitRes) -async def vactor_vit(request: Request, request_body_info: M.VactorSearchVitReq): +@router.post("/faiss/vector/search/vit", summary="vit search", response_model=M.VectorSearchVitRes) +async def vactor_vit(request: Request, request_body_info: M.VectorSearchVitReq): """ 이미지 경로 를 입력받아 vit 방식으로 검색 """ - response = M.VactorSearchVitRes() + response = M.VectorSearchVitRes() try: if not os.path.exists(request_body_info.query_image_path): raise FileNotFoundError(f"File {request_body_info.query_image_path} does not exist.") model = get_models(index_type=request_body_info.index_type, model_type=request_body_info.model_type) - report_info = get_clip_info(model,request_body_info.query_image_path,top_k=request_body_info.search_num) + report_info = get_clip_info(model=model, + query_image_path=request_body_info.query_image_path, + item_info=VectorSearchItem.glass, + top_k=request_body_info.search_num) + + parts = search_glass_parts(report_info.result_image_paths) return response.set_message({ 'result_image_paths': report_info.result_image_paths, - 'result_percents': report_info.result_percents + 'result_percents': report_info.result_percents, + 'result_parts_list': parts + }) + + except Exception as e: + LOG.error(traceback.format_exc()) + return response.set_error(e) + + +@router.post("/faiss/vector/search/vit/parts", summary="vit search parts", response_model=M.VectorSearchVitRes) +async def vactor_vit_parts(request: Request, request_body_info: M.VectorSearchVitReq): + """ + 이미지 경로 를 입력받아 vit 방식으로 검색 + """ + response = M.VectorSearchVitRes() + try: + if not os.path.exists(request_body_info.query_image_path): + raise FileNotFoundError(f"File {request_body_info.query_image_path} does not exist.") + + model = get_models(index_type=request_body_info.index_type, model_type=request_body_info.model_type) + + #TODO parts 데이터 확인필요 + parts = file_name_to_parts(request_body_info.query_image_path) + + report_info = get_clip_info(model=model, + query_image_path=request_body_info.query_image_path, + item_info=parts, + top_k=request_body_info.search_num) + + return response.set_message({ + 'result_image_paths': report_info.result_image_paths, + 'result_percents': report_info.result_percents, }) except Exception as e: