HU는 Hounsfield Unit(혹은 Housfield Scale)의 약자이다.

CT를 이용한 의료 영상을 렌더링할 때 사용하는 단위이다.


 

Godfrey Hounsfield (1919-2004)_*1

CT 촬영시 기준이 되는 단위이며 영국의 전기공학자 고드프리 하운스필드가 정의하였다.

 

이 단위를 통해 CT로 촬영한 영상에서 어떤 물체가 있는지 찾을 수 있다.

 

HU는 물을 0으로 기준값으로 삼는다. 범위는 약 -1000~ 3000의 값을 가지므로 12bits의 데이터로 표현할 수 있다.

 

 

 

주요 값은 아래와 같다. _*2

Substance HU
공기 (Air) -1000
물 (Water) 0
연조직 (Soft tissue on contrast CT) 100 ~ 300
뼈 (Bone) 300~400(Cancellous) or 1800 ~ 1900 (Cortical)

 

- Cancellous bone : Sponge Bone 이라고도 하며, Cortical Bone 보다 강도가 약하다.

                                     내부에 혈액을 생성하는 골수가 존재한다.

 

- Cortical bone : 일반적으로 알고 있는 뼈, , 겉이 단단한 뼈를 의미한다.

 

 

 

 


출처)

*1 : https://en.wikipedia.org/wiki/Godfrey_Hounsfield

 

Godfrey Hounsfield - Wikipedia

Sir Godfrey Newbold Hounsfield CBE FRS[1] (28 August 1919 – 12 August 2004)[2][3][4][5][6] was an English electrical engineer who shared the 1979 Nobel Prize for Physiology or Medicine with Allan McLeod Cormack for his part in developing the diagnostic tec

en.wikipedia.org

*2 : https://en.wikipedia.org/wiki/Hounsfield_scale

 

Hounsfield scale - Wikipedia

The Hounsfield scale , named after Sir Godfrey Hounsfield, is a quantitative scale for describing radiodensity. It is frequently used in CT scans, where its value is also termed CT number. Definition[edit] The Hounsfield unit (HU) scale is a linear transfo

en.wikipedia.org

 

학부 그래픽스 수업 때 배웠지만 졸면서 들었는지..

어렴풋이 "폴리곤이랑 메시는 그.. 삼각형! 볼륨은.. 두루뭉실 그거?" 라는 생각이 들어서

한번 되짚어보며 글을 남기려 한다.


1. 폴리곤(Polygon)

폴리곤 = 삼각형

 

폴리곤은 정점 혹은 버텍스(vertex)라고 불리는 점과 간선 혹은 엣지(edge)라고 불리는 선으로 이루어진 삼각형을 말한다.

모든 도형을 이루는 기본 도형이기도 해서 primitive 라고도 한다.

 

대부분의 렌더링 방식이 폴리곤들을 이어붙여 렌더링하므로 폴리곤 렌더링이라고도 한다.

 

 

2. 메시(Mesh)

 

폴리곤들로 이루어진 물체이다. 그물망처럼 이어져있다고 해서 Mesh이다..

 

 

3. 볼륨(Volume)

 

위의 2개와는 느낌이 다른 형식이다.

레이 캐스팅(ray casting)이라는 방식으로 렌더링되기도 하는데,

이 방법은 광선이 진행하며 볼륨에 있는 점들의 데이터를 보간하여 렌더링한다.

그래서 폴리곤 혹은 메시를 만들지 않고 렌더링하여 따로 볼륨 렌더링이라고 불린다.

 

간단하게 구분하면 이런 느낌?

'개념 간단 정리' 카테고리의 다른 글

혼자 헷갈려 정리하는 용어 (HU)  (0) 2019.12.30

이번에는 SIMD 프로그래밍으로 간단한 비교 연산을 해봅시다.

 

컴퓨터 공학을 전공 중이거나 프로그래밍을 해본 사람들이라면 비교 연산을 잘 알고 계실 겁니다.

그 중 AND가 뭘까요? C언어 등에서 비트연산자로(&)를 사용합니다.

두 피연산자의 비트가 같다면 1(혹은 true) 다르다면 0(혹은 false)를 리턴하죠.

 

그렇다면 인텔에서 제공하는 AND 연산은 어떻게 계산될까요

이전 글에서 소개했던 Intel Intrinsic을 찾아봅시다.

https://software.intel.com/sites/landingpage/IntrinsicsGuide/

 

Intel® Intrinsics Guide

 

software.intel.com

 

위 링크를 클릭합니다. 왼쪽 체크박스에서 SSE, SSE2를 체크하고,

검색창에 and를 검색하면 많은 함수중에 아래와 같은 함수를 찾을 수 있습니다.

 

Intel에서 제공하는 and 함수

 

 

 

"a와 b의 32비트마다 단정도(single precision) 실수 원소들마다 AND 연산을 한 결과를 저장하여 리턴한다"

__m128 자료형은 128비트의 크기를 가진다고 했었죠.

128/32 = 4, 그렇다면 위 함수는 단정도 실수인 float 원소 4개를 한번에 비교 연산할 수 있습니다.

 

 

그렇다면 또 다른 비교 연산 함수인 andnot은 뭘까요?

 

Intel에서 제공하는 andnot 함수

 

 

Operation에 적혀 있는 내용을 읽어보면 굉장히 독특한 연산임을 눈치채실 수 있습니다.

andnot 함수는 두 피연산자 중 앞의 피연산자에 NOT 연산을 하고나서 b와 AND 연산을 하는 것을 볼 수 있습니다.

andnot 함수가 어떤 결과를 리턴하는지 이해는 되셨을겁니다만..

아마 '도대체 이런 연산은 왜 만든거지? 어디다가 쓰는거야?'라는 생각을 하실 겁니다만...!

잠시 예시를 보기전에 같은지, 혹은 작거나 같은지 등의 부등호 연산을 하는 함수를 먼저 만나보겠습니다.

 

 

 

Intel에서 제공하는 cmpeq 함수

 

 

각 값마다 비교해서 같다면 0xffffffff, 다르면 0을 넣고 리턴합니다.

굳이 1이 아니라 0xffffffff를 리턴하는 이유는 무엇일까요?

AND나 OR 연산시에 모든 비트에 값을 대입하기 수월하기 때문입니다.

그렇다면 같을 때의 연산은 이렇다면 같거나 작을 때와 같은 부등호 상황은 어떻게 할까요?

cmplt (compare less than), cmpgt (compare greater than) 등의 함수가 있습니다.

 

 

 

드디어 예시코드를 한번 보겠습니다.

이런 C로 만들어진 코드가 있다고 생각해봅시다.

 

// p 배열에 from인 값을 to 값으로 변경하는 함수
void Change(float *p, float from, float to, int size) {
    for (int i = 0; i < size; i++)
        if(p[i] == from)
            p[i] = to;
}

int main() {
    float buf[4] = { 10, 20, 30, 40 };
    Change(buf, 20, 50, 4); // 20을 50으로 변경!
}

 

 

위 코드를 SIMD 프로그래밍을 적용해서 만들어야한다라면 어떻게 해야할까요?

물론 저렇게 작은 배열에 하나의 원소만 바꾼다면 직접 바꾸는 것도 나쁘지는 않을 겁니다.

 

하지만 하나의 __m128 원소값을 접근하여 바꾸는 것은 전체 연산을 하는 것보다 비교적 비용이 비쌉니다..

그때문에 묶어놓은 변수를 풀어해치지 않고 해결하려는 생각을 해야합니다.

 

저는 이런 방식으로 풀었습니다.

다른 방법들도 있겠지만(cmpneq 등) 위의 함수들을 사용해야한다면 가장 쉬운 방법이라고 생각합니다.

 

void Change(float *p, float from, float to, int size)
{
	__m128 vFrom = _mm_set_ps(from, from, from, from);
	__m128 vTo = _mm_set_ps(to, to, to, to);

	for (int i = 0; i < size; ++i)
	{
		__m128 vData = _mm_loadu_ps(p + i); // 배열 로드
		
		// from과 같은 위치 찾기
		__m128 vCmpData = _mm_cmpeq_ps(vData, vFrom); 
		
		// from과 같은 값이 아닌 곳의 값 유지
		vData = _mm_andnot_ps(vCmpData, vData); 
		
		// to가 들어가야할 위치에 to를 넣기
		__m128 vToData = _mm_and_ps(vCmpData, vTo);
		
		// 위에서 만든 to만 있는 값과
		// from과 같지 않은 유지된 값을 더해서 
		// 최종값 생성
		vData = _mm_add_ps(vData, vToData);
		
		// 배열에 저장
		_mm_storeu_ps(p + i, vData);
	}
}

 

 

for 루프 안쪽 코드를 한 줄씩 살펴보겠습니다.

 

__m128 vData = _mm_loadu_ps(p + i); // 배열 로드

 

배열 혹은 포인터에서 값을 읽어오는 함수입니다. 그냥 load 함수도 있지만 여기서는 loadu 함수를 사용했습니다.

 

 

// from과 같은 위치 찾기
__m128 vCmpData = _mm_cmpeq_ps(vData, vFrom); 

 

from 값(20)이 어느 위치에 있는지 알기 위한 코드입니다.

vData와 vFrom이 같은지 비교하면 vCmpData = (0, 0xffffffff, 0, 0)이 들어 있겠네요.(역순 고려x)

 

 

// from과 같은 값이 아닌 곳의 값 유지
vData = _mm_andnot_ps(vCmpData, vData); 

 

위에서 어느 위치에 from값(20)이 들어있는지 찾았으니 from이 아닌 부분의 값을 유지해야겠죠.

이럴 때 andnot 함수를 사용할 수 있을 겁니다.

결과 : NOT(vCmpData) AND vData = (10, 0, 30, 40)

 

 

// to가 들어가야할 위치에 to를 넣기
__m128 vToData = _mm_and_ps(vCmpData, vTo);

 

to(50)이 들어가야할 위치에만 어떻게 to값을 넣을 수 있을까요?

어느 위치인지 vCmpData 변수가 가지고 있으니 AND 함수를 이용합니다

결과 : vCmpData AND vTo = (0, 50, 0, 0)

 

 

// 위에서 만든 to만 있는 값과
// from과 같지 않은 유지된 값을 더해서 
// 최종값 생성
vData = _mm_add_ps(vData, vToData);

// 배열에 저장
_mm_storeu_ps(p + i, vData);

 

위의 2줄에서 다 구했으니 더하고 저장하기만 하면 되겠습니다.

위의 함수들로 끝나겠네요.

storeu 함수의 경우도 store 함수가 있지만 여기서는 storeu 함수를 사용했습니다.

 

 

결과를 확인해봅시다.

 

 

잘 나왔네요.ㅎㅎ

 

간단하게 and, andnot, cmp계열 함수들을 알아보았습니다.

다른 비교연산 함수들(or, xor)은 따로 다루지는 않겠습니다.

인텔 인트린식 가이드를 꼭 참고하시기 바랍니다.

 

 

 

* 추가하면 좋은 내용이나 부족한 부분은 댓글로 남겨주세요!

'SIMD' 카테고리의 다른 글

SIMD Programming 01 - 시작 및 자료형  (0) 2019.10.04

이전에 물리엔진을 만들어보겠다고 하고 성능향상에 대해서 고민하다가 발견한적이 있었습니다. 당시 자존감이 낮아서 그랬는지.. 약간의 코드를 보고 너무 어렵다고 생각하고 포기했던 기억이 있습니다. 이번에 공부할 수 있는 좋은 기회를 얻어 정리 겸 포스팅을 계획하고 있습니다.

 

기본적으로 참고해야할 레퍼런스 사이트는 이곳입니다.

https://software.intel.com/sites/landingpage/IntrinsicsGuide/

 

Intel® Intrinsics Guide

 

software.intel.com

 

1. SIMD란?

정말 많은 곳에서 SIMD의 개념을 설명합니다. 그래서 여기서는 간략하게 이야기하려고 합니다.

바로 "한가지 명령으로 많은 수의 데이터를 처리하자"(Single Instruction, Multiple Data)라는 개념입니다.

사실 이게 정의이고 설명의 끝이기도 합니다.

 

간단한 예시를 들어보겠습니다.

만약 a=10, b=20, c=30, d=40이라는 데이터가 있다고 가정해봅시다.

모든 데이터에 +10을 해주어야하는 상황이 생겼다면 일반적인 프로그래밍 방법은 이와 같을 것입니다.

 

a = a + 10;

b = b + 10;

c = c + 10;

d = d + 10;

 

모든 숫자에 직접 +10씩 해주는 방법이죠. 그렇다면 SIMD 프로그래밍에서는 어떨까요?

 

__m128 x = _mm_set_ps(a,b,c,d);

__m128 ten = _mm_set1_ps(10);

x = _mm_add_ps(x, ten);

 

맨 처음에 변수 선언부를 제외하면 _mm_add_ps()라는 함수 한번에 해결되는 것을 볼 수 있습니다.

 

이걸 보고 저는 처음에 이런 생각이 들었습니다.

"오? 한가지 명령으로 다수의 데이터를 처리한다고? 이거 여기도 써보고 저기도 써보고 좋겠다!"

알려주시는 교수님께서 따끔한 충고를 해주셨습니다.

"SIMD를 이용해서 프로그래밍할 수 있는 것은 좋은 무기가 되겠지만,

그 무기를 아무 때서나 사용하면 좋은 코드를 만들 수 없다.

먼저 자료구조와 알고리즘을 단단하게 쌓아올리고나서 SIMD를 적용할 수 있는지 확인해보아야 한다."

 

프로그램을 만들기 전에(코드를 짜기 전에) 먼저 구조와 알고리즘을 단단하게 보완해야한다는 것입니다.

굉장히 중요한 말이라서 서두에 이렇게 적습니다. 우리 모두 기억합시다!

 

 

 

본격적으로 공부를 시작하면 처음 보는 헤더와 자료형들을 볼 수 있습니다.

#include <xmmintrin.h>

int main()
{
	__m128 a = _mm_set_ps(1, 2, 3, 4);
	__m128 b = _mm_set_ps(5, 6, 7, 8);
	__m128 c = _mm_add_ps(a, b);
    
    return 0;
}

저도 처음 봤을 때 거부감부터 들었지만, 천천히 보면 그리 어려운 코드는 아닙니다.

 

#include <xmmintrin.h>

아주 기본적인 헤더입니다. 이후에는 다른 헤더파일들도 사용합니다.

(이후 발전된 헤더를 사용합니다만 여기서는 기본적인 xmmintrin.h을 썼습니다.)

 

__m128 a = _mm_set_ps(1, 2, 3, 4);

여기서 새로운 친구를 2명이나 만납니다. __m128과 _mm_set_ps()입니다.

 

 

__m128은 하나의 자료형이라고 볼 수 있습니다.

xmmintrin.h 안의 __m128을 살펴보았다.

 

와우! 그 말로만 듣던 union, 공용체를 발견할 수 있습니다.

C언어를 배우면서 보기 힘든 자료형입니다만 이런 곳에서 유용하게 사용되는 것을 볼 수 있습니다.

그리고 여기서 __m128에 float이 4개, unsigned int8이 16개 등등이 들어가는 것을 보고
우리는 __m128에서 "128"이 128 bits를 의미한다는 것을 알 수 있습니다!
(128 bits = 16 Bytes = 4(float) * 4 = 8(unsigned int8 = unsigned char) * 16)
이에 대해서는 나중에 더 알아보도록 합시다.

 

 

다음으로는 _mm_set_ps()가 있습니다. 이 녀석은 어떤 함수인지 알아보겠습니다.

xmmintrin.h에서의 _mm_set_ps() 선언

 

우리는 __m128에 float 4개를 넣어서 리턴해주는 것을 알 수 있습니다.

"그럼 밑에 _mm_setr_ps()는 뭐야?"라고 물어보실 수 있습니다.

0번이 뒤인가...

 

그냥 _mm_set_ps()를 이용하게 되면 4,3,2,1 순으로 저장되는 것을 볼 수 있습니다.

 

왜 이런 식으로 만들어졌을까요?

바로 인텔 CPU가 리틀 엔디언 방식으로 동작하기 때문입니다.

컴퓨터 구조 시간에 한번쯤 들어보았을 법한 리틀 엔디언, 빅 엔디언 이야기는 여기서 다루지 않겠습니다.

잘 정리된 링크를 추가했습니다.

https://ko.wikipedia.org/wiki/%EC%97%94%EB%94%94%EC%96%B8

 

엔디언 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 엔디언(Endianness)은 컴퓨터의 메모리와 같은 1차원의 공간에 여러 개의 연속된 대상을 배열하는 방법을 뜻하며, 바이트를 배열하는 방법을 특히 바이트 순서(Byte order)라 한다. 엔디언은 보통 큰 단위가 앞에 나오는 빅 엔디언(Big-endian)과 작은 단위가 앞에 나오는 리틀 엔디언(Little-endian)으로 나눌 수 있으며, 두 경우에 속하지 않거나 둘을 모두 지원하는 것을 미들 엔디언(Middle

ko.wikipedia.org

 

다행히도 Intel의 큰 배려(?)로 _mm_setr_ps()는 우리가 생각하는 순서인 1,2,3,4 순으로 저장할 수도 있습니다.

이후 포스팅에서는 두 함수를 필요에 따라 사용하므로 주의 깊게 살펴보며 코드를 읽어 주시기 바랍니다.

 

 

 

 

* 추가하면 좋은 내용이나 부족한 부분은 댓글로 남겨주세요!

'SIMD' 카테고리의 다른 글

SIMD Programming 02 - and, andnot, cmpep, ...  (0) 2019.10.08

기존 프로젝트를 물려받고 유니티 버전업을 하는 작업중에 있다.

유니티도 업그레이드 한 김에 라이브러리도 최신으로 바꾸려는 찰나..

Google VR이 버전업 되면서 GvrMain 프리팹이 없어졌다..

 

몹쓸 Missing 스크립트...

 

구글님께 물어본 결과.. Deprecated된지 좀 되었다고 한다.

심지어 그 다음인 GvrViewerMain도 삭제되었다.. (도대체 언제적 버전이지..)

 

해결법은 간단했다.

1. Assets/GoogleVR/Prefabs 에서 GvrEditorEmulator를 Hierarchy에 드래그한다.

2. Camera를 하나 만들고 Tag를 MainCamera로 설정한다.

3. 새로운 Camera를 드래그하여 GvrEditorEmulator의 자식으로 만든다.

요로코롬

아직 VR 기기를 연결해서 확인한건 아니지만 게임 뷰에 화면이 나온다!

돌아갈때 좀 이상하지만 기분탓이다.. 버그 언제 고치지...

 

젠장할 버그... 또 고치러 가야지ㅜ

글에 오류가 있다면 댓글로 꼭 알려주세요!

 

참고) GvrMain 시절에는 화면이 왼쪽눈 오른쪽눈 따로 잘려서 보였는데 이제는 하나로 보인다고 한다..

 

 

도움 받은 링크 : 

https://forum.unity.com/threads/upgrading-google-cardboard-vr-sdk.417878/

 

Upgrading Google Cardboard/VR SDK

I started building my Google Cardboard project in Unity a while back and have just downloaded the new Google VR SDK. Does anybody have any experience...

forum.unity.com

요놈이 최신

https://forum.unity.com/threads/gvr-1-5-released.471360/

불러오는 중입니다...

 

LibIGL + vckpg 사용시 설정 참고



https://github.com/libigl

LibIGL github 주소


필자는 vcpkg를 이용하여 필요한 library를 이용했다. (glfw, glew, eigen 등등)

libigl 같은 경우 vcpkg에 추가되지 않은 라이브러리여서 따로 추가해주어야한다.


1. Property-C/C++/Additional Include Directories 에서 libigl 파일이 있는 경로 지정

1-1. Tutorial을 따르고 있다면 tutorial_shared_path.h를 복사해서 사용한다.


2. glad.h가 필요하다. glfw 라이브러리를 직접 다운로드 받아 include하는 경우에는 상관이 없는 것 같음.

https://glad.dav1d.de/

링크에 있는 페이지에서 자신의 환경에 알맞는 설정으로 생성, 다운로드된 파일을 복사하여 이용한다.


2-1. 이 경우 glad.c 파일은 프로젝트에 추가하여 같이 빌드하여야 함



3. strdup 함수에서 오류가 있는 경우가 있음(vs2017 버전에서 오류를 발생시키는 것으로 보임)


https://github.com/cesanta/mongoose/issues/655


아래코드를 삽입시켜면 된다.(위 링크 참조)

#ifdef _MSC_VER
#define strdup(s) _strdup(s)
#endif



설정이 끝나면 아래와 같이 출력할 수 있다. (예시: tutorial105)


+ Recent posts