def transformation_and_inverse(origin, step, axes): ox, oy, oz = origin d0, d1, d2 = step ax, ay, az = axes from chimerax.geometry import Place tf = Place(((d0 * ax[0], d1 * ay[0], d2 * az[0], ox), (d0 * ax[1], d1 * ay[1], d2 * az[1], oy), (d0 * ax[2], d1 * ay[2], d2 * az[2], oz))) tf_inv = tf.inverse() return tf, tf_inv
def map_points_and_weights(v, above_threshold, point_to_world_xform=Place(), include_zeros=True): m, xyz_to_ijk_tf = v.matrix_and_transform(point_to_world_xform, subregion=None, step=None) if above_threshold and v.minimum_surface_level is None: above_threshold = False if above_threshold: # Keep only points where density is above lowest displayed threshold threshold = v.minimum_surface_level from chimerax.map import high_indices points_int = high_indices(m, threshold) from numpy import single as floatc points = points_int.astype(floatc) weights = m[points_int[:, 2], points_int[:, 1], points_int[:, 0]] else: from numpy import single as floatc, ravel, count_nonzero, nonzero, take from chimerax.map_data import grid_indices points = grid_indices(m.shape[::-1], floatc) # i,j,k indices weights = ravel(m).astype(floatc) if not include_zeros: if count_nonzero(weights) < len(weights): nz = nonzero(weights)[0] points = take(points, nz, axis=0) weights = take(weights, nz, axis=0) xyz_to_ijk_tf.inverse().transform_points(points, in_place=True) return points, weights
def _rotation_changed(self): v = self._map if v is None: return data = v.data if data.rotation == ((1,0,0),(0,1,0),(0,0,1)): axis, angle = (0,0,1), 0 else: from chimerax.geometry import Place axis, angle = Place(axes = data.rotation).rotation_axis_and_angle() rax = self._rotation_axis.value from .volume_viewer import vector_value naxis = vector_value(rax, axis) if naxis is None: self._status('Invalid rotation axis. Must be 3 numbers separated by spaces.') return if tuple(naxis) == (0,0,0): self._status('Rotation axis must be non-zero') return nangle = self._rotation_angle.value if tuple(naxis) != tuple(axis) or nangle != angle: from chimerax.geometry import rotation r = rotation(naxis, nangle) data.set_rotation(r.matrix[:,:3]) # Have to change xyz origin for index origin to remain the same. self._origin_changed() self._redraw_regions(data) self._status('Set rotation axis %s, angle %.3g' % (rax, nangle))
def read_autopack_results(path): j = read_json(path) recipe_path = j['recipe']['setupfile'] pieces = {} for comp_name, cres in j['compartments'].items(): for interior_or_surface, comp_ingr in cres.items(): for ingr_name, ingr_places in comp_ingr['ingredients'].items(): for translation, rotation44 in ingr_places['results']: t0,t1,t2 = translation r00,r01,r02 = rotation44[0][:3] r10,r11,r12 = rotation44[1][:3] r20,r21,r22 = rotation44[2][:3] tf = ((r00,r01,r02,t0), (r10,r11,r12,t1), (r20,r21,r22,t2)) from chimerax.geometry import Place p = Place(tf) ingr_id = (comp_name, interior_or_surface, ingr_name) if ingr_id in pieces: pieces[ingr_id].append(p) else: pieces[ingr_id] = [p] return recipe_path, pieces
def helix_segment(self, base_index, count, spacing, rise): ''' Lay out a single turn helix on x axis. Radius is calculated to fit the specified number of nucleotides. ''' # Choose radius so one helix turn has desired length. # Helix arc length = s, radius = r, rise = h: s**2 = r**2 + (h/(2*pi))**2 length = spacing * count from math import sqrt, pi radius = sqrt((length / (2 * pi))**2 - (rise / (2 * pi))**2) # Compute screw motion to advance along helix angle = 2 * pi / count angle_deg = angle * 180 / pi from chimerax.geometry import translation, rotation, vector_rotation, Place step = translation((rise / count, 0, 0)) * rotation( (1, 0, 0), angle_deg, center=(0, radius, 0)) # Compute first nucleotide so P-P lies on helix. orient = vector_rotation((1, 0, 0), step * (0, 0, 0)) # Place nucleotides on helix. place = {} p = Place() for i in range(count): place[base_index + i] = p * orient p = step * p return Segment(place, rise, pad=0)
def extrusion_transforms(path, tangents, yaxis=None): from chimerax.geometry import identity, vector_rotation, translation tflist = [] if yaxis is None: # Make xy planes for coordinate frames at each path point not rotate # from one point to next. tf = identity() n0 = (0, 0, 1) for p1, n1 in zip(path, tangents): tf = vector_rotation(n0, n1) * tf tflist.append(translation(p1) * tf) n0 = n1 else: # Make y-axis of coordinate frames at each point align with yaxis. from chimerax.geometry import normalize_vector, cross_product, Place for p, t in zip(path, tangents): za = t xa = normalize_vector(cross_product(yaxis, za)) ya = cross_product(za, xa) tf = Place( ((xa[0], ya[0], za[0], p[0]), (xa[1], ya[1], za[1], p[1]), (xa[2], ya[2], za[2], p[2]))) tflist.append(tf) return tflist
def mmcif_assemblies(model): table_names = ('pdbx_struct_assembly', 'pdbx_struct_assembly_gen', 'pdbx_struct_oper_list') from chimerax import mmcif assem, assem_gen, oper = mmcif.get_mmcif_tables_from_metadata( model, table_names) if not assem or not assem_gen or not oper: return [] name = assem.mapping('id', 'details') ids = list(name.keys()) ids.sort() cops = assem_gen.fields(('assembly_id', 'oper_expression', 'asym_id_list')) chain_ops = {} for id, op_expr, cids in cops: chain_ops.setdefault(id, []).append((cids.split(','), op_expr)) ops = {} mat = oper.fields( ('id', 'matrix[1][1]', 'matrix[1][2]', 'matrix[1][3]', 'vector[1]', 'matrix[2][1]', 'matrix[2][2]', 'matrix[2][3]', 'vector[2]', 'matrix[3][1]', 'matrix[3][2]', 'matrix[3][3]', 'vector[3]')) from chimerax.geometry import Place for id, m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34 in mat: ops[id] = Place(matrix=((m11, m12, m13, m14), (m21, m22, m23, m24), (m31, m32, m33, m34))) alist = [Assembly(id, name[id], chain_ops[id], ops, True) for id in ids] return alist
def perspective_view_all(bounds, position, field_of_view, window_size=None, pad=0): ''' Return the camera position that shows the specified bounds. Camera has perspective projection. ''' from math import radians, sin, cos fov2 = radians(field_of_view) / 2 s, c = sin(fov2), cos(fov2) face_normals = [ position.transform_vector(v) for v in ((c / s, 0, 1), (-c / s, 0, 1)) ] # frustum side normals if window_size is not None: aspect = window_size[1] / window_size[0] from math import tan, atan fov2y = atan(aspect * tan(fov2)) sy, cy = sin(fov2y), cos(fov2y) face_normals.extend([ position.transform_vector(v) for v in ((0, cy / sy, 1), (0, -cy / sy, 1)) ]) # frustum top/bottom normals center = bounds.center() bc = bounds.box_corners() - center from chimerax.geometry import inner_product, Place d = max(inner_product(n, c) for c in bc for n in face_normals) d *= 1 / max(0.01, 1 - pad) view_direction = -position.z_axis() camera_center = center - d * view_direction va_position = Place(axes=position.axes(), origin=camera_center) return va_position
def surfaces_from_nodes(nodes, color, place, instances, session): from collada.scene import GeometryNode, Node from chimerax.geometry import Place from chimerax.core.models import Surface splist = [] for n in nodes: if isinstance(n, GeometryNode): materials = dict((m.symbol, m.target) for m in n.materials) g = n.geometry colors = g.sourceById if g.id in instances: add_geometry_instance(g.primitives, instances[g.id], place, color, materials) else: instances[g.id] = spieces = geometry_node_surfaces( g.primitives, place, color, materials, colors, session) splist.extend(spieces) elif isinstance(n, Node): pl = place * Place(n.matrix[:3, :]) spieces = surfaces_from_nodes(n.children, color, pl, instances, session) name = n.xmlnode.get('name') if len(spieces) > 1: m = Surface(name, session) m.add(spieces) splist.append(m) elif len(spieces) == 1: s = spieces[0] s.name = name splist.append(s) return splist
def read_collada_surfaces(session, path, name=None, color=(200, 200, 200, 255), **kw): '''Open a collada file.''' from collada import Collada c = Collada(path) if name is None: from os.path import basename name = basename(path) from chimerax.geometry import Place splist = surfaces_from_nodes(c.scene.nodes, color, Place(), {}, session) if len(splist) > 1: from chimerax.core.models import Model s = Model(name, session) s.add(splist) elif len(splist) == 1: s = splist[0] s.name = name else: from chimerax.core.errors import UserError raise UserError('Collada file has no TriangleSets: %s' % name) set_instance_positions_and_colors(s.all_drawings()) ai = c.assetInfo if ai: s.collada_unit_name = ai.unitname s.collada_contributors = ai.contributors return [s], ('Opened collada file %s' % name)
def icosahedron_geometry(orientation='2n5'): a23, a25, a35 = icosahedron_angles() a = 2 * a25 # Angle spanned by edge from center # 5-fold symmetry axis along z from math import cos, sin, pi c5 = cos(2 * pi / 5) s5 = sin(2 * pi / 5) from chimerax.geometry import Place tf5 = Place(((c5, -s5, 0, 0), (s5, c5, 0, 0), (0, 0, 1, 0))) # 2-fold symmetry axis along x tf2 = Place(((1, 0, 0, 0), (0, -1, 0, 0), (0, 0, -1, 0))) p0 = (0, 0, 1) p50 = (0, sin(a), cos(a)) p51 = tf5 * p50 p52 = tf5 * p51 p53 = tf5 * p52 p54 = tf5 * p53 vertices = [p0, p50, p51, p52, p53, p54] vertices.extend([tf2 * q for q in vertices]) if orientation != '2n5': tf = coordinate_system_transform('2n5', orientation) vertices = [tf * p for p in vertices] # # Vertex numbering # # Top 1 Bottom # 2 5 9 10 # 0 6 # 3 4 8 11 # 7 # 20 triangles composing icosahedron. # triangles = ((0, 1, 2), (0, 2, 3), (0, 3, 4), (0, 4, 5), (0, 5, 1), (6, 7, 8), (6, 8, 9), (6, 9, 10), (6, 10, 11), (6, 11, 7), (1, 9, 2), (2, 9, 8), (2, 8, 3), (3, 8, 7), (3, 7, 4), (4, 7, 11), (4, 11, 5), (5, 11, 10), (5, 10, 1), (1, 10, 9)) from numpy import array, float32, uint32 va = array(vertices, float32) ta = array(triangles, uint32) return va, ta
def singleton_segments(base_index, count, spacing, placement=None): '''Create one nucleotide Segments with the same orientation and width.''' if placement is None: from chimerax.geometry import Place placement = Place() return [ Segment({base_index + i: placement}, width=spacing, pad=0) for i in range(count) ]
def _cube_map_face_views(): from chimerax.geometry import Place views = [Place(matrix=m) for m in (((0,0,-1,0),(0,-1,0,0),(-1,0,0,0)), ((0,0,1,0),(0,-1,0,0),(1,0,0,0)), ((1,0,0,0),(0,0,-1,0),(0,1,0,0)), ((1,0,0,0),(0,0,1,0),(0,-1,0,0)), ((1,0,0,0),(0,-1,0,0),(0,0,-1,0)), ((-1,0,0,0),(0,-1,0,0),(0,0,1,0)))] return views
def principle_axes_box(varray, tarray): from chimerax import surface weights = surface.vertex_areas(varray, tarray) from chimerax.std_commands.measure_inertia import moments_of_inertia axes, d2e, center = moments_of_inertia([(varray, weights)]) from chimerax.geometry import Place, point_bounds axes_points = Place(axes=axes).inverse().transform_points(varray) bounds = point_bounds(axes_points) return axes, bounds
def make_closest_placement_identity(tflist, center): d = tflist.array()[:, :, 3] - center d2 = (d * d).sum(axis=1) i = d2.argmin() tfinv = tflist[i].inverse() rtflist = [tf * tfinv for tf in tflist] from chimerax.geometry import Place, Places rtflist[i] = Place() return Places(rtflist)
def ellipsoid_geometry(center, axes, axis_lengths, num_triangles = 1000): from chimerax.surface import sphere_geometry varray, narray, tarray = sphere_geometry(num_triangles) narray = narray.copy() # Is same as varray for sphere. from chimerax.geometry import Place, scale, normalize_vectors ptf = Place(axes = axes, origin = center) * scale(axis_lengths) ptf.transform_points(varray, in_place = True) ntf = Place(axes = axes) * scale([1/l for l in axis_lengths]) ntf.transform_vectors(narray, in_place = True) normalize_vectors(narray) return varray, narray, tarray
def euler_rotation(euler_angles): from math import radians, cos, sin a, b, g = [radians(a) for a in euler_angles] R11, R12, R13 = cos(g) * cos(b) * cos(a) - sin(g) * sin(a), -sin(g) * cos( b) * cos(a) - cos(g) * sin(a), sin(b) * cos(a) R21, R22, R23 = cos(g) * cos(b) * sin(a) + sin(g) * cos(a), -sin(g) * cos( b) * sin(a) + cos(g) * cos(a), sin(b) * sin(a) R31, R32, R33 = -cos(g) * sin(b), sin(g) * sin(b), cos(b) from chimerax.geometry import Place rot = Place(axes=((R11, R21, R31), (R12, R22, R32), (R13, R23, R33))) return rot
def show_residue_fit(session, residues, map, range = 2, last_pos = None, motion_frames = 20): '''Set camera to show first residue in list and display map in zone around given residues.''' res = residues[0] ratoms = res.atoms anames = tuple(ratoms.names) try: i = [anames.index(aname) for aname in ('N', 'CA', 'C')] except ValueError: return None # Missing backbone atom xyz = ratoms.filter(i).scene_coords # Align backbone to template backbone coords from chimerax.geometry import align_points, Place from numpy import array txyz = array([[ 12.83300018, 6.83900023, 6.73799992], [ 12.80800056, 7.87400055, 5.70799971], [ 11.91800022, 9.06700039, 5.9920001 ]]) p, rms = align_points(txyz, xyz) # Set camera view relative to template. c = session.main_view.camera cp = c.position if last_pos is None: tc = Place(((-0.46696,0.38225,-0.79739,-3.9125), (0.81905,-0.15294,-0.55296,-4.3407), (-0.33332,-0.91132,-0.24166,-1.4889))) if c.name == 'orthographic': c.field_width = 12 # Set orthographic field of view, Angstroms else: # Maintain same relative camera position to backbone. tc = last_pos.inverse() * cp # Smooth interpolation np = p*tc if motion_frames > 1: def interpolate_camera(session, f, cp=cp, np=np, center=np.inverse()*xyz[1], frames=motion_frames): c = session.main_view.camera p = np if f+1 == frames else cp.interpolate(np, center, frac = (f+1)/frames) c.position = p from chimerax.core.commands import motion motion.CallForNFrames(interpolate_camera, motion_frames, session) else: c.position = np from numpy import concatenate zone_points = concatenate([r.atoms.scene_coords for r in residues]) from chimerax.surface import zone for s in map.surfaces: zone.surface_zone(s, zone_points, range, auto_update = True) for r in residues: r.atoms.displays = True return p
def _update_position(self, *_): v = self._session.main_view if v.center_of_rotation_method == 'front center': # Don't recompute front center rotation point, expensive, distracting. center = tuple(v._center_of_rotation) else: center = tuple(v.center_of_rotation) if center != self._center: self._center = center from chimerax.geometry import Place self.position = Place(origin=center)
def polygon_coordinate_frame(polygon): p = polygon c = p.center() za = p.normal() xa = p.vertices[0].coord - c from chimerax.geometry import normalize_vector, cross_product, Place ya = normalize_vector(cross_product(za, xa)) xa = normalize_vector(cross_product(ya, za)) tf = [(xa[a], ya[a], za[a], c[a]) for a in (0, 1, 2)] return Place(tf)
def parse_symmetry(session, group, center=None, axis=None, molecule=None): # Handle products of symmetry groups. groups = group.split('*') from chimerax.geometry import Places ops = Places() for g in groups: ops = ops * group_symmetries(session, g, molecule) # Apply center and axis transformation. if center is not None or axis is not None: from chimerax.geometry import Place, vector_rotation, translation tf = Place() if center is not None and tuple(center) != (0, 0, 0): tf = translation([-c for c in center]) if axis is not None and tuple(axis) != (0, 0, 1): tf = vector_rotation(axis, (0, 0, 1)) * tf if not tf.is_identity(): ops = ops.transform_coordinates(tf) return ops
def edge_coordinate_frame(edge): x0, x1 = edge_alignment_points(edge) p = edge.polygon c = p.center() c01 = 0.5 * (x0 + x1) from chimerax.geometry import cross_product, normalize_vector, Place xa = normalize_vector(x1 - x0) za = normalize_vector(cross_product(xa, c01 - c)) ya = cross_product(za, xa) tf = Place(((xa[0], ya[0], za[0], c01[0]), (xa[1], ya[1], za[1], c01[1]), (xa[2], ya[2], za[2], c01[2]))) return tf
def scene_and_node_models(scenes, nodes, file_name, session): smodels = [] for si, s in enumerate(scenes): if 'name' in s: sname = s['name'] elif len(scenes) == 1: sname = file_name else: sname = '%s scene %d' % (file_name, si + 1) sm = gltfModel(sname, session) smodels.append(sm) if 'nodes' not in s: raise glTFError('glTF scene %d has no nodes' % si) sm.gltf_nodes = s['nodes'] # Make model for each node nmodels = [] for ni, node in enumerate(nodes): if 'name' in node: nname = node['name'] else: nname = '%d' % (ni + 1) nm = gltfModel(nname, session) if 'mesh' in node: nm.gltf_mesh = node['mesh'] if 'children' in node: nm.gltf_child_nodes = node['children'] if 'matrix' in node: m = node['matrix'] from chimerax.geometry import Place nm.position = Place( ((m[0], m[4], m[8], m[12]), (m[1], m[5], m[9], m[13]), (m[2], m[6], m[10], m[14]))) if 'scale' in node or 'translation' in node or 'rotation' in node: session.logger.warning( 'glTF node %d has unsupported rotation, scale or translation, ignoring it' % ni) nmodels.append(nm) # Add node models to scenes. for sm in smodels: sm.add([nmodels[ni] for ni in sm.gltf_nodes]) # Add child nodes to parent nodes. for nm in nmodels: if hasattr(nm, 'gltf_child_nodes'): nm.add([nmodels[ni] for ni in nm.gltf_child_nodes]) return smodels, nmodels
def map_overlap_and_correlation(map1, map2, above_threshold, xform=None, include_zeros=False): p, w1 = map_points_and_weights(map1, above_threshold, include_zeros=include_zeros) if xform is None: from chimerax.geometry import Place xform = Place() w2 = map2.interpolated_values(p, xform, subregion=None, step=None) return overlap_and_correlation(w1, w2)
def _show_surface(session, varray, tarray, color, mesh, center, rotation, qrotation, coordinate_system, slab, model_id, shape_name, edge_mask = None, sharp_slab = False): if center is not None or rotation is not None or qrotation is not None: from chimerax.geometry import Place, translation tf = Place() if rotation is not None: tf = rotation * tf if qrotation is not None: tf = qrotation * tf if center is not None: from chimerax.core.commands import Center if isinstance(center, Center): center = center.scene_coordinates() tf = translation(center) * tf varray = tf.transform_points(varray) from chimerax.surface import calculate_vertex_normals narray = calculate_vertex_normals(varray, tarray) if slab is not None: from chimerax.mask.depthmask import slab_surface varray, narray, tarray = slab_surface(varray, tarray, narray, slab, sharp_edges = sharp_slab) s = _surface_model(session, model_id, shape_name, coordinate_system) s.set_geometry(varray, narray, tarray) if color is not None: s.color = color if mesh: s.display_style = s.Mesh if edge_mask is not None: s.edge_mask = edge_mask # Hide spokes of hexagons. _add_surface(s) return s
def __init__(self, drawing, *, window_size=(256, 256), trigger_set=None): self.triggers = trigger_set self.drawing = drawing self.window_size = window_size # pixels self._render = None self._opengl_initialized = False # Lights and material properties from .opengl import Lighting, Material, Silhouette self._lighting = Lighting() self._material = Material() self._silhouette = Silhouette() # Red, green, blue, opacity, 0-1 range. self._background_rgba = (0, 0, 0, 0) self._highlight_color = (0, 1, 0, 1) self._highlight_width = 1 # pixels # Create camera from .camera import MonoCamera self._camera = MonoCamera() from chimerax.geometry import Place self._view_matrix = Place() # Temporary used during rendering # Clip planes from .clipping import ClipPlanes self.clip_planes = ClipPlanes() self._near_far_pad = 0.01 # Extra near-far clip plane spacing. self._min_near_fraction = 0.001 # Minimum near distance, fraction of depth # Graphics overlays, used for example for crossfade self._overlays = [] # Center of rotation from numpy import array, float32 self._center_of_rotation = array((0, 0, 0), float32) self._update_center_of_rotation = False self._center_of_rotation_method = 'front center' # Redrawing self.frame_number = 1 self.redraw_needed = True self._time_graphics = False self.update_lighting = True self._drawing_manager = dm = _RedrawNeeded() if trigger_set: self.drawing.set_redraw_callback(dm)
def view_initial(session, models=None): ''' Set models to initial positions. Parameters ---------- models : Models Set model positions to no rotation, no shift. ''' if models is None: models = session.models.list() from chimerax.geometry import Place for m in models: m.position = Place()
def edge_join_transform(link, rlink, vertex_degree): f0 = edge_coordinate_frame(rlink) f1 = edge_coordinate_frame(link) from chimerax.geometry import Place r = Place(((-1, 0, 0, 0), (0, -1, 0, 0), (0, 0, 1, 0))) # Rotate 180 degrees about z. if vertex_degree is None: ea = 0 else: a = 180 - 360.0 / link.polygon.n ra = 180 - 360.0 / rlink.polygon.n ea = vertex_degree * (a + ra) - 720 if ea != 0: from math import sin, cos, pi a = -pi / 6 if ea < 0 else pi / 6 rx = Place( ((1, 0, 0, 0), (0, cos(a), sin(a), 0), (0, -sin(a), cos(a), 0))) r = rx * r tf = f0 * r * f1.inverse() return tf
def pdb_mtrix_matrices(pdb_headers, add_identity=True, given=False): h = pdb_headers have_matrix = ('MTRIX1' in h and 'MTRIX2' in h and 'MTRIX3' in h) if not have_matrix: if add_identity: from chimerax.geometry import identity return [identity()] else: return [] row1_list = h['MTRIX1'] row2_list = h['MTRIX2'] row3_list = h['MTRIX3'] if len(row1_list) != len(row2_list) or len(row2_list) != len(row3_list): if add_identity: from chimerax.geometry import identity return [identity()] else: return [] row_triples = zip(row1_list, row2_list, row3_list) mlist = [] from chimerax.geometry import Place for row_triple in row_triples: matrix = [] for line in row_triple: try: mrow = [ float(f) for f in (line[10:20], line[20:30], line[30:40], line[45:55]) ] except ValueError: break mgiven = (len(line) >= 60 and line[59] == '1') if (mgiven and given) or (not mgiven and not given): matrix.append(mrow) if len(matrix) == 3: mlist.append(Place(matrix)) if add_identity: if len([m for m in mlist if m.is_identity()]) == 0: # Often there is no MTRIX identity entry from chimerax.geometry import identity mlist.append(identity()) return Places(mlist)
def set_map_state(s, volume, notify = True): v = volume v.rendering_options = rendering_options_from_state(s['rendering_options']) if not 'display' in s: # Fix old session files s['display'] = s['displayed'] if 'displayed' in s else True for attr in basic_map_attributes: if attr in s: setattr(v, attr, s[attr]) for old_attr, new_attr in renamed_attributes: if old_attr in s: setattr(v, new_attr, s[old_attr]) if 'representation' in s: # Handle old session files that had representation attribute. style = s['representation'] if style == 'solid': style = 'image' v.set_display_style(style) from chimerax.geometry import Place v.position = Place(s['place']) v.new_region(*s['region'], adjust_step = False) if 'region_list' in s: region_list_from_state(s['region_list'], v.region_list) if s['version'] == 1: for lev, color in zip(s['surface_levels'], s['surface_colors']): v.add_surface(lev, rgba = color) # dsize = [a*b for a,b in zip(v.data.step, v.data.size)] # v.transparency_depth /= min(dsize) if notify: v.call_change_callbacks(('display style changed', 'region changed', 'thresholds changed', 'displayed', 'colors changed', 'rendering options changed', 'coordinates changed'))