well-log-toolkit 0.1.137__tar.gz → 0.1.139__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 (20) hide show
  1. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/PKG-INFO +14 -10
  2. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/README.md +13 -9
  3. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/pyproject.toml +1 -1
  4. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit/manager.py +41 -19
  5. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit/property.py +6 -0
  6. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit/visualization.py +38 -52
  7. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit.egg-info/PKG-INFO +14 -10
  8. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/setup.cfg +0 -0
  9. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit/__init__.py +0 -0
  10. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit/exceptions.py +0 -0
  11. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit/las_file.py +0 -0
  12. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit/operations.py +0 -0
  13. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit/regression.py +0 -0
  14. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit/statistics.py +0 -0
  15. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit/utils.py +0 -0
  16. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit/well.py +0 -0
  17. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit.egg-info/SOURCES.txt +0 -0
  18. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit.egg-info/dependency_links.txt +0 -0
  19. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit.egg-info/requires.txt +0 -0
  20. {well_log_toolkit-0.1.137 → well_log_toolkit-0.1.139}/well_log_toolkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: well-log-toolkit
3
- Version: 0.1.137
3
+ Version: 0.1.139
4
4
  Summary: Fast LAS file processing with lazy loading and filtering for well log analysis
5
5
  Author-email: Kristian dF Kollsgård <kkollsg@gmail.com>
6
6
  License: MIT
@@ -463,7 +463,10 @@ template.add_track(track_type="continuous", logs=[...], title="Resistivity")
463
463
  template.add_track(track_type="discrete", logs=[...], title="Facies")
464
464
  template.add_track(track_type="depth", width=0.3, title="Depth")
465
465
 
466
- # Save for reuse
466
+ # Add to project (saves with manager.save())
467
+ manager.add_template(template) # Uses template name "reservoir"
468
+
469
+ # Or save standalone file
467
470
  template.save("reservoir_template.json")
468
471
  ```
469
472
 
@@ -811,8 +814,8 @@ view.show()
811
814
 
812
815
  **Option 2: Store in manager (recommended for multi-well projects)**
813
816
  ```python
814
- # Store template in manager
815
- manager.set_template("reservoir", template)
817
+ # Store template in manager (uses template.name automatically)
818
+ manager.add_template(template)
816
819
 
817
820
  # Use by name in any well
818
821
  view = well.WellView(depth_range=[2800, 3000], template="reservoir")
@@ -859,8 +862,8 @@ template.remove_track(2)
859
862
  template.add_track(track_type="continuous", logs=[{"name": "RT"}])
860
863
 
861
864
  # Save changes
862
- manager.set_template("reservoir", template) # Update in manager
863
- template.save("updated_template.json") # Save to file
865
+ manager.add_template(template) # Update in manager (uses template.name)
866
+ template.save("updated_template.json") # Save to file
864
867
  ```
865
868
 
866
869
  ### Customization
@@ -1006,8 +1009,8 @@ template.add_track(track_type="depth", width=0.3, title="MD (m)")
1006
1009
  # Add formation tops spanning all tracks
1007
1010
  template.add_tops(property_name='Zone')
1008
1011
 
1009
- # Save and display
1010
- manager.set_template("comprehensive", template)
1012
+ # Add to project and display
1013
+ manager.add_template(template)
1011
1014
  view = well.WellView(depth_range=[2800, 3200], template="comprehensive")
1012
1015
  view.save("comprehensive_log.png", dpi=300)
1013
1016
  ```
@@ -1886,7 +1889,8 @@ from well_log_toolkit import WellDataManager, Well, Property, LasFile
1886
1889
  - `remove_well(name)` - Remove well
1887
1890
  - `save(directory)` - Save project
1888
1891
  - `load(directory)` - Load project
1889
- - `set_template(name, template)` - Store template
1892
+ - `add_template(template)` - Store template (uses template.name)
1893
+ - `set_template(name, template)` - Store template with custom name
1890
1894
  - `get_template(name)` - Retrieve template
1891
1895
  - `list_templates()` - List template names
1892
1896
  - `Crossplot(x, y, wells=None, shape="well", ...)` - Create multi-well crossplot
@@ -2053,7 +2057,7 @@ template.add_track(
2053
2057
  logs=[{"name": "GR", "x_range": [0, 150], "color": "green"}],
2054
2058
  title="Gamma Ray"
2055
2059
  )
2056
- manager.set_template("custom", template)
2060
+ manager.add_template(template) # Stored as "custom"
2057
2061
  view = well.WellView(template="custom")
2058
2062
  view.save("log.png", dpi=300)
2059
2063
  ```
@@ -425,7 +425,10 @@ template.add_track(track_type="continuous", logs=[...], title="Resistivity")
425
425
  template.add_track(track_type="discrete", logs=[...], title="Facies")
426
426
  template.add_track(track_type="depth", width=0.3, title="Depth")
427
427
 
428
- # Save for reuse
428
+ # Add to project (saves with manager.save())
429
+ manager.add_template(template) # Uses template name "reservoir"
430
+
431
+ # Or save standalone file
429
432
  template.save("reservoir_template.json")
430
433
  ```
431
434
 
@@ -773,8 +776,8 @@ view.show()
773
776
 
774
777
  **Option 2: Store in manager (recommended for multi-well projects)**
775
778
  ```python
776
- # Store template in manager
777
- manager.set_template("reservoir", template)
779
+ # Store template in manager (uses template.name automatically)
780
+ manager.add_template(template)
778
781
 
779
782
  # Use by name in any well
780
783
  view = well.WellView(depth_range=[2800, 3000], template="reservoir")
@@ -821,8 +824,8 @@ template.remove_track(2)
821
824
  template.add_track(track_type="continuous", logs=[{"name": "RT"}])
822
825
 
823
826
  # Save changes
824
- manager.set_template("reservoir", template) # Update in manager
825
- template.save("updated_template.json") # Save to file
827
+ manager.add_template(template) # Update in manager (uses template.name)
828
+ template.save("updated_template.json") # Save to file
826
829
  ```
827
830
 
828
831
  ### Customization
@@ -968,8 +971,8 @@ template.add_track(track_type="depth", width=0.3, title="MD (m)")
968
971
  # Add formation tops spanning all tracks
969
972
  template.add_tops(property_name='Zone')
970
973
 
971
- # Save and display
972
- manager.set_template("comprehensive", template)
974
+ # Add to project and display
975
+ manager.add_template(template)
973
976
  view = well.WellView(depth_range=[2800, 3200], template="comprehensive")
974
977
  view.save("comprehensive_log.png", dpi=300)
975
978
  ```
@@ -1848,7 +1851,8 @@ from well_log_toolkit import WellDataManager, Well, Property, LasFile
1848
1851
  - `remove_well(name)` - Remove well
1849
1852
  - `save(directory)` - Save project
1850
1853
  - `load(directory)` - Load project
1851
- - `set_template(name, template)` - Store template
1854
+ - `add_template(template)` - Store template (uses template.name)
1855
+ - `set_template(name, template)` - Store template with custom name
1852
1856
  - `get_template(name)` - Retrieve template
1853
1857
  - `list_templates()` - List template names
1854
1858
  - `Crossplot(x, y, wells=None, shape="well", ...)` - Create multi-well crossplot
@@ -2015,7 +2019,7 @@ template.add_track(
2015
2019
  logs=[{"name": "GR", "x_range": [0, 150], "color": "green"}],
2016
2020
  title="Gamma Ray"
2017
2021
  )
2018
- manager.set_template("custom", template)
2022
+ manager.add_template(template) # Stored as "custom"
2019
2023
  view = well.WellView(template="custom")
2020
2024
  view.save("log.png", dpi=300)
2021
2025
  ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "well-log-toolkit"
7
- version = "0.1.137"
7
+ version = "0.1.139"
8
8
  description = "Fast LAS file processing with lazy loading and filtering for well log analysis"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -384,11 +384,18 @@ class _ManagerPropertyProxy:
384
384
 
385
385
  @labels.setter
386
386
  def labels(self, value: dict):
387
- """Set labels for this property in all wells."""
387
+ """Set labels for this property in all wells.
388
+
389
+ Also sets property type to 'discrete' if not already set,
390
+ since labels are only meaningful for discrete properties.
391
+ """
388
392
  count = 0
389
393
  for well_name, well in self._manager._wells.items():
390
394
  try:
391
395
  prop = well.get_property(self._property_name)
396
+ # Auto-set type to discrete if labels are being set
397
+ if prop.type != 'discrete':
398
+ prop.type = 'discrete'
392
399
  prop.labels = value
393
400
  count += 1
394
401
  except (AttributeError, PropertyNotFoundError):
@@ -2481,37 +2488,52 @@ class WellDataManager:
2481
2488
  if well.name in self._name_mapping:
2482
2489
  del self._name_mapping[well.name]
2483
2490
 
2491
+ def add_template(self, template: 'Template') -> None:
2492
+ """
2493
+ Store a template using its built-in name.
2494
+
2495
+ Parameters
2496
+ ----------
2497
+ template : Template
2498
+ Template object (uses template.name as the key)
2499
+
2500
+ Examples
2501
+ --------
2502
+ >>> from well_log_toolkit import Template
2503
+ >>>
2504
+ >>> template = Template("reservoir")
2505
+ >>> template.add_track(track_type="continuous", logs=[...])
2506
+ >>> manager.add_template(template) # Stored as "reservoir"
2507
+ >>>
2508
+ >>> # Use in WellView
2509
+ >>> view = well.WellView(template="reservoir")
2510
+ """
2511
+ from .visualization import Template
2512
+
2513
+ if not isinstance(template, Template):
2514
+ raise TypeError(f"template must be Template, got {type(template).__name__}")
2515
+
2516
+ self._templates[template.name] = template
2517
+
2484
2518
  def set_template(self, name: str, template: Union['Template', dict]) -> None:
2485
2519
  """
2486
- Store a display template in the manager.
2520
+ Store a template with a custom name (overrides template.name).
2487
2521
 
2488
- Templates can be referenced by name when creating WellView displays.
2522
+ Use add_template() for the simpler case where the template's
2523
+ built-in name should be used.
2489
2524
 
2490
2525
  Parameters
2491
2526
  ----------
2492
2527
  name : str
2493
- Template name for reference
2528
+ Template name for reference (overrides template.name)
2494
2529
  template : Union[Template, dict]
2495
2530
  Template object or dictionary configuration
2496
2531
 
2497
2532
  Examples
2498
2533
  --------
2499
- >>> from well_log_toolkit.visualization import Template
2500
- >>>
2501
- >>> # Create and store template
2534
+ >>> # Store with a different name than the template's built-in name
2502
2535
  >>> template = Template("reservoir")
2503
- >>> template.add_track(
2504
- ... track_type="continuous",
2505
- ... logs=[{"name": "GR", "x_range": [0, 150], "color": "green"}]
2506
- ... )
2507
- >>> manager.set_template("reservoir", template)
2508
- >>>
2509
- >>> # Use template in WellView
2510
- >>> from well_log_toolkit.visualization import WellView
2511
- >>> view = manager.well_36_7_5_A.WellView(
2512
- ... depth_range=[2800, 3000],
2513
- ... template="reservoir"
2514
- ... )
2536
+ >>> manager.set_template("reservoir_v2", template)
2515
2537
  """
2516
2538
  from .visualization import Template
2517
2539
 
@@ -357,12 +357,18 @@ class Property(PropertyOperationsMixin):
357
357
  """
358
358
  Set the label mapping and mark source as modified.
359
359
 
360
+ Also sets property type to 'discrete' if not already set,
361
+ since labels are only meaningful for discrete properties.
362
+
360
363
  Parameters
361
364
  ----------
362
365
  value : Optional[dict[int, str]]
363
366
  Mapping of numeric values to label strings
364
367
  """
365
368
  if value != self._labels:
369
+ # Auto-set type to discrete if labels are being set
370
+ if value is not None and self._type != 'discrete':
371
+ self.type = 'discrete' # Use setter to trigger value rounding
366
372
  self._labels = value
367
373
  self._mark_source_modified()
368
374
 
@@ -1120,17 +1120,18 @@ class WellView:
1120
1120
  )
1121
1121
 
1122
1122
  # Collect all tops data from all tops groups
1123
- all_tops_data = {} # depth -> name mapping
1123
+ all_tops_list = []
1124
1124
  for tops_group in self.tops:
1125
- tops_data = tops_group['tops']
1126
- all_tops_data.update(tops_data)
1125
+ entries = tops_group.get('entries', [])
1126
+ for entry in entries:
1127
+ all_tops_list.append((entry['depth'], entry['name']))
1127
1128
 
1128
1129
  # Find depths for specified tops
1129
1130
  tops_depths = []
1130
1131
  not_found = []
1131
1132
  for top_name in tops_list:
1132
1133
  found = False
1133
- for depth, name in all_tops_data.items():
1134
+ for depth, name in all_tops_list:
1134
1135
  if name == top_name:
1135
1136
  tops_depths.append(depth)
1136
1137
  found = True
@@ -1140,7 +1141,7 @@ class WellView:
1140
1141
 
1141
1142
  # Only raise error if NONE of the tops were found
1142
1143
  if not tops_depths:
1143
- available_tops = list(set(all_tops_data.values()))
1144
+ available_tops = list(set(name for _, name in all_tops_list))
1144
1145
  raise ValueError(
1145
1146
  f"None of the specified formation tops were found: {tops_list}. "
1146
1147
  f"Available tops: {available_tops}"
@@ -1338,11 +1339,8 @@ class WellView:
1338
1339
  if property_name is not None and tops_dict is not None:
1339
1340
  raise ValueError("Cannot specify both 'property_name' and 'tops_dict'")
1340
1341
 
1341
- # Get tops data
1342
- tops_data = {} # depth -> formation name
1343
- color_data = {} # depth -> color
1344
- style_data = {} # depth -> line style
1345
- thickness_data = {} # depth -> line thickness
1342
+ # Get tops data as list of entries (supports multiple tops at same depth)
1343
+ tops_entries = [] # List of {'depth': d, 'name': n, 'color': c, 'style': s, 'thickness': t}
1346
1344
 
1347
1345
  if property_name is not None:
1348
1346
  # Load from discrete property
@@ -1375,7 +1373,7 @@ class WellView:
1375
1373
  if valid_values[i] != valid_values[i-1]:
1376
1374
  boundaries.append(i)
1377
1375
 
1378
- # Build tops dictionary
1376
+ # Build tops entries list
1379
1377
  for idx in boundaries:
1380
1378
  depth = float(valid_depth[idx])
1381
1379
  value = int(valid_values[idx])
@@ -1386,42 +1384,43 @@ class WellView:
1386
1384
  else:
1387
1385
  formation_name = f"Zone {value}"
1388
1386
 
1389
- tops_data[depth] = formation_name
1387
+ entry = {'depth': depth, 'name': formation_name}
1390
1388
 
1391
1389
  # Get color if available (colors parameter overrides property colors)
1392
1390
  if colors is not None and value in colors:
1393
- color_data[depth] = colors[value]
1391
+ entry['color'] = colors[value]
1394
1392
  elif prop.colors and value in prop.colors:
1395
- color_data[depth] = prop.colors[value]
1393
+ entry['color'] = prop.colors[value]
1396
1394
 
1397
1395
  # Get style if available (styles parameter overrides property styles)
1398
1396
  if styles is not None and value in styles:
1399
- style_data[depth] = styles[value]
1397
+ entry['style'] = styles[value]
1400
1398
  elif prop.styles and value in prop.styles:
1401
- style_data[depth] = prop.styles[value]
1399
+ entry['style'] = prop.styles[value]
1402
1400
 
1403
1401
  # Get thickness if available (thicknesses parameter overrides property thicknesses)
1404
1402
  if thicknesses is not None and value in thicknesses:
1405
- thickness_data[depth] = thicknesses[value]
1403
+ entry['thickness'] = thicknesses[value]
1406
1404
  elif prop.thicknesses and value in prop.thicknesses:
1407
- thickness_data[depth] = prop.thicknesses[value]
1405
+ entry['thickness'] = prop.thicknesses[value]
1406
+
1407
+ tops_entries.append(entry)
1408
1408
 
1409
1409
  else:
1410
- # Use provided dictionary
1411
- tops_data = tops_dict
1412
- if colors is not None:
1413
- color_data = colors
1414
- if styles is not None:
1415
- style_data = styles
1416
- if thicknesses is not None:
1417
- thickness_data = thicknesses
1410
+ # Use provided dictionary - convert to list format
1411
+ for depth, name in tops_dict.items():
1412
+ entry = {'depth': depth, 'name': name}
1413
+ if colors is not None and depth in colors:
1414
+ entry['color'] = colors[depth]
1415
+ if styles is not None and depth in styles:
1416
+ entry['style'] = styles[depth]
1417
+ if thicknesses is not None and depth in thicknesses:
1418
+ entry['thickness'] = thicknesses[depth]
1419
+ tops_entries.append(entry)
1418
1420
 
1419
1421
  # Store tops for rendering
1420
1422
  self.tops.append({
1421
- 'tops': tops_data,
1422
- 'colors': color_data if color_data else None,
1423
- 'styles': style_data if style_data else None,
1424
- 'thicknesses': thickness_data if thickness_data else None
1423
+ 'entries': tops_entries
1425
1424
  })
1426
1425
 
1427
1426
  def _get_depth_mask(self, depth: np.ndarray) -> np.ndarray:
@@ -1451,34 +1450,21 @@ class WellView:
1451
1450
 
1452
1451
  # For each tops group
1453
1452
  for tops_group in self.tops:
1454
- tops_data = tops_group['tops']
1455
- colors_data = tops_group['colors']
1456
- styles_data = tops_group['styles']
1457
- thicknesses_data = tops_group['thicknesses']
1453
+ entries = tops_group.get('entries', [])
1458
1454
 
1459
1455
  # Draw each top
1460
- for depth, formation_name in tops_data.items():
1456
+ for entry in entries:
1457
+ depth = entry['depth']
1458
+ formation_name = entry['name']
1459
+
1461
1460
  # Skip tops outside depth range
1462
1461
  if depth < self.depth_range[0] or depth > self.depth_range[1]:
1463
1462
  continue
1464
1463
 
1465
- # Get color for this top
1466
- if colors_data and depth in colors_data:
1467
- color = colors_data[depth]
1468
- else:
1469
- color = 'black' # Default color
1470
-
1471
- # Get line style for this top
1472
- if styles_data and depth in styles_data:
1473
- linestyle = styles_data[depth]
1474
- else:
1475
- linestyle = 'solid' # Default style
1476
-
1477
- # Get line thickness for this top
1478
- if thicknesses_data and depth in thicknesses_data:
1479
- linewidth = thicknesses_data[depth]
1480
- else:
1481
- linewidth = 1.5 # Default thickness
1464
+ # Get color, style, thickness from entry (with defaults)
1465
+ color = entry.get('color', 'black')
1466
+ linestyle = entry.get('style', 'solid')
1467
+ linewidth = entry.get('thickness', 1.5)
1482
1468
 
1483
1469
  # Draw line across all non-depth tracks
1484
1470
  for ax in non_depth_axes:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: well-log-toolkit
3
- Version: 0.1.137
3
+ Version: 0.1.139
4
4
  Summary: Fast LAS file processing with lazy loading and filtering for well log analysis
5
5
  Author-email: Kristian dF Kollsgård <kkollsg@gmail.com>
6
6
  License: MIT
@@ -463,7 +463,10 @@ template.add_track(track_type="continuous", logs=[...], title="Resistivity")
463
463
  template.add_track(track_type="discrete", logs=[...], title="Facies")
464
464
  template.add_track(track_type="depth", width=0.3, title="Depth")
465
465
 
466
- # Save for reuse
466
+ # Add to project (saves with manager.save())
467
+ manager.add_template(template) # Uses template name "reservoir"
468
+
469
+ # Or save standalone file
467
470
  template.save("reservoir_template.json")
468
471
  ```
469
472
 
@@ -811,8 +814,8 @@ view.show()
811
814
 
812
815
  **Option 2: Store in manager (recommended for multi-well projects)**
813
816
  ```python
814
- # Store template in manager
815
- manager.set_template("reservoir", template)
817
+ # Store template in manager (uses template.name automatically)
818
+ manager.add_template(template)
816
819
 
817
820
  # Use by name in any well
818
821
  view = well.WellView(depth_range=[2800, 3000], template="reservoir")
@@ -859,8 +862,8 @@ template.remove_track(2)
859
862
  template.add_track(track_type="continuous", logs=[{"name": "RT"}])
860
863
 
861
864
  # Save changes
862
- manager.set_template("reservoir", template) # Update in manager
863
- template.save("updated_template.json") # Save to file
865
+ manager.add_template(template) # Update in manager (uses template.name)
866
+ template.save("updated_template.json") # Save to file
864
867
  ```
865
868
 
866
869
  ### Customization
@@ -1006,8 +1009,8 @@ template.add_track(track_type="depth", width=0.3, title="MD (m)")
1006
1009
  # Add formation tops spanning all tracks
1007
1010
  template.add_tops(property_name='Zone')
1008
1011
 
1009
- # Save and display
1010
- manager.set_template("comprehensive", template)
1012
+ # Add to project and display
1013
+ manager.add_template(template)
1011
1014
  view = well.WellView(depth_range=[2800, 3200], template="comprehensive")
1012
1015
  view.save("comprehensive_log.png", dpi=300)
1013
1016
  ```
@@ -1886,7 +1889,8 @@ from well_log_toolkit import WellDataManager, Well, Property, LasFile
1886
1889
  - `remove_well(name)` - Remove well
1887
1890
  - `save(directory)` - Save project
1888
1891
  - `load(directory)` - Load project
1889
- - `set_template(name, template)` - Store template
1892
+ - `add_template(template)` - Store template (uses template.name)
1893
+ - `set_template(name, template)` - Store template with custom name
1890
1894
  - `get_template(name)` - Retrieve template
1891
1895
  - `list_templates()` - List template names
1892
1896
  - `Crossplot(x, y, wells=None, shape="well", ...)` - Create multi-well crossplot
@@ -2053,7 +2057,7 @@ template.add_track(
2053
2057
  logs=[{"name": "GR", "x_range": [0, 150], "color": "green"}],
2054
2058
  title="Gamma Ray"
2055
2059
  )
2056
- manager.set_template("custom", template)
2060
+ manager.add_template(template) # Stored as "custom"
2057
2061
  view = well.WellView(template="custom")
2058
2062
  view.save("log.png", dpi=300)
2059
2063
  ```