jpk-reader-rs 0.0.1__tar.gz → 0.0.2__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.
@@ -636,7 +636,7 @@ dependencies = [
636
636
 
637
637
  [[package]]
638
638
  name = "jpk-reader-rs"
639
- version = "0.0.1"
639
+ version = "0.0.2"
640
640
  dependencies = [
641
641
  "arrow",
642
642
  "jpk_reader",
@@ -1164,6 +1164,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1164
1164
  checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd"
1165
1165
  dependencies = [
1166
1166
  "deranged",
1167
+ "js-sys",
1167
1168
  "num-conv",
1168
1169
  "powerfmt",
1169
1170
  "serde_core",
@@ -1267,6 +1268,12 @@ dependencies = [
1267
1268
  "syn",
1268
1269
  ]
1269
1270
 
1271
+ [[package]]
1272
+ name = "typed-path"
1273
+ version = "0.12.0"
1274
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1275
+ checksum = "7922f2cdc51280d47b491af9eafc41eb0cdab85eabcb390c854412fcbf26dbe8"
1276
+
1270
1277
  [[package]]
1271
1278
  name = "typenum"
1272
1279
  version = "1.19.0"
@@ -1473,9 +1480,9 @@ dependencies = [
1473
1480
 
1474
1481
  [[package]]
1475
1482
  name = "zip"
1476
- version = "7.1.0"
1483
+ version = "7.2.0"
1477
1484
  source = "registry+https://github.com/rust-lang/crates.io-index"
1478
- checksum = "9013f1222db8a6d680f13a7ccdc60a781199cd09c2fa4eff58e728bb181757fc"
1485
+ checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0"
1479
1486
  dependencies = [
1480
1487
  "aes",
1481
1488
  "bzip2",
@@ -1493,6 +1500,7 @@ dependencies = [
1493
1500
  "ppmd-rust",
1494
1501
  "sha1",
1495
1502
  "time",
1503
+ "typed-path",
1496
1504
  "zeroize",
1497
1505
  "zopfli",
1498
1506
  "zstd",
@@ -1,8 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jpk-reader-rs
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
7
7
  Requires-Dist: pyarrow
8
8
  Requires-Python: >=3.8
9
+ Project-URL: Homepage, https://github.com/bicarlsen/jpk_reader
10
+ Project-URL: Issues, https://github.com/bicarlsen/jpk_reader/issues
11
+ Project-URL: Source, https://github.com/bicarlsen/jpk_reader
@@ -1,15 +1,16 @@
1
1
  [package]
2
2
  name = "jpk-reader-rs"
3
- version = "0.0.1"
3
+ version = "0.0.2"
4
4
  edition = "2024"
5
5
 
6
6
  authors = ["Brian Carlsen <carlsen.bri@gmail.com>"]
7
7
  description = "Python bindings for JPK data reader."
8
- license = "GNU GPLv3"
8
+ license = "MIT OR Apache-2.0"
9
9
 
10
10
  repository = "https://github.com/bicarlsen/jpk_reader"
11
11
  keywords = ["afm", "atomic force microscopy", "jpk", "bruker"]
12
12
  categories = ["science"]
13
+ readme = "README.md"
13
14
 
14
15
  # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
15
16
  [lib]
File without changes
@@ -0,0 +1,12 @@
1
+ # %%
2
+ import jpk_reader_rs as jpk
3
+
4
+ # DATA_PATH = "../../data/qi_data/qi_data-2_0-lg.jpk-qi-data"
5
+ DATA_PATH = "s:\\_öffentlich_TAUSCHordner\\Mitarbeitende\\carlsen_brian\\degradation\\00-preliminary\\01/carlsen-asfaw-postdegradation-data-2026.01.08-16.35.28.552.jpk-qi-data"
6
+ # %%
7
+ reader = jpk.QIMapReader(DATA_PATH)
8
+ # %%
9
+ metadata = reader.all_metadata()
10
+ # %%
11
+ data = reader.all_data()
12
+ # %%
@@ -0,0 +1,46 @@
1
+ from typing import Optional
2
+ import pyarrow
3
+
4
+ class QIMapReader:
5
+ """A JPK QI Map data (`.jpk-qi-data`) reader."""
6
+
7
+ def __init__(self, path: str) -> None: ...
8
+ def len(self) -> int:
9
+ """
10
+ Returns:
11
+ int: Number of files in the archive.
12
+ """
13
+
14
+ def files(self) -> list[str]:
15
+ """Get all files names in the archive.
16
+
17
+ Returns:
18
+ list[str]: Sorted list of file names.
19
+ """
20
+
21
+ def all_data(self) -> pyarrow.RecordBatch:
22
+ """Gets all data from the file.
23
+
24
+ Returns:
25
+ pyarrow.RecordBatch: All file data.
26
+
27
+ Raises:
28
+ RuntimeError: If the file can not be read.
29
+ """
30
+
31
+ def all_metadata(
32
+ self,
33
+ ) -> dict[tuple[str, Optional[int], Optional[int]], dict[str, str]]:
34
+ """Get all metadata from the file.
35
+
36
+ Returns:
37
+ dict[tuple[str, Optional[int], Optional[int]], dict[str, str]]: Dictionary keyed by the property type.
38
+ Keys are of the form `(type, index, segment)`:
39
+ + ("dataset", None, None)
40
+ + ("shared_data", None, None)
41
+ + ("index", int, None)
42
+ + ("segment", int, int)
43
+
44
+ Raises:
45
+ RuntimeError: If the file can not be read.
46
+ """
@@ -0,0 +1,145 @@
1
+ use arrow::{
2
+ array::{
3
+ ArrayRef, Float64Builder, ListBuilder, RecordBatch, StringBuilder, UInt8Builder,
4
+ UInt32Builder,
5
+ },
6
+ datatypes::{DataType, Field, Schema},
7
+ pyarrow::PyArrowType,
8
+ };
9
+ use jpk_reader::{self as jpk, ArchiveReader, qi_map::QIMapReader as _};
10
+ use pyo3::{exceptions::PyRuntimeError, prelude::*, types::PyDict};
11
+ use std::{borrow::Cow, path::PathBuf, sync::Arc};
12
+
13
+ const CHANNEL_NAME_LEN_HINT: usize = 10;
14
+
15
+ /// Python exports
16
+ #[pymodule]
17
+ mod jpk_reader_rs {
18
+ #[pymodule_export]
19
+ use super::QIMapReader;
20
+ }
21
+
22
+ #[pyclass]
23
+ pub struct QIMapReader {
24
+ inner: jpk::qi_map::VersionedFileReader,
25
+ }
26
+
27
+ #[pymethods]
28
+ impl QIMapReader {
29
+ #[new]
30
+ fn new(path: PathBuf) -> PyResult<Self> {
31
+ let reader = jpk::qi_map::FileReader::new_versioned(path)
32
+ .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
33
+ Ok(Self { inner: reader })
34
+ }
35
+
36
+ fn len(&self) -> PyResult<usize> {
37
+ Ok(self.inner.len())
38
+ }
39
+
40
+ fn files(&self) -> PyResult<Vec<&str>> {
41
+ const SEPARATOR: char = '/';
42
+ let mut files = self.inner.files();
43
+ let max_digits = files.len().ilog10() as usize + 1;
44
+ files.sort_by_key(|file| {
45
+ file.split(SEPARATOR)
46
+ .map(|component| {
47
+ if let Ok(value) = component.parse::<usize>() {
48
+ Cow::Owned(format!("{:0>width$}", value, width = max_digits))
49
+ } else {
50
+ Cow::Borrowed(component)
51
+ }
52
+ })
53
+ .collect::<String>()
54
+ });
55
+
56
+ return Ok(files);
57
+ }
58
+
59
+ fn all_data(&mut self) -> PyResult<PyArrowType<RecordBatch>> {
60
+ let data = self
61
+ .inner
62
+ .query_data(&jpk::qi_map::DataQuery::select_all())
63
+ .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
64
+
65
+ let schema = Schema::new(vec![
66
+ Field::new("index", DataType::UInt32, false),
67
+ Field::new("segment", DataType::UInt8, false),
68
+ Field::new("channel", DataType::Utf8, false),
69
+ Field::new("data", DataType::new_list(DataType::Float64, true), false), // TODO: Can List be non-nullable?
70
+ ]);
71
+
72
+ let (index, values) = data.into_parts();
73
+ let mut idx_index = UInt32Builder::with_capacity(index.len());
74
+ let mut idx_segment = UInt8Builder::with_capacity(index.len());
75
+ let mut idx_channel =
76
+ StringBuilder::with_capacity(index.len(), index.len() * CHANNEL_NAME_LEN_HINT);
77
+ for idx in index {
78
+ let jpk::qi_map::DataIndex {
79
+ index,
80
+ segment,
81
+ channel,
82
+ } = idx;
83
+ idx_index.append_value(index);
84
+ idx_segment.append_value(segment);
85
+ idx_channel.append_value(channel);
86
+ }
87
+ let idx_i = idx_index.finish();
88
+ let idx_segment = idx_segment.finish();
89
+ let idx_channel = idx_channel.finish();
90
+
91
+ let mut value_builder = ListBuilder::with_capacity(Float64Builder::new(), values.len());
92
+ for data in values {
93
+ let data = data.into_iter().map(|value| Some(value));
94
+ value_builder.append_value(data);
95
+ }
96
+ let values = value_builder.finish();
97
+
98
+ let schema = Arc::new(schema);
99
+ let data = vec![
100
+ Arc::new(idx_i) as ArrayRef,
101
+ Arc::new(idx_segment) as ArrayRef,
102
+ Arc::new(idx_channel) as ArrayRef,
103
+ Arc::new(values) as ArrayRef,
104
+ ];
105
+ let records = RecordBatch::try_new(schema, data).unwrap();
106
+ Ok(records.into())
107
+ }
108
+
109
+ fn all_metadata(&mut self, py: Python) -> PyResult<Py<PyAny>> {
110
+ let data = self
111
+ .inner
112
+ .query_metadata(&jpk_reader::qi_map::MetadataQuery::All)
113
+ .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
114
+
115
+ let dict = PyDict::new(py);
116
+ for (index, properties) in data.into_iter() {
117
+ let props = PyDict::new(py);
118
+ for (key, value) in properties.into_iter() {
119
+ props.set_item(key, value)?;
120
+ }
121
+
122
+ let mut idx = ("", None, None);
123
+ match index {
124
+ jpk_reader::qi_map::MetadataIndex::Dataset => {
125
+ idx.0 = "dataset";
126
+ }
127
+ jpk_reader::qi_map::MetadataIndex::SharedData => {
128
+ idx.0 = "shared_data";
129
+ }
130
+ jpk_reader::qi_map::MetadataIndex::Index(index) => {
131
+ idx.0 = "index";
132
+ idx.1 = Some(index);
133
+ }
134
+ jpk_reader::qi_map::MetadataIndex::Segment { index, segment } => {
135
+ idx.0 = "segment";
136
+ idx.1 = Some(index);
137
+ idx.2 = Some(segment);
138
+ }
139
+ };
140
+ dict.set_item(idx, props)?;
141
+ }
142
+
143
+ Ok(dict.into())
144
+ }
145
+ }
@@ -14,6 +14,11 @@ dynamic = ["version"]
14
14
 
15
15
  dependencies = ["pyarrow"]
16
16
 
17
+ [project.urls]
18
+ Issues = "https://github.com/bicarlsen/jpk_reader/issues"
19
+ Source = "https://github.com/bicarlsen/jpk_reader"
20
+ Homepage = "https://github.com/bicarlsen/jpk_reader"
21
+
17
22
  [tool]
18
23
 
19
24
  [tool.maturin]
@@ -12,13 +12,13 @@ keywords = ["afm", "atomic force microscopy", "jpk", "bruker"]
12
12
  categories = ["science"]
13
13
 
14
14
  [dependencies]
15
- derive_more = { workspace = true, features = ["from", "deref"] }
15
+ derive_more = { workspace = true, features = ["from", "deref", "deref_mut"] }
16
16
  rayon = "1.11.0"
17
17
  tracing = { workspace = true, optional = true }
18
18
  tracing-subscriber = { workspace = true, features = [
19
19
  "env-filter",
20
20
  ], optional = true }
21
- zip = "7.1"
21
+ zip = "7.2"
22
22
 
23
23
  [dev-dependencies]
24
24
  tracing-test = { workspace = true }
@@ -0,0 +1,13 @@
1
+ use std::path::Path;
2
+ use zip;
3
+
4
+ pub mod properties;
5
+ pub mod qi_map;
6
+
7
+ pub trait ArchiveReader {
8
+ /// List of files in the archive.
9
+ fn files(&self) -> Vec<&str>;
10
+
11
+ /// Number of files in the archive.
12
+ fn len(&self) -> usize;
13
+ }
@@ -62,7 +62,17 @@ impl Properties {
62
62
  }
63
63
  }
64
64
 
65
+ impl IntoIterator for Properties {
66
+ type Item = (String, String);
67
+ type IntoIter = std::vec::IntoIter<Self::Item>;
68
+
69
+ fn into_iter(self) -> Self::IntoIter {
70
+ self.properties.into_iter()
71
+ }
72
+ }
73
+
65
74
  /// The format of the file was invalid.
75
+ #[derive(Debug)]
66
76
  pub struct InvalidFormat;
67
77
 
68
78
  pub enum PropertyError {