| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | """ |
| | Binvox to Numpy and back. |
| | |
| | |
| | >>> import numpy as np |
| | >>> import binvox_rw |
| | >>> with open('chair.binvox', 'rb') as f: |
| | ... m1 = binvox_rw.read_as_3d_array(f) |
| | ... |
| | >>> m1.dims |
| | [32, 32, 32] |
| | >>> m1.scale |
| | 41.133000000000003 |
| | >>> m1.translate |
| | [0.0, 0.0, 0.0] |
| | >>> with open('chair_out.binvox', 'wb') as f: |
| | ... m1.write(f) |
| | ... |
| | >>> with open('chair_out.binvox', 'rb') as f: |
| | ... m2 = binvox_rw.read_as_3d_array(f) |
| | ... |
| | >>> m1.dims==m2.dims |
| | True |
| | >>> m1.scale==m2.scale |
| | True |
| | >>> m1.translate==m2.translate |
| | True |
| | >>> np.all(m1.data==m2.data) |
| | True |
| | |
| | >>> with open('chair.binvox', 'rb') as f: |
| | ... md = binvox_rw.read_as_3d_array(f) |
| | ... |
| | >>> with open('chair.binvox', 'rb') as f: |
| | ... ms = binvox_rw.read_as_coord_array(f) |
| | ... |
| | >>> data_ds = binvox_rw.dense_to_sparse(md.data) |
| | >>> data_sd = binvox_rw.sparse_to_dense(ms.data, 32) |
| | >>> np.all(data_sd==md.data) |
| | True |
| | >>> # the ordering of elements returned by numpy.nonzero changes with axis |
| | >>> # ordering, so to compare for equality we first lexically sort the voxels. |
| | >>> np.all(ms.data[:, np.lexsort(ms.data)] == data_ds[:, np.lexsort(data_ds)]) |
| | True |
| | """ |
| |
|
| | import numpy as np |
| |
|
| |
|
| | class Voxels(object): |
| | """ Holds a binvox model. |
| | data is either a three-dimensional numpy boolean array (dense representation) |
| | or a two-dimensional numpy float array (coordinate representation). |
| | |
| | dims, translate and scale are the model metadata. |
| | |
| | dims are the voxel dimensions, e.g. [32, 32, 32] for a 32x32x32 model. |
| | |
| | scale and translate relate the voxels to the original model coordinates. |
| | |
| | To translate voxel coordinates i, j, k to original coordinates x, y, z: |
| | |
| | x_n = (i+.5)/dims[0] |
| | y_n = (j+.5)/dims[1] |
| | z_n = (k+.5)/dims[2] |
| | x = scale*x_n + translate[0] |
| | y = scale*y_n + translate[1] |
| | z = scale*z_n + translate[2] |
| | |
| | """ |
| | def __init__(self, data, dims, translate, scale, axis_order): |
| | self.data = data |
| | self.dims = dims |
| | self.translate = translate |
| | self.scale = scale |
| | assert (axis_order in ('xzy', 'xyz')) |
| | self.axis_order = axis_order |
| |
|
| | def clone(self): |
| | data = self.data.copy() |
| | dims = self.dims[:] |
| | translate = self.translate[:] |
| | return Voxels(data, dims, translate, self.scale, self.axis_order) |
| |
|
| | def write(self, fp): |
| | write(self, fp) |
| |
|
| |
|
| | def read_header(fp): |
| | """ Read binvox header. Mostly meant for internal use. |
| | """ |
| | line = fp.readline().strip() |
| | if not line.startswith(b'#binvox'): |
| | raise IOError('Not a binvox file') |
| | dims = [int(i) for i in fp.readline().strip().split(b' ')[1:]] |
| | translate = [float(i) for i in fp.readline().strip().split(b' ')[1:]] |
| | scale = [float(i) for i in fp.readline().strip().split(b' ')[1:]][0] |
| | line = fp.readline() |
| | return dims, translate, scale |
| |
|
| |
|
| | def read_as_3d_array(fp, fix_coords=True): |
| | """ Read binary binvox format as array. |
| | |
| | Returns the model with accompanying metadata. |
| | |
| | Voxels are stored in a three-dimensional numpy array, which is simple and |
| | direct, but may use a lot of memory for large models. (Storage requirements |
| | are 8*(d^3) bytes, where d is the dimensions of the binvox model. Numpy |
| | boolean arrays use a byte per element). |
| | |
| | Doesn't do any checks on input except for the '#binvox' line. |
| | """ |
| | dims, translate, scale = read_header(fp) |
| | raw_data = np.frombuffer(fp.read(), dtype=np.uint8) |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | values, counts = raw_data[::2], raw_data[1::2] |
| | data = np.repeat(values, counts).astype(np.bool) |
| | data = data.reshape(dims) |
| | if fix_coords: |
| | |
| | data = np.transpose(data, (0, 2, 1)) |
| | axis_order = 'xyz' |
| | else: |
| | axis_order = 'xzy' |
| | return Voxels(data, dims, translate, scale, axis_order) |
| |
|
| |
|
| | def read_as_coord_array(fp, fix_coords=True): |
| | """ Read binary binvox format as coordinates. |
| | |
| | Returns binvox model with voxels in a "coordinate" representation, i.e. an |
| | 3 x N array where N is the number of nonzero voxels. Each column |
| | corresponds to a nonzero voxel and the 3 rows are the (x, z, y) coordinates |
| | of the voxel. (The odd ordering is due to the way binvox format lays out |
| | data). Note that coordinates refer to the binvox voxels, without any |
| | scaling or translation. |
| | |
| | Use this to save memory if your model is very sparse (mostly empty). |
| | |
| | Doesn't do any checks on input except for the '#binvox' line. |
| | """ |
| | dims, translate, scale = read_header(fp) |
| | raw_data = np.frombuffer(fp.read(), dtype=np.uint8) |
| |
|
| | values, counts = raw_data[::2], raw_data[1::2] |
| |
|
| | sz = np.prod(dims) |
| | index, end_index = 0, 0 |
| | end_indices = np.cumsum(counts) |
| | indices = np.concatenate(([0], end_indices[:-1])).astype(end_indices.dtype) |
| |
|
| | values = values.astype(np.bool) |
| | indices = indices[values] |
| | end_indices = end_indices[values] |
| |
|
| | nz_voxels = [] |
| | for index, end_index in zip(indices, end_indices): |
| | nz_voxels.extend(range(index, end_index)) |
| | nz_voxels = np.array(nz_voxels) |
| | |
| | |
| | |
| |
|
| | x = nz_voxels / (dims[0] * dims[1]) |
| | zwpy = nz_voxels % (dims[0] * dims[1]) |
| | z = zwpy / dims[0] |
| | y = zwpy % dims[0] |
| | if fix_coords: |
| | data = np.vstack((x, y, z)) |
| | axis_order = 'xyz' |
| | else: |
| | data = np.vstack((x, z, y)) |
| | axis_order = 'xzy' |
| |
|
| | |
| | return Voxels(np.ascontiguousarray(data), dims, translate, scale, axis_order) |
| |
|
| |
|
| | def dense_to_sparse(voxel_data, dtype=np.int): |
| | """ From dense representation to sparse (coordinate) representation. |
| | No coordinate reordering. |
| | """ |
| | if voxel_data.ndim != 3: |
| | raise ValueError('voxel_data is wrong shape; should be 3D array.') |
| | return np.asarray(np.nonzero(voxel_data), dtype) |
| |
|
| |
|
| | def sparse_to_dense(voxel_data, dims, dtype=np.bool): |
| | if voxel_data.ndim != 2 or voxel_data.shape[0] != 3: |
| | raise ValueError('voxel_data is wrong shape; should be 3xN array.') |
| | if np.isscalar(dims): |
| | dims = [dims] * 3 |
| | dims = np.atleast_2d(dims).T |
| | |
| | xyz = voxel_data.astype(np.int) |
| | |
| | valid_ix = ~np.any((xyz < 0) | (xyz >= dims), 0) |
| | xyz = xyz[:, valid_ix] |
| | out = np.zeros(dims.flatten(), dtype=dtype) |
| | out[tuple(xyz)] = True |
| | return out |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | def write(voxel_model, fp): |
| | """ Write binary binvox format. |
| | |
| | Note that when saving a model in sparse (coordinate) format, it is first |
| | converted to dense format. |
| | |
| | Doesn't check if the model is 'sane'. |
| | |
| | """ |
| | if voxel_model.data.ndim == 2: |
| | |
| | dense_voxel_data = sparse_to_dense(voxel_model.data, voxel_model.dims) |
| | else: |
| | dense_voxel_data = voxel_model.data |
| |
|
| | fp.write('#binvox 1\n') |
| | fp.write('dim ' + ' '.join(map(str, voxel_model.dims)) + '\n') |
| | fp.write('translate ' + ' '.join(map(str, voxel_model.translate)) + '\n') |
| | fp.write('scale ' + str(voxel_model.scale) + '\n') |
| | fp.write('data\n') |
| | if not voxel_model.axis_order in ('xzy', 'xyz'): |
| | raise ValueError('Unsupported voxel model axis order') |
| |
|
| | if voxel_model.axis_order == 'xzy': |
| | voxels_flat = dense_voxel_data.flatten() |
| | elif voxel_model.axis_order == 'xyz': |
| | voxels_flat = np.transpose(dense_voxel_data, (0, 2, 1)).flatten() |
| |
|
| | |
| | state = voxels_flat[0] |
| | ctr = 0 |
| | for c in voxels_flat: |
| | if c == state: |
| | ctr += 1 |
| | |
| | if ctr == 255: |
| | fp.write(chr(state)) |
| | fp.write(chr(ctr)) |
| | ctr = 0 |
| | else: |
| | |
| | fp.write(chr(state)) |
| | fp.write(chr(ctr)) |
| | state = c |
| | ctr = 1 |
| | |
| | if ctr > 0: |
| | fp.write(chr(state)) |
| | fp.write(chr(ctr)) |
| |
|
| |
|
| | if __name__ == '__main__': |
| | import doctest |
| | doctest.testmod() |
| |
|