Assembling Features Reading Features

为提升特征叠加工具如联合和交集等在处理大量数据时的性能和可扩展性,引入了自适应细分处理逻辑。当数据无法在物理内存中处理时,通过将原始范围细分为增量处理的子区域来保持在物理内存限制内,从而提高性能。

In order to improve the performance and scalability of feature overlay tools such as Union and Intersect, operational logic called adaptive subdivision processing was added. The use of this logic is triggered when data cannot be processed within the available amount of physical memory. In order to stay in the bounds of physical memory, which greatly improves performance, processing is done incrementally on subdivisions of the original extent. Features which straddle the edges of these subdivisions (also called tiles) will be split at the edge of the tile and then reassembled into a single feature during in the last stage of processing. The vertices introduced at these tile edges will remain in the output features.

Why subdivide the data?

The overlay analysis tools perform best when processing can be done within your machine's physical memory (or RAM). This may not always be possible when working with datasets that contain either a large number of features or very complex features that contain hundreds of thousands or millions of vertices. Previously, when the physical memory was exhausted virtual memory was used, and when it was exhausted an internal paging system was used. Each successive mode of memory management is slower than the previous by an exponential factor.

How do I know when the process was tiled?

Review the messages returned by a tool during or after execution to determine if the input data was tiled. The third line will state "Processing Tiles..." if adaptive subdivision processing occurs, otherwise the input data was not subdivided and the fourth line will state "Cracking Features..."

Example of the messages from a process that was subdivided.

Executing (Identity_1): Identity c:\gp\fgdb.gdb\rivers c:\gp\fgdb.gdb\pf_watersheds c:\gp\fgdb.gdb\rivers_ws

Reading Features...

Cracking Features...

Assembling Features...

Executed (Identity_1) successfully.


Example of the messages from a process that was subdivided.

Executing (Identity_1): Identity c:\gp\fgdb.gdb\rivers c:\gp\fgdb.gdb\pf_watersheds c:\gp\fgdb.gdb\rivers_ws

Reading Features...

Processing Tiles...

Assembling Features...

Executed (Identity_1) successfully.


What do the tiles look like?

Every process starts with a single tile which spans the entire extent of the data. If the data in the single tile is too large to be processed in physical memory, it is subdivided into four equal tiles (using a quadtree approach). Processing then begins on a sub-tile, which is further sub-divided if the data in this second level of tiles is again too large. This continues until the data within each tile can be processed within physical memory. See the example below.

 

The footprint of all the input features.

 

The process begins with a tile that spans the entire extent of all datasets. For reference this is called tile level 1.

 

If the data is too large to process in memory, the level 1 tile is subdivided into four equal tiles. These 4 sub-tiles are called level 2 tiles.

 

Based on the size of data in each tile, some tiles are further subdivided, while others are not.

The tiles are output to the following shapefile c:\Documents and Settings\UserName\Local Settings\Temp\OverlayTile.shp upon completion of a process that required tiling.

Which tools use subdivisions

The following tools from the "Analysis Tools Toolbox" have subdivision logic when dealing with large data.

-Clip

-Erase

-Identity

-Intersect

-Union

-Split

-Symmetrical Difference

Process fails with an "Out of memory" error

The subdivision approach will not help process extremely large features. These are features with many millions of vertices. Splitting and reassembling extremely large features multiple times across tile boundaries is very costly in terms of memory, and may cause "Out of memory" errors if the feature is too large. It is recommended that these features be broken up into smaller features. Road casing for an entire city or a polygon representing a river estuary are examples of very large features with many vertices.

The "Out of memory" error could also happen if a second application is run while a tool is processing. This second application could adjust the available amount of physical memory and render the boundary calculation of the currently processing tile as incorrect, thereby causing the tool process to demand more physical memory than is possible. It is recommended that no other operations be performed on a machine while overlay processing large datasets.

What data format is recommended when working with large data?

The file size limit for the Windows operating system is 2 GB. If the result of a process is going to be a large feature class, writing that output to a shapefile (which is made up of several files) or personal geodatabase (consisting of a single .mdb file) could exceed this 2 GB file size limit. Enterprise and file geodatabases do not have this limitation so they are recommended as the output workspace when using very large datasets.

See Also

·  Temporary files created by Geoprocessing Tools


转载自:

http://resources.esri.com/help/9.3/ArcGISDesktop/com/Gp_ToolRef/analysis_toolbox/tiled_processing_of_large_datasets.htm

(SE3nv) C:\Users\85882\RFdiffusion>python ./scripts/run_inference.py contigmap.contigs=\[150-150\] inference.output_prefix=test_outputs/test inference.num_designs=10 [2025-04-23 12:39:06,956][__main__][INFO] - Found GPU with device_name NVIDIA GeForce GTX 1060. Will run RFdiffusion on NVIDIA GeForce GTX 1060 Reading models from C:\Users\85882\RFdiffusion\rfdiffusion\inference/../../models [2025-04-23 12:39:06,957][rfdiffusion.inference.model_runners][INFO] - Reading checkpoint from C:\Users\85882\RFdiffusion\rfdiffusion\inference/../../models/Base_ckpt.pt This is inf_conf.ckpt_path C:\Users\85882\RFdiffusion\rfdiffusion\inference/../../models/Base_ckpt.pt Assembling -model, -diffuser and -preprocess configs from checkpoint USING MODEL CONFIG: self._conf[model][n_extra_block] = 4 USING MODEL CONFIG: self._conf[model][n_main_block] = 32 USING MODEL CONFIG: self._conf[model][n_ref_block] = 4 USING MODEL CONFIG: self._conf[model][d_msa] = 256 USING MODEL CONFIG: self._conf[model][d_msa_full] = 64 USING MODEL CONFIG: self._conf[model][d_pair] = 128 USING MODEL CONFIG: self._conf[model][d_templ] = 64 USING MODEL CONFIG: self._conf[model][n_head_msa] = 8 USING MODEL CONFIG: self._conf[model][n_head_pair] = 4 USING MODEL CONFIG: self._conf[model][n_head_templ] = 4 USING MODEL CONFIG: self._conf[model][d_hidden] = 32 USING MODEL CONFIG: self._conf[model][d_hidden_templ] = 32 USING MODEL CONFIG: self._conf[model][p_drop] = 0.15 USING MODEL CONFIG: self._conf[model][SE3_param_full] = {'num_layers': 1, 'num_channels': 32, 'num_degrees': 2, 'n_heads': 4, 'div': 4, 'l0_in_features': 8, 'l0_out_features': 8, 'l1_in_features': 3, 'l1_out_features': 2, 'num_edge_features': 32} USING MODEL CONFIG: self._conf[model][SE3_param_topk] = {'num_layers': 1, 'num_channels': 32, 'num_degrees': 2, 'n_heads': 4, 'div': 4, 'l0_in_features': 64, 'l0_out_features': 64, 'l1_in_features': 3, 'l1_out_features': 2, 'num_edge_features': 64} USING MODEL CONFIG: self._conf[model][freeze_track_motif] = Fa
04-25
# train_cnn_lstm.py # PyTorch CNN-LSTM for FY-4B GIIRS profile bias correction (bias = radiosonde - FY4B) # Author: ChatGPT (adapt to your data) # Usage: # python train_cnn_lstm.py --data fy4bt0305_vertical.csv --epochs 60 --batch_size 32 import os import argparse import numpy as np import pandas as pd from scipy import interpolate from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader # ---------------------------- # Config / Column mapping # ---------------------------- DEFAULT_PRESSURE_LEVELS = np.linspace(1000, 100, 101) # 101 levels: 1000 -> 100 hPa DATA_COLUMNS = { "station": "Station_Id_C", "time": "Time_Dev_WQ", "lat": "Lat_Dev", "lon": "Lon_Dev", "pressure": "PRS_HWC", "temp": "TEM", # assume Celsius or Kelvin - see note below "dpt": "DPT", # dewpoint # optionally cloud mask column if present: "clm": "CLM" # if not present, code will fill zeros } # If TEM/DPT in Celsius convert to Kelvin? We'll assume they are in degC; if already K, adjust scale outside. # Function to compute specific humidity from temperature (C) and dewpoint (C) and pressure (hPa) def compute_specific_humidity(t_c, td_c, p_hpa): # Magnus formula for saturation vapor pressure (hPa) over water # t_c, td_c arrays or scalars a = 6.112 b = 17.67 c = 243.5 e_td = a * np.exp(b * td_c / (td_c + c)) # vapor pressure (hPa) q = 0.622 * (e_td / (p_hpa - 0.378 * e_td)) # specific humidity (kg/kg) q_g_per_kg = q * 1000.0 # convert to g/kg (if you prefer) return q # keep kg/kg # ---------------------------- # Data utilities # ---------------------------- def read_and_assemble_profiles(csv_path, pressure_levels=DEFAULT_PRESSURE_LEVELS, min_levels=20): """ Read CSV and assemble profiles grouped by station+time. Returns lists: samples, where each sample is dict with keys: - 'pressure': array - 'fy4b_T': array - 'fy4b_Q': array - 'obs_T': array (if radiosonde TEM present) - 'obs_Q': array (if DPT present -> computed) - 'lat','lon','clm','time' Assumes CSV rows are levels; you may need to adapt column names. """ df = pd.read_csv(csv_path, low_memory=False) # Ensure required columns exist for k,v in DATA_COLUMNS.items(): if v not in df.columns: if k == 'clm': df[v] = 0.0 else: raise ValueError(f"Required column '{v}' not found in CSV. Please adjust DATA_COLUMNS mapping.") # Cast numeric df[DATA_COLUMNS['pressure']] = pd.to_numeric(df[DATA_COLUMNS['pressure']], errors='coerce') df[DATA_COLUMNS['temp']] = pd.to_numeric(df[DATA_COLUMNS['temp']], errors='coerce') df[DATA_COLUMNS['dpt']] = pd.to_numeric(df[DATA_COLUMNS['dpt']], errors='coerce') df = df.dropna(subset=[DATA_COLUMNS['pressure']]) grouped = df.groupby([DATA_COLUMNS['station'], DATA_COLUMNS['time']]) samples = [] for (sid, t), g in grouped: # Build arrays p = g[DATA_COLUMNS['pressure']].values.astype(float) T = g[DATA_COLUMNS['temp']].values.astype(float) DPT = g[DATA_COLUMNS['dpt']].values.astype(float) # convert to SI if needed: assume T/DPT are in degC -> ok for Magnus formula # compute specific humidity Q = compute_specific_humidity(T, DPT, p) # kg/kg if len(p) < min_levels: continue # Interpolate onto standard levels (pressure decreases) try: # Need monotonic p for interp: sort by pressure descending idx = np.argsort(-p) p_sort = p[idx] T_sort = T[idx] Q_sort = Q[idx] # Clip to interpolation range mask = (pressure_levels <= max(p_sort)) & (pressure_levels >= min(p_sort)) if mask.sum() < 10: continue fT = interpolate.interp1d(p_sort, T_sort, kind='linear', bounds_error=False, fill_value="extrapolate") fQ = interpolate.interp1d(p_sort, Q_sort, kind='linear', bounds_error=False, fill_value="extrapolate") T_interp = fT(pressure_levels) Q_interp = fQ(pressure_levels) except Exception as e: continue # Prepare sample dict sample = { "station": sid, "time": t, "lat": float(g[DATA_COLUMNS['lat']].iloc[0]) if DATA_COLUMNS['lat'] in g.columns else 0.0, "lon": float(g[DATA_COLUMNS['lon']].iloc[0]) if DATA_COLUMNS['lon'] in g.columns else 0.0, "clm": float(g[DATA_COLUMNS['clm']].iloc[0]) if DATA_COLUMNS['clm'] in g.columns else 0.0, "pressure": pressure_levels, "obs_T": T_interp, # NOTE: this uses same TEM field as obs; if TEM is FY4B, need separate obs column "obs_Q": Q_interp, # Here we assume the CSV TEM/DPT are radiosonde obs; # If CSV contains both FY4B and obs, you must adapt field names accordingly. } samples.append(sample) return samples # ---------------------------- # Dataset for PyTorch # ---------------------------- class ProfileDataset(Dataset): def __init__(self, samples, scalers=None, use_aux=True): """ samples: list of sample dicts (see read_and_assemble_profiles) We'll build X: [levels, feat_dim] where feat_dim includes fy4bT, fy4bQ (here we use obs as placeholder) y: bias (obs - fy4b) per level. If you have separate FY4B columns adapt upstream. """ self.samples = samples self.use_aux = use_aux # Build arrays X_list = [] Y_list = [] aux_list = [] for s in samples: # Here we only have obs_T/obs_Q; in practice use fy4b_T/fy4b_Q + obs_T/obs_Q # For demonstration, assume CSV contains both 'FY4B_T' & 'OBS_T' etc. # We'll use the same obs as both input and truth unless user provides separate columns. fy4b_T = s.get('fy4b_T', s['obs_T']) fy4b_Q = s.get('fy4b_Q', s['obs_Q']) obs_T = s['obs_T'] obs_Q = s['obs_Q'] # input features per level: [fy4b_T, fy4b_Q] feat = np.stack([fy4b_T, fy4b_Q], axis=1) # shape (levels,2) X_list.append(feat.astype(np.float32)) # target = obs - fy4b (bias) y_T = (obs_T - fy4b_T).astype(np.float32) y_Q = (obs_Q - fy4b_Q).astype(np.float32) # We'll predict both T and Q bias as two-channel output; for simplicity predict T only: Y_list.append(y_T.reshape(-1,1).astype(np.float32)) aux = np.array([s['lat'], s['lon'], s['clm']]).astype(np.float32) aux_list.append(aux) self.X = np.stack(X_list) # (N, levels, 2) self.Y = np.stack(Y_list) # (N, levels, 1) self.aux = np.stack(aux_list) # scalers if scalers is None: # Simple per-feature scaler over levels self.feat_scaler = StandardScaler() N, L, F = self.X.shape self.feat_scaler.fit(self.X.reshape(N*L, F)) else: self.feat_scaler = scalers['feat_scaler'] # scale features N, L, F = self.X.shape self.X = self.feat_scaler.transform(self.X.reshape(N*L, F)).reshape(N, L, F) # scale target self.target_scaler = None def __len__(self): return self.X.shape[0] def __getitem__(self, idx): # return (levels, features) as tensor; model expects (batch, features, levels) for Conv1D x = torch.from_numpy(self.X[idx]) # (levels, feat) y = torch.from_numpy(self.Y[idx]) # (levels,1) aux = torch.from_numpy(self.aux[idx]) # permute x to (feat, levels) x = x.permute(1,0) # (feat, levels) y = y.permute(1,0) # (1, levels) return x, y, aux # ---------------------------- # Model # ---------------------------- class CNN_LSTM_Corrector(nn.Module): def __init__(self, in_channels=2, hidden_channels=64, lstm_hidden=64, levels=101): super().__init__() self.conv1 = nn.Conv1d(in_channels, hidden_channels, kernel_size=3, padding=1) self.bn1 = nn.BatchNorm1d(hidden_channels) self.conv2 = nn.Conv1d(hidden_channels, hidden_channels, kernel_size=3, padding=1) self.bn2 = nn.BatchNorm1d(hidden_channels) # LSTM expects (seq_len, batch, features) -> we'll feed seq_len=levels, features=hidden_channels self.lstm = nn.LSTM(input_size=hidden_channels, hidden_size=lstm_hidden, num_layers=1, bidirectional=True, batch_first=False) self.fc1 = nn.Linear(lstm_hidden*2, 64) self.fc2 = nn.Linear(64, 1) # predict bias per level (T) self.levels = levels def forward(self, x_aux): """ x_aux: x tuple (x, aux) where x: (batch, feat, levels) returns: pred (batch, 1, levels) """ x, aux = x_aux # x shape: (batch, feat, levels) h = self.conv1(x) h = self.bn1(h) h = torch.relu(h) h = self.conv2(h) h = self.bn2(h) h = torch.relu(h) # permute to (levels, batch, hidden_channels) for LSTM h = h.permute(2, 0, 1) # (levels, batch, hidden) out, _ = self.lstm(h) # (levels, batch, 2*lstm_hidden) # apply fc per time step out = out.permute(1, 0, 2) # (batch, levels, 2*h) out = self.fc1(out) # (batch, levels, 64) out = torch.relu(out) out = self.fc2(out) # (batch, levels, 1) out = out.permute(0,2,1) # (batch, 1, levels) -> match y return out # ---------------------------- # Loss with smoothness constraint # ---------------------------- def smoothness_loss(pred): # pred: (batch, 1, levels) diff = pred[:,:,1:] - pred[:,:,:-1] return torch.mean(diff**2) # ---------------------------- # Training loop # ---------------------------- def train_model(model, train_loader, val_loader, epochs=50, lr=1e-3, weight_smooth=0.01, device='cpu', save_path='model.pth'): model.to(device) optimizer = torch.optim.Adam(model.parameters(), lr=lr) criterion = nn.MSELoss() best_val = 1e9 for epoch in range(1, epochs+1): model.train() train_loss = 0.0 for x, y, aux in train_loader: x = x.to(device) # (batch, feat, levels) y = y.to(device) # (batch,1,levels) aux = aux.to(device) optimizer.zero_grad() pred = model((x, aux)) mse = criterion(pred, y) smooth = smoothness_loss(pred) loss = mse + weight_smooth * smooth loss.backward() optimizer.step() train_loss += loss.item() * x.size(0) train_loss /= len(train_loader.dataset) # validation model.eval() val_loss = 0.0 with torch.no_grad(): for x, y, aux in val_loader: x = x.to(device); y = y.to(device); aux = aux.to(device) pred = model((x, aux)) mse = criterion(pred, y) smooth = smoothness_loss(pred) loss = mse + weight_smooth * smooth val_loss += loss.item() * x.size(0) val_loss /= len(val_loader.dataset) print(f"Epoch {epoch:03d} TrainLoss={train_loss:.6f} ValLoss={val_loss:.6f}") if val_loss < best_val: best_val = val_loss torch.save(model.state_dict(), save_path) print(f"Saved best model to {save_path}") return model # ---------------------------- # Main # ---------------------------- def main(args): print("Reading and assembling profiles...") samples = read_and_assemble_profiles(args.data, pressure_levels=DEFAULT_PRESSURE_LEVELS, min_levels=20) print(f"Total samples: {len(samples)}") if len(samples) < 10: raise RuntimeError("Too few samples after assembling. Check CSV format and min_levels setting.") # split train_s, test_s = train_test_split(samples, test_size=0.2, random_state=42) train_s, val_s = train_test_split(train_s, test_size=0.2, random_state=42) train_ds = ProfileDataset(train_s) val_ds = ProfileDataset(val_s, scalers={"feat_scaler": train_ds.feat_scaler}) test_ds = ProfileDataset(test_s, scalers={"feat_scaler": train_ds.feat_scaler}) print("Dataset sizes:", len(train_ds), len(val_ds), len(test_ds)) train_loader = DataLoader(train_ds, batch_size=args.batch_size, shuffle=True, drop_last=False) val_loader = DataLoader(val_ds, batch_size=args.batch_size, shuffle=False) test_loader = DataLoader(test_ds, batch_size=args.batch_size, shuffle=False) device = 'cuda' if torch.cuda.is_available() else 'cpu' model = CNN_LSTM_Corrector(in_channels=2, hidden_channels=64, lstm_hidden=64, levels=DEFAULT_PRESSURE_LEVELS.size) print(model) trained = train_model(model, train_loader, val_loader, epochs=args.epochs, lr=args.lr, weight_smooth=args.smooth, device=device, save_path=args.save) # After training evaluate on test set (compute RMSE/Bias) model.load_state_dict(torch.load(args.save, map_location=device)) model.to(device).eval() import math total_mse = 0.0 total_n = 0 all_bias = [] with torch.no_grad(): for x, y, aux in test_loader: x = x.to(device); y = y.to(device); aux = aux.to(device) pred = model((x, aux)) # (batch,1,levels) # compute bias (mean over levels, then mean over batch) mse = torch.mean((pred - y)**2).item() total_mse += mse * x.size(0) total_n += x.size(0) all_bias.append((pred - y).cpu().numpy()) total_mse /= total_n print(f"Test MSE (on bias): {total_mse:.6f}") # Save scalers & feature mapping for inference import pickle meta = {"feat_scaler": train_ds.feat_scaler} with open(os.path.splitext(args.save)[0] + "_meta.pkl", "wb") as f: pickle.dump(meta, f) print("Saved meta info.") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--data", type=str, default="fy4bt0305_vertical.csv", help="Path to CSV") parser.add_argument("--epochs", type=int, default=50) parser.add_argument("--batch_size", type=int, default=32) parser.add_argument("--lr", type=float, default=1e-3) parser.add_argument("--smooth", type=float, default=0.01, help="weight for smoothness loss") parser.add_argument("--save", type=str, default="cnn_lstm_corrector.pth") args = parser.parse_args() main(args)
11-20
import logging import time from functools import cached_property from typing import Any from lerobot.cameras.utils import make_cameras_from_configs from lerobot.errors import DeviceAlreadyConnectedError, DeviceNotConnectedError from lerobot.motors import Motor, MotorCalibration, MotorNormMode from lerobot.motors.dynamixel import ( DynamixelMotorsBus, OperatingMode, ) from ..robot import Robot from ..utils import ensure_safe_goal_position from .config_koch_follower import KochFollowerConfig logger = logging.getLogger(__name__) class KochFollower(Robot): """ - [Koch v1.0](https://github.com/AlexanderKoch-Koch/low_cost_robot), with and without the wrist-to-elbow expansion, developed by Alexander Koch from [Tau Robotics](https://tau-robotics.com) - [Koch v1.1](https://github.com/jess-moss/koch-v1-1) developed by Jess Moss """ config_class = KochFollowerConfig name = "koch_follower" def __init__(self, config: KochFollowerConfig): super().__init__(config) self.config = config norm_mode_body = MotorNormMode.DEGREES if config.use_degrees else MotorNormMode.RANGE_M100_100 self.bus = DynamixelMotorsBus( port=self.config.port, motors={ "shoulder_pan": Motor(1, "xl430-w250", norm_mode_body), "shoulder_lift": Motor(2, "xl430-w250", norm_mode_body), "elbow_flex": Motor(3, "xl330-m288", norm_mode_body), "wrist_flex": Motor(4, "xl330-m288", norm_mode_body), "wrist_roll": Motor(5, "xl330-m288", norm_mode_body), "gripper": Motor(6, "xl330-m288", MotorNormMode.RANGE_0_100), }, calibration=self.calibration, ) self.cameras = make_cameras_from_configs(config.cameras) @property def _motors_ft(self) -> dict[str, type]: return {f"{motor}.pos": float for motor in self.bus.motors} @property def _cameras_ft(self) -> dict[str, tuple]: return { cam: (self.config.cameras[cam].height, self.config.cameras[cam].width, 3) for cam in self.cameras } @cached_property def observation_features(self) -> dict[str, type | tuple]: return {**self._motors_ft, **self._cameras_ft} @cached_property def action_features(self) -> dict[str, type]: return self._motors_ft @property def is_connected(self) -> bool: return self.bus.is_connected and all(cam.is_connected for cam in self.cameras.values()) def connect(self, calibrate: bool = True) -> None: """ We assume that at connection time, arm is in a rest position, and torque can be safely disabled to run calibration. """ if self.is_connected: raise DeviceAlreadyConnectedError(f"{self} already connected") self.bus.connect() if not self.is_calibrated and calibrate: self.calibrate() for cam in self.cameras.values(): cam.connect() self.configure() logger.info(f"{self} connected.") @property def is_calibrated(self) -> bool: return self.bus.is_calibrated def calibrate(self) -> None: logger.info(f"\nRunning calibration of {self}") self.bus.disable_torque() for motor in self.bus.motors: self.bus.write("Operating_Mode", motor, OperatingMode.EXTENDED_POSITION.value) input(f"Move {self} to the middle of its range of motion and press ENTER....") homing_offsets = self.bus.set_half_turn_homings() full_turn_motors = ["shoulder_pan", "wrist_roll"] unknown_range_motors = [motor for motor in self.bus.motors if motor not in full_turn_motors] print( f"Move all joints except {full_turn_motors} sequentially through their entire " "ranges of motion.\nRecording positions. Press ENTER to stop..." ) range_mins, range_maxes = self.bus.record_ranges_of_motion(unknown_range_motors) for motor in full_turn_motors: range_mins[motor] = 0 range_maxes[motor] = 4095 self.calibration = {} for motor, m in self.bus.motors.items(): self.calibration[motor] = MotorCalibration( id=m.id, drive_mode=0, homing_offset=homing_offsets[motor], range_min=range_mins[motor], range_max=range_maxes[motor], ) self.bus.write_calibration(self.calibration) self._save_calibration() logger.info(f"Calibration saved to {self.calibration_fpath}") def configure(self) -> None: with self.bus.torque_disabled(): self.bus.configure_motors() # Use 'extended position mode' for all motors except gripper, because in joint mode the servos # can't rotate more than 360 degrees (from 0 to 4095) And some mistake can happen while assembling # the arm, you could end up with a servo with a position 0 or 4095 at a crucial point for motor in self.bus.motors: if motor != "gripper": self.bus.write("Operating_Mode", motor, OperatingMode.EXTENDED_POSITION.value) # Use 'position control current based' for gripper to be limited by the limit of the current. For # the follower gripper, it means it can grasp an object without forcing too much even tho, its # goal position is a complete grasp (both gripper fingers are ordered to join and reach a touch). # For the leader gripper, it means we can use it as a physical trigger, since we can force with # our finger to make it move, and it will move back to its original target position when we # release the force. self.bus.write("Operating_Mode", "gripper", OperatingMode.CURRENT_POSITION.value) # Set better PID values to close the gap between recorded states and actions # TODO(rcadene): Implement an automatic procedure to set optimal PID values for each motor self.bus.write("Position_P_Gain", "elbow_flex", 1500) self.bus.write("Position_I_Gain", "elbow_flex", 0) self.bus.write("Position_D_Gain", "elbow_flex", 600) def setup_motors(self) -> None: for motor in reversed(self.bus.motors): input(f"Connect the controller board to the '{motor}' motor only and press enter.") self.bus.setup_motor(motor) print(f"'{motor}' motor id set to {self.bus.motors[motor].id}") def get_observation(self) -> dict[str, Any]: if not self.is_connected: raise DeviceNotConnectedError(f"{self} is not connected.") # Read arm position start = time.perf_counter() obs_dict = self.bus.sync_read("Present_Position") obs_dict = {f"{motor}.pos": val for motor, val in obs_dict.items()} dt_ms = (time.perf_counter() - start) * 1e3 logger.debug(f"{self} read state: {dt_ms:.1f}ms") # Capture images from cameras for cam_key, cam in self.cameras.items(): start = time.perf_counter() obs_dict[cam_key] = cam.async_read() dt_ms = (time.perf_counter() - start) * 1e3 logger.debug(f"{self} read {cam_key}: {dt_ms:.1f}ms") return obs_dict def send_action(self, action: dict[str, float]) -> dict[str, float]: """Command arm to move to a target joint configuration. The relative action magnitude may be clipped depending on the configuration parameter `max_relative_target`. In this case, the action sent differs from original action. Thus, this function always returns the action actually sent. Args: action (dict[str, float]): The goal positions for the motors. Returns: dict[str, float]: The action sent to the motors, potentially clipped. """ if not self.is_connected: raise DeviceNotConnectedError(f"{self} is not connected.") goal_pos = {key.removesuffix(".pos"): val for key, val in action.items() if key.endswith(".pos")} # Cap goal position when too far away from present position. # /!\ Slower fps expected due to reading from the follower. if self.config.max_relative_target is not None: present_pos = self.bus.sync_read("Present_Position") goal_present_pos = {key: (g_pos, present_pos[key]) for key, g_pos in goal_pos.items()} goal_pos = ensure_safe_goal_position(goal_present_pos, self.config.max_relative_target) # Send goal position to the arm self.bus.sync_write("Goal_Position", goal_pos) return {f"{motor}.pos": val for motor, val in goal_pos.items()} def disconnect(self): if not self.is_connected: raise DeviceNotConnectedError(f"{self} is not connected.") self.bus.disconnect(self.config.disable_torque_on_disconnect) for cam in self.cameras.values(): cam.disconnect() logger.info(f"{self} disconnected.")
07-26
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值