시각화/프로세싱

▶함께배우는 프로세싱 :: Kepler2012

비주얼라이즈 2015. 3. 6. 22:30



▶함께배우는 프로세싱 :: Kepler2012


이번글에서는 프로세싱을 이용하여 데이터시각화를 제작한 예인 "kepler2012"코드를 하나 하나 뜯어보고자한다.





kepler2012 시각화소개


데이터시각화 전문가 Jer Thorp이 미 항공우주국(NASA)의 케플러 프로젝트의 일환으로 제작한 것이다. 제작자는 2012년에 Kepler2012라는 이름으로 1,236개에 달하는 행성데이터를 바탕으로 인터랙티브한 시각화를 제작하였으며, 부지런(?)하게도, 이후 1,091개의 행성을 업데이트했다. 이번예제에서 다룰 것은 "kepelr2012"로, 2012년 버전이다.






제작자의 Github주소 :  http://github.com/blprnt/Kepler-Visualization


위의 url에서 전체 코드를 다운로드받자.






KeplerData


Kepler2012 프로세싱 파일을 열어보면 3가지 탭으로 구분되어있다.메인이라고 할 수 있는 Kepler2012와, Controls, Exoplanet가 그것이다.


*Exoplanet은 우리말로 "태양계의 외행성"을 뜻한다.



본격적으로 코드를 들여다보기전에, 한가지 살펴보아야할 것이 있다. 짐작했겠지만, kepler라는 이름은 수학자의 이름이며 이 코드에서도 케플러법칙이 반영된다. 깊숙히 살펴볼 필요는 없고, 기억을 더듬어보는 정도로만 살펴보도록 하자.






Kepler2012에서 사용하는 데이터


Kepler2012 에서 사용하는 데이터는 2가지이다. 



Kepler2012에서 사용하는 데이터


- KeplerData

- planets2012_2


* 두 파일 모두 CSV(쉼표로 구분된 파일형식)

* KeplerData는 2011년의 데이터이며, planets2012_2는 이름 그대로 2012년의 데이터이다.

* KeplerData는 header를 포함하고, planets2012_2는 header를 포함하지 않는다.


* 이유 : 원 저작자의 말에 따르면, 이렇게 두 가지 데이터를 따로 구분하여 사용 및 적용했던 이유는 시각화의 바탕이되는 나사의 데이터포맷이 달랐기 때문이라고 한다. 이에따라 Kepler2012에서는 다른 2가지의 데이터형식에따라 void setup에서 데이터를 로드하는 방법을 다르게 적용하고 있다.





















void getPlanets(String url, boolean is2012)


String[] pArray = loadStrings(url);


각 열을 바탕으로 각 행성을 만든다.


2012년 데이터가 맞으면 0을, 아니라면 1을 반환한다.(0과 1을 반환한 것은 시작지점을 다르게 잡기위함이다.) 다시말해 header의 존재여부에 따라서 데이터로드 시작점이 달라지게 된다.


int start = is2012 ? 0 : 1; // 2011년 데이터라면 header를 skip한다. 


2011년 데이터라면 header를 건너뛰기 위해, 파라미터로 받아온 boolean값을 활용한다.


for(int i = start; i < pArray.length; i++){

ExoPlanet p;


if(is2012){

p = new ExoPlannet().fromCSV2012(split(pArray[i],".")).init();

} else { 

p = new ExoPlanet().fromCSV(split(pArray[i].init();

}

planets.add(p);

maxSize = max(p.radius, maxSize);

minSize = min(p.radius, minSize);


if(p.KOI.equals("326.01") || p.KOI.equals("314.02")){

p.feature = true;

p.label = p.KOI;

}

}


▶if구문에서 2012년 버전 데이터와 2011년버전 데이터를 구분하여 데이터를 로드한다. 이때 불러온 데이터는 ArrayList형태의 'p'에 저장한다. 그리고 이 'p'를 ArrayLis형태인 'planets'에 추가한다.


maxSize와 minSize는 말그대로 최대/최소값을 찾기위한 방법이다.





ExoPlanet 클래스


//Constructor function

ExoPlanet(){

};






ExoPlanet fromCSV2012(String[] sa){

KOI = sa[0];

period = float(sa[1]);

radius = float(sa[2]);

axis = float(sa[3]);

temp = float(sa[4]));

return(this); // 이것자체를 리턴!

}


ExoPlanet의 데이터를 CSV형태의 데이터파일에서 불러오는 방법이다. "fromCSV2012"라는 이름에서 알 수 있듯, 이것은 2012년 데이터를 불러오는 경우에 사용되는 기능이다. 




ExoPlanet fromCSV(Stirng[] sa){

KOI = sa[0];

period = float(sa[6]);

radius = float(sa[14]);

axis = float(sa[15]);

temp = float(sa[16]);

vFlag = int(sa[18]);

return(this);

}



▶여기서 2011년버전과 2012년 버전을 나누는 것은, header유무 때문이 아니다. header유무는 앞서 정리했던 getPlanets함수에서 고려했기때문에, 여기서는 그것을 다룰 필요가 없다. 여기서 나눈 이유는 두 데이터의 구성이 다르기 때문이다.





<planet2012_2>데이터와 <KeplerData>의 모습이다. 이렇게봐서는 뭐가 어떻게 다른지 알 수가 없지만,kepler2012저작자가 친절하게도 <planets2012_2>에 주석처리를 해주어서 알 수 있다. 



▶예를들어 'period'항목의 경우 2012년 데이터의 경우 왼쪽에서 두 번째 열에 있으며, 2011년 데이터의 경우 왼쪽에서 일곱 번째 열에 위치해 있다. 이뿐 아니라 'radius'나 'axis'등 다른 열들도 일치하지 않기때문이 이렇게 2012년과 2011년 데이터를 로드하는데 다른 설정을 적용한 것이다.



ExoPlanet init(){

pixelRadius = radius * ER;

pixelAxis = axis * AU;


float periodInYears = period/365;

float periodInFrames = periodYears * YEAR;

theta = random(2 * PI);

thetaSpeed = (2 * PI);


return(this);

}


이 부분은 픽셀기반의 모션데이터와 색상, 기타 사항들을 ExoPlanet data로 초기화하는 부분이다. 여기서 변환상수가 적용된다.


▶ 세타값은 랜덤으로 설정한다.



변환상수 내용


ER : 픽셀당 지구의 반지름을 의미한다.

AU : 픽셀당 천문단위(Astronomical Unit)를 의미한다.

YEAR : 프레임당 1년을 의미한다.


▶ 변환상수에 대한 내용이다. 이 변환상수들의 값은 프로세싱 시작 후 전역변수로 설정한다.






  // 업데이트!

  void update() {

    theta += thetaSpeed;

    z += (tz - z) * 0.1;

  }


여기서 세타값에 세타스피드가 더해져, 지속적으로 세타값은 변화하게 된다. 





void render() 본격 그리기가 이루어지는 곳.




//본격적으로 화면에 그리기를 수행하는 부분

void render(){ 


float apixelAxis = pixelAxis;


if(axis > 1.06 && feature) { 

apixelAxis = (( 1.06 + ((axis - 1.06) * (1 - flatness)) * AU ) + axis * 10;

}


float x = sin(theta * ( 1- flatness)) * apixelAxis;

float y = cos(theta * (1- flatness)) * apixelAxis;


pushMatrix();  //-------------------------------------------------- pushMatrix #1

translate(x, y, z); //--------------------------------------------------translate#1


rotateZ(-rot, z); //-------------------------------------------------- rotate #1

rotateX(-rot, x);

noStroke();


if(feature){

//만약, 기능(feature)한다면, 다시 translate()를 이용하여 좌표기준을 바꿔준다.


translate(0, 0, 1); //--------------------------------------------------translate#2

stroke(255, 255);

strokeWeight(2);

noFill();

ellipse(0, 0, pixelRadius + 10, pixelRadius + 10);

strokeWeight(1);


pushMatrix(); //-------------------------------------------------- pushMatrix #1


if(label.equals("Earth")){ //만약 label의 문자가 "Earth"와 같다면 실행한다.

stroke(#01FFFD, 50);

line(0, 0, -pixelAxis * flatness, 0);

}

rotate( (1 - flatness) * PI/2 ); //-------------------------------------------------- rotate #2

stroke(255, 100);


float r = max(50, 100 + (( 1- axis) * 200) ); //참고로, axis데이터의 평균값은 0.14이다. 


r *= sqrt(1/zoom); //sqrt()는 괄호안 숫자의 제곱근을 계산하는 함수이다.

if(zoom > 0.5 || label.charAt(0) != '3'){ //**charAt()????????

line(0, 0, 0, -r);

translate(0, -r, -5); //--------------------------------------------------translate#3

rotate(-PI/2); //-------------------------------------------------- rotate #1

scale(1/zoom);

fill(255, 200);

text(label, 0, 4);

}

popMatrix(); //첫 popMatrix()이다.

}

fill(col);

noStroke();

ellipse(0, 0, pixelRadius, pixelRadius);

popMatrix(); // 두 번째 popMatrix()이다.

}

}



여기서 중요한것은 pushMatrix(), popMatrix(), translate(), ratate★()의 활용이다. 좌표계의 이동 또는 회전을 위한 함수호출은 좌표계의 바로 이전 상태에 상대적으로 반응한다. 따라서, 각 도형들이 독립적으로 움직이도록 하고 싶다면 pushmatrix(), popMatrix()기능을 활용하여 원래상태의 매트릭스를 복원해야한다. 무슨말인지 잘 이해가 안간다면 아래글에서 보다 자세한 내용을 확인하도록 하자!


함께배우는 프로세싱 :: pushMatix(), popMatrix(), translate(), rotate()의 활용 바로가기



삼차원에서 이동과 회전[각주:1]


1. 현재 변형 매트릭스를 저장한다.

2. 첫 번째 사각형을 이동시키고 회전시킨다.

3. 첫 번 째 사각형을 화면에 표시한다.

4. 2단계와 3단계의 영향을 받지 않기 위해 1단계의 매트릭스를 복원한다.

5. 두 번째 사각형을 이동하고 회전시킨다.

6. 두 번째 사각형을 화면에 표시한다.



자세한내용은


테스트1. rot.z의 구조파악

테스트2. 새로운 프로세싱파일을 생성하여, 나도 2가지 회전동시에 적용해보기









용어설명



KeplerData파일(2011년 데이터)

KOI

Dur : 통과시간, 첫 접촉부터 마지막 접촉까지 - HOURS

Depth : Transit depth at center of transit - 리듬위치를 조정(조절)

SNR,

t0, t0_unc, :  사이의 평균간격(모든 관찰되는 이동로와 선형 피팅에따라?)**[Average interval between transits based on a linear fit to all observerd transits and uncertainly - DAYS]


a/R*, a/R*_unc,

r/R*, r/R*_unc, : [10,11]


별의 반경과 불확실성의 비율


b, b_unc,

Rp, : [14] 행성의 반경 - 지구 반경 - EARTH RADII

a, : [15] 궤도기반의 반장경 - AU


* 반장경이란, 타원의 장축과 단축중 장축의 절반을 가리킨다.


Teq, : [16] 행성의 균형(평형)온도 - KELVIN

EB prob,

V, : [18] : Vetting fla

g[flag를 심사?]

1) 확정 및 발표 행성

2) 확률이 강한 후보 - 명확하게 정해진 테스트를 통과

3) 보통수준의 확률을 갖는 후보 - 명확한 테스트가 진행. 그러나 명확하게 탈락

4) 후속 테스트를 명확하게 진행할만큼의 전체제품군의 수가 부족함.


2012데이터


KOI : 케플러개체의 관심번호

Per : 이동로 사이의 평균 간격

Rad : 지구반경의 행성 반경이 6,378km[Planetary radius in Earth radii = 6478km]???

AU : 궤도 장반경

K_Teq : 행성의 균형온도

O/E_1 : 홀수의 비율에 짝수통과깊이[Ratio of odd to even numbered transit depths]??

O/E_2 : 홀수의 비율에 짝수통과깊이[Ratio of odd to even numbered transit depths]??

OCC : 소음으로 나눈 단계에서 상대적인 플럭스 수준 = 0.5???

*flux : 끊임없는 변화, 유동, 흐름

aa_dra 타겟과 비교했을대, RA안에서의 상대적인 소스위치

aa e_dar : 소스위치의 불확실성

as_ddec : 타겟과 비교하여 소스위치의 감속

_ dist : noise로 나눈 소스 위치까지의 거리

_MES : 여러 이벤트 통계







  1. 다니엘쉐프만 지음, 랜덤웍스 옮김, "프로세싱 날개를 달다", 비제이퍼블릭, 2011, p.317 [본문으로]