MDGeometry.cpp 21.4 KB
Newer Older
1
2
3
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4
5
//   NScD Oak Ridge National Laboratory, European Spallation Source,
//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6
// SPDX - License - Identifier: GPL - 3.0 +
7
#include "MantidAPI/MDGeometry.h"
8
#include "MantidAPI/AnalysisDataService.h"
9
#include "MantidAPI/CoordTransform.h"
10
#include "MantidGeometry/MDGeometry/IMDDimension.h"
LamarMoore's avatar
LamarMoore committed
11
#include "MantidGeometry/MDGeometry/MDGeometryXMLBuilder.h"
12
#include "MantidGeometry/MDGeometry/MDHistoDimension.h"
LamarMoore's avatar
LamarMoore committed
13
#include "MantidKernel/System.h"
14

15
#include <Poco/NObserver.h>
16
#include <boost/make_shared.hpp>
David Fairbrother's avatar
David Fairbrother committed
17
#include <utility>
18
19
20

using namespace Mantid::Kernel;
using namespace Mantid::API;
21
using namespace Mantid::Geometry;
22

23
24
25
namespace Mantid {
namespace API {

26
27
class MDGeometryNotificationHelper {
public:
28
  explicit MDGeometryNotificationHelper(MDGeometry &parent)
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
      : m_parent(parent),
        m_delete_observer(
            *this, &MDGeometryNotificationHelper::deleteNotificationReceived) {}

  ~MDGeometryNotificationHelper() {
    if (m_observingDelete) {
      // Stop watching once object is deleted
      API::AnalysisDataService::Instance().notificationCenter.removeObserver(
          m_delete_observer);
    }
  }

  void watchForWorkspaceDeletions() {
    if (!m_observingDelete) {
      API::AnalysisDataService::Instance().notificationCenter.addObserver(
          m_delete_observer);
      m_observingDelete = true;
    }
  }

  void deleteNotificationReceived(
      Mantid::API::WorkspacePreDeleteNotification_ptr notice) {
    m_parent.deleteNotificationReceived(notice->object());
  }

private:
  MDGeometry &m_parent;

  /// Poco delete notification observer object
  Poco::NObserver<MDGeometryNotificationHelper, WorkspacePreDeleteNotification>
      m_delete_observer;

  /// Set to True when the m_delete_observer is observing workspace deletions.
  bool m_observingDelete{false};
};

65
66
67
68
//----------------------------------------------------------------------------------------------
/** Constructor
 */
MDGeometry::MDGeometry()
69
70
    : m_dimensions(), m_originalWorkspaces(), m_origin(),
      m_transforms_FromOriginal(), m_transforms_ToOriginal(),
71
      m_notificationHelper(
72
          std::make_unique<MDGeometryNotificationHelper>(*this)),
73
      m_Wtransf(3, 3, true), m_basisVectors() {}
74
75
76
77
78

//----------------------------------------------------------------------------------------------
/** Copy Constructor
 */
MDGeometry::MDGeometry(const MDGeometry &other)
79
    : m_dimensions(), m_originalWorkspaces(), m_origin(other.m_origin),
80
      m_transforms_FromOriginal(), m_transforms_ToOriginal(),
81
      m_notificationHelper(
82
          std::make_unique<MDGeometryNotificationHelper>(*this)),
83
      m_Wtransf(other.m_Wtransf), m_basisVectors(other.m_basisVectors) {
84
85
86
87
  // Perform a deep copy of the dimensions
  std::vector<Mantid::Geometry::IMDDimension_sptr> dimensions;
  for (size_t d = 0; d < other.getNumDims(); d++) {
    // Copy the dimension
88
89
    auto dim =
        boost::make_shared<MDHistoDimension>(other.getDimension(d).get());
90
    dimensions.emplace_back(dim);
91
92
93
94
  }
  this->initGeometry(dimensions);

  // Perform a deep copy of the coordinate transformations
95
  std::vector<CoordTransform_const_sptr>::const_iterator it;
96
97
98
  for (it = other.m_transforms_FromOriginal.begin();
       it != other.m_transforms_FromOriginal.end(); ++it) {
    if (*it)
99
      m_transforms_FromOriginal.emplace_back(
100
          CoordTransform_const_sptr((*it)->clone()));
101
    else
102
      m_transforms_FromOriginal.emplace_back(CoordTransform_const_sptr());
103
104
105
106
107
  }

  for (it = other.m_transforms_ToOriginal.begin();
       it != other.m_transforms_ToOriginal.end(); ++it) {
    if (*it)
108
      m_transforms_ToOriginal.emplace_back(
109
          CoordTransform_const_sptr((*it)->clone()));
110
    else
111
      m_transforms_ToOriginal.emplace_back(CoordTransform_const_sptr());
112
113
114
115
116
117
118
119
120
  }

  // Copy the references to the original workspaces
  // This will also set up the delete observer to listen to those workspaces
  // being deleted.
  for (size_t i = 0; i < other.m_originalWorkspaces.size(); i++)
    this->setOriginalWorkspace(other.m_originalWorkspaces[i], i);
}

121
122
123
124
/**
 * Clear all transforms to and from original workspaces.
 */
void MDGeometry::clearTransforms() {
125
126
  m_transforms_ToOriginal.clear();
  m_transforms_FromOriginal.clear();
127
128
129
130
131
}

/**
 * Clear the original workspaces
 */
132
void MDGeometry::clearOriginalWorkspaces() { m_originalWorkspaces.clear(); }
133

134
135
136
//----------------------------------------------------------------------------------------------
/** Destructor
 */
137
MDGeometry::~MDGeometry() { m_dimensions.clear(); }
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176

//----------------------------------------------------------------------------------------------
/** Initialize the geometry
 *
 * @param dimensions :: vector of IMDDimension objects, in the order of X, Y, Z,
 *t, etc.
 */
void MDGeometry::initGeometry(
    std::vector<Mantid::Geometry::IMDDimension_sptr> &dimensions) {
  // Copy the dimensions array
  m_dimensions = dimensions;
  // Make sure the basis vectors are big enough
  m_basisVectors.resize(m_dimensions.size(), Mantid::Kernel::VMD());
}

// --------------------------------------------------------------------------------------------
/** @return the number of dimensions in this workspace */
size_t MDGeometry::getNumDims() const { return m_dimensions.size(); }

// --------------------------------------------------------------------------------------------
/** Get a dimension
 * @param index :: which dimension
 * @return the dimension at that index
 */
boost::shared_ptr<const Mantid::Geometry::IMDDimension>
MDGeometry::getDimension(size_t index) const {
  if (index >= m_dimensions.size())
    throw std::runtime_error(
        "Workspace does not have a dimension at that index.");
  return m_dimensions[index];
}

// --------------------------------------------------------------------------------------------
/** Get a dimension
 * @param id :: string ID of the dimension
 * @return the dimension with the specified id string.
 */
boost::shared_ptr<const Mantid::Geometry::IMDDimension>
MDGeometry::getDimensionWithId(std::string id) const {
Hahn, Steven's avatar
Hahn, Steven committed
177
178
179
180
181
182
183
184
185
186
187
  auto dimension = std::find_if(
      m_dimensions.begin(), m_dimensions.end(),
      [&id](
          const boost::shared_ptr<const Mantid::Geometry::IMDDimension> &dim) {
        return dim->getDimensionId() == id;
      });
  if (dimension != m_dimensions.end())
    return *dimension;
  else
    throw std::invalid_argument("Dimension tagged " + id +
                                " was not found in the Workspace");
188
189
190
191
192
193
194
195
196
197
}

// --------------------------------------------------------------------------------------------
/** Get non-collapsed dimensions
@return vector of collapsed dimensions in the workspace geometry.
*/
Mantid::Geometry::VecIMDDimension_const_sptr
MDGeometry::getNonIntegratedDimensions() const {
  using namespace Mantid::Geometry;
  VecIMDDimension_const_sptr vecCollapsedDimensions;
198
199
200
201
  std::copy_if(
      m_dimensions.cbegin(), m_dimensions.cend(),
      std::back_inserter(vecCollapsedDimensions),
      [](const auto &dimension) { return !dimension->getIsIntegrated(); });
202
203
204
205
206
207
208
209
  return vecCollapsedDimensions;
}

//-----------------------------------------------------------------------------------------------
/** @return a vector with the size of the smallest bin in each dimension */
std::vector<coord_t> MDGeometry::estimateResolution() const {
  std::vector<coord_t> out;
  for (size_t d = 0; d < this->getNumDims(); d++)
210
    out.emplace_back(this->getDimension(d)->getBinWidth());
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
  return out;
}

//-----------------------------------------------------------------------------------------------
/** Get the index of the dimension that matches the name given
 *
 * @param name :: name of the m_dimensions
 * @return the index (size_t)
 * @throw runtime_error if it cannot be found.
 */
size_t MDGeometry::getDimensionIndexByName(const std::string &name) const {
  for (size_t d = 0; d < m_dimensions.size(); d++)
    if (m_dimensions[d]->getName() == name)
      return d;
  throw std::runtime_error("Dimension named '" + name +
                           "' was not found in the IMDWorkspace.");
}

//-----------------------------------------------------------------------------------------------
/** Get the index of the dimension that matches the ID given
 *
 * @param id :: id string of the dimension
 * @return the index (size_t)
 * @throw runtime_error if it cannot be found.
 */
size_t MDGeometry::getDimensionIndexById(const std::string &id) const {
  for (size_t d = 0; d < m_dimensions.size(); d++)
    if (m_dimensions[d]->getDimensionId() == id)
      return d;
  throw std::runtime_error("Dimension with id '" + id +
                           "' was not found in the IMDWorkspace.");
}

// --------------------------------------------------------------------------------------------
/** Add a dimension
 * @param dim :: shared pointer to the dimension object   */
void MDGeometry::addDimension(
David Fairbrother's avatar
David Fairbrother committed
248
    const boost::shared_ptr<Mantid::Geometry::IMDDimension> &dim) {
249
  m_dimensions.emplace_back(dim);
250
251
252
253
254
255
}

// --------------------------------------------------------------------------------------------
/** Add a dimension
 * @param dim :: bare pointer to the dimension object   */
void MDGeometry::addDimension(Mantid::Geometry::IMDDimension *dim) {
256
  m_dimensions.emplace_back(
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
      boost::shared_ptr<Mantid::Geometry::IMDDimension>(dim));
}

// --------------------------------------------------------------------------------------------
/// Get the x-dimension mapping.
boost::shared_ptr<const Mantid::Geometry::IMDDimension>
MDGeometry::getXDimension() const {
  if (this->getNumDims() < 1)
    throw std::runtime_error("Workspace does not have any dimensions!");
  return this->getDimension(0);
}

/// Get the y-dimension mapping.
boost::shared_ptr<const Mantid::Geometry::IMDDimension>
MDGeometry::getYDimension() const {
  if (this->getNumDims() < 2)
    throw std::runtime_error("Workspace does not have a Y dimension.");
  return this->getDimension(1);
}

/// Get the z-dimension mapping.
boost::shared_ptr<const Mantid::Geometry::IMDDimension>
MDGeometry::getZDimension() const {
  if (this->getNumDims() < 3)
281
    throw std::runtime_error("Workspace does not have a Z dimension.");
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
  return this->getDimension(2);
}

/// Get the t-dimension mapping.
boost::shared_ptr<const Mantid::Geometry::IMDDimension>
MDGeometry::getTDimension() const {
  if (this->getNumDims() < 4)
    throw std::runtime_error("Workspace does not have a T dimension.");
  return this->getDimension(3);
}

// --------------------------------------------------------------------------------------------
/** Get the basis vector (in the original workspace) for a dimension of this
 * workspace.
 * @param index :: which dimension
 * @return a vector, in the dimensions of the original workspace
 */
Mantid::Kernel::VMD &MDGeometry::getBasisVector(size_t index) {
  if (index >= m_basisVectors.size())
    throw std::invalid_argument("getBasisVector(): invalid index");
  return m_basisVectors[index];
}

/** Get the basis vector (in the original workspace) for a dimension of this
 * workspace.
 * @param index :: which dimension
 * @return a vector, in the dimensions of the original workspace
 */
const Mantid::Kernel::VMD &MDGeometry::getBasisVector(size_t index) const {
  if (index >= m_basisVectors.size())
    throw std::invalid_argument("getBasisVector(): invalid index");
  return m_basisVectors[index];
}

/** Set the basis vector (in the original workspace) for a dimension of this
 * workspace.
 * @param index :: which dimension
 * @param vec :: a vector, in the dimensions of the original workspace
 */
void MDGeometry::setBasisVector(size_t index, const Mantid::Kernel::VMD &vec) {
  if (index >= m_basisVectors.size())
    throw std::invalid_argument("getBasisVector(): invalid index");
  m_basisVectors[index] = vec;
}

327
328
329
/**
 * @return True ONLY if ALL the basis vectors have been normalized.
 */
330
bool MDGeometry::allBasisNormalized() const {
Hahn, Steven's avatar
Hahn, Steven committed
331
332
333
334
335
  auto normalized = std::find_if(m_basisVectors.begin(), m_basisVectors.end(),
                                 [](const Mantid::Kernel::VMD &basisVector) {
                                   return basisVector.length() != 1.0;
                                 });
  return normalized == m_basisVectors.end();
336
337
}

338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
//---------------------------------------------------------------------------------------------------
/// @return true if the geometry is defined relative to another workspace.
/// @param index :: index into the vector of original workspaces
bool MDGeometry::hasOriginalWorkspace(size_t index) const {
  if (index >= m_originalWorkspaces.size())
    return false;
  return bool(m_originalWorkspaces[index]);
}

/// @return the number of original workspaces attached to this one
size_t MDGeometry::numOriginalWorkspaces() const {
  return m_originalWorkspaces.size();
}

//---------------------------------------------------------------------------------------------------
/** Get the "original" workspace (the workspace that was the source for a binned
 *MDWorkspace).
 *
 *  In the case of a chain of workspaces: A->binned to B->binned to C:
 *    Index 0 = the workspace that was binned, e.g. "A"
 *    Index 1 = the intermediate workspace, e.g. "B"
 *
 * @return the original workspace to which the basis vectors relate
 * @param index :: index into the vector of original workspaces.
 */
boost::shared_ptr<Workspace>
MDGeometry::getOriginalWorkspace(size_t index) const {
  if (index >= m_originalWorkspaces.size())
    throw std::runtime_error(
        "MDGeometry::getOriginalWorkspace() invalid index.");
  return m_originalWorkspaces[index];
}

//---------------------------------------------------------------------------------------------------
/** Set the "original" workspace (the workspace that was the source for a binned
 *MDWorkspace).
 *
 *  In the case of a chain of workspaces: A->binned to B->binned to C:
 *    Index 0 = the workspace that was binned, e.g. "A"
 *    Index 1 = the intermediate workspace, e.g. "B"
 *
 * @param ws :: original workspace shared pointer
 * @param index :: index into the vector of original workspaces.
 */
void MDGeometry::setOriginalWorkspace(boost::shared_ptr<Workspace> ws,
                                      size_t index) {
  if (index >= m_originalWorkspaces.size())
    m_originalWorkspaces.resize(index + 1);
David Fairbrother's avatar
David Fairbrother committed
386
  m_originalWorkspaces[index] = std::move(ws);
387
  m_notificationHelper->watchForWorkspaceDeletions();
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
}

//---------------------------------------------------------------------------------------------------
/** Transform the dimensions contained in this geometry
 * x' = x*scaling + offset
 *
 * This clears any original workspace or coordinate transformation
 * contained.
 *
 * NOTE! This does not modify any other underlying data. Call the TransformMD
 * algorithm to perform a full transform.
 *
 * @param scaling :: multiply each coordinate by this value.
 * @param offset :: after multiplying, add this offset.
 */
void MDGeometry::transformDimensions(std::vector<double> &scaling,
                                     std::vector<double> &offset) {
  if (scaling.size() != m_dimensions.size())
    throw std::invalid_argument("MDGeometry::transformDimensions(): "
                                "scaling.size() must be equal to number of "
                                "dimensions.");
  if (offset.size() != m_dimensions.size())
    throw std::invalid_argument("MDGeometry::transformDimensions(): "
                                "offset.size() must be equal to number of "
                                "dimensions.");
  for (size_t d = 0; d < m_dimensions.size(); d++) {
    IMDDimension_sptr dim = m_dimensions[d];
    coord_t min = (dim->getMinimum() * static_cast<coord_t>(scaling[d])) +
                  static_cast<coord_t>(offset[d]);
    coord_t max = (dim->getMaximum() * static_cast<coord_t>(scaling[d])) +
                  static_cast<coord_t>(offset[d]);
419
420
421
422
    if (min < max)
      dim->setRange(dim->getNBins(), min, max);
    else
      dim->setRange(dim->getNBins(), max, min);
423
424
425
  }
  // Clear the original workspace
  setOriginalWorkspace(boost::shared_ptr<Workspace>());
426
427
  setTransformFromOriginal(nullptr);
  setTransformToOriginal(nullptr);
428
429
430
431
432
433
434
435
436
}

//---------------------------------------------------------------------------------------------------
/** Function called when observer objects receives a notification that
 * a workspace has been deleted.
 *
 * This checks if the "original workspace" in this object is being deleted,
 * and removes the reference to it to allow it to be destructed properly.
 *
437
 * @param deleted :: The deleted workspace
438
 */
439
440
void MDGeometry::deleteNotificationReceived(
    const boost::shared_ptr<const Workspace> &deleted) {
441
  for (auto &original : m_originalWorkspaces) {
442
443
444
445
    if (original) {
      // Compare the pointer being deleted to the one stored as the original.
      if (original == deleted) {
        // Clear the reference
446
        original.reset();
447
448
      }
    }
Janik Zikovsky's avatar
Janik Zikovsky committed
449
  }
450
451
452
453
454
455
456
457
458
459
460
461
462
}

//---------------------------------------------------------------------------------------------------
/** Get the Coordinate Transformation that goes from the original workspace
 * to this workspace's coordinates.
 *
 *  In the case of a chain of workspaces: A->binned to B->binned to C:
 *    Index 0 = the workspace that was binned, e.g. "A"
 *    Index 1 = the intermediate workspace, e.g. "B"
 *
 * @return CoordTransform pointer
 * @param index :: index into the array of original workspaces
 */
463
Mantid::API::CoordTransform const *
464
465
466
467
MDGeometry::getTransformFromOriginal(size_t index) const {
  if (index >= m_transforms_FromOriginal.size())
    throw std::runtime_error(
        "MDGeometry::getTransformFromOriginal(): invalid index.");
468
  return m_transforms_FromOriginal[index].get();
469
470
471
472
473
474
475
476
477
478
479
480
481
}

//---------------------------------------------------------------------------------------------------
/** Sets the Coordinate Transformation that goes from the original workspace
 * to this workspace's coordinates.
 *
 *  In the case of a chain of workspaces: A->binned to B->binned to C:
 *    Index 0 = the workspace that was binned, e.g. "A"
 *    Index 1 = the intermediate workspace, e.g. "B"
 *
 * @param transform :: CoordTransform pointer (this assumes pointer ownership)
 * @param index :: index into the array of original workspaces
 */
482
483
void MDGeometry::setTransformFromOriginal(
    Mantid::API::CoordTransform *transform, size_t index) {
484
  if (index >= m_transforms_FromOriginal.size()) {
485
    m_transforms_FromOriginal.resize(index + 1);
486
487
  }
  m_transforms_FromOriginal[index] = CoordTransform_const_sptr(transform);
488
489
490
491
492
493
494
495
496
497
498
499
500
501
}

//---------------------------------------------------------------------------------------------------
/** Get the Coordinate Transformation that goes from THIS workspace's
 *coordinates
 * to the ORIGINAL workspace's coordinates
 *
 *  In the case of a chain of workspaces: A->binned to B->binned to C:
 *    Index 0 = the workspace that was binned, e.g. "A"
 *    Index 1 = the intermediate workspace, e.g. "B"
 *
 * @return CoordTransform pointer
 * @param index :: index into the array of original workspaces
 */
502
Mantid::API::CoordTransform const *
503
504
505
506
MDGeometry::getTransformToOriginal(size_t index) const {
  if (index >= m_transforms_ToOriginal.size())
    throw std::runtime_error(
        "MDGeometry::getTransformFromOriginal(): invalid index.");
507
  return m_transforms_ToOriginal[index].get();
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
}

//---------------------------------------------------------------------------------------------------
/** Sets the Coordinate Transformation that goes from THIS workspace's
 *coordinates
 * to the ORIGINAL workspace's coordinates
 *
 *  In the case of a chain of workspaces: A->binned to B->binned to C:
 *    Index 0 = the workspace that was binned, e.g. "A"
 *    Index 1 = the intermediate workspace, e.g. "B"
 *
 * @param transform :: CoordTransform pointer (this assumes pointer ownership)
 * @param index :: index into the array of original workspaces
 */
void MDGeometry::setTransformToOriginal(Mantid::API::CoordTransform *transform,
                                        size_t index) {
524
  if (index >= m_transforms_ToOriginal.size()) {
525
    m_transforms_ToOriginal.resize(index + 1);
526
527
  }
  m_transforms_ToOriginal[index] = CoordTransform_const_sptr(transform);
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
}

//---------------------------------------------------------------------------------------------------
/** @return a XML representation of the geometry of the workspace */
std::string MDGeometry::getGeometryXML() const {
  using Mantid::Geometry::MDGeometryBuilderXML;
  using Mantid::Geometry::NoDimensionPolicy;
  MDGeometryBuilderXML<NoDimensionPolicy> xmlBuilder;
  // Add all dimensions.
  const size_t nDimensions = this->getNumDims();
  for (size_t i = 0; i < nDimensions; i++) {
    xmlBuilder.addOrdinaryDimension(this->getDimension(i));
  }
  // Add mapping dimensions
  if (nDimensions > 0) {
    xmlBuilder.addXDimension(this->getXDimension());
  }
  if (nDimensions > 1) {
    xmlBuilder.addYDimension(this->getYDimension());
  }
  if (nDimensions > 2) {
    xmlBuilder.addZDimension(this->getZDimension());
  }
  if (nDimensions > 3) {
    xmlBuilder.addTDimension(this->getTDimension());
  }
  // Create the xml.
  return xmlBuilder.create();
}

/**
 * Get the number of transforms defined to the original coordinate system.
 * @return The number of transforms.
 */
size_t MDGeometry::getNumberTransformsToOriginal() const {
  return m_transforms_ToOriginal.size();
}

/**
 * Get the number of transforms defined from the original coordinate system.
 * @return The number of transforms.
 */
size_t MDGeometry::getNumberTransformsFromOriginal() const {
  return m_transforms_FromOriginal.size();
}
573
574

} // namespace API
LamarMoore's avatar
LamarMoore committed
575
} // namespace Mantid