Project: s41467-025-60898-9 | Function: _get_node_features (python)

system: You are an expert Python programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow Python best practices and maintain consistent code style.
Return **only** the complete Python function code.

user: Complete this Python function:

**DESCRIPTION:** Generate node features using Neighboring Gene Components (NGC) for spatial transcriptomics data. The function constructs sparse count matrices where genes serve as features by incorporating spatial relationships from k-nearest neighborhoods.

**KEY TERMS:** {
  "X_trans": "NGC matrix",
  "n_neighbors": "k-nearest neighbors",
  "tps_features_indices": "gene indices"
}

**CONTEXT CODE:** from ..objects import Bering_Graph as BrGraph

def _get_edge_index(
    bg: BrGraph,
    df_spots: pd.DataFrame,
    n_neighbors: int,
):
    '''
    Get edges using KNN graph

    Parameters
    ----------
    bg
        Bering object
    df_spots
        The table of spots from neighboring cells
    n_neighbors
        Number of neighbors in KNN
    '''
    tps_features = df_spots.features.values
    
    features = bg.features.index.values
    features_indices = np.arange(len(features)) # 1-1 match to features
    feature_dict = dict(zip(features, features_indices))
    tps_features_indices = np.array([feature_dict[i] for i in tps_features])
    
    ### edges
    x = df_spots.x.values
    y = df_spots.y.values
    if bg.dimension == '2d':
        coords = np.array([x, y]).T 
    else:
        z = df_spots.z.values
        coords = np.array([x, y, z]).T
    A = kneighbors_graph(
        coords, n_neighbors = n_neighbors, mode = 'distance'
    ) # Adjacency
    A = coo_matrix(A)
    E = np.array((A.row, A.col), dtype = np.int64)
    E = torch.from_numpy(E)

    return A, E, tps_features_indices

def _get_edge_attr(
    A, beta
):
    E_attr = np.exp(-beta * A.data / A.data.mean())
    E_attr = torch.from_numpy(E_attr)
    return E_attr

def _get_node_features(
    bg: BrGraph,
    df_spots: pd.DataFrame,
    A = None, # sparse matrix
    tps_features_indices = None,
    n_neighbors = 10,
    use_coordinates = False,
    use_transcriptomics = True,
    use_morphological = True,
):
    '''
    Generate node features using Neighboring Gene Components (NGC) for spatial transcriptomics data.
    The function constructs sparse count matrices where genes serve as features by incorporating
    spatial relationships from k-nearest neighborhoods.
    '''
    # MASKED: Generate node features using Neighboring Gene Components (NGC) for spatial transcriptomics data by constructing sparse count matrices where genes serve as features incorporating spatial relationships from k-nearest neighborhoods implementation
    raise NotImplementedError("This function is masked for testing purposes.")

def _get_classes(
    bg: BrGraph,
    df_spots: pd.DataFrame,
):  
    Y = [bg.labels_dict[i] for i in df_spots['raw_labels'].values]
    Y = torch.LongTensor(Y)
    Y = F.one_hot(Y, num_classes = bg.n_labels_raw)
    return Y

def _get_pos(
    df_spots: pd.DataFrame,
):
    '''
    Add additional information (2d coordinates and cell ids) 
    of nodes into data.pos

    Parameters
    ----------
    df_spots
        Table of spots from neighboring cells
    df_cells
        Metadata table of cells
    '''
    x = df_spots.x.values
    y = df_spots.y.values
    try:
        z = df_spots.z.values
    except:
        z = np.zeros(len(x), dtype = x.dtype)
    tps_names = df_spots.index.values

    # dist = df_spots['dist_to_centroid'].values 
    # ratio_dist = df_spots['ratio_dist_to_centroid'].values 
    # cell_names = [cell_dict[i] if i in cell_dict.keys() else -1 for i in df_spots.raw_cells.values]
    cell_names = df_spots.raw_cells.values
    pos = np.array([tps_names, x, y, z, cell_names]).T
    # pos = np.array([tps_names, x, y, cell_names, dist, ratio_dist]).T
    pos = torch.from_numpy(pos).double()
    return pos

def BuildGraph(
    bg,
    df_spots: pd.DataFrame,
    n_neighbors: int = 10,
    beta: float = 1.0,
    **kwargs,
):
    '''
    Build nearest neighbor graph for mRNA/protein colocalization.

    Parameters
    ----------
    bg
        Bering object
    df_spots
        The table of spots from a defined spatial region
    n_neighbors
        Number of neighbors in KNN

    Returns
    -------
    Graph of ``torch_geometric.data.Data``, which can be used for training.
    '''
    # get edges
    A, E, tps_features_indices = _get_edge_index(bg, df_spots, n_neighbors)
    # get node features
    X = _get_node_features(bg, df_spots, A, tps_features_indices, n_neighbors, **kwargs)
    # get edge attributes
    E_attr = _get_edge_attr(A, beta)
    # get node additional information
    POS = _get_pos(df_spots)
    Y = _get_classes(bg, df_spots)
    bg.n_node_features = int(X.shape[1])

    # create graph
    G = Data(x = X, edge_index = E, edge_attr = E_attr, y = Y, pos = POS)
    G = G.to(bg.device)

    return G


Complete the function implementation replacing "raise NotImplementedError".