diffct 2.4.1__py3-none-any.whl → 2.4.2__py3-none-any.whl
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.
- diffct/__init__.py +1 -1
- diffct/kernels/surface_geometry.py +13 -7
- diffct/operators.py +102 -8
- diffct/surfaces.py +2 -0
- {diffct-2.4.1.dist-info → diffct-2.4.2.dist-info}/METADATA +1 -1
- {diffct-2.4.1.dist-info → diffct-2.4.2.dist-info}/RECORD +9 -9
- {diffct-2.4.1.dist-info → diffct-2.4.2.dist-info}/WHEEL +0 -0
- {diffct-2.4.1.dist-info → diffct-2.4.2.dist-info}/licenses/LICENSE +0 -0
- {diffct-2.4.1.dist-info → diffct-2.4.2.dist-info}/licenses/NOTICE +0 -0
diffct/__init__.py
CHANGED
|
@@ -51,11 +51,17 @@ def compose_surface(frames, offsets, source, points, view_index, pixel_start,
|
|
|
51
51
|
``frame32`` gives the arithmetic dtype of each packed frame component.
|
|
52
52
|
``offset32`` gives the offset arithmetic dtype. Values are stored exactly
|
|
53
53
|
in float64 staging buffers. Results use caller-owned float32 buffers.
|
|
54
|
+
|
|
55
|
+
A launch may compose several views with the same offsets: grid row ``y``
|
|
56
|
+
writes view ``view_index + y`` from the ``y``-th of ``gridDim.y`` equal
|
|
57
|
+
frame packets in ``frames``.
|
|
54
58
|
"""
|
|
55
59
|
pixel = cuda.grid(1)
|
|
60
|
+
view_index += cuda.blockIdx.y
|
|
61
|
+
base = cuda.blockIdx.y * (frames.size // cuda.gridDim.y)
|
|
56
62
|
if pixel == 0:
|
|
57
63
|
for axis in range(rank):
|
|
58
|
-
value = frames[axis]
|
|
64
|
+
value = frames[base + axis]
|
|
59
65
|
if not parallel:
|
|
60
66
|
value = _surface_add(value, -centre[axis], frame32[0])
|
|
61
67
|
source[view_index, axis] = np.float32(value)
|
|
@@ -85,20 +91,20 @@ def compose_surface(frames, offsets, source, points, view_index, pixel_start,
|
|
|
85
91
|
point_squared = np.float64(0.0)
|
|
86
92
|
for axis in range(rank):
|
|
87
93
|
point = _surface_add(
|
|
88
|
-
frames[rank + axis],
|
|
89
|
-
_surface_multiply(tangent, frames[2 * rank + axis], tangent32),
|
|
94
|
+
frames[base + rank + axis],
|
|
95
|
+
_surface_multiply(tangent, frames[base + 2 * rank + axis], tangent32),
|
|
90
96
|
frame32[1] and tangent32,
|
|
91
97
|
)
|
|
92
98
|
if rank == 3:
|
|
93
99
|
point = _surface_add(
|
|
94
|
-
point, _surface_multiply(vertical, frames[3 * rank + axis], vertical32),
|
|
100
|
+
point, _surface_multiply(vertical, frames[base + 3 * rank + axis], vertical32),
|
|
95
101
|
point32,
|
|
96
102
|
)
|
|
97
|
-
normal = frames[4 * rank + axis]
|
|
103
|
+
normal = frames[base + 4 * rank + axis]
|
|
98
104
|
else:
|
|
99
|
-
normal = frames[2 * rank + 1] if axis == 0 else -frames[2 * rank]
|
|
105
|
+
normal = frames[base + 2 * rank + 1] if axis == 0 else -frames[base + 2 * rank]
|
|
100
106
|
point = _surface_add(point, _surface_multiply(depth, normal, depth32), final32)
|
|
101
|
-
source_value = frames[axis]
|
|
107
|
+
source_value = frames[base + axis]
|
|
102
108
|
source_native = np.float32(source_value)
|
|
103
109
|
point_native = np.float32(point)
|
|
104
110
|
finite = (finite and math.isfinite(source_value) and math.isfinite(point)
|
diffct/operators.py
CHANGED
|
@@ -489,6 +489,8 @@ class Projector:
|
|
|
489
489
|
if self.backend != "torch" and detector_surface is not None:
|
|
490
490
|
geometry, spec, surface_trainable, surface_metadata = self._surface_validation_inputs()
|
|
491
491
|
devices = self._devices or (torch.device("cuda", torch.cuda.current_device()),)
|
|
492
|
+
# Validation budgets depend on whether geometry gradients are needed.
|
|
493
|
+
self._geometry_trainable = bool(self._learnable) or surface_trainable
|
|
492
494
|
self._validate_surface_geometry(geometry, spec, devices)
|
|
493
495
|
except (TypeError, ValueError, RuntimeError) as error:
|
|
494
496
|
validation_error = error
|
|
@@ -769,6 +771,8 @@ class Projector:
|
|
|
769
771
|
|
|
770
772
|
def fits(shape):
|
|
771
773
|
points = surface or pixels != self.detector_shape
|
|
774
|
+
# Legacy offsets are one host snapshot; world points expand on the device.
|
|
775
|
+
host_points = points and self._host_world_points(geometry_spec)
|
|
772
776
|
buffers = _pipeline_buffers(self.beam, shape, batch, pixels, points,
|
|
773
777
|
geometry_grad=self._geometry_trainable,
|
|
774
778
|
ray_accumulators=extra_rays, peers=peers or foreign_output)
|
|
@@ -778,7 +782,7 @@ class Projector:
|
|
|
778
782
|
and _reservation_bytes(buffers) <= reservation_budget
|
|
779
783
|
and _largest_buffer_bytes(self.beam, shape, batch, pixels, points) <= allocation_limit
|
|
780
784
|
and max(self._surface_work_buffers(batch, pixels, spec=geometry_spec, streamed=True), default=0) <= allocation_limit
|
|
781
|
-
and _host_geometry_bytes(self.beam, batch, pixels,
|
|
785
|
+
and _host_geometry_bytes(self.beam, batch, pixels, host_points) <= _pipeline._GEOMETRY_WORK_BYTES
|
|
782
786
|
)
|
|
783
787
|
|
|
784
788
|
while not fits(minimum_shape):
|
|
@@ -826,7 +830,11 @@ class Projector:
|
|
|
826
830
|
rank, frames = len(self.volume_shape), 4 if self.beam == "cone" else 3
|
|
827
831
|
capacity = self._surface_capacity(views, pixels)
|
|
828
832
|
columns = 3 if self.beam == "cone" else 2
|
|
829
|
-
|
|
833
|
+
shared = spec is not None and spec.kind == "offsets" and spec.shared
|
|
834
|
+
if shared:
|
|
835
|
+
capacity = math.prod(pixels)
|
|
836
|
+
# Shared offsets pack the frame packets of every view of a job.
|
|
837
|
+
buffers = (_allocation_bytes(2 * (views if shared else 1) * (frames + (3 if rank == 3 else 0)) * rank),
|
|
830
838
|
_allocation_bytes(2 * (columns * capacity + 1)))
|
|
831
839
|
if self._geometry_trainable:
|
|
832
840
|
# Replay one view and a bounded detector row at a time.
|
|
@@ -850,7 +858,17 @@ class Projector:
|
|
|
850
858
|
dtype = torch.float32
|
|
851
859
|
for component in (*frames[1:], offsets):
|
|
852
860
|
dtype = torch.promote_types(dtype, component.dtype)
|
|
853
|
-
return _GeometrySpec("offsets", dtype=dtype)
|
|
861
|
+
return _GeometrySpec("offsets", dtype=dtype, shared=offsets.ndim == len(self.detector_shape) + 1)
|
|
862
|
+
|
|
863
|
+
def _host_world_points(self, spec):
|
|
864
|
+
"""Whether the host geometry budget must count world points of each view.
|
|
865
|
+
|
|
866
|
+
Shared legacy offsets are one host snapshot whose world points are
|
|
867
|
+
composed on the compute device; per-view offsets and geometry
|
|
868
|
+
gradients keep host work proportional to views x pixels.
|
|
869
|
+
"""
|
|
870
|
+
return not (spec is not None and spec.kind == "offsets" and spec.shared
|
|
871
|
+
and not self._geometry_trainable)
|
|
854
872
|
|
|
855
873
|
def _surface_validation_inputs(self):
|
|
856
874
|
"""Stage frame metadata once without expanding surface world points."""
|
|
@@ -884,7 +902,7 @@ class Projector:
|
|
|
884
902
|
buffers = self._surface_work_buffers(batch, pixels, spec=spec)
|
|
885
903
|
return (sum(buffers) <= budget and max(buffers, default=0) <= allocation
|
|
886
904
|
and _reservation_bytes(buffers) <= reservation
|
|
887
|
-
and _host_geometry_bytes(self.beam, batch, pixels,
|
|
905
|
+
and _host_geometry_bytes(self.beam, batch, pixels, self._host_world_points(spec))
|
|
888
906
|
<= _pipeline._GEOMETRY_WORK_BYTES)
|
|
889
907
|
|
|
890
908
|
if not fits():
|
|
@@ -1197,7 +1215,8 @@ class Projector:
|
|
|
1197
1215
|
estimate = size(shape, slots, retained, extra_rays)
|
|
1198
1216
|
reservation = (reserved(shape, slots, retained, extra_rays)
|
|
1199
1217
|
if estimate <= plan.gpu_budget_bytes else (1 << 63) - 1)
|
|
1200
|
-
|
|
1218
|
+
host_points = points and self._host_world_points(spec)
|
|
1219
|
+
host_fits = 2 * _host_geometry_bytes(self.beam, plan.view_chunk_size, pixels, host_points) <= _pipeline._GEOMETRY_WORK_BYTES
|
|
1201
1220
|
if (estimate > plan.gpu_budget_bytes
|
|
1202
1221
|
or reservation > plan.reservation_budget_bytes or not host_fits):
|
|
1203
1222
|
if schedule == "window":
|
|
@@ -1310,6 +1329,9 @@ class Projector:
|
|
|
1310
1329
|
rank, frame_count = len(self.volume_shape), 4 if self.beam == "cone" else 3
|
|
1311
1330
|
count = views.stop - views.start
|
|
1312
1331
|
pixel_shape = tuple(selected.stop - selected.start for selected in pixels)
|
|
1332
|
+
# Offsets sampled here derive from the snapshot and the pixel tile;
|
|
1333
|
+
# offsets supplied by the caller may differ per batch and are not cached.
|
|
1334
|
+
cacheable = offsets is None
|
|
1313
1335
|
if offsets is None:
|
|
1314
1336
|
offsets = _sample_offsets(self.beam, self.detector_shape, self.detector_spacing,
|
|
1315
1337
|
geometry, spec, views, pixels)
|
|
@@ -1324,15 +1346,42 @@ class Projector:
|
|
|
1324
1346
|
frame32 += (True,)
|
|
1325
1347
|
offset32 = ((torch.promote_types(torch.float32, offsets.dtype)
|
|
1326
1348
|
if spec.kind == "offsets" else common) == torch.float32)
|
|
1349
|
+
# Offsets shared by all views are staged once per call and composed
|
|
1350
|
+
# with one launch for all views instead of per view and row segment.
|
|
1351
|
+
shared = spec.kind == "offsets" and offsets.ndim == len(pixel_shape) + 1
|
|
1352
|
+
pixel_count = math.prod(pixel_shape)
|
|
1353
|
+
# One packed frame packet per view: source, center, axes, normal and
|
|
1354
|
+
# float32 cross-product scratch. Shared offsets pack every view of a job.
|
|
1355
|
+
stride = (frame_count + (3 if rank == 3 else 0)) * rank
|
|
1327
1356
|
if "_surface_workspace" not in groups:
|
|
1328
|
-
capacity = self._surface_capacity(count, pixel_shape)
|
|
1357
|
+
capacity = pixel_count if shared else self._surface_capacity(count, pixel_shape)
|
|
1329
1358
|
groups["_surface_workspace"] = (
|
|
1330
|
-
pipe.empty((
|
|
1359
|
+
pipe.empty((count if shared else 1) * stride, dtype=torch.float64),
|
|
1331
1360
|
pipe.empty((3 if rank == 3 else 2) * capacity + 1, dtype=torch.float64), capacity,
|
|
1332
1361
|
)
|
|
1333
1362
|
packed_frames, packed_offsets, capacity = groups["_surface_workspace"]
|
|
1334
1363
|
columns = (0, 1, 2) if rank == 3 else (0, 2)
|
|
1335
1364
|
source, points = targets[0].view(count, rank), targets[1].view(count, -1, rank)
|
|
1365
|
+
# A workspace sized for an earlier, smaller tile keeps the segment path.
|
|
1366
|
+
shared = shared and capacity >= pixel_count
|
|
1367
|
+
if shared and packed_frames.numel() < count * stride:
|
|
1368
|
+
# Earlier launches on the upload stream may still read the smaller buffer.
|
|
1369
|
+
pipe.retain(pipe.event(pipe.upload), packed_frames, None)
|
|
1370
|
+
packed_frames = pipe.empty(count * stride, dtype=torch.float64)
|
|
1371
|
+
groups["_surface_workspace"] = (packed_frames, packed_offsets, capacity)
|
|
1372
|
+
# Every job of a call slices the same offset snapshot: stage it once per
|
|
1373
|
+
# call, pixel tile and workspace. The key holds the snapshot and the
|
|
1374
|
+
# workspace, so neither a later snapshot reusing storage nor a recreated
|
|
1375
|
+
# workspace can match it.
|
|
1376
|
+
if shared and cacheable:
|
|
1377
|
+
staged_key = (geometry[frame_count], packed_offsets, tuple((part.start, part.stop) for part in pixels))
|
|
1378
|
+
restage = not ("_surface_shared_staged" in groups
|
|
1379
|
+
and groups["_surface_shared_staged"][0] is staged_key[0]
|
|
1380
|
+
and groups["_surface_shared_staged"][1] is staged_key[1]
|
|
1381
|
+
and groups["_surface_shared_staged"][2] == staged_key[2])
|
|
1382
|
+
else:
|
|
1383
|
+
restage = shared
|
|
1384
|
+
groups.pop("_surface_shared_staged", None)
|
|
1336
1385
|
with torch.cuda.device(pipe.device), torch.cuda.stream(pipe.upload), cuda.gpus[pipe.device.index]:
|
|
1337
1386
|
if origin is not None:
|
|
1338
1387
|
pipe.upload.wait_stream(origin)
|
|
@@ -1340,7 +1389,51 @@ class Projector:
|
|
|
1340
1389
|
stream = _get_numba_external_stream_for(pipe.upload)
|
|
1341
1390
|
arrays = tuple(TorchCUDABridge.tensor_to_cuda_array(value)
|
|
1342
1391
|
for value in (packed_frames, packed_offsets, source, points))
|
|
1343
|
-
|
|
1392
|
+
if restage:
|
|
1393
|
+
# Pack the float64 columns where the offsets live, then copy each
|
|
1394
|
+
# column into the workspace without a device staging buffer.
|
|
1395
|
+
flat = offsets.detach().reshape(pixel_count, 3)
|
|
1396
|
+
if offsets.is_cuda:
|
|
1397
|
+
for column, original in enumerate(columns):
|
|
1398
|
+
packed_offsets[column * capacity:column * capacity + pixel_count].copy_(flat[:, original])
|
|
1399
|
+
else:
|
|
1400
|
+
host = flat[:, list(columns)].T.to(dtype=torch.float64).contiguous()
|
|
1401
|
+
for column in range(len(columns)):
|
|
1402
|
+
_pipeline._copy(packed_offsets[column * capacity:column * capacity + pixel_count],
|
|
1403
|
+
host[column], pipe.stats)
|
|
1404
|
+
pipe.retain(pipe.event(pipe.upload), host, packed_offsets)
|
|
1405
|
+
if cacheable:
|
|
1406
|
+
groups["_surface_shared_staged"] = staged_key
|
|
1407
|
+
if shared:
|
|
1408
|
+
# Stage the frame packets of every view in one transfer and
|
|
1409
|
+
# compose all views with one launch; grid row y composes view y.
|
|
1410
|
+
block = packed_frames[:count * stride].view(count, stride)
|
|
1411
|
+
packet = torch.zeros((count, stride), dtype=torch.float64)
|
|
1412
|
+
for index, value in enumerate(frames):
|
|
1413
|
+
packet[:, index * rank:(index + 1) * rank].copy_(value[views].detach())
|
|
1414
|
+
_pipeline._copy(block, packet, pipe.stats)
|
|
1415
|
+
pipe.retain(pipe.event(pipe.upload), packet, packed_frames)
|
|
1416
|
+
if rank == 3:
|
|
1417
|
+
normal = block[:, 4 * rank:5 * rank]
|
|
1418
|
+
axis_u, axis_v = block[:, 2 * rank:3 * rank], block[:, 3 * rank:4 * rank]
|
|
1419
|
+
if frame32[2] and frame32[3]:
|
|
1420
|
+
scratch = block.view(torch.float32)[:, 10 * rank:13 * rank]
|
|
1421
|
+
scratch[:, :rank].copy_(axis_u)
|
|
1422
|
+
scratch[:, rank:2 * rank].copy_(axis_v)
|
|
1423
|
+
torch.linalg.cross(scratch[:, :rank], scratch[:, rank:2 * rank], dim=1,
|
|
1424
|
+
out=scratch[:, 2 * rank:])
|
|
1425
|
+
normal.copy_(scratch[:, 2 * rank:])
|
|
1426
|
+
else:
|
|
1427
|
+
torch.linalg.cross(axis_u, axis_v, dim=1, out=normal)
|
|
1428
|
+
for first in range(0, count, 65535):
|
|
1429
|
+
rows = min(65535, count - first)
|
|
1430
|
+
compose_surface[((pixel_count + 127) // 128, rows), 128, stream](
|
|
1431
|
+
TorchCUDABridge.tensor_to_cuda_array(block[first:first + rows].reshape(-1)),
|
|
1432
|
+
*arrays[1:], first, 0, pixel_count, capacity, rank,
|
|
1433
|
+
self.beam == "parallel", frame32, offset32,
|
|
1434
|
+
tuple(centre) + (0.,) * (3 - rank), self.voxel_spacing,
|
|
1435
|
+
)
|
|
1436
|
+
for local in range(0 if shared else count):
|
|
1344
1437
|
packet = torch.cat([value[views.start + local].detach().to(device="cpu", dtype=torch.float64)
|
|
1345
1438
|
for value in frames])
|
|
1346
1439
|
_pipeline._copy(packed_frames[:frame_count * rank], packet, pipe.stats)
|
|
@@ -2139,6 +2232,7 @@ class Projector:
|
|
|
2139
2232
|
slot.finish()
|
|
2140
2233
|
pipe.reclaim_output()
|
|
2141
2234
|
del local_groups["_surface_workspace"]
|
|
2235
|
+
local_groups.pop("_surface_shared_staged", None)
|
|
2142
2236
|
reduced = surface_batch_vjp(
|
|
2143
2237
|
self.beam, self.detector_shape, self.detector_spacing, inputs, offsets,
|
|
2144
2238
|
global_slice, pixels, self.voxel_spacing, pipe.device, parts,
|
diffct/surfaces.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: diffct
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.2
|
|
4
4
|
Summary: Differentiable CT projection and reconstruction: precision modes, streaming, distributed execution and geometry gradients
|
|
5
5
|
Project-URL: Homepage, https://github.com/sypsyp97/diffct
|
|
6
6
|
Project-URL: Documentation, https://sypsyp97.github.io/diffct/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
diffct/__init__.py,sha256=
|
|
1
|
+
diffct/__init__.py,sha256=saZamKDQ9CLB1HoVwIeZG6HMWNnbZbdNXtaT_MIpDLw,2965
|
|
2
2
|
diffct/_solver_state.py,sha256=DKfrgrKcSpx3YetbF8hjA2yzMzJag-iHVpMZGup_dLw,16258
|
|
3
3
|
diffct/_solver_tv.py,sha256=9nE1DVRzdUlcfd-OQQ86GpntX6CcaVvsSVWK-L4uR_I,5783
|
|
4
4
|
diffct/analytical.py,sha256=u2PUp8GTNHoqwP2S4s0y9xNjiVBLfnYMULuuThcWmdU,33245
|
|
@@ -9,7 +9,7 @@ diffct/constants.py,sha256=oy3QNC6an43Ogdn1m81znz1-EMp9GeinVf2wkGYiXfE,3160
|
|
|
9
9
|
diffct/differentiable.py,sha256=cFirhqXRpUemL7y7Sl_0uqZKGJNj__ptQEcTwYNKzys,1879
|
|
10
10
|
diffct/distribution.py,sha256=iLC3BgAQ9lOkndbEOvNB_j_UaA2Ug9w1KiTUHkOzG8o,9982
|
|
11
11
|
diffct/geometry.py,sha256=iwWXQksIJGOPSuNWJ8tImWj6yPZCC71G3RBh2VDVylY,42581
|
|
12
|
-
diffct/operators.py,sha256=
|
|
12
|
+
diffct/operators.py,sha256=QH7TedxIc35616bn0PwgaLDllH3Xt3aadFzIpbe8y0M,158446
|
|
13
13
|
diffct/pipeline.py,sha256=jp7AvDeQQOME3OPkO9XTcjWFLvOLrv8wczhAf1jcklM,10891
|
|
14
14
|
diffct/projectors.py,sha256=0oAFSA5mycN59dayTAWK2W7o96MP8oLuVp2V9X-1388,45334
|
|
15
15
|
diffct/reconstruction.py,sha256=KdDrpxQ31ufyUlL_bkzvDzaTcrK4BB97tnJ04ZfVfos,29368
|
|
@@ -17,7 +17,7 @@ diffct/solvers.py,sha256=ZroVUlhHOE2IAXB0clw9hUCvomrFSgYdGRQWwnSFZxs,17297
|
|
|
17
17
|
diffct/storage.py,sha256=EdcFTzRtEAPRvAZhRPrSf8gRNF5o_JVbPLE8eKPhNdY,4153
|
|
18
18
|
diffct/streaming.py,sha256=2LB5xpl6vIJE1kXMBtQbG0OVxlutEEqXLBrcvlzBHl8,6086
|
|
19
19
|
diffct/surface_gradients.py,sha256=LeM_eDY1Alv604SdiPUx2CWFzr-J9NqCEcJ_ovVFGa8,6290
|
|
20
|
-
diffct/surfaces.py,sha256=
|
|
20
|
+
diffct/surfaces.py,sha256=z_0O98H7eMdSd9qrgUtcg5hBaE643s6sG_AjnH0Dyhw,7935
|
|
21
21
|
diffct/utils.py,sha256=cih3GyZ33gnOrTW7O8r2_aXIP2a-rFjOMuAPuvHeQ4I,14794
|
|
22
22
|
diffct/kernels/__init__.py,sha256=YNdsqAkexN25pI7QaHy4phAnQNICQ7tO8K8S1zt7TnI,1899
|
|
23
23
|
diffct/kernels/analytical_transpose.py,sha256=D4vr_ZFPrjFeQyhoDqTyijbkTY-g-sHJet52ho-cbkQ,9187
|
|
@@ -25,9 +25,9 @@ diffct/kernels/cone_beam.py,sha256=jKlbqQPHhrng-pTbyMFx2b2l0pdQIMiaaB5hj6ayPQI,2
|
|
|
25
25
|
diffct/kernels/fan_beam.py,sha256=NPha0Wl-_ayOdS6kAoXJngQffsUczp3rRnojXZ-F1y8,17556
|
|
26
26
|
diffct/kernels/geometry_vjp.py,sha256=YiPMdsuXINLu1oFhPcSbG1LW2P3WSfeJeEvHNLrmQzU,22008
|
|
27
27
|
diffct/kernels/parallel_beam.py,sha256=tCiSx9W8nO4mn8DVyRuK4E0Ry8q38g3pAUACab0UYVU,17143
|
|
28
|
-
diffct/kernels/surface_geometry.py,sha256=
|
|
29
|
-
diffct-2.4.
|
|
30
|
-
diffct-2.4.
|
|
31
|
-
diffct-2.4.
|
|
32
|
-
diffct-2.4.
|
|
33
|
-
diffct-2.4.
|
|
28
|
+
diffct/kernels/surface_geometry.py,sha256=eXD_aqW0e6vBMsS0R5oZ-cyedK_wrX7RDjFfiM0HIg4,5602
|
|
29
|
+
diffct-2.4.2.dist-info/METADATA,sha256=0NsA9YkbCMTwARCVFc1RCqY53W8-zwGjK6ZNOlbcTgU,17661
|
|
30
|
+
diffct-2.4.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
31
|
+
diffct-2.4.2.dist-info/licenses/LICENSE,sha256=a1fjzGs44LpDnV_LmA2Tzp8z-eJKoNVVdLOnh-F6J6E,11344
|
|
32
|
+
diffct-2.4.2.dist-info/licenses/NOTICE,sha256=du9I2sDF5IDfiY9vF64WWzMJh-MarpVXyLfWQmhbvmI,714
|
|
33
|
+
diffct-2.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|