00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039 #include "MBARI/BitObject.H"
00040
00041 #include "Image/CutPaste.H"
00042 #include "Image/IO.H"
00043 #include "Image/Image.H"
00044 #include "Image/MathOps.H"
00045 #include "Image/Pixels.H"
00046 #include "Image/Transforms.H"
00047 #include "Raster/GenericFrame.H"
00048 #include "Raster/PnmParser.H"
00049 #include "Raster/PnmWriter.H"
00050 #include "Util/Assert.H"
00051 #include "Util/MathFunctions.H"
00052
00053 #include <cmath>
00054 #include <istream>
00055 #include <ostream>
00056
00057
00058 BitObject::BitObject()
00059 {
00060 freeMem();
00061 }
00062
00063
00064 BitObject::BitObject(const Image<byte>& img, const Point2D<int> location,
00065 const byte threshold)
00066 {
00067 reset(img, location, threshold);
00068 }
00069
00070
00071 BitObject::BitObject(const Image<byte>& img)
00072 {
00073 reset(img);
00074 }
00075
00076
00077 BitObject::BitObject(std::istream& is)
00078 {
00079 readFromStream(is);
00080 }
00081
00082
00083 Image<byte> BitObject::reset(const Image<byte>& img, const Point2D<int> location,
00084 const byte threshold)
00085 {
00086 ASSERT(img.initialized());
00087
00088
00089 freeMem();
00090
00091
00092 Image<byte> dest;
00093 int area = floodCleanBB(img, dest, location, threshold,
00094 byte(1), itsBoundingBox);
00095
00096
00097 if (area == -1)
00098 {
00099 itsCentroidXY.reset(location);
00100 return Image<byte>();
00101 }
00102
00103
00104 itsImageDims = img.getDims();
00105
00106
00107 itsObjectMask = crop(dest, itsBoundingBox);
00108
00109
00110 std::vector<float> sumx, sumy;
00111 itsArea = (int)sumXY(itsObjectMask, sumx, sumy);
00112 if (area != itsArea)
00113 LFATAL("area %i doesn't match the one from flooding %i", itsArea, area);
00114
00115 int firstX, lastX, firstY, lastY;
00116 float cX, cY;
00117 bool success = (getCentroidFirstLast(sumx, cX, firstX, lastX) |
00118 getCentroidFirstLast(sumy, cY, firstY, lastY));
00119 itsCentroidXY.reset(cX,cY);
00120
00121 if (!success) LFATAL("determining the centroid failed");
00122
00123 if ((firstX != 0) || (lastX != itsObjectMask.getWidth()-1) ||
00124 (firstY != 0) || (lastY != itsObjectMask.getHeight()-1))
00125 LFATAL("boundary box doesn't match the one from flooding");
00126
00127 itsCentroidXY += Vector2D(itsBoundingBox.left(),itsBoundingBox.top());
00128
00129 return dest;
00130 }
00131
00132
00133 int BitObject::reset(const Image<byte>& img)
00134 {
00135 ASSERT(img.initialized());
00136
00137
00138 freeMem();
00139
00140
00141 itsImageDims = img.getDims();
00142
00143
00144 std::vector<float> sumx, sumy;
00145 itsArea = (int)sumXY(img, sumx, sumy);
00146
00147 if (itsArea == 0)
00148 {
00149 freeMem();
00150 return -1;
00151 }
00152
00153 int firstX, lastX, firstY, lastY;
00154 float cX, cY;
00155 bool success = (getCentroidFirstLast(sumx, cX, firstX, lastX) |
00156 getCentroidFirstLast(sumy, cY, firstY, lastY));
00157 itsCentroidXY.reset(cX,cY);
00158
00159 if (!success)
00160 {
00161 freeMem();
00162 return -1;
00163 }
00164
00165 itsBoundingBox = Rectangle::tlbrI(firstY, firstX, lastY, lastX);
00166
00167
00168 itsObjectMask = crop(img, itsBoundingBox);
00169
00170
00171
00172
00173 return itsArea;
00174 }
00175
00176
00177 void BitObject::computeSecondMoments()
00178 {
00179 ASSERT(isValid());
00180
00181 int w = itsObjectMask.getWidth();
00182 int h = itsObjectMask.getHeight();
00183
00184
00185
00186 float cenX = itsCentroidXY.x() - itsBoundingBox.left();
00187 float cenY = itsCentroidXY.y() - itsBoundingBox.top();
00188
00189
00190 std::vector<float> diffX(w), diffX2(w), diffY(h), diffY2(h);
00191 for (int y = 0; y < h; ++y)
00192 {
00193 diffY[y] = y - cenY;
00194 diffY2[y] = diffY[y] * diffY[y];
00195 }
00196 for (int x = 0; x < w; ++x)
00197 {
00198 diffX[x] = x - cenX;
00199 diffX2[x] = diffX[x] * diffX[x];
00200 }
00201
00202 Image<byte>::const_iterator optr = itsObjectMask.begin();
00203 for (int y = 0; y < h; ++y)
00204 for(int x = 0; x < w; ++x)
00205 {
00206 if (*optr != 0)
00207 {
00208 itsUxx += diffX2[x];
00209 itsUyy += diffY2[y];
00210 itsUxy += (diffX[x] * diffY[y]);
00211 }
00212 ++optr;
00213 }
00214 itsUxx /= itsArea;
00215 itsUyy /= itsArea;
00216 itsUxy /= itsArea;
00217
00218
00219
00220 float coeff = 1 / (4 * (itsUxx * itsUyy - itsUxy * itsUxy));
00221 float d = coeff * itsUyy;
00222 float e = -coeff * itsUxy;
00223 float f = coeff * itsUxx;
00224
00225
00226
00227
00228 float expr = sqrt(4*e*e + squareOf(d - f));
00229 float d2 = 0.5 * (d + f + expr);
00230 float f2 = 0.5 * (d + f - expr);
00231
00232
00233
00234
00235
00236
00237 itsOriAngle = 90 * atan(2 * e / (d - f)) / M_PI;
00238 if (itsUyy > itsUxx) itsOriAngle += 90.0F;
00239 if (itsOriAngle < 0.0F) itsOriAngle += 180.0F;
00240
00241
00242 if (itsOriAngle != itsOriAngle) itsOriAngle = 0.0F;
00243
00244
00245 itsMajorAxis = 2 / sqrt(f2);
00246 itsMinorAxis = 2 / sqrt(d2);
00247 itsElongation = itsMajorAxis / itsMinorAxis;
00248
00249
00250 haveSecondMoments = true;
00251 return;
00252 }
00253
00254
00255 void BitObject::freeMem()
00256 {
00257 itsObjectMask.freeMem();
00258 itsBoundingBox = Rectangle();
00259 itsCentroidXY = Vector2D();
00260 itsArea = 0;
00261 itsUxx = 0.0F;
00262 itsUyy = 0.0F;
00263 itsUxy = 0.0F;
00264 itsMajorAxis = 0.0F;
00265 itsMinorAxis = 0.0F;
00266 itsElongation = 0.0F;
00267 itsOriAngle = 0.0F;
00268 itsImageDims = Dims(0,0);
00269 itsMaxIntensity = -1.0F;
00270 itsMinIntensity = -1.0F;
00271 itsAvgIntensity = -1.0F;
00272 haveSecondMoments = false;
00273 }
00274
00275 void BitObject::writeToStream(std::ostream& os) const
00276 {
00277
00278 if (itsBoundingBox.isValid())
00279 {
00280 os << itsBoundingBox.top() << " "
00281 << itsBoundingBox.left() << " "
00282 << itsBoundingBox.bottomI() << " "
00283 << itsBoundingBox.rightI() << "\n";
00284 }
00285 else
00286 {
00287 os << "-1 -1 -1 -1\n";
00288 }
00289
00290
00291 os << itsImageDims.w() << " " << itsImageDims.h() << "\n";
00292
00293
00294 itsCentroidXY.writeToStream(os);
00295
00296
00297 os << itsArea << "\n";
00298
00299
00300 if (haveSecondMoments) os << "0\n";
00301 else os <<"1\n";
00302
00303
00304 os << itsUxx << " " << itsUyy << " " << itsUxy << "\n";
00305
00306
00307 os << itsMajorAxis << " "
00308 << itsMinorAxis << " "
00309 << itsElongation << " "
00310 << itsOriAngle << "\n";
00311
00312
00313 os << itsMaxIntensity << " "
00314 << itsMinIntensity << " "
00315 << itsAvgIntensity << "\n";
00316
00317
00318 PnmWriter::writeAsciiBW(itsObjectMask, 1, os);
00319
00320 os << "\n";
00321
00322
00323 return;
00324 }
00325
00326
00327 void BitObject::readFromStream(std::istream& is)
00328 {
00329
00330 int t, l, b, r;
00331 is >> t; is >> l; is >> b; is >> r;
00332 if (t >= 0)
00333 itsBoundingBox = Rectangle::tlbrI(t, l, b, r);
00334 else
00335 itsBoundingBox = Rectangle();
00336
00337
00338
00339 int w, h;
00340 is >> w; is >> h;
00341 itsImageDims = Dims(w,h);
00342
00343
00344 itsCentroidXY = Vector2D(is);
00345
00346
00347 is >> itsArea;
00348
00349
00350 int hs; is >> hs;
00351 haveSecondMoments = (hs == 1);
00352
00353
00354 is >> itsUxx; is >> itsUyy; is >> itsUxy;
00355
00356
00357 is >> itsMajorAxis; is >> itsMinorAxis;
00358 is >> itsElongation; is >> itsOriAngle;
00359
00360
00361 is >> itsMaxIntensity;
00362 is >> itsMinIntensity;
00363 is >> itsAvgIntensity;
00364
00365
00366 PnmParser pp(is);
00367 itsObjectMask = pp.getFrame().asGray();
00368 }
00369
00370
00371 template <class T>
00372 void BitObject::setMaxMinAvgIntensity(const Image<T>& img)
00373 {
00374 ASSERT(img.getDims() == itsImageDims);
00375 if (!isValid()) return;
00376
00377 float sum = 0.0F;
00378 int num = 0;
00379
00380
00381 const int iw = img.getWidth();
00382 typename Image<byte>::const_iterator bptr = itsObjectMask.begin();
00383 typename Image<T>::const_iterator iptr2, iptr = img.begin();
00384 iptr += (iw * itsBoundingBox.top() + itsBoundingBox.left());
00385
00386 for (int y = itsBoundingBox.top(); y < itsBoundingBox.bottomO(); ++y)
00387 {
00388 iptr2 = iptr;
00389 for (int x = itsBoundingBox.left(); x < itsBoundingBox.rightO(); ++x)
00390 {
00391
00392 if (*bptr > byte(0))
00393 {
00394 sum += (float)(*iptr2);
00395 ++num;
00396 if ((itsMaxIntensity == -1.0F) || (*iptr2 > itsMaxIntensity))
00397 itsMaxIntensity = *iptr2;
00398 if ((itsMinIntensity == -1.0F) || (*iptr2 < itsMinIntensity))
00399 itsMinIntensity = *iptr2;
00400 }
00401 ++bptr;
00402 ++iptr2;
00403 }
00404 iptr += iw;
00405 }
00406
00407 if (sum == 0) itsAvgIntensity = 0.0F;
00408 else itsAvgIntensity = sum / (float)num;
00409
00410 }
00411
00412
00413 void BitObject::getMaxMinAvgIntensity(float& maxIntensity,
00414 float& minIntensity,
00415 float& avgIntensity)
00416 {
00417 maxIntensity = itsMaxIntensity;
00418 minIntensity = itsMinIntensity;
00419 avgIntensity = itsAvgIntensity;
00420 }
00421
00422
00423 Rectangle BitObject::getBoundingBox(const BitObject::Coords coords) const
00424 {
00425 switch(coords)
00426 {
00427 case OBJECT: return Rectangle::tlbrI(0, 0, itsBoundingBox.height() - 1,
00428 itsBoundingBox.width() - 1);
00429 case IMAGE: return itsBoundingBox;
00430 default: LFATAL("Unknown Coords type - don't know what to do.");
00431 }
00432
00433 return Rectangle();
00434 }
00435
00436
00437 Image<byte> BitObject::getObjectMask(const byte value,
00438 const BitObject::Coords coords) const
00439 {
00440 ASSERT(isValid());
00441 Image<byte> objectCopy = replaceVals(itsObjectMask,byte(1),value);
00442
00443 switch (coords)
00444 {
00445 case OBJECT: return objectCopy;
00446
00447 case IMAGE:
00448 {
00449 Image<byte> result(itsImageDims, ZEROS);
00450 pasteImage(result, objectCopy, byte(0), getObjectOrigin());
00451 return result;
00452 }
00453
00454 default: LFATAL("Unknown Coords type - don't know what to do.");
00455 }
00456
00457
00458 return Image<byte>();
00459 }
00460
00461
00462 Dims BitObject::getObjectDims() const
00463 { return itsObjectMask.getDims(); }
00464
00465
00466 Point2D<int> BitObject::getObjectOrigin() const
00467 {
00468 return Point2D<int>(itsBoundingBox.left(),itsBoundingBox.top());
00469 }
00470
00471
00472 Point2D<int> BitObject::getCentroid(const BitObject::Coords coords) const
00473 {
00474 return getCentroidXY().getPoint2D();
00475 }
00476
00477
00478 Vector2D BitObject::getCentroidXY(const BitObject::Coords coords) const
00479 {
00480 switch (coords)
00481 {
00482 case OBJECT: return (itsCentroidXY - Vector2D(getObjectOrigin()));
00483 case IMAGE: return itsCentroidXY;
00484 default: LFATAL("Unknown Coords type - don't know what to do.");
00485 }
00486
00487 return Vector2D();
00488 }
00489
00490
00491 int BitObject::getArea() const
00492 { return itsArea; }
00493
00494
00495 void BitObject::getSecondMoments(float& uxx, float& uyy, float& uxy)
00496 {
00497 if (!haveSecondMoments) computeSecondMoments();
00498 uxx = itsUxx; uyy = itsUyy; uxy = itsUxy;
00499 }
00500
00501
00502 float BitObject::getMajorAxis()
00503 {
00504 if (!haveSecondMoments) computeSecondMoments();
00505 return itsMajorAxis;
00506 }
00507
00508
00509 float BitObject::getMinorAxis()
00510 {
00511 if (!haveSecondMoments) computeSecondMoments();
00512 return itsMinorAxis;
00513 }
00514
00515
00516 float BitObject::getElongation()
00517 {
00518 if (!haveSecondMoments) computeSecondMoments();
00519 return itsElongation;
00520 }
00521
00522
00523 float BitObject::getOriAngle()
00524 {
00525 return itsOriAngle;
00526 }
00527
00528
00529 bool BitObject::isValid() const
00530 { return ((itsArea > 0) && itsBoundingBox.isValid()); }
00531
00532
00533 bool BitObject::doesIntersect(const BitObject& other) const
00534 {
00535
00536 if (!(isValid() && other.isValid()))
00537 {
00538 LINFO("no interesect, because one of the objects is invalid.");
00539 return false;
00540 }
00541
00542 Rectangle tBB = getBoundingBox(IMAGE);
00543 Rectangle oBB = other.getBoundingBox(IMAGE);
00544
00545
00546 int ll = std::max(tBB.left(),oBB.left());
00547 int rr = std::min(tBB.rightI(),oBB.rightI());
00548 int tt = std::max(tBB.top(),oBB.top());
00549 int bb = std::min(tBB.bottomI(),oBB.bottomI());
00550
00551
00552
00553
00554
00555
00556 if ((ll > rr)||(tt > bb))
00557 {
00558
00559
00560 return false;
00561 }
00562
00563
00564 Rectangle tCM = Rectangle::tlbrI(tt - tBB.top(), ll - tBB.left(),
00565 bb - tBB.top(), rr - tBB.left());
00566 Rectangle oCM = Rectangle::tlbrI(tt - oBB.top(), ll - oBB.left(),
00567 bb - oBB.top(), rr - oBB.left());
00568
00569
00570
00571
00572
00573
00574
00575
00576
00577
00578
00579
00580 Image<byte> cor = takeMin(crop(getObjectMask(byte(1),OBJECT),tCM),
00581 crop(other.getObjectMask(byte(1),OBJECT),oCM));
00582 double s = sum(cor);
00583
00584
00585
00586
00587
00588
00589 return (s > 0.0);
00590 }
00591
00592
00593 template <class T_or_RGB>
00594 void BitObject::drawShape(Image<T_or_RGB>& img,
00595 const T_or_RGB& color,
00596 float opacity)
00597 {
00598 ASSERT(isValid());
00599 ASSERT(img.initialized());
00600 ASSERT(img.getDims() == itsImageDims);
00601
00602 int w = img.getWidth();
00603 float op2 = 1.0F - opacity;
00604
00605 typename Image<T_or_RGB>::iterator iptr, iptr2;
00606 Image<byte>::const_iterator mptr = itsObjectMask.begin();
00607 iptr2 = img.beginw() + itsBoundingBox.top() * w + itsBoundingBox.left();
00608 for (int y = itsBoundingBox.top(); y < itsBoundingBox.bottomO(); ++y)
00609 {
00610 iptr = iptr2;
00611 for (int x = itsBoundingBox.left(); x < itsBoundingBox.rightO(); ++x)
00612 {
00613 if (*mptr > 0) *iptr = T_or_RGB(*iptr * op2 + color * opacity);
00614 ++iptr; ++mptr;
00615 }
00616 iptr2 += w;
00617 }
00618 }
00619
00620
00621 template <class T_or_RGB>
00622 void BitObject::drawOutline(Image<T_or_RGB>& img,
00623 const T_or_RGB& color,
00624 float opacity)
00625 {
00626 ASSERT(isValid());
00627 ASSERT(img.initialized());
00628 ASSERT(img.getDims() == itsImageDims);
00629 float op2 = 1.0F - opacity;
00630
00631 Image<byte> marked(img.getDims(), ZEROS);
00632
00633 int t = itsBoundingBox.top();
00634 int b = itsBoundingBox.bottomO();
00635 int l = itsBoundingBox.left();
00636 int r = itsBoundingBox.rightO();
00637
00638 for (int y = t; y < b; ++y)
00639 for (int x = l; x < r; ++x)
00640 {
00641 if (itsObjectMask.getVal(x-l,y-t) == 0) continue;
00642 for (int dy = -1; dy <= 1; ++dy)
00643 for (int dx = -1; dx <= 1; ++dx)
00644 {
00645 if ((dy == 0) && (dx == 0)) continue;
00646 Point2D<int> pim(x+dx,y+dy);
00647 if (!img.coordsOk(pim)) continue;
00648
00649 bool isBoundary = false;
00650 Point2D<int> pm(x+dx-l,y+dy-t);
00651 if (!itsObjectMask.coordsOk(pm)) isBoundary = true;
00652 else if (itsObjectMask.getVal(pm) == 0) isBoundary = true;
00653
00654 if (isBoundary && marked.getVal(pim) == 0)
00655 {
00656 img.setVal(pim,T_or_RGB(img.getVal(pim) * op2 + color * opacity));
00657 marked.setVal(pim,byte(1));
00658 }
00659 }
00660 }
00661 }
00662
00663
00664
00665 template <class T_or_RGB>
00666 void BitObject::drawBoundingBox(Image<T_or_RGB>& img,
00667 const T_or_RGB& color,
00668 float opacity)
00669 {
00670 ASSERT(isValid());
00671 ASSERT(img.initialized());
00672 ASSERT(img.getDims() == itsImageDims);
00673
00674 float op2 = 1.0F - opacity;
00675 int t = itsBoundingBox.top();
00676 int b = itsBoundingBox.bottomI();
00677 int l = itsBoundingBox.left();
00678 int r = itsBoundingBox.rightI();
00679
00680 for (int x = l; x <= r; ++x)
00681 {
00682 Point2D<int> p1(x,t), p2(x,b);
00683 img.setVal(p1,img.getVal(p1) * op2 + color * opacity);
00684 img.setVal(p2,img.getVal(p2) * op2 + color * opacity);
00685 }
00686 for (int y = t+1; y < b; ++y)
00687 {
00688 Point2D<int> p1(l,y), p2(r,y);
00689 img.setVal(p1,img.getVal(p1) * op2 + color * opacity);
00690 img.setVal(p2,img.getVal(p2) * op2 + color * opacity);
00691 }
00692 }
00693
00694
00695 template <class T_or_RGB>
00696 void BitObject::draw(BitObjectDrawMode mode, Image<T_or_RGB>& img,
00697 const T_or_RGB& color, float opacity)
00698 {
00699 switch(mode)
00700 {
00701 case BODMnone: break;
00702 case BODMshape: drawShape(img, color, opacity); break;
00703 case BODMoutline: drawOutline(img, color, opacity); break;
00704 case BODMbbox: drawBoundingBox(img, color, opacity); break;
00705 default: LERROR("Unknown BitObjectDrawMode: %i - ignoring!",mode);
00706 }
00707 }
00708
00709
00710
00711
00712 template void BitObject::setMaxMinAvgIntensity(const Image<byte>& img);
00713 template void BitObject::setMaxMinAvgIntensity(const Image<float>& img);
00714
00715 #define INSTANTIATE(T_or_RGB) \
00716 template void BitObject::drawShape(Image< T_or_RGB >& img, \
00717 const T_or_RGB& color, \
00718 float opacity); \
00719 template void BitObject::drawOutline(Image< T_or_RGB >& img, \
00720 const T_or_RGB& color, \
00721 float opacity); \
00722 template void BitObject::drawBoundingBox(Image< T_or_RGB >& img, \
00723 const T_or_RGB& color, \
00724 float opacity); \
00725 template void BitObject::draw(BitObjectDrawMode mode, \
00726 Image< T_or_RGB >& img, \
00727 const T_or_RGB& color, \
00728 float opacity);
00729
00730 INSTANTIATE(PixRGB<byte>);
00731 INSTANTIATE(PixRGB<float>);
00732 INSTANTIATE(byte);
00733 INSTANTIATE(float);
00734
00735
00736
00737
00738
00739