597 lines
24 KiB
Python
597 lines
24 KiB
Python
"""
|
|
voxelize_pbr_view.py - Multi-view transform PBR voxelization
|
|
Extends voxelize_pbr.py with scale and mesh rotation logic
|
|
Based on dual_grid_view.py and test_ovoxel_pbr_transform.py implementation
|
|
"""
|
|
import os
|
|
import copy
|
|
import sys
|
|
import importlib
|
|
import argparse
|
|
import json
|
|
import math
|
|
import pandas as pd
|
|
import pickle
|
|
import numpy as np
|
|
import torch
|
|
from easydict import EasyDict as edict
|
|
from functools import partial
|
|
import o_voxel
|
|
from utils import get_new_camera_matrix, sphere_normalize_torch
|
|
|
|
|
|
# ==================== PBR-specific transform functions ====================
|
|
|
|
def transform_vertices(vertices, frame):
|
|
"""
|
|
Apply multi-view transform to vertices based on camera transform matrix.
|
|
|
|
Args:
|
|
vertices: torch.Tensor, shape [N, 3], vertex coordinates
|
|
frame: dict containing transform_matrix
|
|
|
|
Returns:
|
|
transformed_vertices: torch.Tensor, shape [N, 3]
|
|
"""
|
|
device = vertices.device
|
|
c2w_orig = torch.tensor(frame['transform_matrix'], dtype=torch.float32, device=device)
|
|
|
|
# Old and new camera matrices
|
|
radius = c2w_orig[:3, 3].norm().item()
|
|
c2w_new = get_new_camera_matrix(radius=radius, yaw=-90/180.0*math.pi, pitch=0.0,
|
|
dtype=torch.float32, device=device)
|
|
w2c_orig = torch.inverse(c2w_orig)
|
|
|
|
# Initial and final axis alignment matrices
|
|
R_init = torch.tensor([
|
|
[1.0, 0.0, 0.0, 0.0],
|
|
[0.0, 0.0, -1.0, 0.0],
|
|
[0.0, 1.0, 0.0, 0.0],
|
|
[0.0, 0.0, 0.0, 1.0]
|
|
], dtype=torch.float32, device=device)
|
|
|
|
R_back = torch.tensor([
|
|
[1.0, 0.0, 0.0, 0.0],
|
|
[0.0, 0.0, 1.0, 0.0],
|
|
[0.0, -1.0, 0.0, 0.0],
|
|
[0.0, 0.0, 0.0, 1.0]
|
|
], dtype=torch.float32, device=device)
|
|
|
|
R_ply = torch.tensor([
|
|
[1.0, 0.0, 0.0, 0.0],
|
|
[0.0, 0.0, 1.0, 0.0],
|
|
[0.0, -1.0, 0.0, 0.0],
|
|
[0.0, 0.0, 0.0, 1.0]
|
|
], dtype=torch.float32, device=device)
|
|
|
|
T_cam = c2w_new @ w2c_orig @ R_ply
|
|
T_final = R_back @ T_cam @ R_init
|
|
|
|
# Apply transform
|
|
vertices = vertices.reshape(-1, 3)
|
|
verts_h = torch.cat([vertices, torch.ones((vertices.shape[0], 1), dtype=torch.float32, device=device)], dim=1)
|
|
verts_trans = (T_final @ verts_h.T).T[:, :3]
|
|
|
|
return verts_trans
|
|
|
|
|
|
def transform_normals(normals, frame):
|
|
"""
|
|
Apply multi-view transform to normals (rotation only).
|
|
Consistent with test_ovoxel_pbr_transform.py implementation.
|
|
|
|
Args:
|
|
normals: torch.Tensor or np.ndarray, shape [N, 3] or [N, 3, 3]
|
|
frame: dict containing transform_matrix
|
|
|
|
Returns:
|
|
transformed_normals: np.ndarray (always returns numpy for dump compatibility)
|
|
"""
|
|
is_numpy = isinstance(normals, np.ndarray)
|
|
if is_numpy:
|
|
normals = torch.from_numpy(normals).float()
|
|
|
|
device = normals.device
|
|
original_shape = normals.shape
|
|
|
|
# Flatten to [N, 3] for processing
|
|
if len(original_shape) == 3:
|
|
normals_flat = normals.reshape(-1, 3)
|
|
else:
|
|
normals_flat = normals
|
|
|
|
c2w_orig = torch.tensor(frame['transform_matrix'], dtype=torch.float32, device=device)
|
|
|
|
# Old and new camera matrices
|
|
radius = c2w_orig[:3, 3].norm().item()
|
|
c2w_new = get_new_camera_matrix(radius=radius, yaw=-90/180.0*math.pi, pitch=0.0,
|
|
dtype=torch.float32, device=device)
|
|
w2c_orig = torch.inverse(c2w_orig)
|
|
|
|
# Axis alignment matrices (rotation part only, 3x3)
|
|
R_init = torch.tensor([
|
|
[1.0, 0.0, 0.0],
|
|
[0.0, 0.0, -1.0],
|
|
[0.0, 1.0, 0.0]
|
|
], dtype=torch.float32, device=device)
|
|
|
|
R_back = torch.tensor([
|
|
[1.0, 0.0, 0.0],
|
|
[0.0, 0.0, 1.0],
|
|
[0.0, -1.0, 0.0]
|
|
], dtype=torch.float32, device=device)
|
|
|
|
R_ply = torch.tensor([
|
|
[1.0, 0.0, 0.0],
|
|
[0.0, 0.0, 1.0],
|
|
[0.0, -1.0, 0.0]
|
|
], dtype=torch.float32, device=device)
|
|
|
|
# Use rotation part only
|
|
T_cam_rot = c2w_new[:3, :3] @ w2c_orig[:3, :3] @ R_ply
|
|
T_final_rot = R_back @ T_cam_rot @ R_init
|
|
|
|
# Apply rotation transform
|
|
normals_trans = torch.matmul(normals_flat, T_final_rot.T)
|
|
|
|
# Re-normalize
|
|
normals_trans = torch.nn.functional.normalize(normals_trans, dim=-1)
|
|
|
|
# Restore original shape
|
|
if len(original_shape) == 3:
|
|
normals_trans = normals_trans.reshape(original_shape)
|
|
|
|
# Always return numpy array for dump compatibility
|
|
return normals_trans.numpy()
|
|
|
|
|
|
def prepare_pbr_dump(dump):
|
|
"""
|
|
Prepare PBR dump data for processing.
|
|
Consistent with voxelize_pbr.py preprocessing.
|
|
|
|
Args:
|
|
dump: raw PBR dump data
|
|
|
|
Returns:
|
|
processed dump data
|
|
"""
|
|
dump = copy.deepcopy(dump)
|
|
|
|
# Fix dump alpha map
|
|
for mat in dump['materials']:
|
|
if mat['alphaTexture'] is not None and mat['alphaMode'] == 'OPAQUE':
|
|
mat['alphaMode'] = 'BLEND'
|
|
|
|
# Append default material
|
|
dump['materials'].append({
|
|
"baseColorFactor": [0.8, 0.8, 0.8],
|
|
"alphaFactor": 1.0,
|
|
"metallicFactor": 0.0,
|
|
"roughnessFactor": 0.5,
|
|
"alphaMode": "OPAQUE",
|
|
"alphaCutoff": 0.5,
|
|
"baseColorTexture": None,
|
|
"alphaTexture": None,
|
|
"metallicTexture": None,
|
|
"roughnessTexture": None,
|
|
})
|
|
|
|
# Filter out empty objects
|
|
dump['objects'] = [
|
|
obj for obj in dump['objects']
|
|
if obj['vertices'].size != 0 and obj['faces'].size != 0
|
|
]
|
|
|
|
return dump
|
|
|
|
|
|
def transform_pbr_dump(dump, frame):
|
|
"""
|
|
Apply multi-view transform to entire PBR dump data.
|
|
|
|
Processing flow (based on test_ovoxel_pbr_transform.py):
|
|
1. Box normalize all vertices (scale only, no center shift)
|
|
2. Sphere normalize
|
|
3. Apply multi-view transform
|
|
4. Normalize back to [-0.5, 0.5]^3
|
|
|
|
Note: All object vertices are processed together (not per-object) for consistency.
|
|
|
|
Args:
|
|
dump: PBR dump data (already preprocessed via prepare_pbr_dump)
|
|
frame: camera frame info
|
|
|
|
Returns:
|
|
transformed_dump: transformed dump data
|
|
total_scale: total scale from original mesh to final mesh
|
|
"""
|
|
transformed_dump = copy.deepcopy(dump)
|
|
|
|
# 1. Collect all vertices
|
|
all_vertices_list = []
|
|
vertex_counts = []
|
|
for obj in transformed_dump['objects']:
|
|
all_vertices_list.append(obj['vertices'])
|
|
vertex_counts.append(len(obj['vertices']))
|
|
|
|
if len(all_vertices_list) == 0:
|
|
return transformed_dump, 1.0
|
|
|
|
all_vertices = np.concatenate(all_vertices_list, axis=0)
|
|
|
|
# 2. Box normalize (scale only, no center shift, consistent with original rendering)
|
|
vertices_min = all_vertices.min(axis=0)
|
|
vertices_max = all_vertices.max(axis=0)
|
|
box_scale_init = 0.99999 / (vertices_max - vertices_min).max()
|
|
all_vertices_box_normalized = all_vertices * box_scale_init
|
|
|
|
all_vertices_tensor = torch.from_numpy(all_vertices_box_normalized).float()
|
|
|
|
# 3. Sphere normalize all vertices together
|
|
all_vertices_sphere, sphere_center, sphere_radius = sphere_normalize_torch(all_vertices_tensor)
|
|
|
|
# 4. Multi-view transform
|
|
all_transformed = transform_vertices(all_vertices_sphere, frame)
|
|
|
|
# 5. Normalize back to [-0.5, 0.5]^3 (all vertices together)
|
|
abs_max = all_transformed.abs().max().item()
|
|
box_scale_final = 0.49999 / abs_max
|
|
all_transformed_normalized = all_transformed * box_scale_final
|
|
|
|
# Compute total scale (from original mesh to final normalized mesh)
|
|
total_scale = box_scale_init * box_scale_final / sphere_radius.item()
|
|
|
|
# 6. Split back to individual objects
|
|
start_idx = 0
|
|
for i, obj in enumerate(transformed_dump['objects']):
|
|
end_idx = start_idx + vertex_counts[i]
|
|
obj['vertices'] = all_transformed_normalized[start_idx:end_idx].numpy()
|
|
start_idx = end_idx
|
|
|
|
# Transform normals
|
|
if obj['normals'] is not None and obj['normals'].size > 0:
|
|
obj['normals'] = transform_normals(obj['normals'], frame)
|
|
|
|
# Fix mat_ids (replace -1 with default material index)
|
|
obj['mat_ids'][obj['mat_ids'] == -1] = len(transformed_dump['materials']) - 1
|
|
|
|
# Validate range
|
|
assert np.all(obj['mat_ids'] >= 0), 'invalid mat_ids'
|
|
assert np.all(obj['vertices'] >= -0.5) and np.all(obj['vertices'] <= 0.5), 'vertices out of range'
|
|
|
|
return transformed_dump, total_scale
|
|
|
|
|
|
def _pbr_voxelize_view(file, sha256, pbr_dump_root, transform_root, root, view_indices=None):
|
|
"""
|
|
Process multi-view PBR voxelization for a single sha256.
|
|
|
|
Args:
|
|
file: local_path from metadata
|
|
sha256: sha256 string
|
|
pbr_dump_root: directory containing PBR dump files
|
|
transform_root: directory containing transform json files
|
|
root: output directory for PBR voxels
|
|
view_indices: list of view indices to process, None for all views
|
|
"""
|
|
try:
|
|
pack = {'sha256': sha256}
|
|
dump = None
|
|
|
|
# Load transforms
|
|
transform_path = os.path.join(transform_root, sha256, 'transforms.json')
|
|
if not os.path.exists(transform_path):
|
|
print(f'Transform file not found for {sha256}, skipping')
|
|
return {'sha256': sha256, 'error': 'Transform file not found'}
|
|
|
|
with open(transform_path, 'r') as f:
|
|
transforms_json = json.load(f)
|
|
transform_mats = transforms_json['frames']
|
|
|
|
# Determine views to process
|
|
if view_indices is None:
|
|
view_indices = list(range(len(transform_mats)))
|
|
else:
|
|
view_indices = [i for i in view_indices if i < len(transform_mats)]
|
|
|
|
# Track processed and skipped counts
|
|
processed_count = 0
|
|
skipped_count = 0
|
|
|
|
for view_idx in view_indices:
|
|
for res in opt.resolution:
|
|
need_process = False
|
|
|
|
# Check if already processed
|
|
# Path structure: pbr_voxels_view_fix_{res}/{sha256}/view{idx:02d}.vxz
|
|
sha256_dir = os.path.join(root, f'pbr_voxels_view_fix_{res}', sha256)
|
|
vxz_path = os.path.join(sha256_dir, f'view{view_idx:02d}.vxz')
|
|
if os.path.exists(vxz_path):
|
|
try:
|
|
info = o_voxel.io.read_vxz_info(vxz_path)
|
|
pack[f'pbr_voxelized_view_fix{view_idx:02d}_{res}'] = True
|
|
pack[f'num_pbr_voxels_view_fix{view_idx:02d}_{res}'] = info['num_voxel']
|
|
skipped_count += 1
|
|
except Exception as e:
|
|
print(f'Error reading {sha256}/view{view_idx:02d}.vxz: {e}, will reprocess')
|
|
need_process = True
|
|
else:
|
|
need_process = True
|
|
|
|
# Process PBR dump
|
|
if need_process:
|
|
# Lazy load dump
|
|
if dump is None:
|
|
pbr_dump_file = os.path.join(pbr_dump_root, 'pbr_dumps', f'{sha256}.pickle')
|
|
if not os.path.exists(pbr_dump_file):
|
|
print(f'PBR dump not found for {sha256}, skipping')
|
|
return {'sha256': sha256, 'error': 'PBR dump not found'}
|
|
|
|
with open(pbr_dump_file, 'rb') as f:
|
|
dump = pickle.load(f)
|
|
|
|
# Prepare dump data
|
|
dump = prepare_pbr_dump(dump)
|
|
|
|
if len(dump['objects']) == 0:
|
|
print(f'No valid objects in PBR dump for {sha256}, skipping')
|
|
return {'sha256': sha256, 'error': 'No valid objects in PBR dump'}
|
|
|
|
# Get transform for current view
|
|
frame = transform_mats[view_idx]
|
|
|
|
# Multi-view transform (deep copy from original dump each time)
|
|
transformed_dump, total_scale = transform_pbr_dump(dump, frame)
|
|
|
|
# PBR voxelization
|
|
coord, attr = o_voxel.convert.blender_dump_to_volumetric_attr(
|
|
transformed_dump,
|
|
grid_size=res,
|
|
aabb=[[-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]],
|
|
mip_level_offset=0,
|
|
verbose=False,
|
|
timing=False
|
|
)
|
|
|
|
# Remove normal and emissive (consistent with voxelize_pbr.py)
|
|
del attr['normal']
|
|
del attr['emissive']
|
|
|
|
# Save .vxz file
|
|
os.makedirs(sha256_dir, exist_ok=True)
|
|
o_voxel.io.write_vxz(vxz_path, coord, attr)
|
|
|
|
# Save scale info
|
|
scale_path = os.path.join(sha256_dir, f'view{view_idx:02d}_scale.json')
|
|
scale_info = {
|
|
'sha256': sha256,
|
|
'view_idx': view_idx,
|
|
'total_scale': float(total_scale),
|
|
}
|
|
with open(scale_path, 'w') as f:
|
|
json.dump(scale_info, f, indent=2)
|
|
|
|
pack[f'pbr_voxelized_view_fix{view_idx:02d}_{res}'] = True
|
|
pack[f'num_pbr_voxels_view_fix{view_idx:02d}_{res}'] = len(coord)
|
|
pack[f'pbr_voxel_scale_view_fix{view_idx:02d}_{res}'] = float(total_scale)
|
|
processed_count += 1
|
|
|
|
# Record processing stats
|
|
pack['_processed_count'] = processed_count
|
|
pack['_skipped_count'] = skipped_count
|
|
|
|
return pack
|
|
|
|
except Exception as e:
|
|
print(f'Error processing {sha256}: {e}')
|
|
import traceback
|
|
traceback.print_exc()
|
|
return {'sha256': sha256, 'error': str(e)}
|
|
|
|
|
|
if __name__ == '__main__':
|
|
dataset_utils = importlib.import_module(f'datasets.{sys.argv[1]}')
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--root', type=str, required=True,
|
|
help='Directory to save the metadata')
|
|
parser.add_argument('--pbr_dump_root', type=str, default=None,
|
|
help='Directory to load PBR dumps')
|
|
parser.add_argument('--transform_root', type=str, default=None,
|
|
help='Directory to load transform json files (renders_cond)')
|
|
parser.add_argument('--pbr_voxel_root', type=str, default=None,
|
|
help='Directory to save voxelized PBR attributes')
|
|
parser.add_argument('--filter_low_aesthetic_score', type=float, default=None,
|
|
help='Filter objects with aesthetic score lower than this value')
|
|
parser.add_argument('--instances', type=str, default=None,
|
|
help='Instances to process')
|
|
parser.add_argument('--view_indices', type=str, default=None,
|
|
help='View indices to process, e.g., "0,1,2" or "0-5". None for all views')
|
|
parser.add_argument('--skip_list', type=str, default=None,
|
|
help='Path to a file containing sha256 hashes to skip (one per line). '
|
|
'Supports format: "sha256" or "dataset/sha256"')
|
|
parser.add_argument('--clean_pbr_dir', type=str, default=None,
|
|
help='Path to clean_pbr directory. Will auto-load {dataset}_clean_output.txt as ok-list, '
|
|
'only sha256 in ok-list will be processed')
|
|
parser.add_argument('--clean_pbr_name', type=str, default=None,
|
|
help='Dataset name prefix for clean_pbr file (e.g., ObjaverseXL_github). '
|
|
'Defaults to sys.argv[1] if not specified')
|
|
dataset_utils.add_args(parser)
|
|
parser.add_argument('--resolution', type=str, default='1024')
|
|
parser.add_argument('--rank', type=int, default=0)
|
|
parser.add_argument('--world_size', type=int, default=1)
|
|
parser.add_argument('--max_workers', type=int, default=0)
|
|
opt = parser.parse_args(sys.argv[2:])
|
|
opt = edict(vars(opt))
|
|
opt.resolution = sorted([int(x) for x in opt.resolution.split(',')], reverse=True)
|
|
opt.pbr_dump_root = opt.pbr_dump_root or opt.root
|
|
opt.transform_root = opt.transform_root or os.path.join(opt.root, 'renders_cond')
|
|
opt.pbr_voxel_root = opt.pbr_voxel_root or opt.root
|
|
|
|
# Parse view_indices
|
|
view_indices = None
|
|
if opt.view_indices is not None:
|
|
view_indices = []
|
|
for part in opt.view_indices.split(','):
|
|
if '-' in part:
|
|
start, end = map(int, part.split('-'))
|
|
view_indices.extend(range(start, end + 1))
|
|
else:
|
|
view_indices.append(int(part))
|
|
view_indices = list(set(view_indices)) # Deduplicate
|
|
view_indices.sort()
|
|
|
|
# Load skip list (sha256 hashes to skip)
|
|
skip_set = set()
|
|
if opt.skip_list is not None and os.path.exists(opt.skip_list):
|
|
with open(opt.skip_list, 'r') as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if line and not line.startswith('#'):
|
|
# Support "dataset/sha256" and plain "sha256" format, extract pure sha256
|
|
skip_set.add(line.split('/')[-1])
|
|
print(f'Loaded {len(skip_set)} items from skip_list: {opt.skip_list}')
|
|
|
|
# Load clean_pbr ok-list (only process approved sha256)
|
|
ok_set = None
|
|
if opt.clean_pbr_dir is not None:
|
|
dataset_name = opt.clean_pbr_name or sys.argv[1]
|
|
clean_file = os.path.join(opt.clean_pbr_dir, f'{dataset_name}_clean_output.txt')
|
|
if os.path.exists(clean_file):
|
|
ok_set = set()
|
|
with open(clean_file, 'r') as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if line and not line.startswith('#'):
|
|
ok_set.add(line.split('/')[-1])
|
|
print(f'Loaded {len(ok_set)} ok items from clean_pbr: {clean_file}')
|
|
else:
|
|
print(f'Warning: clean_pbr file not found: {clean_file}, proceeding without ok-list filter')
|
|
|
|
for res in opt.resolution:
|
|
os.makedirs(os.path.join(opt.pbr_voxel_root, f'pbr_voxels_view_fix_{res}', 'new_records'), exist_ok=True)
|
|
|
|
# Get file list
|
|
if not os.path.exists(os.path.join(opt.root, 'metadata.csv')):
|
|
raise ValueError('metadata.csv not found')
|
|
metadata = pd.read_csv(os.path.join(opt.root, 'metadata.csv')).set_index('sha256')
|
|
if os.path.exists(os.path.join(opt.root, 'aesthetic_scores', 'metadata.csv')):
|
|
metadata = metadata.combine_first(pd.read_csv(os.path.join(opt.root, 'aesthetic_scores','metadata.csv')).set_index('sha256'))
|
|
if os.path.exists(os.path.join(opt.pbr_dump_root, 'pbr_dumps', 'metadata.csv')):
|
|
metadata = metadata.combine_first(pd.read_csv(os.path.join(opt.pbr_dump_root, 'pbr_dumps', 'metadata.csv')).set_index('sha256'))
|
|
|
|
# Check already processed pbr_voxels_view_fix
|
|
for res in opt.resolution:
|
|
if os.path.exists(os.path.join(opt.pbr_voxel_root, f'pbr_voxels_view_fix_{res}', 'metadata.csv')):
|
|
pbr_voxel_metadata = pd.read_csv(os.path.join(opt.pbr_voxel_root, f'pbr_voxels_view_fix_{res}', 'metadata.csv')).set_index('sha256')
|
|
metadata = metadata.combine_first(pbr_voxel_metadata)
|
|
|
|
metadata = metadata.reset_index()
|
|
|
|
if opt.instances is None:
|
|
if opt.filter_low_aesthetic_score is not None:
|
|
metadata = metadata[metadata['aesthetic_score'] >= opt.filter_low_aesthetic_score]
|
|
metadata = metadata[metadata['pbr_dumped'] == True]
|
|
|
|
# Filter out objects with all views already processed
|
|
if view_indices is not None:
|
|
for res in opt.resolution:
|
|
# Check if each specified view is already processed
|
|
all_views_done_col = f'_all_views_done_{res}'
|
|
metadata[all_views_done_col] = True
|
|
for view_idx in view_indices:
|
|
col_name = f'pbr_voxelized_view_fix{view_idx:02d}_{res}'
|
|
if col_name in metadata.columns:
|
|
metadata[all_views_done_col] = metadata[all_views_done_col] & (metadata[col_name] == True)
|
|
else:
|
|
metadata[all_views_done_col] = False
|
|
break
|
|
|
|
# Keep objects with at least one incomplete resolution
|
|
any_incomplete = None
|
|
for res in opt.resolution:
|
|
all_views_done_col = f'_all_views_done_{res}'
|
|
if all_views_done_col in metadata.columns:
|
|
if any_incomplete is None:
|
|
any_incomplete = ~metadata[all_views_done_col]
|
|
else:
|
|
any_incomplete = any_incomplete | ~metadata[all_views_done_col]
|
|
|
|
if any_incomplete is not None:
|
|
before_filter = len(metadata)
|
|
metadata = metadata[any_incomplete]
|
|
print(f'Filtered out {before_filter - len(metadata)} already completed objects')
|
|
else:
|
|
if os.path.exists(opt.instances):
|
|
with open(opt.instances, 'r') as f:
|
|
instances = f.read().splitlines()
|
|
else:
|
|
instances = opt.instances.split(',')
|
|
metadata = metadata[metadata['sha256'].isin(instances)]
|
|
|
|
# Apply skip_list filter (exclude specified sha256)
|
|
if skip_set:
|
|
before_skip = len(metadata)
|
|
metadata = metadata[~metadata['sha256'].isin(skip_set)]
|
|
print(f'Skip list: filtered out {before_skip - len(metadata)} objects, {len(metadata)} remaining')
|
|
|
|
# Apply clean_pbr ok-list filter (only keep approved sha256)
|
|
if ok_set is not None:
|
|
before_ok = len(metadata)
|
|
metadata = metadata[metadata['sha256'].isin(ok_set)]
|
|
print(f'Ok list: kept {len(metadata)} objects out of {before_ok} (filtered {before_ok - len(metadata)})')
|
|
|
|
metadata = metadata.sample(frac=1, random_state=444).reset_index(drop=True)
|
|
start = len(metadata) * opt.rank // opt.world_size
|
|
end = len(metadata) * (opt.rank + 1) // opt.world_size
|
|
metadata = metadata[start:end]
|
|
|
|
print(f'Processing {len(metadata)} objects...')
|
|
if view_indices:
|
|
print(f'View indices to process: {view_indices}')
|
|
else:
|
|
print('Processing all available views')
|
|
|
|
# Process objects
|
|
func = partial(_pbr_voxelize_view,
|
|
pbr_dump_root=opt.pbr_dump_root,
|
|
transform_root=opt.transform_root,
|
|
root=opt.pbr_voxel_root,
|
|
view_indices=view_indices)
|
|
pbr_voxelized = dataset_utils.foreach_instance(metadata, opt.root, func, max_workers=opt.max_workers, desc='Voxelizing PBR views')
|
|
|
|
# Processing summary
|
|
total_processed = pbr_voxelized['_processed_count'].sum() if '_processed_count' in pbr_voxelized.columns else 0
|
|
total_skipped = pbr_voxelized['_skipped_count'].sum() if '_skipped_count' in pbr_voxelized.columns else 0
|
|
print(f'\n========== Processing Summary ==========')
|
|
print(f'Total processed (new): {int(total_processed)}')
|
|
print(f'Total skipped (existing): {int(total_skipped)}')
|
|
print(f'Total items: {int(total_processed + total_skipped)}')
|
|
print(f'=========================================\n')
|
|
|
|
if 'error' in pbr_voxelized.columns:
|
|
errors = pbr_voxelized[pbr_voxelized['error'].notna()]
|
|
if len(errors) > 0:
|
|
with open('errors_pbr_view.txt', 'w') as f:
|
|
f.write('\n'.join(errors['sha256'].tolist()))
|
|
print(f'Errors written to errors_pbr_view.txt ({len(errors)} errors)')
|
|
|
|
# Save metadata
|
|
for res in opt.resolution:
|
|
# Collect all view-related columns
|
|
view_cols = [col for col in pbr_voxelized.columns if f'pbr_voxelized_view_fix' in col and f'_{res}' in col]
|
|
if view_cols:
|
|
# Save metadata for each view
|
|
pbr_voxel_metadata = pbr_voxelized[pbr_voxelized[view_cols].any(axis=1)]
|
|
if len(pbr_voxel_metadata) > 0:
|
|
# Save simplified metadata
|
|
cols_to_save = ['sha256'] + [col for col in pbr_voxelized.columns if f'_{res}' in col]
|
|
cols_to_save = [col for col in cols_to_save if col in pbr_voxelized.columns]
|
|
pbr_voxel_metadata[cols_to_save].to_csv(
|
|
os.path.join(opt.pbr_voxel_root, f'pbr_voxels_view_fix_{res}', 'new_records', f'part_{opt.rank}.csv'),
|
|
index=False
|
|
)
|
|
|
|
print('Done!')
|