Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
26167b2
Source layers can now have an optional background defined in a map's …
dbrnz Jan 20, 2025
aae15c5
Implement a `feature-groups` section in `properties.json` to group fe…
dbrnz Jan 21, 2025
aaf8bf5
Add Python type annotations and tidy code.
dbrnz Jan 21, 2025
fb1686b
Add missing import.
dbrnz Jan 21, 2025
9b4a20a
Has constants to specify colours of Shapes with errors.
dbrnz Jan 21, 2025
3dcd84e
Put any superscript before any subscript when generating LaTeX for a …
dbrnz Jan 21, 2025
adc5832
Add a new layer to the flatmap **after** its source has been processe…
dbrnz Jan 21, 2025
4441ac5
Better detect arrows at ends of thin polygon shapes representing conn…
dbrnz Jan 21, 2025
b0a3833
Improve detection of connection lines drawn as long thin polygons.
dbrnz Jan 21, 2025
669813b
Note if a possible connection line has a non-arrow shape along it.
dbrnz Jan 21, 2025
429518a
Add comment.
dbrnz Jan 21, 2025
7e21b0f
Handle SVG paths with multiple segments.
dbrnz Jan 21, 2025
ec5a70c
Tidy up how margins are added to functional map layers.
dbrnz Jan 23, 2025
0d3a640
A detailed source's manifest description can have an `alignment` list…
dbrnz Jan 24, 2025
fceae36
Add Python type annotations.
dbrnz Jan 24, 2025
bb63d41
Cody tidying.
dbrnz Jan 24, 2025
0cdaeed
`--max-raster-zoom` should only be for base map rasterisation, not de…
dbrnz Jan 24, 2025
64aab04
Pass `detail-layer` property to viewer (with `image-layer` options).
dbrnz Jan 24, 2025
3715059
Merge branch 'main' into functional-modelling
dbrnz Jan 29, 2025
4ba5a43
Align features in a layer when adding a new layer to the map, not at …
dbrnz Jan 29, 2025
700fefe
A colour as specified in an SVG style might have leading blanks...
dbrnz Jan 30, 2025
8198e08
Tidy up shape classification code.
dbrnz Feb 9, 2025
8c4e265
Improve parent/child detection in shape classification, esp. for text…
dbrnz Feb 9, 2025
92d053f
Recognise `dashed` property when classifying functional connections.
dbrnz Feb 9, 2025
d22c414
MultiPolygons aren't considered as possible connections when classify…
dbrnz Feb 9, 2025
9900bb6
Recognise `Annotation` and `Container` shapes during classification.
dbrnz Feb 9, 2025
ac3684e
Set `source` and `target` properties of connections as part of classi…
dbrnz Feb 9, 2025
2100470
Code tidying.
dbrnz Feb 9, 2025
7a57c69
Add missing import.
dbrnz Feb 9, 2025
a0be2c4
Rework shape text finder to handle sub/superscripts within sub/supers…
dbrnz Feb 9, 2025
58b040a
Use updated shape classification API.
dbrnz Feb 9, 2025
2306072
Add `background` as a markup property -- background shapes don't beco…
dbrnz Feb 9, 2025
b1d9038
Generate CellDL from a functional model when `--export-bondgraphs`.
dbrnz Feb 9, 2025
9751aea
Shape classification: Only check for parent/child relationships betwe…
dbrnz Feb 9, 2025
27ab675
Shape classification: exclude unclassifiable shapes unless authoring,…
dbrnz Feb 9, 2025
f4ff254
Add a `number` property to a shape, in order as they are created, so …
dbrnz Feb 10, 2025
e5b7530
Shape classification: a shape with a 'Multi' shape boundary can't be …
dbrnz Feb 10, 2025
9c97a62
Don't show some properties in the string representation of a `Shape`.
dbrnz Feb 10, 2025
575bc4f
Shape classification: reduce the baseline offset used for detecting t…
dbrnz Feb 10, 2025
0c1f654
Merge branch 'main' into functional-modelling
dbrnz Feb 12, 2025
8da8f6d
Merge branch 'main' into functional-modelling
dbrnz Feb 23, 2025
eeb318e
Merge branch 'main' into functional-modelling
dbrnz Feb 23, 2025
d9ef047
Rename shape type definitions' file as it applies to more than Powerp…
dbrnz Mar 2, 2025
fb6f600
Use a shape's `layer/name` as its ID on FC maps if a specific ID has …
dbrnz Mar 2, 2025
241fe80
Don't use math mode (for LaTex) if a FC shape's name has no super- no…
dbrnz Mar 2, 2025
04a4010
Set a connection's stroke colour when generating CellDL (this will ev…
dbrnz Mar 2, 2025
787c7d7
CellDL: use better names for variables.
dbrnz Mar 4, 2025
54e10ca
CellDL: keep ANNOTATION shapes in their own group when exporting.
dbrnz Mar 4, 2025
fbb760c
Rework feature lookup by name for functional maps so that we keep the…
dbrnz Mar 5, 2025
62fd080
Don't look for super- and sub-scripts in a text block with just a sin…
dbrnz Mar 5, 2025
79bd44a
Minor tidy.
dbrnz Mar 5, 2025
9ccdd0c
TitleCase method names that are class methods.
dbrnz Mar 5, 2025
d3e11e0
GeoJSON: make sure `properties` aren't JSON encoded.
dbrnz Mar 10, 2025
cd91ccd
CellDL: copy across `.background` elements from the source SVG; put e…
dbrnz Mar 10, 2025
13dc3f1
Extend `PropertyMixin` with `append_property(key, value)`.
dbrnz Mar 10, 2025
73b10c7
Remove historical deployment note.
dbrnz Mar 12, 2025
ff1616c
Update the bounds of a details' layer when it is aligned with feature…
dbrnz Mar 12, 2025
34ff132
Export the `extent` (LngLat coordinates) of a MapLayer in its metadata.
dbrnz Mar 12, 2025
4b10ec4
Add `zoom points` to functional maps when a feature has a detailed vi…
dbrnz Mar 12, 2025
6a19d12
For functional maps, export the association between a feature and its…
dbrnz Mar 12, 2025
3bb4adc
A feature can have a `hyperlink` (a single URL) as well as `hyperlink…
dbrnz Mar 12, 2025
25e8d91
Export the `target` property (of a Connection on a functional map), a…
dbrnz Mar 12, 2025
1365a7e
Tidying -- remove unused exported properties.
dbrnz Mar 12, 2025
7175cc7
Make sure exported `associated-details` is an array.
dbrnz Mar 12, 2025
91d6d07
Include the map's style in its index -- this makes it easily availabl…
dbrnz Mar 20, 2025
08a08e0
SVG: improve how missing fonts are handled.
dbrnz Mar 21, 2025
b90a3fd
feat: instead of having the map's `style` in its index, allow a map's…
dbrnz Mar 22, 2025
ba3d3f0
fix: at least 90% of a text shape's area has to be inside its parent …
dbrnz Mar 22, 2025
4e18177
Merge branch 'main' into functional-modelling
dbrnz Mar 31, 2025
48ded77
Merge branch 'main' into functional-modelling
dbrnz Apr 5, 2025
f3078fd
Merge branch 'main' into functional-modelling
dbrnz Apr 10, 2025
2e96d33
Update Python packages.
dbrnz Apr 10, 2025
30c4990
Merge branch 'main' into functional-modelling
dbrnz Apr 11, 2025
4c50cc7
Merge branch 'main' into functional-modelling
dbrnz Apr 15, 2025
7bdb3aa
Always set the map's `style` (as well as `map-kinds`) in its `index.j…
dbrnz Apr 21, 2025
4acafa0
shapes: text can be slightly outside of its parent container.
dbrnz Apr 21, 2025
19ccd5e
Add map's created timestamp to its `index.json`.
dbrnz Apr 21, 2025
c207ee2
A zoom point marker must have the same model as its underlying feature.
dbrnz Apr 22, 2025
8fdb408
functional: increase ZOOM_OFFSET_FROM_BASE, for showing rasterised im…
dbrnz Apr 22, 2025
26d62a6
options: default to have `--no-path-layout` enabled and use new `--pa…
dbrnz Apr 22, 2025
dd61007
Include the parent layer and the zoom point feature id with a detail …
dbrnz Apr 24, 2025
6f833e0
Merge branch 'main' into functional-modelling
dbrnz May 20, 2025
5d1b209
functional: `zoom-point` and related layer metadata depends on the la…
dbrnz May 20, 2025
45d467c
Merge branch 'main' into functional-modelling
dbrnz May 27, 2025
c71b6bd
Merge branch 'main' into functional-modelling
dbrnz Jun 6, 2025
6688bfb
geometry: add `is_identity()`, `scale()`, and `translate()` to our Tr…
dbrnz Jun 6, 2025
04e51a6
Code tidying.
dbrnz Jun 6, 2025
eb4a421
Update Python project file to remove Poetry specific configuration.
dbrnz Jun 10, 2025
732ad80
Merge branch 'main' into functional-modelling
dbrnz Jun 11, 2025
21b91a8
Work on bondgraph RDF export.
dbrnz Jun 12, 2025
4de91ae
Merge branch 'main' into functional-modelling
dbrnz Jul 28, 2025
a7f8cf6
Merge branch 'main' into functional-modelling
dbrnz Aug 7, 2025
20585f4
Merge branch 'main' into functional-modelling
dbrnz Aug 14, 2025
96b76a2
celldl: define `ANNOTATION` and `PORT` shape types and CellDL classes.
dbrnz Aug 15, 2025
d53b3af
svg: the default `id` of a shape is its ID attribute, which then migh…
dbrnz Aug 15, 2025
60f61f1
Minor tidying.
dbrnz Aug 15, 2025
aa1a4ae
bondgraphs: use a separate source file to specify namespaces.
dbrnz Aug 15, 2025
1855003
bondgraphs: change format of names to `BASE_SUBSCRIPT_SUPERSCRIPT`.
dbrnz Aug 15, 2025
c643a33
bondgraphs: use the map's `id` as the id of the generated bondgraph m…
dbrnz Aug 15, 2025
94e1964
bondgraphs: return Unicode bytes when serialising RDF.
dbrnz Aug 15, 2025
5d2e055
bondgraphs: remove unused code.
dbrnz Aug 15, 2025
45cd440
bondgraphs: improve checks and construction of RDF graph.
dbrnz Aug 15, 2025
e1fb589
bondgraphs: an element's name could in fact be a comma separated list…
dbrnz Aug 15, 2025
8216aaf
bondgraphs: reaction nodes have a dark blue border.
dbrnz Aug 15, 2025
a00630f
bondgraphs: `q` nodes are `bgf:ZeroStorage` nodes.
dbrnz Aug 15, 2025
f57d4ff
celldl: use `\ce{}` MathJax markup for known chemical symbols.
dbrnz Aug 15, 2025
e5495d4
celldl: take the size of the containing shape when locating text with…
dbrnz Aug 16, 2025
4dd808e
celldl: allow for a default value when finding items by colour using …
dbrnz Aug 16, 2025
88d8b6f
shapes: expose `width` and `height` of a shape.
dbrnz Aug 16, 2025
297f6af
Remove unused code.
dbrnz Aug 16, 2025
f9343a9
shapes: `shape.id` will in fact be set...
dbrnz Aug 16, 2025
1e69fa7
shapes: don't try to make a connection if we can only find end.
dbrnz Aug 16, 2025
8d28093
shapes: distinguish COMPONENTS as PORTS as shapes that a connection m…
dbrnz Aug 16, 2025
c2b8a64
Resolve linting errors.
dbrnz Aug 16, 2025
86242ef
shapes: add missing import.
dbrnz Aug 16, 2025
3f0fb8e
functional: we don't want annotation nor container shapes to be activ…
dbrnz Aug 16, 2025
23ffc08
shapes: remove debugging.
dbrnz Aug 16, 2025
c6b83f9
shapes: code tidying.
dbrnz Aug 16, 2025
46836bc
bondgraphs: we use `lark` to parse text.
dbrnz Aug 16, 2025
868424c
shapes: the baseline of a block of text is that of the leftmost chara…
dbrnz Aug 16, 2025
4a49e89
shapes: improve detect of parallel pairs of lines that might be part …
dbrnz Aug 16, 2025
add978a
Remove debugging.
dbrnz Aug 16, 2025
9c0a035
Fix lint errors.
dbrnz Aug 16, 2025
70da77b
shapes: make sure two parallel lines actually overlap before calculat…
dbrnz Aug 16, 2025
b11c3f8
logging: rework logging to support the use of a `QueueHandler` to pas…
dbrnz Aug 17, 2025
8b8747c
Minor tweaks.
dbrnz Aug 17, 2025
cb69779
Update `mapknowledge` to version 1.3.3 and `flatmapknowledge` to vers…
dbrnz Aug 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions DEPLOY.rst

This file was deleted.

6 changes: 4 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ Command line help
[--authoring] [--debug]
[--only-networks] [--save-drawml] [--save-geojson] [--tippecanoe]
[--initial-zoom N] [--max-zoom N]
[--export-features EXPORT_FILE] [--export-neurons EXPORT_FILE] [--export-svg EXPORT_FILE]
[--single-file {celldl,svg}]
[--export-bondgraphs] [--export-features EXPORT_FILE] [--export-neurons EXPORT_FILE]
[--export-svg EXPORT_FILE] [--single-file {celldl,svg}]
--output OUTPUT --source SOURCE

Generate a flatmap from its source manifest.
Expand Down Expand Up @@ -163,6 +163,8 @@ Command line help
--max-zoom N Maximum zoom level (defaults to 10)

Miscellaneous:
--export-bondgraphs
Export functional modelling components as CellDL bondgraphs
--export-features EXPORT_FILE
Export identifiers and anatomical terms of labelled features as JSON
--export-neurons EXPORT_FILE
Expand Down
3 changes: 3 additions & 0 deletions mapmaker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
MIN_ZOOM = 2 #: Default minimum zoom level for generated flatmaps
MAX_ZOOM = 10 #: Default maximum zoom level for generated flatmaps

# For showing rasterised image of detailed map on its base map before the details show
ZOOM_OFFSET_FROM_BASE = 2

#===============================================================================

from .maker import MapMaker
Expand Down
4 changes: 3 additions & 1 deletion mapmaker/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def arg_parser():

log_options = parser.add_argument_group('Logging')
log_options.add_argument('--log', dest='logFile', metavar='LOG_FILE',
help="Append messages to a log file")
help="Append messages to a log file as JSON")
log_options.add_argument('--silent', action='store_true',
help='Suppress all messages to screen')
log_options.add_argument('--verbose', action='store_true',
Expand Down Expand Up @@ -93,6 +93,8 @@ def arg_parser():
misc_options = parser.add_argument_group('Miscellaneous')
misc_options.add_argument('--commit', metavar='GIT_COMMIT',
help='The branch/tag/commit to use when the source is a Git repository')
misc_options.add_argument('--export-bondgraphs', dest='exportBondgraphs', action='store_true',
help='Export functional modelling components as CellDL bondgraphs')
misc_options.add_argument('--export-features', dest='exportFeatures', metavar='EXPORT_FILE',
help='Export identifiers and anatomical terms of labelled features as JSON')
misc_options.add_argument('--export-neurons', dest='exportNeurons', metavar='EXPORT_FILE',
Expand Down
2 changes: 1 addition & 1 deletion mapmaker/annotation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from mapmaker.knowledgebase.celldl import FC_CLASS
from mapmaker.knowledgebase.sckan import PATH_TYPE
from mapmaker.shapes import Shape
from mapmaker.sources.fc_powerpoint.components import is_connector
from mapmaker.shapes.types import is_connector
from mapmaker.utils import log

from .json_annotations import JsonAnnotations
Expand Down
86 changes: 67 additions & 19 deletions mapmaker/flatmap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#
#===============================================================================

from collections import OrderedDict
from collections import defaultdict, OrderedDict
from datetime import datetime, timezone
import os
from typing import Optional, TYPE_CHECKING
Expand All @@ -45,7 +45,7 @@
from .layers import FEATURES_TILE_LAYER, MapLayer

# Exports
from .manifest import Manifest, SourceManifest
from .manifest import Manifest, SourceBackground, SourceManifest

if TYPE_CHECKING:
from mapmaker.annotation import Annotator
Expand All @@ -63,9 +63,7 @@ def __init__(self, manifest: Manifest, maker: 'MapMaker', annotator: Optional['A
self.__id = maker.id
self.__uuid = maker.uuid
self.__map_dir = maker.map_dir
self.__map_kind = (MAP_KIND.FUNCTIONAL if manifest.kind == 'functional'
else MAP_KIND.CENTRELINE if manifest.kind == 'centreline'
else MAP_KIND.ANATOMICAL)
self.__map_kind = manifest.map_kind
self.__manifest = manifest
self.__local_id = manifest.id
self.__models = manifest.models
Expand Down Expand Up @@ -195,6 +193,13 @@ def initialise(self):
knowledge = get_knowledge(self.__models)
if 'label' in knowledge:
self.__metadata['describes'] = knowledge['label']
if self.map_kind == MAP_KIND.FUNCTIONAL:
self.__metadata['style'] = 'functional'
elif self.map_kind == MAP_KIND.CENTRELINE:
self.__metadata['style'] = 'centreline'
else:
self.__metadata['style'] = 'anatomical'
self.__metadata['map-kinds'] = self.__manifest.map_kinds

self.__entities = set()

Expand All @@ -211,6 +216,7 @@ def initialise(self):
self.__features_with_name: dict[str, Feature] = {}
self.__last_geojson_id = 0
self.__features_by_geojson_id: dict[int, Feature] = {}
self.__associated_layers: defaultdict[str, list[int]] = defaultdict(list)

# Used to find annotated features containing a region
self.__feature_search = None
Expand Down Expand Up @@ -272,11 +278,11 @@ def has_feature(self, feature_id: str) -> bool:

def get_feature(self, feature_id: str) -> Optional[Feature]:
#===========================================================
return self.__features_with_id.get(feature_id)

def get_feature_by_name(self, full_name: str) -> Optional[Feature]:
#==================================================================
return self.__features_with_name.get(full_name.replace(" ", "_"))
if self.map_kind == MAP_KIND.FUNCTIONAL:
return self.__features_with_name.get(feature_id.replace(" ", "_"),
self.__features_with_id.get(feature_id))
else:
return self.__features_with_id.get(feature_id)

def get_feature_by_geojson_id(self, geojson_id: int) -> Optional[Feature]:
#=========================================================================
Expand All @@ -285,17 +291,21 @@ def get_feature_by_geojson_id(self, geojson_id: int) -> Optional[Feature]:
def new_feature(self, layer_id: str, geometry, properties, is_group=False) -> Feature:
#=====================================================================================
self.__last_geojson_id += 1
properties['layer'] = layer_id
self.properties_store.update_properties(properties) # Update from JSON properties file
feature = Feature(self.__last_geojson_id, geometry, properties, is_group=is_group)
feature.set_property('layer', layer_id)
if (name := properties.get('name', properties.get('label', ''))) != '':
self.__features_with_name[f'{layer_id}/{name.replace(" ", "_")}'] = feature
self.__features_by_geojson_id[feature.geojson_id] = feature
if feature.id:
if feature.id in self.__features_with_id:
pass
else:
self.__features_with_id[feature.id] = feature
if self.map_kind == MAP_KIND.FUNCTIONAL:
if (name := properties.get('name', '')) != '':
self.__features_with_name[f'{layer_id}/{name.replace(" ", "_")}'] = feature
if (associated_layers := properties.get('associated-details')) is not None:
for layer in associated_layers:
self.__associated_layers[layer].append(feature.geojson_id)
return feature

def network_feature(self, feature: Feature) -> bool:
Expand Down Expand Up @@ -364,7 +374,7 @@ def add_source_layers(self, layer_number: int, source: 'MapSource'):
for layer in source.layers:
self.add_layer(layer)
if layer.exported:
layer.add_raster_layer(layer.id, source.extent, source)
layer.add_raster_layers(source.extent, source)
# The first layer is used as the base map
if layer_number == 0:
if source.kind == 'details':
Expand All @@ -377,6 +387,38 @@ def add_source_layers(self, layer_number: int, source: 'MapSource'):
and source.kind not in SOURCE_DETAIL_KINDS):
raise ValueError('Can only have a single base map')

"""
The ``feature`` has details that appear when zoomed.
"""
def add_details_layer(self, feature: Feature, details_layer: str, description: Optional[str]=None) -> Optional[int]:
#===================================================================================================================
if feature.layer is not None:
feature.set_property('details-layer', details_layer)
zoom_point = self.add_zoom_point(feature, description)
if zoom_point is not None:
zoom_point.set_property('details-layer', details_layer)
# Set the ``associated-details`` property for connections to features associated with the details layer
for geojson_id in self.__associated_layers.get(details_layer, []):
if (associated_feature := self.get_feature_by_geojson_id(geojson_id)) is not None:
for connection_id in associated_feature.get_property('connections', []):
if (connection := self.get_feature(connection_id)) is not None:
connection.append_property('associated-details', details_layer)
return zoom_point.geojson_id if zoom_point else None

def add_zoom_point(self, feature: Feature, description: Optional[str]=None) -> Optional[Feature]:
#================================================================================================
if feature.layer is not None:
zoom_point = self.new_feature(feature.properties['layer'], feature.geometry.centroid, {
'kind': 'zoom-point',
'tile-layer': feature.properties['tile-layer']
})
if description is not None:
zoom_point.set_property('label', description)
if (models := feature.models) is not None:
zoom_point.set_property('models', models)
feature.layer.add_feature(zoom_point)
return zoom_point

def layer_metadata(self):
#========================
metadata = []
Expand All @@ -390,7 +432,9 @@ def layer_metadata(self):
{ 'id': raster_layer.id,
'options': {
'max-zoom': raster_layer.max_zoom,
'min-zoom': raster_layer.min_zoom
'min-zoom': raster_layer.min_zoom,
'background': raster_layer.background_layer,
'detail-layer': layer.detail_layer
}
} for raster_layer in layer.raster_layers
]
Expand All @@ -399,6 +443,10 @@ def layer_metadata(self):
map_layer['min-zoom'] = layer.min_zoom
if layer.max_zoom is not None:
map_layer['max-zoom'] = layer.max_zoom
if layer.parent_layer is not None:
map_layer['extent'] = layer.extent
map_layer['parent-layer'] = layer.parent_layer
map_layer['zoom-point'] = layer.zoom_point_id
metadata.append(map_layer)
return metadata

Expand Down Expand Up @@ -482,11 +530,11 @@ def __add_detail_features(self, layer, detail_layer, lowres_features):
else: # nerve
feature.pop_property('maxzoom')

if hires_layer.source.raster_source is not None:
if len(hires_layer.source.raster_sources):
extent = transform.transform_extent(hires_layer.source.extent)
layer.add_raster_layer('{}_{}'.format(detail_layer.id, hires_layer.id),
extent, hires_layer.source, minzoom,
local_world_to_base=transform)
layer.add_raster_layers(extent, hires_layer.source,
id=f'{detail_layer.id}_{hires_layer.id}',
min_zoom=minzoom, local_world_to_base=transform)

# The detail layer gets a scaled copy of each high-resolution feature
for hires_feature in hires_layer.features:
Expand Down
Loading