Medical Imaging Interaction Toolkit  2018.4.99-b585543d
Medical Imaging Interaction Toolkit
mitkTemporoSpatialStringProperty.cpp
Go to the documentation of this file.
1 /*============================================================================
2 
3 The Medical Imaging Interaction Toolkit (MITK)
4 
5 Copyright (c) German Cancer Research Center (DKFZ)
6 All rights reserved.
7 
8 Use of this source code is governed by a 3-clause BSD license that can be
9 found in the LICENSE file.
10 
11 ============================================================================*/
12 
13 #include <iterator>
14 #include <set>
15 
17 
18 #include <boost/property_tree/json_parser.hpp>
19 #include <boost/property_tree/ptree.hpp>
20 #include <boost/type_traits/make_unsigned.hpp>
21 
23 {
24  if (s)
25  {
26  SliceMapType slices{{0, s}};
27 
28  m_Values.insert(std::make_pair(0, slices));
29  }
30 }
31 
33 {
34  SliceMapType slices{{0, s}};
35 
36  m_Values.insert(std::make_pair(0, slices));
37 }
38 
40  : BaseProperty(other), m_Values(other.m_Values)
41 {
42 }
43 
44 bool mitk::TemporoSpatialStringProperty::IsEqual(const BaseProperty &property) const
45 {
46  return this->m_Values == static_cast<const Self &>(property).m_Values;
47 }
48 
49 bool mitk::TemporoSpatialStringProperty::Assign(const BaseProperty &property)
50 {
51  this->m_Values = static_cast<const Self &>(property).m_Values;
52  return true;
53 }
54 
56 {
57  return GetValue();
58 }
59 
61 {
62  auto refValue = this->GetValue();
63 
64  for (const auto& timeStep : m_Values)
65  {
66  auto finding = std::find_if_not(timeStep.second.begin(), timeStep.second.end(), [&refValue](const mitk::TemporoSpatialStringProperty::SliceMapType::value_type& val) { return val.second == refValue; });
67  if (finding != timeStep.second.end())
68  {
69  return false;
70  }
71  }
72 
73  return true;
74 }
75 
76 itk::LightObject::Pointer mitk::TemporoSpatialStringProperty::InternalClone() const
77 {
78  itk::LightObject::Pointer result(new Self(*this));
79  result->UnRegister();
80  return result;
81 }
82 
84 {
85  std::string result = "";
86 
87  if (!m_Values.empty())
88  {
89  if (!m_Values.begin()->second.empty())
90  {
91  result = m_Values.begin()->second.begin()->second;
92  }
93  }
94  return result;
95 };
96 
97 std::pair<bool, mitk::TemporoSpatialStringProperty::ValueType> mitk::TemporoSpatialStringProperty::CheckValue(
98  const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime, bool allowCloseSlice) const
99 {
100  std::string value = "";
101  bool found = false;
102 
103  auto timeIter = m_Values.find(timeStep);
104  auto timeEnd = m_Values.end();
105  if (timeIter == timeEnd && allowCloseTime)
106  { // search for closest time step (earlier preverd)
107  timeIter = m_Values.upper_bound(timeStep);
108  if (timeIter != m_Values.begin())
109  { // there is a key lower than time step
110  timeIter = std::prev(timeIter);
111  }
112  }
113 
114  if (timeIter != timeEnd)
115  {
116  const SliceMapType &slices = timeIter->second;
117 
118  auto sliceIter = slices.find(zSlice);
119  auto sliceEnd = slices.end();
120  if (sliceIter == sliceEnd && allowCloseSlice)
121  { // search for closest slice (earlier preverd)
122  sliceIter = slices.upper_bound(zSlice);
123  if (sliceIter != slices.begin())
124  { // there is a key lower than slice
125  sliceIter = std::prev(sliceIter);
126  }
127  }
128 
129  if (sliceIter != sliceEnd)
130  {
131  value = sliceIter->second;
132  found = true;
133  }
134  }
135 
136  return std::make_pair(found, value);
137 };
138 
140  const IndexValueType &zSlice,
141  bool allowCloseTime,
142  bool allowCloseSlice) const
143 {
144  return CheckValue(timeStep, zSlice, allowCloseTime, allowCloseSlice).second;
145 };
146 
148  const IndexValueType &zSlice, bool allowClose) const
149 {
150  return GetValue(0, zSlice, true, allowClose);
151 };
152 
154  const TimeStepType &timeStep, bool allowClose) const
155 {
156  return GetValue(timeStep, 0, allowClose, true);
157 };
158 
160 {
161  return !m_Values.empty();
162 };
163 
165  const IndexValueType &zSlice,
166  bool allowCloseTime,
167  bool allowCloseSlice) const
168 {
169  return CheckValue(timeStep, zSlice, allowCloseTime, allowCloseSlice).first;
170 };
171 
172 bool mitk::TemporoSpatialStringProperty::HasValueBySlice(const IndexValueType &zSlice, bool allowClose) const
173 {
174  return HasValue(0, zSlice, true, allowClose);
175 };
176 
177 bool mitk::TemporoSpatialStringProperty::HasValueByTimeStep(const TimeStepType &timeStep, bool allowClose) const
178 {
179  return HasValue(timeStep, 0, allowClose, true);
180 };
181 
182 std::vector<mitk::TemporoSpatialStringProperty::IndexValueType> mitk::TemporoSpatialStringProperty::GetAvailableSlices() const
183 {
184  std::set<IndexValueType> uniqueSlices;
185 
186  for (const auto& timeStep : m_Values)
187  {
188  for (const auto& slice : timeStep.second)
189  {
190  uniqueSlices.insert(slice.first);
191  }
192  }
193 
194  return std::vector<IndexValueType>(std::begin(uniqueSlices), std::end(uniqueSlices));
195 }
196 
197 std::vector<mitk::TemporoSpatialStringProperty::IndexValueType> mitk::TemporoSpatialStringProperty::GetAvailableSlices(
198  const TimeStepType &timeStep) const
199 {
200  std::vector<IndexValueType> result;
201 
202  auto timeIter = m_Values.find(timeStep);
203  auto timeEnd = m_Values.end();
204 
205  if (timeIter != timeEnd)
206  {
207  for (auto const &element : timeIter->second)
208  {
209  result.push_back(element.first);
210  }
211  }
212 
213  return result;
214 };
215 
216 std::vector<mitk::TimeStepType> mitk::TemporoSpatialStringProperty::GetAvailableTimeSteps() const
217 {
218  std::vector<mitk::TimeStepType> result;
219 
220  for (auto const &element : m_Values)
221  {
222  result.push_back(element.first);
223  }
224 
225  return result;
226 };
227 
228 std::vector<mitk::TimeStepType> mitk::TemporoSpatialStringProperty::GetAvailableTimeSteps(const IndexValueType& slice) const
229 {
230  std::vector<mitk::TimeStepType> result;
231 
232  for (const auto& timeStep : m_Values)
233  {
234  if (timeStep.second.find(slice) != std::end(timeStep.second))
235  {
236  result.push_back(timeStep.first);
237  }
238  }
239  return result;
240 }
241 
242 
244  const IndexValueType &zSlice,
245  const ValueType &value)
246 {
247  auto timeIter = m_Values.find(timeStep);
248  auto timeEnd = m_Values.end();
249 
250  if (timeIter == timeEnd)
251  {
252  SliceMapType slices{{zSlice, value}};
253  m_Values.insert(std::make_pair(timeStep, slices));
254  }
255  else
256  {
257  timeIter->second[zSlice] = value;
258  }
259  this->Modified();
260 };
261 
263 {
264  this->Modified();
265  m_Values.clear();
266  this->SetValue(0, 0, value);
267 };
268 
269 // Create necessary escape sequences from illegal characters
270 // REMARK: This code is based upon code from boost::ptree::json_writer.
271 // The corresponding boost function was not used directly, because it is not part of
272 // the public interface of ptree::json_writer. :(
273 // A own serialization strategy was implemented instead of using boost::ptree::json_write because
274 // currently (<= boost 1.60) everything (even numbers) are converted into string representations
275 // by the writer, so e.g. it becomes "t":"2" instaed of "t":2
276 template <class Ch>
277 std::basic_string<Ch> CreateJSONEscapes(const std::basic_string<Ch> &s)
278 {
279  std::basic_string<Ch> result;
280  typename std::basic_string<Ch>::const_iterator b = s.begin();
281  typename std::basic_string<Ch>::const_iterator e = s.end();
282  while (b != e)
283  {
284  typedef typename boost::make_unsigned<Ch>::type UCh;
285  UCh c(*b);
286  // This assumes an ASCII superset.
287  // We escape everything outside ASCII, because this code can't
288  // handle high unicode characters.
289  if (c == 0x20 || c == 0x21 || (c >= 0x23 && c <= 0x2E) || (c >= 0x30 && c <= 0x5B) || (c >= 0x5D && c <= 0x7F))
290  result += *b;
291  else if (*b == Ch('\b'))
292  result += Ch('\\'), result += Ch('b');
293  else if (*b == Ch('\f'))
294  result += Ch('\\'), result += Ch('f');
295  else if (*b == Ch('\n'))
296  result += Ch('\\'), result += Ch('n');
297  else if (*b == Ch('\r'))
298  result += Ch('\\'), result += Ch('r');
299  else if (*b == Ch('\t'))
300  result += Ch('\\'), result += Ch('t');
301  else if (*b == Ch('/'))
302  result += Ch('\\'), result += Ch('/');
303  else if (*b == Ch('"'))
304  result += Ch('\\'), result += Ch('"');
305  else if (*b == Ch('\\'))
306  result += Ch('\\'), result += Ch('\\');
307  else
308  {
309  const char *hexdigits = "0123456789ABCDEF";
310  unsigned long u = (std::min)(static_cast<unsigned long>(static_cast<UCh>(*b)), 0xFFFFul);
311  int d1 = u / 4096;
312  u -= d1 * 4096;
313  int d2 = u / 256;
314  u -= d2 * 256;
315  int d3 = u / 16;
316  u -= d3 * 16;
317  int d4 = u;
318  result += Ch('\\');
319  result += Ch('u');
320  result += Ch(hexdigits[d1]);
321  result += Ch(hexdigits[d2]);
322  result += Ch(hexdigits[d3]);
323  result += Ch(hexdigits[d4]);
324  }
325  ++b;
326  }
327  return result;
328 }
329 
330 using CondensedTimeKeyType = std::pair<mitk::TimeStepType, mitk::TimeStepType>;
331 using CondensedTimePointsType = std::map<CondensedTimeKeyType, std::string>;
332 
333 using CondensedSliceKeyType = std::pair<mitk::TemporoSpatialStringProperty::IndexValueType, mitk::TemporoSpatialStringProperty::IndexValueType>;
334 using CondensedSlicesType = std::map<CondensedSliceKeyType, CondensedTimePointsType>;
335 
337 template<typename TValue>
338 bool isGap(const TValue& value, const TValue& successor)
339 {
340  return value<successor || value > successor + 1;
341 }
342 
343 
344 template<typename TNewKey, typename TNewValue, typename TMasterKey, typename TMasterValue, typename TCondensedContainer>
345 void CheckAndCondenseElement(const TNewKey& newKeyMinID, const TNewValue& newValue, TMasterKey& masterKey, TMasterValue& masterValue, TCondensedContainer& condensedContainer)
346 {
347  if (newValue != masterValue
348  || isGap(newKeyMinID, masterKey.second))
349  {
350  condensedContainer[masterKey] = masterValue;
351  masterValue = newValue;
352  masterKey.first = newKeyMinID;
353  }
354  masterKey.second = newKeyMinID;
355 }
356 
359 {
360  CondensedSlicesType uncondensedSlices;
361 
362  auto zs = tsProp->GetAvailableSlices();
363  for (const auto z : zs)
364  {
365  CondensedTimePointsType condensedTimePoints;
366  auto timePointIDs = tsProp->GetAvailableTimeSteps(z);
367  CondensedTimeKeyType condensedKey = { timePointIDs.front(),timePointIDs.front() };
368  auto refValue = tsProp->GetValue(timePointIDs.front(), z);
369 
370  for (const auto timePointID : timePointIDs)
371  {
372  const auto& newVal = tsProp->GetValue(timePointID, z);
373  CheckAndCondenseElement(timePointID, newVal, condensedKey, refValue, condensedTimePoints);
374  }
375  condensedTimePoints[condensedKey] = refValue;
376  uncondensedSlices[{ z, z }] = condensedTimePoints;
377  }
378  return uncondensedSlices;
379 }
380 
382  const mitk::BaseProperty *prop)
383 {
384  // REMARK: Implemented own serialization instead of using boost::ptree::json_write because
385  // currently (<= boost 1.60) everything (even numbers) are converted into string representations
386  // by the writer, so e.g. it becomes "t":"2" instaed of "t":2
387  // If this problem is fixed with boost, we shoud switch back to json_writer (and remove the custom
388  // implementation of CreateJSONEscapes (see above)).
389  const auto *tsProp = dynamic_cast<const mitk::TemporoSpatialStringProperty *>(prop);
390 
391  if (!tsProp)
392  {
393  mitkThrow() << "Cannot serialize properties of types other than TemporoSpatialStringProperty.";
394  }
395 
396  std::ostringstream stream;
397  stream.imbue(std::locale("C"));
398  stream << "{\"values\":[";
399 
400  //we condense the content of the property to have a compact serialization.
401  //we start with condensing time points and then slices (in difference to the
402  //internal layout). Reason: There is more entropy in slices (looking at DICOM)
403  //than across time points for one slice, so we can "compress" to a higher rate.
404  //We don't wanted to change the internal structure of the property as it would
405  //introduce API inconvinience and subtle changes in behavior.
406  CondensedSlicesType uncondensedSlices = CondenseTimePointValuesOfProperty(tsProp);
407 
408  //now condense the slices
409  CondensedSlicesType condensedSlices;
410  if(!uncondensedSlices.empty())
411  {
412  CondensedTimePointsType& masterSlice = uncondensedSlices.begin()->second;
413  CondensedSliceKeyType masterSliceKey = uncondensedSlices.begin()->first;
414 
415  for (const auto& uncondensedSlice : uncondensedSlices)
416  {
417  const auto& uncondensedSliceID = uncondensedSlice.first.first;
418  CheckAndCondenseElement(uncondensedSliceID, uncondensedSlice.second, masterSliceKey, masterSlice, condensedSlices);
419  }
420  condensedSlices[masterSliceKey] = masterSlice;
421  }
422 
423 
424  bool first = true;
425  for (const auto& z : condensedSlices)
426  {
427  for (const auto& t : z.second)
428  {
429  if (first)
430  {
431  first = false;
432  }
433  else
434  {
435  stream << ", ";
436  }
437 
438  const auto& minSliceID = z.first.first;
439  const auto& maxSliceID = z.first.second;
440  const auto& minTimePointID = t.first.first;
441  const auto& maxTimePointID = t.first.second;
442 
443  stream << "{\"z\":" << minSliceID << ", ";
444  if (minSliceID != maxSliceID)
445  {
446  stream << "\"zmax\":" << maxSliceID << ", ";
447  }
448  stream << "\"t\":" << minTimePointID << ", ";
449  if (minTimePointID != maxTimePointID)
450  {
451  stream << "\"tmax\":" << maxTimePointID << ", ";
452  }
453 
454  const auto& value = t.second;
455  stream << "\"value\":\"" << CreateJSONEscapes(value) << "\"}";
456  }
457  }
458 
459  stream << "]}";
460 
461  return stream.str();
462 }
463 
465  const std::string &value)
466 {
467  if (value.empty())
468  return nullptr;
469 
471 
472  boost::property_tree::ptree root;
473 
474  std::istringstream stream(value);
475  stream.imbue(std::locale("C"));
476 
477  boost::property_tree::read_json(stream, root);
478 
479  for (boost::property_tree::ptree::value_type &element : root.get_child("values"))
480  {
481  std::string value = element.second.get("value", "");
483  element.second.get<mitk::TemporoSpatialStringProperty::IndexValueType>("z", 0);
485  element.second.get<mitk::TemporoSpatialStringProperty::IndexValueType>("zmax", z);
486  TimeStepType t = element.second.get<TimeStepType>("t", 0);
487  TimeStepType tmax = element.second.get<TimeStepType>("tmax", t);
488 
489  for (auto currentT = t; currentT <= tmax; ++currentT)
490  {
491  for (auto currentZ = z; currentZ <= zmax; ++currentZ)
492  {
493  prop->SetValue(currentT, currentZ, value);
494  }
495  }
496  }
497 
498  return prop.GetPointer();
499 }
std::basic_string< Ch > CreateJSONEscapes(const std::basic_string< Ch > &s)
std::pair< mitk::TemporoSpatialStringProperty::IndexValueType, mitk::TemporoSpatialStringProperty::IndexValueType > CondensedSliceKeyType
MITKCORE_EXPORT::std::string serializeTemporoSpatialStringPropertyToJSON(const mitk::BaseProperty *prop)
std::vector< IndexValueType > GetAvailableSlices() const
ValueType GetValueBySlice(const IndexValueType &zSlice, bool allowClose=false) const
std::pair< bool, ValueType > CheckValue(const TimeStepType &timeStep, const IndexValueType &zSlice, bool allowCloseTime=false, bool allowCloseSlice=false) const
void SetValue(const TimeStepType &timeStep, const IndexValueType &zSlice, const ValueType &value)
std::map< CondensedTimeKeyType, std::string > CondensedTimePointsType
Abstract base class for properties.
std::vector< IndexValueType > GetAvailableSlices(const TimeStepType &timeStep) const
bool HasValueByTimeStep(const TimeStepType &timeStep, bool allowClose=false) const
std::vector< TimeStepType > GetAvailableTimeSteps(const IndexValueType &slice) const
std::vector< TimeStepType > GetAvailableTimeSteps() const
#define mitkThrow()
ValueType GetValueByTimeStep(const TimeStepType &timeStep, bool allowClose=false) const
std::pair< mitk::TimeStepType, mitk::TimeStepType > CondensedTimeKeyType
std::vcl_size_t TimeStepType
void CheckAndCondenseElement(const TNewKey &newKeyMinID, const TNewValue &newValue, TMasterKey &masterKey, TMasterValue &masterValue, TCondensedContainer &condensedContainer)
static T min(T x, T y)
Definition: svm.cpp:53
CondensedSlicesType CondenseTimePointValuesOfProperty(const mitk::TemporoSpatialStringProperty *tsProp)
std::map< CondensedSliceKeyType, CondensedTimePointsType > CondensedSlicesType
bool HasValueBySlice(const IndexValueType &zSlice, bool allowClose=false) const
std::map< IndexValueType, std::string > SliceMapType
MITKCORE_EXPORT mitk::BaseProperty::Pointer deserializeJSONToTemporoSpatialStringProperty(const std::string &value)
bool isGap(const TValue &value, const TValue &successor)
Property for time and space resolved string values.