순서도 모듈로 길이 측정 실습
Recipe 개요
- 1.3에서 진행했던 레시피를 순서도로 실습

모듈 속성
- Enhancement 모듈 속성

- BuildROIs 설정
- ROI는 1개만 설정합니다

-
BuildROIs에서 ROI 박스를 여러개 그려줍니다
사용자 모듈 생성하는 법은 사용자모듈은 UI Manual을 참고해주세요 -
Segmentation 모듈 속성

- LineMeasurement 속성 (모듈 도움말 참조)


- CustomFunction 코드
- "RegionsExtraction" 또는 "PaintRegionsBoundary"의 경우 코드로 진행이 가능합니다
📜 CustomFunction 코드 보기
def paintMeasure(input1, input2, input3, input4, input5):
output1 = input1
output2 = input2
output3 = input3
output4 = input4
output5 = input5
img_src = input1
img_bin = input2
base_point_list = input3
pair_point_list = input4
distance_list = input5
# Paint reigons
img_color = mpp.intel64.gray_to_rgb(img_src)
regions = mpp.intel64.regions_extraction(img_bin, 8)
mpp.intel64.paint_regions_boundary_inplace(img_color, regions, 0, 255, 255)
filtered = [x for x in distance_list[0][0] if x != 0.0]
if len(filtered) > 0 :
min_cd_value = np.min(filtered)
max_cd_value = np.max(filtered)
avg_cd_value = np.mean(filtered)
else:
min_cd_value = 0
max_cd_value = 0
avg_cd_value = 0
# Paint Base Line
for point_x, point_y in base_point_list[0] :
px_x = int(point_x)
px_y = int(point_y)
if px_x < 0 or px_y < 0 : continue
img_color[px_y, px_x] = [255, 0, 0]
# Paint Pair Line
for point_x, point_y in pair_point_list[0] :
px_x = int(point_x)
px_y = int(point_y)
if px_x < 0 or px_y < 0 : continue
img_color[px_y, px_x] = [0, 255, 0]
result_data = [avg_cd_value, min_cd_value, max_cd_value]
output1 = img_color
output2 = result_data
return output1, output2, output3, output4, output5
- "config_spec" 과 "DM_CLASS_DcollTempalte"은 사용자모듈로 생성되어 있습니다
- 사용자 모듈 생성하는 법은 사용자모듈은 UI Manual을 참고해주세요

- 사용자모듈은 내문서/RecipeCreator/UserModule 에 저장되어 있는 파일을 불러옵니다

- 사용자모듈 선택 후 카테고리를 설정합니다

- 아래 코드를 넣어주세요
📜 config_spec 코드 보기
def config_spec(result_image, result_data, item, spec):
img_result = result_image.copy()
result_text = "OUT"
if item == "AVG":
if result_data[0] > spec :
result_text = "OUT"
else:
result_text = "IN"
mpp.intel64.paint_text_inplace(img_result,
f"AVG: {result_data[0]:.3f} ({result_text})",
'simple',
False,
10, 30,
255, 0, 0,
0.7,
1
)
if item == "MIN":
if result_data[1] > spec :
result_text = "OUT"
else:
result_text = "IN"
mpp.intel64.paint_text_inplace(img_result,
f"MIN: {result_data[0]:.3f} ({result_text})",
'simple',
False,
10, 30,
255, 0, 0,
0.7,
1
)
if item == "MAX":
if result_data[2] > spec :
result_text = "OUT"
else:
result_text = "IN"
mpp.intel64.paint_text_inplace(img_result,
f"MAX: {result_data[0]:.3f} ({result_text})",
'simple',
False,
10, 30,
255, 0, 0,
0.7,
1
)
return img_result, result_text
- 오른쪽 속성에서 입력 부분과 출력 부분을 지정해주세요



- 추가로 DM_CLASS_DCollTemplate도 카테고리를 지정하고, 코드를 넣어주세요

📜 DM_CLASS_DCollTemplate 코드 보기
def DM_CLASS_DcollTemplate(input1, input2, input3, input4, input5):
import mpp
import os
result_image = input1
resultData = input2
spec_in_class_name = input3
spec_out_class_name = input4
global_items = input5
if resultData == "IN" :
result_class = spec_in_class_name
else :
result_class = spec_out_class_name
defect_id = global_items['gs_defectid']
input_image_file_name_without_extension = mpp.daq.utilities.get_image_file_name_without_extension(global_items['gs_semimage1'])
#### Result Uri
image_file_name = f'{defect_id}_result.jpg' if input_image_file_name_without_extension is None else f'{input_image_file_name_without_extension}_result.jpg'
result_image_file_uri = os.path.join(global_items['gs_resultimageurl'].strip(), image_file_name)
summary_file_uri = os.path.join(global_items['gs_resultdataurl'].strip(), f'{defect_id}_Summary.csv')
defect_file_uri = os.path.join(global_items['gs_resultdataurl'].strip(), f'{defect_id}_Defect.csv')
dcoll_file_uri = os.path.join(global_items['gs_resultdataurl'].strip(), f'{defect_id}_DColl.csv')
#### Summary Data
summary_data = list()
summary_data.append(['WAFERID','DEFECTID','DIEX','DIEY','XREAL','YREAL','IMAGEWIDTH','IMAGEHEIGHT','ALIGNMENT','MEASUREMENT','DEFECT','EXTENSIONFUNC','RELATIVEPATH','PATH','GDSX','GDSY'])
summary_data.append([global_items['gs_waferid'],global_items['gs_defectid'],global_items['gs_xindex'],global_items['gs_yindex'],global_items['gs_xreal'],global_items['gs_yreal'],'0','0','OK','OK','OK','OK',image_file_name,result_image_file_uri,'0','0'])
#### DCOLL Data
dcoll_data = list()
dcoll_data.append(['ID', 'TYPE', 'NAME', 'VALUE', 'DESCRIPTION'])
dcoll_data.append([defect_id, 'IMG', 'PATH', result_image_file_uri, 'Result Image'])
dcoll_data.append([defect_id, 'SMF', 'DEFECTID', defect_id, 'DefectID'])
#### TODO : Change Following Items
#### MEASURE -> MESURE : Due to Typo in the SMF File.
dcoll_data.append([defect_id, 'SMF', 'TEMP5', float(result_class), 'CLASS'])
#### Save Result
mpp.intel64.save(result_image, result_image_file_uri)
mpp.intel64.save_csv(summary_data, summary_file_uri)
mpp.intel64.save_csv(summary_data, defect_file_uri)
mpp.intel64.save_csv(dcoll_data, dcoll_file_uri)
return None
- 오른쪽 속성을 값을 기입해주세요
- UserControl로 되어 있는 부분이 실제로 순서도에서 조절이 가능하도록하는 부분입니다


- "SetValue"라는 모듈을 연결하여 줍니다

- "디버그/계속(F5)"을 눌러서 실행되는지 확인합니다
ROI 2개이상으로 수정
- 현재 여기까지는 ROI가 1개일 때만 연산하는 부분입니다
- ROI가 2개 이상일 때는 코드를 수정해야 합니다
- 먼저 샘플이미지를 불러옵니다

- BuildROIs에서 ROI 박스를 여러개 그려줍니다

- "CustomFunction"에서 F9를 눌러 중단점을 설정한 후, 실행하여 에러가 없는지 확인합니다

- "paintMeasure" 코드 내에서 중단점을 설정한 후, 직접실행창으로 데이터를 확인합니다

- 예제에서는 전체 CD에서 최소값을 구하는 방법과 라인별로 CD 최소값을 구하는 방법을 진행해보겠습니다
전체라인의 최소, 최대, 평균 CD
- "distance_list"는 line별로 처음 구성되어있어서 distance_list[0], distance_list[1]... 순으로 ROI개수만큼 존재합니다
- 그리고 (1, N)배열로 구성되어 있어서 distance_list[N][0]으로 전체 CD 값을 접근할 수 있습니다

- 반복문(For)을 넣어서 각 라인별로 평균, 최소, 최대를 구해서 리스트에 저장합니다
📜 paintMeasure 코드 수정
total_min_cd_list = list()
total_max_cd_list = list()
total_avg_cd_list = list()
for distance in distance_list :
if len(distance[0]) > 0 :
min_cd_value = np.min(distance[0])
max_cd_value = np.max(distance[0])
avg_cd_value = np.mean(distance[0])
else:
min_cd_value = 0
max_cd_value = 0
avg_cd_value = 0
total_min_cd_list.append(min_cd_value)
total_max_cd_list.append(max_cd_value)
total_avg_cd_list.append(avg_cd_value)
total_min_cd_value = np.min(min_cd_value)
total_max_cd_value = np.max(max_cd_value)
total_avg_cd_value = np.mean(avg_cd_value)
result_data = [total_avg_cd_value, total_min_cd_value, total_max_cd_value]
- 측정 라인을 구하기 위해서 For loop를 넣어서 실행해봅니다
- base_point_list와 pair_point_list도 ROI개수만큼 인덱스가 존재합니다
📜 paintMeasure 코드 수정
for index in range(3):
# Paint Base Line
for point_x, point_y in base_point_list[index] :
px_x = int(point_x)
px_y = int(point_y)
if px_x < 0 or px_y < 0 : continue
img_color[px_y, px_x] = [255, 0, 0]
# Paint Pair Line
for point_x, point_y in pair_point_list[index] :
px_x = int(point_x)
px_y = int(point_y)
if px_x < 0 or px_y < 0 : continue
img_color[px_y, px_x] = [0, 255, 0]
- 실행 후 오류가 없는지 확인합니다
라인별로 CD 최소값 구하기
- 반복문(For loop)으로 ROI마다 최소 CD값을 구해서 Result Data에 대입하여 함수를 종료할 수 있도록 수정해봅니다
📜 paintMeasure 코드 수정
result_data = [total_min_cd_list[0], total_min_cd_list[1], total_min_cd_list[2]]
- 수정 후 실행해봅니다
- config_spec 부분도 수정이 필요합니다
- 사용자모듈을 편집하여 코드를 수정하고 속성을 편집해주세요

📜 config_spec 코드 수정
def config_spec_1(result_image, result_data, item, spec):
img_result = result_image.copy()
result_text = "OUT"
if item == "ROI1":
if result_data[0] > spec :
result_text = "OUT"
else:
result_text = "IN"
mpp.intel64.paint_text_inplace(img_result,
f"ROI1: {result_data[0]:.3f} ({result_text})",
'simple',
False,
10, 30,
255, 0, 0,
0.7,
1
)
if item == "ROI2":
if result_data[0] > spec :
result_text = "OUT"
else:
result_text = "IN"
mpp.intel64.paint_text_inplace(img_result,
f"ROI2: {result_data[1]:.3f} ({result_text})",
'simple',
False,
10, 30,
255, 0, 0,
0.7,
1
)
if item == "ROI3":
if result_data[0] > spec :
result_text = "OUT"
else:
result_text = "IN"
mpp.intel64.paint_text_inplace(img_result,
f"ROI3: {result_data[2]:.3f} ({result_text})",
'simple',
False,
10, 30,
255, 0, 0,
0.7,
1
)
return img_result, result_text
- 속성을 OFF하고 다음에 모듈을 교체해주세요

- 실행해보고 오류가 있는지 확인합니다