packages feed

pcd-loader 0.1.1.1 → 0.2.0

raw patch · 4 files changed

+197/−36 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- PCD.Data: loadPoints :: Storable a => Parser a -> FilePath -> IO (Vector a)
- PCD.Data: readAsciiPoints :: Vector v a => Header -> Handle -> Parser a -> IO (v a)
- PCD.Data: readAsciiPointsDefault :: Header -> Handle -> IO (Vector [FieldType])
- PCD.Data: readBinPoints :: Storable a => Header -> Handle -> IO (Either String (Vector a))
- PCD.Data: readPointData :: Storable a => Header -> Handle -> Parser a -> IO (Either String (Vector a))
- PCD.Data: readXYZW_ascii :: Fractional a => Parser (V4 a)
- PCD.Data: readXYZ_ascii :: Fractional a => Parser (V3 a)
- PCD.Data: saveBinaryPcd :: Storable a => FilePath -> Header -> Vector a -> IO ()
+ PCD.Data: TChar :: {-# UNPACK #-} !Int8 -> FieldType
+ PCD.Data: TDouble :: {-# UNPACK #-} !Double -> FieldType
+ PCD.Data: TFloat :: {-# UNPACK #-} !Float -> FieldType
+ PCD.Data: TInt :: {-# UNPACK #-} !Int32 -> FieldType
+ PCD.Data: TShort :: {-# UNPACK #-} !Int16 -> FieldType
+ PCD.Data: TUchar :: {-# UNPACK #-} !Word8 -> FieldType
+ PCD.Data: TUint :: {-# UNPACK #-} !Word32 -> FieldType
+ PCD.Data: TUshort :: {-# UNPACK #-} !Word16 -> FieldType
+ PCD.Data: data FieldType
+ PCD.Data: loadAllFields :: FilePath -> IO (Text -> Vector FieldType)
+ PCD.Data: unsafeUnwrap :: PCDType a => FieldType -> a
+ PCD.Header: class PCDType a
+ PCD.Header: instance PCDType Double
+ PCD.Header: instance PCDType Float
+ PCD.Header: instance PCDType Int16
+ PCD.Header: instance PCDType Int32
+ PCD.Header: instance PCDType Int8
+ PCD.Header: instance PCDType Word16
+ PCD.Header: instance PCDType Word32
+ PCD.Header: instance PCDType Word8
+ PCD.Header: instance Show FieldType
+ PCD.Header: sequence' :: Monad m => [m a] -> m [a]
+ PCD.Header: totalBinarySize :: Header -> Int
+ PCD.Header: unsafeUnwrap :: PCDType a => FieldType -> a
+ PCD.Internal.StorableFieldType: P :: !FieldType -> {-# UNPACK #-} !(Ptr a) -> P a
+ PCD.Internal.StorableFieldType: data P a
+ PCD.Internal.StorableFieldType: parseBinaryField :: DimType -> Int -> Ptr a -> IO (P a)
+ PCD.Internal.StorableFieldType: parseBinaryFields :: Header -> Ptr a -> IO (Vector FieldType, Ptr a)
+ PCD.Internal.StorableFieldType: parseBinaryPoints :: Header -> Handle -> IO (Vector (Vector FieldType))
+ PCD.Internal.StorableFieldType: peekStep :: Storable a => (a -> FieldType) -> Ptr b -> IO (P b)
+ PCD.Internal.StorableFieldType: pokeBinaryField :: FieldType -> Ptr a -> IO (Ptr a)
+ PCD.Internal.StorableFieldType: pokeBinaryFields :: Ptr a -> Vector FieldType -> IO (Ptr a)
+ PCD.Internal.StorableFieldType: pokeBinaryPoints :: Ptr a -> Vector (Vector FieldType) -> IO ()
+ PCD.Internal.StorableFieldType: pokeStep :: Storable a => a -> Ptr b -> IO (Ptr b)
- PCD.Header: TChar :: Int8 -> FieldType
+ PCD.Header: TChar :: {-# UNPACK #-} !Int8 -> FieldType
- PCD.Header: TDouble :: Double -> FieldType
+ PCD.Header: TDouble :: {-# UNPACK #-} !Double -> FieldType
- PCD.Header: TFloat :: Float -> FieldType
+ PCD.Header: TFloat :: {-# UNPACK #-} !Float -> FieldType
- PCD.Header: TInt :: Int32 -> FieldType
+ PCD.Header: TInt :: {-# UNPACK #-} !Int32 -> FieldType
- PCD.Header: TShort :: Int16 -> FieldType
+ PCD.Header: TShort :: {-# UNPACK #-} !Int16 -> FieldType
- PCD.Header: TUchar :: Word8 -> FieldType
+ PCD.Header: TUchar :: {-# UNPACK #-} !Word8 -> FieldType
- PCD.Header: TUint :: Word32 -> FieldType
+ PCD.Header: TUint :: {-# UNPACK #-} !Word32 -> FieldType
- PCD.Header: TUshort :: Word16 -> FieldType
+ PCD.Header: TUshort :: {-# UNPACK #-} !Word16 -> FieldType

Files

pcd-loader.cabal view
@@ -1,5 +1,5 @@ name:                pcd-loader-version:             0.1.1.1+version:             0.2.0 synopsis:            PCD file loader. description:         Parser for PCD (point cloud data) formats.  See                      <http://pointclouds.org/documentation/tutorials/pcd_file_format.php>@@ -18,7 +18,8 @@   location: git://github.com/acowley/pcd-loader.git  library-  exposed-modules:  PCD.Data, PCD.Header, PCD.Internal.Types+  exposed-modules:  PCD.Data, PCD.Header, PCD.Internal.Types, +                    PCD.Internal.StorableFieldType   other-modules:    PCD.Internal.SmallLens   ghc-options:      -O2 -Wall   hs-source-dirs:   src
src/PCD/Data.hs view
@@ -1,24 +1,30 @@-{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ScopedTypeVariables, BangPatterns #-} -- |Parser for PCD (point cloud data) files. Also provides a facility -- for converting from ASCII to binary formatted point data.-module PCD.Data where+module PCD.Data (FieldType(..), unsafeUnwrap, loadAllFields, +                 loadXyzw, loadXyz, asciiToBinary) where import Control.Applicative import Control.DeepSeq import Control.Monad (when)-import Data.Attoparsec.Text+import Data.Attoparsec.Text hiding (I) import qualified Data.Attoparsec.Text.Lazy as ATL+import Data.Text (Text) import qualified Data.Text.Lazy.IO as TL import qualified Data.Text.IO as T import qualified Data.Vector as B+import qualified Data.Vector.Mutable as BM import qualified Data.Vector.Generic as G import qualified Data.Vector.Generic.Mutable as GM import qualified Data.Vector.Storable as V import qualified Data.Vector.Storable.Mutable as VM-import Foreign.Storable (Storable, sizeOf)+import Foreign.Marshal.Alloc (allocaBytes)+import Foreign.Ptr (Ptr, castPtr, plusPtr)+import Foreign.Storable (Storable, sizeOf, peek) import System.IO (Handle, openFile, hClose,                    IOMode(..), withBinaryFile, hPutBuf, hGetBuf) import PCD.Header import PCD.Internal.SmallLens+import PCD.Internal.StorableFieldType import PCD.Internal.Types  -- |Read point data using a user-supplied ASCII point parser.@@ -29,22 +35,24 @@         aux t0 = G.create $                  do v <- GM.new n                     let write = GM.write v-                        go i t+                        go !i !t                           | i == n = return v                           | otherwise = case ATL.parse p t of-                                          ATL.Done t' pt -> write i pt >> -                                                            go (i+1) t'+                                          ATL.Done !t' !pt -> write i pt >> +                                                              go (i+1) t'                                           ATL.Fail _ _ msg -> error msg                     go 0 t0  -- |Load points of unknown dimension into a boxed vector with a list -- of 'FieldType' as the point representation.-readAsciiPointsDefault :: Header -> Handle -> IO (B.Vector [FieldType])-readAsciiPointsDefault pcd h = readAsciiPoints pcd h $ pointParser pcd+readAsciiPointsDefault :: Header -> Handle -> IO (B.Vector (B.Vector FieldType))+readAsciiPointsDefault pcd h = readAsciiPoints pcd h $ +                               B.fromList <$> pointParser pcd  -- |Read back 'Storable' points saved as binary data.-readBinPoints :: forall a. Storable a => Header -> Handle -> IO (Either String (Vector a))-readBinPoints pcd h+readHomogenousBinaryPoints :: forall a. Storable a => +                              Header -> Handle -> IO (Either String (Vector a))+readHomogenousBinaryPoints pcd h   | ptSize /= sz = return . Left $                     "Deserialization type is not the same size as the points "++                    "described by this file. The PCD file dicates "++@@ -66,7 +74,7 @@                  IO (Either String (Vector a)) readPointData hd h pa    | hd^.format == ASCII = readAsciiPoints hd h pa >>= return . Right-  | otherwise = readBinPoints hd h+  | otherwise = readHomogenousBinaryPoints hd h  -- |Parse 3D points serialized in ASCII. readXYZ_ascii :: Fractional a => ATL.Parser (V3 a)@@ -92,25 +100,24 @@      withBinaryFile outputFile AppendMode $ \h ->        V.unsafeWith pts (flip (hPutBuf h) sz) --- |Convert the single-precision floating point XYZ or XYZW (where--- \"W\" may be an RGB triple encoded as a single float) points in an--- ASCII PCD to a binary PCD file.+-- |@asciiToBinary inputFile outputFile@ converts a PCD file from+-- ASCII to Binary. asciiToBinary :: FilePath -> FilePath -> IO () asciiToBinary i o = do h <- openFile i ReadMode                        (pcdh,_) <- readHeader h                        pcdh `deepseq` print pcdh-                       when ((pcdh^.format) /= ASCII)+                       when (pcdh^.format /= ASCII)                             (error "Input PCD is already binary!")-                       case pcdh ^. sizes of-                         [4,4,4] -> readAsciiPoints pcdh h readXYZ_ascii >>=-                                    (saveBinaryPcd o pcdh-                                       :: V.Vector (V3 Float) -> IO ())-                         [4,4,4,4] -> readAsciiPoints pcdh h readXYZW_ascii >>=-                                      (saveBinaryPcd o pcdh-                                         :: V.Vector (V4 Float) -> IO ())-                         _ -> error $ "Only 32-bit floating point 3D or 4D "++-                                     "points are supported."+                       let numBytes = totalBinarySize pcdh+                       putStrLn $ "Expecting to generate "++show numBytes++" bytes"+                       v <- readAsciiPoints pcdh h (B.fromList <$> pointParser pcdh)+                       putStrLn $ "Parsed "++show (B.length v)++" ASCII points"                        hClose h+                       T.writeFile o (writeHeader (format .~ Binary $ pcdh))+                       withBinaryFile o AppendMode $ \h' ->+                         allocaBytes numBytes $ \ptr ->+                           pokeBinaryPoints ptr v >>+                           hPutBuf h' ptr numBytes  -- |Load points stored in a PCD file into a 'Vector'. loadPoints :: Storable a => ATL.Parser a -> FilePath -> IO (Vector a)@@ -134,3 +141,21 @@ loadXyzw = loadPoints readXYZW_ascii {-# SPECIALIZE loadXyzw :: FilePath -> IO (Vector (V4 Float)) #-} {-# SPECIALIZE loadXyzw :: FilePath -> IO (Vector (V4 Double)) #-}++-- |Parse every field of every point in a PCD file. Returns a function+-- that may be used to project out a named field.+loadAllFields :: FilePath -> IO (Text -> B.Vector FieldType)+loadAllFields f = do h <- openFile f ReadMode+                     (pcdh,_) <- readHeader h+                     (mkProjector pcdh <$>+                       if pcdh ^. format == ASCII+                         then readAsciiPoints pcdh h +                                              (B.fromList <$> pointParser pcdh)+                         else parseBinaryPoints pcdh h)+                       <* hClose h+  where mkProjector :: Header -> B.Vector (B.Vector FieldType) -> +                       (Text -> B.Vector FieldType)+        mkProjector h pts = let fieldNames = B.fromList $ h ^. fields+                            in \name -> maybe B.empty +                                              (flip B.map pts . flip (B.!))+                                              (B.findIndex (name==) fieldNames)
src/PCD/Header.hs view
@@ -27,15 +27,51 @@ -- |The PCD format has both ASCII and binary variants. data DataFormat = ASCII | Binary deriving (Eq,Show,Ord) -data FieldType = TUchar Word8-               | TChar Int8-               | TUshort Word16-               | TShort Int16-               | TUint Word32-               | TInt Int32-               | TFloat Float-               | TDouble Double+data FieldType = TUchar  {-# UNPACK #-}!Word8+               | TChar   {-# UNPACK #-}!Int8+               | TUshort {-# UNPACK #-}!Word16+               | TShort  {-# UNPACK #-}!Int16+               | TUint   {-# UNPACK #-}!Word32+               | TInt    {-# UNPACK #-}!Int32+               | TFloat  {-# UNPACK #-}!Float+               | TDouble {-# UNPACK #-}!Double+                 deriving Show +class PCDType a where+  unsafeUnwrap :: FieldType -> a++instance PCDType Word8 where+  unsafeUnwrap (TUchar x) = x+  unsafeUnwrap y = error $ "Tried to unwrap "++show y++" as a Word8"++instance PCDType Int8 where+  unsafeUnwrap (TChar x) = x+  unsafeUnwrap y = error $ "Tried to unwrap "++show y++" as a Int8"++instance PCDType Word16 where+  unsafeUnwrap (TUshort x) = x+  unsafeUnwrap y = error $ "Tried to unwrap "++show y++" as a Word16"++instance PCDType Int16 where+  unsafeUnwrap (TShort x) = x+  unsafeUnwrap y = error $ "Tried to unwrap "++show y++" as a Int16"++instance PCDType Word32 where+  unsafeUnwrap (TUint x) = x+  unsafeUnwrap y = error $ "Tried to unwrap "++show y++" as a Word32"++instance PCDType Int32 where+  unsafeUnwrap (TInt x) = x+  unsafeUnwrap y = error $ "Tried to unwrap "++show y++" as a Int32"++instance PCDType Float where+  unsafeUnwrap (TFloat x) = x+  unsafeUnwrap y = error $ "Tried to unwrap "++show y++" as a Float"++instance PCDType Double where+  unsafeUnwrap (TDouble x) = x+  unsafeUnwrap y = error $ "Tried to unwrap "++show y++" as a Double"+ -- |Construct a parser for a field based on its type and size. fieldParser :: DimType -> Int -> Parser FieldType fieldParser I 1 = TChar <$> (decimal :: Parser Int8)@@ -48,10 +84,18 @@ fieldParser F 8 = TDouble <$> double fieldParser t s = error $ "Unknown field description: "++show t++" "++show s +sequence' :: Monad m => [m a] -> m [a]+sequence' [] = return []+sequence' (x:xs) = do !x' <- x+                      !xs' <- sequence' xs+                      return $ x':xs'++ -- |Assemble a parser for points by sequencing together all necessary -- field parsers. pointParser :: Header -> Parser [FieldType]-pointParser h = sequence $ zipWith fieldParser (_dimTypes h) (_sizes h)+pointParser h = sequence' . map (<* skipSpace) $ +                zipWith fieldParser (_dimTypes h) (_sizes h)  data Header = Header { _version   :: Text                       , _fields    :: [Text]@@ -147,3 +191,9 @@         tshow = T.pack . show         ftshow :: (Foldable t, Show a) => t a -> Text         ftshow = mconcat . intersperse " " . foldMap ((:[]) . tshow)++-- |Compute the number of bytes this point cloud would occupy if+-- serialized with the 'Binary' encoding.+totalBinarySize :: Header -> Int+totalBinarySize h = fromIntegral (h^.points) * +                    sum (zipWith (*) (h^.counts) (h^.sizes))
+ src/PCD/Internal/StorableFieldType.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE ScopedTypeVariables, BangPatterns #-}+module PCD.Internal.StorableFieldType where+import Control.Applicative+import Control.Lens ((^.))+import Control.Monad (void)+import qualified Data.Vector as B+import qualified Data.Vector.Mutable as BM+import PCD.Header+import Foreign.Marshal.Alloc (allocaBytes)+import Foreign.Ptr (Ptr, castPtr, plusPtr)+import Foreign.Storable (Storable, peek, poke, sizeOf)+import System.IO (Handle, hGetBuf)++data P a = P !FieldType {-# UNPACK #-} !(Ptr a)++-- |'peek' a 'Storable' and advance the source pointer past this+-- datum.+peekStep :: forall a b. Storable a => (a -> FieldType) -> Ptr b -> IO (P b)+peekStep mk ptr = P . mk <$> peek (castPtr ptr) +                         <*> pure (plusPtr ptr (sizeOf (undefined::a)))++parseBinaryField :: DimType -> Int -> Ptr a -> IO (P a)+parseBinaryField I 1 = peekStep TChar+parseBinaryField I 2 = peekStep TShort+parseBinaryField I 4 = peekStep TInt+parseBinaryField U 1 = peekStep TUchar+parseBinaryField U 2 = peekStep TUshort+parseBinaryField U 4 = peekStep TUint+parseBinaryField F 4 = peekStep TFloat+parseBinaryField F 8 = peekStep TDouble++parseBinaryPoints :: Header -> Handle -> IO (B.Vector (B.Vector FieldType))+parseBinaryPoints pcdh h = B.unsafeFreeze =<<+                           do v <- BM.new n+                              let go !i !ptr+                                    | i == n = return v+                                    | otherwise = do (pt,ptr') <- pointParser ptr+                                                     BM.write v i pt+                                                     go (i+1) ptr'+                              allocaBytes numBytes $ \ptr -> +                                hGetBuf h ptr numBytes >>+                                go 0 ptr+  where n = fromIntegral $ pcdh ^. points+        numBytes = n * sum (zipWith (*) (pcdh^.counts) (pcdh^.sizes))+        pointParser = parseBinaryFields pcdh++parseBinaryFields :: Header -> Ptr a -> IO (B.Vector FieldType, Ptr a)+parseBinaryFields h ptr = aux ptr+  where numFields = sum (h^.counts)+        aux ptr0 = (\(v,ptr) -> (,) <$> B.unsafeFreeze v <*> pure ptr) =<<+                   do v <- BM.new numFields+                      let write = BM.write v+                          go !i !ptr ss ts cs+                            | i == numFields = return (v,ptr)+                            | otherwise = +                              do P x ptr' <- parseBinaryField (head ts) +                                                              (head ss)+                                                              ptr+                                 write i x+                                 let (c:cs') = cs+                                 if c == 1+                                   then go (i+1) ptr' (tail ss) (tail ts) cs'+                                   else go (i+1) ptr' ss ts (c-1 : cs')+                      go 0 ptr0 (h^.sizes) (h^.dimTypes) (h^.counts)++pokeStep :: forall a b. Storable a => a -> Ptr b -> IO (Ptr b)+pokeStep x ptr = poke (castPtr ptr) x >>+                 return (plusPtr ptr (sizeOf (undefined::a)))++pokeBinaryField :: FieldType -> Ptr a -> IO (Ptr a)+pokeBinaryField (TUchar x) = pokeStep x+pokeBinaryField (TChar x) = pokeStep x+pokeBinaryField (TUshort x) = pokeStep x+pokeBinaryField (TShort x) = pokeStep x+pokeBinaryField (TUint x) = pokeStep x+pokeBinaryField (TInt x) = pokeStep x+pokeBinaryField (TFloat x) = pokeStep x+pokeBinaryField (TDouble x) = pokeStep x++pokeBinaryFields :: Ptr a -> B.Vector FieldType -> IO (Ptr a)+pokeBinaryFields = B.foldM' aux+  where aux ptr x = pokeBinaryField x ptr++pokeBinaryPoints :: Ptr a -> B.Vector (B.Vector FieldType) -> IO ()+pokeBinaryPoints = (void .) . B.foldM' pokeBinaryFields