diff --git a/include/DoocsPVFactory.h b/include/DoocsPVFactory.h
index 6287afe5d0a9811f762a50d7b5812cfd286fc9b1..082f78c60bc79efb67fb42ea8883db11ac9918a6 100644
--- a/include/DoocsPVFactory.h
+++ b/include/DoocsPVFactory.h
@@ -54,7 +54,7 @@ namespace ChimeraTK {
     boost::shared_ptr<D_fct> autoCreate(std::shared_ptr<PropertyDescription> const& propertyDescription);
 
     template<class DOOCS_SCALAR_T, class DOOCS_PRIMARY_T, class DOOCS_ARRAY_T, class DOOCS_ARRAY_PRIMITIVE_T>
-    boost::shared_ptr<D_fct> typedCreateScalarOrArray(std::type_index valueType, ProcessVariable& processVariable,
+    boost::shared_ptr<D_fct> typedCreateScalarOrArray(const std::type_info& valueType, ProcessVariable& processVariable,
         AutoPropertyDescription const& propertyDescription, DecoratorType decoratorType);
   };
 
diff --git a/src/DoocsPVFactory.cc b/src/DoocsPVFactory.cc
index 1ad217ea6ba5c6a7a613ab7122e8fab1ed543068..aec76e47a74fac61f48f5dfe4b1dabae86142847 100644
--- a/src/DoocsPVFactory.cc
+++ b/src/DoocsPVFactory.cc
@@ -307,96 +307,47 @@ namespace ChimeraTK {
       boost::dynamic_pointer_cast<DoocsIfff>(doocsPV)->publishZeroMQ();
     }
 
-    if(not ifffDescription.isWriteable){
+    if(not ifffDescription.isWriteable) {
       doocsPV->set_ro_access();
     }
     return doocsPV;
   }
 
-  static std::map<std::type_index, std::function<unsigned int(ProcessVariable&)>> castingMap{
-      {typeid(uint8_t),
-          [](auto& pv) { return dynamic_cast<ChimeraTK::NDRegisterAccessor<uint8_t>&>(pv).getNumberOfSamples(); }},
-      {typeid(int8_t),
-          [](auto& pv) -> auto {return dynamic_cast<ChimeraTK::NDRegisterAccessor<int8_t>&>(pv).getNumberOfSamples();
-} // namespace ChimeraTK
-}
-,
-    {typeid(uint16_t),
-        [](auto& pv) { return dynamic_cast<ChimeraTK::NDRegisterAccessor<uint16_t>&>(pv).getNumberOfSamples(); }},
-    {typeid(int16_t),
-        [](auto& pv) -> auto {return dynamic_cast<ChimeraTK::NDRegisterAccessor<int16_t>&>(pv).getNumberOfSamples();
-}
-}
-,
-    {typeid(uint32_t),
-        [](auto& pv) -> auto {return dynamic_cast<ChimeraTK::NDRegisterAccessor<uint32_t>&>(pv).getNumberOfSamples();
-}
-}
-,
-    {typeid(int32_t),
-        [](auto& pv) -> auto {return dynamic_cast<ChimeraTK::NDRegisterAccessor<int32_t>&>(pv).getNumberOfSamples();
-}
-}
-,
-    {typeid(uint64_t),
-        [](auto& pv) -> auto {return dynamic_cast<ChimeraTK::NDRegisterAccessor<uint64_t>&>(pv).getNumberOfSamples();
-}
-}
-,
-    {typeid(int64_t),
-        [](auto& pv) -> auto {return dynamic_cast<ChimeraTK::NDRegisterAccessor<int64_t>&>(pv).getNumberOfSamples();
-}
-}
-,
-    {typeid(float),
-        [](auto& pv) -> auto {return dynamic_cast<ChimeraTK::NDRegisterAccessor<float>&>(pv).getNumberOfSamples();
-}
-}
-,
-    {typeid(double),
-        [](auto& pv) -> auto {return dynamic_cast<ChimeraTK::NDRegisterAccessor<double>&>(pv).getNumberOfSamples();
-}
-}
-,
-    {typeid(std::string),
-        [](auto& pv) -> auto {return dynamic_cast<ChimeraTK::NDRegisterAccessor<std::string>&>(pv).getNumberOfSamples();
-}
-}
-,
-}
-;
-
-// fixme: some of the variables needed here are redundant and can be sovled with
-// mpl and/or fusion maps
-template<class DOOCS_SCALAR_T, class DOOCS_PRIMITIVE_T, class DOOCS_ARRAY_T, class DOOCS_ARRAY_PRIMITIVE_T>
-boost::shared_ptr<D_fct> DoocsPVFactory::typedCreateScalarOrArray(std::type_index valueType,
-    ProcessVariable& processVariable, AutoPropertyDescription const& autoPropertyDescription,
-    DecoratorType decoratorType) {
-  // We have to convert to the original NDRegisterAccessor to determine the
-  // number of samples. We cannot use a decorator because scalar and array
-  // DOOCS_PRIMITIVE_T can be different, and once a decorator is created you
-  // cannot get the other type any more.
-
-  auto nSamples = castingMap[valueType](processVariable);
-
-  if(nSamples == 1) {
-    return createDoocsScalar<DOOCS_PRIMITIVE_T, DOOCS_SCALAR_T>(autoPropertyDescription, decoratorType);
-  }
-  else {
-    return typedCreateDoocsArray<DOOCS_ARRAY_PRIMITIVE_T, DOOCS_ARRAY_T>(
-        AutoPropertyDescription(autoPropertyDescription));
+  // fixme: some of the variables needed here are redundant and can be sovled with
+  // mpl and/or fusion maps
+  template<class DOOCS_SCALAR_T, class DOOCS_PRIMITIVE_T, class DOOCS_ARRAY_T, class DOOCS_ARRAY_PRIMITIVE_T>
+  boost::shared_ptr<D_fct> DoocsPVFactory::typedCreateScalarOrArray(const std::type_info& valueType,
+      ProcessVariable& processVariable, AutoPropertyDescription const& autoPropertyDescription,
+      DecoratorType decoratorType) {
+    // We have to convert to the original NDRegisterAccessor to determine the
+    // number of samples. We cannot use a decorator because scalar and array
+    // DOOCS_PRIMITIVE_T can be different, and once a decorator is created you
+    // cannot get the other type any more.
+
+    size_t nSamples;
+    callForType(valueType, [&](auto t) {
+      using T = decltype(t);
+      nSamples = dynamic_cast<ChimeraTK::NDRegisterAccessor<T>&>(processVariable).getNumberOfSamples();
+    });
+
+    if(nSamples == 1) {
+      return createDoocsScalar<DOOCS_PRIMITIVE_T, DOOCS_SCALAR_T>(autoPropertyDescription, decoratorType);
+    }
+    else {
+      return typedCreateDoocsArray<DOOCS_ARRAY_PRIMITIVE_T, DOOCS_ARRAY_T>(
+          AutoPropertyDescription(autoPropertyDescription));
+    }
   }
-}
 
-boost::shared_ptr<D_fct> DoocsPVFactory::autoCreate(std::shared_ptr<PropertyDescription> const& propertyDescription) {
-  // do auto creation
-  auto autoPropertyDescription = std::static_pointer_cast<AutoPropertyDescription>(propertyDescription);
+  boost::shared_ptr<D_fct> DoocsPVFactory::autoCreate(std::shared_ptr<PropertyDescription> const& propertyDescription) {
+    // do auto creation
+    auto autoPropertyDescription = std::static_pointer_cast<AutoPropertyDescription>(propertyDescription);
 
-  auto pvName = autoPropertyDescription->source;
-  auto processVariable = _controlSystemPVManager->getProcessVariable(pvName);
+    auto pvName = autoPropertyDescription->source;
+    auto processVariable = _controlSystemPVManager->getProcessVariable(pvName);
 
-  std::type_info const& valueType = processVariable->getValueType();
-  /*  TODO:
+    std::type_info const& valueType = processVariable->getValueType();
+    /*  TODO:
         - create functions "createDoocsArray" and "createDoocsSpectrum"
         - first use spectrum here for 1D, then switch to array (tests need to be
        adapted)
@@ -404,154 +355,154 @@ boost::shared_ptr<D_fct> DoocsPVFactory::autoCreate(std::shared_ptr<PropertyDesc
        (scalar for D_array and 1D)
     */
 
-  if(autoPropertyDescription->dataType == AutoPropertyDescription::DataType::Auto) {
-    autoPropertyDescription->deriveType(valueType);
-  }
+    if(autoPropertyDescription->dataType == AutoPropertyDescription::DataType::Auto) {
+      autoPropertyDescription->deriveType(valueType);
+    }
 
-  switch(autoPropertyDescription->dataType) {
-    case AutoPropertyDescription::DataType::Byte:
-      return typedCreateScalarOrArray<D_int, int32_t, D_bytearray, uint8_t>(
-          valueType, *processVariable, *autoPropertyDescription, DecoratorType::C_style_conversion);
-    case AutoPropertyDescription::DataType::Short:
-      return typedCreateScalarOrArray<D_int, int32_t, D_shortarray, int16_t>(
-          valueType, *processVariable, *autoPropertyDescription, DecoratorType::C_style_conversion);
-    case AutoPropertyDescription::DataType::Int:
-      return typedCreateScalarOrArray<D_int, int32_t, D_intarray, int32_t>(
-          valueType, *processVariable, *autoPropertyDescription, DecoratorType::C_style_conversion);
-    case AutoPropertyDescription::DataType::Long:
-      return typedCreateDoocsArray<int64_t, D_longarray>(AutoPropertyDescription(*autoPropertyDescription));
-    case AutoPropertyDescription::DataType::Float:
-      return typedCreateScalarOrArray<D_float, float, D_floatarray, float>(
-          valueType, *processVariable, *autoPropertyDescription, DecoratorType::C_style_conversion);
-    case AutoPropertyDescription::DataType::Double:
-      return typedCreateScalarOrArray<D_double, double, D_doublearray, double>(
-          valueType, *processVariable, *autoPropertyDescription, DecoratorType::C_style_conversion);
-    case AutoPropertyDescription::DataType::Auto:
-      if(valueType == typeid(std::string)) {
-        return typedCreateScalarOrArray<D_textUnifier, std::string, std::nullptr_t, std::nullptr_t>(
-            valueType, *processVariable, *autoPropertyDescription, DecoratorType::limiting);
-      }
-      throw std::logic_error("DoocsPVFactory does not implement a data type it should!");
+    switch(autoPropertyDescription->dataType) {
+      case AutoPropertyDescription::DataType::Byte:
+        return typedCreateScalarOrArray<D_int, int32_t, D_bytearray, uint8_t>(
+            valueType, *processVariable, *autoPropertyDescription, DecoratorType::C_style_conversion);
+      case AutoPropertyDescription::DataType::Short:
+        return typedCreateScalarOrArray<D_int, int32_t, D_shortarray, int16_t>(
+            valueType, *processVariable, *autoPropertyDescription, DecoratorType::C_style_conversion);
+      case AutoPropertyDescription::DataType::Int:
+        return typedCreateScalarOrArray<D_int, int32_t, D_intarray, int32_t>(
+            valueType, *processVariable, *autoPropertyDescription, DecoratorType::C_style_conversion);
+      case AutoPropertyDescription::DataType::Long:
+        return typedCreateDoocsArray<int64_t, D_longarray>(AutoPropertyDescription(*autoPropertyDescription));
+      case AutoPropertyDescription::DataType::Float:
+        return typedCreateScalarOrArray<D_float, float, D_floatarray, float>(
+            valueType, *processVariable, *autoPropertyDescription, DecoratorType::C_style_conversion);
+      case AutoPropertyDescription::DataType::Double:
+        return typedCreateScalarOrArray<D_double, double, D_doublearray, double>(
+            valueType, *processVariable, *autoPropertyDescription, DecoratorType::C_style_conversion);
+      case AutoPropertyDescription::DataType::Auto:
+        if(valueType == typeid(std::string)) {
+          return typedCreateScalarOrArray<D_textUnifier, std::string, std::nullptr_t, std::nullptr_t>(
+              valueType, *processVariable, *autoPropertyDescription, DecoratorType::limiting);
+        }
+        throw std::logic_error("DoocsPVFactory does not implement a data type it should!");
+    }
+
+    // Make compiler happy
+    throw std::logic_error("Should not be reached");
   }
 
-  // Make compiler happy
-  throw std::logic_error("Should not be reached");
-}
+  template<class DOOCS_PRIMITIVE_T, class DOOCS_T>
+  boost::shared_ptr<D_fct> DoocsPVFactory::typedCreateDoocsArray(AutoPropertyDescription const& propertyDescription) {
+    auto processVariable = _controlSystemPVManager->getProcessVariable(propertyDescription.source);
 
-template<class DOOCS_PRIMITIVE_T, class DOOCS_T>
-boost::shared_ptr<D_fct> DoocsPVFactory::typedCreateDoocsArray(AutoPropertyDescription const& propertyDescription) {
-  auto processVariable = _controlSystemPVManager->getProcessVariable(propertyDescription.source);
+    // the DoocsProcessScalar needs the real ProcessScalar type, not just
+    // ProcessVariable
+    boost::shared_ptr<NDRegisterAccessor<DOOCS_PRIMITIVE_T>> processArray;
+    if(typeid(DOOCS_PRIMITIVE_T) == processVariable->getValueType()) {
+      processArray = boost::dynamic_pointer_cast<ChimeraTK::NDRegisterAccessor<DOOCS_PRIMITIVE_T>>(processVariable);
+    }
+    else {
+      processArray = getDecorator<DOOCS_PRIMITIVE_T>(processVariable, DecoratorType::C_style_conversion);
+    }
 
-  // the DoocsProcessScalar needs the real ProcessScalar type, not just
-  // ProcessVariable
-  boost::shared_ptr<NDRegisterAccessor<DOOCS_PRIMITIVE_T>> processArray;
-  if(typeid(DOOCS_PRIMITIVE_T) == processVariable->getValueType()) {
-    processArray = boost::dynamic_pointer_cast<ChimeraTK::NDRegisterAccessor<DOOCS_PRIMITIVE_T>>(processVariable);
-  }
-  else {
-    processArray = getDecorator<DOOCS_PRIMITIVE_T>(processVariable, DecoratorType::C_style_conversion);
-  }
+    ///@todo FIXME Add the decorator type as option  to the array description, and
+    /// only use C_style_conversion as default
+    boost::shared_ptr<D_fct> doocsPV(
+        new DoocsProcessArray<DOOCS_T, DOOCS_PRIMITIVE_T>(_eqFct, propertyDescription.name, processArray, _updater));
 
-  ///@todo FIXME Add the decorator type as option  to the array description, and
-  /// only use C_style_conversion as default
-  boost::shared_ptr<D_fct> doocsPV(
-      new DoocsProcessArray<DOOCS_T, DOOCS_PRIMITIVE_T>(_eqFct, propertyDescription.name, processArray, _updater));
+    // set read only mode if configures in the xml file or for output variables
+    if(!processVariable->isWriteable() || !propertyDescription.isWriteable) {
+      doocsPV->set_ro_access();
+    }
 
-  // set read only mode if configures in the xml file or for output variables
-  if(!processVariable->isWriteable() || !propertyDescription.isWriteable) {
-    doocsPV->set_ro_access();
-  }
+    // publish via ZeroMQ if configured in the xml file
+    if(propertyDescription.publishZMQ) {
+      boost::dynamic_pointer_cast<DoocsProcessArray<DOOCS_T, DOOCS_PRIMITIVE_T>>(doocsPV)->publishZeroMQ();
+    }
 
-  // publish via ZeroMQ if configured in the xml file
-  if(propertyDescription.publishZMQ) {
-    boost::dynamic_pointer_cast<DoocsProcessArray<DOOCS_T, DOOCS_PRIMITIVE_T>>(doocsPV)->publishZeroMQ();
-  }
+    // set data matching mode (need to call before setMacroPulseNumberSource, as the mode is checked there)
+    boost::dynamic_pointer_cast<DoocsProcessArray<DOOCS_T, DOOCS_PRIMITIVE_T>>(doocsPV)
+        ->_consistencyGroup.setMatchingMode(propertyDescription.dataMatching);
 
-  // set data matching mode (need to call before setMacroPulseNumberSource, as the mode is checked there)
-  boost::dynamic_pointer_cast<DoocsProcessArray<DOOCS_T, DOOCS_PRIMITIVE_T>>(doocsPV)
-      ->_consistencyGroup.setMatchingMode(propertyDescription.dataMatching);
-
-  // set macro pulse number source, if configured
-  if(propertyDescription.macroPulseNumberSource.size() > 0) {
-    auto mpnSource = _controlSystemPVManager->getProcessVariable(propertyDescription.macroPulseNumberSource);
-    auto mpnDecorated = getDecorator<int64_t>(mpnSource, DecoratorType::C_style_conversion);
-    if(mpnDecorated->getNumberOfSamples() != 1) {
-      throw ChimeraTK::logic_error("The property '" + mpnDecorated->getName() +
-          "' is used as a macro pulse number source, but it has an array "
-          "length of " +
-          std::to_string(mpnDecorated->getNumberOfSamples()) + ". Length must be exactly 1");
-    }
-    if(!mpnDecorated->isReadable()) {
-      throw ChimeraTK::logic_error("The property '" + mpnDecorated->getName() +
-          "' is used as a macro pulse number source, but it is not readable.");
-    }
-    boost::dynamic_pointer_cast<DoocsProcessArray<DOOCS_T, DOOCS_PRIMITIVE_T>>(doocsPV)->setMacroPulseNumberSource(
-        mpnDecorated);
-  }
+    // set macro pulse number source, if configured
+    if(propertyDescription.macroPulseNumberSource.size() > 0) {
+      auto mpnSource = _controlSystemPVManager->getProcessVariable(propertyDescription.macroPulseNumberSource);
+      auto mpnDecorated = getDecorator<int64_t>(mpnSource, DecoratorType::C_style_conversion);
+      if(mpnDecorated->getNumberOfSamples() != 1) {
+        throw ChimeraTK::logic_error("The property '" + mpnDecorated->getName() +
+            "' is used as a macro pulse number source, but it has an array "
+            "length of " +
+            std::to_string(mpnDecorated->getNumberOfSamples()) + ". Length must be exactly 1");
+      }
+      if(!mpnDecorated->isReadable()) {
+        throw ChimeraTK::logic_error("The property '" + mpnDecorated->getName() +
+            "' is used as a macro pulse number source, but it is not readable.");
+      }
+      boost::dynamic_pointer_cast<DoocsProcessArray<DOOCS_T, DOOCS_PRIMITIVE_T>>(doocsPV)->setMacroPulseNumberSource(
+          mpnDecorated);
+    }
 
-  return doocsPV;
-}
-
-// template specialisation for cases with no matching DOOCS array type (e.g.
-// string)
-template<>
-boost::shared_ptr<D_fct> DoocsPVFactory::typedCreateDoocsArray<std::nullptr_t, std::nullptr_t>(
-    AutoPropertyDescription const&) {
-  throw std::invalid_argument("Type not supported as an array");
-}
-
-boost::shared_ptr<D_fct> DoocsPVFactory::createDoocsArray(
-    std::shared_ptr<AutoPropertyDescription> const& propertyDescription) {
-  if(propertyDescription->dataType == AutoPropertyDescription::DataType::Auto) {
-    // leave the desision which array to produce to the auto creation algorithm.
-    // We need it there anyway
-    // FIXME: This does not produce arrays of length 1 because it will produce a
-    // scalar
-    return autoCreate(propertyDescription);
-  }
-  else if(propertyDescription->dataType == AutoPropertyDescription::DataType::Byte) {
-    return typedCreateDoocsArray<uint8_t, D_bytearray>(*propertyDescription);
-  }
-  else if(propertyDescription->dataType == AutoPropertyDescription::DataType::Short) {
-    return typedCreateDoocsArray<int16_t, D_shortarray>(*propertyDescription);
-  }
-  else if(propertyDescription->dataType == AutoPropertyDescription::DataType::Int) {
-    return typedCreateDoocsArray<int32_t, D_intarray>(*propertyDescription);
-  }
-  else if(propertyDescription->dataType == AutoPropertyDescription::DataType::Long) {
-    return typedCreateDoocsArray<int64_t, D_longarray>(*propertyDescription);
-  }
-  else if(propertyDescription->dataType == AutoPropertyDescription::DataType::Float) {
-    return typedCreateDoocsArray<float, D_floatarray>(*propertyDescription);
-  }
-  else if(propertyDescription->dataType == AutoPropertyDescription::DataType::Double) {
-    return typedCreateDoocsArray<double, D_doublearray>(*propertyDescription);
-  }
-  else {
-    throw std::logic_error("DoocsPVFactory does not implement a data type it should!");
+    return doocsPV;
   }
-}
 
-boost::shared_ptr<D_fct> DoocsPVFactory::create(std::shared_ptr<PropertyDescription> const& propertyDescription) {
-  auto& requestedType = propertyDescription->type();
-  if(requestedType == typeid(AutoPropertyDescription)) {
-    return autoCreate(propertyDescription);
-  }
-  else if(requestedType == typeid(SpectrumDescription)) {
-    return createDoocsSpectrum(*std::static_pointer_cast<SpectrumDescription>(propertyDescription));
-  }
-  else if(requestedType == typeid(XyDescription)) {
-    return createXy(*std::static_pointer_cast<XyDescription>(propertyDescription));
-  }
-  else if(requestedType == typeid(IfffDescription)) {
-    return createIfff(*std::static_pointer_cast<IfffDescription>(propertyDescription));
+  // template specialisation for cases with no matching DOOCS array type (e.g.
+  // string)
+  template<>
+  boost::shared_ptr<D_fct> DoocsPVFactory::typedCreateDoocsArray<std::nullptr_t, std::nullptr_t>(
+      AutoPropertyDescription const&) {
+    throw std::invalid_argument("Type not supported as an array");
   }
-  else if(requestedType == typeid(AutoPropertyDescription)) {
-    return createDoocsArray(std::static_pointer_cast<AutoPropertyDescription>(propertyDescription));
+
+  boost::shared_ptr<D_fct> DoocsPVFactory::createDoocsArray(
+      std::shared_ptr<AutoPropertyDescription> const& propertyDescription) {
+    if(propertyDescription->dataType == AutoPropertyDescription::DataType::Auto) {
+      // leave the desision which array to produce to the auto creation algorithm.
+      // We need it there anyway
+      // FIXME: This does not produce arrays of length 1 because it will produce a
+      // scalar
+      return autoCreate(propertyDescription);
+    }
+    else if(propertyDescription->dataType == AutoPropertyDescription::DataType::Byte) {
+      return typedCreateDoocsArray<uint8_t, D_bytearray>(*propertyDescription);
+    }
+    else if(propertyDescription->dataType == AutoPropertyDescription::DataType::Short) {
+      return typedCreateDoocsArray<int16_t, D_shortarray>(*propertyDescription);
+    }
+    else if(propertyDescription->dataType == AutoPropertyDescription::DataType::Int) {
+      return typedCreateDoocsArray<int32_t, D_intarray>(*propertyDescription);
+    }
+    else if(propertyDescription->dataType == AutoPropertyDescription::DataType::Long) {
+      return typedCreateDoocsArray<int64_t, D_longarray>(*propertyDescription);
+    }
+    else if(propertyDescription->dataType == AutoPropertyDescription::DataType::Float) {
+      return typedCreateDoocsArray<float, D_floatarray>(*propertyDescription);
+    }
+    else if(propertyDescription->dataType == AutoPropertyDescription::DataType::Double) {
+      return typedCreateDoocsArray<double, D_doublearray>(*propertyDescription);
+    }
+    else {
+      throw std::logic_error("DoocsPVFactory does not implement a data type it should!");
+    }
   }
-  else {
-    throw std::invalid_argument("Sorry, your type is not supported yet.");
+
+  boost::shared_ptr<D_fct> DoocsPVFactory::create(std::shared_ptr<PropertyDescription> const& propertyDescription) {
+    auto& requestedType = propertyDescription->type();
+    if(requestedType == typeid(AutoPropertyDescription)) {
+      return autoCreate(propertyDescription);
+    }
+    else if(requestedType == typeid(SpectrumDescription)) {
+      return createDoocsSpectrum(*std::static_pointer_cast<SpectrumDescription>(propertyDescription));
+    }
+    else if(requestedType == typeid(XyDescription)) {
+      return createXy(*std::static_pointer_cast<XyDescription>(propertyDescription));
+    }
+    else if(requestedType == typeid(IfffDescription)) {
+      return createIfff(*std::static_pointer_cast<IfffDescription>(propertyDescription));
+    }
+    else if(requestedType == typeid(AutoPropertyDescription)) {
+      return createDoocsArray(std::static_pointer_cast<AutoPropertyDescription>(propertyDescription));
+    }
+    else {
+      throw std::invalid_argument("Sorry, your type is not supported yet.");
+    }
   }
-}
 
 } // namespace ChimeraTK