PeakOverlay.cpp 13.9 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 +
Roman Tolchenov's avatar
Roman Tolchenov committed
7
#include "MantidQtWidgets/InstrumentView/PeakOverlay.h"
8
#include "MantidAPI/AlgorithmManager.h"
LamarMoore's avatar
LamarMoore committed
9
#include "MantidAPI/IPeaksWorkspace.h"
Jose Borreguero's avatar
Jose Borreguero committed
10
#include "MantidDataObjects/Peak.h"
LamarMoore's avatar
LamarMoore committed
11
#include "MantidQtWidgets/InstrumentView/UnwrappedSurface.h"
12
13

#include <QList>
LamarMoore's avatar
LamarMoore committed
14
#include <QPainter>
15
#include <algorithm>
LamarMoore's avatar
LamarMoore committed
16
#include <cmath>
17
18
#include <stdexcept>

Jose Borreguero's avatar
Jose Borreguero committed
19
20
21
22
namespace {
Mantid::Kernel::Logger g_log("PeakOverlay");
}

23
24
25
26
27
28
namespace MantidQt {
namespace MantidWidgets {

QList<PeakMarker2D::Style> PeakOverlay::g_defaultStyles;

/**
LamarMoore's avatar
LamarMoore committed
29
30
 * Constructor.
 */
31
32
33
34
35
36
37
38
39
PeakHKL::PeakHKL(PeakMarker2D *m, const QRectF &trect, bool sr)
    : p(m->origin()), rect(trect),
      // rectTopLeft(m->getLabelRect().topLeft()),
      h(m->getH()), k(m->getK()), l(m->getL()), nh(true), nk(true), nl(true),
      showRows(sr) {
  rows.append(m->getRow());
}

/**
LamarMoore's avatar
LamarMoore committed
40
41
42
43
44
 * Check if this rect intersects with marker's and if it does combine the labels
 * @param marker :: A marker to check for intersection
 * @param trect :: Transformed marker's label rect
 * @return True if labels were combined, false otherwise.
 */
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
bool PeakHKL::add(PeakMarker2D *marker, const QRectF &trect) {
  if (!rect.intersects(trect)) {
    return false;
  }
  if (nh && marker->getH() != h) {
    nh = false;
  }
  if (nk && marker->getK() != k) {
    nk = false;
  }
  if (nl && marker->getL() != l) {
    nl = false;
  }
  rows.append(marker->getRow());
  return true;
}
/**
LamarMoore's avatar
LamarMoore committed
62
63
64
65
 * Draw the label
 * @param painter :: QPainter to draw with
 * @param prec :: precision
 */
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
void PeakHKL::draw(QPainter &painter, int prec) {
  QString label;
  if (nh) {
    label += formatNumber(h, prec) + " ";
  } else
    label = "h ";
  if (nk) {
    label += formatNumber(k, prec) + " ";
  } else
    label += "k ";
  if (nl) {
    label += formatNumber(l, prec) + " ";
  } else
    label += "l";
  if (showRows) {
    label += " [" + QString::number(rows[0]);
    for (int i = 1; i < rows.size(); ++i) {
      label += "," + QString::number(rows[i]);
    }
    label += "]";
  }
  painter.drawText(rect.bottomLeft(), label);
}

void PeakHKL::print() const {
  std::cerr << "     " << p.x() << ' ' << p.y() << '(' << h << ',' << k << ','
92
            << l << ")(" << nh << ',' << nk << ',' << nl << ')' << '\n';
93
94
95
}

/**
LamarMoore's avatar
LamarMoore committed
96
97
98
99
100
 * Creates formated string for outputting h,k, or l
 *
 * @param h :: Value to output.
 * @param prec :: Precision as a number of decimal places.
 */
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
QString PeakHKL::formatNumber(double h, int prec) {
  if (h == 0)
    return "0";
  int max_prec = std::max(prec, int(log10(h) + 1));
  QString str = QString::number(h, 'f', max_prec);
  if (str.contains('.')) {
    while (str.endsWith('0'))
      str.chop(1);
    if (str.endsWith('.'))
      str.chop(1);
  }
  return str;
}

/// Extract minimum and maximum intensity from peaks workspace for scaling.
void AbstractIntensityScale::setPeaksWorkspace(
117
    const std::shared_ptr<Mantid::API::IPeaksWorkspace> &pws) {
118
119
120
121
122
123
124
125
126
127
  m_maxIntensity = 0.0;
  m_minIntensity = 0.0;

  if (pws) {
    int peakCount = pws->getNumberPeaks();

    std::vector<double> intensities;
    intensities.reserve(peakCount);

    for (int i = 0; i < peakCount; ++i) {
128
      intensities.emplace_back(pws->getPeak(i).getIntensity());
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
    }

    auto minMaxIntensity =
        std::minmax_element(intensities.begin(), intensities.end());

    if (peakCount > 0) {
      m_maxIntensity = *minMaxIntensity.second;
      m_minIntensity = *minMaxIntensity.first;
    }
  }
}

/// Returns the scaled style by intensity. Only size is changed, the other
/// properties are kept the same. If the max intensity is 0 or less, the
/// style is returned as it is.
PeakMarker2D::Style QualitativeIntensityScale::getScaledMarker(
    double intensity, const PeakMarker2D::Style &baseStyle) const {
  if (m_maxIntensity <= 0.0) {
    return baseStyle;
  }

  return PeakMarker2D::Style(baseStyle.symbol, baseStyle.color,
                             3 * getIntensityLevel(intensity) + 1);
}

/**
LamarMoore's avatar
LamarMoore committed
155
156
157
158
159
160
161
162
163
164
165
 * Returns the marker size corresponding to the supplied intensity
 *
 * Intensity levels are specified in m_intensityLevels. The method looks for
 * the first element >= than the relative intensity and returns the distance
 * from the beginning of list to that element + 1.
 *
 * For values less than the first element, 0 is returned.
 *
 * @param intensity :: Absolute intensity.
 * @return Intensity level between 0 and the number of intensity levels + 1
 */
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
int QualitativeIntensityScale::getIntensityLevel(double intensity) const {
  auto intensityGreaterThan =
      std::lower_bound(m_intensityLevels.cbegin(), m_intensityLevels.cend(),
                       intensity / m_maxIntensity);

  // For weak peaks below first intensity
  if (intensityGreaterThan == m_intensityLevels.cend()) {
    return 0;
  }

  return static_cast<int>(
             std::distance(m_intensityLevels.cbegin(), intensityGreaterThan)) +
         1;
}

/**---------------------------------------------------------------------
LamarMoore's avatar
LamarMoore committed
182
183
 * Constructor
 */
David Fairbrother's avatar
David Fairbrother committed
184
185
PeakOverlay::PeakOverlay(
    UnwrappedSurface *surface,
186
    const std::shared_ptr<Mantid::API::IPeaksWorkspace> &pws)
187
188
    : Shape2DCollection(), m_peaksWorkspace(pws), m_surface(surface),
      m_precision(6), m_showRows(true), m_showLabels(true),
Sam Jenkins's avatar
Sam Jenkins committed
189
      m_peakIntensityScale(std::make_unique<QualitativeIntensityScale>(pws)) {
190
191
192
193
194
195
196
197
198
199

  if (g_defaultStyles.isEmpty()) {
    g_defaultStyles << PeakMarker2D::Style(PeakMarker2D::Circle, Qt::red);
    g_defaultStyles << PeakMarker2D::Style(PeakMarker2D::Diamond, Qt::green);
    g_defaultStyles << PeakMarker2D::Style(PeakMarker2D::Square, Qt::magenta);
  }
  observeAfterReplace();
}

/**---------------------------------------------------------------------
LamarMoore's avatar
LamarMoore committed
200
201
202
203
 * Overridden virtual function to remove peaks from the workspace along with
 * the shapes.
 * @param shapeList :: Shapes to remove.
 */
204
205
206
207
208
209
210
void PeakOverlay::removeShapes(const QList<Shape2D *> &shapeList) {
  // vectors of rows to delete from the peaks workspace.
  std::vector<size_t> rows;
  foreach (Shape2D *shape, shapeList) {
    PeakMarker2D *marker = dynamic_cast<PeakMarker2D *>(shape);
    if (!marker)
      throw std::logic_error("Wrong shape type found.");
211
    rows.emplace_back(static_cast<size_t>(marker->getRow()));
212
213
214
215
216
  }

  // Run the DeleteTableRows algorithm to delete the peak.
  auto alg =
      Mantid::API::AlgorithmManager::Instance().create("DeleteTableRows", -1);
217
  alg->setPropertyValue("TableWorkspace", m_peaksWorkspace->getName());
218
219
220
221
222
  alg->setProperty("Rows", rows);
  emit executeAlgorithm(alg);
}

/**---------------------------------------------------------------------
LamarMoore's avatar
LamarMoore committed
223
224
 * Not implemented yet.
 */
225
226
227
228
229
230
void PeakOverlay::clear() {
  Shape2DCollection::clear();
  m_det2marker.clear();
}

/**---------------------------------------------------------------------
LamarMoore's avatar
LamarMoore committed
231
232
233
 * Add new marker to the overlay.
 * @param m :: Pointer to the new marker
 */
234
235
236
237
238
239
void PeakOverlay::addMarker(PeakMarker2D *m) {
  addShape(m, false);
  m_det2marker.insert(m->getDetectorID(), m);
}

/**---------------------------------------------------------------------
LamarMoore's avatar
LamarMoore committed
240
241
242
243
244
 * Create the markers which graphically represent the peaks on the surface.
 * The coordinates of the Shape2DCollection must be set (calling setWindow())
 * prior calling this method.
 * @param style :: A style of drawing the markers.
 */
245
246
247
248
249
250
void PeakOverlay::createMarkers(const PeakMarker2D::Style &style) {
  int nPeaks = getNumberPeaks();

  this->clear();
  for (int i = 0; i < nPeaks; ++i) {
    Mantid::Geometry::IPeak &peak = getPeak(i);
Jose Borreguero's avatar
Jose Borreguero committed
251
252
253
254
    Mantid::Kernel::V3D pos;
    try {
      auto peakFull = dynamic_cast<Mantid::DataObjects::Peak &>(peak);
      pos = peakFull.getDetPos();
255
    } catch (std::bad_cast&) {
Jose Borreguero's avatar
Jose Borreguero committed
256
257
258
      g_log.error("Cannot create markers for this type of peak");
      return;
    }
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
    // Project the peak (detector) position onto u,v coords
    double u, v, uscale, vscale;
    m_surface->project(pos, u, v, uscale, vscale);

    // Create a peak marker at this position
    PeakMarker2D *r = new PeakMarker2D(
        *this, u, v,
        m_peakIntensityScale->getScaledMarker(peak.getIntensity(), style));
    r->setPeak(peak, i);
    addMarker(r);
  }

  deselectAll();
}

/**---------------------------------------------------------------------
LamarMoore's avatar
LamarMoore committed
275
276
277
 * Draw peaks on screen.
 * @param painter :: The QPainter to draw with.
 */
278
279
280
281
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
void PeakOverlay::draw(QPainter &painter) const {
  // Draw symbols
  Shape2DCollection::draw(painter);

  if (!m_showLabels)
    return;

  // Sort the labels to avoid overlapping
  QColor color(Qt::red);
  if (!m_shapes.isEmpty()) {
    color = m_shapes[0]->getColor();
  }
  QRectF clipRect(painter.viewport());
  m_labels.clear();
  foreach (Shape2D *shape, m_shapes) {
    if (!shape->isVisible())
      continue;
    if (!clipRect.contains(m_transform.map(shape->origin())))
      continue;
    PeakMarker2D *marker = dynamic_cast<PeakMarker2D *>(shape);
    if (!marker)
      continue;

    QPointF p0 = marker->origin();
    QPointF p1 = m_transform.map(p0);
    QRectF rect = marker->getLabelRect();
    QPointF dp = rect.topLeft() - p0;
    p1 += dp;
    rect.moveTo(p1);

    bool overlap = false;
    // if current label overlaps with another
    // combine them substituting differing numbers with letter 'h','k', or 'l'
Tom Titcombe's avatar
Tom Titcombe committed
311
    for (auto &hkl : m_labels) {
312
313
314
315
316
317
318
319
320
321
322
323
      overlap = hkl.add(marker, rect);
      if (overlap)
        break;
    }

    if (!overlap) {
      PeakHKL hkl(marker, rect, m_showRows);
      m_labels.append(hkl);
    }
  }

  painter.setPen(color);
Tom Titcombe's avatar
Tom Titcombe committed
324
  for (auto &hkl : m_labels) {
325
326
327
328
329
    hkl.draw(painter, m_precision);
  }
}

/**---------------------------------------------------------------------
LamarMoore's avatar
LamarMoore committed
330
331
332
333
 * Return a list of markers put onto a detector
 * @param detID :: A detector ID for which markers are to be returned.
 * @return :: A list of zero ot more markers.
 */
334
335
336
337
338
QList<PeakMarker2D *> PeakOverlay::getMarkersWithID(int detID) const {
  return m_det2marker.values(detID);
}

/**---------------------------------------------------------------------
LamarMoore's avatar
LamarMoore committed
339
340
 * Return the total number of peaks.
 */
341
342
343
344
345
int PeakOverlay::getNumberPeaks() const {
  return m_peaksWorkspace->getNumberPeaks();
}

/** ---------------------------------------------------------------------
LamarMoore's avatar
LamarMoore committed
346
347
348
349
 * Return the i-th peak.
 * @param i :: Peak index.
 * @return A reference to the peak.
 */
350
351
352
353
Mantid::Geometry::IPeak &PeakOverlay::getPeak(int i) {
  return m_peaksWorkspace->getPeak(i);
}

354
355
QList<PeakMarker2D *> PeakOverlay::getSelectedPeakMarkers() {
  QList<PeakMarker2D *> peaks;
356
357
  for (auto &shape : m_selectedShapes) {
    auto marker = dynamic_cast<PeakMarker2D *>(shape);
358
359
    if (marker)
      peaks.append(marker);
360
361
362
363
364
  }

  return peaks;
}

365
366
367
368
/// Sets the scaler that is used to determine the size of peak markers.
void PeakOverlay::setShowRelativeIntensityFlag(bool yes) {
  if (yes) {
    m_peakIntensityScale =
Sam Jenkins's avatar
Sam Jenkins committed
369
        std::make_unique<QualitativeIntensityScale>(m_peaksWorkspace);
370
371
  } else {
    m_peakIntensityScale =
372
        std::make_unique<DefaultIntensityScale>(m_peaksWorkspace);
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
  }

  recreateMarkers(getCurrentStyle());
}

/// Returns the current style or the default style is no markers are present.
PeakMarker2D::Style PeakOverlay::getCurrentStyle() const {
  auto baseStyle = getDefaultStyle(0);

  if (isEmpty()) {
    return baseStyle;
  }

  auto currentStyle = m_det2marker.begin().value()->getStyle();

  return PeakMarker2D::Style(currentStyle.symbol, currentStyle.color,
                             baseStyle.size);
}

/** ---------------------------------------------------------------------
LamarMoore's avatar
LamarMoore committed
393
394
395
396
 * Handler of the AfterReplace notifications. Updates the markers.
 * @param wsName :: The name of the modified workspace.
 * @param ws :: The shared pointer to the modified workspace.
 */
397
void PeakOverlay::afterReplaceHandle(const std::string &wsName,
398
                                     const Mantid::API::Workspace_sptr &ws) {
399
  Q_UNUSED(wsName);
400
  auto peaksWS = std::dynamic_pointer_cast<Mantid::API::IPeaksWorkspace>(ws);
401
402
403
404
405
406
407
408
409
410
411
412
413
414
  if (peaksWS && peaksWS == m_peaksWorkspace && m_surface) {
    m_peakIntensityScale->setPeaksWorkspace(peaksWS);

    recreateMarkers(getCurrentStyle());
  }
}

void PeakOverlay::recreateMarkers(const PeakMarker2D::Style &style) {
  clear();
  createMarkers(style);
  m_surface->requestRedraw(true);
}

/** ---------------------------------------------------------------------
LamarMoore's avatar
LamarMoore committed
415
416
417
 * Return a default style for creating markers by index.
 * Styles are taken form g_defaultStyles
 */
418
419
420
421
422
423
PeakMarker2D::Style PeakOverlay::getDefaultStyle(int index) {
  index %= g_defaultStyles.size();
  return g_defaultStyles[index];
}

/** ---------------------------------------------------------------------
LamarMoore's avatar
LamarMoore committed
424
425
426
427
428
429
430
431
 * Set visibility of the peak markers according to the integration range
 * in the instrument actor.
 *
 * @param xmin :: The lower bound of the integration range.
 * @param xmax :: The upper bound of the integration range.
 * @param units :: Units of the x - array in the underlying workspace:
 *     "TOF", "dSpacing", or "Wavelength".
 */
David Fairbrother's avatar
David Fairbrother committed
432
433
void PeakOverlay::setPeakVisibility(double xmin, double xmax,
                                    const QString &units) {
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
  enum XUnits { Unknown, TOF, dSpacing, Wavelength };
  XUnits xUnits = Unknown;
  if (units == "TOF")
    xUnits = TOF;
  else if (units == "dSpacing")
    xUnits = dSpacing;
  else if (units == "Wavelength")
    xUnits = Wavelength;
  foreach (Shape2D *shape, m_shapes) {
    PeakMarker2D *marker = dynamic_cast<PeakMarker2D *>(shape);
    if (!marker)
      continue;
    Mantid::Geometry::IPeak &peak = getPeak(marker->getRow());
    double x = 0.0;
    switch (xUnits) {
    case TOF:
      x = peak.getTOF();
      break;
    case dSpacing:
      x = peak.getDSpacing();
      break;
    case Wavelength:
      x = peak.getWavelength();
      break;
    // if unknown units always vidsible
    default:
      x = xmin;
    }
    bool on = x >= xmin && x <= xmax;
    marker->setVisible(on);
  }
}

LamarMoore's avatar
LamarMoore committed
467
468
} // namespace MantidWidgets
} // namespace MantidQt