Skip to content

Inference

core.InferenceHandler

온라인 부품 식별과 3D 검사를 한 프로세스에서 제공하는 public handler class.

모델 weight와 활성 카탈로그를 각각 하나만 상주시키고 이미지 식별·스캔 식별이 그것을 함께 사용. 따라서 두 기능을 함께 사용해도 상주 메모리가 두 배가 되지 않고, set_catalog 로 카탈로그를 교체하면 두 기능에 동시에 적용됨. 스캔 준비(prepare_scan)와 스캔 검사(inspect_scan)는 활성 카탈로그를 읽지 않고, 검사 기준 형상은 호출자가 PartGeometry 로 전달.

모든 public 메서드는 (code, message, value) 튜플을 반환. code 는 성공 시 0, 실패 시 음수 6자리 정수이고 실패한 호출의 value 는 None. 메서드별 Raises 의 오류는 예외로 전파되지 않고 code 로 반환되며, code 절댓값의 끝 세 자리가 오류 종류. 실패한 호출의 message 는 첫 줄이 오류 종류별 고정 문구이고 상세 사유가 있으면 다음 줄에 이어짐. CUDA 메모리 부족과 numpy 배열 할당 실패는 SaigeToolkit 공통 오류 번호로 반환되고, 그 밖의 종류별 번호는 다음과 같음.

  • 001: ConfigError
  • 003: CheckpointNotExistError
  • 005: InvalidInferenceOptionError
  • 006: DataFormatError
  • 019: WeightBundleCorruptError
  • 022: InvalidResizeMethodError
  • 026: CatalogConfigMismatchError
  • 028: CatalogNotSetError
  • 999: 위 종류에 속하지 않는 예외 (message 는 Unknown error).

build config는 inference_option·device·enable_timer 세 key만 받음. build 직후는 카탈로그 미설정 상태이고, 카탈로그는 set_catalog 로 CatalogData 를 전달. 검출·crop·feature·matcher 등 내부 모듈 구성은 배포 시점에 고정되며 공개면에 노출하지 않음.

이 class는 lock도 queue도 두지 않고 카탈로그 교체도 검출하지 않음. 활성 카탈로그를 읽는 identify_images · identify_scan 과 set_catalog 의 직렬화는 호출자 책임. prepare_scan · inspect_scan 은 활성 카탈로그를 읽지 않아 결과가 카탈로그 교체와 무관.

Usage
from core import InferenceHandler
from core.data.catalog.catalog_data import CatalogData

err, msg, handler = InferenceHandler.build({"device": "cuda:0"})
err, msg, _ = handler.set_catalog(CatalogData(catalog_id="v0001", parts=parts))
err, msg, _ = handler.warmup()
err, msg, result = handler.identify_images(images)

build(config) classmethod

config로부터 InferenceHandler instance를 생성.

허용 key는 아래 셋뿐이며 그 밖의 key가 포함되면 ConfigError 로 거절. 오류 메시지에 위반 key와 허용 key를 함께 표기하므로 key 오타가 무시되지 않음. 검출·crop·feature·matcher 등 내부 모듈 구성은 배포 시점에 고정되어 이 config로 변경할 수 없음. 반환 handler 는 카탈로그 미설정 상태이므로 추론 호출 전에 set_catalog 를 호출할 것.

Parameters:

Name Type Description Default
config dict[str, Any]

build config. 허용 key 는 다음과 같음.

{
    "inference_option": dict,  # 선택. 초기 inference option. {target: {key: value}}
                               # target 은 identify. set_inference_option 의
                               # (target, key, value) 와 같은 이름 공간.
                               # 예: {"identify": {"top_m": 3}}. 지원 key 는
                               # get_default_inference_option 참고.
    "device": str,             # 선택. 연산 device. (default "cuda:0")
    "enable_timer": bool,      # 선택. 단계별 소요 시간 측정 여부. True 면 identify_images ·
                               # prepare_scan · identify_scan · inspect_scan 결과의
                               # timing 에 호출마다 자기 단계의 시간(ms), False 면
                               # timing 은 None. bool 이 아닌 값은 거절. device 가
                               # CUDA 면 검출 · feature 추출 timer 가 단계 끝에서 GPU
                               # 동기화를 수행해 호출 시간이 증가할 수 있음. 단계 정의는
                               # 각 메서드 Returns 의 timing 참고. (default False)
}

required

Returns:

Type Description
InferenceHandler

build가 완료된 instance.

Raises:

Type Description
ConfigError

허용 밖의 key가 있거나 enable_timer 가 bool 이 아닌 경우.

CheckpointNotExistError

모델 weight 번들 경로가 존재하지 않는 경우.

WeightBundleCorruptError

모델 weight 번들이 손상되어 읽을 수 없는 경우.

InvalidInferenceOptionError

inference_option 의 target·key가 지원 대상이 아니거나 top_m 이 1 이상의 정수 (bool 제외) 가 아닌 경우.

identify_images(images, boxes=None)

query 이미지에 대해 top-M candidate와 이미지별 detection을 반환.

images 의 모든 crop 이 query 하나를 구성하고 candidate 목록은 호출당 하나이므로, 한 호출에는 같은 부품을 촬영한 이미지만 전달할 것.

고해상 이미지는 engine이 내부에서 한 번 축소해 추론하므로 호출자의 사전 축소가 불필요. 입력 박스와 반환 detection은 모두 원본 좌표계 기준.

Parameters:

Name Type Description Default
images list[ndarray]

RGB uint8 [H, W, 3] query 이미지 목록 (원본 해상도 그대로).

required
boxes list[list[tuple[int, int, int, int]] | None] | None

이미지별 사용자 지정 bbox (images 와 길이 동일). 항목은 (x1, y1, x2, y2) 박스 목록이며, 지정된 이미지는 grounding을 생략하고 분할 box prompt로 mask를 산출. 박스는 이미지 경계로 클램프. None/빈 항목은 기존 detect 경로.

None

Returns:

Type Description
IdentificationResult

추론 결과. 결과와 각 항목(Candidate·Detection)은 dataclass 이며 필드는 attribute 로 접근 (result.candidates[0].part_id). 필드 구조는 다음과 같음.

{
    "candidates": [  # score 내림차순 top-M 후보. 검출이 없는 이미지는 이미지
                     # 전체를 crop 으로 사용.
        {
            "part_id": int,                    # 부품 식별자 (CatalogEntry.part_id).
            "score": float,                    # 매칭 점수 (내림차순 정렬 기준).
            "matched_view_indices": list[int], # query 항목(crop)별 최유사 템플릿 뷰
                                               # 번호 (query 항목 순서와 1:1). 그 부품
                                               # PartRecord.thumbnails 의 색인.
        },
        ...
    ],
    "detections": [  # 이미지별 검출 목록 (입력 images와 길이 동일).
        [  # 이미지 1장의 검출. grounding 경로는 최대 1개(면적 최대),
           # box prompt 경로는 입력 박스 순서·개수 그대로.
            {
                "bbox": (int, int, int, int),  # (x1, y1, x2, y2), 원본 좌표.
                "score": float,             # 검출 confidence. box prompt 경로는 1.0.
                "mask": np.ndarray | None,  # 원본 해상도 boolean instance mask
                                            # (첫 접근 시 생성, 값은 동일).
                "meta": dict | None,        # detector별 부가 정보.
            },
        ],
        ...
    ],
    "timing": dict | None,  # 단계별 소요 시간(ms). detect_time·crop_time·
                            # extract_time·aggregate_time·match_time.
                            # build config enable_timer 가 False(기본)면 None.
}

Raises:

Type Description
CatalogNotSetError

set_catalog 전에 호출한 경우.

DataFormatError

boxes 길이가 images 와 다르거나, 클램프 후 면적이 0 이하인 박스가 있는 경우.

InvalidResizeMethodError

설정된 input_resize option의 method 가 지원 밖인 경우.

InvalidInferenceOptionError

설정된 aggregate option의 method 가 지원 밖인 경우.

Usage
err, msg, result = handler.identify_images(images)
for candidate in result.candidates:
    print(candidate.part_id, candidate.score, candidate.matched_view_indices)
# 이미지 2 장 중 첫 이미지만 박스를 지정. 둘째 이미지는 detect 경로.
err, msg, result = handler.identify_images(images, boxes=[[(120, 80, 640, 520)], None])

prepare_scan(scan_path)

스캔 파일을 읽어 배경을 제거한 foreground 를 반환.

검사 한 건은 이 호출과 identify_scan · inspect_scan 세 호출로 구성. 스캔 파일을 읽는 지점은 이 호출 하나뿐이므로 반환된 표면을 두 호출에 그대로 전달할 것. 호출자 생성·수정 대상 아님. 재사용 시 GiB 단위 스캔 재로드 회피. 활성 카탈로그를 읽지 않아 set_catalog 전에도 동작.

스캔 하나에 부품 하나가 전제. 배경 제거는 부품이 접한 바닥 평면의 접촉부와 평면 아래, 최대 연결 성분에 비해 작은 분리 조각을 제거하며 스캔을 여러 부품으로 분할하지 않음. 바닥이 없는 스캔도 허용.

Parameters:

Name Type Description Default
scan_path str

스캔 mesh 파일 경로. 확장자로 loader 추론 (.ply/.stl/.obj/.stp/.step).

required

Returns:

Type Description
ScanSurface

배경 제거된 foreground. 호출자가 읽는 attribute 는 다음과 같음.

  • corners (np.ndarray): shape (T, 3, 3) float32, mm. 편차 값이 따르는 삼각형 순서의 기준이라 읽기 전용으로 참조 가능하며 수정 금지.
  • floor_plane (np.ndarray | None): shape (4,) 바닥 평면 계수 (nx, ny, nz, d), 단위 mm. 바닥을 검출하지 못했으면 None.
  • timing (dict[str, float] | None): enable_timer 로 build 한 경우 load_time·background_time·select_time (ms, 서로 중복 구간 없음). load_time 은 스캔 파일 읽기와 축소본 구성, background_time 은 배경 제거 판정, select_time 은 foreground 선택. 기본 None.

Raises:

Type Description
DataFormatError

파일이 없거나 확장자를 지원하지 않거나, 스캔에 삼각형이 없거나 bbox가 퇴화한 경우.

Usage
err, msg, scan = handler.prepare_scan("/scans/measured_1.stl")
err, msg, ident = handler.identify_scan(scan)
geometry = {part.part_id: part.record.geometry for part in parts}
err, msg, result = handler.inspect_scan(scan, geometry[ident.candidates[0].part_id])

identify_scan(scan)

준비된 스캔의 부품 후보 순위를 활성 카탈로그에서 반환.

검색 대상은 활성 카탈로그이므로 이 호출과 set_catalog 의 직렬화는 호출자 책임이며, 이 class 는 교체를 검출하지 않음.

Parameters:

Name Type Description Default
scan ScanSurface

prepare_scan 이 반환한 값 그대로.

required

Returns:

Type Description
ScanIdentification

식별 결과. dataclass 이며 필드는 attribute 로 접근. 필드 구조는 다음과 같음.

  • candidates (list[Candidate]): score 내림차순 부품 후보. 순위 대상은 크기 게이트를 통과한 부품이며, 통과 부품이 없으면 활성 카탈로그 전체. 개수 상한은 identify option의 top_m (set_inference_option 으로 조정). 항목은 part_id (int)·score (float)·matched_view_indices (list[int]).
  • timing (dict[str, float] | None): enable_timer 로 build 한 경우 render_time·identify_time (ms, 서로 중복 구간 없음). render_time 은 스캔 멀티뷰 렌더, identify_time 은 렌더 뷰의 feature 추출·종합·매칭. 기본 None.

Raises:

Type Description
CatalogNotSetError

set_catalog 전에 호출한 경우.

inspect_scan(scan, part_geometry)

배경 제거된 스캔을 받은 부품 기하와 대조해 pose 와 표면 편차를 산출.

기준 형상은 호출자가 전달한 part_geometry 하나이며 활성 카탈로그를 읽지 않아 set_catalog 전에도 동작. 정합·편차 구현은 배포 시점에 고정되어 이 호출로 변경할 수 없음. part_geometry 가 이 엔진과 같은 구성으로 온보딩된 형상인지(config_digest)는 호출자 확인 대상, 이 호출은 대조하지 않음.

Parameters:

Name Type Description Default
scan ScanSurface

prepare_scan 이 반환한 값 그대로.

required
part_geometry PartGeometry

검사 대상 부품의 기하. 온보딩 산출물 PartRecord.geometry.

required

Returns:

Type Description
ScanInspectionResult

검사 결과. dataclass 이며 필드는 attribute 로 접근. 필드 구조는 다음과 같음.

  • registration (Registration): 정합 결과. transform 은 shape (4, 4) float64 부품 → 스캔 pose (mm), runner_up_margin (float) 은 차순위 pose 후보 점수에서 채택 pose 점수를 뺀 값.
  • deviation (Deviation): 편차 결과. distances_mm 은 shape (T,) float32, 원본 해상도 스캔 삼각형마다 부호 있는 표면 편차 (mm). 순서는 scan.corners 삼각형 순서, 기준 형상 바깥이 양수 · 안쪽이 음수. 부호는 기준 형상이 닫힌 입체 (PartGeometry.watertight) 이고 법선이 반대인 coincident 면이 없을 때만 신뢰. 탐색 실패 지점은 NaN.
  • timing (dict[str, float] | None): enable_timer 로 build 한 경우 register_time (정합)·deviate_time (편차), 단위 ms. 기본 None.
Usage
err, msg, result = handler.inspect_scan(scan, record.geometry)
pose = result.registration.transform  # (4, 4) 부품 → 스캔, mm
deviation = result.deviation.distances_mm  # (T,) 스캔 삼각형별 편차, mm

warmup()

더미 입력으로 식별 파이프라인과 검사 편차 단계를 1회 실행해 초회 호출 지연을 제거.

식별 파이프라인을 매칭 단계까지 실행하므로 set_catalog 뒤에 호출할 것.

Returns:

Type Description
None

반환값 없음.

Raises:

Type Description
CatalogNotSetError

set_catalog 전에 호출한 경우.

set_catalog(catalog)

모델을 다시 로드하지 않고 이미지 식별·스캔 식별의 활성 카탈로그를 catalog 로 교체.

부품 config_digest 를 이 core 의 온보딩 구성 digest 와 먼저 대조해 다르면 결합 없이 거절 (재온보딩 필요). 통과하면 식별 feature 를 device 에 로드하고 스캔 식별의 크기 게이트 기하를 결합한 뒤 한 번에 전환. 대조나 결합이 실패하면 이전 활성 카탈로그(없으면 미설정 상태)가 그대로 남으므로, 실패 code 를 받은 뒤에도 identify_images 는 교체 이전과 같은 결과를 반환. 이미 설정한 inference option은 교체 뒤에도 유지. 교체 구간에는 이전 · 신규 카탈로그가 함께 상주해 카탈로그 종속 메모리가 일시적으로 증가. catalog_id 와 부품 수 · 소요 시간은 INFO 로그 한 줄에만 기록.

이 호출과 활성 카탈로그를 읽는 identify_images · identify_scan 의 직렬화는 호출자 책임이며 이 class 는 교체를 검출하지 않음. prepare_scan · inspect_scan 결과는 카탈로그 교체와 무관. 같은 catalog_id 에 다른 내용을 부여하지 않는 규칙도 호출자 담당.

catalog 는 호출자가 OnboardHandler.onboard 산출물로 조립. 조립은 handler 밖의 dataclass 생성이라 형식 위반은 code 가 아니라 예외로 직접 전파됨. 조립 규칙은 다음과 같음.

  • CatalogEntry(part_id, record): 부품 하나. part_id 는 호출자가 부여하는 int (bool 제외) 이며 식별 후보가 반환하는 키, record 는 그 부품의 PartRecord. 위반이면 DataFormatError.
  • CatalogData(catalog_id, parts): 카탈로그. catalog_id 는 빈 문자열이 아닌 str, parts 는 CatalogEntry 1개 이상의 list 이며 순서가 동점 처리 순서. 형식 위반이나 부품 간 feature 차원 불일치는 DataFormatError, part_id 중복은 DuplicatePartIdError, 부품 간 config_digest 불일치는 CatalogConfigMismatchError.

Parameters:

Name Type Description Default
catalog CatalogData

새 활성 카탈로그. 모듈은 내용을 수정하지 않음.

required

Returns:

Type Description
None

반환값 없음.

Raises:

Type Description
CatalogConfigMismatchError

부품 config_digest 가 이 core 의 온보딩 구성과 다른 경우.

Usage
from core.data.catalog.catalog_data import CatalogData, CatalogEntry

# records: 호출자가 보관한 part_id → PartRecord (OnboardHandler.onboard 산출물).
parts = [CatalogEntry(part_id=part_id, record=record) for part_id, record in records.items()]
err, msg, handler = InferenceHandler.build({"device": "cuda:0"})
err, msg, _ = handler.set_catalog(CatalogData(catalog_id="v0001", parts=parts))
# 교체는 같은 호출의 반복이며 모델 재로드 없음.
err, msg, _ = handler.set_catalog(CatalogData(catalog_id="v0002", parts=other_parts))

get_default_inference_option(target, key)

target·key inference option의 기본값 사본을 반환.

Parameters:

Name Type Description Default
target str

option 대상은 identify.

required
key str

option key.

required

Returns:

Type Description
Any

기본값의 사본. 호출자가 변형해도 기본값 원본은 불변.

Keys

target: identify key: 식별 결과 후처리 파라미터. 가능한 key는 다음과 같음.

{
    "top_m": int,             # 반환할 candidate 수. 1 이상의 정수. (default 3)
    "aggregate": dict,        # query feature 종합 방식.
                              # {"method": "none" | "mean" | "max"}
                              # (default {"method": "none"})
    "input_resize": dict,     # 진입부 query 축소 방법.
                              # {"method": "area" | "area_halve" | "pyrdown" |
                              #  "linear" | "cubic" | "lanczos4" | "nearest"}
                              # (default {"method": "linear"})
}

Raises:

Type Description
InvalidInferenceOptionError

지원하지 않는 target·key인 경우.

Usage
err, msg, option = handler.get_default_inference_option(target="identify", key="top_m")

get_inference_option(target, key)

현재 설정된 target·key inference option 값의 사본을 반환.

Parameters:

Name Type Description Default
target str

option 대상은 identify.

required
key str

option key.

required

Returns:

Type Description
Any

현재 값의 사본.

Keys

get_default_inference_option 과 동일.

Raises:

Type Description
InvalidInferenceOptionError

지원하지 않는 target·key인 경우.

set_inference_option(target, key, value)

target·key inference option을 모델 재빌드 없이 설정.

Parameters:

Name Type Description Default
target str

option 대상은 identify.

required
key str

option key.

required
value Any

설정할 값.

required

Returns:

Type Description
None

반환값 없음.

Keys

get_default_inference_option 과 동일.

Raises:

Type Description
InvalidInferenceOptionError

지원하지 않는 target·key이거나 top_m 이 1 이상의 정수 (bool 제외) 가 아닌 경우. aggregate·input_resize 의 method 값은 다음 identify_images 호출 시점에 검증.