File failed to load: https://isis.astrogeology.usgs.gov/6.0.0/Object/assets/jax/output/NativeMML/config.js
Isis 3 Programmer Reference
ProcessRubberSheet.cpp
1 
6 /* SPDX-License-Identifier: CC0-1.0 */
7 #include <iostream>
8 #include <iomanip>
9 #include <algorithm>
10 
11 #include <QVector>
12 
13 #include "Affine.h"
14 #include "BasisFunction.h"
15 #include "BoxcarCachingAlgorithm.h"
16 #include "Brick.h"
17 #include "Interpolator.h"
18 #include "LeastSquares.h"
19 #include "Portal.h"
20 #include "ProcessRubberSheet.h"
21 #include "TileManager.h"
22 #include "Transform.h"
23 #include "UniqueIOCachingAlgorithm.h"
24 
25 using namespace std;
26 
27 
28 namespace Isis {
35  ProcessRubberSheet::ProcessRubberSheet(int startSize, int endSize) {
36 
37  p_bandChangeFunct = NULL;
38 
39  // Information used only in the tile transform method (StartProcess)
40  p_forceSamp = Null;
41  p_forceLine = Null;
42  p_startQuadSize = startSize;
43  p_endQuadSize = endSize;
44 
45  // Patch parameters used only in the patch transform (processPatchTransform)
46  m_patchStartSample = 1;
47  m_patchStartLine = 1;
48  m_patchSamples = 5;
49  m_patchLines = 5;
50  m_patchSampleIncrement = 4;
51  m_patchLineIncrement = 4;
52  };
53 
54 
102  void ProcessRubberSheet::setPatchParameters(int startSample, int startLine,
103  int samples, int lines,
104  int sampleIncrement, int lineIncrement) {
105 
106  m_patchStartSample = startSample;
107  m_patchStartLine = startLine;
108  m_patchSamples = samples;
109  m_patchLines = lines;
110  m_patchSampleIncrement = sampleIncrement;
111  m_patchLineIncrement = lineIncrement;
112  }
113 
114 
132  void ProcessRubberSheet::StartProcess(Transform &trans,
133  Interpolator &interp) {
134 
135  // Error checks ... there must be one input and one output
136  if (InputCubes.size() != 1) {
137  string m = "You must specify exactly one input cube";
138  throw IException(IException::Programmer, m, _FILEINFO_);
139  }
140  else if (OutputCubes.size() != 1) {
141  string m = "You must specify exactly one output cube";
142  throw IException(IException::Programmer, m, _FILEINFO_);
143  }
144 
145  // allocate the sampMap/lineMap vectors
146  p_lineMap.resize(p_startQuadSize);
147  p_sampMap.resize(p_startQuadSize);
148 
149  for (unsigned int pos = 0; pos < p_lineMap.size(); pos++) {
150  p_lineMap[pos].resize(p_startQuadSize);
151  p_sampMap[pos].resize(p_startQuadSize);
152  }
153 
154  // Create a tile manager for the output file
155  TileManager otile(*OutputCubes[0], p_startQuadSize, p_startQuadSize);
156 
157  // Create a portal buffer for the input file
158  Portal iportal(interp.Samples(), interp.Lines(),
159  InputCubes[0]->pixelType() ,
160  interp.HotSample(), interp.HotLine());
161 
162  // Start the progress meter
163  p_progress->SetMaximumSteps(otile.Tiles());
164  p_progress->CheckStatus();
165 
166  if (p_bandChangeFunct == NULL) {
167  // A portal could read up to four chunks so we need to cache four times the number of bands to
168  // minimize I/O thrashing
169  InputCubes[0]->addCachingAlgorithm(new UniqueIOCachingAlgorithm(2 * InputCubes[0]->bandCount()));
170  OutputCubes[0]->addCachingAlgorithm(new BoxcarCachingAlgorithm());
171 
172  long long int tilesPerBand = otile.Tiles() / OutputCubes[0]->bandCount();
173 
174  for (long long int tile = 1; tile <= tilesPerBand; tile++) {
175  bool useLastTileMap = false;
176  for (int band = 1; band <= OutputCubes[0]->bandCount(); band++) {
177  otile.SetTile(tile, band);
178 
179  // If either image or quad sizes are small, skip to SlowGeom.
180  if (p_startQuadSize <= 2 || min(OutputCubes[0]->lineCount(), OutputCubes[0]->sampleCount()) <= p_startQuadSize) {
181  SlowGeom(otile, iportal, trans, interp);
182  }
183  else {
184  QuadTree(otile, iportal, trans, interp, useLastTileMap);
185  }
186 
187  useLastTileMap = true;
188 
189  OutputCubes[0]->write(otile);
190  p_progress->CheckStatus();
191  }
192  }
193  }
194  else {
195  int lastOutputBand = -1;
196 
197  for (otile.begin(); !otile.end(); otile++) {
198  // Keep track of the current band
199  if (lastOutputBand != otile.Band()) {
200  lastOutputBand = otile.Band();
201  // Call an application function if the band number changes
202  p_bandChangeFunct(lastOutputBand);
203  }
204 
205  if (p_startQuadSize <= 2) {
206  SlowGeom(otile, iportal, trans, interp);
207  }
208  else {
209  QuadTree(otile, iportal, trans, interp, false);
210  }
211 
212  OutputCubes[0]->write(otile);
213  p_progress->CheckStatus();
214  }
215  }
216 
217  p_sampMap.clear();
218  p_lineMap.clear();
219  }
220 
221 
232  void ProcessRubberSheet::BandChange(void (*funct)(const int band)) {
233 
234  p_bandChangeFunct = funct;
235  }
236 
237 
238  void ProcessRubberSheet::SlowGeom(TileManager &otile, Portal &iportal,
239  Transform &trans, Interpolator &interp) {
240 
241  double outputSamp, outputLine;
242  double inputSamp, inputLine;
243  int outputBand = otile.Band();
244 
245  for (int i = 0; i < otile.size(); i++) {
246  outputSamp = otile.Sample(i);
247  outputLine = otile.Line(i);
248  // Use the defined transform to find out what input pixel the output
249  // pixel came from
250  if (trans.Xform(inputSamp, inputLine, outputSamp, outputLine)) {
251  if ((inputSamp < 0.5) || (inputLine < 0.5) ||
252  (inputLine > InputCubes[0]->lineCount() + 0.5) ||
253  (inputSamp > InputCubes[0]->sampleCount() + 0.5)) {
254  otile[i] = NULL8;
255  }
256  else {
257  // Set the position of the portal in the input cube
258  iportal.SetPosition(inputSamp, inputLine, outputBand);
259  InputCubes[0]->read(iportal);
260  otile[i] = interp.Interpolate(inputSamp, inputLine,
261  iportal.DoubleBuffer());
262  }
263  }
264  else {
265  otile[i] = NULL8;
266  }
267  }
268  }
269 
270 
271  void ProcessRubberSheet::QuadTree(TileManager &otile, Portal &iportal,
272  Transform &trans, Interpolator &interp,
273  bool useLastTileMap) {
274 
275  // Initializations
276  vector<Quad *> quadTree;
277 
278  if (!useLastTileMap) {
279  // Set up the boundaries of the full tile
280  Quad *quad = new Quad;
281  quad->sline = otile.Line();
282  quad->ssamp = otile.Sample();
283 
284  quad->eline = otile.Line(otile.size() - 1);
285  quad->esamp = otile.Sample(otile.size() - 1);
286  quad->slineTile = otile.Line();
287  quad->ssampTile = otile.Sample();
288 
289  quadTree.push_back(quad);
290 
291  // Loop and compute the input coordinates filling the maps
292  // until the quad tree is empty
293  while (quadTree.size() > 0) {
294  ProcessQuad(quadTree, trans, p_lineMap, p_sampMap);
295  }
296  }
297 
298  // Apply the map to the output tile
299  int outputBand = otile.Band();
300  for (int i = 0, line = 0; line < p_startQuadSize; line++) {
301  for (int samp = 0; samp < p_startQuadSize; samp++, i++) {
302  double inputLine = p_lineMap[line][samp];
303  double inputSamp = p_sampMap[line][samp];
304  if (inputLine != NULL8) {
305  iportal.SetPosition(inputSamp, inputLine, outputBand);
306  InputCubes[0]->read(iportal);
307  otile[i] = interp.Interpolate(inputSamp, inputLine,
308  iportal.DoubleBuffer());
309  }
310  else {
311  otile[i] = NULL8;
312  }
313  }
314  }
315  }
316 
317 
330  bool ProcessRubberSheet::TestLine(Transform &trans, int ssamp, int esamp,
331  int sline, int eline, int increment) {
332 
333  for (int line = sline; line <= eline; line += increment) {
334  for (int sample = ssamp; sample <= esamp; sample += increment) {
335  double sjunk = 0.0;
336  double ljunk = 0.0;
337 
338  if (trans.Xform(sjunk, ljunk, sample, line)) {
339  return true;
340  }
341  }
342  }
343 
344  return false;
345  }
346 
347 
348  // Process a quad trying to find input positions for output positions
349  void ProcessRubberSheet::ProcessQuad(std::vector<Quad *> &quadTree,
350  Transform &trans,
351  std::vector< std::vector<double> > &lineMap,
352  std::vector< std::vector<double> > &sampMap) {
353 
354  Quad *quad = quadTree[0];
355  double oline[4], osamp[4];
356  double iline[4], isamp[4];
357 
358  // Try to convert the upper left corner to input coordinates
359  int badCorner = 0;
360  oline[0] = quad->sline;
361  osamp[0] = quad->ssamp;
362  if (!trans.Xform(isamp[0], iline[0], osamp[0], oline[0])) {
363  badCorner++;
364  }
365 
366  // Now try the upper right corner
367  oline[1] = quad->sline;
368  osamp[1] = quad->esamp;
369  if (!trans.Xform(isamp[1], iline[1], osamp[1], oline[1])) {
370  badCorner++;
371  }
372 
373  // Now try the lower left corner
374  oline[2] = quad->eline;
375  osamp[2] = quad->ssamp;
376  if (!trans.Xform(isamp[2], iline[2], osamp[2], oline[2])) {
377  badCorner++;
378  }
379 
380  // Now try the lower right corner
381  oline[3] = quad->eline;
382  osamp[3] = quad->esamp;
383  if (!trans.Xform(isamp[3], iline[3], osamp[3], oline[3])) {
384  badCorner++;
385  }
386 
387  // If all four corners are bad then walk the edges. If any points
388  // on the edges transform we will split the quad or
389  // if the quad is already small just transform everything
390  if (badCorner == 4) {
391  if ((quad->eline - quad->sline) < p_endQuadSize) {
392  SlowQuad(quadTree, trans, lineMap, sampMap);
393  }
394  else {
395  if (p_forceSamp != Null && p_forceLine != Null) {
396  if (p_forceSamp >= quad->ssamp && p_forceSamp <= quad->esamp &&
397  p_forceLine >= quad->sline && p_forceLine <= quad->eline) {
398  SplitQuad(quadTree);
399  return;
400  }
401  }
402 
403  int centerSample = (quad->ssamp + quad->esamp) / 2;
404  int centerLine = (quad->sline + quad->eline) / 2;
405 
406  // All 4 corner points have failed tests.
407  //
408  // If we find data around the quad by walking around a 2x2 grid in the
409  // box, then we need to split the quad. Check outside the box and
410  // interior crosshair.
411  //
412  // This is what we're walking:
413  // -----------
414  // | | |
415  // | | |
416  // |----|----|
417  // | | |
418  // | | |
419  // -----------
420  // Top Edge
421  if (TestLine(trans, quad->ssamp + 1, quad->esamp - 1,
422  quad->sline, quad->sline, 4) ||
423  // Bottom Edge
424  TestLine(trans, quad->ssamp + 1, quad->esamp - 1,
425  quad->eline, quad->eline, 4) ||
426  // Left Edge
427  TestLine(trans, quad->ssamp, quad->ssamp,
428  quad->sline + 1, quad->eline - 1, 4) ||
429  // Right Edge
430  TestLine(trans, quad->esamp, quad->esamp,
431  quad->sline + 1, quad->eline - 1, 4) ||
432  // Center Column
433  TestLine(trans, centerSample, centerSample,
434  quad->sline + 1, quad->eline - 1, 4) ||
435  // Center Row
436  TestLine(trans, quad->ssamp + 1, quad->esamp - 1,
437  centerLine, centerLine, 4)) {
438 
439 
440  SplitQuad(quadTree);
441  return;
442  }
443 
444  // Nothing in quad, fill with nulls
445  for (int i = quad->sline; i <= quad->eline; i++) {
446  for (int j = quad->ssamp; j <= quad->esamp; j++) {
447  lineMap[i-quad->slineTile][j-quad->ssampTile] = NULL8;
448  }
449  }
450  delete quad;
451  quadTree.erase(quadTree.begin());
452  }
453  return;
454  }
455 
456  // If all four corners are bad then assume the whole tile is bad
457  // Load the maps with nulls and delete the quad from the list
458  // Free memory too
459  // if (badCorner == 4) {
460  // for (int i=quad->sline; i<=quad->eline; i++) {
461  // for (int j=quad->ssamp; j<=quad->esamp; j++) {
462  // lineMap[i-quad->slineTile][j-quad->ssampTile] = NULL8;
463  // }
464  // }
465  // delete quad;
466  // quadTree.erase(quadTree.begin());
467  // return;
468  // }
469 
470  // See if any other corners are bad in which case we will need to
471  // split the quad into finer pieces. But lets not get ridiculous.
472  // If the split distance is small we might as well compute at every
473  // point
474  if (badCorner > 0) {
475  if ((quad->eline - quad->sline) < p_endQuadSize) {
476  SlowQuad(quadTree, trans, lineMap, sampMap);
477  }
478  else {
479  SplitQuad(quadTree);
480  }
481  return;
482  }
483 
484  // We have good corners ... create two equations using them
485  // iline = a*oline + b*osamp + c*oline*osamp + d
486  // isamp = e*oline + f*osamp + g*oline*osamp + h
487  // Start by setting up a 4x4 matrix
488  double A[4][4];
489  for (int i = 0; i < 4; i++) {
490  A[i][0] = oline[i];
491  A[i][1] = osamp[i];
492  A[i][2] = oline[i] * osamp[i];
493  A[i][3] = 1.0;
494  }
495 
496  // Make sure the determinate is non-zero, otherwise split it up again
497  // and hope for the best. If this happens it probably is because the
498  // transform is lame (bugged)
499  double detA;
500  if ((detA = Det4x4(A)) == 0.0) {
501  if ((quad->eline - quad->sline) < p_endQuadSize) {
502  SlowQuad(quadTree, trans, lineMap, sampMap);
503  }
504  else {
505  SplitQuad(quadTree);
506  }
507  return;
508  }
509 
510  // Substitute our desired answers into B to get the coefficients for the
511  // line dimension (Cramers Rule!!)
512  double B[4][4];
513  double lineCoef[4];
514  for (int j = 0; j < 4; j++) {
515  memmove(B, A, 16 * sizeof(double));
516 
517  for (int i = 0; i < 4; i++) {
518  B[i][j] = iline[i];
519  }
520 
521  lineCoef[j] = Det4x4(B) / detA;
522  }
523 
524  // Do it again to get the sample coefficients
525  double sampCoef[4];
526  for (int j = 0; j < 4; j++) {
527  memmove(B, A, 16 * sizeof(double));
528 
529  for (int i = 0; i < 4; i++) {
530  B[i][j] = isamp[i];
531  }
532 
533  sampCoef[j] = Det4x4(B) / detA;
534  }
535 
536  // Test the middle point to see if the equations are good
537  double quadMidLine = (quad->sline + quad->eline) / 2.0;
538  double quadMidSamp = (quad->ssamp + quad->esamp) / 2.0;
539  double midLine, midSamp;
540 
541  if (!trans.Xform(midSamp, midLine, quadMidSamp, quadMidLine)) {
542  if ((quad->eline - quad->sline) < p_endQuadSize) {
543  SlowQuad(quadTree, trans, lineMap, sampMap);
544  }
545  else {
546  SplitQuad(quadTree);
547  }
548  return;
549  }
550 
551  double cmidLine = lineCoef[0] * quadMidLine +
552  lineCoef[1] * quadMidSamp +
553  lineCoef[2] * quadMidLine * quadMidSamp +
554  lineCoef[3];
555 
556  double cmidSamp = sampCoef[0] * quadMidLine +
557  sampCoef[1] * quadMidSamp +
558  sampCoef[2] * quadMidLine * quadMidSamp +
559  sampCoef[3];
560 
561  if ((abs(cmidSamp - midSamp) > 0.5) || (abs(cmidLine - midLine) > 0.5)) {
562  if ((quad->eline - quad->sline) < p_endQuadSize) {
563  SlowQuad(quadTree, trans, lineMap, sampMap);
564  }
565  else {
566  SplitQuad(quadTree);
567  }
568  return;
569  }
570 
571  // Equations are suitably accurate.
572  // First compute input at the top corner of the output quad
573  double ulLine = lineCoef[0] * (double) quad->sline +
574  lineCoef[1] * (double) quad->ssamp +
575  lineCoef[2] * (double) quad->sline * (double) quad->ssamp +
576  lineCoef[3];
577 
578  double ulSamp = sampCoef[0] * (double) quad->sline +
579  sampCoef[1] * (double) quad->ssamp +
580  sampCoef[2] * (double) quad->sline * (double) quad->ssamp +
581  sampCoef[3];
582 
583  // Compute the derivate of the equations with respect to the
584  // output line as we will be changing the output line in a loop
585  double lineChangeWrLine = lineCoef[0] + lineCoef[2] * (double) quad->ssamp;
586  double sampChangeWrLine = sampCoef[0] + sampCoef[2] * (double) quad->ssamp;
587 
588  for (int ol = quad->sline; ol <= quad->eline; ol++) {
589  // Now Compute the derivates of the equations with respect to the
590  // output sample at the current line
591  double lineChangeWrSamp = lineCoef[1] + lineCoef[2] * (double) ol;
592  double sampChangeWrSamp = sampCoef[1] + sampCoef[2] * (double) ol;
593 
594  // Set first computed line to the left-edge position
595  double cline = ulLine;
596  double csamp = ulSamp;
597 
598  // Get pointers to speed processing
599  int startSamp = quad->ssamp - quad->ssampTile;
600  std::vector<double> &lineVect = lineMap[ol-quad->slineTile];
601  std::vector<double> &sampleVect = sampMap[ol-quad->slineTile];
602 
603  // Loop computing input positions for respective output positions
604  for (int os = quad->ssamp; os <= quad->esamp; os++) {
605  lineVect[startSamp] = cline;
606  sampleVect[startSamp] = csamp;
607 
608  startSamp ++;
609 
610  cline += lineChangeWrSamp;
611  csamp += sampChangeWrSamp;
612  }
613 
614  // Reposition at the left edge of the tile for the next line
615  ulLine += lineChangeWrLine;
616  ulSamp += sampChangeWrLine;
617  }
618 
619  // All done so remove the quad
620  delete quad;
621  quadTree.erase(quadTree.begin());
622  }
623 
624 
625  // Break input quad into four pieces
626  void ProcessRubberSheet::SplitQuad(std::vector<Quad *> &quadTree) {
627 
628  // Get the quad to split
629  Quad *quad = quadTree[0];
630  int n = (quad->eline - quad->sline + 1) / 2;
631 
632  // New upper left quad
633  Quad *q1 = new Quad;
634  *q1 = *quad;
635  q1->eline = quad->sline + n - 1;
636  q1->esamp = quad->ssamp + n - 1;
637  quadTree.push_back(q1);
638 
639  // New upper right quad
640  Quad *q2 = new Quad;
641  *q2 = *quad;
642  q2->eline = quad->sline + n - 1;
643  q2->ssamp = quad->ssamp + n;
644  quadTree.push_back(q2);
645 
646  // New lower left quad
647  Quad *q3 = new Quad;
648  *q3 = *quad;
649  q3->sline = quad->sline + n;
650  q3->esamp = quad->ssamp + n - 1;
651  quadTree.push_back(q3);
652 
653  // New lower right quad
654  Quad *q4 = new Quad;
655  *q4 = *quad;
656  q4->sline = quad->sline + n;
657  q4->ssamp = quad->ssamp + n;
658  quadTree.push_back(q4);
659 
660  // Remove the old quad since it has been split up
661  delete quad;
662  quadTree.erase(quadTree.begin());
663  }
664 
665 
666  // Slow quad computation for every output pixel
667  void ProcessRubberSheet::SlowQuad(std::vector<Quad *> &quadTree,
668  Transform &trans,
669  std::vector< std::vector<double> > &lineMap,
670  std::vector< std::vector<double> > &sampMap) {
671 
672  // Get the quad
673  Quad *quad = quadTree[0];
674  double iline, isamp;
675 
676  // Loop and do the slow computation of input position from output position
677  for (int oline = quad->sline; oline <= quad->eline; oline++) {
678  int lineIndex = oline - quad->slineTile;
679  for (int osamp = quad->ssamp; osamp <= quad->esamp; osamp++) {
680  int sampIndex = osamp - quad->ssampTile;
681  lineMap[lineIndex][sampIndex] = NULL8;
682  if (trans.Xform(isamp, iline, (double) osamp, (double) oline)) {
683  if ((isamp >= 0.5) ||
684  (iline >= 0.5) ||
685  (iline <= InputCubes[0]->lineCount() + 0.5) ||
686  (isamp <= InputCubes[0]->sampleCount() + 0.5)) {
687  lineMap[lineIndex][sampIndex] = iline;
688  sampMap[lineIndex][sampIndex] = isamp;
689  }
690  }
691  }
692  }
693 
694  // All done with the quad
695  delete quad;
696  quadTree.erase(quadTree.begin());
697  }
698 
699 
700  // Determinate method for 4x4 matrix using cofactor expansion
701  double ProcessRubberSheet::Det4x4(double m[4][4]) {
702 
703  double cofact[3][3];
704 
705  cofact [0][0] = m[1][1];
706  cofact [0][1] = m[1][2];
707  cofact [0][2] = m[1][3];
708  cofact [1][0] = m[2][1];
709  cofact [1][1] = m[2][2];
710  cofact [1][2] = m[2][3];
711  cofact [2][0] = m[3][1];
712  cofact [2][1] = m[3][2];
713  cofact [2][2] = m[3][3];
714  double det = m[0][0] * Det3x3(cofact);
715 
716  cofact [0][0] = m[1][0];
717  cofact [0][1] = m[1][2];
718  cofact [0][2] = m[1][3];
719  cofact [1][0] = m[2][0];
720  cofact [1][1] = m[2][2];
721  cofact [1][2] = m[2][3];
722  cofact [2][0] = m[3][0];
723  cofact [2][1] = m[3][2];
724  cofact [2][2] = m[3][3];
725  det -= m[0][1] * Det3x3(cofact);
726 
727  cofact [0][0] = m[1][0];
728  cofact [0][1] = m[1][1];
729  cofact [0][2] = m[1][3];
730  cofact [1][0] = m[2][0];
731  cofact [1][1] = m[2][1];
732  cofact [1][2] = m[2][3];
733  cofact [2][0] = m[3][0];
734  cofact [2][1] = m[3][1];
735  cofact [2][2] = m[3][3];
736  det += m[0][2] * Det3x3(cofact);
737 
738  cofact [0][0] = m[1][0];
739  cofact [0][1] = m[1][1];
740  cofact [0][2] = m[1][2];
741  cofact [1][0] = m[2][0];
742  cofact [1][1] = m[2][1];
743  cofact [1][2] = m[2][2];
744  cofact [2][0] = m[3][0];
745  cofact [2][1] = m[3][1];
746  cofact [2][2] = m[3][2];
747  det -= m[0][3] * Det3x3(cofact);
748 
749  return det;
750  }
751 
752 
753  // Determinate for 3x3 matrix
754  double ProcessRubberSheet::Det3x3(double m[3][3]) {
755 
756  return m[0][0] * m[1][1] * m[2][2] -
757  m[0][0] * m[1][2] * m[2][1] -
758  m[0][1] * m[1][0] * m[2][2] +
759  m[0][1] * m[1][2] * m[2][0] +
760  m[0][2] * m[1][0] * m[2][1] -
761  m[0][2] * m[1][1] * m[2][0];
762  }
763 
764 
787  void ProcessRubberSheet::processPatchTransform(Transform &trans,
788  Interpolator &interp) {
789 
790  // Error checks ... there must be one input and one output
791  if (InputCubes.size() != 1) {
792  string m = "You must specify exactly one input cube";
793  throw IException(IException::Programmer, m, _FILEINFO_);
794  }
795  else if (OutputCubes.size() != 1) {
796  string m = "You must specify exactly one output cube";
797  throw IException(IException::Programmer, m, _FILEINFO_);
798  }
799 
800  // Create a portal buffer for reading from the input file
801  Portal iportal(interp.Samples(), interp.Lines(),
802  InputCubes[0]->pixelType() ,
803  interp.HotSample(), interp.HotLine());
804 
805  // Setup the progress meter
806  int patchesPerBand = 0;
807  for (int line = m_patchStartLine; line <= InputCubes[0]->lineCount();
808  line += m_patchLineIncrement) {
809  for (int samp = m_patchStartSample;
810  samp <= InputCubes[0]->sampleCount();
811  samp += m_patchSampleIncrement) {
812  patchesPerBand++;
813  }
814  }
815 
816  int patchCount = InputCubes[0]->bandCount() * patchesPerBand;
817  p_progress->SetMaximumSteps(patchCount);
818  p_progress->CheckStatus();
819 
820  // For each band we will loop through the input file and work on small
821  // spatial patches (nxm). The objective is to determine where each patch
822  // falls in the output file and transform that output patch. As we loop
823  // we want some overlap in the input patches which guarantees there will
824  // be no gaps in the output cube.
825  //
826  // We use the variables m_patchLines and m_patchSamples to define
827  // the patch size
828  //
829  // We use the variables m_patchStartLine and m_patchStartSample to define
830  // where to start in the cube. In almost all cases these should be (1,1).
831  // An expection would be for PushFrameCameras which need to have
832  // a different starting line for the even framed cubes
833  //
834  // We use the variables m_patchLineIncrement and m_patchSampleIncrement
835  // to skip to the next patch. Typically these are one less than the
836  // patch size which guarantees a pixel of overlap in the patches. An
837  // exception would be for PushFrameCameras which should increment lines by
838  // twice the framelet height.
839 
840  for (int band=1; band <= InputCubes[0]->bandCount(); band++) {
841  if (p_bandChangeFunct != NULL) p_bandChangeFunct(band);
842  iportal.SetPosition(1,1,band);
843 
844  for (int line = m_patchStartLine;
845  line <= InputCubes[0]->lineCount();
846  line += m_patchLineIncrement) {
847  for (int samp = m_patchStartSample;
848  samp <= InputCubes[0]->sampleCount();
849  samp += m_patchSampleIncrement, p_progress->CheckStatus()) {
850  transformPatch((double)samp, (double)(samp + m_patchSamples - 1),
851  (double)line, (double)(line + m_patchLines - 1),
852  iportal, trans, interp);
853  }
854  }
855  }
856  }
857 
858 
859  // Private method to process a small patch of the input cube
860  void ProcessRubberSheet::transformPatch(double ssamp, double esamp,
861  double sline, double eline,
862  Portal &iportal,
863  Transform &trans,
864  Interpolator &interp) {
865  // Let's make sure our patch is contained in the input file
866  // TODO: Think about the image edges should I be adding 0.5
867  if (esamp > InputCubes[0]->sampleCount()) {
868  esamp = InputCubes[0]->sampleCount();
869  if (ssamp == esamp) ssamp = esamp - 1;
870  }
871  if (eline > InputCubes[0]->lineCount()) {
872  eline = InputCubes[0]->lineCount();
873  if (sline == eline) sline = eline - 1;
874  }
875 
876  // Use these to build a list of four corner control points
877  // If any corner doesn't trasform, split the patch into for smaller patches
878  QVector<double> isamps;
879  QVector<double> ilines;
880  QVector<double> osamps;
881  QVector<double> olines;
882 
883  // Upper left control point
884  double tline, tsamp;
885  if (trans.Xform(tsamp,tline,ssamp,sline)) {
886  isamps.push_back(ssamp);
887  ilines.push_back(sline);
888  osamps.push_back(tsamp);
889  olines.push_back(tline);
890  }
891 
892  // Upper right control point
893  if (trans.Xform(tsamp,tline,esamp,sline)) {
894  isamps.push_back(esamp);
895  ilines.push_back(sline);
896  osamps.push_back(tsamp);
897  olines.push_back(tline);
898  }
899 
900  // Lower left control point
901  if (trans.Xform(tsamp,tline,ssamp,eline)) {
902  isamps.push_back(ssamp);
903  ilines.push_back(eline);
904  osamps.push_back(tsamp);
905  olines.push_back(tline);
906  }
907 
908  // Lower right control point
909  if (trans.Xform(tsamp,tline,esamp,eline)) {
910  isamps.push_back(esamp);
911  ilines.push_back(eline);
912  osamps.push_back(tsamp);
913  olines.push_back(tline);
914  }
915 
916  // If none of the 4 input tile corners transformed inside the output cube, give up on this tile
917  // NOTE: Probably should split if the tile is large
918  if (isamps.size() == 0) {
919  return;
920  }
921 
922  // If at least one of the 4 input tile corners did NOT transform, split it
923  if (isamps.size() < 4) {
924  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
925  return;
926  }
927 
928  // Get the min/max output line/sample patch (bounding box of the transformed output samp,line)
929  int osampMin = (int) osamps[0];
930  int osampMax = (int) (osamps[0] + 0.5);
931  int olineMin = (int) olines[0];
932  int olineMax = (int) (olines[0] + 0.5);
933  for (int i = 1; i < osamps.size(); i++) {
934  if (osamps[i] < osampMin) osampMin = (int) osamps[i];
935  if (osamps[i] > osampMax) osampMax = (int) (osamps[i] + 0.5);
936  if (olines[i] < olineMin) olineMin = (int) olines[i];
937  if (olines[i] > olineMax) olineMax = (int) (olines[i] + 0.5);
938  }
939 
940  // Check to see if the output patch is completely outside the image. No
941  // sense in computing the affine if we don't have to.
942  if (osampMax < 1) return;
943  if (olineMax < 1) return;
944  if (osampMin > OutputCubes[0]->sampleCount()) return;
945  if (olineMin > OutputCubes[0]->lineCount()) return;
946 
947  // Adjust our output patch if it extends outside the output cube (overlaps cube boundry)
948  if (osampMin < 1) osampMin = 1;
949  if (olineMin < 1) olineMin = 1;
950  if (osampMax > OutputCubes[0]->sampleCount()) {
951  osampMax = OutputCubes[0]->sampleCount();
952  }
953  if (olineMax > OutputCubes[0]->lineCount()) {
954  olineMax = OutputCubes[0]->lineCount();
955  }
956 
957  /* A small input patch should create a small output patch. If we had
958  * the 0-360 seam (or -180/180) in our patch it could be split across
959  * a cylindrical projection (e.g., equirectangular, simple, etc). So
960  * If the output patch looks like it will span the full output image,
961  * either lines or sample then resplit the input patch. When the patch
962  * spans more than 50% (was 99% but there were problems with double
963  * rounding error on different machines) of the image it is split.
964  */
965 
966  if (osampMax - osampMin + 1.0 > OutputCubes[0]->sampleCount() * 0.50) {
967  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
968  return;
969  }
970  if (olineMax - olineMin + 1.0 > OutputCubes[0]->lineCount() * 0.50) {
971  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
972  return;
973  }
974 
975  // Can we create an affine transform from output to input coordinates
976  BasisFunction isampFunc("Ax+By+C",3,3);
977  LeastSquares isampLSQ(isampFunc);
978 
979  BasisFunction ilineFunc("Dx+Ey+F",3,3);
980  LeastSquares ilineLSQ(ilineFunc);
981 
982  try {
983  for (int i=0; i < isamps.size(); i++) {
984  std::vector<double> vars;
985  vars.push_back(osamps[i]);
986  vars.push_back(olines[i]);
987  vars.push_back(1.0);
988 
989  isampLSQ.AddKnown(vars,isamps[i]);
990  ilineLSQ.AddKnown(vars,ilines[i]);
991  }
992 
993  isampLSQ.Solve(LeastSquares::QRD);
994  ilineLSQ.Solve(LeastSquares::QRD);
995  }
996  catch (IException &e) {
997  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
998  return;
999  }
1000 
1001  // If the fit at any corner isn't good enough break it down
1002  for (int i=0; i<isamps.size(); i++) {
1003  if (fabs(isampLSQ.Residual(i)) > 0.5) {
1004  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
1005  return;
1006  }
1007  if (fabs(ilineLSQ.Residual(i)) > 0.5) {
1008  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
1009  return;
1010  }
1011  }
1012 
1013 #if 0
1014  // What about the center point
1015  // TODO: The patches are so small so maybe we don't care if the
1016  // corners worked
1017  double csamp = (ssamp + esamp) / 2.0;
1018  double cline = (sline + eline) / 2.0;
1019  if (trans.Xform(tsamp,tline,csamp,cline)) {
1020  std::vector<double> vars;
1021  vars.push_back(tsamp);
1022  vars.push_back(tline);
1023  vars.push_back(1.0);
1024 
1025  double isamp = isampFunc.Evaluate(vars);
1026  double iline = ilineFunc.Evaluate(vars);
1027 
1028  double err = (csamp - isamp) * (csamp - isamp) +
1029  (cline - iline) * (cline - iline);
1030  if (err > 0.25) {
1031  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
1032  return;
1033  }
1034  }
1035  else {
1036  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
1037  return;
1038  }
1039 #endif
1040 
1041  double A = isampFunc.Coefficient(0);
1042  double B = isampFunc.Coefficient(1);
1043  double C = isampFunc.Coefficient(2);
1044 
1045  double D = ilineFunc.Coefficient(0);
1046  double E = ilineFunc.Coefficient(1);
1047  double F = ilineFunc.Coefficient(2);
1048 
1049  // Now we can do our typical backwards geom. Loop over the output cube
1050  // coordinates and compute input cube coordinates for the corners of the current
1051  // buffer. The buffer is the same size as the current patch size.
1052  Brick oBrick(*OutputCubes[0], osampMax-osampMin+1, olineMax-olineMin+1, 1);
1053  oBrick.SetBasePosition(osampMin, olineMin, iportal.Band());
1054 
1055  int brickIndex = 0;
1056  bool foundNull = false;
1057  for (int oline = olineMin; oline <= olineMax; oline++) {
1058  double isamp = A * osampMin + B * oline + C;
1059  double iline = D * osampMin + E * oline + F;
1060  double isampChangeWRTosamp = A;
1061  double ilineChangeWRTosamp = D;
1062  for (int osamp = osampMin; osamp <= osampMax;
1063  osamp++, isamp += isampChangeWRTosamp, iline += ilineChangeWRTosamp) {
1064  // Now read the data around the input coordinate and interpolate a DN
1065  iportal.SetPosition(isamp, iline, iportal.Band());
1066  InputCubes[0]->read(iportal);
1067  double dn = interp.Interpolate(isamp, iline, iportal.DoubleBuffer());
1068  oBrick[brickIndex] = dn;
1069  if (dn == Null) foundNull = true;
1070  brickIndex++;
1071  }
1072  }
1073 
1074  // If there are any special pixel Null values in this output brick, we may be
1075  // up against an edge of the input image where the interpolaters get Nulls from
1076  // outside the image. Since the patches have some overlap due to finding the
1077  // rectangular area (bounding box, min/max line/samp) of the four points input points
1078  // projected into the output space, this causes valid DNs
1079  // from a previously processed patch to be replaced with Null DNs from this patch.
1080  // NOTE: A different method of accomplishing this fixing of Nulls was tested. We read
1081  // the buffer from the output and tested each pixel values before overwriting it
1082  // This resulted in a slighly slower run for the test. A concern was found in the
1083  // asynchronous write of buffers to the cube, where a race condition may have generated
1084  // different dns, not bad, but making testing more difficult.
1085  if (foundNull) {
1086  Brick readBrick(*OutputCubes[0], osampMax-osampMin+1, olineMax-olineMin+1, 1);
1087  readBrick.SetBasePosition(osampMin, olineMin, iportal.Band());
1088  OutputCubes[0]->read(readBrick);
1089  for (brickIndex = 0; brickIndex<(osampMax-osampMin+1)*(olineMax-olineMin+1); brickIndex++) {
1090  if (readBrick[brickIndex] != Null) {
1091  oBrick[brickIndex] = readBrick[brickIndex];
1092  }
1093  }
1094  }
1095 
1096  // Write filled buffer to cube
1097  OutputCubes[0]->write(oBrick);
1098  }
1099 
1100 
1101  // Private method used to split up input patches if the patch is too big to
1102  // process
1103  void ProcessRubberSheet::splitPatch(double ssamp, double esamp,
1104  double sline, double eline, Portal &iportal,
1105  Transform &trans, Interpolator &interp) {
1106 
1107  // Is the input patch too small to even worry about transforming?
1108  if ((esamp - ssamp < 0.1) && (eline - sline < 0.1)) return;
1109 
1110  // It's big enough so break it into four pieces
1111  double midSamp = (esamp + ssamp) / 2.0;
1112  double midLine = (eline + sline) / 2.0;
1113 
1114  transformPatch(ssamp, midSamp,
1115  sline, midLine,
1116  iportal, trans, interp);
1117  transformPatch(midSamp, esamp,
1118  sline, midLine,
1119  iportal, trans, interp);
1120  transformPatch(ssamp, midSamp,
1121  midLine, eline,
1122  iportal, trans, interp);
1123  transformPatch(midSamp, esamp,
1124  midLine, eline,
1125  iportal, trans, interp);
1126 
1127  return;
1128  }
1129 
1130 
1131 } // end namespace isis
Isis::Portal
Buffer for containing a two dimensional section of an image.
Definition: Portal.h:36
Isis::BoxcarCachingAlgorithm
This algorithm is designed for applications that jump around between a couple of spots in the cube wi...
Definition: BoxcarCachingAlgorithm.h:32
Isis::Buffer::DoubleBuffer
double * DoubleBuffer() const
Returns the value of the shape buffer.
Definition: Buffer.h:138
Isis::Interpolator::Lines
int Lines()
Returns the number of lines needed by the interpolator.
Definition: Interpolator.cpp:236
Isis::TileManager
Buffer manager, for moving through a cube in tiles.
Definition: TileManager.h:39
Isis::TileManager::SetTile
bool SetTile(const int Tile, const int band=1)
Sets the current tile as requested.
Definition: TileManager.cpp:50
Isis::BufferManager::begin
bool begin()
Moves the shape buffer to the first position.
Definition: BufferManager.h:96
Isis::Interpolator::HotSample
double HotSample()
Returns the sample coordinate of the center pixel in the buffer for the interpolator.
Definition: Interpolator.cpp:265
Isis::BufferManager::end
bool end() const
Returns true if the shape buffer has accessed the end of the cube.
Definition: BufferManager.h:115
Isis::Transform
Pixel transformation.
Definition: Transform.h:72
Isis::Interpolator
Pixel interpolator.
Definition: Interpolator.h:34
Isis::IException
Isis exception class.
Definition: IException.h:91
Isis::Null
const double Null
Value for an Isis Null pixel.
Definition: SpecialPixel.h:95
Isis::Transform::Xform
virtual bool Xform(double &inSample, double &inLine, const double outSample, const double outLine)
Transforms the given output line and sample to the corresponding output line and sample.
Definition: Transform.h:123
Isis::Buffer::Band
int Band(const int index=0) const
Returns the band position associated with a shape buffer index.
Definition: Buffer.cpp:162
std
Namespace for the standard library.
Isis::Interpolator::Interpolate
double Interpolate(const double isamp, const double iline, const double buf[])
Performs an interpolation on the data according to the parameters set in the constructor.
Definition: Interpolator.cpp:56
Isis::E
const double E
Sets some basic constants for use in ISIS programming.
Definition: Constants.h:39
Isis::UniqueIOCachingAlgorithm
This algorithm is designed for applications that jump around between a couple of spots in the cube wi...
Definition: UniqueIOCachingAlgorithm.h:30
Isis::Buffer::size
int size() const
Returns the total number of pixels in the shape buffer.
Definition: Buffer.h:97
Isis::Interpolator::Samples
int Samples()
Returns the number of samples needed by the interpolator.
Definition: Interpolator.cpp:208
QVector< double >
Isis::Buffer::Sample
int Sample(const int index=0) const
Returns the sample position associated with a shape buffer index.
Definition: Buffer.cpp:127
Isis::Interpolator::HotLine
double HotLine()
Returns the line coordinate of the center pixel in the buffer for the interpolator.
Definition: Interpolator.cpp:297
Isis::Portal::SetPosition
void SetPosition(const double sample, const double line, const int band)
Sets the line and sample position of the buffer.
Definition: Portal.h:93
Isis::Buffer::Line
int Line(const int index=0) const
Returns the line position associated with a shape buffer index.
Definition: Buffer.cpp:145
Isis::TileManager::Tiles
int Tiles()
Returns the number of Tiles in the cube.
Definition: TileManager.h:60
Isis
This is free and unencumbered software released into the public domain.
Definition: Apollo.h:16

U.S. Department of the Interior | U.S. Geological Survey
ISIS | Privacy & Disclaimers | Astrogeology Research Program
To contact us, please post comments and questions on the USGS Astrogeology Discussion Board
To report a bug, or suggest a feature go to: ISIS Github
File Modified: 07/13/2023 15:17:04