fusion-tools 2.4.2__tar.gz → 2.4.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/PKG-INFO +1 -1
  2. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/setup.py +1 -1
  3. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/components/maps.py +2 -0
  4. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/components/segmentation.py +232 -84
  5. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/utils/shapes.py +41 -6
  6. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools.egg-info/PKG-INFO +1 -1
  7. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/LICENSE +0 -0
  8. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/README.md +0 -0
  9. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/pyproject.toml +0 -0
  10. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/setup.cfg +0 -0
  11. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/__init__.py +0 -0
  12. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/components/__init__.py +0 -0
  13. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/components/tools.py +0 -0
  14. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/dataset.py +0 -0
  15. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/feature_extraction.py +0 -0
  16. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/handler.py +0 -0
  17. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/tileserver.py +0 -0
  18. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/utils/__init__.py +0 -0
  19. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/utils/omics.py +0 -0
  20. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/utils/stats.py +0 -0
  21. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/visualization/__init__.py +0 -0
  22. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/visualization/components.py +0 -0
  23. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools/visualization/vis_utils.py +0 -0
  24. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools.egg-info/SOURCES.txt +0 -0
  25. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools.egg-info/dependency_links.txt +0 -0
  26. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools.egg-info/requires.txt +0 -0
  27. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/src/fusion_tools.egg-info/top_level.txt +0 -0
  28. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_classification_model.py +0 -0
  29. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_custom_component.py +0 -0
  30. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_dataset_classes.py +0 -0
  31. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_extract_nested_prop.py +0 -0
  32. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_extract_recursive.py +0 -0
  33. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_feature_annotation.py +0 -0
  34. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_feature_extraction.py +0 -0
  35. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_generate_property_dict.py +0 -0
  36. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_get_gene_info.py +0 -0
  37. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_hra_viewer.py +0 -0
  38. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_image_overlay.py +0 -0
  39. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_model_train.py +0 -0
  40. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_multiframe_rgb.py +0 -0
  41. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_multiframeslidemap.py +0 -0
  42. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_process.py +0 -0
  43. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_property_plotter.py +0 -0
  44. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_read_anndata.py +0 -0
  45. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_remote_slide_annotations.py +0 -0
  46. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_remote_tileserver.py +0 -0
  47. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_segmentation.py +0 -0
  48. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_slide_annotations.py +0 -0
  49. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_spatial_aggregation.py +0 -0
  50. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_tile_server.py +0 -0
  51. {fusion_tools-2.4.2 → fusion_tools-2.4.3}/tests/test_visualization_component.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fusion-tools
3
- Version: 2.4.2
3
+ Version: 2.4.3
4
4
  Summary: Modular visualization and analysis dashboard creation for high-resolution microscopy images
5
5
  Home-page: https://github.com/spborder/fusion-tools
6
6
  Author: Sam Border
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="fusion-tools",
8
- version="2.4.2",
8
+ version="2.4.3",
9
9
  author="Sam Border",
10
10
  author_email="sam.border2256@gmail.com",
11
11
  description="Modular visualization and analysis dashboard creation for high-resolution microscopy images",
@@ -366,6 +366,7 @@ class SlideMap(MapComponent):
366
366
  var overlayVal = Number.Nan;
367
367
  if (overlayProp) {
368
368
  if (overlayProp.name) {
369
+ //TODO: Update this for different types of nested props (--+ = list, --# = external reference object)
369
370
  var overlaySubProps = overlayProp.name.split(" --> ");
370
371
  var prop_dict = feature.properties;
371
372
  for (let i = 0; i < overlaySubProps.length; i++) {
@@ -423,6 +424,7 @@ class SlideMap(MapComponent):
423
424
  // Iterating through filterVals list
424
425
  var filter = filterVals[i];
425
426
  if (filter.name) {
427
+ //TODO: Update this for different types of nested props (--+ = list, --# = external reference object)
426
428
  var filterSubProps = filter.name.split(" --> ");
427
429
  var prop_dict = feature.properties;
428
430
  for (let j = 0; j < filterSubProps.length; j++) {
@@ -1240,6 +1240,10 @@ class BulkLabels(Tool):
1240
1240
  n_clicks = 0,
1241
1241
  id = {'type': 'bulk-labels-apply-labels','index': 0},
1242
1242
  disabled = True
1243
+ ),
1244
+ dbc.Tooltip(
1245
+ 'Saves current label to labeling session but does not add label to structure-level data',
1246
+ target = {'type': 'bulk-labels-apply-labels','index': 0}
1243
1247
  )
1244
1248
  ])
1245
1249
  ]),
@@ -1252,6 +1256,10 @@ class BulkLabels(Tool):
1252
1256
  id = {'type': 'bulk-labels-download-labels','index': 0},
1253
1257
  disabled = True
1254
1258
  ),
1259
+ dbc.Tooltip(
1260
+ 'Downloads both individual labels as well as associated labeling metadata containing rationales for labels.',
1261
+ target = {'type': 'bulk-labels-download-labels','index': 0}
1262
+ ),
1255
1263
  dcc.Download(
1256
1264
  id = {'type':'bulk-labels-download-data','index': 0}
1257
1265
  ),
@@ -1259,6 +1267,29 @@ class BulkLabels(Tool):
1259
1267
  id = {'type': 'bulk-labels-download-metadata','index': 0}
1260
1268
  )
1261
1269
  ])
1270
+ ],style = {'marginTop': '10px'}),
1271
+ dbc.Row([
1272
+ dbc.Col([
1273
+ dbc.Button(
1274
+ 'Add Labels to Structures',
1275
+ id = {'type': 'bulk-labels-add-labels-to-structures','index': 0},
1276
+ className = 'd-grid col-12 mx-auto',
1277
+ n_clicks = 0,
1278
+ disabled = True
1279
+ ),
1280
+ dbc.Tooltip(
1281
+ 'Adds current labels to structures for use in plotting, filtering, etc.',
1282
+ target = {'type': 'bulk-labels-add-labels-to-structures','index': 0}
1283
+ )
1284
+ ])
1285
+ ],style = {'marginTop': '10px'}),
1286
+ dbc.Row([
1287
+ dbc.Col([
1288
+ html.Div(
1289
+ id = {'type': 'bulk-labels-label-stats-div','index': 0},
1290
+ children = []
1291
+ )
1292
+ ])
1262
1293
  ],style = {'marginTop': '10px'})
1263
1294
  ])
1264
1295
  ])
@@ -1307,7 +1338,8 @@ class BulkLabels(Tool):
1307
1338
  Output({'type': 'bulk-labels-label-text','index': ALL},'disabled'),
1308
1339
  Output({'type': 'bulk-labels-label-rationale','index': ALL},'disabled'),
1309
1340
  Output({'type': 'bulk-labels-apply-labels','index': ALL},'disabled'),
1310
- Output({'type': 'bulk-labels-download-labels','index': ALL},'disabled')
1341
+ Output({'type': 'bulk-labels-download-labels','index': ALL},'disabled'),
1342
+ Output({'type': 'bulk-labels-add-labels-to-structures','index': ALL},'disabled')
1311
1343
  ],
1312
1344
  [
1313
1345
  State({'type': 'bulk-labels-include-structures','index': ALL},'value'),
@@ -1381,6 +1413,8 @@ class BulkLabels(Tool):
1381
1413
  ],
1382
1414
  [
1383
1415
  Output({'type': 'bulk-labels-labels-store','index': ALL},'data'),
1416
+ Output({'type': 'bulk-labels-apply-labels','index': ALL},'color'),
1417
+ Output({'type': 'bulk-labels-label-stats-div','index': ALL},'children')
1384
1418
  ],
1385
1419
  [
1386
1420
  State({'type': 'bulk-labels-markers','index': ALL},'data'),
@@ -1413,7 +1447,9 @@ class BulkLabels(Tool):
1413
1447
  Output({'type': 'bulk-labels-label-source','index': ALL},'children'),
1414
1448
  Output({'type': 'bulk-labels-apply-labels','index': ALL},'disabled'),
1415
1449
  Output({'type': 'bulk-labels-include-structures','index':ALL},'options'),
1416
- Output({'type': 'bulk-labels-spatial-query-structures','index':ALL},'options')
1450
+ Output({'type': 'bulk-labels-spatial-query-structures','index':ALL},'options'),
1451
+ Output({'type': 'bulk-labels-apply-labels','index': ALL},'color'),
1452
+ Output({'type': 'map-marker-div','index': ALL},'children')
1417
1453
  ],
1418
1454
  [
1419
1455
  State({'type': 'feature-overlay','index':ALL},'name')
@@ -1457,6 +1493,33 @@ class BulkLabels(Tool):
1457
1493
  ]
1458
1494
  )(self.update_structure_count)
1459
1495
 
1496
+ # Adding current labels to structure data
1497
+ self.blueprint.callback(
1498
+ [
1499
+ Input({'type': 'bulk-labels-add-labels-to-structures','index': ALL},'n_clicks')
1500
+ ],
1501
+ [
1502
+ Output({'type': 'map-annotations-store','index': ALL},'data')
1503
+ ],
1504
+ [
1505
+ State({'type': 'map-annotations-store','index': ALL},'data'),
1506
+ State({'type': 'bulk-labels-labels-store','index': ALL},'data')
1507
+ ]
1508
+ )(self.add_labels_to_structures)
1509
+
1510
+ # Editing current labels by editing the label table
1511
+ self.blueprint.callback(
1512
+ [
1513
+ Input({'type': 'bulk-labels-label-table','index': ALL},'data'),
1514
+ ],
1515
+ [
1516
+ Output({'type': 'bulk-labels-labels-store','index': ALL},'data')
1517
+ ],
1518
+ [
1519
+ State({'type': 'bulk-labels-labels-store','index': ALL},'data'),
1520
+ ]
1521
+ )(self.update_label_table)
1522
+
1460
1523
 
1461
1524
  def update_slide(self, new_annotations: list):
1462
1525
 
@@ -1511,6 +1574,8 @@ class BulkLabels(Tool):
1511
1574
 
1512
1575
  if query_type is None:
1513
1576
  raise exceptions.PreventUpdate
1577
+ if not type(query_type)==str:
1578
+ raise exceptions.PreventUpdate
1514
1579
 
1515
1580
  query_types = {
1516
1581
  'within': '''
@@ -1770,7 +1835,7 @@ class BulkLabels(Tool):
1770
1835
  (f['bbox'][1]+f['bbox'][3])/2
1771
1836
  ]
1772
1837
  },
1773
- 'properties': f_data
1838
+ 'properties': f_data | {'_id': f['properties']['_id']}
1774
1839
  }
1775
1840
  for f,f_data in zip(filtered_geojson['features'],filtered_ref_list)
1776
1841
  ]
@@ -1792,15 +1857,17 @@ class BulkLabels(Tool):
1792
1857
  labels_rationale_disabled = False
1793
1858
  labels_apply_button_disabled = False
1794
1859
  labels_download_button_disabled = False
1860
+ labels_add_to_structures_button_disabled = False
1795
1861
  else:
1796
1862
  labels_type_disabled = True
1797
1863
  labels_text_disabled = True
1798
1864
  labels_rationale_disabled = True
1799
1865
  labels_apply_button_disabled = True
1800
1866
  labels_download_button_disabled = True
1867
+ labels_add_to_structures_button_disabled = True
1801
1868
 
1802
1869
 
1803
- return [new_structures_div], [new_markers_div], [new_labels_source], [labels_type_disabled], [labels_text_disabled], [labels_rationale_disabled], [labels_apply_button_disabled], [labels_download_button_disabled]
1870
+ return [new_structures_div], [new_markers_div], [new_labels_source], [labels_type_disabled], [labels_text_disabled], [labels_rationale_disabled], [labels_apply_button_disabled], [labels_download_button_disabled], [labels_add_to_structures_button_disabled]
1804
1871
 
1805
1872
  def update_label_properties(self, add_click:list, remove_click:list, property_info:list):
1806
1873
  """Adding/removing property filter dropdown
@@ -1913,36 +1980,13 @@ class BulkLabels(Tool):
1913
1980
  )
1914
1981
  )
1915
1982
 
1916
- return values_selector
1917
-
1918
- def parse_marker_div(self, parent_marker_div,slide_information):
1919
- """Parse div containing markers, extracting position and data from each
1920
-
1921
- :param parent_marker_div: Div containing all current Markers
1922
- :type parent_marker_div: list
1923
- :return: Coordinates for all markers and associated data (source structure and index)
1924
- :rtype: tuple
1925
- """
1926
-
1927
- marker_coords = []
1928
- marker_data = []
1929
- for p in parent_marker_div:
1930
- coords = p['props']['position']
1931
- data = json.loads(p['props']['children'][-1]['props']['children'])
1932
-
1933
- marker_coords.append(
1934
- [coords[1]/slide_information['x_scale'],coords[0]/slide_information['y_scale']]
1935
- )
1936
- marker_data.append(data)
1937
-
1938
-
1939
- return marker_coords, marker_data
1983
+ return values_selector
1940
1984
 
1941
- def search_labels(self, marker_centroids, current_labels, label_type, label_text, label_method):
1985
+ def search_labels(self, marker_features, current_labels, label_type, label_text, label_method):
1942
1986
  """Checking current labels and adding the new label based on label_method (inclusive, exclusive, or unlabeled).
1943
1987
 
1944
- :param marker_centroids: Centroids for all new markers
1945
- :type marker_centroids: list
1988
+ :param marker_features: GeoJSON Features for new markers
1989
+ :type marker_features: list
1946
1990
  :param current_labels: List of all current labels (dicts with keys: centroid, labels)
1947
1991
  :type current_labels: list
1948
1992
  :param label_type: String corresponding to the type for the new label
@@ -1959,8 +2003,10 @@ class BulkLabels(Tool):
1959
2003
  else:
1960
2004
  current_labels['labels'] = []
1961
2005
  current_centroids = []
1962
- for m_idx, m in enumerate(marker_centroids):
1963
- if m in current_centroids:
2006
+ for m_idx, m in enumerate(marker_features):
2007
+ m_centroid = m['geometry']['coordinates']
2008
+ m_id = m['properties']['_id']
2009
+ if m_centroid in current_centroids:
1964
2010
  cent_idx = current_centroids.index(m)
1965
2011
  if label_method=='in':
1966
2012
  # Adding inclusive label to current set of labels
@@ -1996,7 +2042,8 @@ class BulkLabels(Tool):
1996
2042
  else:
1997
2043
  current_labels['labels'].append(
1998
2044
  {
1999
- 'centroid': m,
2045
+ 'centroid': m_centroid,
2046
+ '_id': m_id,
2000
2047
  'labels': [{
2001
2048
  'type': label_type,
2002
2049
  'value': label_text
@@ -2049,57 +2096,14 @@ class BulkLabels(Tool):
2049
2096
 
2050
2097
  scaled_markers = geojson.utils.map_geometries(lambda g: geojson.utils.map_tuples(lambda c: (c[0]/slide_information['x_scale'],c[1]/slide_information['y_scale']),g),current_markers)
2051
2098
 
2052
- marker_positions = [i['geometry']['coordinates'] for i in scaled_markers['features']]
2053
- labeled_items = self.search_labels(marker_positions, current_data, label_type, label_text, label_method)
2054
-
2055
- #TODO: Applying property to structures
2056
- """
2057
- ann_names_order = [i['properties']['name'] for i in current_annotations]
2058
- apply_label_dict = {'type': label_type, 'value': label_text}
2059
- for m_d in marker_data:
2060
- ann_idx = ann_names_order.index(m_d['name'])
2061
- if label_method=='in':
2062
- if 'fusion_labels' in current_annotations[ann_idx]['features'][m_d['feature_index']]['properties']:
2063
- current_annotations[ann_idx]['features'][m_d['feature_index']]['properties']['fusion_labels'].append(
2064
- apply_label_dict
2065
- )
2066
- else:
2067
- current_annotations[ann_idx]['features'][m_d['feature_index']]['properties']['fusion_labels'] = [
2068
- apply_label_dict
2069
- ]
2070
- elif label_method=='over':
2071
- if 'fusion_labels' in current_annotations[ann_idx]['features'][m_d['feature_index']]['properties']:
2072
- if any([label_type==i['type'] for i in current_annotations[ann_idx]['features'][m_d['feature_index']]['properties']['fusion_labels']]):
2073
- current_annotations[ann_idx]['features'][m_d['feature_index']]['properties']['fusion_labels'] = [
2074
- i if not i['type']==label_type else apply_label_dict
2075
- for i in current_annotations[ann_idx]['features'][m_d['feature_index']]['properties']['fusion_labels']
2076
- ]
2077
- else:
2078
- current_annotations[ann_idx]['features'][m_d['feature_index']]['properties']['fusion_labels'].append(
2079
- apply_label_dict
2080
- )
2081
- else:
2082
- current_annotations[ann_idx]['features'][m_d['feature_index']]['properties']['fusion_labels'] = [
2083
- apply_label_dict
2084
- ]
2085
- elif label_method=='un':
2086
- if 'fusion_labels' in current_annotations[ann_idx]['features'][m_d['feature_index']]['properties']['fusion_labels']:
2087
- if not any([label_type==i['type'] for i in current_annotations[ann_idx]['features'][m_d['feature_index']]['properties']['fusion_labels']]):
2088
- current_annotations[ann_idx]['features'][m_d['feature_index']]['properties']['fusion_labels'].append(
2089
- apply_label_dict
2090
- )
2091
- else:
2092
- current_annotations[ann_idx]['features'][m_d['feature_index']]['properties']['fusion_labels'] = [
2093
- apply_label_dict
2094
- ]
2095
-
2096
- """
2099
+ labeled_items = self.search_labels(scaled_markers['features'], current_data, label_type, label_text, label_method)
2097
2100
 
2098
- if 'labels' in current_data:
2101
+ if 'labels' in current_data and 'labels_metadata' in current_data:
2099
2102
  current_data['labels'] = labeled_items['labels']
2100
2103
  current_data['labels_metadata'].append(
2101
2104
  {
2102
2105
  'type': label_type,
2106
+ 'label': label_text,
2103
2107
  'rationale': label_rationale,
2104
2108
  'source': label_source
2105
2109
  }
@@ -2109,15 +2113,56 @@ class BulkLabels(Tool):
2109
2113
  current_data['labels_metadata'] = [
2110
2114
  {
2111
2115
  'type': label_type,
2116
+ 'label': label_text,
2112
2117
  'rationale': label_rationale,
2113
2118
  'source': label_source
2114
2119
  }
2115
2120
  ]
2116
2121
 
2117
2122
  new_data = json.dumps(current_data)
2118
- #new_annotations = json.dumps(current_annotations)
2119
2123
 
2120
- return [new_data]
2124
+ # Creating a table for count of unique labels:
2125
+ labels = []
2126
+ for l in labeled_items['labels']:
2127
+ for m in l['labels']:
2128
+ # Accounting for multiple values for the same "type"
2129
+ l_dict = {
2130
+ 'Label Type': m['type'],
2131
+ 'Value': m['value']
2132
+ }
2133
+
2134
+ labels.append(l_dict)
2135
+
2136
+ label_count_df = pd.DataFrame.from_records(labels).groupby(by='Label Type')
2137
+ label_counts = label_count_df.value_counts(['Value']).to_frame()
2138
+ label_counts.reset_index(inplace=True)
2139
+ label_counts.columns = ['Label Type','Label Value','Count']
2140
+
2141
+ label_count_table = dash_table.DataTable(
2142
+ columns = [{'name':i,'id':i,'selectable':True} for i in label_counts],
2143
+ data = label_counts.to_dict('records'),
2144
+ editable=True,
2145
+ row_deletable=True,
2146
+ sort_mode='multi',
2147
+ sort_action = 'native',
2148
+ page_current=0,
2149
+ page_size=5,
2150
+ style_cell = {
2151
+ 'overflow':'hidden',
2152
+ 'textOverflow':'ellipsis',
2153
+ 'maxWidth':0
2154
+ },
2155
+ tooltip_data = [
2156
+ {
2157
+ column: {'value':str(value),'type':'markdown'}
2158
+ for column, value in row.items()
2159
+ } for row in label_counts.to_dict('records')
2160
+ ],
2161
+ tooltip_duration = None,
2162
+ id = {'type': f'{self.component_prefix}-bulk-labels-label-table','index':0}
2163
+ )
2164
+
2165
+ return [new_data], ['success'], [label_count_table]
2121
2166
 
2122
2167
  def refresh_labels(self, refresh_click, structure_options):
2123
2168
  """Clear current label components and start over
@@ -2143,6 +2188,8 @@ class BulkLabels(Tool):
2143
2188
  rationale_disable = True
2144
2189
  label_source = '`Label Source`'
2145
2190
  apply_disable = True
2191
+ apply_style = 'primary'
2192
+ markers = []
2146
2193
 
2147
2194
  include_options = [{'label': i, 'value': i} for i in structure_options]
2148
2195
  if len(ctx.outputs_list[13])>0:
@@ -2150,7 +2197,7 @@ class BulkLabels(Tool):
2150
2197
  else:
2151
2198
  spatial_queries_structures = []
2152
2199
 
2153
- return [include_structures], [spatial_queries], [add_property], [current_structures],[label_type],[type_disable],[label_text], [text_disable], [label_rationale], [rationale_disable], [label_source], [apply_disable], [include_options], spatial_queries_structures
2200
+ return [include_structures], [spatial_queries], [add_property], [current_structures],[label_type],[type_disable],[label_text], [text_disable], [label_rationale], [rationale_disable], [label_source], [apply_disable], [include_options], spatial_queries_structures, [apply_style], [markers]
2154
2201
  else:
2155
2202
  raise exceptions.PreventUpdate
2156
2203
 
@@ -2184,4 +2231,105 @@ class BulkLabels(Tool):
2184
2231
  )
2185
2232
  ]
2186
2233
 
2187
- return new_structure_count_children
2234
+ return new_structure_count_children
2235
+
2236
+ def add_labels_to_structures(self, button_click, current_annotations, current_labels):
2237
+
2238
+ if not any([i['value'] for i in ctx.triggered]):
2239
+ raise exceptions.PreventUpdate
2240
+
2241
+ current_annotations = json.loads(get_pattern_matching_value(current_annotations))
2242
+ current_labels = json.loads(get_pattern_matching_value(current_labels))
2243
+
2244
+ label_ids = [i['_id'] for i in current_labels['labels']]
2245
+ for c in current_annotations:
2246
+ feature_ids = [i['properties']['_id'] for i in c['features']]
2247
+ id_intersect = list(set(label_ids) & set(feature_ids))
2248
+ if len(id_intersect)>0:
2249
+ for id in id_intersect:
2250
+ feature_label = current_labels['labels'][label_ids.index(id)]
2251
+ print(feature_label)
2252
+ c['features'][feature_ids.index(id)]['properties'] = c['features'][feature_ids.index(id)]['properties'] | current_labels['labels'][label_ids.index(id)]
2253
+
2254
+ updated_annotations = json.dumps(current_annotations)
2255
+
2256
+ return [updated_annotations]
2257
+
2258
+ def update_label_table(self, table_data, current_labels):
2259
+
2260
+ table_data = get_pattern_matching_value(table_data)
2261
+ current_labels = json.loads(get_pattern_matching_value(current_labels))
2262
+ if not table_data is None:
2263
+ included_label_types = sorted(list(set([i['Label Type'] for i in table_data])))
2264
+ current_label_types = sorted(list(set([i['type'] for i in current_labels['labels_metadata']])))
2265
+
2266
+ included_label_values = []
2267
+ current_label_values = []
2268
+ for t in included_label_types:
2269
+ included_label_values.extend(sorted([i['Label Value'] for i in table_data if i['Label Type']==t]))
2270
+ current_label_values.extend(sorted([i['label'] for i in current_labels['labels_metadata'] if i['type']==t]))
2271
+
2272
+ # Removing not-included label from labels and label metadata
2273
+ if not included_label_types==current_label_types or not included_label_values==current_label_values:
2274
+ # If the len of included_label_types does not equal the len of current_label_types then there has been a type deletion
2275
+ deletion = False
2276
+ replation = False
2277
+ if not len(included_label_types)==len(current_label_types):
2278
+ # This would be a deletion of one label type/value
2279
+ #print(f'type deletion: {included_label_types}, {current_label_types}')
2280
+ deletion = True
2281
+ replation = False
2282
+ else:
2283
+ # If the len of the types are the same length then check if they contain the same values:
2284
+ if not included_label_types==current_label_types:
2285
+ # This is a type replation
2286
+ #print(f'type replation: {included_label_types}, {current_label_types}')
2287
+ deletion = False
2288
+ replation = {'type': [i for i in included_label_types if not i in current_label_types][0]}
2289
+
2290
+ else:
2291
+ # In this case there is most likely a value replation
2292
+ if not included_label_values==current_label_values:
2293
+ #print(f'value replation: {included_label_values}, {current_label_values}')
2294
+ deletion = False
2295
+ replation = {'value': [i for i in included_label_values if not i in current_label_values][0]}
2296
+
2297
+ for l_idx,l in enumerate(current_labels['labels']):
2298
+ for l_m_idx,l_m in enumerate(l['labels']):
2299
+ if not l_m['type'] in included_label_types:
2300
+ if deletion:
2301
+ del current_labels['labels'][l_idx]['labels'][l_m_idx]
2302
+ elif replation:
2303
+ current_labels['labels'][l_idx]['labels'][l_m_idx]['type'] = replation['type']
2304
+ else:
2305
+ if not l_m['value'] in included_label_values:
2306
+ if deletion:
2307
+ del current_labels['labels'][l_idx]['labels'][l_m_idx]
2308
+ elif replation:
2309
+ current_labels['labels'][l_idx]['labels'][l_m_idx]['value'] = replation['value']
2310
+
2311
+ if len(l['labels'])==0:
2312
+ del current_labels['labels'][l_idx]
2313
+
2314
+ if deletion:
2315
+ current_labels['labels_metadata'] = [i for i in current_labels['labels_metadata'] if i['label'] in included_label_values and i['type'] in included_label_types]
2316
+ elif replation:
2317
+ if 'type' in replation:
2318
+ current_labels['labels_metadata'] = [i if i['type'] in included_label_types else i | replation for i in current_labels['labels_metadata']]
2319
+ elif 'value' in replation:
2320
+ current_labels['labels_metadata'] = [i if i['label'] in included_label_values else i | {'label': replation['value']} for i in current_labels['labels_metadata']]
2321
+
2322
+ updated_labels = json.dumps(current_labels)
2323
+ else:
2324
+ raise exceptions.PreventUpdate
2325
+ else:
2326
+ updated_labels = json.dumps({})
2327
+
2328
+ return [updated_labels]
2329
+
2330
+
2331
+
2332
+
2333
+
2334
+
2335
+
@@ -740,6 +740,7 @@ def spatially_aggregate(child_geo:dict, parent_geos: list, separate: bool = True
740
740
  'Sum': nested_sum
741
741
  }
742
742
 
743
+ #TODO: Update this for different types of nested props (--+ = list, --# = external reference object)
743
744
  sub_keys = reversed(p.split(' --> '))
744
745
  for p_idx,part in enumerate(sub_keys):
745
746
  nested_dict = {part: nested_dict}
@@ -756,6 +757,7 @@ def spatially_aggregate(child_geo:dict, parent_geos: list, separate: bool = True
756
757
  props = numeric_df.columns.tolist()
757
758
  for p in props:
758
759
  mean_dict = mean_props[p]
760
+ #TODO: Update this for different types of nested props (--+ = list, --# = external reference object)
759
761
  for part in reversed(p.split(' --> ')):
760
762
  mean_dict = {part: mean_dict}
761
763
 
@@ -801,6 +803,7 @@ def spatially_aggregate(child_geo:dict, parent_geos: list, separate: bool = True
801
803
  'Sum': nested_sum
802
804
  }
803
805
 
806
+ #TODO: Update this for different types of nested props (--+ = list, --# = external reference object)
804
807
  sub_keys = reversed(p.split(' --> '))
805
808
  for part in sub_keys:
806
809
  nested_dict = {part: nested_dict}
@@ -819,6 +822,7 @@ def spatially_aggregate(child_geo:dict, parent_geos: list, separate: bool = True
819
822
  props = numeric_df.columns.tolist()
820
823
  for p in props:
821
824
  mean_dict = mean_props[p]
825
+ #TODO: Update this for different types of nested props (--+ = list, --# = external reference object)
822
826
  for part in reversed(p.split(' --> ')):
823
827
  mean_dict = {part: mean_dict}
824
828
 
@@ -860,18 +864,44 @@ def extract_nested_prop(main_prop_dict: dict, depth: int, path: tuple = (), valu
860
864
  values_list.append({
861
865
  ' --> '.join(list(path+(keys,))): values
862
866
  })
867
+
868
+ elif type(values)==list:
869
+ values_list.extend(extract_listed_prop,path+(keys,),[])
870
+
863
871
  else:
864
- # Skipping properties that are still nested
872
+ # Skipping properties that are still nested
865
873
  continue
866
874
  else:
867
875
  if type(values)==dict:
868
876
  extract_nested_prop(values, depth-1, path+ (keys,), values_list)
869
- else:
877
+ elif type(values)==list:
878
+ values_list.extend(extract_listed_prop,path+(keys,),[])
879
+ elif type(values) in [int,float,str]:
870
880
  # Only adding properties to the list one time
871
881
  if not any([' --> '.join(list(path+(keys,))) in list(i.keys()) for i in values_list]):
872
882
  values_list.append({
873
883
  ' --> '.join(list(path+(keys,))): values
874
884
  })
885
+ else:
886
+ # Skipping properties of some mysterious fifth type
887
+ continue
888
+
889
+ return values_list
890
+
891
+ def extract_listed_prop(main_list: list, path: tuple = (), values_list: list = []):
892
+
893
+ if len(main_list)>0:
894
+ for item_idx,list_item in enumerate(main_list):
895
+ item_key = f'Value {item_idx}'
896
+ if type(list_item) in [int,float,str]:
897
+ values_list.append({
898
+ ' --+ '.join(list(path+(item_key,))): list_item
899
+ })
900
+ elif type(list_item)==dict:
901
+ nested_n = find_nested_levels({item_key:list_item})
902
+ values_list.extend(extract_nested_prop({item_key: list_item},nested_n,path+(item_key,),[]))
903
+ elif type(list_item)==list:
904
+ extract_listed_prop(list_item,path+(item_key,),[])
875
905
 
876
906
  return values_list
877
907
 
@@ -916,8 +946,13 @@ def extract_geojson_properties(geo_list: list, reference_object: Union[str,None]
916
946
  for p in f_props:
917
947
  # Checking for sub-properties
918
948
  sub_props = []
919
- if type(f['properties'][p])==dict:
920
- nested_value = extract_nested_prop({p: f['properties'][p]}, nested_depth, (), [])
949
+ if type(f['properties'][p]) in [dict,list]:
950
+ # Pulling out nested properties (either dictionaries or lists or lists of dictionaries or dictionaries of lists, etc.)
951
+ if type(f['properties'][p]) ==dict:
952
+ nested_value = extract_nested_prop({p: f['properties'][p]}, nested_depth, (), [])
953
+ elif type(f['properties'][p])==list:
954
+ nested_value = extract_listed_prop(f['properties'][p],(p,),[])
955
+
921
956
  if len(nested_value)>0:
922
957
  for n in nested_value:
923
958
  n_key = list(n.keys())[0]
@@ -950,7 +985,7 @@ def extract_geojson_properties(geo_list: list, reference_object: Union[str,None]
950
985
  property_info[n_key]['unique'].append(n_value)
951
986
  property_info[n_key]['distinct'] +=1
952
987
 
953
- else:
988
+ elif type(f['properties'][p]) in [int,float,str]:
954
989
  f_sup_val = f['properties'][p]
955
990
 
956
991
  if not p in property_info:
@@ -986,7 +1021,6 @@ def extract_geojson_properties(geo_list: list, reference_object: Union[str,None]
986
1021
 
987
1022
  #TODO: After loading an experiment, reference the file here for additional properties
988
1023
 
989
-
990
1024
 
991
1025
  geojson_properties = sorted(geojson_properties)
992
1026
 
@@ -1090,6 +1124,7 @@ def process_filters_queries(filter_list:list, spatial_list:list, structures:list
1090
1124
 
1091
1125
  for f in filter_list:
1092
1126
  if include:
1127
+ #TODO: Update this for different types of nested props (--+ = list, --# = external reference object)
1093
1128
  filter_name_parts = f['name'].split(' --> ')
1094
1129
 
1095
1130
  include = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fusion-tools
3
- Version: 2.4.2
3
+ Version: 2.4.3
4
4
  Summary: Modular visualization and analysis dashboard creation for high-resolution microscopy images
5
5
  Home-page: https://github.com/spborder/fusion-tools
6
6
  Author: Sam Border
File without changes
File without changes
File without changes