entropy_range_coder.hpp
Go to the documentation of this file.
00001 /*
00002  * Software License Agreement (BSD License)
00003  *
00004  *  Point Cloud Library (PCL) - www.pointclouds.org
00005  *  Copyright (c) 2010-2012, Willow Garage, Inc.
00006  *
00007  *  All rights reserved.
00008  *
00009  *  Redistribution and use in source and binary forms, with or without
00010  *  modification, are permitted provided that the following conditions
00011  *  are met:
00012  *
00013  *   * Redistributions of source code must retain the above copyright
00014  *     notice, this list of conditions and the following disclaimer.
00015  *   * Redistributions in binary form must reproduce the above
00016  *     copyright notice, this list of conditions and the following
00017  *     disclaimer in the documentation and/or other materials provided
00018  *     with the distribution.
00019  *   * Neither the name of Willow Garage, Inc. nor the names of its
00020  *     contributors may be used to endorse or promote products derived
00021  *     from this software without specific prior written permission.
00022  *
00023  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00024  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00025  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00026  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00027  *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00028  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00029  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00030  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00031  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00032  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00033  *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00034  *  POSSIBILITY OF SUCH DAMAGE.
00035  *
00036  *
00037  * range coder based on Dmitry Subbotin's carry-less implementation (http://www.compression.ru/ds/)
00038  * Added optimized symbol lookup and fixed/static frequency tables
00039  *
00040  */
00041 
00042 #ifndef __PCL_IO_RANGECODING__HPP
00043 #define __PCL_IO_RANGECODING__HPP
00044 
00045 #include <pcl/compression/entropy_range_coder.h>
00046 #include <map>
00047 #include <iostream>
00048 #include <vector>
00049 #include <string.h>
00050 #include <algorithm>
00051 #include <stdio.h>
00052 
00054 unsigned long
00055 pcl::AdaptiveRangeCoder::encodeCharVectorToStream (const std::vector<char>& inputByteVector_arg,
00056                                                    std::ostream& outputByteStream_arg)
00057 {
00058   DWord freq[257];
00059   uint8_t ch;
00060   unsigned int i, j, f;
00061   char out;
00062 
00063   // define limits
00064   const DWord top = static_cast<DWord> (1) << 24;
00065   const DWord bottom = static_cast<DWord> (1) << 16;
00066   const DWord maxRange = static_cast<DWord> (1) << 16;
00067 
00068   DWord low, range;
00069 
00070   unsigned int readPos;
00071   unsigned int input_size;
00072 
00073   input_size = static_cast<unsigned> (inputByteVector_arg.size ());
00074 
00075   // init output vector
00076   outputCharVector_.clear ();
00077   outputCharVector_.reserve (sizeof(char) * input_size);
00078 
00079   readPos = 0;
00080 
00081   low = 0;
00082   range = static_cast<DWord> (-1);
00083 
00084   // initialize cumulative frequency table
00085   for (i = 0; i < 257; i++)
00086     freq[i] = i;
00087 
00088   // scan input
00089   while (readPos < input_size)
00090   {
00091 
00092     // read byte
00093     ch = inputByteVector_arg[readPos++];
00094 
00095     // map range
00096     low += freq[ch] * (range /= freq[256]);
00097     range *= freq[ch + 1] - freq[ch];
00098 
00099     // check range limits
00100     while ((low ^ (low + range)) < top || ((range < bottom) && ((range = -int (low) & (bottom - 1)), 1)))
00101     {
00102       out = static_cast<char> (low >> 24);
00103       range <<= 8;
00104       low <<= 8;
00105       outputCharVector_.push_back (out);
00106     }
00107 
00108     // update frequency table
00109     for (j = ch + 1; j < 257; j++)
00110       freq[j]++;
00111 
00112     // detect overflow
00113     if (freq[256] >= maxRange)
00114     {
00115       // rescale
00116       for (f = 1; f <= 256; f++)
00117       {
00118         freq[f] /= 2;
00119         if (freq[f] <= freq[f - 1])
00120           freq[f] = freq[f - 1] + 1;
00121       }
00122     }
00123 
00124   }
00125 
00126   // flush remaining data
00127   for (i = 0; i < 4; i++)
00128   {
00129     out = static_cast<char> (low >> 24);
00130     outputCharVector_.push_back (out);
00131     low <<= 8;
00132   }
00133 
00134   // write to stream
00135   outputByteStream_arg.write (&outputCharVector_[0], outputCharVector_.size ());
00136 
00137   return (static_cast<unsigned long> (outputCharVector_.size ()));
00138 
00139 }
00140 
00142 unsigned long
00143 pcl::AdaptiveRangeCoder::decodeStreamToCharVector (std::istream& inputByteStream_arg,
00144                                                    std::vector<char>& outputByteVector_arg)
00145 {
00146   uint8_t ch;
00147   DWord freq[257];
00148   unsigned int i, j, f;
00149 
00150   // define limits
00151   const DWord top = static_cast<DWord> (1) << 24;
00152   const DWord bottom = static_cast<DWord> (1) << 16;
00153   const DWord maxRange = static_cast<DWord> (1) << 16;
00154 
00155   DWord low, range;
00156   DWord code;
00157 
00158   unsigned int outputBufPos;
00159   unsigned int output_size = static_cast<unsigned> (outputByteVector_arg.size ());
00160 
00161   unsigned long streamByteCount;
00162 
00163   streamByteCount = 0;
00164 
00165   outputBufPos = 0;
00166 
00167   code = 0;
00168   low = 0;
00169   range = static_cast<DWord> (-1);
00170 
00171   // init decoding
00172   for (i = 0; i < 4; i++)
00173   {
00174     inputByteStream_arg.read (reinterpret_cast<char*> (&ch), sizeof(char));
00175     streamByteCount += sizeof(char);
00176     code = (code << 8) | ch;
00177   }
00178 
00179   // init cumulative frequency table
00180   for (i = 0; i <= 256; i++)
00181     freq[i] = i;
00182 
00183   // decoding loop
00184   for (i = 0; i < output_size; i++)
00185   {
00186     uint8_t symbol = 0;
00187     uint8_t sSize = 256 / 2;
00188 
00189     // map code to range
00190     DWord count = (code - low) / (range /= freq[256]);
00191 
00192     // find corresponding symbol
00193     while (sSize > 0)
00194     {
00195       if (freq[symbol + sSize] <= count)
00196       {
00197         symbol = static_cast<uint8_t> (symbol + sSize);
00198       }
00199       sSize /= 2;
00200     }
00201 
00202     // output symbol
00203     outputByteVector_arg[outputBufPos++] = symbol;
00204 
00205     // update range limits
00206     low += freq[symbol] * range;
00207     range *= freq[symbol + 1] - freq[symbol];
00208 
00209     // decode range limits
00210     while ((low ^ (low + range)) < top || ((range < bottom) && ((range = -int (low) & (bottom - 1)), 1)))
00211     {
00212       inputByteStream_arg.read (reinterpret_cast<char*> (&ch), sizeof(char));
00213       streamByteCount += sizeof(char);
00214       code = code << 8 | ch;
00215       range <<= 8;
00216       low <<= 8;
00217     }
00218 
00219     // update cumulative frequency table
00220     for (j = symbol + 1; j < 257; j++)
00221       freq[j]++;
00222 
00223     // detect overflow
00224     if (freq[256] >= maxRange)
00225     {
00226       // rescale
00227       for (f = 1; f <= 256; f++)
00228       {
00229         freq[f] /= 2;
00230         if (freq[f] <= freq[f - 1])
00231           freq[f] = freq[f - 1] + 1;
00232       }
00233     }
00234   }
00235 
00236   return (streamByteCount);
00237 
00238 }
00239 
00241 unsigned long
00242 pcl::StaticRangeCoder::encodeIntVectorToStream (std::vector<unsigned int>& inputIntVector_arg,
00243                                                 std::ostream& outputByteStream_arg)
00244 {
00245 
00246   unsigned int inputsymbol;
00247   unsigned int i, f;
00248   char out;
00249 
00250   uint64_t frequencyTableSize;
00251   uint8_t frequencyTableByteSize;
00252 
00253   // define numerical limits
00254   const uint64_t top = static_cast<uint64_t> (1) << 56;
00255   const uint64_t bottom = static_cast<uint64_t> (1) << 48;
00256   const uint64_t maxRange = static_cast<uint64_t> (1) << 48;
00257 
00258   unsigned long input_size = static_cast<unsigned long> (inputIntVector_arg.size ());
00259   uint64_t low, range;
00260 
00261   unsigned int inputSymbol;
00262 
00263   unsigned int readPos;
00264 
00265   unsigned long streamByteCount;
00266 
00267   streamByteCount = 0;
00268 
00269   // init output vector
00270   outputCharVector_.clear ();
00271   outputCharVector_.reserve ((sizeof(char) * input_size * 2));
00272 
00273   frequencyTableSize = 1;
00274 
00275   readPos = 0;
00276 
00277   // calculate frequency table
00278   cFreqTable_[0] = cFreqTable_[1] = 0;
00279   while (readPos < input_size)
00280   {
00281     inputSymbol = inputIntVector_arg[readPos++];
00282 
00283     if (inputSymbol + 1 >= frequencyTableSize)
00284     {
00285       // frequency table is to small -> adaptively extend it
00286       uint64_t oldfrequencyTableSize;
00287       oldfrequencyTableSize = frequencyTableSize;
00288 
00289       do
00290       {
00291         // increase frequency table size by factor 2
00292         frequencyTableSize <<= 1;
00293       } while (inputSymbol + 1 > frequencyTableSize);
00294 
00295       if (cFreqTable_.size () < frequencyTableSize + 1)
00296       {
00297         // resize frequency vector
00298         cFreqTable_.resize (static_cast<std::size_t> (frequencyTableSize + 1));
00299       }
00300 
00301       // init new frequency range with zero
00302       memset (&cFreqTable_[static_cast<std::size_t> (oldfrequencyTableSize + 1)], 0,
00303               sizeof(uint64_t) * static_cast<std::size_t> (frequencyTableSize - oldfrequencyTableSize));
00304     }
00305     cFreqTable_[inputSymbol + 1]++;
00306   }
00307   frequencyTableSize++;
00308 
00309   // convert to cumulative frequency table
00310   for (f = 1; f < frequencyTableSize; f++)
00311   {
00312     cFreqTable_[f] = cFreqTable_[f - 1] + cFreqTable_[f];
00313     if (cFreqTable_[f] <= cFreqTable_[f - 1])
00314       cFreqTable_[f] = cFreqTable_[f - 1] + 1;
00315   }
00316 
00317   // rescale if numerical limits are reached
00318   while (cFreqTable_[static_cast<std::size_t> (frequencyTableSize - 1)] >= maxRange)
00319   {
00320     for (f = 1; f < cFreqTable_.size (); f++)
00321     {
00322       cFreqTable_[f] /= 2;
00323       ;
00324       if (cFreqTable_[f] <= cFreqTable_[f - 1])
00325         cFreqTable_[f] = cFreqTable_[f - 1] + 1;
00326     }
00327   }
00328 
00329   // calculate amount of bytes per frequency table entry
00330   frequencyTableByteSize = static_cast<uint8_t> (ceil (
00331       Log2 (static_cast<double> (cFreqTable_[static_cast<std::size_t> (frequencyTableSize - 1)])) / 8.0));
00332 
00333   // write size of frequency table to output stream
00334   outputByteStream_arg.write (reinterpret_cast<const char*> (&frequencyTableSize), sizeof(frequencyTableSize));
00335   outputByteStream_arg.write (reinterpret_cast<const char*> (&frequencyTableByteSize), sizeof(frequencyTableByteSize));
00336 
00337   streamByteCount += sizeof(frequencyTableSize) + sizeof(frequencyTableByteSize);
00338 
00339   // write cumulative  frequency table to output stream
00340   for (f = 1; f < frequencyTableSize; f++)
00341   {
00342     outputByteStream_arg.write (reinterpret_cast<const char*> (&cFreqTable_[f]), frequencyTableByteSize);
00343     streamByteCount += frequencyTableByteSize;
00344   }
00345 
00346   readPos = 0;
00347   low = 0;
00348   range = static_cast<uint64_t> (-1);
00349 
00350   // start encoding
00351   while (readPos < input_size)
00352   {
00353 
00354     // read symol
00355     inputsymbol = inputIntVector_arg[readPos++];
00356 
00357     // map to range
00358     low += cFreqTable_[inputsymbol] * (range /= cFreqTable_[static_cast<std::size_t> (frequencyTableSize - 1)]);
00359     range *= cFreqTable_[inputsymbol + 1] - cFreqTable_[inputsymbol];
00360 
00361     // check range limits
00362     while ((low ^ (low + range)) < top || ((range < bottom) && ((range = -low & (bottom - 1)), 1)))
00363     {
00364       out = static_cast<char> (low >> 56);
00365       range <<= 8;
00366       low <<= 8;
00367       outputCharVector_.push_back (out);
00368     }
00369 
00370   }
00371 
00372   // flush remaining data
00373   for (i = 0; i < 8; i++)
00374   {
00375     out = static_cast<char> (low >> 56);
00376     outputCharVector_.push_back (out);
00377     low <<= 8;
00378   }
00379 
00380   // write encoded data to stream
00381   outputByteStream_arg.write (&outputCharVector_[0], outputCharVector_.size ());
00382 
00383   streamByteCount += static_cast<unsigned long> (outputCharVector_.size ());
00384 
00385   return (streamByteCount);
00386 }
00387 
00389 unsigned long
00390 pcl::StaticRangeCoder::decodeStreamToIntVector (std::istream& inputByteStream_arg,
00391                                                 std::vector<unsigned int>& outputIntVector_arg)
00392 {
00393   uint8_t ch;
00394   unsigned int i, f;
00395 
00396   // define range limits
00397   const uint64_t top = static_cast<uint64_t> (1) << 56;
00398   const uint64_t bottom = static_cast<uint64_t> (1) << 48;
00399 
00400   uint64_t low, range;
00401   uint64_t code;
00402 
00403   unsigned int outputBufPos;
00404   unsigned long output_size;
00405 
00406   uint64_t frequencyTableSize;
00407   unsigned char frequencyTableByteSize;
00408 
00409   unsigned long streamByteCount;
00410 
00411   streamByteCount = 0;
00412 
00413   outputBufPos = 0;
00414   output_size = static_cast<unsigned long> (outputIntVector_arg.size ());
00415 
00416   // read size of cumulative frequency table from stream
00417   inputByteStream_arg.read (reinterpret_cast<char*> (&frequencyTableSize), sizeof(frequencyTableSize));
00418   inputByteStream_arg.read (reinterpret_cast<char*> (&frequencyTableByteSize), sizeof(frequencyTableByteSize));
00419 
00420   streamByteCount += sizeof(frequencyTableSize) + sizeof(frequencyTableByteSize);
00421 
00422   // check size of frequency table vector
00423   if (cFreqTable_.size () < frequencyTableSize)
00424   {
00425     cFreqTable_.resize (static_cast<std::size_t> (frequencyTableSize));
00426   }
00427 
00428   // init with zero
00429   memset (&cFreqTable_[0], 0, sizeof(uint64_t) * static_cast<std::size_t> (frequencyTableSize));
00430 
00431   // read cumulative frequency table
00432   for (f = 1; f < frequencyTableSize; f++)
00433   {
00434     inputByteStream_arg.read (reinterpret_cast<char *> (&cFreqTable_[f]), frequencyTableByteSize);
00435     streamByteCount += frequencyTableByteSize;
00436   }
00437 
00438   // initialize range & code
00439   code = 0;
00440   low = 0;
00441   range = static_cast<uint64_t> (-1);
00442 
00443   // init code vector
00444   for (i = 0; i < 8; i++)
00445   {
00446     inputByteStream_arg.read (reinterpret_cast<char*> (&ch), sizeof(char));
00447     streamByteCount += sizeof(char);
00448     code = (code << 8) | ch;
00449   }
00450 
00451   // decoding
00452   for (i = 0; i < output_size; i++)
00453   {
00454     uint64_t count = (code - low) / (range /= cFreqTable_[static_cast<std::size_t> (frequencyTableSize - 1)]);
00455 
00456     // symbol lookup in cumulative frequency table
00457     uint64_t symbol = 0;
00458     uint64_t sSize = (frequencyTableSize - 1) / 2;
00459     while (sSize > 0)
00460     {
00461       if (cFreqTable_[static_cast<std::size_t> (symbol + sSize)] <= count)
00462       {
00463         symbol += sSize;
00464       }
00465       sSize /= 2;
00466     }
00467 
00468     // write symbol to output stream
00469     outputIntVector_arg[outputBufPos++] = static_cast<unsigned int> (symbol);
00470 
00471     // map to range
00472     low += cFreqTable_[static_cast<std::size_t> (symbol)] * range;
00473     range *= cFreqTable_[static_cast<std::size_t> (symbol + 1)] - cFreqTable_[static_cast<std::size_t> (symbol)];
00474 
00475     // check range limits
00476     while ((low ^ (low + range)) < top || ((range < bottom) && ((range = -low & (bottom - 1)), 1)))
00477     {
00478       inputByteStream_arg.read (reinterpret_cast<char*> (&ch), sizeof(char));
00479       streamByteCount += sizeof(char);
00480       code = code << 8 | ch;
00481       range <<= 8;
00482       low <<= 8;
00483     }
00484 
00485   }
00486 
00487   return streamByteCount;
00488 }
00489 
00491 unsigned long
00492 pcl::StaticRangeCoder::encodeCharVectorToStream (const std::vector<char>& inputByteVector_arg,
00493                                                  std::ostream& outputByteStream_arg)
00494 {
00495   DWord freq[257];
00496   uint8_t ch;
00497   int i, f;
00498   char out;
00499 
00500   // define numerical limits
00501   const DWord top = static_cast<DWord> (1) << 24;
00502   const DWord bottom = static_cast<DWord> (1) << 16;
00503   const DWord maxRange = static_cast<DWord> (1) << 16;
00504 
00505   DWord low, range;
00506 
00507   unsigned int input_size;
00508   input_size = static_cast<unsigned int> (inputByteVector_arg.size ());
00509 
00510   unsigned int readPos;
00511 
00512   unsigned long streamByteCount;
00513 
00514   streamByteCount = 0;
00515 
00516   // init output vector
00517   outputCharVector_.clear ();
00518   outputCharVector_.reserve (sizeof(char) * input_size);
00519 
00520   uint64_t FreqHist[257];
00521 
00522   // calculate frequency table
00523   memset (FreqHist, 0, sizeof(FreqHist));
00524   readPos = 0;
00525   while (readPos < input_size)
00526   {
00527     uint8_t symbol = static_cast<uint8_t> (inputByteVector_arg[readPos++]);
00528     FreqHist[symbol + 1]++;
00529   }
00530 
00531   // convert to cumulative frequency table
00532   freq[0] = 0;
00533   for (f = 1; f <= 256; f++)
00534   {
00535     freq[f] = freq[f - 1] + static_cast<DWord> (FreqHist[f]);
00536     if (freq[f] <= freq[f - 1])
00537       freq[f] = freq[f - 1] + 1;
00538   }
00539 
00540   // rescale if numerical limits are reached
00541   while (freq[256] >= maxRange)
00542   {
00543     for (f = 1; f <= 256; f++)
00544     {
00545       freq[f] /= 2;
00546       ;
00547       if (freq[f] <= freq[f - 1])
00548         freq[f] = freq[f - 1] + 1;
00549     }
00550   }
00551 
00552   // write cumulative  frequency table to output stream
00553   outputByteStream_arg.write (reinterpret_cast<const char*> (&freq[0]), sizeof(freq));
00554   streamByteCount += sizeof(freq);
00555 
00556   readPos = 0;
00557 
00558   low = 0;
00559   range = static_cast<DWord> (-1);
00560 
00561   // start encoding
00562   while (readPos < input_size)
00563   {
00564 
00565     // read symol
00566     ch = inputByteVector_arg[readPos++];
00567 
00568     // map to range
00569     low += freq[ch] * (range /= freq[256]);
00570     range *= freq[ch + 1] - freq[ch];
00571 
00572     // check range limits
00573     while ((low ^ (low + range)) < top || ((range < bottom) && ((range = -int (low) & (bottom - 1)), 1)))
00574     {
00575       out = static_cast<char> (low >> 24);
00576       range <<= 8;
00577       low <<= 8;
00578       outputCharVector_.push_back (out);
00579     }
00580 
00581   }
00582 
00583   // flush remaining data
00584   for (i = 0; i < 4; i++)
00585   {
00586     out = static_cast<char> (low >> 24);
00587     outputCharVector_.push_back (out);
00588     low <<= 8;
00589   }
00590 
00591   // write encoded data to stream
00592   outputByteStream_arg.write (&outputCharVector_[0], outputCharVector_.size ());
00593 
00594   streamByteCount += static_cast<unsigned long> (outputCharVector_.size ());
00595 
00596   return (streamByteCount);
00597 }
00598 
00600 unsigned long
00601 pcl::StaticRangeCoder::decodeStreamToCharVector (std::istream& inputByteStream_arg,
00602                                                  std::vector<char>& outputByteVector_arg)
00603 {
00604   uint8_t ch;
00605   DWord freq[257];
00606   unsigned int i;
00607 
00608   // define range limits
00609   const DWord top = static_cast<DWord> (1) << 24;
00610   const DWord bottom = static_cast<DWord> (1) << 16;
00611 
00612   DWord low, range;
00613   DWord code;
00614 
00615   unsigned int outputBufPos;
00616   unsigned int output_size;
00617 
00618   unsigned long streamByteCount;
00619 
00620   streamByteCount = 0;
00621 
00622   output_size = static_cast<unsigned int> (outputByteVector_arg.size ());
00623 
00624   outputBufPos = 0;
00625 
00626   // read cumulative frequency table
00627   inputByteStream_arg.read (reinterpret_cast<char*> (&freq[0]), sizeof(freq));
00628   streamByteCount += sizeof(freq);
00629 
00630   code = 0;
00631   low = 0;
00632   range = static_cast<DWord> (-1);
00633 
00634   // init code
00635   for (i = 0; i < 4; i++)
00636   {
00637     inputByteStream_arg.read (reinterpret_cast<char*> (&ch), sizeof(char));
00638     streamByteCount += sizeof(char);
00639     code = (code << 8) | ch;
00640   }
00641 
00642   // decoding
00643   for (i = 0; i < output_size; i++)
00644   {
00645     // symbol lookup in cumulative frequency table
00646     uint8_t symbol = 0;
00647     uint8_t sSize = 256 / 2;
00648 
00649     DWord count = (code - low) / (range /= freq[256]);
00650 
00651     while (sSize > 0)
00652     {
00653       if (freq[symbol + sSize] <= count)
00654       {
00655         symbol = static_cast<uint8_t> (symbol + sSize);
00656       }
00657       sSize /= 2;
00658     }
00659 
00660     // write symbol to output stream
00661     outputByteVector_arg[outputBufPos++] = symbol;
00662 
00663     low += freq[symbol] * range;
00664     range *= freq[symbol + 1] - freq[symbol];
00665 
00666     // check range limits
00667     while ((low ^ (low + range)) < top || ((range < bottom) && ((range = -int (low) & (bottom - 1)), 1)))
00668     {
00669       inputByteStream_arg.read (reinterpret_cast<char*> (&ch), sizeof(char));
00670       streamByteCount += sizeof(char);
00671       code = code << 8 | ch;
00672       range <<= 8;
00673       low <<= 8;
00674     }
00675 
00676   }
00677 
00678   return (streamByteCount);
00679 }
00680 
00681 #endif
00682 


pcl
Author(s): Open Perception
autogenerated on Wed Aug 26 2015 15:23:31