House-GAN predicts room masks from a room-relation graph and decodes those masks into normalized layout boxes. Public outputs use normalized center xywh boxes in [0, 1], dataset-local integer room labels, a valid-element mask, and id2label.
Developed by: Nelson Nauata, Kai-Hung Chang, Chin-Yi Cheng, Greg Mori, and Yasutaka Furukawa.
Shared by: creative-graphic-design.
Model type: content-agnostic; task: single-task; conditioning: relation.
Use this package for research inference, conversion checks, and agreement checks against the original implementation for relation-conditioned floorplan generation.
Conditional generation accepts a scene graph with room nodes and adjacency relations:
Do not treat generated layouts as code-compliant architectural plans, safety-reviewed building documents, or license-cleared design assets without separate review.
The converted behavior follows the upstream checkpoint format, graph vocabulary, and vectorized floorplan dataset. Dataset coverage, room taxonomy, and geometric quality inherit the limits of those sources.
Re-run the agreement-check suite against the original implementation with the released assets before publishing converted checkpoints or comparing new results.
Run the download and conversion steps in REPRODUCING.md to create .cache/housegan/converted/housegan-floorplan-d. For local conversion workflows, clone the repository and install the workspace member:
fromhouseganimportHouseGanPipelinepath=".cache/housegan/converted/housegan-floorplan-d"# After Hub publication: from_pretrained("creative-graphic-design/housegan-floorplan-d")pipe=HouseGanPipeline.from_pretrained(path,local_files_only=True)out=pipe(condition_type="relation",scene_graph={"nodes":[{"id":0,"label":"living_room"}]},seed=0,)print(out.bbox)print(out.labels)print(out.mask)
Room labels and relations are normalized into complete graph tensors before generation. Generated masks are decoded to original-implementation ltrb boxes and then converted to the public normalized center xywh schema.
Agreement checks use local-only generated fixtures from the released House-GAN checkpoint and vectorized floorplan assets. Large generated tensors, images, weights, and downloaded artifacts are not committed.
Agreement checks are scoped to target set D, fixed seed 0, relation conditioning, and three selected floorplan graphs when the external assets are present.
Metrics are exact key mapping checks, exact model-output checks against regenerated original-implementation fixtures, and exact public postprocessing checks for bbox, labels, and mask.
See REPRODUCING.md for the commands that download original-implementation assets, generate reference outputs, run agreement checks, convert checkpoints, and smoke-test local loading.
House-GAN loads the released graph-conditioned generator checkpoint. The generator embeds room labels, relation edges, and latent noise, applies convolutional message passing over the room graph, and predicts one mask per room.
CPU is sufficient for import and smoke tests. CUDA is recommended for heavyweight agreement checks against the original implementation and released checkpoint.
Repository code is licensed under Apache-2.0. Upstream House-GAN code and checkpoint redistribution follow the upstream GPL-3.0 license posture, and converted weights should not be uploaded until maintainers approve publication.
@inproceedings{nauata2020housegan,title={House-GAN: Relational Generative Adversarial Networks for Graph-constrained House Layout Generation},author={Nauata, Nelson and Chang, Kai-Hung and Cheng, Chin-Yi and Mori, Greg and Furukawa, Yasutaka},booktitle={ECCV},year={2020}}
classHouseGanConfig(PretrainedConfig):"""Configuration for the House-GAN graph-conditioned generator. Args: dataset_name: Dataset identifier for the vectorized floorplan assets. target_set: House-GAN split target set, one of ``A`` through ``E``. checkpoint_step: Original checkpoint training step. id2label: Public zero-based room label map. relation_id2label: Signed relation label map. latent_dim: Per-room latent vector dimension. node_feature_dim: One-hot room feature dimension. graph_edge_values: Supported signed edge values. mask_size: Generated square room-mask size. canvas_size: Original floorplan canvas size as ``(width, height)``. cmp_channels: CMP feature channel count. num_cmp_layers: Number of CMP layers in the generator. postprocess_threshold: Threshold used by mask-to-box postprocessing. bbox_source: Source of public boxes. source_checkpoint: Original checkpoint path or name. conversion_report: Measured conversion metadata. license_note: Upstream license and research-purpose warning. Examples: >>> config = HouseGanConfig() >>> config.id2label[0] 'living_room' """model_type="housegan"def__init__(self,*,dataset_name:str="housegan_floorplan_vectorized",target_set:str="D",checkpoint_step:int=200000,id2label:Id2LabelMapping|None=None,relation_id2label:Id2LabelMapping|None=None,latent_dim:int=128,node_feature_dim:int=10,graph_edge_values:tuple[int,int]=(-1,1),mask_size:int=32,canvas_size:tuple[int,int]=(256,256),cmp_channels:int=16,num_cmp_layers:int=2,postprocess_threshold:float=0.0,bbox_source:str="generated_mask",source_checkpoint:str|None=None,conversion_report:HouseGanConversionReport|None=None,license_note:str="GPL-3.0 with upstream research-purpose notice",**kwargs:str|int|float|bool|None,)->None:"""Initialize a House-GAN config."""kwargs.pop("label2id",None)kwargs.pop("num_labels",None)kwargs.pop("max_supported_room_type_id",None)self.dataset_name=dataset_nameself.target_set=target_setself.checkpoint_step=checkpoint_stepself.id2label={int(key):valueforkey,valuein(id2labelorDEFAULT_ID2LABEL).items()}self.label2id={value:keyforkey,valueinself.id2label.items()}self.relation_id2label={int(key):valueforkey,valuein(relation_id2labelorDEFAULT_RELATION_ID2LABEL).items()}self.latent_dim=latent_dimself.node_feature_dim=node_feature_dimself.graph_edge_values=tuple(graph_edge_values)self.mask_size=mask_sizeself.canvas_size=tuple(canvas_size)self.cmp_channels=cmp_channelsself.num_cmp_layers=num_cmp_layersself.postprocess_threshold=postprocess_thresholdself.bbox_source=bbox_sourceself.source_checkpoint=source_checkpointself.conversion_report=conversion_reportor{}self.license_note=license_noteself.num_labels=len(self.id2label)self.max_supported_room_type_id=self.num_labels-1super().__init__(id2label=self.id2label,label2id=self.label2id)forkey,valueinkwargs.items():setattr(self,key,value)
__init__(*,dataset_name:str="housegan_floorplan_vectorized",target_set:str="D",checkpoint_step:int=200000,id2label:Id2LabelMapping|None=None,relation_id2label:Id2LabelMapping|None=None,latent_dim:int=128,node_feature_dim:int=10,graph_edge_values:tuple[int,int]=(-1,1),mask_size:int=32,canvas_size:tuple[int,int]=(256,256),cmp_channels:int=16,num_cmp_layers:int=2,postprocess_threshold:float=0.0,bbox_source:str="generated_mask",source_checkpoint:str|None=None,conversion_report:HouseGanConversionReport|None=None,license_note:str="GPL-3.0 with upstream research-purpose notice",**kwargs:str|int|float|bool|None,)->None
Initialize a House-GAN config.
Source code in models/housegan/src/housegan/configuration_housegan.py
def__init__(self,*,dataset_name:str="housegan_floorplan_vectorized",target_set:str="D",checkpoint_step:int=200000,id2label:Id2LabelMapping|None=None,relation_id2label:Id2LabelMapping|None=None,latent_dim:int=128,node_feature_dim:int=10,graph_edge_values:tuple[int,int]=(-1,1),mask_size:int=32,canvas_size:tuple[int,int]=(256,256),cmp_channels:int=16,num_cmp_layers:int=2,postprocess_threshold:float=0.0,bbox_source:str="generated_mask",source_checkpoint:str|None=None,conversion_report:HouseGanConversionReport|None=None,license_note:str="GPL-3.0 with upstream research-purpose notice",**kwargs:str|int|float|bool|None,)->None:"""Initialize a House-GAN config."""kwargs.pop("label2id",None)kwargs.pop("num_labels",None)kwargs.pop("max_supported_room_type_id",None)self.dataset_name=dataset_nameself.target_set=target_setself.checkpoint_step=checkpoint_stepself.id2label={int(key):valueforkey,valuein(id2labelorDEFAULT_ID2LABEL).items()}self.label2id={value:keyforkey,valueinself.id2label.items()}self.relation_id2label={int(key):valueforkey,valuein(relation_id2labelorDEFAULT_RELATION_ID2LABEL).items()}self.latent_dim=latent_dimself.node_feature_dim=node_feature_dimself.graph_edge_values=tuple(graph_edge_values)self.mask_size=mask_sizeself.canvas_size=tuple(canvas_size)self.cmp_channels=cmp_channelsself.num_cmp_layers=num_cmp_layersself.postprocess_threshold=postprocess_thresholdself.bbox_source=bbox_sourceself.source_checkpoint=source_checkpointself.conversion_report=conversion_reportor{}self.license_note=license_noteself.num_labels=len(self.id2label)self.max_supported_room_type_id=self.num_labels-1super().__init__(id2label=self.id2label,label2id=self.label2id)forkey,valueinkwargs.items():setattr(self,key,value)
Source code in models/housegan/src/housegan/graph_schema.py
1415161718192021
@dataclass(frozen=True)classHouseGanRoomNode:"""Room node in a House-GAN scene graph."""id:intlabel:int|strbbox:tuple[float,float,float,float]|None=Noneattributes:Mapping[str,object]|None=None
Source code in models/housegan/src/housegan/graph_schema.py
343536373839
@dataclass(frozen=True)classHouseGanSceneGraph:"""Flat room relation graph used by House-GAN."""nodes:tuple[HouseGanRoomNode,...]relations:tuple[HouseGanRelation,...]|None=None
classHouseGanPipeline(LayoutGenerationPipeline):"""Transformers-side House-GAN layout generation pipeline."""config_class:ClassVar[type[PretrainedConfig]]=HouseGanConfigcomponent_specs:ClassVar[dict[str,PipelineComponentSpec]]=(model_processor_component_specs(model_loader=_load_model_component,processor_loader=_load_processor_component,))config:HouseGanConfigmodel:HouseGanGeneratorprocessor:HouseGanProcessordef__init__(self,model:HouseGanGenerator,processor:HouseGanProcessor|None=None,config:HouseGanConfig|None=None,device:int|torch.device|None=None,)->None:"""Initialize the pipeline."""super().__init__(configormodel.config)self.config=configormodel.configself.model=modelself.processor=processororHouseGanProcessor(config=self.config,)ifdeviceisnotNone:resolved=(torch.device("cpu")ifisinstance(device,int)anddevice<0elsetorch.device(f"cuda:{device}")ifisinstance(device,int)elsedevice)self.to(resolved)@classmethoddef_from_pretrained_components(cls,*,config:PretrainedConfig,components:Mapping[str,PipelineComponent|None],)->"HouseGanPipeline":"""Build a pipeline from loaded model and processor components."""returncls(config=cast(HouseGanConfig,config),model=cast(HouseGanGenerator,components["model"]),processor=cast(HouseGanProcessor,components["processor"]),)@torch.no_grad()def__call__(# ty: ignore[invalid-method-override]self,*,batch_size:int=1,seed:int|None=None,generator:torch.Generator|None=None,condition_type:ConditionType|str=ConditionType.relation,labels:Int[torch.Tensor,"..."]|Int[np.ndarray,"..."]|NestedIntList|None=None,bbox:Float[torch.Tensor,"... 4"]|Float[np.ndarray,"... 4"]|NestedFloatList|None=None,mask:Bool[torch.Tensor,"..."]|Bool[np.ndarray,"..."]|NestedBoolList|None=None,num_elements:int|list[int]|Int[torch.Tensor,"..."]|None=None,box_format:BoxFormat|str=BoxFormat.xywh,normalized:bool=True,canvas_size:tuple[int,int]|None=None,num_inference_steps:int|None=None,scene_graph:HouseGanSceneGraph|HouseGanSceneGraphPayload|list[HouseGanSceneGraphPayload]|None=None,relations:Sequence[HouseGanRelationPayload]|None=None,latents:Float[torch.Tensor,"elements latent"]|None=None,output_type:OutputType="dataclass",return_intermediates:bool=False,)->(LayoutGenerationOutput|dict[str,Shaped[torch.Tensor,"..."]|dict[int,str]|list[dict[str,str|int|float|bool|None]]|None,]):"""Generate a floorplan layout from room relation constraints."""delnum_elements,num_inference_stepsifbatch_size<1:raiseValueError("batch_size must be positive")graph_batch=_expand_graph_batch(scene_graph,batch_size)outputs:list[LayoutGenerationOutput]=[]torch_generator=self.prepare_generator(generator=generator,seed=seed,)forgraph_index,graph_iteminenumerate(graph_batch):condition=self.processor(condition_type=condition_type,scene_graph=graph_item,relations=relations,labels=labels,bbox=bbox,mask=mask,box_format=box_format,normalized=normalized,canvas_size=canvas_size,)node_features=condition["node_features"].to(self.model.device)edges=condition["edges"].to(self.model.device)labels_t=condition["labels"].to(self.model.device)room_count=node_features.shape[0]graph_latents=latentsifgraph_latentsisNone:graph_latents=randn(room_count,self.model.config.latent_dim,generator=torch_generator,device=self.model.device,dtype=next(self.model.parameters()).dtype,)elifgraph_latents.ndim==3:graph_latents=graph_latents[graph_index]model_output=self.model(latents=graph_latents.to(self.model.device),node_features=node_features,edges=edges,)decoded=self.processor.post_process_masks(model_output.masks,labels=labels_t,edges=edges,node_features=node_features,scene_graph=condition["scene_graph"],output_type="dataclass",return_intermediates=return_intermediates,)outputs.append(cast(LayoutGenerationOutput,decoded))merged=_merge_outputs(outputs,output_type=output_type)returnmerged
def__init__(self,model:HouseGanGenerator,processor:HouseGanProcessor|None=None,config:HouseGanConfig|None=None,device:int|torch.device|None=None,)->None:"""Initialize the pipeline."""super().__init__(configormodel.config)self.config=configormodel.configself.model=modelself.processor=processororHouseGanProcessor(config=self.config,)ifdeviceisnotNone:resolved=(torch.device("cpu")ifisinstance(device,int)anddevice<0elsetorch.device(f"cuda:{device}")ifisinstance(device,int)elsedevice)self.to(resolved)
@torch.no_grad()def__call__(# ty: ignore[invalid-method-override]self,*,batch_size:int=1,seed:int|None=None,generator:torch.Generator|None=None,condition_type:ConditionType|str=ConditionType.relation,labels:Int[torch.Tensor,"..."]|Int[np.ndarray,"..."]|NestedIntList|None=None,bbox:Float[torch.Tensor,"... 4"]|Float[np.ndarray,"... 4"]|NestedFloatList|None=None,mask:Bool[torch.Tensor,"..."]|Bool[np.ndarray,"..."]|NestedBoolList|None=None,num_elements:int|list[int]|Int[torch.Tensor,"..."]|None=None,box_format:BoxFormat|str=BoxFormat.xywh,normalized:bool=True,canvas_size:tuple[int,int]|None=None,num_inference_steps:int|None=None,scene_graph:HouseGanSceneGraph|HouseGanSceneGraphPayload|list[HouseGanSceneGraphPayload]|None=None,relations:Sequence[HouseGanRelationPayload]|None=None,latents:Float[torch.Tensor,"elements latent"]|None=None,output_type:OutputType="dataclass",return_intermediates:bool=False,)->(LayoutGenerationOutput|dict[str,Shaped[torch.Tensor,"..."]|dict[int,str]|list[dict[str,str|int|float|bool|None]]|None,]):"""Generate a floorplan layout from room relation constraints."""delnum_elements,num_inference_stepsifbatch_size<1:raiseValueError("batch_size must be positive")graph_batch=_expand_graph_batch(scene_graph,batch_size)outputs:list[LayoutGenerationOutput]=[]torch_generator=self.prepare_generator(generator=generator,seed=seed,)forgraph_index,graph_iteminenumerate(graph_batch):condition=self.processor(condition_type=condition_type,scene_graph=graph_item,relations=relations,labels=labels,bbox=bbox,mask=mask,box_format=box_format,normalized=normalized,canvas_size=canvas_size,)node_features=condition["node_features"].to(self.model.device)edges=condition["edges"].to(self.model.device)labels_t=condition["labels"].to(self.model.device)room_count=node_features.shape[0]graph_latents=latentsifgraph_latentsisNone:graph_latents=randn(room_count,self.model.config.latent_dim,generator=torch_generator,device=self.model.device,dtype=next(self.model.parameters()).dtype,)elifgraph_latents.ndim==3:graph_latents=graph_latents[graph_index]model_output=self.model(latents=graph_latents.to(self.model.device),node_features=node_features,edges=edges,)decoded=self.processor.post_process_masks(model_output.masks,labels=labels_t,edges=edges,node_features=node_features,scene_graph=condition["scene_graph"],output_type="dataclass",return_intermediates=return_intermediates,)outputs.append(cast(LayoutGenerationOutput,decoded))merged=_merge_outputs(outputs,output_type=output_type)returnmerged
classHouseGanProcessor(ProcessorMixin):"""Normalize House-GAN scene graphs and decode generated masks."""attributes:list[str]=[]config_name="processor_config.json"def__init__(self,*,config:HouseGanConfig,default_missing_relation:Literal["not_adjacent","error"]="not_adjacent",)->None:"""Initialize processor metadata."""self.config=configself.id2label={int(key):valueforkey,valueincast(Id2LabelMapping,self.config.id2label).items()}self.label2id={value:keyforkey,valueinself.id2label.items()}self.relation_id2label={int(key):valueforkey,valueincast(Id2LabelMapping,self.config.relation_id2label).items()}self.canvas_size=tuple(self.config.canvas_size)self.mask_size=self.config.mask_sizeself.default_missing_relation=default_missing_relationself.chat_template=Nonedefsave_pretrained(self,save_directory:str|Path,push_to_hub:bool=False,**kwargs:str|int|float|bool|None,)->None:"""Save processor metadata."""delpush_to_hub,kwargsroot=Path(save_directory)root.mkdir(parents=True,exist_ok=True)payload={"config":self.config.to_dict(),"processor_class":self.__class__.__name__,"id2label":self.id2label,"relation_id2label":self.relation_id2label,"canvas_size":self.canvas_size,"mask_size":self.mask_size,"default_missing_relation":self.default_missing_relation,}(root/self.config_name).write_text(json.dumps(payload,indent=2,sort_keys=True),encoding="utf-8")@classmethoddeffrom_pretrained(cls,pretrained_model_name_or_path:str|PathLike[str],cache_dir:str|PathLike[str]|None=None,force_download:bool=False,local_files_only:bool=False,token:str|bool|None=None,revision:str="main",subfolder:str|None=None,**kwargs:str|int|float|bool|None,)->Self:"""Load processor metadata from ``processor_config.json``."""delcache_dir,force_download,local_files_only,token,revision,kwargsroot=Path(pretrained_model_name_or_path)ifsubfolderisnotNone:root=root/subfolderpayload=json.loads((root/cls.config_name).read_text(encoding="utf-8"))config_payload=payload.get("config")ifnotisinstance(config_payload,dict):raiseTypeError("processor config payload must be a dictionary")returncls(config=HouseGanConfig.from_dict(config_payload),default_missing_relation=payload.get("default_missing_relation","not_adjacent"),)def__call__(self,*,condition_type:ConditionType|str=ConditionType.relation,scene_graph:HouseGanSceneGraph|HouseGanSceneGraphPayload|None=None,relations:Sequence[HouseGanRelationPayload]|None=None,labels:Int[torch.Tensor,"..."]|Int[np.ndarray,"..."]|Sequence[Sequence[int]]|Sequence[int]|None=None,bbox:Float[torch.Tensor,"... 4"]|Float[np.ndarray,"... 4"]|Sequence[Sequence[Sequence[float]]]|Sequence[Sequence[float]]|Sequence[ArrayLikeInput]|None=None,mask:Bool[torch.Tensor,"..."]|Bool[np.ndarray,"..."]|Sequence[Sequence[bool]]|Sequence[bool]|Sequence[ArrayLikeInput]|None=None,box_format:BoxFormat|str=BoxFormat.xywh,normalized:bool=True,canvas_size:tuple[int,int]|None=None,return_tensors:Literal["pt"]="pt",)->BatchEncoding:"""Encode public relation inputs into House-GAN tensors."""ifreturn_tensors!="pt":raiseValueError("HouseGanProcessor only supports return_tensors='pt'")self.normalize_condition_type(condition_type)relation_payload=relationsifrelation_payloadisNoneandbboxisnotNoneandlabelsisnotNone:bbox_t,labels_t,_=prepare_layout_tensors(bbox=cast(Float[torch.Tensor,"... 4"]|Float[np.ndarray,"... 4"]|Sequence[Sequence[Sequence[float]]]|Sequence[Sequence[float]]|Sequence[ArrayLikeInput],bbox,),labels=cast(Int[torch.Tensor,"..."]|Int[np.ndarray,"..."]|Sequence[Sequence[int]]|Sequence[int]|Sequence[ArrayLikeInput],labels,),mask=cast(Bool[torch.Tensor,"..."]|Bool[np.ndarray,"..."]|Sequence[Sequence[bool]]|Sequence[bool]|Sequence[ArrayLikeInput]|None,mask,),box_format=normalize_box_format(box_format),normalized=normalized,canvas_size=canvas_sizeorself.canvas_size,clamp_converted_normalized=True,)relation_payload=relation_from_bboxes(_xywh_to_ltrb_list(bbox_t[0]),)labels=labels_t[0]graph=normalize_scene_graph(scene_graph,labels=labels,relations=relation_payload,id2label=self.id2label,)ifself.default_missing_relation=="error"andnotgraph.relations:raiseValueError("House-GAN requires relations when missing-pair policy is 'error'")node_features=graph_to_node_features(graph.nodes,label2id=self.label2id,num_labels=len(self.id2label),)edges=complete_signed_edges(graph.nodes,graph.relations,default_adjacent=False,)label_ids=torch.tensor([self.label2id[node.label]ifisinstance(node.label,str)elseint(node.label)fornodeingraph.nodes],dtype=torch.long,)returnBatchEncoding({"node_features":node_features,"edges":edges,"labels":label_ids,"scene_graph":graph,})defnormalize_condition_type(self,condition_type:ConditionType|str)->ConditionType:"""Normalize and validate the House-GAN condition type."""condition=normalize_condition_type(condition_type)ifconditionisnotConditionType.relation:raiseNotImplementedError("House-GAN supports only condition_type='relation' and aliases ""'scene_graph', 'graph', or 'gen_r'.")returnconditiondefpost_process_masks(self,masks:Float[torch.Tensor,"elements height width"],*,labels:Int[torch.Tensor,"elements"],edges:Int[torch.Tensor,"edges 3"]|None=None,node_features:Float[torch.Tensor,"elements room_labels"]|None=None,scene_graph:HouseGanSceneGraph|None=None,output_type:OutputType="dataclass",return_intermediates:bool=False,)->(LayoutGenerationOutput|dict[str,Shaped[torch.Tensor,"..."]|dict[int,str]|dict[str,Shaped[torch.Tensor,"..."]|HouseGanSceneGraph|None]|None,]):"""Convert generated room masks to public normalized boxes."""bbox_ltrb=mask_to_ltrb(masks,threshold=0.0)bbox_xywh=(ltrb_to_xywh(bbox_ltrb/float(self.mask_size)).unsqueeze(0).clamp(0.0,1.0))labels_b=labels.to(dtype=torch.long).unsqueeze(0)valid=torch.ones(labels_b.shape,dtype=torch.bool,device=labels_b.device)intermediates=Noneifreturn_intermediates:intermediates={"room_masks":masks.detach(),"bbox_ltrb_32":bbox_ltrb,"signed_edges":edges,"node_features":node_features,"scene_graph":scene_graph,}ifoutput_type=="dict":return{"bbox":bbox_xywh,"labels":labels_b,"mask":valid,"id2label":self.id2label,"sequences":edges,"scores":None,"trajectory":None,"intermediates":intermediates,}returnLayoutGenerationOutput(bbox=bbox_xywh,labels=labels_b,mask=valid,id2label=self.id2label,sequences=edges,intermediates=intermediates,)
@classmethoddeffrom_pretrained(cls,pretrained_model_name_or_path:str|PathLike[str],cache_dir:str|PathLike[str]|None=None,force_download:bool=False,local_files_only:bool=False,token:str|bool|None=None,revision:str="main",subfolder:str|None=None,**kwargs:str|int|float|bool|None,)->Self:"""Load processor metadata from ``processor_config.json``."""delcache_dir,force_download,local_files_only,token,revision,kwargsroot=Path(pretrained_model_name_or_path)ifsubfolderisnotNone:root=root/subfolderpayload=json.loads((root/cls.config_name).read_text(encoding="utf-8"))config_payload=payload.get("config")ifnotisinstance(config_payload,dict):raiseTypeError("processor config payload must be a dictionary")returncls(config=HouseGanConfig.from_dict(config_payload),default_missing_relation=payload.get("default_missing_relation","not_adjacent"),)
def__call__(self,*,condition_type:ConditionType|str=ConditionType.relation,scene_graph:HouseGanSceneGraph|HouseGanSceneGraphPayload|None=None,relations:Sequence[HouseGanRelationPayload]|None=None,labels:Int[torch.Tensor,"..."]|Int[np.ndarray,"..."]|Sequence[Sequence[int]]|Sequence[int]|None=None,bbox:Float[torch.Tensor,"... 4"]|Float[np.ndarray,"... 4"]|Sequence[Sequence[Sequence[float]]]|Sequence[Sequence[float]]|Sequence[ArrayLikeInput]|None=None,mask:Bool[torch.Tensor,"..."]|Bool[np.ndarray,"..."]|Sequence[Sequence[bool]]|Sequence[bool]|Sequence[ArrayLikeInput]|None=None,box_format:BoxFormat|str=BoxFormat.xywh,normalized:bool=True,canvas_size:tuple[int,int]|None=None,return_tensors:Literal["pt"]="pt",)->BatchEncoding:"""Encode public relation inputs into House-GAN tensors."""ifreturn_tensors!="pt":raiseValueError("HouseGanProcessor only supports return_tensors='pt'")self.normalize_condition_type(condition_type)relation_payload=relationsifrelation_payloadisNoneandbboxisnotNoneandlabelsisnotNone:bbox_t,labels_t,_=prepare_layout_tensors(bbox=cast(Float[torch.Tensor,"... 4"]|Float[np.ndarray,"... 4"]|Sequence[Sequence[Sequence[float]]]|Sequence[Sequence[float]]|Sequence[ArrayLikeInput],bbox,),labels=cast(Int[torch.Tensor,"..."]|Int[np.ndarray,"..."]|Sequence[Sequence[int]]|Sequence[int]|Sequence[ArrayLikeInput],labels,),mask=cast(Bool[torch.Tensor,"..."]|Bool[np.ndarray,"..."]|Sequence[Sequence[bool]]|Sequence[bool]|Sequence[ArrayLikeInput]|None,mask,),box_format=normalize_box_format(box_format),normalized=normalized,canvas_size=canvas_sizeorself.canvas_size,clamp_converted_normalized=True,)relation_payload=relation_from_bboxes(_xywh_to_ltrb_list(bbox_t[0]),)labels=labels_t[0]graph=normalize_scene_graph(scene_graph,labels=labels,relations=relation_payload,id2label=self.id2label,)ifself.default_missing_relation=="error"andnotgraph.relations:raiseValueError("House-GAN requires relations when missing-pair policy is 'error'")node_features=graph_to_node_features(graph.nodes,label2id=self.label2id,num_labels=len(self.id2label),)edges=complete_signed_edges(graph.nodes,graph.relations,default_adjacent=False,)label_ids=torch.tensor([self.label2id[node.label]ifisinstance(node.label,str)elseint(node.label)fornodeingraph.nodes],dtype=torch.long,)returnBatchEncoding({"node_features":node_features,"edges":edges,"labels":label_ids,"scene_graph":graph,})
Normalize and validate the House-GAN condition type.
Source code in models/housegan/src/housegan/processing_housegan.py
232233234235236237238239240241242243
defnormalize_condition_type(self,condition_type:ConditionType|str)->ConditionType:"""Normalize and validate the House-GAN condition type."""condition=normalize_condition_type(condition_type)ifconditionisnotConditionType.relation:raiseNotImplementedError("House-GAN supports only condition_type='relation' and aliases ""'scene_graph', 'graph', or 'gen_r'.")returncondition
classHouseGanConfig(PretrainedConfig):"""Configuration for the House-GAN graph-conditioned generator. Args: dataset_name: Dataset identifier for the vectorized floorplan assets. target_set: House-GAN split target set, one of ``A`` through ``E``. checkpoint_step: Original checkpoint training step. id2label: Public zero-based room label map. relation_id2label: Signed relation label map. latent_dim: Per-room latent vector dimension. node_feature_dim: One-hot room feature dimension. graph_edge_values: Supported signed edge values. mask_size: Generated square room-mask size. canvas_size: Original floorplan canvas size as ``(width, height)``. cmp_channels: CMP feature channel count. num_cmp_layers: Number of CMP layers in the generator. postprocess_threshold: Threshold used by mask-to-box postprocessing. bbox_source: Source of public boxes. source_checkpoint: Original checkpoint path or name. conversion_report: Measured conversion metadata. license_note: Upstream license and research-purpose warning. Examples: >>> config = HouseGanConfig() >>> config.id2label[0] 'living_room' """model_type="housegan"def__init__(self,*,dataset_name:str="housegan_floorplan_vectorized",target_set:str="D",checkpoint_step:int=200000,id2label:Id2LabelMapping|None=None,relation_id2label:Id2LabelMapping|None=None,latent_dim:int=128,node_feature_dim:int=10,graph_edge_values:tuple[int,int]=(-1,1),mask_size:int=32,canvas_size:tuple[int,int]=(256,256),cmp_channels:int=16,num_cmp_layers:int=2,postprocess_threshold:float=0.0,bbox_source:str="generated_mask",source_checkpoint:str|None=None,conversion_report:HouseGanConversionReport|None=None,license_note:str="GPL-3.0 with upstream research-purpose notice",**kwargs:str|int|float|bool|None,)->None:"""Initialize a House-GAN config."""kwargs.pop("label2id",None)kwargs.pop("num_labels",None)kwargs.pop("max_supported_room_type_id",None)self.dataset_name=dataset_nameself.target_set=target_setself.checkpoint_step=checkpoint_stepself.id2label={int(key):valueforkey,valuein(id2labelorDEFAULT_ID2LABEL).items()}self.label2id={value:keyforkey,valueinself.id2label.items()}self.relation_id2label={int(key):valueforkey,valuein(relation_id2labelorDEFAULT_RELATION_ID2LABEL).items()}self.latent_dim=latent_dimself.node_feature_dim=node_feature_dimself.graph_edge_values=tuple(graph_edge_values)self.mask_size=mask_sizeself.canvas_size=tuple(canvas_size)self.cmp_channels=cmp_channelsself.num_cmp_layers=num_cmp_layersself.postprocess_threshold=postprocess_thresholdself.bbox_source=bbox_sourceself.source_checkpoint=source_checkpointself.conversion_report=conversion_reportor{}self.license_note=license_noteself.num_labels=len(self.id2label)self.max_supported_room_type_id=self.num_labels-1super().__init__(id2label=self.id2label,label2id=self.label2id)forkey,valueinkwargs.items():setattr(self,key,value)
__init__(*,dataset_name:str="housegan_floorplan_vectorized",target_set:str="D",checkpoint_step:int=200000,id2label:Id2LabelMapping|None=None,relation_id2label:Id2LabelMapping|None=None,latent_dim:int=128,node_feature_dim:int=10,graph_edge_values:tuple[int,int]=(-1,1),mask_size:int=32,canvas_size:tuple[int,int]=(256,256),cmp_channels:int=16,num_cmp_layers:int=2,postprocess_threshold:float=0.0,bbox_source:str="generated_mask",source_checkpoint:str|None=None,conversion_report:HouseGanConversionReport|None=None,license_note:str="GPL-3.0 with upstream research-purpose notice",**kwargs:str|int|float|bool|None,)->None
Initialize a House-GAN config.
Source code in models/housegan/src/housegan/configuration_housegan.py
def__init__(self,*,dataset_name:str="housegan_floorplan_vectorized",target_set:str="D",checkpoint_step:int=200000,id2label:Id2LabelMapping|None=None,relation_id2label:Id2LabelMapping|None=None,latent_dim:int=128,node_feature_dim:int=10,graph_edge_values:tuple[int,int]=(-1,1),mask_size:int=32,canvas_size:tuple[int,int]=(256,256),cmp_channels:int=16,num_cmp_layers:int=2,postprocess_threshold:float=0.0,bbox_source:str="generated_mask",source_checkpoint:str|None=None,conversion_report:HouseGanConversionReport|None=None,license_note:str="GPL-3.0 with upstream research-purpose notice",**kwargs:str|int|float|bool|None,)->None:"""Initialize a House-GAN config."""kwargs.pop("label2id",None)kwargs.pop("num_labels",None)kwargs.pop("max_supported_room_type_id",None)self.dataset_name=dataset_nameself.target_set=target_setself.checkpoint_step=checkpoint_stepself.id2label={int(key):valueforkey,valuein(id2labelorDEFAULT_ID2LABEL).items()}self.label2id={value:keyforkey,valueinself.id2label.items()}self.relation_id2label={int(key):valueforkey,valuein(relation_id2labelorDEFAULT_RELATION_ID2LABEL).items()}self.latent_dim=latent_dimself.node_feature_dim=node_feature_dimself.graph_edge_values=tuple(graph_edge_values)self.mask_size=mask_sizeself.canvas_size=tuple(canvas_size)self.cmp_channels=cmp_channelsself.num_cmp_layers=num_cmp_layersself.postprocess_threshold=postprocess_thresholdself.bbox_source=bbox_sourceself.source_checkpoint=source_checkpointself.conversion_report=conversion_reportor{}self.license_note=license_noteself.num_labels=len(self.id2label)self.max_supported_room_type_id=self.num_labels-1super().__init__(id2label=self.id2label,label2id=self.label2id)forkey,valueinkwargs.items():setattr(self,key,value)
Source code in models/housegan/src/housegan/conversion.py
18192021222324
defsha256_file(path:str|Path)->str:"""Compute a SHA256 digest for a file."""digest=hashlib.sha256()withPath(path).open("rb")ashandle:forchunkiniter(lambda:handle.read(1024*1024),b""):digest.update(chunk)returndigest.hexdigest()
defconvert_original_checkpoint(*,checkpoint:str|Path,output_dir:str|Path,target_set:str="D",checkpoint_step:int=200000,)->HouseGanConversionReport:"""Convert a raw House-GAN generator state dict into HF files."""checkpoint_path=Path(checkpoint)raw_state=torch.load(checkpoint_path,map_location="cpu")converted,report=convert_state_dict(raw_state)config=HouseGanConfig(target_set=target_set,checkpoint_step=checkpoint_step,source_checkpoint=checkpoint_path.name,conversion_report={**report.to_dict(),"source_sha256":sha256_file(checkpoint_path),},)model=HouseGanGenerator(config)load_result=model.load_state_dict(converted,strict=True)measured={**config.conversion_report,"missing_keys":list(load_result.missing_keys),"unexpected_keys":list(load_result.unexpected_keys),}model.config.conversion_report=measuredoutput_path=Path(output_dir)processor=HouseGanProcessor(config=config)HouseGanPipeline(model=model,processor=processor,config=config).save_pretrained(output_path)(output_path/"conversion_report.json").write_text(json.dumps(measured,indent=2,sort_keys=True),encoding="utf-8",)returnmeasured
Load a local House-GAN .npy asset without downloading data.
Source code in models/housegan/src/housegan/datasets.py
282930
defload_housegan_numpy(path:str|Path)->Shaped[np.ndarray,"..."]:"""Load a local House-GAN ``.npy`` asset without downloading data."""returnnp.load(Path(path),allow_pickle=True)
Convert one [room_types, ltrb_boxes] row to a scene graph.
Source code in models/housegan/src/housegan/datasets.py
333435363738394041424344454647484950515253
defnormalize_graph_row(row:tuple[Sequence[int],Sequence[Sequence[float]]],*,canvas_size:tuple[int,int]=(256,256),)->HouseGanSceneGraph:"""Convert one ``[room_types, ltrb_boxes]`` row to a scene graph."""delcanvas_sizeroom_types,room_bbs=rowbboxes=np.asarray(room_bbs,dtype=np.float32)/256.0labels=[int(value)-1forvalueinroom_types]nodes=tuple(HouseGanRoomNode(id=index,label=label,bbox=cast(tuple[float,float,float,float],tuple(map(float,bbox))),)forindex,(label,bbox)inenumerate(zip(labels,bboxes,strict=True)))returnHouseGanSceneGraph(nodes=nodes,relations=relation_from_bboxes(bboxes.tolist()))
Build public adjacency relations from normalized ltrb boxes.
Source code in models/housegan/src/housegan/datasets.py
72737475767778
defbuild_edges_from_bboxes(bbox_ltrb:Float[np.ndarray,"elements 4"],*,threshold:float=0.03,)->list[HouseGanRelation]:"""Build public adjacency relations from normalized ``ltrb`` boxes."""returnlist(relation_from_bboxes(bbox_ltrb.tolist(),threshold=threshold))
Source code in models/housegan/src/housegan/graph_schema.py
1415161718192021
@dataclass(frozen=True)classHouseGanRoomNode:"""Room node in a House-GAN scene graph."""id:intlabel:int|strbbox:tuple[float,float,float,float]|None=Noneattributes:Mapping[str,object]|None=None
Source code in models/housegan/src/housegan/graph_schema.py
343536373839
@dataclass(frozen=True)classHouseGanSceneGraph:"""Flat room relation graph used by House-GAN."""nodes:tuple[HouseGanRoomNode,...]relations:tuple[HouseGanRelation,...]|None=None
defnormalize_scene_graph(scene_graph:HouseGanSceneGraph|HouseGanSceneGraphPayload|None,*,labels:Int[torch.Tensor,"..."]|Int[np.ndarray,"..."]|Sequence[int|str]|Sequence[Sequence[int|str]]|None,relations:Sequence[HouseGanRelationPayload]|None,id2label:Mapping[int,str],)->HouseGanSceneGraph:"""Normalize public scene-graph payloads. Args: scene_graph: Dataclass or mapping with ``nodes`` and ``edges`` fields. labels: Optional labels used when no full scene graph is supplied. relations: Optional relation payload. id2label: Public label map. Returns: Normalized scene graph preserving node order. Raises: ValueError: If no nodes can be resolved or labels are invalid. Examples: >>> graph = normalize_scene_graph(None, labels=[0, 1], relations=[], id2label={0: "a", 1: "b"}) >>> len(graph.nodes) 2 """ifisinstance(scene_graph,HouseGanSceneGraph):returnscene_graphifscene_graphisNone:iflabelsisNone:raiseValueError("scene_graph or labels must be provided")label_values=_to_label_sequence(labels)nodes=tuple(HouseGanRoomNode(id=index,label=label)forindex,labelinenumerate(label_values))returnHouseGanSceneGraph(nodes=nodes,relations=_normalize_relations(relations),)nodes_payload=cast(Sequence[HouseGanNodePayload],scene_graph.get("nodes",scene_graph.get("rooms",())),)nodes:list[HouseGanRoomNode]=[]forindex,raw_nodeinenumerate(nodes_payload):item=raw_noderaw_id=item.get("id",index)raw_label=item.get("label_id",item.get("label"))ifraw_labelisNone:raiseValueError("Each House-GAN node requires a label")raw_bbox=item.get("bbox")bbox=tuple(cast(Sequence[float],raw_bbox))ifraw_bboxisnotNoneelseNonenodes.append(HouseGanRoomNode(id=int(cast(int|str,raw_id)),label=cast(int|str,raw_label),bbox=cast(tuple[float,float,float,float]|None,bbox),attributes=cast(Mapping[str,HouseGanScalar]|None,item.get("attributes")),))edges=scene_graph.get("edges",scene_graph.get("relations",relations))ifnotnodes:raiseValueError("House-GAN relation graphs require at least one node")_=id2labelreturnHouseGanSceneGraph(nodes=tuple(nodes),relations=_normalize_relations(cast(Sequence[HouseGanRelationPayload]|None,edges)),)
defgraph_to_node_features(nodes:Sequence[HouseGanRoomNode],*,label2id:Mapping[str,int],num_labels:int,device:torch.device|None=None,)->Float[torch.Tensor,"elements room_labels"]:"""Convert public room labels to 10-way one-hot features."""label_ids=[_label_to_id(node.label,label2id=label2id)fornodeinnodes]labels_t=torch.tensor(label_ids,dtype=torch.long,device=device)iflabels_t.numel()and(int(labels_t.min().item())<0orint(labels_t.max().item())>=num_labels):raiseValueError("House-GAN labels must be dataset-local ids in range")returncast(torch.FloatTensor,torch.nn.functional.one_hot(labels_t,num_classes=num_labels).to(dtype=torch.float32),)
Source code in models/housegan/src/housegan/modeling_housegan.py
6768697071727374
def__init__(self,in_channels:int)->None:"""Initialize the CMP block."""super().__init__()self.encoder=nn.Sequential(*_conv_block(3*in_channels,2*in_channels,3,1,1,act="leaky"),*_conv_block(2*in_channels,2*in_channels,3,1,1,act="leaky"),*_conv_block(2*in_channels,in_channels,3,1,1,act="leaky"),)
classHouseGanPipeline(LayoutGenerationPipeline):"""Transformers-side House-GAN layout generation pipeline."""config_class:ClassVar[type[PretrainedConfig]]=HouseGanConfigcomponent_specs:ClassVar[dict[str,PipelineComponentSpec]]=(model_processor_component_specs(model_loader=_load_model_component,processor_loader=_load_processor_component,))config:HouseGanConfigmodel:HouseGanGeneratorprocessor:HouseGanProcessordef__init__(self,model:HouseGanGenerator,processor:HouseGanProcessor|None=None,config:HouseGanConfig|None=None,device:int|torch.device|None=None,)->None:"""Initialize the pipeline."""super().__init__(configormodel.config)self.config=configormodel.configself.model=modelself.processor=processororHouseGanProcessor(config=self.config,)ifdeviceisnotNone:resolved=(torch.device("cpu")ifisinstance(device,int)anddevice<0elsetorch.device(f"cuda:{device}")ifisinstance(device,int)elsedevice)self.to(resolved)@classmethoddef_from_pretrained_components(cls,*,config:PretrainedConfig,components:Mapping[str,PipelineComponent|None],)->"HouseGanPipeline":"""Build a pipeline from loaded model and processor components."""returncls(config=cast(HouseGanConfig,config),model=cast(HouseGanGenerator,components["model"]),processor=cast(HouseGanProcessor,components["processor"]),)@torch.no_grad()def__call__(# ty: ignore[invalid-method-override]self,*,batch_size:int=1,seed:int|None=None,generator:torch.Generator|None=None,condition_type:ConditionType|str=ConditionType.relation,labels:Int[torch.Tensor,"..."]|Int[np.ndarray,"..."]|NestedIntList|None=None,bbox:Float[torch.Tensor,"... 4"]|Float[np.ndarray,"... 4"]|NestedFloatList|None=None,mask:Bool[torch.Tensor,"..."]|Bool[np.ndarray,"..."]|NestedBoolList|None=None,num_elements:int|list[int]|Int[torch.Tensor,"..."]|None=None,box_format:BoxFormat|str=BoxFormat.xywh,normalized:bool=True,canvas_size:tuple[int,int]|None=None,num_inference_steps:int|None=None,scene_graph:HouseGanSceneGraph|HouseGanSceneGraphPayload|list[HouseGanSceneGraphPayload]|None=None,relations:Sequence[HouseGanRelationPayload]|None=None,latents:Float[torch.Tensor,"elements latent"]|None=None,output_type:OutputType="dataclass",return_intermediates:bool=False,)->(LayoutGenerationOutput|dict[str,Shaped[torch.Tensor,"..."]|dict[int,str]|list[dict[str,str|int|float|bool|None]]|None,]):"""Generate a floorplan layout from room relation constraints."""delnum_elements,num_inference_stepsifbatch_size<1:raiseValueError("batch_size must be positive")graph_batch=_expand_graph_batch(scene_graph,batch_size)outputs:list[LayoutGenerationOutput]=[]torch_generator=self.prepare_generator(generator=generator,seed=seed,)forgraph_index,graph_iteminenumerate(graph_batch):condition=self.processor(condition_type=condition_type,scene_graph=graph_item,relations=relations,labels=labels,bbox=bbox,mask=mask,box_format=box_format,normalized=normalized,canvas_size=canvas_size,)node_features=condition["node_features"].to(self.model.device)edges=condition["edges"].to(self.model.device)labels_t=condition["labels"].to(self.model.device)room_count=node_features.shape[0]graph_latents=latentsifgraph_latentsisNone:graph_latents=randn(room_count,self.model.config.latent_dim,generator=torch_generator,device=self.model.device,dtype=next(self.model.parameters()).dtype,)elifgraph_latents.ndim==3:graph_latents=graph_latents[graph_index]model_output=self.model(latents=graph_latents.to(self.model.device),node_features=node_features,edges=edges,)decoded=self.processor.post_process_masks(model_output.masks,labels=labels_t,edges=edges,node_features=node_features,scene_graph=condition["scene_graph"],output_type="dataclass",return_intermediates=return_intermediates,)outputs.append(cast(LayoutGenerationOutput,decoded))merged=_merge_outputs(outputs,output_type=output_type)returnmerged
def__init__(self,model:HouseGanGenerator,processor:HouseGanProcessor|None=None,config:HouseGanConfig|None=None,device:int|torch.device|None=None,)->None:"""Initialize the pipeline."""super().__init__(configormodel.config)self.config=configormodel.configself.model=modelself.processor=processororHouseGanProcessor(config=self.config,)ifdeviceisnotNone:resolved=(torch.device("cpu")ifisinstance(device,int)anddevice<0elsetorch.device(f"cuda:{device}")ifisinstance(device,int)elsedevice)self.to(resolved)
@torch.no_grad()def__call__(# ty: ignore[invalid-method-override]self,*,batch_size:int=1,seed:int|None=None,generator:torch.Generator|None=None,condition_type:ConditionType|str=ConditionType.relation,labels:Int[torch.Tensor,"..."]|Int[np.ndarray,"..."]|NestedIntList|None=None,bbox:Float[torch.Tensor,"... 4"]|Float[np.ndarray,"... 4"]|NestedFloatList|None=None,mask:Bool[torch.Tensor,"..."]|Bool[np.ndarray,"..."]|NestedBoolList|None=None,num_elements:int|list[int]|Int[torch.Tensor,"..."]|None=None,box_format:BoxFormat|str=BoxFormat.xywh,normalized:bool=True,canvas_size:tuple[int,int]|None=None,num_inference_steps:int|None=None,scene_graph:HouseGanSceneGraph|HouseGanSceneGraphPayload|list[HouseGanSceneGraphPayload]|None=None,relations:Sequence[HouseGanRelationPayload]|None=None,latents:Float[torch.Tensor,"elements latent"]|None=None,output_type:OutputType="dataclass",return_intermediates:bool=False,)->(LayoutGenerationOutput|dict[str,Shaped[torch.Tensor,"..."]|dict[int,str]|list[dict[str,str|int|float|bool|None]]|None,]):"""Generate a floorplan layout from room relation constraints."""delnum_elements,num_inference_stepsifbatch_size<1:raiseValueError("batch_size must be positive")graph_batch=_expand_graph_batch(scene_graph,batch_size)outputs:list[LayoutGenerationOutput]=[]torch_generator=self.prepare_generator(generator=generator,seed=seed,)forgraph_index,graph_iteminenumerate(graph_batch):condition=self.processor(condition_type=condition_type,scene_graph=graph_item,relations=relations,labels=labels,bbox=bbox,mask=mask,box_format=box_format,normalized=normalized,canvas_size=canvas_size,)node_features=condition["node_features"].to(self.model.device)edges=condition["edges"].to(self.model.device)labels_t=condition["labels"].to(self.model.device)room_count=node_features.shape[0]graph_latents=latentsifgraph_latentsisNone:graph_latents=randn(room_count,self.model.config.latent_dim,generator=torch_generator,device=self.model.device,dtype=next(self.model.parameters()).dtype,)elifgraph_latents.ndim==3:graph_latents=graph_latents[graph_index]model_output=self.model(latents=graph_latents.to(self.model.device),node_features=node_features,edges=edges,)decoded=self.processor.post_process_masks(model_output.masks,labels=labels_t,edges=edges,node_features=node_features,scene_graph=condition["scene_graph"],output_type="dataclass",return_intermediates=return_intermediates,)outputs.append(cast(LayoutGenerationOutput,decoded))merged=_merge_outputs(outputs,output_type=output_type)returnmerged
classHouseGanProcessor(ProcessorMixin):"""Normalize House-GAN scene graphs and decode generated masks."""attributes:list[str]=[]config_name="processor_config.json"def__init__(self,*,config:HouseGanConfig,default_missing_relation:Literal["not_adjacent","error"]="not_adjacent",)->None:"""Initialize processor metadata."""self.config=configself.id2label={int(key):valueforkey,valueincast(Id2LabelMapping,self.config.id2label).items()}self.label2id={value:keyforkey,valueinself.id2label.items()}self.relation_id2label={int(key):valueforkey,valueincast(Id2LabelMapping,self.config.relation_id2label).items()}self.canvas_size=tuple(self.config.canvas_size)self.mask_size=self.config.mask_sizeself.default_missing_relation=default_missing_relationself.chat_template=Nonedefsave_pretrained(self,save_directory:str|Path,push_to_hub:bool=False,**kwargs:str|int|float|bool|None,)->None:"""Save processor metadata."""delpush_to_hub,kwargsroot=Path(save_directory)root.mkdir(parents=True,exist_ok=True)payload={"config":self.config.to_dict(),"processor_class":self.__class__.__name__,"id2label":self.id2label,"relation_id2label":self.relation_id2label,"canvas_size":self.canvas_size,"mask_size":self.mask_size,"default_missing_relation":self.default_missing_relation,}(root/self.config_name).write_text(json.dumps(payload,indent=2,sort_keys=True),encoding="utf-8")@classmethoddeffrom_pretrained(cls,pretrained_model_name_or_path:str|PathLike[str],cache_dir:str|PathLike[str]|None=None,force_download:bool=False,local_files_only:bool=False,token:str|bool|None=None,revision:str="main",subfolder:str|None=None,**kwargs:str|int|float|bool|None,)->Self:"""Load processor metadata from ``processor_config.json``."""delcache_dir,force_download,local_files_only,token,revision,kwargsroot=Path(pretrained_model_name_or_path)ifsubfolderisnotNone:root=root/subfolderpayload=json.loads((root/cls.config_name).read_text(encoding="utf-8"))config_payload=payload.get("config")ifnotisinstance(config_payload,dict):raiseTypeError("processor config payload must be a dictionary")returncls(config=HouseGanConfig.from_dict(config_payload),default_missing_relation=payload.get("default_missing_relation","not_adjacent"),)def__call__(self,*,condition_type:ConditionType|str=ConditionType.relation,scene_graph:HouseGanSceneGraph|HouseGanSceneGraphPayload|None=None,relations:Sequence[HouseGanRelationPayload]|None=None,labels:Int[torch.Tensor,"..."]|Int[np.ndarray,"..."]|Sequence[Sequence[int]]|Sequence[int]|None=None,bbox:Float[torch.Tensor,"... 4"]|Float[np.ndarray,"... 4"]|Sequence[Sequence[Sequence[float]]]|Sequence[Sequence[float]]|Sequence[ArrayLikeInput]|None=None,mask:Bool[torch.Tensor,"..."]|Bool[np.ndarray,"..."]|Sequence[Sequence[bool]]|Sequence[bool]|Sequence[ArrayLikeInput]|None=None,box_format:BoxFormat|str=BoxFormat.xywh,normalized:bool=True,canvas_size:tuple[int,int]|None=None,return_tensors:Literal["pt"]="pt",)->BatchEncoding:"""Encode public relation inputs into House-GAN tensors."""ifreturn_tensors!="pt":raiseValueError("HouseGanProcessor only supports return_tensors='pt'")self.normalize_condition_type(condition_type)relation_payload=relationsifrelation_payloadisNoneandbboxisnotNoneandlabelsisnotNone:bbox_t,labels_t,_=prepare_layout_tensors(bbox=cast(Float[torch.Tensor,"... 4"]|Float[np.ndarray,"... 4"]|Sequence[Sequence[Sequence[float]]]|Sequence[Sequence[float]]|Sequence[ArrayLikeInput],bbox,),labels=cast(Int[torch.Tensor,"..."]|Int[np.ndarray,"..."]|Sequence[Sequence[int]]|Sequence[int]|Sequence[ArrayLikeInput],labels,),mask=cast(Bool[torch.Tensor,"..."]|Bool[np.ndarray,"..."]|Sequence[Sequence[bool]]|Sequence[bool]|Sequence[ArrayLikeInput]|None,mask,),box_format=normalize_box_format(box_format),normalized=normalized,canvas_size=canvas_sizeorself.canvas_size,clamp_converted_normalized=True,)relation_payload=relation_from_bboxes(_xywh_to_ltrb_list(bbox_t[0]),)labels=labels_t[0]graph=normalize_scene_graph(scene_graph,labels=labels,relations=relation_payload,id2label=self.id2label,)ifself.default_missing_relation=="error"andnotgraph.relations:raiseValueError("House-GAN requires relations when missing-pair policy is 'error'")node_features=graph_to_node_features(graph.nodes,label2id=self.label2id,num_labels=len(self.id2label),)edges=complete_signed_edges(graph.nodes,graph.relations,default_adjacent=False,)label_ids=torch.tensor([self.label2id[node.label]ifisinstance(node.label,str)elseint(node.label)fornodeingraph.nodes],dtype=torch.long,)returnBatchEncoding({"node_features":node_features,"edges":edges,"labels":label_ids,"scene_graph":graph,})defnormalize_condition_type(self,condition_type:ConditionType|str)->ConditionType:"""Normalize and validate the House-GAN condition type."""condition=normalize_condition_type(condition_type)ifconditionisnotConditionType.relation:raiseNotImplementedError("House-GAN supports only condition_type='relation' and aliases ""'scene_graph', 'graph', or 'gen_r'.")returnconditiondefpost_process_masks(self,masks:Float[torch.Tensor,"elements height width"],*,labels:Int[torch.Tensor,"elements"],edges:Int[torch.Tensor,"edges 3"]|None=None,node_features:Float[torch.Tensor,"elements room_labels"]|None=None,scene_graph:HouseGanSceneGraph|None=None,output_type:OutputType="dataclass",return_intermediates:bool=False,)->(LayoutGenerationOutput|dict[str,Shaped[torch.Tensor,"..."]|dict[int,str]|dict[str,Shaped[torch.Tensor,"..."]|HouseGanSceneGraph|None]|None,]):"""Convert generated room masks to public normalized boxes."""bbox_ltrb=mask_to_ltrb(masks,threshold=0.0)bbox_xywh=(ltrb_to_xywh(bbox_ltrb/float(self.mask_size)).unsqueeze(0).clamp(0.0,1.0))labels_b=labels.to(dtype=torch.long).unsqueeze(0)valid=torch.ones(labels_b.shape,dtype=torch.bool,device=labels_b.device)intermediates=Noneifreturn_intermediates:intermediates={"room_masks":masks.detach(),"bbox_ltrb_32":bbox_ltrb,"signed_edges":edges,"node_features":node_features,"scene_graph":scene_graph,}ifoutput_type=="dict":return{"bbox":bbox_xywh,"labels":labels_b,"mask":valid,"id2label":self.id2label,"sequences":edges,"scores":None,"trajectory":None,"intermediates":intermediates,}returnLayoutGenerationOutput(bbox=bbox_xywh,labels=labels_b,mask=valid,id2label=self.id2label,sequences=edges,intermediates=intermediates,)
@classmethoddeffrom_pretrained(cls,pretrained_model_name_or_path:str|PathLike[str],cache_dir:str|PathLike[str]|None=None,force_download:bool=False,local_files_only:bool=False,token:str|bool|None=None,revision:str="main",subfolder:str|None=None,**kwargs:str|int|float|bool|None,)->Self:"""Load processor metadata from ``processor_config.json``."""delcache_dir,force_download,local_files_only,token,revision,kwargsroot=Path(pretrained_model_name_or_path)ifsubfolderisnotNone:root=root/subfolderpayload=json.loads((root/cls.config_name).read_text(encoding="utf-8"))config_payload=payload.get("config")ifnotisinstance(config_payload,dict):raiseTypeError("processor config payload must be a dictionary")returncls(config=HouseGanConfig.from_dict(config_payload),default_missing_relation=payload.get("default_missing_relation","not_adjacent"),)
def__call__(self,*,condition_type:ConditionType|str=ConditionType.relation,scene_graph:HouseGanSceneGraph|HouseGanSceneGraphPayload|None=None,relations:Sequence[HouseGanRelationPayload]|None=None,labels:Int[torch.Tensor,"..."]|Int[np.ndarray,"..."]|Sequence[Sequence[int]]|Sequence[int]|None=None,bbox:Float[torch.Tensor,"... 4"]|Float[np.ndarray,"... 4"]|Sequence[Sequence[Sequence[float]]]|Sequence[Sequence[float]]|Sequence[ArrayLikeInput]|None=None,mask:Bool[torch.Tensor,"..."]|Bool[np.ndarray,"..."]|Sequence[Sequence[bool]]|Sequence[bool]|Sequence[ArrayLikeInput]|None=None,box_format:BoxFormat|str=BoxFormat.xywh,normalized:bool=True,canvas_size:tuple[int,int]|None=None,return_tensors:Literal["pt"]="pt",)->BatchEncoding:"""Encode public relation inputs into House-GAN tensors."""ifreturn_tensors!="pt":raiseValueError("HouseGanProcessor only supports return_tensors='pt'")self.normalize_condition_type(condition_type)relation_payload=relationsifrelation_payloadisNoneandbboxisnotNoneandlabelsisnotNone:bbox_t,labels_t,_=prepare_layout_tensors(bbox=cast(Float[torch.Tensor,"... 4"]|Float[np.ndarray,"... 4"]|Sequence[Sequence[Sequence[float]]]|Sequence[Sequence[float]]|Sequence[ArrayLikeInput],bbox,),labels=cast(Int[torch.Tensor,"..."]|Int[np.ndarray,"..."]|Sequence[Sequence[int]]|Sequence[int]|Sequence[ArrayLikeInput],labels,),mask=cast(Bool[torch.Tensor,"..."]|Bool[np.ndarray,"..."]|Sequence[Sequence[bool]]|Sequence[bool]|Sequence[ArrayLikeInput]|None,mask,),box_format=normalize_box_format(box_format),normalized=normalized,canvas_size=canvas_sizeorself.canvas_size,clamp_converted_normalized=True,)relation_payload=relation_from_bboxes(_xywh_to_ltrb_list(bbox_t[0]),)labels=labels_t[0]graph=normalize_scene_graph(scene_graph,labels=labels,relations=relation_payload,id2label=self.id2label,)ifself.default_missing_relation=="error"andnotgraph.relations:raiseValueError("House-GAN requires relations when missing-pair policy is 'error'")node_features=graph_to_node_features(graph.nodes,label2id=self.label2id,num_labels=len(self.id2label),)edges=complete_signed_edges(graph.nodes,graph.relations,default_adjacent=False,)label_ids=torch.tensor([self.label2id[node.label]ifisinstance(node.label,str)elseint(node.label)fornodeingraph.nodes],dtype=torch.long,)returnBatchEncoding({"node_features":node_features,"edges":edges,"labels":label_ids,"scene_graph":graph,})
Normalize and validate the House-GAN condition type.
Source code in models/housegan/src/housegan/processing_housegan.py
232233234235236237238239240241242243
defnormalize_condition_type(self,condition_type:ConditionType|str)->ConditionType:"""Normalize and validate the House-GAN condition type."""condition=normalize_condition_type(condition_type)ifconditionisnotConditionType.relation:raiseNotImplementedError("House-GAN supports only condition_type='relation' and aliases ""'scene_graph', 'graph', or 'gen_r'.")returncondition
Source code in models/housegan/src/housegan/vendor_state_dict.py
2425262728293031
defto_dict(self)->HouseGanConversionReport:"""Serialize report to JSON-compatible values."""return{"key_count":self.key_count,"tensor_shapes":self.tensor_shapes,"missing_keys":self.missing_keys,"unexpected_keys":self.unexpected_keys,}
Validate and copy an original raw generator state dict.
Source code in models/housegan/src/housegan/vendor_state_dict.py
444546474849505152535455565758
defconvert_state_dict(source:Mapping[str,Shaped[torch.Tensor,"..."]],)->tuple[OrderedDict[str,Shaped[torch.Tensor,"..."]],ConversionReport]:"""Validate and copy an original raw generator state dict."""converted:OrderedDict[str,Shaped[torch.Tensor,"..."]]=OrderedDict()forkey,tensorinsource.items():ifnotkey.startswith(EXPECTED_PREFIXES):raiseKeyError(key)converted[key]=tensorreport=ConversionReport(key_count=len(converted),tensor_shapes={key:tuple(value.shape)forkey,valueinconverted.items()},)returnconverted,report
Render normalized center xywh boxes for debugging.
Source code in models/housegan/src/housegan/visualization.py
1213141516171819202122232425262728293031
defrender_layout(bbox:Float[torch.Tensor,"elements 4"],labels:Int[torch.Tensor,"elements"],*,id2label:Mapping[int,str],canvas_size:tuple[int,int]=(256,256),)->Image.Image:"""Render normalized center ``xywh`` boxes for debugging."""width,height=canvas_sizeimage=Image.new("RGB",canvas_size,"white")draw=ImageDraw.Draw(image)forbox,labelinzip(bbox.detach().cpu(),labels.detach().cpu(),strict=True):cx,cy,bw,bh=box.tolist()left=(cx-bw/2.0)*widthtop=(cy-bh/2.0)*heightright=(cx+bw/2.0)*widthbottom=(cy+bh/2.0)*height_=id2label.get(int(label.item()),str(int(label.item())))draw.rectangle((left,top,right,bottom),outline="black",width=2)returnimage