torch-grid-utils 0.0.1__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.
@@ -0,0 +1,11 @@
1
+ # Do not edit - changes here will be overwritten by Copier
2
+ _commit: v1
3
+ _src_path: gh:pydev-guide/pyrepo-copier
4
+ author_email: alisterburt@gmail.com
5
+ author_name: Alister Burt
6
+ github_username: alisterburt
7
+ mode: tooling
8
+ module_name: torch_grid_utils
9
+ project_name: torch-grid-utils
10
+ project_short_description: Grids for 2D/3D image manipulations in PyTorch
11
+
@@ -0,0 +1,15 @@
1
+ * torch-grids version:
2
+ * Python version:
3
+ * Operating System:
4
+
5
+ ### Description
6
+
7
+ Describe what you were trying to get done.
8
+ Tell us what happened, what went wrong, and what you expected to happen.
9
+
10
+ ### What I Did
11
+
12
+ ```
13
+ Paste the command(s) you ran and the output.
14
+ If there was a crash, please include the traceback here.
15
+ ```
@@ -0,0 +1,12 @@
1
+ ---
2
+ title: "{{ env.TITLE }}"
3
+ labels: [bug]
4
+ ---
5
+ The {{ workflow }} workflow failed on {{ date | date("YYYY-MM-DD HH:mm") }} UTC
6
+
7
+ The most recent failing test was on {{ env.PLATFORM }} py{{ env.PYTHON }}
8
+ with commit: {{ sha }}
9
+
10
+ Full run: https://github.com/{{ repo }}/actions/runs/{{ env.RUN_ID }}
11
+
12
+ (This post will be updated if another test fails, as long as this issue remains open.)
@@ -0,0 +1,10 @@
1
+ # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
2
+
3
+ version: 2
4
+ updates:
5
+ - package-ecosystem: "github-actions"
6
+ directory: "/"
7
+ schedule:
8
+ interval: "weekly"
9
+ commit-message:
10
+ prefix: "ci(dependabot):"
@@ -0,0 +1,108 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ tags:
8
+ - "v*"
9
+ pull_request:
10
+ workflow_dispatch:
11
+ schedule:
12
+ # run every week (for --pre release tests)
13
+ - cron: "0 0 * * 0"
14
+
15
+ # cancel in-progress runs that use the same workflow and branch
16
+ concurrency:
17
+ group: ${{ github.workflow }}-${{ github.ref }}
18
+ cancel-in-progress: true
19
+
20
+ jobs:
21
+ check-manifest:
22
+ # check-manifest is a tool that checks that all files in version control are
23
+ # included in the sdist (unless explicitly excluded)
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - run: pipx run check-manifest
28
+
29
+ test:
30
+ name: ${{ matrix.platform }} (${{ matrix.python-version }})
31
+ runs-on: ${{ matrix.platform }}
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ python-version: ["3.10", "3.11", "3.12"]
36
+ platform: [ubuntu-latest, ] # macos-latest, windows-latest]
37
+
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+
41
+ - name: ๐Ÿ Set up Python ${{ matrix.python-version }}
42
+ uses: actions/setup-python@v4
43
+ with:
44
+ python-version: ${{ matrix.python-version }}
45
+ cache-dependency-path: "pyproject.toml"
46
+ cache: "pip"
47
+
48
+ - name: Install Dependencies
49
+ run: |
50
+ python -m pip install -U pip
51
+ # if running a cron job, we add the --pre flag to test against pre-releases
52
+ python -m pip install .[test] ${{ github.event_name == 'schedule' && '--pre' || '' }}
53
+
54
+ - name: ๐Ÿงช Run Tests
55
+ run: pytest --color=yes --cov --cov-report=xml --cov-report=term-missing
56
+
57
+ # If something goes wrong with --pre tests, we can open an issue in the repo
58
+ - name: ๐Ÿ“ Report --pre Failures
59
+ if: failure() && github.event_name == 'schedule'
60
+ uses: JasonEtco/create-an-issue@v2
61
+ env:
62
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
63
+ PLATFORM: ${{ matrix.platform }}
64
+ PYTHON: ${{ matrix.python-version }}
65
+ RUN_ID: ${{ github.run_id }}
66
+ TITLE: "[test-bot] pip install --pre is failing"
67
+ with:
68
+ filename: .github/TEST_FAIL_TEMPLATE.md
69
+ update_existing: true
70
+
71
+ - name: Coverage
72
+ uses: codecov/codecov-action@v3
73
+
74
+ deploy:
75
+ name: Deploy
76
+ needs: test
77
+ if: success() && startsWith(github.ref, 'refs/tags/') && github.event_name != 'schedule'
78
+ runs-on: ubuntu-latest
79
+
80
+ permissions:
81
+ # IMPORTANT: this permission is mandatory for trusted publishing on PyPi
82
+ # see https://docs.pypi.org/trusted-publishers/
83
+ id-token: write
84
+ # This permission allows writing releases
85
+ contents: write
86
+
87
+ steps:
88
+ - uses: actions/checkout@v4
89
+ with:
90
+ fetch-depth: 0
91
+
92
+ - name: ๐Ÿ Set up Python
93
+ uses: actions/setup-python@v4
94
+ with:
95
+ python-version: "3.x"
96
+
97
+ - name: ๐Ÿ‘ท Build
98
+ run: |
99
+ python -m pip install build
100
+ python -m build
101
+
102
+ - name: ๐Ÿšข Publish to PyPI
103
+ uses: pypa/gh-action-pypi-publish@release/v1
104
+
105
+ - uses: softprops/action-gh-release@v1
106
+ with:
107
+ generate_release_notes: true
108
+ files: './dist/*'
@@ -0,0 +1,111 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ env/
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+
28
+ .DS_Store
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ .hypothesis/
50
+ .pytest_cache/
51
+
52
+ # Translations
53
+ *.mo
54
+ *.pot
55
+
56
+ # Django stuff:
57
+ *.log
58
+ local_settings.py
59
+
60
+ # Flask stuff:
61
+ instance/
62
+ .webassets-cache
63
+
64
+ # Scrapy stuff:
65
+ .scrapy
66
+
67
+ # Sphinx documentation
68
+ docs/_build/
69
+
70
+ # PyBuilder
71
+ target/
72
+
73
+ # Jupyter Notebook
74
+ .ipynb_checkpoints
75
+
76
+ # pyenv
77
+ .python-version
78
+
79
+ # celery beat schedule file
80
+ celerybeat-schedule
81
+
82
+ # SageMath parsed files
83
+ *.sage.py
84
+
85
+ # dotenv
86
+ .env
87
+
88
+ # virtualenv
89
+ .venv
90
+ venv/
91
+ ENV/
92
+
93
+ # Spyder project settings
94
+ .spyderproject
95
+ .spyproject
96
+
97
+ # Rope project settings
98
+ .ropeproject
99
+
100
+ # mkdocs documentation
101
+ /site
102
+
103
+ # mypy
104
+ .mypy_cache/
105
+
106
+ # ruff
107
+ .ruff_cache/
108
+
109
+ # IDE settings
110
+ .vscode/
111
+ .idea/
@@ -0,0 +1,37 @@
1
+ # enable pre-commit.ci at https://pre-commit.ci/
2
+ # it adds:
3
+ # 1. auto fixing pull requests
4
+ # 2. auto updating the pre-commit configuration
5
+ ci:
6
+ autoupdate_schedule: monthly
7
+ autofix_commit_msg: "style(pre-commit.ci): auto fixes [...]"
8
+ autoupdate_commit_msg: "ci(pre-commit.ci): autoupdate"
9
+
10
+ repos:
11
+ - repo: https://github.com/abravalheri/validate-pyproject
12
+ rev: v0.16
13
+ hooks:
14
+ - id: validate-pyproject
15
+
16
+ - repo: https://github.com/crate-ci/typos
17
+ rev: v1.20.9
18
+ hooks:
19
+ - id: typos
20
+ args: [--force-exclude] # omitting --write-changes
21
+
22
+ - repo: https://github.com/charliermarsh/ruff-pre-commit
23
+ rev: v0.4.1
24
+ hooks:
25
+ - id: ruff
26
+ args: [--fix] # may also add '--unsafe-fixes'
27
+ - id: ruff-format
28
+
29
+ - repo: https://github.com/pre-commit/mirrors-mypy
30
+ rev: v1.9.0
31
+ hooks:
32
+ - id: mypy
33
+ files: "^src/"
34
+ # # you have to add the things you want to type check against here
35
+ # additional_dependencies:
36
+ # - numpy
37
+
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2023, Alister Burt
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.3
2
+ Name: torch-grid-utils
3
+ Version: 0.0.1
4
+ Summary: Grid utilities for 2D/3D image manipulations in PyTorch
5
+ Project-URL: homepage, https://github.com/alisterburt/torch-grids
6
+ Project-URL: repository, https://github.com/alisterburt/torch-grids
7
+ Author-email: Alister Burt <alisterburt@gmail.com>
8
+ License: BSD-3-Clause
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: License :: OSI Approved :: BSD License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: einops
19
+ Requires-Dist: numpy
20
+ Requires-Dist: torch
21
+ Provides-Extra: dev
22
+ Requires-Dist: ipython; extra == 'dev'
23
+ Requires-Dist: mypy; extra == 'dev'
24
+ Requires-Dist: pdbpp; extra == 'dev'
25
+ Requires-Dist: pre-commit; extra == 'dev'
26
+ Requires-Dist: rich; extra == 'dev'
27
+ Requires-Dist: ruff; extra == 'dev'
28
+ Provides-Extra: test
29
+ Requires-Dist: pytest; extra == 'test'
30
+ Requires-Dist: pytest-cov; extra == 'test'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # torch-grid-utils
34
+
35
+ [![License](https://img.shields.io/pypi/l/torch-grids.svg?color=green)](https://github.com/alisterburt/torch-grids/raw/main/LICENSE)
36
+ [![PyPI](https://img.shields.io/pypi/v/torch-grids.svg?color=green)](https://pypi.org/project/torch-grids)
37
+ [![Python Version](https://img.shields.io/pypi/pyversions/torch-grids.svg?color=green)](https://python.org)
38
+ [![CI](https://github.com/alisterburt/torch-grids/actions/workflows/ci.yml/badge.svg)](https://github.com/alisterburt/torch-grids/actions/workflows/ci.yml)
39
+ [![codecov](https://codecov.io/gh/alisterburt/torch-grids/branch/main/graph/badge.svg)](https://codecov.io/gh/alisterburt/torch-grids)
40
+
41
+ *torch-grid-utils* provides grids for 2D/3D image manipulations in PyTorch.
42
+
43
+
@@ -0,0 +1,11 @@
1
+ # torch-grid-utils
2
+
3
+ [![License](https://img.shields.io/pypi/l/torch-grids.svg?color=green)](https://github.com/alisterburt/torch-grids/raw/main/LICENSE)
4
+ [![PyPI](https://img.shields.io/pypi/v/torch-grids.svg?color=green)](https://pypi.org/project/torch-grids)
5
+ [![Python Version](https://img.shields.io/pypi/pyversions/torch-grids.svg?color=green)](https://python.org)
6
+ [![CI](https://github.com/alisterburt/torch-grids/actions/workflows/ci.yml/badge.svg)](https://github.com/alisterburt/torch-grids/actions/workflows/ci.yml)
7
+ [![codecov](https://codecov.io/gh/alisterburt/torch-grids/branch/main/graph/badge.svg)](https://codecov.io/gh/alisterburt/torch-grids)
8
+
9
+ *torch-grid-utils* provides grids for 2D/3D image manipulations in PyTorch.
10
+
11
+
@@ -0,0 +1,150 @@
1
+ # https://peps.python.org/pep-0517/
2
+ [build-system]
3
+ requires = ["hatchling", "hatch-vcs"]
4
+ build-backend = "hatchling.build"
5
+
6
+ # https://hatch.pypa.io/latest/config/metadata/
7
+ [tool.hatch.version]
8
+ source = "vcs"
9
+
10
+ # read more about configuring hatch at:
11
+ # https://hatch.pypa.io/latest/config/build/
12
+ [tool.hatch.build.targets.wheel]
13
+ only-include = ["src"]
14
+ sources = ["src"]
15
+
16
+ # https://peps.python.org/pep-0621/
17
+ [project]
18
+ name = "torch-grid-utils"
19
+ dynamic = ["version"]
20
+ description = "Grid utilities for 2D/3D image manipulations in PyTorch"
21
+ readme = "README.md"
22
+ requires-python = ">=3.10"
23
+ license = { text = "BSD-3-Clause" }
24
+ authors = [{ name = "Alister Burt", email = "alisterburt@gmail.com" }]
25
+ # https://pypi.org/classifiers/
26
+ classifiers = [
27
+ "Development Status :: 3 - Alpha",
28
+ "License :: OSI Approved :: BSD License",
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Typing :: Typed",
34
+ ]
35
+ # add your package dependencies here
36
+ dependencies = [
37
+ "torch",
38
+ "numpy",
39
+ "einops",
40
+ ]
41
+
42
+ # https://peps.python.org/pep-0621/#dependencies-optional-dependencies
43
+ # "extras" (e.g. for `pip install .[test]`)
44
+ [project.optional-dependencies]
45
+ # add dependencies used for testing here
46
+ test = ["pytest", "pytest-cov"]
47
+ # add anything else you like to have in your dev environment here
48
+ dev = [
49
+ "ipython",
50
+ "mypy",
51
+ "pdbpp", # https://github.com/pdbpp/pdbpp
52
+ "pre-commit",
53
+ "rich", # https://github.com/Textualize/rich
54
+ "ruff",
55
+ ]
56
+
57
+ [project.urls]
58
+ homepage = "https://github.com/alisterburt/torch-grids"
59
+ repository = "https://github.com/alisterburt/torch-grids"
60
+
61
+ # Entry points
62
+ # https://peps.python.org/pep-0621/#entry-points
63
+ # same as console_scripts entry point
64
+ # [project.scripts]
65
+ # torch-grid-utils-cli = "torch_grid_utils:main_cli"
66
+
67
+ # [project.entry-points."some.group"]
68
+ # tomatoes = "torch_grid_utils:main_tomatoes"
69
+
70
+ # https://docs.astral.sh/ruff
71
+ [tool.ruff]
72
+ line-length = 88
73
+ target-version = "py38"
74
+ src = ["src"]
75
+
76
+ # https://docs.astral.sh/ruff/rules
77
+ [tool.ruff.lint]
78
+ pydocstyle = { convention = "numpy" }
79
+ select = [
80
+ "E", # style errors
81
+ "W", # style warnings
82
+ "F", # flakes
83
+ "D", # pydocstyle
84
+ "D417", # Missing argument descriptions in Docstrings
85
+ "I", # isort
86
+ "UP", # pyupgrade
87
+ "C4", # flake8-comprehensions
88
+ "B", # flake8-bugbear
89
+ "A001", # flake8-builtins
90
+ "RUF", # ruff-specific rules
91
+ "TCH", # flake8-type-checking
92
+ "TID", # flake8-tidy-imports
93
+ ]
94
+ ignore = [
95
+ "D401", # First line should be in imperative mood (remove to opt in)
96
+ ]
97
+
98
+ [tool.ruff.lint.per-file-ignores]
99
+ "tests/*.py" = ["D", "S"]
100
+
101
+ # https://docs.astral.sh/ruff/formatter/
102
+ [tool.ruff.format]
103
+ docstring-code-format = true
104
+ skip-magic-trailing-comma = false # default is false
105
+
106
+ # https://mypy.readthedocs.io/en/stable/config_file.html
107
+ [tool.mypy]
108
+ files = "src/**/"
109
+ strict = true
110
+ disallow_any_generics = false
111
+ disallow_subclassing_any = false
112
+ show_error_codes = true
113
+ pretty = true
114
+
115
+ # # module specific overrides
116
+ # [[tool.mypy.overrides]]
117
+ # module = ["numpy.*",]
118
+ # ignore_errors = true
119
+
120
+ # https://docs.pytest.org/
121
+ [tool.pytest.ini_options]
122
+ minversion = "7.0"
123
+ testpaths = ["tests"]
124
+ filterwarnings = ["error"]
125
+
126
+ # https://coverage.readthedocs.io/
127
+ [tool.coverage.report]
128
+ show_missing = true
129
+ exclude_lines = [
130
+ "pragma: no cover",
131
+ "if TYPE_CHECKING:",
132
+ "@overload",
133
+ "except ImportError",
134
+ "\\.\\.\\.",
135
+ "raise NotImplementedError()",
136
+ "pass",
137
+ ]
138
+
139
+ [tool.coverage.run]
140
+ source = ["torch_grids"]
141
+
142
+ # https://github.com/mgedmin/check-manifest#configuration
143
+ # add files that you want check-manifest to explicitly ignore here
144
+ # (files that are in the repo but shouldn't go in the package)
145
+ [tool.check-manifest]
146
+ ignore = [
147
+ ".pre-commit-config.yaml",
148
+ ".ruff_cache/**/*",
149
+ "tests/**/*",
150
+ ]
@@ -0,0 +1,13 @@
1
+ """Grids for 2D/3D image manipulations in PyTorch"""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("torch-grid-utils")
7
+ except PackageNotFoundError:
8
+ __version__ = "uninstalled"
9
+ __author__ = "Alister Burt"
10
+ __email__ = "alisterburt@gmail.com"
11
+
12
+ from .fftfreq_grid import fftfreq_grid
13
+ from .coordinate_grid import coordinate_grid
@@ -0,0 +1,61 @@
1
+ from typing import Sequence
2
+
3
+ import einops
4
+ import numpy as np
5
+ import torch
6
+
7
+
8
+ def coordinate_grid(
9
+ image_shape: Sequence[int],
10
+ center: torch.Tensor | tuple[float, ...] | None = False,
11
+ norm: bool = False,
12
+ device: torch.device | None = None,
13
+ ) -> torch.FloatTensor:
14
+ """Get a dense grid of array coordinates from grid dimensions.
15
+
16
+ For input `image_shape` of `(d, h, w)`, this function produces a
17
+ `(d, h, w, 3)` grid of coordinates. Coordinate order matches the order of
18
+ dimensions in `image_shape`.
19
+
20
+ Parameters
21
+ ----------
22
+ image_shape: Sequence[int]
23
+ Shape of the image for which coordinates should be returned.
24
+ center: torch.Tensor | tuple[float, ...] | None
25
+ Array of center points relative to which coordinates will be calculated.
26
+ If `None`, default to the array origin `[0, ...]` of zero in all dimensions.
27
+ norm: bool
28
+ Whether to compute the Euclidean norm of the coordinate grid.
29
+ device: torch.device
30
+ PyTorch device on which to put the coordinate grid.
31
+
32
+ Returns
33
+ -------
34
+ grid: torch.LongTensor
35
+ `(*image_shape, image_ndim)` array of coordinates if `norm` is `False`
36
+ else `(*image_shape, )`.
37
+ """
38
+ grid = torch.tensor(
39
+ np.indices(image_shape),
40
+ device=device,
41
+ dtype=torch.float32
42
+ ) # (coordinates, *image_shape)
43
+ grid = einops.rearrange(grid, 'coords ... -> ... coords')
44
+ ndim = len(image_shape)
45
+ if center is not None:
46
+ center = torch.as_tensor(center, dtype=grid.dtype, device=grid.device)
47
+ center = torch.atleast_1d(center)
48
+ center, ps = einops.pack([center], pattern='* coords')
49
+ ones = ' '.join('1' * ndim)
50
+ axis_ids = ' '.join(_unique_characters(ndim))
51
+ center = einops.rearrange(center, f"b coords -> b {ones} coords")
52
+ grid = grid - center
53
+ [grid] = einops.unpack(grid, packed_shapes=ps, pattern=f'* {axis_ids} coords')
54
+ if norm is True:
55
+ grid = einops.reduce(grid ** 2, '... coords -> ...', reduction='sum') ** 0.5
56
+ return grid
57
+
58
+
59
+ def _unique_characters(n: int) -> str:
60
+ chars = "abcdefghijklmnopqrstuvwxyz"
61
+ return chars[:n]