날짜별 글 목록: 2014년 4월 23일

[번역 : AOSA Volume 1, 24장] VTK

이 내용은 AOSA(The Architecture of Open Source Applications) Volume 1의 24장을 번역한 내용입니다. 참고로, 오역이 상당히 많으니 원문을 꼭 참고해서 보시기를 강추합니다.

VTK(시각화 툴킷)는 데이터 처리와 시각화를 위해서 폭넓게 사용되는 소프트웨어 시스템이다. VTK는 과학 연산, 의료 영상 분석, 계산 기하학, 렌더링, 영상 처리와 정보과학(informatics)에서 사용한다. 이 장에서는 성공적인 시스템을 만드는 기본 디자인 패턴의 일부를 포함하는 VTK의 간략한 개요를 제공한다.

소프트웨어 시스템을 제대로 이해하려면, 소프트웨어가 해결하는 문제가 무엇인지 이해하는 것과 더불어, 소프트웨어가 등장하는 특정 문화의 이해가 필요하다. VTK의 경우, 표면적으로는 과학적 데이터를 위한 3D 시각화 시스템으로 개발했다. 소프트웨어가 등장하는 문화적 상황은 노력에 의미 있는 뒷이야기를 추가하고, 그 당시의 소프트웨어 설계 및 배포 이유를 설명하는데 도움을 준다.

VTK를 계획하고 개발할 당시에, 초창기 저자들(Will Schroeder, Ken Martin, BillLorensen)은 GE(R&D)의 연구원이었다. GE에서는 C 언어로 구현한 스몰토크(Smalltalk)와 비슷한 환경인 LYMB로 알려진 프리커서(precursor) 시스템에 매우 많이 투자했다. 그 당시에, LYMB가 대단한 시스템이긴 했지만, 연구원으로 일을 진행하려고 시도할 때, 계속 두 가지 장벽으로 인해 좌절했다: 1) IP 문제와 2) 비표준, 특허 소프트웨어. IP 이슈는 GE 외부에 소프트웨어를 배포하려는 시도는 회사 변호사가 관여하게 되면 거의 불가능했기 때문에 문제가 되었다.

두 번째로, GE 내부에 소프트웨어를 배포해도, 많은 직원(고객)들이 숙련된 직원이 회사를 그만둘 때, 숙련의 노력이 다른 직원으로 전달되지 않기 때문에, 독점 및 비표준 시스템을 배우는 것을 꺼렸고, 표준 도구 셋의 폭넓은 지원을 받지 못했다. 따라서 결국에는 VTK의 주요 동기는 쉽게 기술을 고객에게 전수할 수 있도록 하는 개방형 표준 혹은 협력 플랫폼(collaboration platform )을 개발하는 것이었다. 따라서 VTK에 대한 오픈 소스 라이선스 선택은 아마도, 우리가 한 디자인 결정 중에 가장 중요한 것이었다.

이 라이선스(GPL이 아닌 BSD)가 궁극적으로 VTK를 기반으로 서비스와 컨설팅 기반의 비즈니스를 활성화 시켜서 결국, Kitware(각주 필요)가 생겨났기에, 돌이켜 보면, 비상호주의(non-reciprocal), 관대한 라이선스 선택은 저자의 훌륭한 결정이었다. 그 당시에는 학계, 연구실, 그리고 회사와의 협력을 위한 장애물을 감소시키는 것이 주된 관심사였다. 상호주의 라이선스(reciprocal licenses)가 큰 손해를 입을 수 있는 잠재적인 위험 때문에 많은 조직이 기피한다는 것을 발견했다.

사실, 상호주의 라이선스(reciprocal licenses)가 오픈 소스 소프트웨어의 수용을 매우 더디게 한다고 생각하지만, 그것은 다음 논쟁거리이다. 요점은 다음과 같다: 특정 소프트웨어 시스템과 연관해서 해야 하는 주요 설계 결정 중의 하나가 저작권 라이선스의 선택이다. 프로젝트 목적을 검토하고 다음에 IP 문제를 적절히 해결하는 것이 중요하다.

24.1. VTK란 무엇인가?

처음에, VTK는 과학적 데이터 시각화 시스템으로 계획했다. 다른 분야의 사람들은 단순히 기하학 렌더링(가상 물체와 이것들의 상호작용을 시험하는)의 특정 형태로 생각한다. 이것도 확실히 시각화 일부이지만, 일반적으로 데이터 시각화는 데이터를 지각적 정보(sensory input)나, 일반적인 이미지뿐 아니라 촉감, 청각 그리고 다른 형태도 포함하고 있는 전체과정을 포함한다. 데이터 형태는 메시(mesh)나 복합적 공간 분해(complex spatial decompositions)와 같은 추상적 개념을 포함하는 기하학과 위상 구조로 구성한 것과 더불어, 스칼렛(scalars, 예로, 온도나 압력), 벡터(vectors, 예로, 속도), 텐서(tensors, 예로, 응력과 변형률)와 같은 핵심 구조에 법선 속도면(surface normal)과 텍스처 좌표(texture coordinate)와 같은 렌더링 속성도 추가한다.

일반적으로 시공간(spatial-temporal) 정보를 나타내는 데이터는 과학적 시각화의 일부로 여겨지는 것에 주의해라. 그러나 마케팅 인구 통계, 웹 페이지, 문서와 비구조화된 문서, 테이블, 그래프, 트리와 같은 추상적 관계(즉, 시공간이 아닌)로 나타낼 수 있는 또 다른 정보와 같은 더 많은 추상 데이터 형태가 있다. 일반적으로 이런 추상 데이터는 정보 시각화의 방법으로 해결한다. 현재, VTK는 커뮤니티의 도움으로, 과학과 정보 시각화 둘 다에서 사용할 수 있다.

시각화 시스템으로써, VTK의 역할은 궁극적으로 이런 형태의 데이터를 가져와서 인간의 감각으로 이해할 수 있는 형태로 데이터를 변환하는 것이다. 따라서 VTK의 핵심 요구사항 중의 하나는 데이터를 받고, 처리하고, 표현하고 궁극적으로 데이터를 렌더링할 수 있는 데이터 플로우 파이프라인(data flow pipelines)을 만드는 능력이다. 그러므로 툴킷은 필연적으로 유연한 시스템으로 설계되었고, 툴킷의 설계는 이를 다양한 레벨에서 반영한다. 예를 들면, 매우 다양한 데이터를 처리할 수 있는 교체 가능한 많은 컴포넌트를 가진 툴킷을 목표로 VTK를 설계했다.

24.2. 아키텍처 특징

VTK의 아키텍처 특징으로 들어가기 전에, 시스템 개발과 사용에 큰 영향을 주는 고 수준 개념들이 있다. 이런 개념들 중의 하나가 하이브리드 래퍼(hybrid wrapper) 기능이다. 이 기능은 자동으로 VTK의 C++ 구현(추가된 언어가 있을 수도 있다)에서 파이썬, 자바와 Tcl용의 언어 바인딩을 만든다.

실력 있는 상당수의 개발자는 C++로 작업할 것이다. 사용자와 애플리케이션 개발자는 C++를 사용할 수도 있지만, 종종 위에서 언급한 인터프리터 언어를 선호했다. 이런 하이브리드 컴파일/인터프리터 환경은 상반된 두 가지의 장점을 결합한다. 고성능 연산-집약적(compute-intensive)인 알고리즘과 애플리케이션을 프로토타이핑하거나 개발할 때의 유연성. 사실, 다중-언어(multi-language) 컴퓨팅에 대한 접근은 과학 컴퓨팅 커뮤니티에서 많은 것들과 함께 인기가 있었고, 종종 자신의 소프트웨어를 개발하기 위한 템플릿으로 VTK를 사용한다.

소프트웨어 프로세스에 대해서, VTK는 빌드를 제어하기 위해 CMake; 테스트를 위해 CDash/CTest; 크로스 플랫폼 배포를 위해 CPack을 사용한다. 실제로, VTK는 보통 원시 개발 환경으로 악명이 높은 슈퍼컴퓨터를 포함해서 대부분 컴퓨터에서 컴파일할 수 있다. 게다가, 웹 페이지, 위키, 메일링 리스트(사용자와 개발자), 문서 생성 기능(예로, Doxygen)과 버그 관리툴(Mantis)을 사용해서 개발 도구를 완성하게 한다.

24.2.1. 핵심 특징

VTK가 객체 지향 시스템이기에, 클래스와 객체 데이터 멤버의 접근을 조심해서 다루고 있다. 일반적으로, 모든 데이터 멤버는 protected이거나 private이다. 데이터에 대한 접근은 부울(Boolean) 데이터, 모달(modal) 데이터, 문자열과 벡터를 위한 특정 변수로, Set과 Get 메서드로 접근한다. 실제로 이 메서드 대부분은 클래스 헤더 파일에 매크로를 추가해서 만든다. 그래서, 예를 들면:

vtkSetMacro(Tolerance, double);
vtkGetMacro(Tolerance, double);

상세히 하면 아래가 된다:

virtual void SetTolerance(double);
virtual double GetTolerance();

단순히 코드의 명확함 이상으로, 이런 매크로를 사용하는 많은 이유가 있다. VTK에는 디버깅을 제어하고, 객체의 수정 시간(MTime)을 갱신하고, 적절히 참조 계수를 관리하는 중요한 데이터 멤버가 있다. 이 매크로들은 중요한 데이터를 정확하게 조작하고, 매크로의 사용을 강력하게 추천한다. 예로, VTK에서 객체의 수정 시간이 적절히 관리되지 않을 때, 특히 치명적인 버그가 생긴다. 이 경우의 코드는 수행해야 할 때 수행하지 않거나 너무 자주 수행할 수도 있다.

VTK의 강점 중의 하나가 데이터 표현과 관리 수단이 비교적 단순하다는 것이다. 일반적으로, 특정 타입의 다양한 데이터 배열(예로, vtkFloatArray)은 인접한 정보의 부분을 나타내는 데 사용한다. 예로, 세 개의 XYZ 포인트 리스트는 아홉 개 엔트리(x,y,z, x,y,z, 등)의 vtkFloatArray로 표현할 수 있다. 이 배열에는 튜플의 개념이 있어서, 3D 포인트는 3-튜플이고, 반면 대칭 3×3 텐서 매트릭스는 6-튜플로 나타낸다(대칭 공간 저장이 가능한).

이 설계는 과학 연산에서 배열을 조작하는 시스템(예로, 포트란)과 인터페이스로 연결하는 것이 일반적이기 때문에 의도적으로 적용되었고, 이는 크고 인접한 청크(chunk)에서 메모리를 할당하고 해제하는 데 있어 더 효율적이다.

게다가, 일반적으로 통신, 직렬화와 IO 수행은 인접한 데이터를 이용하면 훨씬 더 효율적이다. 이 핵심 데이터 배열(다양한 타입의)은 VTK에서 많은 데이터를 표현하고, 빠른 접근을 위한 메서드와 데이터를 추가할 때, 필요에 따라 메모리를 자동으로 할당하는 메서드를 포함해서 정보에 접근하고 입력하기 위한 여러 가지 편리한 메서드를 가지고 있다. 데이터 배열은 vtkDataArray 추상 클래스의 서브 클래스이고 이는 제네릭 가상 메서드(generic virtual methods)가 코딩을 단순화하는 데 사용될 수 있다는 것을 의미한다.

그러나 더 좋은 성능을 위해서 인접 데이터 배열을 연속적이고 직접 접근해서 타입 기반의 스위치(switch)를 사용하는 정적(static) 템플릿 함수를 사용한다.

일반적으로 C++ 템플릿은 성능의 이유로 널리 사용되지만, public 클래스 API에서는 볼 수 없다. 이것은 STL에도 해당된다: 일반적으로 사용자나 애플리케이션 개발자에게 템플릿 구현의 복잡함을 감추기 위해 PIMPL 디자인 패턴을 적용한다. 이는 이전에 설명한데로 래핑한 코드가 해석도니 코드가 될 때 특히 도움이 되었다. Public API에서 템플릿의 복잡성을 줄이는 것은, 애플리케이션 개발자 관점에서, VTK 구현이 데이터 타입 선택의 복잡성에서 거의 벗어난다는 것을 의미한다. 물론 후드(hood) 아래에서 코드 실행은 일반적으로 데이터에 접근할 때 런타임에 결정되는 데이터 타입으로 이루어진다.

일부 사용자는 VTK가 메모리 관리를 위해, 가비지 콜렉션과 같이 사용자에게 더 친숙한 방식을 사용하지 않고, 참조 계수를 사용하는 이유를 궁금해한다. 기본적인 대답은 데이터 크기가 거대할 수 있기에, 데이터를 삭제할 때 VTK의 완벽한 제어가 필요하다는 것이다. 예로, 바이트(byte) 데이터 1000×1000×1000의 볼륨 크기는 기가바이트(gigabyte) 크기이다. 가비지 콜렉터가 이 데이터를 해제할 건지 아닐지를 결정하는 동안 이 데이터를 남겨두는 것은 좋은 생각이 아니다. VTK에서 대부분 클래스(vtkObject의 서브클래스)는 참조 계수를 위한 내장 기능이 있다.

모든 객체는 객체가 만들어질 때 ‘1’로 초기화된 참조 계수를 포함한다. 매번 객체의 사용을 등록하고, 참조 계수는 하나씩 증가한다. 비슷하게, 객체의 사용을 등록하지 않을 때(또는 같은 객체가 삭제될 때) 참조 계수는 하나씩 감소한다. 결국, 객체의 참조 계수는 객체 자체가 소멸하는 것을 가리키는 ‘0’으로 감소한다. 일반적인 예제는 다음과 같다.

vtkCamera *camera = vtkCamera::New();   //reference count is 1
camera->Register(this);                 //reference count is 2
camera->Unregister(this);               //reference count is 1
renderer->SetActiveCamera(camera);      //reference count is 2
renderer->Delete();                     //ref count is 1 when renderer is deleted
camera->Delete();                       //camera self destructs

VTK에서 참조 계수가 중요한 또 다른 이유가 있다—참조 계수가 효율적으로 데이터를 복사하는 기능을 제공한다. 예를 들어, 점, 다각형, 색, 스칼렛(scalars)과 텍스처 좌표(texture coordinates)와 같은 많은 데이터 배열로 이루어진 데이터 객체 D1을 생각해보자. 이제 처음 데이터 배열에 벡터 데이터(점의 위치)를 추가한 새로운 데이터 객체 D2를 생성하기 위해 데이터 배열을 처리하는 것을 생각해 보자. 한 가지 비 효율적인 방식으로 D1을 복사(deep copy)해서 D2를 만들고, D2에 새로운 벡터 데이터 배열을 추가한다는 것이다.

그 대신에, 빈 D2를 만들고 다음에 데이터의 소유를 관리하기 위해 참조 계수를 사용해서, D1에서 D2로 배열을 복사(shallow copy)하고, 마지막으로 새로운 벡터 배열을 D2에 추가한다. 후자의 방식은 앞에서 살펴본 것처럼, 좋은 시각화 시스템에 필요한 데이터 복사를 방지한다. 이 장의 뒷부분을 보면 알겠지만, 데이터 처리 파이프라인은 알고리즘의 입력값에서 출력으로 데이터를 복사하는 것과 같은 동작을 반복해서 수행하기 때문에, 참조 계수는 VTK에 필수이다.

물론, 참조 계수도 잘 알려진 몇 가지 문제점이 있다. 때때로, 상호 지원하는 환경에서 서로 참조하는 객체(순환 참조하는)가 존재할 수 있다. 이 경우, 지능적인 중재가 필요하고, VTK의 경우에는 vtkGarbageCollector에서 주기에 연관된 객체를 관리하는 특별한 기능을 구현했다.

순환 클래스가 확인될 때(이것은 개발하는 동안 예상했다), 클래스는 가비지 콜렉터에 자신을 등록하고, 자신의 Register, UnRegister 메서드를 오버로드한다. 다음의 객체 삭제(또는 등록해제) 메서드는 서로 참조하는 객체의 분리된 객체(detached islands)를 찾기 위해 로컬 참조 계수 네트워크에서 위상 분석(topological analysis)을 수행한다. 그 다음에 가비지 콜렉터가 이것을 삭제한다.

VTK 대부분의 객체 초기화는 정적 클래스 멤버로 구현한 객체 팩토리(object factory)를 사용한다. 일반적인 문법은 다음과 같다:

vtkLight *a = vtkLight::New();

여기에서 알아야 하는 중요한 것은, 실제로 객체를 초기화하는 것이 vtkLight 가 아닐 수 있고, vtkLight의 서브클래스일 수 있다(즉, vtkOpenGLLight). 객체 팩토리를 사용하는 다양한 동기가 있지만, 가장 중요한 것은 애플리케이션의 이식성과 장치의 독립성이다.

예를 들면, 위에서 렌더링 된 화면에 조명을 만들고 있다. 특정 플랫폼의 애플리케이션에서, vtkLight::New는 결과로 OpenGL 라이트가 될 수 있지만, 다른 플랫폼에서 그래픽 시스템에서 라이트를 만들기 위해서 다른 렌더링 라이브러리나 메서드가 있을 가능성이 있다. 정확하게 파생된 클래스를 객체화하는 것은 런타임 시스템 정보의 기능이다. VTK 초기에는 gl, PHIGS, Starbase, XGL와 OpenGL을 포함해서 매우 많은 선택 사항이 있었다. 대부분의 이런 것들이 사라졌지만, DirectX와 GPU를 기반으로 한 접근 방법을 포함해서 새로운 접근 방식이 나타나고 있다.

시간이 지나면서, 개발자가 vtkLight 에 새로운 장치의 특정 서브 클래스와 진화중인 기술을 지원하기 위한 다른 렌더링 클래스의 구현으로 VTK로 개발한 애플리케이션을 변경할 필요가 없다. 객체 팩토리의 또 다른 중요한 사용은 성능-향상(performance-enhanced) 변화를 런타임에 교체할 수 있다는 것이다. 예로, vtkImageFFT 는 특수용 하드웨어나 수치 라이브러리에 접근하는 클래스로 교체될 수 있다.

24.2.2. 데이터 표현

VTK의 강점 중의 하나가 복잡한 데이터 형태를 표현하는 기능이다. 복잡한 데이터 형태는 단순한 테이블에서부터 유한요소 메쉬(finite element meshes)와 같은 복잡한 구조를 포함한다. 이런 데이터 형태는 전부 그림 24.1에서 보는 것과 같이 vtkDataObject의 서브 클래스이다(그림 24.1은 많은 데이터 객체 클래스의 부분적인 상속 도표임을 주의해라).

그림 24.1 데이터 객체 클래스

vtkDataObject의 가장 중요한 특징 중의 하나는 데이터 객체가 시각화 파이프라인에서 처리될 수 있다는 것이다(다음 절). 그림 24.2에서 볼 수 있는 많은 클래스에서, 대부분의 애플리케이션에서 사용하는 것은 일부이다. vtkDataSet과 파생된 클래스는 과학 시각화를 위해 사용된다(그림 24.2). 예를 들면, vtkPolyData는 다각형의 메쉬를 나타내는 데 사용하고, vtkUnstructuredGrid는 메쉬를 나타내고, vtkImageData는 2D와 3D 픽셀 그리고 3D 화소(voxel) 데이터를 나타낸다.

그림 24.2 데이터 셋 클래스

24.2.3. 파이프라인 아키텍처

VTK은 여러 개의 주요 서브시스템으로 이루어진다. 아마도 시각화 패키지와 연관된 대부분 서브시스템은 데이터 플로우/파이프라인 아키텍처이다. 개념적으로, 파이프라인 아키텍처는 객체의 세 가지 기본 클래스로 이루어진다: 데이터를 나타내는 객체(위에서 살펴본 vtkDataObject), 하나의 형태에서 다른 형태로 데이터 객체의 처리, 변환, 필터 또는 연결(map)하는 객체(vtkAlgorithm) 그리고 인터리브드(interleaved) 데이터의 연결 그래프를 제어하고 객체를 처리하는, 파이프라인을 실행하기 위한 객체(vtkExecutive). 그림 24.3은 일반적인 파이프라인을 묘사한다.

그림 24.3 일반적인 파이프라인

개념적으로는 단순하지만, 실제로 파이프라인 아키텍처를 구현하는 것은 어렵다. 한 가지 이유가 데이터의 표현이 복잡할 수 있다는 것이다. 예로, 일부 데이터셋(dataset)은 계층 구조나 데이터 그룹으로 이뤄져서, 데이터 전체를 실행하는 것은 적지 않은 반복이나 재귀가 필요하다. 문제를 절충하기 위해, 병렬 처리(공유 메모리 또는 확장할 수 있는, 분산 방식을 사용할지)는 데이터를 조각으로 분리하는 것을 필요로 하고, 파생된 것과 같은 지속적으로 바운드리 정보를 연산하기 위해 조각들이 중첩될 필요가 있을 수 있다.

또한, 알고리즘 객체는 자신의 특수한 복잡성을 소개한다. 어떤 알고리즘은 다중 입력을 받고/또는 다른 타입으로 다중 출력을 만들 수도 있다. 막대 그래프를 계산하는 예에서, 일부는 근처에 데이터로 동작할(예로, 셀의 중심을 계산하는) 수 있지만, 다른 것은 전역 정보가 필요하다. 이런 모든 경우에, 알고리즘이 입력값들을 불변으로 다루어서 출력값을 만들기 위해서 단순히 입력값을 읽기만 한다. 이것은 데이터가 다중 알고리즘에 입력으로 사용할 수 있기 때문이고, 하나의 알고리즘이 다른 것의 입력을 무시하는 것은 좋은 생각이 아니다.

마지막으로, 관리(the executive)는 실행 전략에 따라 복잡해질 수 있다. 일부의 경우, 필터 사이에 중간 결과를 캐시 하도록 원할 수도 있다. 이것은 파이프라인에 있는 어떤 것이 변한다면 수행될 재연산의 양을 최소화한다. 반면에, 연산을 위해 더는 필요하지 않은 데이터를 해제하기 원하기 경우에, 시각화 데이터셋은 거대할 수 있다. 마지막으로, 파이프라인이 반복적인 방식으로 동작하는 것이 필요한, 데이터의 다중 해상도 처리와 같은 복잡한 실행 전략이 있다.

이런 개념의 일부 예를 보여주고 파이프라인 아키텍처를 더 설명하기 위해, 다음의 C++ 예제를 살펴보자.

vtkPExodusIIReader *reader = vtkPExodusIIReader::New();
reader->SetFileName("exampleFile.exo");
vtkContourFilter *cont = vtkContourFilter::New();
cont->SetInputConnection(reader->GetOutputPort());
cont->SetNumberOfContours(1);
cont->SetValue(0, 200);
vtkQuadricDecimation *deci = vtkQuadricDecimation::New();
deci->SetInputConnection(cont->GetOutputPort());
deci->SetTargetReduction( 0.75 );
vtkXMLPolyDataWriter *writer = vtkXMLPolyDataWriter::New();
writer->SetInputConnection(deci->GetOuputPort());
writer->SetFileName("outputFile.vtp");
writer->Write();

이 예에서, reader 객체는 크고 비구조화된 그리드(또는 메쉬) 데이터 파일을 읽는다. 다음 필터는 메쉬에서 등위면(isosurfac) 을 생성한다. vtkQuadricDecimation 필터는 약화시켜서, 다각형 데이터셋인 등위면의 크기를 줄인다(즉, 등위를 나타내는 삼각형의 개수를 줄이는 것). 마지막으로 감소시킨 후, 감소한 새로운 데이터 파일을 다시 디스크에 저장한다. 실제 파이프라인 실행은 writer가 Write() 메서드를 호출해서 발생한다(즉, 데이터에 대한 요구 시).

이 예제에서 설명한 것처럼, VTK의 파이프라인 실행 방법은 요구 주도(demand driven)이다. writer나 mapper(데이터 렌더링 객체)와 같은 싱크(sink)가 데이터가 필요할 때, 입력을 요청한다. 이미 입력 필터가 적절한 데이터를 가지고 있다면, 단순히 싱크에 실행 제어를 반환한다. 그러나 입력이 적절한 데이터를 가지고 있지 않다면, 그것을 계산할 필요가 있다. 따라서 데이터에 대한 입력을 처음에 요청해야 한다. 이 처리는 필터나 소스가 “적절한 데이터”를 가지거나 파이프라인이 시작에 도달할 때까지 업스트림이 파이프라인을 따라 계속될 것이다. 그리고 필터가 가리키는 곳은 정확한 순서로 실행할 것이고, 요청된 파이프라인이 가리키는 장소로 데이터가 흐를 것이다.

여기서 “적절한 데이터” 의미를 확장해야 한다. 기본적으로, VTK 소스나 필터를 실행한 후, 그 출력은 미래에 불필요한 실행을 피하려고 파이프라인으로 캐시한다. 이것은 메모리의 비용에서 연산(계산)과 I/O를 최소화하기 위한 것이고, 설정할 수 있는 행동이다. 파이프라인은 데이터 객체와 더불어 이런 데이터 객체가 만들어진 조건에 대한 메타데이터도 캐시한다. 이 메타데이터는 데이터 객체가 언제 연산 됐는지를 수집하는 타임스탬프(즉, 연산시간)를 포함한다.

그래서 가장 간단한 경우, “적절한 데이터”는 모든 업스트림 파이프라인 객체가 수정된 후 연산된 것이다. 다음의 예제를 고려해서, 이 행동을 예로 드는 것이 더 쉽다. 이전 VTK 프로그램의 끝에 다음을 추가해 보자:

vtkXMLPolyDataWriter *writer2 = vtkXMLPolyDataWriter::New();
writer2->SetInputConnection(deci->GetOuputPort());
writer2->SetFileName("outputFile2.vtp");
writer2->Write();

앞에서 설명한 것처럼, 처음 writer->Write 호출은 전체 파이프라인의 실행을 유발한다. writer2->Write()를 호출할 때, 파이프라인은 감소(decimation) 필터, 윤곽(contour) 필터와 reader의 수정된 시간이 있는 캐시의 타임스탬프를 비교할 때, 감소 필터의 캐시 결과가 최신인지 알게 될 것이다. 따라서 데이터 요청은 이전 writer2를 전파할 필요가 없다. 이제, 다음 변화를 생각해보자.

cont->SetValue(0, 400);

vtkXMLPolyDataWriter *writer2 = vtkXMLPolyDataWriter::New();
writer2->SetInputConnection(deci->GetOuputPort());
writer2->SetFileName("outputFile2.vtp");
writer2->Write();

이제 파이프라인 관리는 윤곽과 감소 필터가 마지막으로 실행한 후에 윤곽 필터가 수정된 것을 알게 될 것이다. 따라서 이 두 개의 필터에 대한 캐시가 오래되었고, 두 필터는 다시 실행해야 한다. 그러나 reader는 윤곽 필터 이전에 수정되지 않아서, reader의 캐시가 유효하고 그러므로 reader는 다시 실행할 필요가 없다.

여기에 설명한 시나리오는 요구-주도(demand-driven) 파이프라인의 가장 간단한 예제이다. VTK의 파이프라인은 훨씬 더 정교하다. 필터나 싱크가 데이터가 필요할 때, 특정 데이터 서브셋을 요청하기 위해 부가적인 정보를 제공할 수 있다. 예를 들면, 필터는 데이터 스트리밍 조각으로 핵심에서 벗어난 분석을 수행할 수 있다. 예제를 설명하기 위해 이전 예제를 변경해 보자.

vtkXMLPolyDataWriter *writer = vtkXMLPolyDataWriter::New();
writer->SetInputConnection(deci->GetOuputPort());
writer->SetNumberOfPieces(2);
writer->SetWritePiece(0);
writer->SetFileName("outputFile0.vtp");
writer->Write();
writer->SetWritePiece(1);
writer->SetFileName("outputFile1.vtp");
writer->Write();

위의 writer는 독립적으로 스트림된 두 개의 조각에 데이터를 로드하고 처리하기 위해 업스트림 파이프라인을 요청한다. 이전에 설명한 간단한 실행 로직이 여기서는 동작하지 않으리라는 것을 알고 있을 것이다. 이 로직에 따라, 두 번째에 Write 함수를 호출한 때, 업스트림이 변경되지 않았기 때문에 파이프라인은 다시 실행할 필요가 없다.

따라서 더 복잡한 경우를 해결하기 위해, 관리는 이와 같은 조각 요청을 처리하기 위해 부가적인 로직을 가지고 있다. VTK의 파이프라인 실행은 실제로 다중 단계로 이루어진다. 실제로 데이터 객체의 연산은 가장 마지막 단계이다. 그전의 단계는 요청 단계이다. 이것은 싱크와 필터가 곧 있을 연산에서 원하는 것을 업스트림에 알릴 수 있다. 위의 예제에서, writer는 2개 중에 0번의 조각을 원하는 것을 업스트림의 입력으로 알릴 것이다. 이 요청은 실제로 reader에 완전히 전파할 것이다.

파이프라인이 실행할 때, reader는 데이터의 서브셋을 읽을 필요가 있다는 것을 알 것이다. 게다가, 캐시한 데이터에 해당하는 조각에 대한 정보는 객체를 위해 메타데이터에 저장된다. 다음번에 필터는 그것의 입력으로부터 데이터를 요청하고, 이 메타데이터는 현재 요청과 비교될 것이다. 따라서 이 예제에서 파이프라인은 다른 조각 요청을 처리하기 위해 다시 실행할 것이다.

필터가 만들 수 있는 여러 가지 더 많은 요청의 종류가 있다. 이 요청은 특정 시간 단계, 특정 구조화의 확장 또는 많은 유령 계층(즉, 이웃 정보를 연산하기 위한 바운드리 계층)에 대한 요청을 포함한다. 게다가, 요청 단계 중에, 각 필터는 다운스트림에서 요청을 수정하도록 한다. 예를 들어, 스트림(즉, 스트림라인 필터)할 수 없는 필터는 조각 요청을 무시하고 전체 데이터를 요청할 수 있다.

24.2.4. 서브시스템 렌더링

언뜻 보기에, VTK는 3D 화면을 만드는 컴포넌트에 해당하는 클래스로 간단한 객체-지향 렌더링 모델을 가지고 있다. 예를 들어, vtkActor는 vtkCamera, 아마 vtkRenderWindow에 있는 다중 vtkRenderer 들과 협력하는 vtkRenderer가 렌더링 하는 객체이다. 화면은 하나 이상의 vtkLight에서 빛을 받는다. 각 vtkActor의 위치는 vtkTransform이 제어하고, 액터(actor)의 모습은 vtkProperty로 명시한다.

마지막으로, 액터의 기하학적인 표현은 vtkMapper가 정의한다. 매퍼(Mapper)는 렌더링 시스템의 인터페이스와 더불어 데이터 처리 파이프라인 종료를 제공하는 중요한 역할을 한다. 데이터를 감소시키고 파일에 결과를 저장하고, 다음에 매퍼를 사용해서 결과를 시각화하거나 상호작용하는 예제를 생각해 보자:

vtkOBJReader *reader = vtkOBJReader::New();
reader->SetFileName("exampleFile.obj");
vtkTriangleFilter *tri = vtkTriangleFilter::New();
tri->SetInputConnection(reader->GetOutputPort());
vtkQuadricDecimation *deci = vtkQuadricDecimation::New();
deci->SetInputConnection(tri->GetOutputPort());
deci->SetTargetReduction( 0.75 );
vtkPolyDataMapper *mapper = vtkPolyDataMapper::New();
mapper->SetInputConnection(deci->GetOutputPort());
vtkActor *actor = vtkActor::New();
actor->SetMapper(mapper);
vtkRenderer *renderer = vtkRenderer::New();
renderer->AddActor(actor);
vtkRenderWindow *renWin = vtkRenderWindow::New();
renWin->AddRenderer(renderer);
vtkRenderWindowInteractor *interactor = vtkRenderWindowInteractor::New();
interactor->SetRenderWindow(renWin);
renWin->Render();

여기 하나의 액터, 렌더러 그리고 윈도우 렌더는, 렌더링 시스템에 파이프라인을 연결하는 하나의 매퍼를 추가해서 생성한다. 또한, 마우스와 키보드 이벤트를 캡처하고 캡처한 데이터를 카메라 조작이나 다른 액션으로 변환하는 인스턴스인 vtkRenderWindowInteractor의 추가를 주의해라. 이 변환 과정은 vtkInteractorStyle을 통해서 정의한다(아래에서 더 살펴본다).

기본적으로, 많은 인스턴스와 데이터 값은 씬 내부에서 설정된다. 예를 들면, 단일 기본 (헤드)라이트와 속성뿐 아니라 단위 변환이 만들어 진다.

시간이 지남에 따라 이 객체 모델은 더 정교화되고 있다. 상당한 복잡도가 렌더링 처리의 측면에서 구체화하는 파생된 클래스를 개발하는 것에서 비롯된다. 현재 vtkActor는 vtkProp(무대에 발견하는 소품과 같은)의 구체화이고, 2D 오버레이 그래픽과 텍스트 렌더링, 구체화한 3D 객체 렌더링, 그리고 심지어 볼륨 렌더링(volume rendering)과 같은 고급 렌더링 기술 또는 GPU 구현을 지원하기 위한 이런 소품들이 아주 많이 있다(그림 24.4를 보라).

비슷하게도, VTK가 지원하는 데이터 모델이 증가하는 것과 마찬가지로, 데이터를 렌더링 시스템에 인터페이스하는 다양한 매퍼가 있다. 확장의 중요한 또 다른 영역은 변환 계층 구조이다. 원래 간단한 선형 4X4 변환 매트릭스가 TPS(thin-plate spline) 변환을 포함해서 비선형 변환을 지원하는 강력한 계층 구조가 되었다. 예로, vtkPolyDataMapper는 디바이스에 종속적인 서브클래스를 가진다(예로, vtkOpenGLPolyDataMapper). 최근에, 그림 24.4에서 보이는 “페인터(painter)” 파이프라인이라는 정교화된 그래픽 파이프라인으로 교체됐다.

TPS : 평면상의 점들에서 주어지는 데이터값을 보간하는 곡면을 찾는 방법, http://en.wikipedia.org/wiki/Thin_plate_spline

그림 24.4 디스플레이 클래스

페인터 설계는 특수한 렌더링 효과를 제공하기 위해 결합할 수 있는 렌더링 데이터를 위한 다양한 기술을 지원한다. 이 기능은 1994년에 처음으로 구현한 간단한 vtkPolyDataMapper를 크게 능가한다.

시각화 시스템의 또 다른 중요한 측면은 서브시스템의 선택이다. VTK에는 픽 기능(pick operation)후에 다양한 수준의 정보를 제공하는 객체뿐 아니라, 대략 소프트웨어 메서드(예로, 레이캐스팅 )에 비해 하드웨어 기반 메서드에 기반을 둔 vtkProp을 선택하는 객체로 분류하는 “픽커(pickers)” 계층 구조가 있다;

예를 들면, 일부 픽커는 단지 선택된 vtkProp의 지정 없이 XYZ 공간에서 위치를 제공한다; 다른 픽커는 선택된 vtkProp 뿐 아니라 프롭 기하학에서 정의한 메쉬를 구성하는 특정 포인트나 셀을 제공한다.

24.2.5. 이벤트와 상호작용

데이터와 상호작용하는 것은 시각화의 필수 부분이다. VTK에서 이것은 다양한 방법으로 발생한다. 가장 간단한 단계에서, 사용자는 이벤트를 관찰할 수 있고, 커맨드를 통해 적절하게 응답할 수 있다(커맨드/옵저버 디자인 패턴). vtkObject의 모든 서브 클래스는 객체로 자신을 등록한 옵저버 목록을 유지한다. 등록하는 동안, 이벤트가 발생하는 경우 옵저버는, 호출되는 연관 커맨드를 추가해서 흥미있는 특정 이벤트를 지정한다.

이것이 어떻게 동작하는 보기 위해, 세 가지 StartEvent, ProgressEvent와 EndEvent 이벤트를 관찰하는 옵저버를 가지고 있는 필터(다각형 감소 필터)에서 다음의 예제를 생각해보자. 이런 이벤트는 필터가 실행을 시작하고, 주기적으로 실행하는 내내, 다음에 실행을 완료한 때에 호출된다. 다음의 vtkCommand 클래스는 알고리즘을 실행하는 데 걸리는 시간에 관련된 적당한 정보를 출력하는 Execute 메서드를 가지고 있다.

classvtkProgressCommand : public vtkCommand
{public:
staticvtkProgressCommand *New() { return new vtkProgressCommand; }
virtual void Execute(vtkObject *caller, unsigned long, void *callData)
    {
double progress = *(static_cast<double*>(callData));
std::cout<< "Progress at "<< progress<<std::endl;     } }; vtkCommand* pobserver = vtkProgressCommand::New(); vtkDecimatePro *deci = vtkDecimatePro::New(); deci->SetInputConnection( byu->GetOutputPort() );
deci->SetTargetReduction( 0.75 );
deci->AddObserver( vtkCommand::ProgressEvent, pobserver );

이것은 상호작용의 기본적인 형태이지만, VTK를 사용하는 많은 애플리케이션에 기본적인 요소이다. 예를 들면, 위의 간단한 코드는 GUI 프로그레스 바를 보여주고 관리하기 위해서 쉽게 바꿀 수 있다. 또한, 이 커맨드/옵저버 서브시스템은 질의, 조작 그리고 데이터 편집을 위해 정교화한 상호 작용 객체인 3D 위젯의 핵심으로 다음에서 설명한다.

위 예제를 참조하면, VTK에서 미리 정의한 이벤트를 주의하는 것이 중요하지만, 사용자 정의 이벤트를 위한 백도어가 있다. vtkCommand 클래스는 미리 정의된 이벤트(즉, 위 예에서 vtkCommand::ProgressEvent)와 더불어 사용자 이벤트도 정의한다. 일반적으로, UserEvent는 간단한 필수적인 값이고, 이 값은 애플리케이션 사용자 정의 이벤트 셋의 오프셋(offset)의 시작값으로 사용된다. 그래서, 예들 들면, vtkCommand::UserEvent+100은 VTK가 정의한 이벤트 셋 이외에 특정 이벤트를 참조할 수도 있다.

사용자 관점에서, VTK 위젯은 사용자가 핸들 조작이나 다른 기하학적인 기능으로 처리할 수 있는 것을 제외하면 씬에서 액터로 나타난다(핸들 조작과 기하학적 특징 처리는 앞에서 설명한 픽킹 기능을 기반으로 한다). 이 위젯과의 상호 작용은 꽤 직관적이다: 사용자는 구 모양의 핸들을 잡고 그것을 움직이거나, 선을 잡고 그것을 움직인다. 그러나 씬의 뒤에서는, 이벤트가 발생하고 적절히 개발된 애플리케이션은 이 이벤트를 관찰할 수 있고, 다음에 적절한 액션을 한다. 예를 들면, 이벤트들은 다음의 vtkCommand::InteractionEvent을 종종 실행시킨다.

vtkLW2Callback *myCallback = vtkLW2Callback::New();
myCallback->PolyData = seeds;    // streamlines seed points, updated on interaction
myCallback->Actor = streamline;  // streamline actor, made visible on interaction
vtkLineWidget2 *lineWidget = vtkLineWidget2::New();
lineWidget->SetInteractor(iren);
lineWidget->SetRepresentation(rep);
lineWidget->AddObserver(vtkCommand::InteractionEvent,myCallback);

실제로 VTK 위젯은 vtkInteractorObserver의 서브클래스와 vtkProp의 서브 클래스, 두 개의 객체를 사용해서 만든다. vtkInteractorObserver는 단순하게 랜더 윈도우(즉, 마우스와 키보드 이벤트)에서 사용자 상호 작용을 관찰하고 그것을 처리한다. vtkProp의 서브클래스(즉, 액터)는 vtkInteractorObserver가 간단하게 조작한다. 일반적으로 그런 조작은 하이라이트 처리, 커서 모습 변화, 그리고 데이터 변환을 포함해서 vtkProp의 기하학을 수정하도록 구성하고 있다. 물론, 특정 위젯은 위젯 행동의 뉘앙스를 제어하기 위해 서브클래스가 필요하고, 현재 시스템에는 50개 이상의 다른 위젯이 있다.

24.2.6. 라이브러리 요약

VTK는 큰 소프트웨어 툴킷이다. 최근에 시스템은 대략 150만 라인의 코드(주석은 포함하지만, 자동으로 만들어낸 래퍼 소프트웨어를 포함하지 않는다)와 대략 1,000개의 C++ 클래스로 이뤄져 있다. 시스템의 복잡성을 관리하고, 빌드 및 링크하는 시간을 줄이기 위해 시스템은 수십 개의 서브 디렉터리로 분할되었다. 표 24.1은 라이브러리가 제공하는 기능에 대한 간단한 요약 설명과 함께, 서브 디렉터리의 목록이다.

Common 핵심 VTK 클래스
Filtering 파이프라인 데이터플로우를 관리하는 데 사용하는 클래스
Rendering 렌더링, 픽킹, 이미지 뷰와 상호 작용
VolumeRendering 볼륨 렌더링 기술
Graphics 3D 기하학 처리
GenericFiltering 비선형 3D 기하학 처리
Imaging 이미지 파이프라인
Hybrid 그래픽과 이미징의 두 기능이 필요한 클래스
Widgets 정교한 상호 작용
IO VTK 입력과 출력
Infovis 정보 시각화
Parallel 병렬 처리(제어기와 통신기)
Wrapping Tcl, 파이썬 그리고 자바 래핑 지원
Examples 확장된, 잘 문서화된 예제
표 24.1 VTK 서브 디렉토리

24.3. 회고와 기대

VTK는 대단히 성공한 시스템이다. 1993년 처음 개발하기 시작했지만, VTK를 개발하는 내내 계속 강해지고 있고, 오히려 개발 속도는 증가하고 있다. 이 장에서는 몇 가지 교훈과 향후 도전에 대해서 살펴본다.

24.3.1. 성장 관리

VTK의 경험에서 가장 놀라운 면 중의 하나가 프로젝트가 오래 지속된다는 것이다. 개발 속도는 몇 개의 주요 요인에 달려있다.

• 새로운 알고리즘과 기능이 계속해서 추가된다. 예를 들어, 정보학 서브시스템은 최근에 중요하게 추가된 것이다(Titan, Sandia National Labs와 Kitware에서 주로 개발했다). 새로운 과학 데이터셋 타입을 위한 기능뿐 아니라, 추가적인 도표와 렌더링 클래스 또한 추가되었다. 또 다른 중요한 추가로 3D 상호 작용 위젯이 있다. 마지막으로, GPU 기반 렌더링의 지속적인 발전과 데이터 처리가 VTK에 새로운 기능을 만들어 내고 있다.
• VTK의 알려짐과 사용의 증가는 심지어 더 많은 사용자와 개발자를 커뮤니티에 추가하는 지속적인 프로세스이다. 예를 들어, ParaView는 VTK를 기반으로 한 가장 인기 있는 과학용 시각화 애플리케이션이고 고성능 컴퓨팅 커뮤니티에서 높이 평가하고 있다. 3D 슬라이서(3D Slicer)는 주로 VTK를 기반으로 하는 주요 생체 의학 컴퓨팅 플랫폼이고, 매년 수백만 달러의 재정 지원을 받는다.
• VTK의 개발 프로세스는 계속 발전하고 있다. 최근에, 소프트웨어 프로세스 도구인 CMake, CDash, CTest 그리고 CPack이 VTK 빌드 환경으로 통합되었다. 더 최근에는, VTK 코드 저장소를 Git로 이전했고, 작업 흐름이 더 정교해졌다. 이런 개선은 VTK가 과학용 컴퓨팅 커뮤니티에서 소프트웨어 개발의 선두에 있다는 것을 확신하게 한다.

성장은 흥미롭지만, 소프트웨어 시스템의 제품을 검증하고, VTK의 미래를 잘 예언하고, 제대로 관리하는 것은 매우 어려울 수 있다. 결과적으로, VTK의 가까운 시기의 미래는 소프트웨어와 더불어, 커뮤니티의 성장을 관리하는데 더 초점을 맞춘다. 이것과 관련해서 여러 단계를 취했다.

첫 번째, 공식화한 관리 구조를 만들었다. 아키텍처 리뷰 보드(Architecture Review Board)는 고수준에 주력하고, 전략적인 이슈, 커뮤니티와 기술의 개발을 도와주기 위해 만들었다. 또한, VTK 커뮤니티는 특정 VTK 서브시스템의 기술적 개발을 도와주기 위해 인정받은 토픽 리드(Topic Lead)팀을 만들었다.

다음으로, 일반적으로 사용자와 개발자가 툴킷의 작은 서브시스템으로 작업하기를 원하고 전체 패키지에 대해 빌드와 링크를 원하지 않다는 것을 알고 있을 뿐 아니라, git가 소개하는 워크플로우 기능에 부분적으로 대응하도록, 툴킷을 더 모듈화할 계획이 있다. 게다가, 성장하는 커뮤니티를 지원하기 위해, 툴킷의 핵심 부분이 아니더라도, 새로운 기능의 기여와 서브 시스템의 지원은 중요하다. 느슨하고, 모듈화된 모듈의 컬렉션을 만들어서, 핵심 안정성을 유지하면서 주변에 상당히 많은 기여를 수용하는 것이 가능하다.

24.3.2. 기술 혁신

소프트웨어 프로세스 외에, 파이프라인 개발에 많은 기술적인 혁신이 있다.

• 공동 처리(Co-processing)는 시각화 엔진을 시뮬레이션 코드에 통합하는 기능이고, 주기적으로 시각화를 위해 추출한 데이터를 만든다. 이 기술은 많은 양의 완전한 솔루션 데이터 출력의 필요성을 감소시킨다.
• VTK에서 데이터 처리 파이프라인은 여전히 너무 복잡하다. 서브시스템을 단순화하고 리팩토링이 진행중이다.
• 데이터와 직접 상호 작용하는 기능이 사용자에게 점점 인기를 얻고 있다. VTK에 많은 위젯 셋이 있지만, 터치 스크린 기반과 3D 메서드를 포함하는 더 많은 상호 작용 기술이 나타나고 있다. 상호 작용은 빠른 속도로 개발이 계속될 것이다.
• 컴퓨터 화학은 재료 디자이너와 엔지니어에게 중요도가 증가하고 있다. 화학 데이터를 시각화하고 상호 작용하는 기능이 추가되고 있다.
• VTK의 렌더링 시스템은 너무 복잡해서, 새로운 클래스를 확장하거나 새로운 렌더링 기술을 지원하는 것을 어렵게 만든다고 비판받고 있다. 게다가, 많은 사용자가 요구하는 어떤 것에 대해, 화면 그래프의 개념을 직접 지원하지 않는다.
• 마지막으로 새로운 형태의 데이터가 계속 나타나고 있다. 예를 들어, 의학 분야에서 다양한 해상도의 계층적인 볼륨 데이터셋(예로, 다중초점 형광현미경으로 일부 확대).

24.3.3. 오픈 과학

마지막으로, 키트웨어(Kitware)와 더 일반적인 VTK 커뮤니티가 오픈 과학에 헌신하고 있다. 실용적으로, 이것은 오픈 데이터, 오픈 발행, 그리고 오픈 소스를 널리 알리는 방법이다(재현할 수 있는 과학적 시스템을 만드는 것을 확인하는 필수 기능). 오픈 소스 그리고 오픈 데이터 시스템으로 VTK는 오랫동안 배포되었지만, 문서화는 부족하다.

좋은 책[Kit10,SML06]이 있지만, 새로운 소스 코드 기여를 포함해서 기술적인 발행을 수집하는 다양한 특별한 방법이 있다. 문서, 소스 코드, 데이터 그리고 유효한 테스트 이미지로 구성한 글이 가능한 VTK Journal과 같은 새로운 발행 방법을 개발해서 상황이 개선되고 있다. 또한, 저널은 제출된 글에 대한 사람의 리뷰와 더불어 자동화된 코드 리뷰가 가능하다(VTK의 질적인 소프트웨어 테스팅 프로세스를 사용해서).

24.3.4. 교훈

VTK가 성공적이지만, 좋지 않은 것들도 많다:

• 모듈화 설계: 클래스의 모듈화는 좋은 선택이었다. 예를 들어, 픽셀당 객체를 만드는 것과 같은 어리석은 것을 하지 않고, 후드 아래에서 픽셀 데이터의 데이터 배열을 다루는 고 수준의 vtkImageClass를 만들었다. 그러나 일부 경우에 vtkImageClass가 너무 고 수준이고 복잡했다. 많은 인스턴스를 더 작게 리팩토링 해야 하고, 이 리팩토링은 계속되고 있다. 하나의 주요한 예제는 데이터 처리 파이프라인이다. 처음에, 파이프라인은 데이터와 알고리즘 객체의 상호작용으로 암시적으로 구현했다. 결국에, 데이터와 알고리즘 사이의 상호작용을 조정하고, 다른 데이터 프로세싱 전략을 구현하기 위해 명시적인 파이프라인 실행 객체를 만들어야 한다는 것을 알게 되었다.
• 중요한 개념 실수: 일단 가장 큰 후회는 C++ 이터레이터(iterator)를 널리 사용하지 않았다는 것이다. VTK에서 데이터 순회의 많은 경우 과학 프로그래밍 언어 포트란과 비슷하다. 이터레이터의 추가적인 유연성은 시스템에 중요한 장점이 됐을 것이다. 예를 들어, 이터레이터는 데이터의 로컬 지역 또는 일부 반복 기준에 맞는 데이터만 처리하는데 매우 유리하다.
• 설계 문제: 물론 최상의 안이 아닐 수 있지만, 설계 결정의 오랜 목록이 있다. 더 좋은 설계를 만들 때마다, 여러 세대에 이어서 데이터 실행 파이프라인과 싸워왔다. 렌더링 시스템은 너무 복잡하고 그것에서 확장하는 것은 어렵다. 또 다른 도전은 VTK의 초기 개념에서 발생한다: 데이터를 보기 위한 시각화 시스템이 읽기 전용이라는 것을 알았다. 그러나 요즘 고객은 주요하게 다른 데이터 구조가 필요한 데이터를 편집할 수 있기를 자주 원한다.

VTK와 같은 오픈 소스 시스템에 대한 좋은 것 중의 하나가 실수의 많은 것들이 고쳐질 것이라는데 있다. 우리의 활동적이고, 유능한 개발 커뮤니티는 매일 시스템을 개선하고 가까운 미래에도 계속되기를 기대한다.

[번역 : AOSA Volume 1, 23장] 비스트레일(VisTrails)

이 내용은 AOSA(The Architecture of Open Source Applications) Volume 1의 23장을 번역한 내용입니다. 참고로, 오역이 상당히 많으니 원문을 꼭 참고해서 보시기를 강추합니다.

비스트레일(VisTrails)은 데이터 탐색(exploration)과 시각화를 지원하는 오픈 소스 시스템이다. 비스트레일은 과학 워크플로우 및 시각화 시스템의 유용한 기능을 포함하고 있고, 많이 확장하고 있다. 케플러(Kepler, https://kepler-project.org)나 타베르나(Taverna, http://www.taverna.org.uk)와 같은 과학 워크플로우 시스템과 마찬가지로, 비스트레일도 일련의 규칙에 따라 기존 애플리케이션, 느슨하게 결합(loosely-coupled)한 리소스 그리고 라이브러리를 통합하는 전산 처리(computational processes)을 가능하게 한다. AVS와 ParaView와 같은 시각화 시스템과 마찬가지로, 데이터를 다양한 시각적 표현으로 탐색하고 비교하게 해서, 사용자가 이용할 수 있는 진보된 과학과 정보 시각화 기술을 지원한다. 결과적으로, 사용자는 데이터 수집과 조작에서 복잡한 분석과 시각화까지, 과학 발견(scientific discovery)의 중요한 단계를 포함하는 복잡한 워크플로우를 만들 수 있고, 전부 한 시스템으로 통합했다.

비스트레일의 특징은 그 자체가 이력(provenance) 인프라라는 것이다. 비스트레일은 탐색(exploratory) 작업 중에 따라오는 단계와 만들어진 데이터의 자세한 기록을 수집(capture)하고 유지한다. 전통적으로, 워크플로우는 반복적인 작업을 자동화하는 데 사용했지만, 현실적으로 애플리케이션에서의 작업은 탐색으로, 데이터 분석과 시각화와 같이, 거의 반복되지 않는다—기준이 바뀌고 있다. 사용자가 자신의 데이터에 대한 가설을 만들고 입증하기 위해, 다양하지만 연관 워크플로우를 반복적으로 조정하면서 만들어 간다.

비스트레일은 빠르게 변하는 워크플로우를 관리하기 위해 설계했다: 제품(예로, visualizations, plots)이 만들어 낸 워크플로우 및 실행의 이력을 유지한다. 또한, 어노테이션(annotation) 기능을 제공해서, 사용자는 자동으로 수집한(automatically-captured) 이력의 질을 높일 수 있다.

게다가, 결과를 재현할 수 있는 비스트레일은 사용자에게 협력적 데이터 분석을 도와주는 일련의 기능과 직관적인 유저 인터페이스로 이력 정보를 강화한다. 특히, 비스트레일은 임시 결과를 저장해서 반영적 유추(reflective reasoning)를 지원하고, 사용자에게 결과를 이끌어내거나 실행취소(backward)와 재실행(forward) 이유를 연속으로 따라오게 하는 동작을 검토하게 한다. 사용자는 직관적으로 워크플로우 버전을 탐색할 수 있고, 실행취소(undo)는 결과에 손실 없이 변하고, 여러 워크플로우를 시각적으로 비교하고 그 결과를 시각화한 스프레드시트에 나란히 보여준다.

비스트레일은 워크플로우 및 시각화 시스템의 폭넓은 채택을 방해하던 중요한 사용성 문제를 해결한다. 더 폭넓은 사용자에게 제공하기 위해, 프로그래밍 지식이 없는 사람도 포함해서, 일련의 기능, 워크플로우 설계 및 사용을 간단화한 유저 인터페이스를 제공한다[FSC 06]. 그리고 유추해서 워크플로우를 만들고 개선하는 능력을 포함해서(예로, 워크플로우 질의하기), 사용자가 추천 시스템[SVK+07]을 사용해서 상호작용해서 워크플로우를 만들어, 워크플로우 완성을 제안하는 것도 포함한다. 또한, 최종 사용자(비전문가)에게 배포할 수 있는 사용자 정의 애플리케이션을 쉽게 만들 수 있는 새로운 프레임워크를 개발했다.

비스트레일의 확장성은 새로운 기능을 빠르게 프로토타이핑하는 것에 더불어, 툴과 라이브러리 통합을 단순하게 하는 인프라의 결과이다. 이것은 환경 과학, 정신 의학, 천문학, 우주론(학), 고에너지 물리학, 양자 물리학과 분자 모델링 등을 포함한 광범위한 애플리케이션 영역에서, 비스트레일을 사용할 수 있게 도와준다.

비스트레일을 오픈소스 및 무료로 유지하기 위해, 오직 무료와 오픈소스 패키지만을 사용해서 빌드했다. 비스트레일은 파이썬으로 개발했고 GUI 툴킷(PyQt 파이썬 바인딩으로)으로 Ot를 사용한다. 사용자와 애플리케이션이 광범위하므로, 이식성(portability)을 염두에 두고 처음부터 다시 시스템을 설계했다. 비스트레일은 윈도, 맥 그리고 리눅스에서 동작한다.

그림 23.1:비스트레일 UI의 컴포넌트

23.1. 시스템 개요

원래, 데이터 탐색은 사용자가 연관 데이터를 알아내고, 데이터를 통합하고 시각화하고, 다양한 솔루션을 탐색하는 동안 동료와 협력하기 위해서 그리고 결과를 배포하는 데 필요한 창조적인 과정이다. 데이터의 크기와 분석의 복잡성을 고려하면, 과학적 탐색이 일반적이고, 더 나은 창조성을 지원하는 툴이 필요했다.

이런 툴을 위해서 두 가지의 기본 조건이 있고, 이것은 서로 관련이 있다. 첫째, 이상적이고 실행할 수 있는 정규 표현(formal descriptions)을 사용해서 탐색 과정을 지정할 수 있는 것이 중요하다. 둘째, 이런 프로세스의 결과와 더불어, 문제를 해결하기 위해 따라오는 다양한 단계에 대한 유추를 재현하기 위해, 이런 툴은 체계적으로 이력을 수집하는 기능이 있어야 한다. 비스트레일은 이런 조건을 염두에 두고 설계했다.

23.1.1. 워크플로우와 워크플로우 기반 시스템

워크플로우 시스템은 여러 툴과 결합한 파이프라인(워크플로우) 생성을 지원한다. 이 시스템들은 반복적인 작업과 결과 재현의 자동화를 가능하게 한다. 워크플로우는 상업용(예로, 애플 맥 OS X의 Automator 와 야후! Pipes)과 학술용(예로, NiPype, 케플러(Kepler)와 타베르나(Taverna))을 포함해서, 몇 개의 워크플로우 기반 애플리케이션이 입증했듯이, 다양한 작업에서 원시 셸 스크립트를 빠르게 바꿔나가고 있다.

워크플로우는 고 수준 언어로 개발한 스크립트나 프로그램과 비교해서 몇 가지 장점을 가지고 있다. 연속적인 작업의 경우, 한 작업의 출력이 다른 작업의 입력으로 연결해 이어지는 단순한 프로그래밍 모델을 제공한다. 그림 23.1은 기상 관측 데이터를 포함하고 있고 데이터의 산포도(scatter plot)를 생성한 CSV 파일을 읽는 워크플로우를 보여준다.

이 단순한 프로그래밍 모델은 프로그래밍 지식이 없는 많은 사용자에게 더 적합하고, 직관적인 비주얼 프로그래밍 인터페이스를 제공하기 위한 워크플로우 시스템을 가능하게 한다. 또한, 워크플로우는 명시적인 구조로 되어 있다: 노드는 매개변수와 같이 프로세스(또는 모듈)를 나타내고, 가장자리는 프로세스 사이의 데이터 흐름을 수집하는 것을 그래프로 볼 수 있다. 그림 23.1의 예에서, 모듈 CSVReader는 매개 변수로 파일이름 (/weather/temp_precip.dat)을 받고, 파일을 읽고, 내용을 차례대로 기온과 강우량 값의 산포도(scatter plot)를 만드는 matplotlib 기능에 보내는 GetTemperature와 GetPrecipitation 모듈에 보낸다.

대부분의 워크플로우 시스템은 특정 애플리케이션 영역을 위해 설계했다. 예로, 타베르나(Taverna)는 생물 정보학(bioinformatics) 워크플로우가 대상이고, NiPype는 뇌 영상(neuroimaging) 워크플로우를 만들게 한다. 비스트레일은 다른 워크플로우 시스템이 제공하는 대부분 기능을 지원하는 동시에, 여러 툴, 라이브러리 및 서비스 통합으로, 다양한 분야의 영역에서 일반적인 탐색 작업을 지원하게 설계했다.

23.1.2. 데이터와 워크플로우 이력

결과(와 데이터 제품)에 대한 이력 정보 유지의 중요성은 과학계에서 잘 인지하고 있다. 데이터 제품의 이력(또한, 감사 추적, 혈통 그리고 족보라고 알려짐)은 데이터 제품이 만들어낸 프로세스와 데이터에 대한 정보를 포함한다. 이력은 데이터의 질과 저작권을 결정하고 결과의 재현과 더불어 검증하기 위해, 데이터를 보존하는 핵심으로 중요한 문서를 제공한다[FKSS08].

이력의 중요한 컴포넌트는 인과관계에 대한 정보이다. 예를 들면, 데이터 제품을 만드는 입력 데이터, 매개변수와 함께 프로세스(단계 순서대로)의 설명이 있다. 따라서 이력 구조는 특정 결과 셋(result set)을 얻는 데 사용한 워크플로우(또는 워크플로우 셋)의 구조를 반영한다.

사실, 과학계에서 워크플로우 시스템의 광범위한 사용을 위한 촉매는 자동으로 수집한 이력을 쉽게 사용할 수 있다는 것이었다. 초기 워크플로우 시스템은 이력을 수집하기 위해 확장(extends)했지만, 비스트레일은 이력을 지원(support)하도록 설계했다.

그림 23.2: 어노테이션으로 향상된 탐색 이력

23.1.3. 유저 인터페이스와 기본 기능

시스템의 다양한 유저 인터페이스 컴포넌트는 그림 23.1과 그림 23.2에서 볼 수 있다. 사용자는 워크플로우 편집기로 워크플로우를 만들고 편집한다. 워크플로우 그래프를 빌드하기 위해, 사용자는 모듈 레지스트리(Module Registry)에서 모듈을 드래그해서 워크플로우 편집기 캔버스에 드랍할 수 있다. 모듈을 선택하면, 사용자가 모듈의 값을 설정하고 변경할 수 있는 매개 변수(매개변수 편집 부분에서)를 보여준다.

다시 정의한 워크플로우 스펙에 따라, 시스템은 아래에서 설명하고 있는 버전 트리 뷰(Version Tree View)에서 변경을 수집해서 사용자에게 이력을 제공한다. 사용자는 비스트레일 스프레드시트에서 워크플로우 그리고 결과와 상호 작용할 수 있다. 스프레드시트의 각 셀은 워크플로우 인스턴스(instance)에 해당하는 뷰를 나타낸다. 그림 23.1에서, 워크플로우 편집기에 보이는 워크플로우의 결과는 스프레드시트의 왼쪽 위 셀에 표시된다. 사용자는 직접 스프레드시트에서 다른 셀에서 매개별수를 동기화할 수 있는 것에 더불어, 워크플로우의 매개변수를 수정할 수 있다.

버전 트리 뷰는 사용자가 서로 다른 워크플로우 버전을 탐색하게 도와준다. 그림 23.2에서 보듯이, 버전 트리에서 노드를 클릭하면, 사용자는 워크플로우, 워크플로우와 연관된 결과(미리보기 화면) 그리고 메타데이터를 볼 수 있다. 메타데이터 일부는 자동으로 수집된다. 예를 들면, 특정 워크플로우를 생성한 사용자의 아이디와 생성 날짜가 있지만, 또한, 사용자가 워크플로우 및 쓰여진 설명을 식별하기 위한 태그를 포함해서 부가적인 메타데이터를 제공할 수 있다.

그림 23.3: 비스트레일 아키텍처

23.2. 프로젝트 연혁

비스트레일 초기 버전은 자바와 C++로 개발했다[BCC+05]. C + + 버전은 시스템을 위해 우리의 요구사항을 갖추는 데 중요한 역할인 피드백을 하는 몇몇 얼리 어댑터에게 배포했다.

여러 과학 커뮤니티에서 파이썬 기반 라이브러리와 툴 숫자가 증가 추세여서, 기본으로 파이썬을 사용하기로 선택했다. 파이썬은 빠르게 과학 소프트웨어를 위해서 범용의 현대적으로 잘 어울리는 언어가 돼가고 있다. 포트란(Fortran), C 그리고 C++과 같은 다른 언어로 개발한 많은 라이브러리는 스크립트 기능을 제공하는 방법으로 파이썬 바인딩을 사용한다. 비스트레일이 워크플로우에서 쉽게 다양한 소프트웨어 라이브러리의 통합을 목표로 하기에, 순수 파이썬 구현이 더 쉬워진다. 특별히, 파이썬은 LISP 환경에서 볼 수 있던 것과 비슷하게 동적 코드 로딩 기능이 있고, 동시에 훨씬 큰 개발자 커뮤니티와 매우 풍부한 표준 라이브러리를 가지고 있다. 2005년 후반, 파이썬/PyQt/Qt를 사용해서 현재 시스템의 개발을 시작했다. 이 선택은 특히 시스템 확장, 새로운 모듈과 패키지의 추가를 단순화했다.

비스트레일의 첫 베타 버전은 2007년 1월에 출시됐다. 이후, 25,000번 넘게 다운로드가 됐다.

23.3. 비스트레일 내부

그림 23.3에서 보듯이, 위에서 설명한 유저 인터페이스 기능을 지원하는 내부 컴포넌트는 고수준 아키텍처로 설명하고 있다. 워크플로우 실행은 기능 호출과 해당 매개변수를 추적하고 워크플로우 실행(실행 이력)의 이력을 수집하는 실행 엔진(Execution Engine)이 제어한다. 실행 일부로, 또한 메모리와 디스크 모두에 중간 결과를 캐시할 수 있다. 23.3장에서 살펴보듯이, 단지 모듈과 매개변수의 새로운 조합으로 다시 실행하고, 이것은 라이브러리(matplotlib)에서 적합한 함수를 호출해서 실행한다. 이력에 연결한 워크플로우 결과는, 전자 문서(23.4장)에 포함될 수 있다.

워크플로우 변경에 대한 정보는 로컬 디렉터리에 저장한 XML 파일과 관계형 데이터베이스를 포함한, 다양한 스토리지 백엔드를 사용해서 영속시킬 수 있는 버전 트리에서 수집한다. 또한, 비스트레일은 사용자가 이력 정보를 검색할 수 있게, 질의 엔진을 제공한다.

비록 비스트레일을 상호작용 툴(interactive tool)로 설계했지만, 서버 모드로 사용할 수 있다는 것을 주목한다. 워크플로우가 만들어지면, 비스트레일 서버가 워크플로우를 실행할 수 있다. 이 기능은 사용자에게 워크플로우와 상호작용하고 고성능 컴퓨팅 환경에서 워크플로우를 실행할 수 있게 하는 웹 기반 인터페이스 생성을 포함해서, 여러 시나리오에서 유용하다.

23.3.1. 버전 트리: 변경 기반 이력

그림 23.3: 비스트레일 아키텍처

비스트레일에서 도입한 새로운 개념은 워크플로우 변화[FSC+06] 이력의 개념이다. 단지 데이터 제품이 만들어낸 이력만 유지하는 이전 워크플로우와 워크플로우 기반 시각화 시스템과 대조해서, 비스트레일은 데이터 항목과 수집한 이력을 값 형식의 클래스(first-class)로 워크플로우를 다룬다. 워크플로우 변화 이력의 이용 가능성은 반성적 유추(reflective reasoning)를 지원한다. 사용자는 일부 결과의 손실 없이 여러 유추를 탐색할 수 있고, 시스템이 중간 결과를 저장하기에, 사용자는 저장된 정보로 이유와 추론을 할 수 있다. 또한, 탐색 프로세스를 단순화한 일련의 동작을 가능하게 한다. 예로, 사용자는 특정 작업을 위해 만든 워크플로우 공간을 탐색(navigate)하고, 워크플로우와 그 결과(그림 23.4 참조)를 시각적으로 비교하고 (큰)매개변수 공간의 탐색을 쉽게 할 수 있다. 게다가, 사용자는 이력 정보를 질의하고 예제로 배울 수 있다.

워크플로우 변경은 변경 기반 이력 모델을 사용해서 수집한다. 그림 23.4에서 보듯이, 데이터베이스 트랜잭션 로그와 비슷하게, 워크플로우(예로, 모듈 추가, 매개변수 수정 등)에 적용한 동작과 변화를 저장한다. 이 정보는 트리로 모델화하고, 트리의 각 노드는 워크플로우 버전에 해당하고, 부모와 자식 노드 사이의 엣지는 자식 노드를 얻기 위해 부모 노드에 적용한 변화를 나타낸다. 이 트리를 언급하기 위해 버전 트리(version tree)와 비스트레일(vistrail, visual trail의 약자)용어를 같은 의미로 사용한다. 변경 기반 모델이 일률적으로 매개변수 값과 워크플로우 정의 변경 모두를 수집한다는 것에 주의해라. 이 변화의 순서는 데이터 제품의 이력을 알아내기에 충분하고, 또한 시간이 흐르면서 어떻게 워크플로우가 변해가는지에 대한 정보를 수집한다. 모델은 단순하고 간편하다—워크플로우의 여러 버전을 저장하는 안보다 매우 적은 공간을 사용한다.

변경 기반 이력 모델은 많은 이점을 가지고 있다. 그림 23.4는 비스트레일이 두 워크플로우를 비교하기 위해 제공하는 시각적 차이 기능을 보여준다. 워크플로우는 그래프로 나타내지만, 변경 기반 모델을 사용해서, 두 개의 워크플로우 비교하는 것은 매우 간단하다: 그것은 버전 트리를 탐색하고 한 워크플로우에서 다른 워크플로우로 변환하는 데 필요한 일련의 동작을 식별하는데 충분하다.

버전 트리의 기본으로, 변경 기반 이력 모델의 또 다른 중요한 이점은 협업을 지원하는 방법으로 동작할 수 있다. 워크플로우 설계는 매우 어려운 작업이기에, 종종 여러 사용자의 협업이 필요하다. 버전 트리는 다양한 사용자(예로, 해당 워크플로우를 만든 사용자에 따라 노드 색칠하기)의 기여를 시각화하는 직관적인 방법을 제공하는 것과 더불어, 모델의 단조성(monotonicity)은 여러 사용자가 수행한 변화의 동기화를 위한 간단한 알고리즘을 가능하게 한다.

워크플로우가 실행하는 동안, 이력 정보를 쉽게 수집할 수 있다. 실행을 완료하면, 또한 데이터 제품과 그 이력의 밀접한 연결을 유지하는 것이 중요하다. 예를 들면, 데이터 제품을 만들어내는 데 사용한 워크플로우, 매개변수 그리고 입력 파일이 있다. 데이터 파일이나 이력을 이동하거나 수정할 때, 이력과 연관된 데이터나 데이터와 연관된 이력을 찾기가 어려울 수 있다. 비스트레일은 입력, 중간 결과 그리고 출력 데이터 파일을 관리하는 영속적인 저장 방법을 제공해서, 이력과 데이터 사이의 연결을 강화한다. 이 방법은 이력 정보가 데이터의 출처를 보장해서 쉽게(정확하게) 찾을 수 있기 때문에, 더 나은 재현성을 제공한다. 이런 관리의 또 다른 중요한 이점은 다른 사용자와 공유할 수 있는 중간 데이터의 캐시를 가능하게 한다는 것이다.

23.3.2. 워크플로우 실행과 캐싱

비스트레일 실행 엔진은 신규 및 기존 툴과 라이브러리의 통합이 가능하게 설계했다. 과학적 시각화와 전산 소프트웨어(computation software) 써드파티(third-party) 래핑에 사용하는 다양한 스타일에 대응하려고 노력했다. 특히, 비스트레일은 셸에서 실행하고, 입/출력으로 파일을 사용하는 프리컴파일(pre-compiled)된 바이너리로 존재하거나 입/출력으로 내부 객체를 전달하는 C++/자바/파이썬 클래스 라이브러리 중의 하나로 존재하는 애플리케이션 라이브러리와 함께 통합할 수 있다.

비스트레일은 각 모듈이 연산을 수행하고 모듈이 만들어낸 데이터는 모듈끼리의 연결로 전달하는 데이터플로우(dataflow) 실행 모델을 적용하고 있다. 모듈은 상향식(bottom-up)으로 실행한다; 각 입력은 반복적으로(recursively) 실행하는 업스트림(upstream) 모듈의 요청으로 만들어진다(A에서 B로 가는 순서의 연결이 있을 때, 모듈 A는 B의 업스트림이라고 한다). 중간 데이터는 메모리(파이썬 객체로)나 디스크(데이터에 연결하는 정보를 가지고 있는 파이썬 객체로 래핑해서)에 임시로 저장한다.

사용자가 비스트레일에 자신만의 기능을 추가할 수 있도록, 확장할 수 있는 패키지 시스템(23.3장을 참조)을 구축했다. 패키지는 사용자가 비스트레일 워크플로우에 자신 또는 써드파티 모듈을 포함할 수 있게 한다. 패키지 개발자는 일련의 연산 모듈(computational modules)을 확인해야 하고, 각 모듈은 입력과 출력 포트 확인과 더불어 연산을 정의한다. 기존 라이브러리에서, 연산 메소드는 기존 기능에 대한 입력 포트를 매개변수로 바꾸고, 결과 값을 출력 포트로 매핑하도록 명시하는 것이 필요하다.

탐색 작업에서, 공통의 하부구조(sub-structures)를 공유하는 비슷한 워크플로우는, 종종 밀접하게 연속으로 실행한다. 워크플로우 실행 효율을 개선하기 위해, 비스트레일은 재연산을 더하기 위해 중간값을 캐시 한다. 이전 실행 결과를 재사용하기 때문에, 암묵적으로 캐시할 수 있는 모듈이 정상 동작한다고 가정한다: 같은 입력이 주어지면, 모듈은 같은 결과를 만들 것이다. 이 요구사항은 분명히 클래스 동작을 제약하지만, 합리적이다.

그러나 캐시가 쓸모없는 명백한 상황이 있다. 예를 들면, 파일을 원격 서버에 업로드 하거나 디스크에 저장하는 모듈은 결과가 상대적으로 중요하지 않으며, 상당한 부작용이 있다. 그 밖의 모듈은 무작위로 사용할 수 있고, 선택하지 않는 것(non-determinism)이 바람직할 수 있다: 이런 모듈은 캐시 비활성화 플래그를 설정할 수 있다. 그러나 일부 모듈에서 동작이 바뀔 수 있다는 것은 자연스럽지 않다: 두 개의 파일에 데이터를 기록하는 기능은 파일의 내용을 출력으로 래핑할 수도 있다.

23.3.3. 데이터 직렬화와 저장

이력을 지원하는 시스템의 핵심 컴포넌트 중의 하나가 데이터의 직렬화와 저장이다. 원래 비스트레일은 내부 객체(예, 버전 트리, 각 모듈)가 포함하고 있는 단순한 fromXML 과 toXML 메서드로 데이터를 XML로 저장했다. 내부 객체의 스키마 변화를 지원하기 위해, 이 기능은 또한 스키마 버전 사이의 변화를 인코드했다. 프로젝트가 진행됨에 따라, 사용자 기반은 성장했고, 관계형 저장을 포함해서, 다양한 직렬화를 지원하기로 했다. 게다가, 스키마 객체가 변화함에 따라, 스키마 버전 관리, 버전 간의 변환, 그리고 엔티티 관계 지원과 같은 공통 데이터 관리와 관련해서 더 좋은 인프라를 유지하는 것이 필요했다. 그렇게 하려고, 새로운 데이터베이스(DB) 계층(layer)을 추가했다.

디비 계층은 3가지 핵심 컴포넌트로 구성되어 있다: 도메인 객체, 서비스 로직 그리고 퍼시스턴스(persistence) 메서드. 도메인과 퍼시스턴스 컴포넌트는 버전을 관리해서, 각 스키마 버전은 버전 별 클래스 셋을 가지고 있다. 이 방법으로, 각 스키마 버전을 읽는 코드를 유지한다. 또한, 클래스는 한 스키마 버전에서 다른 버전으로 객체에 대한 변화를 정의한다. 서비스 클래스는 인터페이스를 위한 메서드를 제공한다. 서비스 클래스는 데이터와 인터페이스하는 메서드를 제공하고, 스키마 버전의 감지와 변환을 처리한다.

코드 작성의 상당 부분은 지루하고 반복적이기에, 객체 레이아웃(임의 메모리 인덱스)과 직렬화 코드 모두 정의하는 템플릿과 메타 스키마(meta-schema)를 사용한다. 메타 스키마는 XML로 작성하고, 기본 XML과 비스트레일이 추가할 수 있다고 정의한 관계형 매핑외에, 직렬화의 경우에 확장할 수 있다. 이것은 하이버네이트(Hibernate)나 SQLObject와 같은 객체-관계 매핑 프레임웍과 비슷하지만, 한 스키마 버전에서 다음 버전으로 재 매핑(re-mapping) 식별자와 변환 객체와 같은 작업을 자동화하는 일부의 특별한 루틴을 추가한다. 게다가, 많은 언어에 대한 직렬화 코드를 생성하기 위해 같은 메타 스키마를 사용할 수 있다. 원래 메타-파이썬(meta-Python)을 작성한 후에, 메타 스키마에서 얻은 변수로 파이썬 코드를 실행해서 생성한 도메인 및 퍼시스턴스 코드는, 최근 마코 템플릿(Mako templates)으로 마이그레이션 했다.

자동 변환은 자신의 데이터를 새로운 버전의 시스템으로 마이그레이션해야 하는 사용자를 위한 핵심이다. 우리의 설계는 개발자에게 조금 덜 힘들게 변환하도록 후크(개발자들을 같이 이끌어가는 수단)를 추가했다. 각 버전 코드의 복사본을 유지하기 때문에, 변환 코드는 단지 한 버전을 다른 버전으로 매핑하는 것이 필요하다. 루트 수준에서, 어느 버전이 다른 버전으로 변환되는 방법을 확인하기 위해 맵을 정의한다. 일반적으로, 멀리 떨어져 있는 버전에 대해서는, 여러 중간 버전을 통해 이어져서 수반한다. 처음에는, 다음 버전만 지원하는 맵이었다, 즉 새 버전은 이전 버전으로 변환할 수 없었지만, 리버스 매핑(reverse mappings)이 최근 스키마 매핑에 추가되었다.

각 객체는 객체의 다양한 버전을 가져오고 현재 버전을 반환하는 update_version 메서드를 가지고 있다. 기본적으로, 이 메서드는 각 객체가 예전 객체의 매핑 필드를 새로운 버전의 매핑 필드에 갱신하는 재귀 변환을 한다. 이 매핑은 기본으로, 각 필드는 같은 이름의 필드에 복사하지만, 특정 필드의 기본 행위를 “오버라이드(override)”하는 메서드를 정의할 수 있다. 오버라이드는 기존 객체를 가져오고 새로운 버전을 반환하는 메서드이다. 대부분의 스키마 변경은 단지 적은 필드에 영향을 주기 때문에, 기본 매핑이 대부분은 해결하지만, 오버라이드는 로컬 변경을 정의하는 유연한 방법을 제공한다.

23.3.4. 패키지와 파이썬을 통한 확장성

비스트레일의 첫 프로토타입은 고정 셋 모듈을 가지고 있었다. 고정 셋 모듈은 비스트레일의 버전 트리와 다중 실행 캐싱에 대한 기본 아이디어를 개발하기 위한 이상적인 환경이었지만, 심각하게 장기적인 유용성을 제한했다.

전산 과학(computational science)에 대한 인프라로 비스트레일을 보고 있고, 그리고 그것은, 문자 그대로, 시스템이 개발될 다른 툴과 프로세스에 대한 스캐폴딩(scaffolding, 발판)을 제공해야 한다. 이 시나리오의 필수 조건은 확장성이다. 확장성을 달성하는 일반적인 방법은 대상 언어의 정의와 적당한 인터프리터 작성과 연관이 있다. 이것은 실행 내내 정교한 제어를 할 수 있기에 매력적이다. 이 관심은 캐싱 요구에 고려돼서 극대화되었다. 그러나 완전한 프로그래밍 언어를 구현하는 것은 우리의 주된 목표에 없는 큰 시도이다. 더 중요하게도, 단지 비스트레일을 사용하기 위해 시도하는 사용자에게 전적으로 새로운 언어를 배우도록 강요하는 것은 불가능하다.

우리는 사용자가 자신의 기능 추가가 쉬운 시스템을 원했다. 동시에, 소프트웨어의 상당히 복잡한 부분을 표현하는데 충분히 강력한 시스템을 원했다. 예를 들어, 비스트레일은 VTK 시각화 라이브러리를 지원한다. VTK는 컴파일, 설정 및 운영 체제에 따라, 약 1,000개의 클래스가 있다. 모든 경우를 위해 다양한 코드 패스를 작성하는 것이 역효과를 내고 결국에는 가망이 없어 보였기 때문에, 임의 패키지가 제공하는 비스트레일 모듈의 셋을 동적으로 결정하는 것이 필요하다고 결정했고, 자연스럽게 VTK는 복잡한 패키지에 대한 대상 모델이 되었다.
전산 과학은 원래 대상으로 한 영역중의 하나이고, 그 즈음해서 시스템을 설계했고, 파이썬은 과학자들 사이에서 “글루 코드(glue code, http://en.wikipedia.org/wiki/Glue_code)”로 유명해 졌다. 파이썬 자체를 사용해서 사용자 정의 비스트레일 모듈의 행동을 지정해서, 적용에 대한 큰 장벽을 거의 제거할 것이다. 알고 보니, 파이썬은 동적으로 정의한 클래스와 리플렉션을 위한 좋은 인프라를 제공한다. 파이썬의 거의 모든 정의는 값 형식의 클래스(first-class) 표현으로 동등한 형태를 보이고 있다. 패키지 시스템을 위한 파이썬의 두 가지 중요한 리플렉션은 다음과 같다:

• 파이썬 클래스는 callable 타입에 호출하는 함수로 동적으로 정의할 수 있다. 반환 값은 일반적으로 정의한 파이썬 클래스와 정확하게 같은 방법으로 사용할 수 있는 클래스의 표현이다.
• 파이썬 모듈은 __import__ 함수 호출로 불러올 수 있고, 결과 값은 표준 import 문에 식별자와 같은 방법으로 동작한다. 이 모듈의 경로는 런타임에 지정할 수 있다.

물론, 파이썬을 사용해서 가지게 된 단점도 있다. 우선, 파이썬의 동적인 특징은 비스트레일 패키지의 타입 세이프(type safety)와 같은 일부를 보장하고 싶은 경우, 일반적으로 할 수 없다. 더 중요한 것은, 비스트레일 모듈에 대한 요구사항의 일부인, 특히 참조 투명성에 대해서, 파이썬은 강제할 수 없다. 그래도, 파이썬의 문화적 방법(cultural mechanisms)에 주의해서 허용된 구조를 제한하는 것은 의미가 있다고 생각하고, 파이썬은 소프트웨어 확장을 위해서 매우 매력적인 언어이다.

23.3.5. 비스트레일 패키지와 번들

비스트레일 패키지는 일련의 모듈을 캡슐화한다. 디스크에서 패키지의 가장 일반적인 표현은 파이썬 패키지(운이 없다면, 명명 충돌이 있을 수 있다)와 같은 표현이다. 파이썬 패키지는 함수와 클래스 같은 파이썬 값을 정의하는 일련의 파이썬 파일로 구성되어 있다. 비스트레일 패키지는 특정 인터페이스를 따르는 파이썬 패키지이다. 비스트레일 패키지는 특정 함수와 변수를 정의한 파일을 가지고 있다. 가장 단순한 형태는, 비스트레일 패키지가 두 개의 파일인 __init__.py와 init.py를 포함하고 있는 디렉터리 형태이다.

첫 번째 파일 __init__.py는 파이썬 패키지의 필수 조건이고, 몇 가지 정의를 포함해야 하고, 이 정의는 상수여야 한다. 비록 이 상수 정의를 보장하는 방법은 없지만, 비스트레일 패키지가 이것을 따르지 못하면 버그로 간주한다. 파일에서 정의하는 값으로 패키지에 대한 유일한 식별자(globally unique identifier)는 워크플로우를 직렬화할 때 모듈을 구분하기 위해 사용하고, 패키지 버전도 포함한다(패키지 버전은 워크플로우와 패키지 업그레이드를 처리할 때 중요하다, 23.4장 참조). 또한, 이 파일은 package_dependencies와 package_requirements라는 함수를 포함할 수 있다. 비스트레일 모듈은 다른 비스트레일 모듈 옆의 루트 모듈 클래스의 서브클래스(subclass)를 허용하기 때문에, 특정 비스트레일 패키지가 다른 패키지 동작을 확장하는 것이 가능해서, 특정 패키지는 확장한 패키지의 초기화 전에 초기화해야 한다. 이런 패키지간 의존성은package_dependencies가 명시한다. 반면에, package_requirements 함수는 비스트레일이 필요한 시스템 수준의 라이브러리를 명시하고, 경우에 따라, 라이브러리의 번들 추상화를 통해서 자동으로 만족하도록 시도할 수 있다.

비스트레일에서 번들은 레드햇의 RPM이나 우분투의 APT와 같은 특정 시스템 툴로 관리하는 시스템 수준의 패키지이다. 이런 속성이 만족하면, 비스트레일은 직접 파이썬 모듈을 불러와서(importing) 패키지 속성과 적절한 변수 접근을 결정할 수 있다.

두 번째 파일, init.py는 모든 실 비스트레일 모듈 정의에 대한 진입 점을 포함한다. 이 파일의 가장 중요한 기능은 두 함수, initialize와 finalize의 정의이다. Initialize 함수는, 모든 의존 패키지 자신이 활성화된 다음에, 특정 패키지가 활성화될 때 호출된다. 이 함수는 패키지의 모든 모듈에 대해 설치(setup) 작업을 수행한다. 반면에, finalize 함수는 일반적으로 런타임 리소스(예로, 패키지가 만든 임시 파일을 정리할 수 있다)를 해제하는 데 사용한다.

비스트레일 패키지에서 각 모듈은 하나의 파이썬 클래스로 나타낸다. 이 클래스를 비스트레일에 등록하기 위해, 패키지 개발자는 각 비스트레일 모듈에 대해, add_module 함수를 한번 호출한다. 이 비스트레일 모듈은 임의의 파이썬 클래스일 수 있지만, 몇 가지 조건을 준수해야 한다. 조건 중에 첫 번째는, 각 모듈은 아마 평범하게 Module 이라고 정의한 기본 파이썬 클래스를 상속(subclass)해야 한다. 비스트레일 모듈은 다중 상속을 사용할 수 있지만, 오직 한 클래스만이 비스트레일 모듈이어야 하고, 다이아몬드 계층(diamond hierarchies, 다중상속의 구조) 의 모듈 트리는 허용하지 않는다. 다중 상속은 특히 클래스 믹스(mix-ins, 섞는)를 정의하는데 유용하다: 더 복잡한 동작을 만들기 위해 같이 구성할 수 있는 부모 클래스가 단순 동작을 코드화한다.

사용할 수 있는 포트 셋은 비스트레일 모듈의 인터페이스를 결정하고, 그래서 이런 모듈의 표시와 더불어 다른 모듈의 연결에 영향을 준다. 이런 포트는, 다음에, 명시적으로 비스트레일 인프라에 기술해야 한다. 이 작업은 initialize를 호출하는 동안 적절한 add_input_port나 add_output_port를 호출해서, 또는 각 비스트레일 모듈에 클래스별 _input_ports 와 _output_ports 목록을 지정해서 완료할 수 있다.

각 모듈은 compute 메서드를 재정의(overriding)해서 연산을 명시한다. 모듈간의 데이터 전달은 포트를 사용하고, get_input_from_port와 set_result 메서드로 접근한다. 전통적인 데이터플로우 환경에서, 실행 순서는 데이터 요청에 따라 지정된다. 우리의 경우, 실행 순서는 워크플로우 모듈의 위상 정렬(topological sorting) 로 명시한다. 캐싱 알고리즘은 비순환(acyclic) 그래프 가 필요해서, 역 위상 정렬 순서(reverse topological sorted order)로 실행을 예정해서, 이 함수 호출은 업스트림(upstream) 모듈의 실행을 트리거 하지 않는다. 신중하게 이것을 결정했다: 이것은 다른 모듈로부터 별도로 각 모듈의 동작을 고려해서 간단하게 만들고, 캐싱 전략을 단순하고 강력하게 만든다.

위상 정렬 : 보통, 그래프에서 A~D 까지 이동을 해야 하는데, A->B->D나 A->C->D 의 순서로 가야 A에서 D까지 이동을 할 수 있는 경우, A가 D까지 가려면 반드시 B나 C를 거쳐야 갈 수 있는 순서를 가지는 정렬. 즉, 엣지에 방향이 있어서 순환(cycle)이 없음.
비순환 그래프 : 한 노드에서 출발하여 지나간 경로를 다시 거치지 않으면 출발점에 도달할 수 없는 그래프로, Root를 가지고 있는 비순환 그래프가 트리이다.

일반적인 가이드로, 비스트레일 모듈은 compute 메서드를 검증하는 도중에 부작용이 있는 함수를 사용하지 말아야 한다. 23.3장에서 살펴봤듯이, 이 요구사항은 부분 워크플로우 캐싱을 가능하게 한다: 모듈이 이 속성이 반영했다면, 모듈의 동작은 업스트림 모듈의 출력 기능이다. 다음에, 모든 비순환 서브그래프는 다시 한번 연산해야 하고, 결과를 재사용할 수 있다.

23.3.6. 모듈에 데이터 전달하기

모듈과 모듈간 통신의 독특한 특징 중 하나가 모듈간에 전달하는 데이터 자체가 비스트레일 모듈이라는 것이다. 비스트레일에서, 모듈과 데이터 클래스는 단일 계층이다. 예를 들어, 모듈은 연산의 출력으로 자신을 제공할 수 있다(실제로, 모든 모듈은 기본적으로 “자신” 출력 포트를 제공한다). 주요 단점은, 종종 데이터플로우 기반(dataflow-based) 아키텍처에서 볼 수 있는, 연산과 데이터 간의 개념적 분리가 손실된다는 것이다. 그러나 두 가지 큰 장점이 있다. 첫 번째는 자바나 C++의 객체 타입 시스템과 매우 비슷하고 선택의 여지가 없었다: VTK와 같은 방대한 클래스 라이브러리의 자동 래핑을 지원하는 것이 매우 중요했다. 이 라이브러는 연산 결과로 객체가 다른 객체를 만들 수 있게 하고, 더 복잡해진 연산과 데이터 간의 구분을 래핑한다.

이 결정의 두 번째 장점은 워크플로우에서 상수 값과 사용자가 설정할 수 있는 매개변수를 정의할 수 있게 해서, 시스템의 통합을 더 쉽게 그리고 단일화한다. 예를 들면, 상수로 지정한 웹에 있는 파일을 로드하는 워크플로우를 생각해 보자. 이것은 현재 매개변수로 URL을 지정해서 GUI로 지정한다(그림 32.1의 매개변수 편집 부분을 참고해라). 이 워크플로우의 자연스러운 수정은 업스트림에서 연산된 URL을 호출(fetch)하기 위해 사용하는 것이다. 우리는 가능한 한 적게 워크플로우를 변경하고 싶다. 모듈이 자신을 출력할 수 있다고 가정하면, 단순히 매개변수에 해당하는 포트에 적당한 값으로 문자열을 연결할 수 있다. 상수 검증의 출력이 자신이기 때문에, 실제로 그 값이 상수로 지정된 것처럼 동작은 같다.

그림 23.5: PythonSource 모듈과 함께 새로운 기능 프로토타이핑 하기

상수 설계와 연관된 다른 고려사항이 있다. 각 상수 타입은 자신의 값을 지정하기 위한 다양한 이상적인 GUI 인터페이스가 있다. 예로, 비스트레일에서, 파일 상수 모듈은 파일 선택 창을 제공하고; 부울(Boolean) 값은 체크박스로 지정하고; 색상 값은 각 운영체제에 있는 컬러 피커를 가지고 있다. 이 일반성을 이루기 위해, 개발자는 기본 상수 클래스에서 사용자 정의 상수로 상속해야 하고, 적절한 GUI 위젯과 문자열 표현(그래서 임의의 상수는 디스크에 직렬화될 수 있다)을 정의해서 오버라이드를 제공한다.

간단한 프로토타이핑 작업을 위해, 비스트레일이 내장PythonSource 모듈을 제공하는 것을 주의하자. PythonSource 모듈은 직접 워크플로우에 스크립트를 입력하는 데 사용할 수 있다. PythonSource(그림 23.5 참조)를 위한 설정 창은 실행하는 파이썬 코드와 함께 다중 입/출력 포트로 지정되는 것을 허용한다.

23.4. 컴포넌트와 기능

위에서 설명한 것처럼, 비스트레일은 탐색 연산 작업을 만들고 실행을 단순화하는 일련의 기능과 유저 인터페이스를 제공한다. 다음은 이들 중 일부를 설명한다. 또한, 풍부한 이력 발행의 생성을 지원하는 인프라를 위해 기본으로 사용하는 방법을 간단하게 설명한다. 비스트레일과 그 기능에 대한 종합적인 설명은 온라인 문서를 참고해라.

그림 23.6: 비주얼 스프레드시트

23.4.1. 비주얼 스프레드시트

비스트레일은 사용자가 비주얼 스프레드시트를 사용해서 여러 워크플로우에서 결과를 탐색하고 비교할 수 있게 한다. 스프레드시트는 시트(sheets)와 셀(cells)로 구성된 자체 인터페이스가 있는 비스트레일 패키지이다. 각 시트는 일련의 셀을 포함하고 있고 사용자 정의 레이아웃을 가지고 있다. 셀은 워크플로우가 만들어낸 결과의 비주얼 표현을 포함하고 있고, 다양한 타입의 데이터를 표시하도록 사용자가 정의할 수 있다.

스프레드시트에 셀을 표시하기 위해, 워크플로우는 SpreadsheetCell 모듈을 기반으로 하는 모듈을 포함해야 한다. SpreadsheetCell 모듈은 스프레드시트의 셀에 해당하기에, 워크플로우는 여러 셀을 만들 수 있다. SpreadsheetCell 모듈의 compute 메서드는 실행엔진(그림 23.3)과 스프레드시트 사이의 통신을 처리한다. 실행 중에, 스프레드시트는 파이썬의 동적 클래스 인스턴스화를 활용해서, 요청한 타입에 따라 셀을 만든다. 따라서 사용자 정의 비주얼 표시는 SpreadsheetCell 서브 클래스를 만들고 그 클래스의 compute 메서드가 스프레드시트에 사용자 정의 셀 타입을 전달해서 달성할 수 있다. 예로, 그림 23.1의 워크플로우에서, MplFigureCell 은 SpreadsheetCell 모듈이고matplotlib가 만들어내는 이미지를 표시하도록 설계했다.

스프레드시트가 GUI 백엔드로 PyQt를 사용하기에, 사용자 정의 셀 위젯은 PyQt의QWidget를 서브클래스 해야 한다. 또한, 새로운 데이터가 도착하면, 스프레드시트가 위젯을 업데이트하기 위한, updateContents 메서드를 정의해야 한다. 각 셀 위젯은 선택적으로 toolbar 메서드를 구현해서, 사용자 정의 툴바를 정의할 수 있다; 툴바는 셀을 선택했을 때, 스프레드시트에서 툴바 영역에 표시될 것이다.

그림 23.6은 VTK 셀을 선택했을 때 보이는 스프레드시트이고, 이 경우, 툴바는 PDF 이미지 내보내기, 워크플로우에 카메라 위치 저장하기 그리고 애니메이션 만들기의 특정 위젯을 제공한다. 스프레드시트 패키지는 기록 재생(애니메이션)과 멀티 터치 이벤트 전송과 같은 공통 기능을 제공하는 사용자 정의 QCellWidget을 정의한다. QCellWidget은 새로운 셀 타입의 빠른 개발을 위해 QWidget 대신 사용할 수 있다.

비록 스프레드시트가 셀 타입으로 PyQt 위젯만 허용하지만, 다른 GUI 툴킷으로 작성한 위젯도 통합할 수 있다. 이렇게 하려면, 위젯이 네이티브 플랫폼(native platform)에 요소(elements)를 내보내야 하고, 그리고 다음에 PyQt가 요소를 사용할 수 있다. VTKCell 위젯은 실제로 C++로 개발되었기 때문에, 이 방식을 사용한다. 실행 시에, VTKCell은 시스템에 의존적인, 윈도 아이디(window id), Win32, 또는 코코아/카본(Cocoa/Carbon) 핸들을 저장하고, 스프레드시트 캔버스에 매핑한다.

셀처럼, 시트 또한 사용자 정의할 수 있다. 기본적으로, 각 시트는 탭 뷰에 있고 테이블 레이아웃의 형태이다. 그러나 한번에 여러 시트를 표시하도록, 시트를 스프레드시트 창에서 분리할 수 있다. 또한, StandardWidgetSheet를 상속해서 다양한 시트 레이아웃을 만들 수 있고, 또한 PyQt 위젯도 그렇다. StandardWidgetSheet는 셀 레이아웃과 더불어 편집 모드에서 스프레드시트와의 상호 작용을 관리한다. 편집 모드에서, 사용자는 셀 데이터와 상호작용보다 셀 레이아웃을 바꾸고 셀에 고급 동작을 수행할 수 있다. 이런 동작은 매개 변수 탐색에서 유사 적용(23.4장 참고)과 새로운 워크플로우 버전 생성을 포함한다.

23.4.2. 시각적 차이와 유추

비스트레일이 설계한 대로, 이력정보의 사용과 더불어 수집이 활성화되길 원했다. 먼저, 사용자가 버전 간의 정확한 차이를 보기 원했지만, 다른 워크플로우에 이 차이를 적용할 수 있는 더 유용한 기능을 알게 됐다. 비스트레일이 워크플로우의 변화를 기록하기 때문에, 이 작업은 둘 다 가능하다.

버전 트리가 모든 변경을 저장하고 각 동작을 되돌리 수 있기 때문에, 다른 버전에서 변경한 동작의 전체 순서를 찾을 수 있다. 일부 변경이 서로 상쇄시켜, 이 순서를 압축할 수 있게 한다는 것을 주의해라. 예를 들면, 나중에 삭제된 모듈의 추가는 차이를 연산할 때 검사할 필요가 없다. 마지막으로, 순서를 더 단순화하기 위한 일부 휴리스틱(heuristics)을 가지고 있다: 두 워크플로우에 같은 모듈이 존재하지만, 추가(adds)와 삭제(deletes)를 취소하는 분리된 동작으로 추가됐다.

변경에서, 우리는 비슷하고 다른 모듈, 연결 그리고 매개변수를 보여주는 시각적인 표시를 만들 수 있다. 이것은 그림 23.4가 보여주고 있다. 워크플로우 양쪽에 보이는 모듈과 연결은 회색이고, 나타나는 워크플로우에 따라 하나만 색을 띠게 된다. 다양한 매개변수와 일치하는 모듈은 밝은 회색으로 그림자 처리하고, 사용자는 각 워크플로우의 값을 보여주는 테이블에서 특정 모듈에 대한 매개변수 차이를 검사할 수 있다.

유추 작업(operation)은 사용자가 이런 차이를 이해해서 다른 워크플로우에 적용하게 한다. 사용자가 기존의 워크플로우(예로, 출력 이미지의 해상도 및 파일 포맷 변경)에 일련의 변경을 하면, 유추로 다른 워크플로우에 같은 변경을 적용할 수 있다. 이렇게 하려고, 사용자는 일련의 원하는 변경의 제한과 더불어 유추를 적용하기 원하는 소스 워크플로우와 타켓 워크플로우를 선택한다. 비스트레일은 템플릿으로 처음 두 워크플로우를 사이의 차이점을 계산하고, 다음에 세 번째 워크플로우에 적용하기 위해 차이를 다시 매핑하는 방법을 결정한다. 시작하는 워크플로우와 완전히 일치하지 않는 워크플로우에 차이점을 적용하는 것이 가능해서, 비슷한 모듈 사이에 통신을 허용하는 소프트 매칭이 필요하다. 이 매칭으로, 차이점을 다시 매핑할 수 있고, 그래서 변경 순서는 선택된 워크플로우에 적용될 수 있다[SVK+07]. 이 방법은 안전하지 않고 전혀 원하지 않는 새로운 워크플로우를 만들어낼 수도 있다. 이런 경우, 사용자는 특정 도입 실수를 해결하려고 시도할 수도 있고, 또는 이전 버전으로 돌아가고 수동으로 변경을 적용한다.

유추에 사용하는 소프트 매칭을 계산하기 위해, 전체 워크플로우 구조와 로컬 매치(같거나 매우 유사한 모듈)를 비교해 보길 원한다. 심지어 같은 매칭의 계산이 서브그래프 동일유형의 일치하는 정도에 기인하기 때문에 충분하지 않다는 것을 주의해라, 그래서 휴리스틱의 적용이 필요하다. 간단하게, 비슷한 이웃(모듈)을 공유하는 두 개의 워크플로우에 약간 비슷한 모듈이 두 개가 있다면, 이 두 모듈의 기능이 유사하고 또는 일치하리라 판단할 수 있다. 공식적으로, 각 노드는 원본 워크플로우에서 모듈의 짝일 수 있고, 엣지는 공유된 연결을 나타내는 제품 그래프를 만든다. 그런 다음, 인접 노드에 엣지로 걸치는 각 노드에서 점수를 확산하는 단계를 실행한다. 이것은 구글의 페이지 랭크와 비슷한 마르코프 프로세스(Markov process) 이고, 결과적으로 지금은 일부 글로벌 정보를 포함하는 일련의 점수를 남겨서 수렴할 것이다. 이 점수로부터, 아주 비슷하지 않은 모듈을 짝 지우지 않고 내버려 두기 위해 임계 값을 사용해서 최적의 매칭을 결정할 수 있다.

유추에 사용하는 소프트 매칭 : 정확하게일치해서검색하는기존의방식보다발전된형태로, 사용자의심리나상관관계가높은정보에가중치를부여해서원하는결과를검색하는방식
마르코프 프로세스(Markov process, http://en.wikipedia.org/wiki/Markov_process, 미래의 프로세스가 현재의 상태에 의해서만 결정되는, 과거와는 독립적인 랜덤 프로세스

23.4.3. 이력 질의

비스트레일이 수집한 이력은 일련의 워크플로우, 자신의 구조, 메타데이터 그리고 실행 로그를 포함한다. 사용자가 이력 데이터에 접근해서 탐색할 수 있는 것은 중요하다. 비스트레일은 텍스트 기반 그리고 비주얼 위지윅(WYSIWYG) 질의 인터페이스 모두 제공한다. 태그, 어노테이션 그리고 날짜와 같은 정보에 대해서, 선택적인 마크업으로 키워드 검색을 사용할 수 있다. 예로, 사용자(user:~dakoop)가 만든 plot 키워드로 모든 워크플로우를 찾고 있는 중. 그러나 워크플로우의 특정 서브그래프에 대한 질의는 사용자가 처음부터 질의를 만들거나 파이프라인에 기존 조각을 복사하거나 수정하는 QBE(query-by-example) 인터페이스로 더 쉽게 나타낸다.

http://en.wikipedia.org/wiki/Query_by_Example, 사용자가 데이터베이스에 있는 정보를 검색할때, 기존의 방법(SQL)을 좀 더 쉽게 만들어주는 방법, 예로 인터넷에서 검색하는 방법이 QBE에 해당한다.

이 QBE 인터페이스를 설계할 때, 매개 변수 구성에 일부 변화와 함께 기존의 워크플로우 편집기의 코드 대부분을 유지했다. 매개변수는, 정확한 값보다 범위나 키워드를 검색하는데 종종 유용하다. 따라서 매개변수 값 필드에 제한자를 추가했다; 사용자가 매개변수 값을 추가하거나 수정할 때, 정확하게 일치하는 기본값으로 제한자 중 하나를 선택해서 고를 수 있다. 비주얼 질의 구성과 더불어, 질의 결과도 시각적으로 보인다. 일치하는 버전은 버전 트리에서 강조하고, 선택된 워크플로우는 일치하는 부분이 강조돼서 표시된다. 사용자는 다른 질의를 시작하거나 리셋 버튼을 클릭해서 질의 결과 모드를 종료할 수 있다.

23.4.4. 영속 데이터

비스트레일은 각 단계의 결과가 만들어진 방법과 스펙의 이력을 저장한다. 그러나 워크플로우가 더는 사용할 수 없는 데이터가 필요한 경우, 워크플로우의 재현하기가 어려울 수 있다. 게다가, 오래가는 워크플로우에 대해서, 재계산을 피하고자 세션에 영속적인 캐시로 중간 데이터를 저장하는 것은 유용할 수 있다.

많은 워크플로우 시스템이 파일시스템 경로에 이력 데이터를 저장하는데, 이 방법은 문제가 있다. 사용자가 파일 이름을 변경할 수도 있고, 데이터를 복사하지 않고 다른 시스템으로 워크플로우를 이동하거나 데이터 내용을 변경할 수 있다. 이런 경우에, 이력을 경로에 저장하는 것은 적합하지 않다. 데이터를 해시하고, 이력으로 해시된 데이터를 저장하는 것은 데이터가 변경되었는지 결정하는 데 도움이 되지만, 데이터가 존재한다 해도, 데이터가 위치를 알려주지 않는다. 이 문제를 해결하기 위해, 이력에서 참조할 수 있는 데이터를 저장하기 위해 버전 관리 인프라를 사용하는 비스트레일 패키지인, 영속 패키지(Persistence Package)를 만들었다. 현재, 다른 시스템도 쉽게 적용할 수 있지만, 데이터를 관리하기 위해 Git를 사용하고 있다.

데이터를 식별하기 위해 일반적으로 UUID(universally unique identifiers)를 사용하고, 기트(git)에서 버전을 참조하기 위해 해시를 커밋한다. 실행에서 다른 실행으로 데이터가 변경된 경우, 새로운 버전이 리파지토리에 체크인된다. 따라서, (uuid, version) 튜플은 어떤 상태에서도 데이터를 검색하기 위한 복합 식별자가 된다. 게다가, 데이터의 해시를 저장하는 것과 더불어 그것(입력이 아니라면)이 만들어낸 워크플로우의 업스트림 부분의 서명도 저장한다. 이것은 별도로 식별할 수 있는 것과 더불어 같은 계산을 다시 시작할 때 재사용하는 데이터에 링크를 허용한다.

이 패키지 설계 시에 주된 관심은 사용자가 자신의 데이터를 선택하고 검색할 수 방법이었다. 또한, 데이터를 입력, 출력 또는 중간 데이터(워크플로우의 출력을 다른 워크플로우의 입력으로 사용할 수 있다)로 사용하는지에 상관없이, 같은 리파지토리에 모든 데이터를 유지하기 원했다. 사용자가 데이터를 식별하기 위해 적용할 수 있는 두 가지 유형이 있다: 새로운 참조를 만들기로 선택하거나 기존의 참조를 사용하기. 처음 실행 후에, 새로운 참조는 실행내내 지속하는 기존의 참조가 된다는 것을 주의해라: 사용자가 원한다면 나중에 새로운 참조를 만드는 것을 선택할 수도 있지만, 이것은 드문 경우다. 사용자는 종종 최신 버전의 데이터를 사용하기 원하기 때문에, 특정 버전 없이 식별된 참조는 기본으로 최신 버전이 될 것이다.

모듈을 실행하기 전에, 모든 입력이 재귀적으로 갱신되는 것을 상기해라. 업스트림 연산이 이미 실행한 경우, 영속 데이터 모듈은 그것의 입력을 갱신하지 않을 것이다. 이를 확인하기 위해, 영구 저장소에 업스트림 하위 워크플로우의 서명을 확인하고 서명이 있는 경우 사전에 계산된 데이터를 검색한다. 게다가, 특정 실행을 재현할 수 있도록, 이력으로 데이터 식별자와 버전을 기록한다.

23.4.5. 업그레이드

비스트레일의 핵심인 이력으로, 기존 워크플로우를 업그레이드하는 능력은 새로운 버전의 패키지를 실행하는 방식이므로 중요한 문제이다. 써드 파티(third-parties)가 패키지를 만들 수 있기에, 워크플로우를 업그레이드하기 위한 인프라와 더불어 업그레이드 경로를 지정해서 패키지 개발자를 위한 후크 둘 다 필요하다. 워크플로우 업그레이드에 연관된 핵심 동작은 모듈을 새로운 버전으로 교체하는 것이다. 기존 모듈에서 모든 연결과 매개변수를 교체해야 하므로, 교체하는 동작이 복잡하다는 것을 주의해라. 게다가, 업그레이드는 모듈에 대한 매개변수와 연결의 재설정, 재할당 또는 이름 변경이 필요할 수 있다. 예를 들면, 모듈의 인터페이스가 변경될 수도 있다.

각 패키지는(연관 모듈과 같이) 버전으로 태깅하고, 버전이 변경된 경우, 패키지의 모듈이 변경되었다고 간주한다. 일부 또는 대부분이 변경되지 않았을 수도 있지만, 코드를 분석하지 않고는 확인할 수 없다는 것에 주의해라. 그러나 변경하지 않은 모든 인터페이스의 업그레이드도 자동으로 시도한다. 자동으로 업그레이드하려고, 모듈을 새로운 버전으로 교체해 보고, 동작하지 않으면 예외처리한다. 개발자가 모듈의 인터페이스 변경하거나 모듈의 이름을 변경했을 경우, 명시적으로 변경을 지정할 수 있다. 이것을 더 쉽게 관리 하기 위해서, 단지 기본 업그레이드 동작이 수정해야 하는 유일한 위치를 지정하는 remap_module 메서드를 만들었다. 예로, 개발자가 입력포트 ‘file’을 ‘value’로 이름 변경하는 것은 특정 리패밍(remapping)으로 지정할 수 있어서, 새로운 모듈이 만들어질 때, 기존 모듈에서 ‘file’에 연결된 모든 연결은 ‘value’로 연결할 것이다. 내장 비스트레일 모듈에 대한 업그레이드 경로의 예는 다음과 같다.

def handle_module_upgrade_request(controller, module_id, pipeline):
   module_remap = {'GetItemsFromDirectory':
                       [(None, '1.6', 'Directory',
                         {'dst_port_remap':
                              {'dir': 'value'},
                          'src_port_remap':
                              {'itemlist': 'itemList'},
                          })],
                   }
  return UpgradeWorkflowHandler.remap_module(controller, module_id, pipeline, module_remap)

이 코드 조각은 기존의 GetItemsFromDirectory(1.6버전까지) 모듈 대신 Directory 모듈을 사용하도록 워크플로우를 업그레이드한다. 이 코드는 기존 모듈에서 ‘dir’ 포트를 ‘value’로 ‘itemlist’ 포트를 ‘itemList’ 포트로 매핑한다.

모든 업그레이드는 버전 트리에서 새로운 버전을 만들어서, 업그레이드 전/후 실행을 구분하고 비교할 수 있다. 이것은 업그레이드가 워크플로우(예, 패키지 개발자가 버그 픽스)의 실행을 변경하고, 이력 정보로 변경을 추적해야 하는 것을 가능하게 한다. 기존 비스트레일에서, 트리의 모든 버전을 업그레이드할 필요가 있을 수도 있다는 것을 주의해라. 복잡성을 줄이기 위해, 사용자가 탐색(navigate)한 버전만 업그레이드한다. 게다가, 워크플로우가 수정되거나 실행될 때까지, 모든 업그레이드의 지속을 지연할 수 있게 환경설정을 제공한다: 사용자가 기존 버전을 사용하는 경우, 업그레이드를 고집할 필요는 없다.

23.4.6. 풍부한 이력 결과의 공유와 출판

재현성은 과학적 방법의 초석(기초)이기에, 전산 실험(computational experiments)을 설명하고 있는 현재의 출판물은 반복되거나 일반화할 수 있는 결과를 가능하게 하는 충분한 정보를 제공하는 것이 종종 실패한다. 최근, 재현할 수 있는 결과의 발행에 새로운 관심이 있었다. 이 실천의 광범위한 적용의 큰 장애는, 결과를 재현해야 하는 것과 더불어 결과를 검증해야 하는 컴포넌트(예, 데이터, 코드, 매개변수 설정)를 모두 포함하는 번들을 만드는 것이 어렵다는 사실이다.

자세한 이력을 캡처하고, 위에서 설명한 많은 기능을 통해서, 비스트레일은 시스템에서 실행하는 전산 실험을 위한 프로세스를 단순화한다. 그러나, 이 방법은 문서를 링크하는 것과 이력 정보를 공유하는 것 둘다 필요했다.

딥 캡션(deep caption, 글이나 이미지등을 흥미롭게 만드는 2~3 문장의 내용) 과 같이, 그들의 이력에 링크된 논문의 결과를 보여주는 것이 가능한 비스트레일 패키지를 개발했다. 라텍스(LaText) 패키지를 사용해서 개발했고, 사용자는 비스트레일 워크플로우에 링크 수치를 포함할 수 있다. 다음의 라텍스 코드는 워크플로우 결과를 포함하고 있는 수치를 만들 것이다.

\begin{figure}[t]
{
\vistrail[wfid=119,buildalways=false]{width=0.9\linewidth}
}
\caption{Visualizing a binary star system simulation. This is an image
  that was generated by embedding a workflow directly in the text.}
\label{fig:astrophysics}
\end{figure}

Pdflatex를 사용해서 문서를 컴파일할 때, \vistrail 명령은 XML-RPC 메시지를 비스트레일 서버에 id 119 와 함께 워크플로우를 실행하기 위해 전송하는 매개변수와 함께 파이썬 스크립트를 호출한다. 같은 파이썬 스크립트가 서버에서 워크플로우의 결과를 다운받고 특정 레이아웃 옵션(width=0.9\linewidth)을 사용해서, 링크된 라텍스 \includegraphics 명령을 만들어서 PDF 문서 결과에 그것들(링크)을 포함한다.

또한, 웹 페이지, 위키(Wiki), 워드 문서 그리고 파워포인트 문서에 비스트레일 결과를 포함할 수 있다. 마이크로소프트 파워포인트와 비스트레일 사이의 연결은 COM(Component Object Model)과 OLE(Object Linking and Embedding) 인터페이스를 사용하고 있다. 파워포인트와 상호 작용하기 위한 객체는, 최소한 COM 인터페이스 IOleObject, IDataObject와 IPersistStorageinterface를 구현해야 한다. COM 인터페이스 구현을 위해 추상화한 Qt의 QAxAggregated 클래스를 사용하고, OLE 객체를 빌드하기 위해, IDataObject 와 IPersistStorage는 둘 다 Qt가 자동으로 처리한다. 따라서 단지 IOleObject 인터페이스를 구현하면 된다. 이 인터페이스의 가장 중요한 호출이 DoVerb이다. 이것은 객체 활성화와 같이, 파워포인트에서의 특정 액션에 비스트레일이 반응하게 해 준다. 이 구현에서는, 비스트레일 객체가 활성화될 때, 비스트레일 애플리케이션을 로딩하고 사용자에게 열고, 상호작용하고, 입력하기 원하는 파이프라인을 선택하는 것이다. 비스트레일을 종료한 후, 파이프라인 결과는 파워포인트에 보일 것이다. 또한, 파이프라인 정보는 OLE 객체에 저장되어 있다.

사용자에게 연관 이력과 함께 결과를 자유롭게 공유할 수 있도록, crowdLabs을 만들었다. crowdLabs은 협력해서 데이터 분석과 시각화하도록 과학자를 위한 환경을 제공하는 일련의 유용한 툴과 확장성 있는 인프라를 통합한 소셜 웹 사이트이다. crowdLabs은 비스트레일과 밀접하게 통합되어 있다. 사용자가 비스트레일에서 만들어진 결과를 공유하기 원한다면, 정보를 업로드하기 위해서 직접 crowdLabs 서버에 연결할 수 있다. 정보가 업로드 되면, 사용자는 웹 브라우저로 상호 작용과 워크플로우를 실행할 수 있다. 이 워크플로우는 crowdLabs을 동작시키는 비스트레일 서버가 실행한다. 재현할 수 있는 출판물을 만드는 데 사용하는 비스트레일 방법에 대한 자세한 내용은 http://www.vistrails.org를 참조해라.

23.5. 교훈

다행히, 2004년에 다시 이력을 지원하는 데이터 탐색과 시각화 시스템을 빌드하는 것에 대해서 생각하기 시작했다. 이 도전이 얼마나 어려운 것인지, 또한 지금 있는 지점까지 얼마나 오래 걸릴지 알지 못했다. 만약 알고 있었다면, 아마도 시작하지 않았을 것이다.

초기에, 잘한 하나의 전략은 새로운 기능의 빠른 프로토타이핑으로 선발된 사용자에게 빨리 보여줬다. 사용자로부터 받은 초기 피드백과 격려는 프로젝트를 앞으로 이끄는 데 중요하다. 이것은 사용자의 피드백 없이도 비스트레일을 설계하는 것을 가능하게 한다. 프로젝트에서 강조하고 싶은 한 측면이 있다면, 시스템 기능 대부분은 사용자 피드백에 직접 응답으로 설계했다는 것이다. 그러나 사용자가 요청한 것이 사용자(his/her)의 필요(사용자가 여러번 요청했다고 사용자의 요청에 대한 최고의 솔루션은 아니다)에 대한 최고의 솔루션이 아니라는 것은 여러번 주의할 만한 가치가 있다. 되풀이해서, 시스템에 유용하고 적절하게 통합하도록 확인하기 위해, 기능을 설계하고 재설계했다.

우리의 사용자 중심 접근 방식을 고려하면, 모든 기능이 자주 사용될 것이라고 기대할 수도 있다. 불행히도 이 경우는 일어나지 않는다. 종종 그 이유는 그것이 다른 툴에서 발견되지 않기에, 기능은 매우 “특별한” 것이다. 예로, 유추와 심지어 버전트리는 사용자 대부분이 잘 모르는 개념이고, 편하게 사용하기 위해서는 익숙해지는 시간이 필요하다. 또 다른 중요한 문제는, 문서화이고, 또는 문서화 부족이다. 다른 많은 오픈 소스 프로젝트와 마찬가지로, 우리는 기존의 것을 문서화하는 것보다 새로운 기능의 개발이 훨씬 더 되어있다. 문서화 지연은 유용한 기능을 충분히 활용하지 못하는 것과 더불어 메일링 리스트에 많은 질문이 올라오게 한다.

비스트레일과 같은 시스템을 사용하는 데 있어, 어려운 것 중 하나가 아주 일반적이다는 것이다. 사용성을 향상하기 위한 최대한 노력에도, 비스트레일은 복잡한 툴이고, 일부 사용자에게는 가파른 학습 곡선이 필요하다. 시간이 지나면서 개선된 문서로, 더 개선된 시스템, 그리고 더 많은 애플리케이션과 특정 도메인, 특정 필드에 대한 적용의 장애가 더 낮아질 것이라 믿는다. 또한, 이력의 개념이 더 광범위해져서, 사용자가 비스트레일 개발에 적용한 철학을 이해하기가 쉬워질 것이다.

23.5.1. 감사

비스트레일에 기여한 훌륭한 개발자들 모두에게 감사하고 싶다: 에릭 앤더슨(Erik Anderson), 루이 바보일(Louis Bavoil), 클리프턴 브룩스(Clifton Brooks), 제이슨 캘러핸(Jason Callahan), 스티브 캘러핸(Steve Callahan), 로레나 카를로(Lorena Carlo), 라우로 린스(Lauro Lins), 토미 엘크비스트(Tommy Ellkvist), 필립 메이트(Phillip Mates), 다니엘 리스(Daniel Rees) 그리고 네이선 스미스(Nathan Smith).

프로젝트의 비전을 개발하고 지원을 위해 노력한 안토니오 밥티스타(Antonio Baptista), 시스템을 개선하기 위해서 같이 협력하고 있고, 풍부한 이력 발행 기능의 개발과 배포를 위해 많은 자극을 준 매티아스 토로이어(Matthias Troyer)에게 특별히 감사한다.

비스트레일 시스템의 연구와 개발은 국립 과학 재단(National Science Foundation)의 IIS 1050422, IIS-0905385, IIS 0844572, ATM-0835821, IIS-0844546, IIS-0746500, CNS-0751152, IIS-0713637, OCE-0424602, IIS-0534628, CNS-0514485, IIS-0513692, CNS-0524096, CCF-0401498, OISE-0405402, CCF-0528201, CNS-0551724, 미국 에너지부 산하 SciDAC(Scientific Data Management Center), IBM Faculty Awards의 지원을 받고 있다.