diff --git a/modules/seq/alg/pymod/wrap_seq_alg.cc b/modules/seq/alg/pymod/wrap_seq_alg.cc
index 4ed94737c66b2bec8a0b9122858b3b5efd55cc62..8007201a917311bf6bae57bc0d2332336044a1c3 100644
--- a/modules/seq/alg/pymod/wrap_seq_alg.cc
+++ b/modules/seq/alg/pymod/wrap_seq_alg.cc
@@ -78,6 +78,15 @@ list VarMapGetData(const VarianceMapPtr v_map) {
 list DistToMeanGetData(const Dist2MeanPtr d2m) {
   return GetList(*d2m, d2m->GetNumResidues(), d2m->GetNumStructures());
 }
+
+void AAPseudoCountsSimple(ProfileHandle& profile) {
+  AddAAPseudoCounts(profile);
+}
+
+void AAPseudoCountsAngermueller(ProfileHandle& profile, const ContextProfileDB& db) {
+  AddAAPseudoCounts(profile, db);
+}
+
 } // anon ns
 ////////////////////////////////////////////////////////////////////
 
@@ -228,7 +237,28 @@ void export_distance_analysis()
 ////////////////////////////////////////////////////////////////////
 // algorithms involving hmms
 void export_hmm_algorithms() {
-  def("AddAAPseudoCounts", &AddAAPseudoCounts, (arg("profile")));
+
+  class_<ContextProfile>("ContextProfile", init<int>())
+    .def("SetWeight",&ContextProfile::SetWeight, (arg("pos"), arg("olc"), arg("weight")))
+    .def("SetPseudoCount",&ContextProfile::SetPseudoCount, (arg("olc"), arg("count")))
+    .def("SetBias",&ContextProfile::SetBias, (arg("bias")))
+    .def("GetWeight", &ContextProfile::GetWeight, (arg("pos"), arg("olc")))
+    .def("GetPseudoCount", &ContextProfile::GetPseudoCount,(arg("olc")))
+    .def("GetBias", &ContextProfile::GetBias)
+    .def("GetLength", &ContextProfile::GetLength)
+  ;
+
+  class_<ContextProfileDB, ContextProfileDBPtr>("ContextProfileDB", init<>())
+    .def("__len__",&ContextProfileDB::size)
+    .def("__getitem__",&ContextProfileDB::at,return_value_policy<reference_existing_object>(), (arg("idx")))
+    .def("Save", &ContextProfileDB::Save, (arg("filename")))
+    .def("Load", &ContextProfileDB::Load, (arg("filename"))).staticmethod("Load")
+    .def("FromCRF", &ContextProfileDB::FromCRF, (arg("filename"))).staticmethod("FromCRF")
+    .def("AddProfile", &ContextProfileDB::AddProfile, (arg("profile")))
+  ;
+
+  def("AddAAPseudoCounts", &AAPseudoCountsSimple, (arg("profile")));
+  def("AddAAPseudoCounts", &AAPseudoCountsAngermueller, (arg("profile"), arg("context_profile_db")));
   def("AddTransitionPseudoCounts", &AddTransitionPseudoCounts, (arg("profile")));
   def("HMMScore", &HMMScore, (arg("profile_0"), arg("profile_1"), arg("alignment"),
                               arg("s_0_idx"), arg("s_1_idx")));
diff --git a/modules/seq/alg/src/hmm_pseudo_counts.cc b/modules/seq/alg/src/hmm_pseudo_counts.cc
index ab911d62a6fb79548740898cd0c0b2f43ecfc939..63bece4adff6af0da2bd71fd6b0b4c001f261dc4 100644
--- a/modules/seq/alg/src/hmm_pseudo_counts.cc
+++ b/modules/seq/alg/src/hmm_pseudo_counts.cc
@@ -1,9 +1,19 @@
 #include <ost/seq/alg/hmm_pseudo_counts.hh>
 
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/filesystem/convenience.hpp>
+#include <boost/filesystem/fstream.hpp>
+#include <boost/iostreams/filter/gzip.hpp>
+#include <boost/iostreams/filtering_stream.hpp>
 
-namespace {
+#include <limits>
+#include <cmath>
+
+#include <ost/string_ref.hh>
 
 
+namespace {
+
   // to mimic the HHblits behaviour, R is based on the Gonnet substitution matrix
   // every entry R[a][b] corresponds to P(a|b) 
   const Real R[20][20] = {
@@ -34,6 +44,378 @@ namespace {
 namespace ost{ namespace seq{ namespace alg{
 
 
+void ContextProfileDB::Save(const String& filename) const {
+
+  if(profiles_.empty()) {
+    throw Error("Cannot save empty ContextProfileDB");
+  }
+
+  std::ofstream out_stream(filename.c_str(), std::ios::binary);
+
+  if (!out_stream){
+    std::stringstream ss;
+    ss << "the file '" << filename << "' could not be opened.";
+    throw Error(ss.str());
+  }
+
+  std::vector<Real> data;
+  for(auto it = profiles_.begin(); it != profiles_.end(); ++it) {
+    const std::vector<Real>& p_data = it->GetData();
+    data.insert(data.end(), p_data.begin(), p_data.end());
+  }
+
+  uint32_t magic_number = 424242;
+  out_stream.write(reinterpret_cast<char*>(&magic_number), sizeof(uint32_t));
+
+  uint8_t version = 1;
+  out_stream.write(reinterpret_cast<char*>(&version), sizeof(uint8_t));
+
+  uint32_t length = profiles_[0].GetLength();
+  out_stream.write(reinterpret_cast<char*>(&length),sizeof(uint32_t));
+
+  uint32_t n_profiles = profiles_.size();
+  out_stream.write(reinterpret_cast<char*>(&n_profiles),sizeof(uint32_t));
+
+  uint32_t data_size = data.size();
+  out_stream.write(reinterpret_cast<char*>(&data_size),sizeof(uint32_t));
+
+  out_stream.write(reinterpret_cast<char*>(&data[0]), data_size*sizeof(Real));
+}
+
+
+ContextProfileDBPtr ContextProfileDB::Load(const String& filename) {
+
+  std::ifstream in_stream(filename.c_str(), std::ios::binary);
+  if (!in_stream){
+    std::stringstream ss;
+    ss << "the file '" << filename << "' could not be opened.";
+    throw Error(ss.str());
+  }
+
+  uint32_t magic_number;
+  in_stream.read(reinterpret_cast<char*>(&magic_number), sizeof(uint32_t));
+
+  if(magic_number != 424242) {
+    std::stringstream ss;
+    ss << "Could not read magic number in " << filename<<". Either the file ";
+    ss << "is corrupt or does not contain a ContextProfileDB.";
+    throw Error(ss.str());
+  }
+
+  uint8_t version;
+  in_stream.read(reinterpret_cast<char*>(&version), sizeof(uint8_t));
+  if(version != 1) {
+    std::stringstream ss;
+    ss << "ContextProfileDB in " << filename << " is of version " << version;
+    ss << " but only version 1 can be read.";
+    throw Error(ss.str());
+  }
+
+  uint32_t length;
+  in_stream.read(reinterpret_cast<char*>(&length), sizeof(uint32_t));
+
+  uint32_t n_profiles;
+  in_stream.read(reinterpret_cast<char*>(&n_profiles),sizeof(uint32_t));
+
+  uint32_t data_size;
+  in_stream.read(reinterpret_cast<char*>(&data_size), sizeof(uint32_t));
+
+  std::vector<Real> data(data_size, 0.0);
+  in_stream.read(reinterpret_cast<char*>(&data[0]), data_size*sizeof(Real));
+
+  ContextProfileDBPtr db(new ContextProfileDB);
+
+  int data_loc = 0;
+  int profile_data_size = ContextProfile::DataSize(length);
+  for(uint i = 0; i < n_profiles; ++i, data_loc+=profile_data_size) {
+    db->profiles_.push_back(ContextProfile(length, &data[data_loc]));
+  }
+
+  return db;
+}
+
+ContextProfileDBPtr ContextProfileDB::FromCRF(const String& filename) {
+
+  // open it up
+  boost::iostreams::filtering_stream<boost::iostreams::input> in;
+  boost::filesystem::ifstream stream(filename);
+  if(!stream) {
+    throw Error("Could not open " + filename);
+  }
+  // add unzip if necessary
+  if(boost::iequals(".gz", boost::filesystem::extension(filename))) {
+    in.push(boost::iostreams::gzip_decompressor());
+  }
+  in.push(stream);
+
+  // tmp. storage
+  std::string line;
+  ost::StringRef sline;
+  std::vector<ost::StringRef> chunks;
+
+  int size = -1;
+  int length = -1;
+
+  while(std::getline(in, line)) {
+
+    sline = ost::StringRef(line.c_str(), line.length());
+
+    if(sline.length()>=8 &&
+       sline.substr(0, 8) == ost::StringRef("CrfState", 8)) {
+      throw Error("Require to read SIZE and LENG before first ContextProfile");
+    }
+
+    if(sline.length()>4 && 
+        sline.substr(0, 4) == ost::StringRef("SIZE", 4)) {
+      chunks = sline.split();
+      if(chunks.size() != 2) {
+        throw Error("Badly formatted line: " + line);
+      }
+      std::pair<bool, int> s = chunks[1].to_int();
+      if (!s.first) {
+        throw Error("Badly formatted line: " + line);
+      }
+      size = s.second;
+    }
+
+    if(sline.length()>4 && 
+        sline.substr(0, 4) == ost::StringRef("LENG", 4)) {
+      chunks = sline.split();
+      if(chunks.size() != 2) {
+        throw Error("Badly formatted line: " + line);
+      }
+      std::pair<bool, int> l = chunks[1].to_int();
+      if (!l.first) {
+        throw Error("Badly formatted line: " + line);
+      }
+      length = l.second;
+    }
+
+    if(size != -1 && length != -1) {
+      break;
+    }
+  }
+
+  ContextProfileDBPtr db(new ContextProfileDB);
+  bool in_crf_state=false;
+  ContextProfile current_context_profile(length);
+
+  // stuff that we read for every context profile
+  Real cp_bias = std::numeric_limits<Real>::quiet_NaN();
+  int cp_leng = -1;
+  int cp_alph = -1;
+  std::vector<char> cp_olcs;
+  std::vector<std::vector<Real> > cp_weights;
+  std::vector<Real> cp_pc;
+
+  while(std::getline(in, line)) {
+
+    sline = ost::StringRef(line.c_str(), line.length());
+
+    if(!in_crf_state) {
+      if(sline.length()>=8 &&
+         sline.substr(0, 8) == ost::StringRef("CrfState", 8)) {
+        in_crf_state = true;
+      }
+      continue;
+    }
+
+    if(sline.length()>4 && 
+        sline.substr(0, 4) == ost::StringRef("BIAS", 4)) {
+      chunks = sline.split();
+      if(chunks.size() != 2) {
+        throw Error("Badly formatted line: " + line);
+      }
+      std::pair<bool, Real> b = chunks[1].to_float();
+      if (!b.first) {
+        throw Error("Badly formatted line: " + line);
+      }
+      cp_bias = b.second;
+      continue;
+    }
+
+    if(sline.length()>4 && 
+        sline.substr(0, 4) == ost::StringRef("LENG", 4)) {
+      chunks = sline.split();
+      if(chunks.size() != 2) {
+        throw Error("Badly formatted line: " + line);
+      }
+      std::pair<bool, int> l = chunks[1].to_int();
+      if (!l.first) {
+        throw Error("Badly formatted line: " + line);
+      }
+      cp_leng = l.second;
+      continue;
+    }
+
+    if(sline.length()>4 && 
+        sline.substr(0, 4) == ost::StringRef("ALPH", 4)) {
+
+      chunks = sline.split();
+      if(chunks.size() != 2) {
+        throw Error("Badly formatted line: " + line);
+      }
+      std::pair<bool, int> a = chunks[1].to_int();
+      if (!a.first) {
+        throw Error("Badly formatted line: " + line);
+      }
+      cp_alph = a.second;
+      continue;
+    }
+
+    if(sline.length()>7 && 
+        sline.substr(0, 7) == ost::StringRef("WEIGHTS", 7)) {
+
+      if(cp_alph == -1 || cp_leng == -1) {
+        throw Error("Require LENG and ALPH before reading WEIGHTS in entry");
+      }
+
+      chunks = sline.split();
+      if(chunks.size() != static_cast<uint>(cp_alph+1)) {
+        throw Error("Badly formatted line: " + line);
+      }
+      for(int i = 0; i < cp_alph; ++i) {
+        cp_olcs.push_back(chunks[i+1][0]);
+      }
+
+      cp_weights = std::vector<std::vector<Real> >(cp_leng, std::vector<Real>());
+
+      // the next cp_leng lines should be the weights
+      for(int i = 0; i < cp_leng; ++i) {
+        if(!std::getline(in, line)) {
+          throw Error("Failed to load all WEIGHTS in entry");
+        }
+        sline = ost::StringRef(line.c_str(), line.length());
+
+        if(sline.length()>=2 &&
+           sline.substr(0, 2) == ost::StringRef("//", 2)) {
+          throw Error("Arrived at end of entry before reading all weights");
+        }        
+
+        chunks = sline.split();
+
+        if(chunks.size() != static_cast<uint>(cp_alph + 1)) {
+          throw Error("Failed to load all WEIGHTS in entry");
+        }
+
+        // read the position of the weights. if it can't be parsed to int, 
+        // something is fishy (e.g. already another key word)
+        std::pair<bool, int> p = chunks[0].to_int();
+        if(!p.first) {
+          throw Error("Badly formatted line: " + line);
+        }
+
+        if(p.second < 1 || p.second > cp_leng) {
+          throw Error("Badly formatted line: " + line);
+        }
+
+        for(int j = 0; j < cp_alph; ++j) {
+          std::pair<bool, Real> w = chunks[j+1].to_float();
+          if(!w.first) {
+            throw Error("Badly formatted line: " + line);
+          }
+          cp_weights[p.second-1].push_back(0.001 * w.second);
+        }
+      }
+      continue;
+    }
+
+    if(sline.length()>2 && 
+        sline.substr(0, 2) == ost::StringRef("PC", 2)) {
+
+      chunks = sline.split();
+      if(chunks.size() != static_cast<uint>(cp_alph+1)) {
+        throw Error("Badly formatted line: " + line);
+      }
+      for(int i = 0; i < cp_alph; ++i) {
+        std::pair<bool, Real> w = chunks[i+1].to_float();
+        if(!w.first) {
+          throw Error("Badly formatted line: " + line);
+        }
+        cp_pc.push_back(0.001*w.second);
+      }
+    }
+
+    if(sline.length()>=2 &&
+       sline.substr(0, 2) == ost::StringRef("//", 2)) {
+
+      //check if data is OK
+      if(std::isnan(cp_bias)) {
+        throw Error("Oberved entry without BIAS value");
+      }
+      if(cp_leng == -1) {
+        throw Error("Observed entry without LENG value");
+      }
+      if(cp_leng != length) {
+        throw Error("Require all entries to be of same length as specified in file header");
+      }
+      if(cp_alph == -1) {
+        throw Error("Observed Entry without ALPH value");
+      }
+      if(cp_alph != 20) {
+        throw Error("Expect ALPH to be 20 for all entries");
+      }
+      if(cp_olcs.empty()) {
+        throw Error("Observed Entry without WEIGHTS value");          
+      }
+      if(cp_olcs.size() != 20) {
+        throw Error("Expect exactly 20 items after WEIGHTS key word");
+      }
+      for(int i = 0; i < length; ++i) {
+        if(cp_weights[i].size() != 20) {
+          throw Error("Observed Entry with != 20 weight values for each pos");
+        }
+      }
+      if(cp_pc.size() != 20) {
+        throw Error("Observed Entry with != 20 PC values");
+      }
+
+      // fill data in context profile
+      current_context_profile = ContextProfile(length);
+      current_context_profile.SetBias(cp_bias);
+      for(int i = 0; i < length; ++i) {
+        for(int j = 0; j < 20; ++j) {
+          current_context_profile.SetWeight(i, cp_olcs[j], cp_weights[i][j]);
+        }
+      }
+
+      // magic rescaling of context profiles as implemented in hhblits
+      Real max = -std::numeric_limits<Real>::max();
+      for(int i = 0; i < 20; ++i) {
+        max = std::max(max, cp_pc[i]);
+      }
+
+      Real sum = 0.0;
+      for(int i = 0; i < 20; ++i) {
+        sum += std::exp(cp_pc[i]-max);
+      }
+
+      Real tmp = max + std::log(sum);
+      for(int i = 0; i < 20; ++i) {
+        current_context_profile.SetPseudoCount(cp_olcs[i], std::exp(cp_pc[i]-tmp));
+      }
+
+      db->AddProfile(current_context_profile);
+
+      // invalidate all variables for a next profile
+      cp_bias = std::numeric_limits<Real>::quiet_NaN();
+      cp_leng = -1;
+      cp_alph = -1;
+      cp_olcs = std::vector<char>();
+      cp_weights = std::vector<std::vector<Real> >();
+      cp_pc = std::vector<Real>();
+      in_crf_state = false;
+    }      
+  }
+
+  if(db->size() != static_cast<size_t>(size)) {
+    throw Error("Number of read entries does not correspond to what was promised in the header");
+  }
+
+  return db;
+}
+
+
 void AddTransitionPseudoCounts(ost::seq::ProfileHandle& profile) {
 
   // a priori probabilities estimated with default values of HHblits
@@ -137,4 +519,134 @@ void AddAAPseudoCounts(ost::seq::ProfileHandle& profile) {
   profile.SetNullModel(new_null_model);
 }
 
+
+void AddAAPseudoCounts(ost::seq::ProfileHandle& profile,
+                       const ContextProfileDB& db) {
+
+  ////////////////////
+  // do frequencies //
+  ////////////////////
+  std::vector<Real> cp_scores(db.size(), 0.0);
+  int cp_length = db.profile_length();
+  if(cp_length % 2 != 1) {
+    throw Error("Length of profiles in db must be an odd number");
+  } 
+  // extension from center to both directions
+  int cp_ext = (cp_length - 1) / 2;
+
+  std::vector<std::vector<Real> > 
+  count_profile(profile.size(), std::vector<Real>(20, 0.0));
+  std::vector<std::vector<Real> > 
+  context_profile(profile.size(), std::vector<Real>(20, 0.0));
+
+  // fill counts profile
+  for(size_t col_idx = 0; col_idx < profile.size(); ++col_idx) {
+    HMMDataPtr hmm_data = profile[col_idx].GetHMMData(); 
+    Real neff = hmm_data->GetNeff();
+    Real* col_freq = profile[col_idx].freqs_begin();
+    std::vector<Real>& counts = count_profile[col_idx];
+    for(int i = 0; i < 20; ++i) {
+      counts[i] = col_freq[i] * neff;
+    }
+  }
+
+  // process columns
+  for(size_t col_idx = 0; col_idx < count_profile.size(); ++col_idx) {
+    int min = std::max(0, static_cast<int>(col_idx) - cp_ext);
+    int max = std::min(col_idx + cp_ext, count_profile.size() - 1); 
+    int cp_min = static_cast<int>(col_idx) < cp_ext ? 
+                 std::abs(static_cast<int>(col_idx) - cp_ext) : 0; 
+    Real max_score = -std::numeric_limits<Real>::max();
+    // estimate score for each context profile in db
+    for(size_t cp_idx = 0; cp_idx < db.size(); ++cp_idx) {
+      const ContextProfile& cp = db[cp_idx];
+      Real score = cp.GetBias();
+      for(int i = min, j = cp_min; i<=max; ++i, ++j) {
+        const Real* weights = cp.GetWeights(j);
+        const std::vector<Real>& counts = count_profile[i];
+        for(int k = 0; k < 20; ++k) {
+          score += weights[k] * counts[k];
+        }
+      }
+      max_score = std::max(max_score, score);
+      cp_scores[cp_idx] = score;
+    }
+
+    // same in hhblits code: log-sum-exp trick to avoid overflows
+    Real summed_exp_score = 0.0;
+    for(size_t i = 0; i < cp_scores.size(); ++i) {
+      summed_exp_score += std::exp(cp_scores[i]-max_score);
+    }
+    Real tmp = max_score - std::log(summed_exp_score);
+
+    std::vector<Real>& col_freq = context_profile[col_idx];
+    for(size_t cp_idx = 0; cp_idx < db.size(); ++cp_idx) {
+      Real w = std::exp(cp_scores[cp_idx]-tmp);
+      const Real* cp_pc = db[cp_idx].GetPseudoCounts();
+      for(int i = 0; i < 20; ++i) {
+        col_freq[i] += w*cp_pc[i];
+      }
+    }
+    // normalize
+    Real sum = 0.0;
+    for(int i = 0; i < 20; ++i) {
+      sum += col_freq[i];
+    }
+    Real norm_factor = 1.0 / sum;
+    for(int i = 0; i < 20; ++i) {
+      col_freq[i] *= norm_factor;
+    }
+  }
+
+  // mix together count and context profile to get final frequencies
+  for(size_t col_idx = 0; col_idx < profile.size(); ++col_idx) {
+    // tau estimated as in hhblits in diversity dependent mode:
+    // tau = a/(1+((Neff[i]-1)/b)^c) with default values a=0.9, b=4.0, c=1.0
+    // this is the equation they write in HHblits when you display the help
+    // (Neff[i]-1) got rid of the -1. Well, that's how HHblits implements it
+    Real neff = profile[col_idx].GetHMMData()->GetNeff();
+    Real tau = std::min(1.0, 0.9 / (1.0 + (neff) / 4.0));
+    Real* col_freq = profile[col_idx].freqs_begin();
+    const std::vector<Real>& counts = count_profile[col_idx];
+    const std::vector<Real>& context = context_profile[col_idx];
+    for(int i = 0; i < 20; ++i) {
+      col_freq[i] = tau*context[i] + (1.-tau)*counts[i]/neff;
+    }
+  }
+
+
+  /////////////////////////
+  // do null_frequencies //
+  /////////////////////////
+  Real mixing_factor = 100.0 / profile.GetNeff();
+  const Real* current_null_freq = profile.GetNullModel().freqs_begin();
+  Real null_freq[20];
+  for(int i = 0; i < 20; ++i) {
+    null_freq[i] = current_null_freq[i] * mixing_factor;
+  } 
+  for(size_t i = 0; i < profile.size(); ++i) {
+    const Real* freq = profile[i].freqs_begin();
+    for(int j = 0; j < 20; ++j) {
+      null_freq[j] += freq[j]; 
+    }
+  }
+  // normalize
+  Real summed_p = 0.0;
+  for(int i = 0; i < 20; ++i) {
+    summed_p += null_freq[i];
+  }
+  Real factor = 1.0/summed_p;
+  for(int i = 0; i < 20; ++i) {
+    null_freq[i] *= factor;
+  }
+  // create new nullmodel and set it
+  ost::seq::ProfileColumn new_null_model;
+  Real* new_null_freq = new_null_model.freqs_begin();
+  for(int i = 0; i < 20; ++i) {
+    new_null_freq[i] = null_freq[i];
+  }
+  profile.SetNullModel(new_null_model);
+}
+
+
 }}} // ns
diff --git a/modules/seq/alg/src/hmm_pseudo_counts.hh b/modules/seq/alg/src/hmm_pseudo_counts.hh
index 48506f25dde5b673aa10c2cd9be32d9845b5038d..6042916dbe9b6de060d968353f39bee05df3d59b 100644
--- a/modules/seq/alg/src/hmm_pseudo_counts.hh
+++ b/modules/seq/alg/src/hmm_pseudo_counts.hh
@@ -24,10 +24,147 @@
 
 namespace ost{ namespace seq{ namespace alg{
 
+
+class ContextProfileDB;
+typedef boost::shared_ptr<ContextProfileDB> ContextProfileDBPtr;
+
+
+class ContextProfile{
+
+public:
+
+ContextProfile(int length): length_(length), 
+                            data_(ContextProfile::DataSize(length), 0.0) { }
+
+ContextProfile(int length, Real* data): length_(length), 
+                            data_(ContextProfile::DataSize(length), 0.0) {
+  memcpy(&data_[0], data, data_.size() * sizeof(Real));
+}
+
+void SetWeight(int pos, char olc, Real weight) {
+  if(pos >= length_) {
+  	throw Error("Tried to access invalid pos in ContextProfile");
+  }
+  int olc_idx = ProfileColumn::GetIndex(olc);
+  if(olc_idx != -1) {
+    data_[pos*20 + olc_idx] = weight;
+  } else {
+  	throw Error("Invalid one letter code in ContextProfile");
+  }
+}
+
+void SetPseudoCount(char olc, Real count) {
+  int olc_idx = ProfileColumn::GetIndex(olc);
+  if(olc_idx != -1) {
+    data_[length_*20 + olc_idx] = count;
+  } else {
+  	throw Error("Invalid one letter code in ContextProfile");
+  }
+}
+
+void SetBias(Real bias) { data_.back() = bias; }
+
+const Real* GetWeights(int pos) const{ 
+  if(pos >= length_) {
+  	throw Error("Tried to access invalid pos in ContextProfile");
+  }
+  return &data_[pos*20];
+}
+
+Real GetWeight(int pos, char olc) {
+  if(pos >= length_) {
+    throw Error("Tried to access invalid pos in ContextProfile");
+  }
+  int olc_idx = ProfileColumn::GetIndex(olc);
+  if(olc_idx != -1) {
+    return data_[pos*20 + olc_idx];
+  } else {
+    throw Error("Invalid one letter code in ContextProfile");
+  }  
+}
+
+const Real* GetPseudoCounts() const { return &data_[length_*20]; }
+
+Real GetPseudoCount(char olc) {
+  int olc_idx = ProfileColumn::GetIndex(olc);
+  if(olc_idx != -1) {
+    return data_[length_*20 + olc_idx];
+  } else {
+    throw Error("Invalid one letter code in ContextProfile");
+  }    
+}
+
+Real GetBias() const { return data_.back(); }
+
+const std::vector<Real>& GetData() const { return data_; }
+
+int GetLength() const { return length_; }
+
+static int DataSize(int length) { return (length+1)*20+1; }
+
+private:
+int length_;
+// data organisation:
+// context weights in chunks of 20 (length_ chunks)
+// followed by 20 elements representing the context pseudo counts
+// last element is the bias
+std::vector<Real> data_;
+};
+
+
+class ContextProfileDB {
+
+public:
+
+ContextProfileDB() { } 
+
+void Save(const String& filename) const;
+
+static ContextProfileDBPtr Load(const String& filename);
+
+static ContextProfileDBPtr FromCRF(const String& filename);
+
+void AddProfile(const ContextProfile& profile){
+
+  // enforce same length for all profiles
+  if(!profiles_.empty()) {
+    if(profile.GetLength() != profiles_[0].GetLength()) {
+      throw Error("Require all profiles to be of same length");
+    }
+  }
+  profiles_.push_back(profile);
+}
+
+const ContextProfile& operator [](int idx) const {
+  return profiles_[idx];
+}
+
+const ContextProfile& at(int idx) const {
+  return profiles_.at(idx);
+}
+
+size_t size() const {
+  return profiles_.size();
+}
+
+size_t profile_length() const {
+  if(profiles_.empty()) {
+    throw Error("DB must contain profiles to get profile length");
+  }
+  return profiles_[0].GetLength();
+}
+
+private:
+std::vector<ContextProfile> profiles_;
+};
+
 void AddTransitionPseudoCounts(ost::seq::ProfileHandle& profile);
 
 void AddAAPseudoCounts(ost::seq::ProfileHandle& profile);
 
+void AddAAPseudoCounts(ost::seq::ProfileHandle& profile, 
+                       const ContextProfileDB& db);
+
 }}} // ns
 
 #endif